diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 765fa76d5e..3c9a572374 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -265,7 +265,7 @@ jobs: - name: Upload executable if: steps.build.outputs.viewer_app - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: "${{ steps.build.outputs.artifact }}-app" path: | @@ -275,13 +275,13 @@ jobs: # artifact for that too. - name: Upload symbol file if: steps.build.outputs.symbolfile - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: "${{ steps.build.outputs.artifact }}-symbols" path: ${{ steps.build.outputs.symbolfile }} - name: Upload metadata - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: "${{ steps.build.outputs.artifact }}-metadata" # emitted by build.sh, possibly multiple lines @@ -289,7 +289,7 @@ jobs: ${{ steps.build.outputs.metadata }} - name: Upload physics package - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 # should only be set for viewer-private if: matrix.configuration == 'Release' && steps.build.outputs.physicstpv with: @@ -369,13 +369,13 @@ jobs: runs-on: ubuntu-latest steps: - name: Download viewer exe - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: Windows-app path: _artifacts - name: Download Windows Symbols if: env.BUGSPLAT_USER && env.BUGSPLAT_PASS - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: Windows-symbols - name: Extract viewer pdb @@ -405,7 +405,7 @@ jobs: steps: - name: Download Mac Symbols if: env.BUGSPLAT_USER && env.BUGSPLAT_PASS - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: macOS-symbols - name: Post Mac symbols @@ -425,11 +425,11 @@ jobs: runs-on: ubuntu-latest if: needs.setup.outputs.release_run steps: - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: pattern: "*-installer" - - uses: actions/download-artifact@v5 + - uses: actions/download-artifact@v6 with: pattern: "*-metadata" diff --git a/.github/workflows/build_viewer.yml b/.github/workflows/build_viewer.yml index 4d74d5ba3d..1f86943641 100644 --- a/.github/workflows/build_viewer.yml +++ b/.github/workflows/build_viewer.yml @@ -482,7 +482,7 @@ jobs: dumpSyms: false - name: Publish artifacts if: runner.os == 'Windows' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ env.FS_RELEASE_TYPE }}-${{ matrix.os }}-${{ matrix.variant }}-${{ matrix.grid }}-artifacts.zip path: | @@ -491,7 +491,7 @@ jobs: - name: publish Linux artifacts if: runner.os == 'Linux' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ env.FS_RELEASE_TYPE }}-${{ matrix.os }}-${{ matrix.variant }}-${{matrix.grid}}-artifacts.zip path: | @@ -500,7 +500,7 @@ jobs: - name: publish MacOS artifacts if: runner.os == 'macOS' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ env.FS_RELEASE_TYPE }}-${{ matrix.os }}-${{ matrix.variant }}-${{matrix.grid}}-artifacts.zip path: | @@ -529,7 +529,7 @@ jobs: run: pip install discord-webhook - name: Download artifacts - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 id: download with: path: to_deploy @@ -581,7 +581,7 @@ jobs: # Upload Build Info Artifact (note that this file is expected to be identical for each matrix run, so items like OS and VARIANT cannot be in this file.) - name: Upload Tag Info - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: build_info path: build_info.json diff --git a/.github/workflows/sign.yml b/.github/workflows/sign.yml index 828f8fd6ad..d49d51980f 100644 --- a/.github/workflows/sign.yml +++ b/.github/workflows/sign.yml @@ -62,7 +62,7 @@ jobs: files_json=$(ls setup_exe_files | jq -R . | jq -s -c .) echo "setup_files=$files_json" >> $GITHUB_OUTPUT - name: Upload Setup.exe Files - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: setup-exe-files path: setup_exe_files/ @@ -86,7 +86,7 @@ jobs: shell: bash - name: Download Setup.exe Files Artifact - uses: actions/download-artifact@v5 + uses: actions/download-artifact@v6 with: name: setup-exe-files path: setup_exe_files @@ -97,7 +97,7 @@ jobs: shell: bash # - name: Upload unsigned artifact # id: upload-unsigned-artifact - # uses: actions/upload-artifact@v4 + # uses: actions/upload-artifact@v5 # with: # name: unsigned-artifact-${{ matrix.file }} # path: to_sign/${{ matrix.file }} @@ -115,7 +115,7 @@ jobs: # wait-for-completion: true # output-artifact-directory: 'application-signed' - name: Azure Trusted Signing - uses: azure/trusted-signing-action@v0.5.9 + uses: azure/trusted-signing-action@v0.5.10 with: azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} @@ -129,14 +129,14 @@ jobs: files-folder-filter: exe # - name: Upload signed artifact - # uses: actions/upload-artifact@v4 + # uses: actions/upload-artifact@v5 # with: # name: signed-artifact-${{ matrix.file }} # path: ./application-signed # - name: Upload unsigned artifact - name: Upload signed artifact id: upload-unsigned-artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: unsigned-artifact-${{ matrix.file }} path: to_sign\${{ matrix.file }} diff --git a/autobuild.xml b/autobuild.xml index c00cf38d65..baebc62c3a 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -720,11 +720,11 @@ archive hash - 126e0fa4c16dfd433c9fb7d1d242da98f213d933 + 2c81fa7aa03b427088ab54ce3b71088a0003126b hash_algorithm sha1 url - https://github.com/secondlife/dullahan/releases/download/v1.24.0-CEF_139.0.40/dullahan-1.24.0.202510081737_139.0.40_g465474a_chromium-139.0.7258.139-darwin64-18353103947.tar.zst + https://github.com/secondlife/dullahan/releases/download/v1.26.0-CEF_139.0.40/dullahan-1.26.0.202510161627_139.0.40_g465474a_chromium-139.0.7258.139-darwin64-18568015445.tar.zst name darwin64 @@ -734,11 +734,11 @@ archive hash - 209d031ae67bc66d8e8f8509c71d259014c65ceb + 8e06d060729250c5bfcb993c8f818f36ea17de16 hash_algorithm sha1 url - https://github.com/secondlife/dullahan/releases/download/v1.14.0-r3/dullahan-1.14.0.202408091637_118.4.1_g3dd6078_chromium-118.0.5993.54-linux64-10322607516.tar.zst + https://github.com/secondlife/dullahan/releases/download/v1.26.0-CEF_139.0.40/dullahan-1.26.0.202510161627_139.0.40_g465474a_chromium-139.0.7258.139-linux64-18568015445.tar.zst name linux64 @@ -748,11 +748,11 @@ archive hash - 20de62c9e57d9e6539c1e2437ec4b46c3ca237bc + 7ca6db37f019b47e230c0861607c546d45535367 hash_algorithm sha1 url - https://github.com/secondlife/dullahan/releases/download/v1.24.0-CEF_139.0.40/dullahan-1.24.0.202510081738_139.0.40_g465474a_chromium-139.0.7258.139-windows64-18353103947.tar.zst + https://github.com/secondlife/dullahan/releases/download/v1.26.0-CEF_139.0.40/dullahan-1.26.0.202510161628_139.0.40_g465474a_chromium-139.0.7258.139-windows64-18568015445.tar.zst name windows64 @@ -765,7 +765,7 @@ copyright Copyright (c) 2017, Linden Research, Inc. version - 1.24.0.202510081737_139.0.40_g465474a_chromium-139.0.7258.139 + 1.26.0.202510161627_139.0.40_g465474a_chromium-139.0.7258.139 name dullahan description diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake index 4bdec91c28..5e92f59964 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -197,6 +197,8 @@ if (LINUX) set(CMAKE_EXE_LINKER_FLAGS "-Wl,--no-keep-memory -Wl,--build-id -Wl,-rpath,'$ORIGIN:$ORIGIN/../lib' -Wl,--exclude-libs,ALL") set(CMAKE_CXX_FLAGS_DEBUG "-fno-inline ${CMAKE_CXX_FLAGS_DEBUG}") + # Prefer static libraries on Linux + set(CMAKE_FIND_LIBRARY_SUFFIXES ".so;.a") endif (LINUX) if (DARWIN) diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index 9f93fbc039..73e8d7184a 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -113,6 +113,9 @@ if(WINDOWS) elseif (MSVC_VERSION GREATER_EQUAL 1930 AND MSVC_VERSION LESS 1950) # Visual Studio 2022 set(MSVC_VER 140) set(MSVC_TOOLSET_VER 143) + elseif (MSVC_VERSION GREATER_EQUAL 1950 AND MSVC_VERSION LESS 1970) # Visual Studio 2026 + set(MSVC_VER 140) + set(MSVC_TOOLSET_VER 145) else (MSVC80) MESSAGE(WARNING "New MSVC_VERSION ${MSVC_VERSION} of MSVC: adapt Copy3rdPartyLibs.cmake") endif (MSVC80) @@ -150,6 +153,7 @@ if(WINDOWS) msvcp${MSVC_VER}_atomic_wait.dll msvcp${MSVC_VER}_codecvt_ids.dll #msvcr${MSVC_VER}.dll # Can't build with older VS versions anyway - no need trying to copy this file + vccorlib${MSVC_VER}.dll vcruntime${MSVC_VER}.dll vcruntime${MSVC_VER}_1.dll vcruntime${MSVC_VER}_threads.dll diff --git a/indra/cmake/GStreamer10Plugin.cmake b/indra/cmake/GStreamer10Plugin.cmake index cb83d09d8a..875ebfc194 100644 --- a/indra/cmake/GStreamer10Plugin.cmake +++ b/indra/cmake/GStreamer10Plugin.cmake @@ -1,5 +1,6 @@ # -*- cmake -*- include(Prebuilt) +include(GLIB) # add glib includes if (NOT LINUX) return() endif() @@ -10,4 +11,12 @@ use_system_binary(gstreamer10) use_prebuilt_binary(gstreamer10) use_prebuilt_binary(libxml2) -target_include_directories( ll::gstreamer SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/include/gstreamer-1.0) +# make sure glib is found +set(_gstreamer10_include_dirs + ${LIBS_PREBUILT_DIR}/include/gstreamer-1.0 + ${GLIB_INCLUDE_DIRS} +) +list(REMOVE_ITEM _gstreamer10_include_dirs "") +# + +target_include_directories( ll::gstreamer SYSTEM INTERFACE ${_gstreamer10_include_dirs}) diff --git a/indra/cmake/LLKDU.cmake b/indra/cmake/LLKDU.cmake index 65268e8945..8a86284421 100644 --- a/indra/cmake/LLKDU.cmake +++ b/indra/cmake/LLKDU.cmake @@ -32,7 +32,7 @@ if (USE_KDU) find_library(KDU_LIBRARY NAMES libkdu.a - libkdu${ND_KDU_SUFFIX} // FS-specific naming + libkdu${ND_KDU_SUFFIX}.a // FS-specific naming PATHS "${ARCH_PREBUILT_DIRS_RELEASE}" REQUIRED NO_DEFAULT_PATH) endif (WINDOWS) diff --git a/indra/cmake/LLPrimitive.cmake b/indra/cmake/LLPrimitive.cmake index 2699f8efee..d3dc5f981c 100644 --- a/indra/cmake/LLPrimitive.cmake +++ b/indra/cmake/LLPrimitive.cmake @@ -56,3 +56,11 @@ find_library(COLLADADOM_LIBRARY PATHS "${ARCH_PREBUILT_DIRS_RELEASE}" REQUIRED NO_DEFAULT_PATH) target_link_libraries(ll::colladadom INTERFACE ${COLLADADOM_LIBRARY} ll::boost ll::libxml ll::minizip-ng) + +# GLIB uses pcre, so we need to keep it for Linux builds +if (LINUX) + add_library( ll::pcre INTERFACE IMPORTED ) + use_prebuilt_binary(pcre) + target_link_libraries( ll::pcre INTERFACE pcrecpp pcre ) +endif () +# \ No newline at end of file diff --git a/indra/llaudio/llaudioengine.h b/indra/llaudio/llaudioengine.h index 2a2355b019..aa0617f936 100644 --- a/indra/llaudio/llaudioengine.h +++ b/indra/llaudio/llaudioengine.h @@ -39,6 +39,10 @@ #include "llframetimer.h" #include "llassettype.h" #include "llextendedstatus.h" +// [FIRE-36022] - Removing my USB headset crashes entire viewer +// Need to include for audio device mutex shared with other audio/voice systems. +#include "inlinemutexs.h" +// [FIRE-36022] #include "lllistener.h" diff --git a/indra/llaudio/llaudioengine_fmodstudio.cpp b/indra/llaudio/llaudioengine_fmodstudio.cpp index e9996b93e0..7daaea1c76 100644 --- a/indra/llaudio/llaudioengine_fmodstudio.cpp +++ b/indra/llaudio/llaudioengine_fmodstudio.cpp @@ -44,6 +44,9 @@ #include "sound_ids.h" +// [FIRE-36022] - Removing my USB headset crashes entire viewer +using namespace std::chrono_literals; // Needed for shared timed mutex to use time +// [FIRE-36022] constexpr U32 EXTRA_SOUND_CHANNELS = 10; FMOD_RESULT F_CALL windCallback(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels); @@ -113,8 +116,14 @@ static void set_device(FMOD::System* system, const LLUUID& device_uuid) } } -FMOD_RESULT F_CALL systemCallback(FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK_TYPE type, void *commanddata1, void *commanddata2, void* userdata) +// [FIRE-36022] - Removing my USB headset crashes entire viewer +// According to FMOD, not having this method static is very bad. +//FMOD_RESULT F_CALL systemCallback(FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK_TYPE type, void *commanddata1, void *commanddata2, void* userdata) +static FMOD_RESULT F_CALL systemCallback(FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK_TYPE type, void *commanddata1, void *commanddata2, void* userdata) { + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid + { +// [FIRE-36022] FMOD::System* sys = (FMOD::System*)system; LLAudioEngine_FMODSTUDIO* audio_engine = (LLAudioEngine_FMODSTUDIO*)userdata; @@ -124,6 +133,17 @@ FMOD_RESULT F_CALL systemCallback(FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK_TYPE LL_DEBUGS("FMOD") << "FMOD system callback FMOD_SYSTEM_CALLBACK_DEVICELISTCHANGED" << LL_ENDL; if (sys && audio_engine) { + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + LL_INFOS() << "Could not access the audio device mutex, trying again later" << LL_ENDL; + return FMOD_OK; // Could be a FMOD_ERR_ALREADY_LOCKED; + } + // [FIRE-36022] set_device(sys, audio_engine->getSelectedDeviceUUID()); audio_engine->OnOutputDeviceListChanged(audio_engine->getDevices()); } @@ -132,6 +152,34 @@ FMOD_RESULT F_CALL systemCallback(FMOD_SYSTEM *system, FMOD_SYSTEM_CALLBACK_TYPE break; } return FMOD_OK; + // [FIRE-36022] - Removing my USB headset crashes entire viewer + } + // There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL; + return FMOD_ERR_ALREADY_LOCKED; + } + else if (e.code() == std::errc::operation_not_permitted) + { + LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL; + return FMOD_ERR_ALREADY_LOCKED; + } + else + { + LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL; + } + + return FMOD_ERR_ALREADY_LOCKED; + } + catch (const std::exception& e) + { + LL_WARNS() << "Exception FMOD: " << " " << e.what() << LL_ENDL; + return FMOD_ERR_ALREADY_LOCKED; + } + // [FIRE-36022] } LLAudioEngine_FMODSTUDIO::LLAudioEngine_FMODSTUDIO(bool enable_profiler, U32 resample_method) @@ -311,6 +359,11 @@ bool LLAudioEngine_FMODSTUDIO::init(void* userdata, const std::string &app_title LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init() FMOD Studio initialized correctly" << LL_ENDL; FMOD_ADVANCEDSETTINGS settings_dump = { }; + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // With the FMOD debug library used, turns out this object needs to have a size assigned to it otherwise it will fail. + // So the viewer never got any advanced settings for the info below. + settings_dump.cbSize = sizeof(FMOD_ADVANCEDSETTINGS); + // [FIRE-36022] mSystem->getAdvancedSettings(&settings_dump); LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): resampler=" << settings_dump.resamplerMethod << " bytes" << LL_ENDL; @@ -373,7 +426,47 @@ LLAudioEngine_FMODSTUDIO::output_device_map_t LLAudioEngine_FMODSTUDIO::getDevic void LLAudioEngine_FMODSTUDIO::setDevice(const LLUUID& device_uuid) { mSelectedDeviceUUID = device_uuid; + // [FIRE-36022] - Removing my USB headset crashes entire viewer + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid + { + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + LL_INFOS() << "Could not access the audio device mutex, trying again later" << LL_ENDL; + return; + } + // [FIRE-36022] set_device(mSystem, device_uuid); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + } + // There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL; + } + else if (e.code() == std::errc::operation_not_permitted) + { + LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL; + } + else + { + LL_WARNS() << "Exception FMOD: " << e.code() << " " << e.what() << LL_ENDL; + } + + return; + } + catch (const std::exception& e) + { + LL_WARNS() << "Exception FMOD: " << " " << e.what() << LL_ENDL; + return; + } + // [FIRE-36022] + } std::string LLAudioEngine_FMODSTUDIO::getDriverName(bool verbose) diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 0d88dec885..1ba315b11a 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -122,6 +122,7 @@ set(llcommon_HEADER_FILES fsyspath.h function_types.h indra_constants.h + inlinemutexs.h lazyeventapi.h linden_common.h llalignedarray.h diff --git a/indra/llcommon/inlinemutexs.h b/indra/llcommon/inlinemutexs.h new file mode 100644 index 0000000000..3b8ab5c4ef --- /dev/null +++ b/indra/llcommon/inlinemutexs.h @@ -0,0 +1,39 @@ +/** +* @file inlinemutexs.h +* @brief Declaration of inline mutexs +* @author minerjr@firestorm +* + * $LicenseInfo:firstyear=2025&license=fsviewerlgpl$ + * Phoenix Firestorm Viewer Source Code + * Copyright (C) 2025, Minerjr + * + * 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 + * + * The Phoenix Firestorm Project, Inc., 1831 Oakwood Drive, Fairmont, Minnesota 56031-3225 USA + * http://www.firestormviewer.org + * $/LicenseInfo$ +*/ + +#ifndef INLINE_MUTEXS_HEADER +#define INLINE_MUTEXS_HEADER +#include +#include +#include + +// Audio device mutex to be shared between audio engine and Voice systems to +// syncronize on when audio hardware accessed for disconnected/connecting hardware +// Uses Timed Mutex so as to not lockup the threads forever. +inline std::timed_mutex gAudioDeviceMutex; +#endif diff --git a/indra/llcommon/lltreeiterators.h b/indra/llcommon/lltreeiterators.h index cef501b987..22b21af955 100644 --- a/indra/llcommon/lltreeiterators.h +++ b/indra/llcommon/lltreeiterators.h @@ -238,7 +238,22 @@ private: mParents.pop_back(); } /// equality - bool equal(const self_type& that) const { return this->mParents == that.mParents; } + bool equal(const self_type& that) const + { + if (this == &that) + { + return true; + } + + const bool empty = mParents.empty(); + const bool that_empty = that.mParents.empty(); + if (empty || that_empty) + { + return empty && that_empty; + } + + return mParents == that.mParents; + } /// implement dereference/indirection operators ptr_type& dereference() const { return const_cast(mParents.back()); } @@ -373,7 +388,22 @@ private: mSkipChildren = false; } /// equality - bool equal(const self_type& that) const { return this->mPending == that.mPending; } + bool equal(const self_type& that) const + { + if (this == &that) + { + return true; + } + + const bool empty = mPending.empty(); + const bool that_empty = that.mPending.empty(); + if (empty || that_empty) + { + return empty && that_empty; + } + + return mPending == that.mPending; + } /// implement dereference/indirection operators ptr_type& dereference() const { return const_cast(mPending.back()); } @@ -474,7 +504,22 @@ private: makeCurrent(); } /// equality - bool equal(const self_type& that) const { return this->mPending == that.mPending; } + bool equal(const self_type& that) const + { + if (this == &that) + { + return true; + } + + const bool empty = mPending.empty(); + const bool that_empty = that.mPending.empty(); + if (empty || that_empty) + { + return empty && that_empty; + } + + return mPending == that.mPending; + } /// implement dereference/indirection operators ptr_type& dereference() const { return const_cast(mPending.back().first); } @@ -611,7 +656,22 @@ private: mPending.push_back(*chi); } /// equality - bool equal(const self_type& that) const { return this->mPending == that.mPending; } + bool equal(const self_type& that) const + { + if (this == &that) + { + return true; + } + + const bool empty = mPending.empty(); + const bool that_empty = that.mPending.empty(); + if (empty || that_empty) + { + return empty && that_empty; + } + + return mPending == that.mPending; + } /// implement dereference/indirection operators ptr_type& dereference() const { return const_cast(mPending.front()); } diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index afcbafd249..9b8ce58cbd 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -1131,7 +1131,13 @@ bool LLDAELoader::OpenFile(const std::string& filename) LLModel* mdl = *i; if(mdl->getStatus() != LLModel::NO_ERRORS) { - setLoadState(ERROR_MODEL + mdl->getStatus()) ; + // Fix deprecated arithmetic between different enum types (ERROR_MODEL + EModelStatus) + // Ugly fix. could use a helper instead but its only called in two places. + // setLoadState(ERROR_MODEL + mdl->getStatus()); + setLoadState( + static_cast( + static_cast(ERROR_MODEL) + static_cast(mdl->getStatus()))); + // return false; //abort } diff --git a/indra/llui/llaccordionctrl.cpp b/indra/llui/llaccordionctrl.cpp index 1a64c2699d..2cd394476e 100644 --- a/indra/llui/llaccordionctrl.cpp +++ b/indra/llui/llaccordionctrl.cpp @@ -303,11 +303,8 @@ void LLAccordionCtrl::ctrlSetLeftTopAndSize(LLView* panel, S32 left, S32 top, S3 return; LLRect panel_rect = panel->getRect(); panel_rect.setLeftTopAndSize( left, top, width, height); - if (panel->getRect() != panel_rect) - { - panel->reshape( width, height, 1); - panel->setRect(panel_rect); - } + panel->reshape( width, height, 1); + panel->setRect(panel_rect); } void LLAccordionCtrl::ctrlShiftVertical(LLView* panel, S32 delta) diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp index 3bb0d3d947..31d1bbf839 100644 --- a/indra/llwebrtc/llwebrtc.cpp +++ b/indra/llwebrtc/llwebrtc.cpp @@ -27,6 +27,11 @@ #include "llwebrtc_impl.h" #include #include +// [FIRE-36022] - Removing my USB headset crashes entire viewer +// Needed for accessing the inline timed mutex for accessing audio hardware. +#include +#include +// [FIRE-36022] #include "api/audio_codecs/audio_decoder_factory.h" #include "api/audio_codecs/audio_encoder_factory.h" @@ -39,6 +44,14 @@ #include "modules/audio_mixer/audio_mixer_impl.h" #include "api/environment/environment_factory.h" +// [FIRE-36022] - Removing my USB headset crashes entire viewer +// Audio device mutex to be shared between audio engine and Voice systems to +// syncronize on when audio hardware accessed for disconnected/connecting hardware +// Uses Timed Mutex so as to not lockup the threads forever. +inline std::timed_mutex gAudioDeviceMutex; +// Need to use to access the 3 second timeout for the lock. +using namespace std::chrono_literals; +// [FIRE-36022] namespace llwebrtc { #if WEBRTC_WIN @@ -269,7 +282,11 @@ void LLWebRTCImpl::init() webrtc::InitializeSSL(); // Normal logging is rather spammy, so turn it off. + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Turn on more verbose logging as we are looking for crashes. webrtc::LogMessage::LogToDebug(webrtc::LS_NONE); + //webrtc::LogMessage::LogToDebug(webrtc::LS_VERBOSE); + // [FIRE-36022] webrtc::LogMessage::SetLogToStderr(true); webrtc::LogMessage::AddLogToStream(mLogSink, webrtc::LS_VERBOSE); @@ -463,8 +480,24 @@ void LLWebRTCImpl::unsetDevicesObserver(LLWebRTCDevicesObserver *observer) // must be run in the worker thread. void LLWebRTCImpl::workerDeployDevices() { + // [FIRE-36022] - Removing my USB headset crashes entire viewer + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid + { + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + return; + } + // [FIRE-36022] if (!mDeviceModule) { + // [FIRE-36022] + // If the device is not avaiable, then make sure the flag for the WebRTC updated devices flag is turned off for the co-routine + gWebRTCUpdateDevices = false; + // [FIRE-36022] - Removing my USB headset crashes entire viewer return; } @@ -489,6 +522,10 @@ void LLWebRTCImpl::workerDeployDevices() } } + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Flag the device is being interacted with for the Co-routine in case something goes wrong. + gWebRTCUpdateDevices = true; + // [FIRE-36022] mDeviceModule->StopPlayout(); mDeviceModule->ForceStopRecording(); #if WEBRTC_WIN @@ -552,6 +589,10 @@ void LLWebRTCImpl::workerDeployDevices() { mDeviceModule->StartPlayout(); } + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Finally signal to the co-routine everyting is OK. + gWebRTCUpdateDevices = false; + // [FIRE-36022] mSignalingThread->PostTask( [this] { @@ -572,6 +613,38 @@ void LLWebRTCImpl::workerDeployDevices() mWorkerThread->PostTask([this] { workerDeployDevices(); }); } }); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + } + // There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + // Another thead may have alreayd called this method + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else if (e.code() == std::errc::operation_not_permitted) + { + // This should not be reached + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else + { + // Log any other message + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + catch (const std::exception& e) + { + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + // [FIRE-36022] } void LLWebRTCImpl::setCaptureDevice(const std::string &id) @@ -596,11 +669,27 @@ void LLWebRTCImpl::setRenderDevice(const std::string &id) // updateDevices needs to happen on the worker thread. void LLWebRTCImpl::updateDevices() { + // [FIRE-36022] - Removing my USB headset crashes entire viewer + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid + { + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + return; + } + // [FIRE-36022] if (!mDeviceModule) { return; } + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Flag the device is being interacted with for the Co-routine in case something goes wrong. + gWebRTCUpdateDevices = true; + // [FIRE-36022] int16_t renderDeviceCount = mDeviceModule->PlayoutDevices(); mPlayoutDeviceList.clear(); @@ -637,10 +726,46 @@ void LLWebRTCImpl::updateDevices() mRecordingDeviceList.emplace_back(name, guid); } + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Flag the device is no longer being interacted with for the Co-routine in case something goes wrong. + gWebRTCUpdateDevices = false; + // [FIRE-36022] for (auto &observer : mVoiceDevicesObserverList) { observer->OnDevicesChanged(mPlayoutDeviceList, mRecordingDeviceList); } + // [FIRE-36022] - Removing my USB headset crashes entire viewer + } + // There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + // Another thead may have alreayd called this method + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else if (e.code() == std::errc::operation_not_permitted) + { + // This should not be reached + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else + { + // Log any other message + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + catch (const std::exception& e) + { + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + // [FIRE-36022] } void LLWebRTCImpl::OnDevicesUpdated() @@ -745,7 +870,56 @@ void LLWebRTCImpl::intSetMute(bool mute, int delay_ms) { if (mDeviceModule) { + // [FIRE-36022] - Removing my USB headset crashes entire viewer + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is + // invalid + { + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + return; + } + // Flag the device is being interacted with for the Co-routine in case something goes wrong. + gWebRTCUpdateDevices = true; + // [FIRE-36022] mDeviceModule->ForceStopRecording(); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Finally signal to the co-routine everyting is OK. + gWebRTCUpdateDevices = false; + } + // There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + // Another thead may have alreayd called this method + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else if (e.code() == std::errc::operation_not_permitted) + { + // This should not be reached + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else + { + // Log any other message + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + catch (const std::exception& e) + { + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + // [FIRE-36022] } }, webrtc::TimeDelta::Millis(delay_ms)); @@ -757,8 +931,57 @@ void LLWebRTCImpl::intSetMute(bool mute, int delay_ms) { if (mDeviceModule) { + // [FIRE-36022] - Removing my USB headset crashes entire viewer + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is + // invalid + { + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + return; + } + // Flag the device is being interacted with for the Co-routine in case something goes wrong. + gWebRTCUpdateDevices = true; + // [FIRE-36022] mDeviceModule->InitRecording(); mDeviceModule->ForceStartRecording(); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Finally signal to the co-routine everyting is OK. + gWebRTCUpdateDevices = false; + } + // There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + // Another thead may have alreayd called this method + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else if (e.code() == std::errc::operation_not_permitted) + { + // This should not be reached + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + else + { + // Log any other message + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + } + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + catch (const std::exception& e) + { + mLogSink->OnLogMessage(std::string("Excepton: WebRTC: ") + e.what()); + // Device no longer being interacted with + gWebRTCUpdateDevices = false; + return; + } + // [FIRE-36022] } }); } diff --git a/indra/llwebrtc/llwebrtc.h b/indra/llwebrtc/llwebrtc.h index 7d06b7d2b4..acd3b686fa 100644 --- a/indra/llwebrtc/llwebrtc.h +++ b/indra/llwebrtc/llwebrtc.h @@ -40,6 +40,10 @@ #include #include +// [FIRE-36022] - Removing my USB headset crashes entire viewer +// Needed for inline variable for the crash check +#include +// [FIRE-36022] #ifdef LL_MAKEDLL #ifdef WEBRTC_WIN @@ -53,6 +57,12 @@ #define LLSYMEXPORT /**/ #endif // LL_MAKEDLL +// [FIRE-36022] - Removing my USB headset crashes entire viewer +// Create an atomic inline flag that will be shared between the various WebRTC threads and co-routines +// to track of when the audio hardware is being talked to. The co-routine can use it to +// exit if it too many iterations with the hardware locked indicating that the worker thread died. +inline std::atomic gWebRTCUpdateDevices = false; +// [FIRE-36022] namespace llwebrtc { diff --git a/indra/newview/Info-Firestorm.plist b/indra/newview/Info-Firestorm.plist index 6b2e73a5a2..add3bef56b 100644 --- a/indra/newview/Info-Firestorm.plist +++ b/indra/newview/Info-Firestorm.plist @@ -11,7 +11,7 @@ CFBundleIconFile ${MACOSX_BUNDLE_ICON_FILE} CFBundleIdentifier - ${PRODUCT_BUNDLE_IDENTIFIER} + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 2140d0e4eb..1599b93056 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -23499,7 +23499,7 @@ Change of this parameter will affect the layout of buttons in notification toast InventoryAddAttachmentBehavior Comment - Defines behavior when hitting return on an inventory item + Defines behavior when hitting return on an inventory item (unused in favour of FSDoubleClickAddInventoryObjects) Persist 1 Type diff --git a/indra/newview/app_settings/settings_per_account.xml b/indra/newview/app_settings/settings_per_account.xml index 4193a4edca..1641ec6b89 100644 --- a/indra/newview/app_settings/settings_per_account.xml +++ b/indra/newview/app_settings/settings_per_account.xml @@ -1141,6 +1141,30 @@ Value + FSBuildPrefs_UseCustomScript + + Comment + Use custom script template when clicking New Script button + Persist + 1 + Type + Boolean + Value + 0 + + FSBuildPrefs_CustomScriptItem + + HideFromEditor + 1 + Comment + Script UUID to use as template for New Script button + Persist + 1 + Type + String + Value + + FSllOwnerSayToScriptDebugWindow Comment diff --git a/indra/newview/fspanelface.cpp b/indra/newview/fspanelface.cpp index 484b725b0b..0a16503229 100644 --- a/indra/newview/fspanelface.cpp +++ b/indra/newview/fspanelface.cpp @@ -4106,6 +4106,7 @@ void FSPanelFace::onCancelSpecularTexture() // onClickBtnAddMedia() where needed, so the naming is probably just old cruft -Zi void FSPanelFace::onClickBtnEditMedia() { + LLFloaterMediaSettings::getInstance(); // make sure floater we are about to open exists before refreshMedia refreshMedia(); LLFloaterReg::showInstance("media_settings"); } @@ -4124,6 +4125,7 @@ void FSPanelFace::onClickBtnAddMedia() // check if multiple faces are selected if (LLSelectMgr::getInstance()->getSelection()->isMultipleTESelected()) { + LLFloaterMediaSettings::getInstance(); // make sure floater we are about to open exists before refreshMedia refreshMedia(); LLNotificationsUtil::add("MultipleFacesSelected", LLSD(), LLSD(), boost::bind(&FSPanelFace::multipleFacesSelectedConfirm, this, _1, _2)); } diff --git a/indra/newview/fspanelprefs.cpp b/indra/newview/fspanelprefs.cpp index 8ddf3fe0e6..a27984ad5f 100644 --- a/indra/newview/fspanelprefs.cpp +++ b/indra/newview/fspanelprefs.cpp @@ -49,6 +49,7 @@ FSPanelPrefs::FSPanelPrefs() : LLPanelPreference() mCommitCallbackRegistrar.add("Perms.Trans", boost::bind(&FSPanelPrefs::onCommitTrans, this)); mEmbeddedItem = gSavedPerAccountSettings.getString("FSBuildPrefs_Item"); + mCustomScriptItem = gSavedPerAccountSettings.getString("FSBuildPrefs_CustomScriptItem"); } bool FSPanelPrefs::postBuild() @@ -75,6 +76,9 @@ bool FSPanelPrefs::postBuild() mInvDropTarget = getChild("embed_item"); mInvDropTarget->setDADCallback(boost::bind(&FSPanelPrefs::onDADEmbeddedItem, this, _1)); + mCustomScriptDropTarget = getChild("custom_script"); + mCustomScriptDropTarget->setDADCallback(boost::bind(&FSPanelPrefs::onDADCustomScript, this, _1)); + return LLPanelPreference::postBuild(); } @@ -101,12 +105,35 @@ void FSPanelPrefs::onOpen(const LLSD& key) getChild("build_item_add_disp_rect_txt")->setTextArg("[ITEM]", getString("EmbeddedItemNotAvailable")); } } + + getChild("FSBuildPrefs_UseCustomScript")->setEnabled(true); + mCustomScriptItem = gSavedPerAccountSettings.getString("FSBuildPrefs_CustomScriptItem"); + LLUUID script_id(mCustomScriptItem); + if (script_id.isNull()) + { + getChild("custom_script_disp_rect_txt")->setTextArg("[SCRIPT]", getString("EmbeddedItemNotSet")); + } + else + { + LLInventoryObject* script = gInventory.getObject(script_id); + if (script) + { + getChild("custom_script_disp_rect_txt")->setTextArg("[SCRIPT]", script->getName()); + } + else + { + getChild("custom_script_disp_rect_txt")->setTextArg("[SCRIPT]", getString("EmbeddedItemNotAvailable")); + } + } + getChild("reset_default_folders")->setEnabled(true); } else { getChild("FSBuildPrefs_EmbedItem")->setEnabled(false); getChild("build_item_add_disp_rect_txt")->setTextArg("[ITEM]", getString("EmbeddedItemNotLoggedIn")); + getChild("FSBuildPrefs_UseCustomScript")->setEnabled(false); + getChild("custom_script_disp_rect_txt")->setTextArg("[SCRIPT]", getString("EmbeddedItemNotLoggedIn")); getChild("reset_default_folders")->setEnabled(false); } } @@ -118,6 +145,7 @@ void FSPanelPrefs::apply() if (LLStartUp::getStartupState() == STATE_STARTED) { gSavedPerAccountSettings.setString("FSBuildPrefs_Item", mEmbeddedItem); + gSavedPerAccountSettings.setString("FSBuildPrefs_CustomScriptItem", mCustomScriptItem); } } @@ -277,6 +305,16 @@ void FSPanelPrefs::onDADEmbeddedItem(const LLUUID& item_id) } } +void FSPanelPrefs::onDADCustomScript(const LLUUID& item_id) +{ + LLInventoryObject* item = gInventory.getObject(item_id); + if (item && item->getType() == LLAssetType::AT_LSL_TEXT) + { + getChild("custom_script_disp_rect_txt")->setTextArg("[SCRIPT]", item->getName()); + mCustomScriptItem = item_id.asString(); + } +} + void FSPanelPrefs::onResetDefaultFolders() { gSavedPerAccountSettings.getControl("ModelUploadFolder")->resetToDefault(true); diff --git a/indra/newview/fspanelprefs.h b/indra/newview/fspanelprefs.h index e0f37d880a..a349314101 100644 --- a/indra/newview/fspanelprefs.h +++ b/indra/newview/fspanelprefs.h @@ -55,11 +55,14 @@ private: void onCommitTrans(); void onDADEmbeddedItem(const LLUUID& item_id); + void onDADCustomScript(const LLUUID& item_id); void onResetDefaultFolders(); FSEmbeddedItemDropTarget* mInvDropTarget; + FSEmbeddedItemDropTarget* mCustomScriptDropTarget; std::string mEmbeddedItem; + std::string mCustomScriptItem; }; #endif // FS_PANELPREFS_H diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 70fdf8f0b0..6b6a785651 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -489,7 +489,13 @@ void LLGLTFLoader::processNodeHierarchy(S32 node_idx, std::map } else { - setLoadState(ERROR_MODEL + pModel->getStatus()); + // Fix deprecated arithmetic between different enum types (ERROR_MODEL + EModelStatus) + // Ugly fix. could use a helper instead but its only called in two places. + // setLoadState(ERROR_MODEL + pModel->getStatus()); + setLoadState( + static_cast( + static_cast(ERROR_MODEL) + static_cast(pModel->getStatus()))); + // delete pModel; return; } diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index adf08a0c8a..509fa4b6e4 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -5206,6 +5206,8 @@ bool LLAppViewer::initCache() LL_WARNS("AppCache") << "Unable to set cache location" << LL_ENDL; gSavedSettings.setString("CacheLocation", ""); gSavedSettings.setString("CacheLocationTopFolder", ""); + gSavedSettings.setString("NewCacheLocation", ""); + gSavedSettings.setString("NewCacheLocationTopFolder", ""); } // Sound cache diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 64a92b0968..a337b13e21 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -1026,6 +1026,11 @@ bool LLAppViewerWin32::cleanup() gDXHardware.cleanup(); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Need to unilitialize connection to COM, otherwise it will be treated as a memroy leak. + CoUninitialize(); + // [FIRE-36022] + if (mIsConsoleAllocated) { FreeConsole(); @@ -1066,6 +1071,19 @@ bool LLAppViewerWin32::initWindow() LL_WARNS("AppInit") << "Unable to set WindowWidth and WindowHeight for FullScreen mode" << LL_ENDL; } } + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Acccording to the FMOD spec, you are suppose to initalize COM on the thead that will talk to FMOD. IE the main thread. + // There is a coorisponding CoUninitialize in the shutdown code. Otherwise, FMOD will force the initalize with a warning, but does not clean up COM + HRESULT hr = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + if (SUCCEEDED(hr)) + { + LL_INFOS() << "WIN32: CoInitializeEx COM as COINIT_APARTMENTTHREADED Successful" << LL_ENDL; + } + else + { + LL_INFOS() << "WIN32: CoInitializeEx COM as COINIT_APARTMENTTHREADED Failed" << LL_ENDL; + } + // [FIRE-36022] return LLAppViewer::initWindow(); } diff --git a/indra/newview/lldrawable.cpp b/indra/newview/lldrawable.cpp index e85358f6cc..4d6c296166 100644 --- a/indra/newview/lldrawable.cpp +++ b/indra/newview/lldrawable.cpp @@ -938,7 +938,10 @@ void LLDrawable::updateDistance(LLCamera& camera, bool force_update) LLVector3 cam_pos_from_agent = LLViewerCamera::getInstance()->getOrigin(); LLVector3 cam_to_box_offset = point_to_box_offset(cam_pos_from_agent, av_box); mDistanceWRTCamera = llmax(0.01f, ll_round(cam_to_box_offset.magVec(), 0.01f)); - mVObjp->updateLOD(); + if (mVObjp) + { + mVObjp->updateLOD(); + } return; } } @@ -949,7 +952,10 @@ void LLDrawable::updateDistance(LLCamera& camera, bool force_update) pos -= camera.getOrigin(); mDistanceWRTCamera = ll_round(pos.magVec(), 0.01f); - mVObjp->updateLOD(); + if (mVObjp) + { + mVObjp->updateLOD(); + } } } @@ -961,6 +967,11 @@ void LLDrawable::updateTexture() return; } + if (!mVObjp) + { + return; + } + if (getNumFaces() != mVObjp->getNumTEs()) { //drawable is transitioning its face count return; diff --git a/indra/newview/llface.h b/indra/newview/llface.h index 651a4167e0..4b6c2a9d50 100644 --- a/indra/newview/llface.h +++ b/indra/newview/llface.h @@ -52,7 +52,12 @@ class LLDrawInfo; class LLMeshSkinInfo; const F32 MIN_ALPHA_SIZE = 1024.f; -const F32 MIN_TEX_ANIM_SIZE = 512.f; +// +//const F32 MIN_TEX_ANIM_SIZE = 512.f; +// Reduce the cut off point of the animated textures down to the size when textures get discarded/set to invalid size. +// This should prevent "on screen" animated textures from stopping. +const F32 MIN_TEX_ANIM_SIZE = 16.f; +// const U8 FACE_DO_NOT_BATCH_TEXTURES = 255; class alignas(16) LLFace @@ -128,7 +133,7 @@ public: void setIndexInTex(U32 ch, S32 index) { llassert(ch < LLRender::NUM_TEXTURE_CHANNELS); mIndexInTex[ch] = index; } void setWorldMatrix(const LLMatrix4& mat); - const LLTextureEntry* getTextureEntry() const { return mVObjp->getTE(mTEOffset); } + const LLTextureEntry* getTextureEntry() const { return mVObjp ? mVObjp->getTE(mTEOffset) : nullptr; } LLFacePool* getPool() const { return mDrawPoolp; } U32 getPoolType() const { return mPoolType; } diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 81c6d7da94..54e2962850 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -7925,8 +7925,12 @@ void LLObjectBridge::performAction(LLInventoryModel* model, std::string action) item = (LLViewerInventoryItem*)gInventory.getItem(object_id); if(item && gInventory.isObjectDescendentOf(object_id, gInventory.getRootFolderID())) { - static LLCachedControl replace_item(gSavedSettings, "InventoryAddAttachmentBehavior", false); - rez_attachment(item, NULL, ("attach" == action) ? replace_item() : true); // Replace if "Wear"ing. + // Firestorm's replace on double click + // static LLCachedControl replace_item(gSavedSettings, "InventoryAddAttachmentBehavior", false); + // rez_attachment(item, NULL, ("attach" == action) ? replace_item() : true); // Replace if "Wear"ing. + static LLCachedControl replace_item(gSavedSettings, "FSDoubleClickAddInventoryObjects", false); + rez_attachment(item, NULL, ("attach" == action) ? !replace_item : true); // Replace if "Wear"ing. + // } else if(item && item->isFinished()) { @@ -7969,11 +7973,13 @@ void LLObjectBridge::performAction(LLInventoryModel* model, std::string action) void LLObjectBridge::openItem() { + static LLCachedControl replace_item(gSavedSettings, "FSDoubleClickAddInventoryObjects", false); // Double-click add/replace // object double-click action is to wear/unwear object performAction(getInventoryModel(), // Double-click add/replace //get_is_item_worn(mUUID) ? "detach" : "attach"); - get_is_item_worn(mUUID) ? "detach" : gSavedSettings.getBOOL("FSDoubleClickAddInventoryObjects") ? "wear_add" : "attach"); + get_is_item_worn(mUUID) ? "detach" : replace_item ? "wear_add" : "attach"); + // } std::string LLObjectBridge::getLabelSuffix() const @@ -9190,7 +9196,9 @@ void LLObjectBridgeAction::attachOrDetach() // Double-click add/replace option //static LLCachedControl inventory_linking(gSavedSettings, "InventoryAddAttachmentBehavior", false); //LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, inventory_linking()); // Don't replace if adding. - LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, !gSavedSettings.getBOOL("FSDoubleClickAddInventoryObjects")); // Don't replace if adding. + static LLCachedControl replace_item(gSavedSettings, "FSDoubleClickAddInventoryObjects", false); + LLAppearanceMgr::instance().wearItemOnAvatar(mUUID, true, !replace_item); // Don't replace if adding. + // } } diff --git a/indra/newview/llmaniptranslate.cpp b/indra/newview/llmaniptranslate.cpp index 4b93d946ec..ca3b9a7ae4 100644 --- a/indra/newview/llmaniptranslate.cpp +++ b/indra/newview/llmaniptranslate.cpp @@ -1,4 +1,4 @@ -/** +/** * @file llmaniptranslate.cpp * @brief LLManipTranslate class implementation * @@ -716,24 +716,28 @@ bool LLManipTranslate::handleHover(S32 x, S32 y, MASK mask) // handle attachments in local space if (object->isAttachment() && object->mDrawable.notNull()) { - // calculate local version of relative move - LLQuaternion objWorldRotation = object->mDrawable->mXform.getParent()->getWorldRotation(); - objWorldRotation.transQuat(); - - LLVector3 old_position_local = object->getPosition(); - LLVector3 new_position_local = selectNode->mSavedPositionLocal + (clamped_relative_move_f * objWorldRotation); - - //RN: I forget, but we need to do this because of snapping which doesn't often result - // in position changes even when the mouse moves - object->setPosition(new_position_local); - rebuild(object); - gAgentAvatarp->clampAttachmentPositions(); - new_position_local = object->getPosition(); - - if (selectNode->mIndividualSelection) + LLXform* object_xform_parent = object->mDrawable->mXform.getParent(); + if (object_xform_parent) { - // counter-translate child objects if we are moving the root as an individual - object->resetChildrenPosition(old_position_local - new_position_local, true); + // calculate local version of relative move + LLQuaternion objWorldRotation = object_xform_parent->getWorldRotation(); + objWorldRotation.transQuat(); + + LLVector3 old_position_local = object->getPosition(); + LLVector3 new_position_local = selectNode->mSavedPositionLocal + (clamped_relative_move_f * objWorldRotation); + + //RN: I forget, but we need to do this because of snapping which doesn't often result + // in position changes even when the mouse moves + object->setPosition(new_position_local); + rebuild(object); + gAgentAvatarp->clampAttachmentPositions(); + new_position_local = object->getPosition(); + + if (selectNode->mIndividualSelection) + { + // counter-translate child objects if we are moving the root as an individual + object->resetChildrenPosition(old_position_local - new_position_local, true); + } } } else diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp index 1dc127f8e0..fae02f4e05 100644 --- a/indra/newview/lloutfitslist.cpp +++ b/indra/newview/lloutfitslist.cpp @@ -1780,11 +1780,18 @@ bool LLOutfitAccordionCtrlTab::handleToolTip(S32 x, S32 y, MASK mask) // Make thumbnail tooltip work properly //if (y >= getLocalRect().getHeight() - getHeaderHeight()) static LLCachedControl showInventoryThumbnailTooltips(gSavedSettings, "FSShowInventoryThumbnailTooltips"); - if (showInventoryThumbnailTooltips && y >= getLocalRect().getHeight() - getHeaderHeight() && gInventory.getCategory(mFolderID)->getThumbnailUUID().notNull()) + if (showInventoryThumbnailTooltips && y >= getLocalRect().getHeight() - getHeaderHeight()) { LLSD params; params["inv_type"] = LLInventoryType::IT_CATEGORY; LLViewerInventoryCategory* cat = gInventory.getCategory(mFolderID); + // Make thumbnail tooltip work properly + if (!cat || cat->getThumbnailUUID().isNull()) + { + return LLAccordionCtrlTab::handleToolTip(x, y, mask); + } + // + if (cat) { params["thumbnail_id"] = cat->getThumbnailUUID(); diff --git a/indra/newview/llpanelcontents.cpp b/indra/newview/llpanelcontents.cpp index 82ccca2600..227216705e 100644 --- a/indra/newview/llpanelcontents.cpp +++ b/indra/newview/llpanelcontents.cpp @@ -52,8 +52,10 @@ #include "lltool.h" #include "lltoolcomp.h" #include "lltoolmgr.h" +#include "lltooldraganddrop.h" // For custom script template #include "lltrans.h" #include "llviewerassettype.h" +#include "llviewercontrol.h" // For custom script template #include "llviewerinventory.h" #include "llviewermenu.h" // Script reset in edit floater #include "llviewerobject.h" @@ -280,6 +282,20 @@ void LLPanelContents::onClickNewScript(void *userdata) } // [/RLVa:KB] + // FIRE-36059 Optional custom script template for New Script button + if (gSavedPerAccountSettings.getBOOL("FSBuildPrefs_UseCustomScript")) + { + if (LLUUID custom_script_id(gSavedPerAccountSettings.getString("FSBuildPrefs_CustomScriptItem")); custom_script_id.notNull()) + { + if (auto custom_script = gInventory.getItem(custom_script_id); custom_script && custom_script->getType() == LLAssetType::AT_LSL_TEXT) + { + LLToolDragAndDrop::dropScript(object, custom_script, true, LLToolDragAndDrop::SOURCE_AGENT, gAgentID); + return; + } + } + } + // + LLPermissions perm; perm.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null); diff --git a/indra/newview/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp index e621f16a84..f72ad8cfbb 100644 --- a/indra/newview/llpaneloutfitedit.cpp +++ b/indra/newview/llpaneloutfitedit.cpp @@ -1324,7 +1324,10 @@ void LLPanelOutfitEdit::showFilteredWearablesListView(LLWearableType::EType type showWearablesListView(); //e_list_view_item_type implicitly contains LLWearableType::EType starting from LVIT_SHAPE - applyListViewFilter(static_cast(LVIT_SHAPE + type)); + // More deprecated enum math + // applyListViewFilter(static_cast(LVIT_SHAPE + type)); + applyListViewFilter(static_cast(static_cast(LVIT_SHAPE) + static_cast(type))); + // mWearableItemsList->setMenuWearableType(type); } diff --git a/indra/newview/llpanelwearing.cpp b/indra/newview/llpanelwearing.cpp index 8001c4c6e0..86ffc84054 100644 --- a/indra/newview/llpanelwearing.cpp +++ b/indra/newview/llpanelwearing.cpp @@ -305,6 +305,14 @@ bool LLPanelWearing::postBuild() // Show avatar complexity in appearance floater mAvatarComplexityLabel = getChild("avatar_complexity_label"); + // FIRE-36089: Connect bottom gear menu button to the gear menu + LLMenuButton* gear_btn = findChild("options_gear_btn"); + if (gear_btn) + { + gear_btn->setMenu(mGearMenu->getMenu()); + } + // + return true; } diff --git a/indra/newview/llpreviewtexture.cpp b/indra/newview/llpreviewtexture.cpp index b17b5b5aa5..fbc5ea25d3 100644 --- a/indra/newview/llpreviewtexture.cpp +++ b/indra/newview/llpreviewtexture.cpp @@ -475,7 +475,7 @@ void LLPreviewTexture::saveTextureToFile(const std::vector& filenam void LLPreviewTexture::saveMultipleToFile(const std::string& file_name) { std::string texture_location(gSavedSettings.getString("TextureSaveLocation")); - std::string texture_name = file_name.empty() ? getItem()->getName() : file_name; + std::string texture_name = LLDir::getScrubbedFileName(file_name.empty() ? getItem()->getName() : file_name); std::string filepath; S32 i = 0; diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 21b7058c3b..4872c9a805 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -7488,7 +7488,7 @@ void LLAlphaObject::getBlendFunc(S32 face, LLRender::eBlendFactor& src, LLRender void LLStaticViewerObject::updateDrawable(bool force_damped) { // Force an immediate rebuild on any update - if (mDrawable.notNull()) + if (mDrawable.notNull() && mDrawable->getVObj()) { mDrawable->updateXform(true); gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 50d1d69576..17152b8e46 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -1124,7 +1124,6 @@ F32 LLViewerTextureList::updateImagesCreateTextures(F32 max_time) imagep->postCreateTexture(); imagep->mCreatePending = false; - mCreateTextureList.pop(); if (imagep->hasGLTexture() && imagep->getDiscardLevel() < imagep->getDesiredDiscardLevel() && (imagep->getDesiredDiscardLevel() <= MAX_DISCARD_LEVEL)) @@ -1136,6 +1135,8 @@ F32 LLViewerTextureList::updateImagesCreateTextures(F32 max_time) imagep->scaleDown(); } + mCreateTextureList.pop(); + if (create_timer.getElapsedTimeF32() > max_time) { break; diff --git a/indra/newview/llvograss.cpp b/indra/newview/llvograss.cpp index db37563e14..c133f1f5d2 100644 --- a/indra/newview/llvograss.cpp +++ b/indra/newview/llvograss.cpp @@ -781,7 +781,7 @@ void LLGrassPartition::getGeometry(LLSpatialGroup* group) void LLVOGrass::updateDrawable(bool force_damped) { // Force an immediate rebuild on any update - if (mDrawable.notNull()) + if (mDrawable.notNull() && mDrawable->getVObj()) { mDrawable->updateXform(true); gPipeline.markRebuild(mDrawable, LLDrawable::REBUILD_ALL); diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h index 33ed6b969d..aea9893686 100644 --- a/indra/newview/llvoiceclient.h +++ b/indra/newview/llvoiceclient.h @@ -38,6 +38,10 @@ class LLVOAvatar; #include "llcallingcard.h" // for LLFriendObserver #include "llsecapi.h" #include "llcontrol.h" +// [FIRE-36022] - Removing my USB headset crashes entire viewer +// Need to include for audio device mutex shared with other audio/voice systems. +#include "inlinemutexs.h" +// [FIRE-36022] // devices diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index a8a7f9784b..962ace5efc 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -82,6 +82,9 @@ extern void handle_voice_morphing_subscribe(); const std::string VIVOX_VOICE_SERVER_TYPE = "vivox"; +// [FIRE-36022] - Removing my USB headset crashes entire viewer +using namespace std::chrono_literals; // Needed for shared timed mutex to use time +// [FIRE-36022] namespace { const F32 VOLUME_SCALE_VIVOX = 0.01f; @@ -2412,6 +2415,19 @@ void LLVivoxVoiceClient::sendCaptureAndRenderDevices() { if (mCaptureDeviceDirty || mRenderDeviceDirty) { + // [FIRE-36022] - Removing my USB headset crashes entire viewer + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid + { + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + LL_INFOS() << "Could not access the audio device mutex, trying again later" << LL_ENDL; + return; + } + // [FIRE-36022] std::ostringstream stream; buildSetCaptureDevice(stream); @@ -2423,6 +2439,35 @@ void LLVivoxVoiceClient::sendCaptureAndRenderDevices() } llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + } + // There are two exceptions that unique_lock can trigger, operation_not_permitted or resource_deadlock_would_occur + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + // When trying to lock the same lock a second time + LL_WARNS() << "Exception Vinvox: " << e.code() << " " << e.what() << LL_ENDL; + } + else if (e.code() == std::errc::operation_not_permitted) + { + // When the mutex is invalid + LL_WARNS() << "Exception Vinvox: " << e.code() << " " << e.what() << LL_ENDL; + } + else + { + // Everything else + LL_WARNS() << "Exception Vinvox: " << e.code() << " " << e.what() << LL_ENDL; + } + + return; + } + catch (const std::exception& e) + { + LL_WARNS() << "Exception Vinvox: " << " " << e.what() << LL_ENDL; + return; + } + // [FIRE-36022] } } diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index 4f0f9266c8..d57b042f5b 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -80,6 +80,9 @@ const std::string WEBRTC_VOICE_SERVER_TYPE = "webrtc"; +// [FIRE-36022] - Removing my USB headset crashes entire viewer +using namespace std::chrono_literals; // Needed for shared timed mutex to use time +// [FIRE-36022] namespace { const F32 MAX_AUDIO_DIST = 50.0f; @@ -506,6 +509,12 @@ void LLWebRTCVoiceClient::voiceConnectionCoro() try { LLMuteList::getInstance()->addObserver(this); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // Add a counter to check if the main thread locked up + // to prevent this thread/corutine form filling up + // the mMainQueue. + static U32 crash_check = 0; + // [FIRE-36022] while (!sShuttingDown) { LL_PROFILE_ZONE_NAMED_CATEGORY_VOICE("voiceConnectionCoroLoop") @@ -591,6 +600,26 @@ void LLWebRTCVoiceClient::voiceConnectionCoro() // to send position updates. updatePosition(); } + // [FIRE-36022] - Removing my USB headset crashes entire viewer + // If the device locked, count up by 1 + if (gWebRTCUpdateDevices) + { + crash_check++; + } + // Else if the device is not locked, then reset the counter back to 0 + else + { + crash_check = 0; + } + // If there are over 10 cycles of the devices being locked, there is a good + // chance that the thread failed due to hardware/audio engine issue. + if (crash_check > 10) + { + LL_WARNS() << "WebRTC detected locked worker thread, will shutdown to prevent total viewer lockup." << LL_ENDL; + // Exit out of the thread and flag WebRTC to shutdown, hopefully clearing the lock and allowing the viewer to continue. + sShuttingDown = true; + } + // [FIRE-36022] } LL::WorkQueue::postMaybe(mMainQueue, [=, this] { @@ -731,6 +760,19 @@ void LLWebRTCVoiceClient::OnDevicesChanged(const llwebrtc::LLWebRTCVoiceDeviceLi void LLWebRTCVoiceClient::OnDevicesChangedImpl(const llwebrtc::LLWebRTCVoiceDeviceList &render_devices, const llwebrtc::LLWebRTCVoiceDeviceList &capture_devices) { + // [FIRE-36022] - Removing my USB headset crashes entire viewer + try // Try catch needed for uniquie lock as will throw an exception if a second lock is attempted or the mutex is invalid + { + // Attempt to lock the access to the audio device, wait up to 1 second for other threads to unlock. + std::unique_lock lock(gAudioDeviceMutex, 1s); + // If the lock could not be accessed, return as we don't have hardware access and will need to try again another pass. + // Prevents threads from interacting with the hardware at the same time as other audio/voice threads. + if (!lock.owns_lock()) + { + LL_INFOS() << "Could not access the audio device mutex, trying again later" << LL_ENDL; + return; + } + // [FIRE-36022] if (sShuttingDown) { return; @@ -774,6 +816,34 @@ void LLWebRTCVoiceClient::OnDevicesChangedImpl(const llwebrtc::LLWebRTCVoiceDevi } setDevicesListUpdated(true); + // [FIRE-36022] - Removing my USB headset crashes entire viewer + } + catch (const std::system_error& e) + { + if (e.code() == std::errc::resource_deadlock_would_occur) + { + // When trying to lock the same lock a second time + LL_WARNS() << "Exception WebRTC: " << e.code() << " " << e.what() << LL_ENDL; + } + else if (e.code() == std::errc::operation_not_permitted) + { + // When the mutex is invalid + LL_WARNS() << "Exception WebRTC: " << e.code() << " " << e.what() << LL_ENDL; + } + else + { + // Everything else + LL_WARNS() << "Exception WebRTC: " << e.code() << " " << e.what() << LL_ENDL; + } + + return; + } + catch (const std::exception& e) + { + LL_WARNS() << "Exception WebRTC: " << " " << e.what() << LL_ENDL; + return; + } + // [FIRE-36022] } void LLWebRTCVoiceClient::clearRenderDevices() diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 9777aa6bc4..aff67413fe 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -3100,7 +3100,7 @@ void LLPipeline::markMoved(LLDrawable *drawablep, bool damped_motion) void LLPipeline::markShift(LLDrawable *drawablep) { - if (!drawablep || drawablep->isDead()) + if (!drawablep || drawablep->isDead() || !drawablep->getVObj()) { return; } @@ -3134,7 +3134,7 @@ void LLPipeline::shiftObjects(const LLVector3 &offset) iter != mShiftList.end(); iter++) { LLDrawable *drawablep = *iter; - if (drawablep->isDead()) + if (drawablep->isDead() || !drawablep->getVObj()) { continue; } diff --git a/indra/newview/skins/ansastorm/xui/en/panel_main_inventory.xml b/indra/newview/skins/ansastorm/xui/en/panel_main_inventory.xml index 07122d6243..9c87cd6a79 100644 --- a/indra/newview/skins/ansastorm/xui/en/panel_main_inventory.xml +++ b/indra/newview/skins/ansastorm/xui/en/panel_main_inventory.xml @@ -852,7 +852,7 @@ bevel_style="none" follows="all" label="Favorites" - help_topic="recent_inventory_tab" + help_topic="favorites_inventory_tab" layout="topleft" name="Favorites" show_item_link_overlays="true" diff --git a/indra/newview/skins/default/xui/az/panel_preferences_firestorm.xml b/indra/newview/skins/default/xui/az/panel_preferences_firestorm.xml index 7dc384fefa..c6d2cdde1c 100644 --- a/indra/newview/skins/default/xui/az/panel_preferences_firestorm.xml +++ b/indra/newview/skins/default/xui/az/panel_preferences_firestorm.xml @@ -156,6 +156,34 @@ Məzmun: [ITEM] + + + Drop a script here. + + + Məzmun: [SCRIPT] + + + + + + + + + + + + Constrain rotations to multiples of + + + + degrees, when not using 'snap to grid' + + + + + objects + + + + + - - - - Script Editor Font: - - - - - - - - - - - - - - - - - - - - - - Preprocessor include path: - - - - - External Editor: - - - @@ -1465,169 +1526,10 @@ label="Build 2" name="BuildTab2"> - - - - - - - - Constrain rotations to multiples of - - - - degrees, when not using 'snap to grid' - - - - - objects - - - - - + + + + Script Editor Font: + + + + + + + + + + + + + + + + + + + + + + + + + Preprocessor include path: + + + + + External Editor: + + + + + All Items Recent Items Worn Items + Favorites A group member named [NAME] (online) diff --git a/indra/newview/skins/default/xui/es/panel_preferences_firestorm.xml b/indra/newview/skins/default/xui/es/panel_preferences_firestorm.xml index 1bb878354a..d14a0557db 100644 --- a/indra/newview/skins/default/xui/es/panel_preferences_firestorm.xml +++ b/indra/newview/skins/default/xui/es/panel_preferences_firestorm.xml @@ -129,33 +129,41 @@ Actualmente establecido a: [ITEM] + + + Drop a script here. + + + Actualmente establecido a: [SCRIPT] + + + + + Punto de pivotaje - + + + + + + + - + Ruta include del preprocesador: