diff options
| author | Jonathan "Geenz" Goodman <geenz@lindenlab.com> | 2026-05-20 12:05:18 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-05-20 12:05:18 -0400 |
| commit | 40cd1617a6a663fb921265101307454b490ec4ba (patch) | |
| tree | 2f13741295c385172364e3fa940d7591f32fac4e | |
| parent | 9c2ee024bef564ebb6e2e8942b43ffae1d99232c (diff) | |
| parent | df844170600608b37c2725f7a263f1de3b055618 (diff) | |
Merge branch 'develop' into geenz/26.3-to-develop
146 files changed, 2721 insertions, 645 deletions
diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 866af3dbac..cee977e668 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -434,7 +434,7 @@ jobs: tar -xJf "${{ needs.build.outputs.viewer_channel }}.sym.tar.xz" -C _artifacts - name: Post Windows symbols if: env.BUGSPLAT_DATABASE && env.SYMBOL_UPLOAD_CLIENT_ID - uses: BugSplat-Git/symbol-upload@095d163ae9ceb006d286a731dcd35cf6a1b458c8 + uses: BugSplat-Git/symbol-upload@33f604b631df9b4be22a06c52e46acccd2db596b with: clientId: "${{ env.SYMBOL_UPLOAD_CLIENT_ID }}" clientSecret: "${{ env.SYMBOL_UPLOAD_CLIENT_SECRET }}" @@ -462,7 +462,7 @@ jobs: name: macOS-symbols - name: Post Mac symbols if: env.BUGSPLAT_DATABASE && env.SYMBOL_UPLOAD_CLIENT_ID - uses: BugSplat-Git/symbol-upload@095d163ae9ceb006d286a731dcd35cf6a1b458c8 + uses: BugSplat-Git/symbol-upload@33f604b631df9b4be22a06c52e46acccd2db596b with: clientId: "${{ env.SYMBOL_UPLOAD_CLIENT_ID }}" clientSecret: "${{ env.SYMBOL_UPLOAD_CLIENT_SECRET }}" @@ -487,7 +487,7 @@ jobs: with: pattern: "*-metadata" - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: pattern: "*-releases" diff --git a/.github/workflows/check-pr.yaml b/.github/workflows/check-pr.yaml index 08e907e83f..68ca8244c0 100644 --- a/.github/workflows/check-pr.yaml +++ b/.github/workflows/check-pr.yaml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check PR description - uses: actions/github-script@v8 + uses: actions/github-script@v9 with: script: | const description = context.payload.pull_request.body || ''; diff --git a/.github/workflows/tag-release.yaml b/.github/workflows/tag-release.yaml index 0f826222a0..f1afb0f1a2 100644 --- a/.github/workflows/tag-release.yaml +++ b/.github/workflows/tag-release.yaml @@ -37,7 +37,7 @@ jobs: echo NIGHTLY_DATE=${NIGHTLY_DATE} >> ${GITHUB_ENV} echo TAG_ID="$(echo ${{ github.sha }} | cut -c1-8)-${{ inputs.project || '${NIGHTLY_DATE}' }}" >> ${GITHUB_ENV} - name: Create Tag - uses: actions/github-script@v8 + uses: actions/github-script@v9 with: # use a real access token instead of GITHUB_TOKEN default. # required so that the results of this tag creation can trigger the build workflow diff --git a/.gitignore b/.gitignore index c4accf37b5..62a6b462f9 100755 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,9 @@ *~ # Specific paths and/or names +/.autobuild-installables/ +/.build-variables/ +/.logs/ CMakeCache.txt cmake_install.cmake LICENSES diff --git a/autobuild.xml b/autobuild.xml index 3a2cfa0b46..17d35d82e9 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2019,11 +2019,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>archive</key> <map> <key>hash</key> - <string>94a72c6ddbfb23796ce913c55bc47c128542a582</string> + <string>8dc190451c5b7af0d72e7ab54f8fbde6dccf79c6</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-openjpeg/releases/download/v2.5.3-r1/openjpeg-2.5.3.15590356935-darwin64-15590356935.tar.zst</string> + <string>https://github.com/secondlife/3p-openjpeg/releases/download/v2.5.4-r1/openjpeg-2.5.4.18754730947-darwin64-18754730947.tar.zst</string> </map> <key>name</key> <string>darwin64</string> @@ -2033,11 +2033,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>archive</key> <map> <key>hash</key> - <string>751172af405f4a47a3aebb37729d62229cab6c07</string> + <string>9c89879f81ee0434e1f59c47d74a25958ae08e9e</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-openjpeg/releases/download/v2.5.3-r1/openjpeg-2.5.3.15590356935-linux64-15590356935.tar.zst</string> + <string>https://github.com/secondlife/3p-openjpeg/releases/download/v2.5.4-r1/openjpeg-2.5.4.18754730947-linux64-18754730947.tar.zst</string> </map> <key>name</key> <string>linux64</string> @@ -2047,11 +2047,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>archive</key> <map> <key>hash</key> - <string>8aab9cf250dfee252386e1c79b5205e6d3b3e19e</string> + <string>b78887212f18ae59dc7961e0e1af781e568bd92c</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-openjpeg/releases/download/v2.5.3-r1/openjpeg-2.5.3.15590356935-windows64-15590356935.tar.zst</string> + <string>https://github.com/secondlife/3p-openjpeg/releases/download/v2.5.4-r1/openjpeg-2.5.4.18754730947-windows64-18754730947.tar.zst</string> </map> <key>name</key> <string>windows64</string> @@ -2401,11 +2401,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>archive</key> <map> <key>hash</key> - <string>e88a7c97a6843d43e0093388f211299ec2892790</string> + <string>d1584b3a0011dbb741bc64d32e8bd28845ddd4da</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-viewer-fonts/releases/download/v1.1.0-r1/viewer_fonts-1.0.0.10204976553-common-10204976553.tar.zst</string> + <string>https://github.com/secondlife/3p-viewer-fonts/releases/download/v1.1.0-r2/viewer_fonts-1.0.0.25128287087-common-25128287087.tar.zst</string> </map> <key>name</key> <string>common</string> @@ -2416,9 +2416,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>license_file</key> <string>LICENSES/fonts.txt</string> <key>copyright</key> - <string>Copyright 2016-2022 Brad Erickson CC-BY-4.0/MIT, Copyright 2016-2022 Twitter, Inc. CC-BY-4.0, Copyright 2013 Joe Loughry and Terence Eden MIT</string> + <string>Copyright 2016-2022 Brad Erickson CC-BY-4.0/MIT, Copyright 2016-2022 Twitter, Inc. CC-BY-4.0, Copyright 2013 Joe Loughry and Terence Eden MIT, Copyright (c) 2003 by Bitstream, Inc., Copyright (c) 2006 by Tavmjong Bah.</string> <key>version</key> - <string>1.0.0.10204976553</string> + <string>1.0.0.25128287087</string> <key>name</key> <string>viewer-fonts</string> <key>description</key> @@ -2896,14 +2896,12 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <map> <key>archive</key> <map> - <key>creds</key> - <string>github</string> <key>hash</key> - <string>91abbc360640b5b2e0a4c001a36ad411a9a42602</string> + <string>df2260187110aa51c20101183e18a55f86e71090</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife-3p/3p-velopack/releases/assets/380583560</string> + <string>https://github.com/secondlife-3p/3p-velopack/releases/download/v0.0.1535-r2/velopack-40232ef.24685318450-windows64-24685318450.tar.zst</string> </map> <key>name</key> <string>windows64</string> @@ -2912,14 +2910,12 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <map> <key>archive</key> <map> - <key>creds</key> - <string>github</string> <key>hash</key> - <string>05563a79bdeb83d66a72ac1e97587dc2a8f64511</string> + <string>ead94386d7b9a143824d7ccedc86433127028a91</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://api.github.com/repos/secondlife-3p/3p-velopack/releases/assets/380583554</string> + <string>https://github.com/secondlife-3p/3p-velopack/releases/download/v0.0.1535-r2/velopack-40232ef.24685318450-darwin64-24685318450.tar.zst</string> </map> <key>name</key> <string>darwin64</string> @@ -2932,7 +2928,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>copyright</key> <string>Velopack Ltd.</string> <key>version</key> - <string>40232ef.23500976684</string> + <string>40232ef.24685318450</string> <key>name</key> <string>velopack</string> <key>description</key> @@ -3390,7 +3386,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>license_file</key> <string>docs/LICENSE-source.txt</string> <key>copyright</key> - <string>Copyright (c) 2020, Linden Research, Inc.</string> + <string>Copyright (c) 2026, Linden Research, Inc.</string> <key>version_file</key> <string>newview/viewer_version.txt</string> <key>name</key> diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index dab18c240d..c8fd2cba70 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -1647,10 +1647,10 @@ glm::mat4 LLAvatarBoneInfo::getJointMatrix() glm::mat4 mat(1.0f); // 1. Scaling mat = glm::scale(mat, glm::vec3(mScale[0], mScale[1], mScale[2])); - // 2. Rotation (Euler angles rad) - mat = glm::rotate(mat, mRot[0], glm::vec3(1, 0, 0)); - mat = glm::rotate(mat, mRot[1], glm::vec3(0, 1, 0)); - mat = glm::rotate(mat, mRot[2], glm::vec3(0, 0, 1)); + // 2. Rotation (avatar_skeleton.xml stores Euler angles in degrees) + mat = glm::rotate(mat, glm::radians(mRot[0]), glm::vec3(1, 0, 0)); + mat = glm::rotate(mat, glm::radians(mRot[1]), glm::vec3(0, 1, 0)); + mat = glm::rotate(mat, glm::radians(mRot[2]), glm::vec3(0, 0, 1)); // 3. Position mat = glm::translate(mat, glm::vec3(mPos[0], mPos[1], mPos[2])); return mat; @@ -1698,6 +1698,7 @@ void LLAvatarSkeletonInfo::getJointMatricesAndHierarhy( data.mRestMatrix = parent_mat * data.mJointMatrix; data.mIsJoint = bone_info->mIsJoint; data.mGroup = bone_info->mGroup; + data.setSupport(bone_info->mSupport); for (LLAvatarBoneInfo* child_info : bone_info->mChildren) { LLJointData& child_data = data.mChildren.emplace_back(); diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index 41893a35e5..0083865cf7 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -314,6 +314,7 @@ namespace LLError ERROR_OTHER = 0, ERROR_BAD_ALLOC = 1, ERROR_MISSING_FILES = 2, + ERROR_INIT_FAILED = 3, } eLastExecEvent; // tittle, message and error code to include in error marker file diff --git a/indra/llcommon/llsdserialize_xml.cpp b/indra/llcommon/llsdserialize_xml.cpp index f399c51608..af79fabe81 100644 --- a/indra/llcommon/llsdserialize_xml.cpp +++ b/indra/llcommon/llsdserialize_xml.cpp @@ -63,15 +63,80 @@ S32 LLSDXMLFormatter::format(const LLSD& data, std::ostream& ostr, EFormatterOptions options) const { std::streamsize old_precision = ostr.precision(25); + std::ios_base::iostate old_exceptions = ostr.exceptions(); + // Merged exception mask: preserve the caller's bits and add failbit|badbit + // for I/O error detection, so we never drop bits the caller already enabled. + std::ios_base::iostate new_exceptions = + old_exceptions | std::ios_base::badbit | std::ios_base::failbit; + // Bits we are newly adding (not already in the caller's mask). + std::ios_base::iostate added_bits = new_exceptions & ~old_exceptions; - std::string post; - if (options & LLSDFormatter::OPTIONS_PRETTY) + // If the stream already has error-state bits that we would newly add to the + // exception mask, enabling those bits would throw immediately; bail out early. + if (added_bits && (ostr.rdstate() & added_bits)) { - post = "\n"; + LL_WARNS() << "LLSDXMLFormatter::format: Stream already in error state" << LL_ENDL; + ostr.precision(old_precision); + return -1; + } + + S32 rv = 0; + + try + { + // Enable the merged exception mask to detect I/O errors during formatting. + if (added_bits) + { + ostr.exceptions(new_exceptions); + } + + std::string post; + if (options & LLSDFormatter::OPTIONS_PRETTY) + { + post = "\n"; + } + ostr << "<llsd>" << post; + rv = format_impl(data, ostr, options, 1); + ostr << "</llsd>\n"; + } + catch (const std::ios_base::failure& e) + { + LL_WARNS() << "LLSDXMLFormatter::format: Stream I/O exception: " << e.what() + << " - Stream state: good=" << ostr.good() + << " eof=" << ostr.eof() + << " fail=" << ostr.fail() + << " bad=" << ostr.bad() << LL_ENDL; + rv = -1; + } + catch (const std::bad_alloc&) + { + // we might be saving something massive, don't error or crash + LL_WARNS() << "LLSDXMLFormatter::format: Memory allocation failed during formatting" << LL_ENDL; + rv = -1; + } + catch (const std::exception& e) + { + LL_WARNS() << "LLSDXMLFormatter::format: Standard exception: " << e.what() << LL_ENDL; + rv = -1; + } + catch (...) + { + LL_WARNS() << "LLSDXMLFormatter::format: Unknown exception during formatting" << LL_ENDL; + rv = -1; + } + + // Restore original exception mask. First set to goodbit (never throws) so + // the subsequent restore call won't immediately throw if the stream is in + // error state for bits in old_exceptions. + try + { + ostr.exceptions(std::ios_base::goodbit); + ostr.exceptions(old_exceptions); + } + catch (...) + { + LL_WARNS() << "LLSDXMLFormatter::format: failed to restore exceptions" << LL_ENDL; } - ostr << "<llsd>" << post; - S32 rv = format_impl(data, ostr, options, 1); - ostr << "</llsd>\n"; ostr.precision(old_precision); return rv; diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index fae9f7023f..f52b3fec71 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -243,6 +243,58 @@ namespace tut xml_test("binary", expected); } + template<> template<> + void sd_xml_object::test<7>() + { + // Test that stream state (precision and exceptions) is correctly restored after format() + { + std::ostringstream ostr; + + // Set custom precision + ostr.precision(10); + + // Set some exception bits + ostr.exceptions(std::ios_base::badbit); + std::ios_base::iostate original_exceptions = ostr.exceptions(); + + // Format some LLSD data + mSD = 3.141592653589793; + S32 result = mFormatter->format(mSD, ostr); + + // Verify formatting succeeded + ensure("format should succeed", result >= 0); + + // Verify precision was restored + ensure_equals("precision should be restored", + ostr.precision(), 10); + + // Verify exceptions were restored + ensure_equals("exception bits should be restored", + ostr.exceptions(), original_exceptions); + } + + // Test with no bits set + { + std::ostringstream ostr; + ostr.precision(5); + std::ios_base::iostate original_exceptions = ostr.exceptions(); // 0 + + mSD = "test"; + S32 result = mFormatter->format(mSD, ostr); + + // Verify formatting succeeded + ensure("format should succeed", result >= 0); + + // Verify exceptions were removed + ensure_equals("exception bits should remain unchanged", + ostr.exceptions(), original_exceptions); + + // Verify precision was still restored even on failure + ensure_equals("precision should be restored even on stream failure", + ostr.precision(), (std::streamsize)5); + } + } + class TestLLSDSerializeData { public: diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index dd7d1a043f..ea33178a1e 100644 --- a/indra/llfilesystem/lldiskcache.cpp +++ b/indra/llfilesystem/lldiskcache.cpp @@ -64,9 +64,9 @@ LLDiskCache::LLDiskCache(const std::string& cache_dir, // WARNING: purge() is called by LLPurgeDiskCacheThread. As such it must // NOT touch any LLDiskCache data without introducing and locking a mutex! -// Interaction through the filesystem itself should be safe. Let’s say thread +// Interaction through the filesystem itself should be safe. Let's say thread // A is accessing the cache file for reading/writing and thread B is trimming -// the cache. Let’s also assume using llifstream to open a file and +// the cache. Let's also assume using llifstream to open a file and // boost::filesystem::remove are not atomic (which will be pretty much the // case). diff --git a/indra/llimagej2coj/llimagej2coj.cpp b/indra/llimagej2coj/llimagej2coj.cpp index 7cfadb889d..8d544b360b 100644 --- a/indra/llimagej2coj/llimagej2coj.cpp +++ b/indra/llimagej2coj/llimagej2coj.cpp @@ -227,11 +227,11 @@ static void opj_free_user_data_write(void * user_data) */ static U32 estimate_num_layers(U32 surface) { - if (surface <= 1024) return 2; // Tiny (≤32×32) - else if (surface <= 16384) return 3; // Small (≤128×128) - else if (surface <= 262144) return 4; // Medium (≤512×512) + if (surface <= 1024) return 2; // Tiny (<=32x32) + else if (surface <= 16384) return 3; // Small (<=128x128) + else if (surface <= 262144) return 4; // Medium (<=512x512) else if (surface <= 1048576) return 5; // Up to ~1MP - else return 6; // Up to ~1.5–2MP + else return 6; // Up to ~1.5-2MP } /** diff --git a/indra/llinventory/llsettingssky.cpp b/indra/llinventory/llsettingssky.cpp index 6f7510fef2..4957cf3c02 100644 --- a/indra/llinventory/llsettingssky.cpp +++ b/indra/llinventory/llsettingssky.cpp @@ -653,39 +653,14 @@ void LLSettingsSky::blend(LLSettingsBase::ptr_t &end, F64 blendf) } } + mHasLegacyHaze |= lerp_legacy_float(mHazeHorizon, mLegacyHazeHorizon, other->mHazeHorizon, other->mLegacyHazeHorizon, 0.19f, (F32)blendf); mHasLegacyHaze |= lerp_legacy_float(mHazeDensity, mLegacyHazeDensity, other->mHazeDensity, other->mLegacyHazeDensity, 0.7f, (F32)blendf); mHasLegacyHaze |= lerp_legacy_float(mDistanceMultiplier, mLegacyDistanceMultiplier, other->mDistanceMultiplier, other->mLegacyDistanceMultiplier, 0.8f, (F32)blendf); + mHasLegacyHaze |= lerp_legacy_float(mDensityMultiplier, mLegacyDensityMultiplier, other->mDensityMultiplier, other->mLegacyDensityMultiplier, 0.0001f, (F32)blendf); mHasLegacyHaze |= lerp_legacy_color(mAmbientColor, mLegacyAmbientColor, other->mAmbientColor, other->mLegacyAmbientColor, LLColor3(0.25f, 0.25f, 0.25f), (F32)blendf); + mHasLegacyHaze |= lerp_legacy_color(mBlueHorizon, mLegacyBlueHorizon, other->mBlueHorizon, other->mLegacyBlueHorizon, LLColor3(0.4954f, 0.4954f, 0.6399f), (F32)blendf); mHasLegacyHaze |= lerp_legacy_color(mBlueDensity, mLegacyBlueDensity, other->mBlueDensity, other->mLegacyBlueDensity, LLColor3(0.2447f, 0.4487f, 0.7599f), (F32)blendf); - if (mLegacyHazeHorizon == mLegacyDensityMultiplier == mLegacyBlueHorizon) - { - // mHazeHorizon coupled with mDensityMultiplier, mDistanceMultiplier and - // drastic blue horizon changes can result in a very bright sky during - // the transition. Purpose of this code is to calculate a 'fake level' - // and use it to even out brightness change. - // - // Example values that make lerp-based individual transition painfully bright: - // Start: 3 Haze Horiz, 0.1 Density, 6.54 Distance, white ambient, white blue horizon - // End: 0.03 Haze Horiz, 0.775 Density, 90.95 Distance, black ambient, black blue horizon - F32 strt_level = mHazeHorizon * mDensityMultiplier * mBlueHorizon.length(); - F32 end_level = other->mHazeHorizon * other->mDensityMultiplier * other->mBlueHorizon.length(); - mHasLegacyHaze |= lerp_legacy_float(mHazeHorizon, mLegacyHazeHorizon, other->mHazeHorizon, other->mLegacyHazeHorizon, 0.19f, (F32)blendf); - mHasLegacyHaze |= lerp_legacy_float(mDensityMultiplier, mLegacyDensityMultiplier, other->mDensityMultiplier, other->mLegacyDensityMultiplier, 0.0001f, (F32)blendf); - mHasLegacyHaze |= lerp_legacy_color(mBlueHorizon, mLegacyBlueHorizon, other->mBlueHorizon, other->mLegacyBlueHorizon, LLColor3(0.4954f, 0.4954f, 0.6399f), (F32)blendf); - - // lerp the fake level instead of density multiplier to avoid brightening the sky too much. - // This makes density multiplier non linear. - F32 new_level = lerp(strt_level, end_level, (F32)blendf); - mDensityMultiplier = new_level / (mHazeHorizon * mBlueHorizon.length()); - } - else - { - // default values are used, so we should lerp settings independently - mHasLegacyHaze |= lerp_legacy_float(mHazeHorizon, mLegacyHazeHorizon, other->mHazeHorizon, other->mLegacyHazeHorizon, 0.19f, (F32)blendf); - mHasLegacyHaze |= lerp_legacy_float(mDensityMultiplier, mLegacyDensityMultiplier, other->mDensityMultiplier, other->mLegacyDensityMultiplier, 0.0001f, (F32)blendf); - mHasLegacyHaze |= lerp_legacy_color(mBlueHorizon, mLegacyBlueHorizon, other->mBlueHorizon, other->mLegacyBlueHorizon, LLColor3(0.4954f, 0.4954f, 0.6399f), (F32)blendf); - } parammapping_t defaults = other->getParameterMap(); stringset_t skip = getSkipInterpolateKeys(); stringset_t slerps = getSlerpKeys(); diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp index e7ac6bdb31..21e5bc0ab4 100644 --- a/indra/llkdu/llimagej2ckdu.cpp +++ b/indra/llkdu/llimagej2ckdu.cpp @@ -158,6 +158,9 @@ public: kdu_codestream* codestreamp); ~LLKDUDecodeState(); bool processTileDecode(F32 decode_time, bool limit_time = true); +#ifdef LL_WINDOWS + bool processTileDecodeSEH(F32 decode_time, bool limit_time = true); +#endif private: S32 mNumComponents; @@ -228,11 +231,11 @@ struct LLKDUMessageError : public LLKDUMessage { // According to the documentation nat found: // http://pirlwww.lpl.arizona.edu/resources/guide/software/Kakadu/html_pages/globals__kdu$mize_errors.html - // "If a kdu_error object is destroyed, handler→flush will be called with + // "If a kdu_error object is destroyed, handler->flush will be called with // an end_of_message argument equal to true and the process will // subsequently be terminated through exit. The termination may be // avoided, however, by throwing an exception from within the message - // terminating handler→flush call." + // terminating handler->flush call." // So throwing an exception here isn't arbitrary: we MUST throw an // exception if we want to recover from a KDU error. // Because this confused me: the above quote specifically refers to @@ -282,10 +285,16 @@ void LLImageJ2CKDU::setupCodeStream(LLImageJ2C &base, bool keep_codestream, ECod try { - S32 data_size = base.getDataSize(); S32 max_bytes = (base.getMaxBytes() ? base.getMaxBytes() : data_size); + // Data sanity checks + // 0 data size appears to be valid. + if (data_size < 0 || data_size > 128 * 1024 * 1024) // 128MB + { + LLTHROW(KDUError(STRINGIZE("Invalid data size: " << data_size))); + } + // // Initialization // @@ -385,7 +394,8 @@ void LLImageJ2CKDU::setupCodeStream(LLImageJ2C &base, bool keep_codestream, ECod catch (std::bad_alloc&) { // we are in a thread, can't show an 'out of memory' here, - // main thread will keep going + // as main thread will keep going + // Todo: should cause main thread to stop and show an error. throw; } catch (...) @@ -602,7 +612,11 @@ bool LLImageJ2CKDU::decodeImpl(LLImageJ2C &base, LLImageRaw &raw_image, F32 deco // Do the actual processing F32 remaining_time = limit_time ? decode_time - decode_timer.getElapsedTimeF32().value() : 0.0f; // This is where we do the actual decode. If we run out of time, return false. +#ifdef LL_WINDOWS + if (mDecodeState->processTileDecodeSEH(remaining_time, limit_time)) +#else if (mDecodeState->processTileDecode(remaining_time, limit_time)) +#endif { mDecodeState.reset(); } @@ -1302,6 +1316,11 @@ all necessary level shifting, type conversion, rounding and truncation. */ LLKDUDecodeState::LLKDUDecodeState(kdu_tile tile, kdu_byte *buf, S32 row_gap, kdu_codestream* codestreamp) { + if (!buf) + { + LLTHROW(KDUError("Null buffer passed to LLKDUDecodeState")); + } + S32 c; mTile = tile; @@ -1311,6 +1330,11 @@ LLKDUDecodeState::LLKDUDecodeState(kdu_tile tile, kdu_byte *buf, S32 row_gap, mNumComponents = tile.get_num_components(); llassert(mNumComponents <= 4); + if (mNumComponents <= 0 || mNumComponents > 4) + { + LL_WARNS() << "Invalid component count: " << mNumComponents << ", clamping to valid range" << LL_ENDL; + mNumComponents = llclamp(mNumComponents, 1, 4); + } mUseYCC = tile.get_ycc(); for (c = 0; c < 4; ++c) @@ -1363,6 +1387,20 @@ LLKDUDecodeState::~LLKDUDecodeState() mTile.close(); } +#ifdef LL_WINDOWS +bool LLKDUDecodeState::processTileDecodeSEH(F32 decode_time, bool limit_time) +{ + __try + { + return processTileDecode(decode_time, limit_time); + } + __except (msc_exception_filter(GetExceptionCode(), GetExceptionInformation())) + { + return false; + } +} +#endif + bool LLKDUDecodeState::processTileDecode(F32 decode_time, bool limit_time) /* Decompresses a tile, writing the data into the supplied byte buffer. The buffer contains interleaved image components, if there are any. diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index 01463b4962..db6c5c291f 100644 --- a/indra/llui/llcombobox.cpp +++ b/indra/llui/llcombobox.cpp @@ -521,7 +521,7 @@ bool LLComboBox::setCurrentByIndex(S32 index) if (item->getEnabled()) { mList->selectItem(item, -1, true); - LLSD::String label = item->getColumn(0)->getValue().asString(); + LLSD::String label = getSelectedItemLabel(); if (mTextEntry) { mTextEntry->setText(label); diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 9a88083a5d..404e394ace 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -493,12 +493,13 @@ void LLLineEditor::setCursor( S32 pos ) S32 pixels_after_scroll = findPixelNearestPos(); if( pixels_after_scroll > mTextRightEdge ) { - S32 width_chars_to_left = mGLFont->getWidth(mText.getWString().c_str(), 0, mScrollHPos); - S32 last_visible_char = mGLFont->maxDrawableChars(mText.getWString().c_str(), llmax(0.f, (F32)(mTextRightEdge - mTextLeftEdge + width_chars_to_left))); + const LLWString& wtext = mText.getWString(); + S32 width_chars_to_left = mGLFont->getWidth(wtext.c_str(), 0, mScrollHPos); + S32 last_visible_char = mGLFont->maxDrawableChars(wtext.c_str(), llmax(0.f, (F32)(mTextRightEdge - mTextLeftEdge + width_chars_to_left))); // character immediately to left of cursor should be last one visible (SCROLL_INCREMENT_ADD will scroll in more characters) // or first character if cursor is at beginning S32 new_last_visible_char = llmax(0, getCursor() - 1); - S32 min_scroll = mGLFont->firstDrawableChar(mText.getWString().c_str(), (F32)(mTextRightEdge - mTextLeftEdge), mText.length(), new_last_visible_char); + S32 min_scroll = mGLFont->firstDrawableChar(wtext.c_str(), (F32)(mTextRightEdge - mTextLeftEdge), mText.length(), new_last_visible_char); if (old_cursor_pos == last_visible_char) { mScrollHPos = llmin(mText.length(), llmax(min_scroll, mScrollHPos + SCROLL_INCREMENT_ADD)); diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp index 2100b23783..d09fc3e22d 100644 --- a/indra/llui/llpanel.cpp +++ b/indra/llui/llpanel.cpp @@ -489,58 +489,70 @@ bool LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu LL_RECORD_BLOCK_TIME(FTM_PANEL_SETUP); LLXMLNodePtr referenced_xml; - std::string xml_filename = mXMLFilename; + const std::string& xml_filename = mXMLFilename; // if the panel didn't provide a filename, check the node if (xml_filename.empty()) { - node->getAttributeString("filename", xml_filename); - setXMLFilename(xml_filename); + std::string temp_filename; + node->getAttributeString("filename", temp_filename); + setXMLFilename(temp_filename); } + // Cache singleton and filename to avoid repeated calls + LLUICtrlFactory* factory = LLUICtrlFactory::getInstance(); + + // Cache node name pointer to avoid repeated dereferencing + const LLStringTableEntry* node_name = node->getName(); + + // Cache registry to avoid repeated singleton access + const child_registry_t& registry = child_registry_t::instance(); + LLXUIParser parser; - if (!xml_filename.empty()) + if (!mXMLFilename.empty()) { if (output_node) { //if we are exporting, we want to export the current xml //not the referenced xml - parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); + parser.readXUI(node, params, factory->getCurFileName()); Params output_params(params); setupParamsForExport(output_params, parent); - output_node->setName(node->getName()->mString); + output_node->setName(node_name->mString); parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params); return true; } - LLUICtrlFactory::instance().pushFileName(xml_filename); + factory->pushFileName(mXMLFilename); LL_RECORD_BLOCK_TIME(FTM_EXTERNAL_PANEL_LOAD); - if (!LLUICtrlFactory::getLayeredXMLNode(xml_filename, referenced_xml)) + if (!LLUICtrlFactory::getLayeredXMLNode(mXMLFilename, referenced_xml)) { - LL_WARNS() << "Couldn't parse panel from: " << xml_filename << LL_ENDL; + LL_WARNS() << "Couldn't parse panel from: " << mXMLFilename << LL_ENDL; return false; } - parser.readXUI(referenced_xml, params, LLUICtrlFactory::getInstance()->getCurFileName()); + // Get filename after pushFileName + const std::string& updated_filename = factory->getCurFileName(); + parser.readXUI(referenced_xml, params, updated_filename); // add children using dimensions from referenced xml for consistent layout setShape(params.rect); - LLUICtrlFactory::createChildren(this, referenced_xml, child_registry_t::instance()); + LLUICtrlFactory::createChildren(this, referenced_xml, registry); - LLUICtrlFactory::instance().popFileName(); + factory->popFileName(); } // ask LLUICtrlFactory for filename, since xml_filename might be empty - parser.readXUI(node, params, LLUICtrlFactory::getInstance()->getCurFileName()); + parser.readXUI(node, params, factory->getCurFileName()); if (output_node) { Params output_params(params); setupParamsForExport(output_params, parent); - output_node->setName(node->getName()->mString); + output_node->setName(node_name->mString); parser.writeXUI(output_node, output_params, LLInitParam::default_parse_rules(), &default_params); } @@ -552,7 +564,7 @@ bool LLPanel::initPanelXML(LLXMLNodePtr node, LLView *parent, LLXMLNodePtr outpu } // add children - LLUICtrlFactory::createChildren(this, node, child_registry_t::instance(), output_node); + LLUICtrlFactory::createChildren(this, node, registry, output_node); // Connect to parent after children are built, because tab containers // do a reshape() on their child panels, which requires that the children diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 0521853b02..802367720f 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -2288,6 +2288,7 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) registrar.add("Url.AddFriend", boost::bind(&LLUrlAction::addFriend, url)); registrar.add("Url.RemoveFriend", boost::bind(&LLUrlAction::removeFriend, url)); registrar.add("Url.ReportAbuse", boost::bind(&LLUrlAction::reportAbuse, url)); + registrar.add("Url.ReportAbuseObj", boost::bind(&LLUrlAction::reportAbuseObj, url)); registrar.add("Url.SendIM", boost::bind(&LLUrlAction::sendIM, url)); registrar.add("Url.ZoomInObject", boost::bind(&LLUrlAction::zoomInObject, url)); registrar.add("Url.ShowOnMap", boost::bind(&LLUrlAction::showLocationOnMap, url)); diff --git a/indra/llui/llurlaction.cpp b/indra/llui/llurlaction.cpp index 8b320b59cc..61a529c5d2 100644 --- a/indra/llui/llurlaction.cpp +++ b/indra/llui/llurlaction.cpp @@ -269,13 +269,22 @@ void LLUrlAction::reportAbuse(std::string url) } } +void LLUrlAction::reportAbuseObj(std::string url) +{ + std::string object_id = getObjectId(url); + if (LLUUID::validate(object_id)) + { + executeSLURL("secondlife:///app/object/" + object_id + "/reportAbuse"); + } +} + void LLUrlAction::blockObject(std::string url) { std::string object_id = getObjectId(url); std::string object_name = getObjectName(url); if (LLUUID::validate(object_id)) { - executeSLURL("secondlife:///app/agent/" + object_id + "/block/" + LLURI::escape(object_name)); + executeSLURL("secondlife:///app/object/" + object_id + "/block/" + LLURI::escape(object_name)); } } @@ -285,6 +294,6 @@ void LLUrlAction::unblockObject(std::string url) std::string object_name = getObjectName(url); if (LLUUID::validate(object_id)) { - executeSLURL("secondlife:///app/agent/" + object_id + "/unblock/" + object_name); + executeSLURL("secondlife:///app/object/" + object_id + "/unblock/" + LLURI::escape(object_name)); } } diff --git a/indra/llui/llurlaction.h b/indra/llui/llurlaction.h index c4cfd0f3fb..d110fe56d3 100644 --- a/indra/llui/llurlaction.h +++ b/indra/llui/llurlaction.h @@ -89,6 +89,7 @@ public: static void addFriend(std::string url); static void removeFriend(std::string url); static void reportAbuse(std::string url); + static void reportAbuseObj(std::string url); static void blockObject(std::string url); static void unblockObject(std::string url); diff --git a/indra/llui/llxyvector.cpp b/indra/llui/llxyvector.cpp index 1521823ce2..b3c38bf82d 100644 --- a/indra/llui/llxyvector.cpp +++ b/indra/llui/llxyvector.cpp @@ -211,7 +211,16 @@ void LLXYVector::draw() mGhostY = pointY; } - if (abs(mValueX) >= mIncrementX || abs(mValueY) >= mIncrementY) + S32 dx = abs(pointX - centerX); + S32 dy = abs(pointY - centerY); + bool draw_arrow = + (abs(mValueX) >= mIncrementX || abs(mValueY) >= mIncrementY) + && (dx >= 1 || dy >= 1); // At least 1 pixel displacement + + // Todo: Arrow doesn't display well with small values. + // Ex: (0.1, 0.05) will point to the right, as if it is (0.1, 0.0) + // as position will be offset by a single pixes on X, and 0 pixels on Y. + if (draw_arrow) { // draw the vector arrow drawArrow(centerX, centerY, pointX, pointY, mArrowColor); diff --git a/indra/llwindow/llwindowcallbacks.cpp b/indra/llwindow/llwindowcallbacks.cpp index 7331f50ba0..6267433751 100644 --- a/indra/llwindow/llwindowcallbacks.cpp +++ b/indra/llwindow/llwindowcallbacks.cpp @@ -68,6 +68,10 @@ void LLWindowCallbacks::handleMouseLeave(LLWindow *window) return; } +void LLWindowCallbacks::handlePreCloseRequest() +{ +} + bool LLWindowCallbacks::handleCloseRequest(LLWindow *window, bool from_user) { //allow the window to close diff --git a/indra/llwindow/llwindowcallbacks.h b/indra/llwindow/llwindowcallbacks.h index 59dcdd3ade..457087448f 100644 --- a/indra/llwindow/llwindowcallbacks.h +++ b/indra/llwindow/llwindowcallbacks.h @@ -41,6 +41,8 @@ public: virtual bool handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); virtual bool handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); virtual void handleMouseLeave(LLWindow *window); + // Called before close request is processed (ex: to create marker file in case OS is about to kill app). + virtual void handlePreCloseRequest(); // return true to allow window to close, which will then cause handleQuit to be called virtual bool handleCloseRequest(LLWindow *window, bool from_user); virtual bool handleSessionExit(LLWindow* window); diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index c185fc6c4a..52e0cdd5d5 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -508,6 +508,7 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks, : LLWindow(callbacks, fullscreen, flags), mAbsoluteCursorPosition(false), + mReceivedSCClose(false), mMaxGLVersion(max_gl_version), mMaxCores(max_cores) { @@ -1420,7 +1421,7 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo catch (...) { LOG_UNHANDLED_EXCEPTION("ChoosePixelFormat"); - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1431,7 +1432,7 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR), &pfd)) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1469,7 +1470,7 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo if (!SetPixelFormat(mhDC, pixel_format, &pfd)) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1477,14 +1478,14 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo if (!(mhRC = SafeCreateContext(mhDC))) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } if (!wglMakeCurrent(mhDC, mhRC)) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1690,14 +1691,14 @@ const S32 max_format = (S32)num_formats - 1; if (!mhDC) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBDevContextErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBDevContextErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } if (!SetPixelFormat(mhDC, pixel_format, &pfd)) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1729,7 +1730,7 @@ const S32 max_format = (S32)num_formats - 1; { LL_WARNS("Window") << "No wgl_ARB_pixel_format extension!" << LL_ENDL; // cannot proceed without wgl_ARB_pixel_format extension, shutdown same as any other gGLManager.initGL() failure - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1738,7 +1739,7 @@ const S32 max_format = (S32)num_formats - 1; if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR), &pfd)) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1760,14 +1761,14 @@ const S32 max_format = (S32)num_formats - 1; if (!wglMakeCurrent(mhDC, mhRC)) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } if (!gGLManager.initGL()) { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); close(); return false; } @@ -1975,7 +1976,7 @@ void* LLWindowWin32::createSharedContext() if (!rc && !(rc = wglCreateContext(mhDC))) { close(); - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/); + LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED); } return rc; @@ -2524,8 +2525,15 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_SYSCOMMAND: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SYSCOMMAND"); - switch (w_param) + switch (w_param & 0xFFF0) { + case SC_CLOSE: + // User clicked close from system menu/taskbar or 'end process' from task manager + // Do nothing, will cause WM_CLOSE. + // If we don't get this message before WM_CLOSE, we are likely getting + // a kill from some external program. Win11 task manager Does cause SC_CLOSE. + window_imp->mReceivedSCClose = true; + break; case SC_KEYMENU: // Disallow the ALT key from triggering the default system menu. return 0; @@ -2540,9 +2548,30 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_CLOSE: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_CLOSE"); - // todo: WM_CLOSE can be caused by user and by task manager, - // distinguish these cases. - // For now assume it is always user. + + window_imp->mCallbacks->handlePreCloseRequest(); // mark app as potentially closing + if (!window_imp->mReceivedSCClose) + { + // Some external program is trying to close the app. + // Assume that it's going to destroy process if it fails + // and try to fast-quit without confirmation or cleanup. + window_imp->post([=]() + { + // Check if app needs cleanup or can be closed immediately. + if (window_imp->mCallbacks->handleSessionExit(window_imp)) + { + // Get the app to initiate cleanup. + window_imp->mCallbacks->handleQuit(window_imp); + } + }); + return 0; + } + window_imp->mReceivedSCClose = false; + + // There is no way to tell the difference between a user issued + // WM_CLOSE or task manager's WM_CLOSE. + // Assume it is a user and ask for confirmation, but create a marker file. + // If App keeps doing something after a second, or gets 'destroy' message clear the marker. window_imp->post([=]() { // Will the app allow the window to close? @@ -2564,6 +2593,16 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ } return 0; } + case WM_NCDESTROY: + LL_INFOS("Window") << "Received WM_NCDESTROY" << LL_ENDL; + break; + case WM_WTSSESSION_CHANGE: + { + // Detects Remote Desktop disconnects, fast user switching, session logoff + // w_param: WTS_CONSOLE_CONNECT, WTS_CONSOLE_DISCONNECT, WTS_SESSION_LOGOFF, etc. + LL_INFOS("Window") << "Received WM_WTSSESSION_CHANGE with wParam: " << (U32)w_param << LL_ENDL; + break; + } case WM_QUERYENDSESSION: { // Generally means that OS is going to shut down or user is going to log off. @@ -2585,8 +2624,10 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ || (end_session_flags & ENDSESSION_CRITICAL) // will shutdown regardless of app state || (end_session_flags & ENDSESSION_LOGOFF)) // logoff, can delay shutdown { + window_imp->mCallbacks->handlePreCloseRequest(); // mark app as closing window_imp->post([=]() { + LL_INFOS("Window") << "Shutting down due to session terminating" << LL_ENDL; // Check if app needs cleanup or can be closed immediately. if (window_imp->mCallbacks->handleSessionExit(window_imp)) { @@ -2605,6 +2646,191 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ // if session is ending OS is going to take care of it. return 0; } + case WM_POWERBROADCAST: + { + LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_POWERBROADCAST"); + switch (w_param) + { + case PBT_APMSUSPEND: + LL_INFOS("Window") << "System is suspending (sleep/hibernate)" << LL_ENDL; + // System is about to enter sleep or hibernation + // Viewer can't function in hibernation, try to shut down. + // The system allows approximately two seconds for an + // application to handle this notification. + window_imp->post([=]() + { + LL_INFOS("Window") << "Shutting down due to system suspending (sleep/hibernate)" << LL_ENDL; + if (window_imp->mCallbacks->handleSessionExit(window_imp)) + { + // Get the app to initiate cleanup. + window_imp->mCallbacks->handleQuit(window_imp); + } + }); + ms_sleep(1000); + return TRUE; + + case PBT_APMRESUMESUSPEND: + LL_INFOS("Window") << "System is resuming from suspend" << LL_ENDL; + // Shouldn't be up, but log just in case. + return TRUE; + + case PBT_APMPOWERSTATUSCHANGE: + LL_INFOS("Window") << "Power status has changed" << LL_ENDL; + // Power status change (AC/battery) + // Viewer requires high performance, not much we can do. + // about it, but log for diagnostic purposes (example: + // OS trying to throw viewer at an iGPU after this message) + return TRUE; + + default: + break; + } + break; + } + case WM_POST_UNINSTALL_: + { + LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_POST_UNINSTALL_"); + // Other instance, likely velopack, requested we quit. + // Don't trust PID alone (can be spoofed), verify the + // path for security purposes before processing. + // Verifying path isn't a strong varranty, if this turns + // up to be a risk, we will want something more secure. + // See sendShutdownToOtherInstances for the sender. + + // LPARAM contains message type. + DWORD message_type = static_cast<DWORD>(l_param); + if (message_type == WM_POST_UNINSTALL_MSG_SHUTDOWN || message_type == WM_POST_UNINSTALL_MSG_UPDATE) + { + DWORD sender_process_id = static_cast<DWORD>(w_param); + + // Make sure something didn't just send us our own process + DWORD our_process_id = GetCurrentProcessId(); + if (our_process_id == sender_process_id) + { + LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ from our own process, ignoring" << LL_ENDL; + break; + } + + if (sender_process_id == 0) + { + LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ but couldn't get sender process ID" << LL_ENDL; + break; + } + + // Open the existing sender process to verify its executable path + HANDLE hSenderProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, sender_process_id); + if (!hSenderProcess) + { + LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ but couldn't open sender process" << LL_ENDL; + break; + } + + // Get the actual executable path of the sender + wchar_t sender_exe_path[MAX_PATH]; + DWORD size = MAX_PATH; + bool got_sender_path = QueryFullProcessImageNameW(hSenderProcess, 0, sender_exe_path, &size) != 0; + CloseHandle(hSenderProcess); + + if (!got_sender_path) + { + LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ but couldn't query sender executable path" << LL_ENDL; + break; + } + + // Extract directory from sender's executable path + wchar_t sender_dir[MAX_PATH]; + wchar_t* file_part = nullptr; + DWORD result = GetFullPathNameW(sender_exe_path, MAX_PATH, sender_dir, &file_part); + + if (result == 0 || result >= MAX_PATH) + { + LL_WARNS("Window") << "Failed to normalize sender executable path" << LL_ENDL; + break; + } + + // Remove the filename to get just directory + if (file_part) + { + *file_part = L'\0'; + } + + // Remove trailing backslash + size_t sender_dir_len = wcslen(sender_dir); + if (sender_dir_len > 0 && sender_dir[sender_dir_len - 1] == L'\\') + { + sender_dir[sender_dir_len - 1] = L'\0'; + sender_dir_len--; + } + + // Remove "\current" suffix from sender's path if present + const std::wstring current_suffix = L"\\current"; + std::wstring sender_normalized_str(sender_dir); + if (sender_normalized_str.length() >= current_suffix.length() && + _wcsicmp(sender_normalized_str.c_str() + sender_normalized_str.length() - current_suffix.length(), + current_suffix.c_str()) == 0) + { + sender_normalized_str.resize(sender_normalized_str.length() - current_suffix.length()); + } + + // Get our executable directory for comparison + std::wstring our_wide = ll_convert<std::wstring>(gDirUtilp->getExecutableDir()); + + // Normalize our path + wchar_t our_normalized[MAX_PATH]; + file_part = nullptr; + + DWORD result2 = GetFullPathNameW(our_wide.c_str(), MAX_PATH, our_normalized, &file_part); + + if (result2 == 0 || result2 >= MAX_PATH) + { + LL_WARNS("Window") << "Failed to normalize our executable path" << LL_ENDL; + break; + } + + // Remove trailing backslash + size_t our_len = wcslen(our_normalized); + if (our_len > 0 && our_normalized[our_len - 1] == L'\\') + { + our_normalized[our_len - 1] = L'\0'; + our_len--; + } + + // Remove "\current" suffix from our path if present + std::wstring our_normalized_str(our_normalized); + if (our_normalized_str.length() >= current_suffix.length() && + _wcsicmp(our_normalized_str.c_str() + our_normalized_str.length() - current_suffix.length(), + current_suffix.c_str()) == 0) + { + our_normalized_str.resize(our_normalized_str.length() - current_suffix.length()); + } + + // Compare the normalized base installation paths (case-insensitive) + if (_wcsicmp(sender_normalized_str.c_str(), our_normalized_str.c_str()) == 0) + { + window_imp->post([=]() + { + LL_INFOS("Window") << "Received valid shutdown request from verified same installation directory" << LL_ENDL; + // Check if app needs cleanup or can be closed immediately. + if (window_imp->mCallbacks->handleCloseRequest(window_imp, false)) + { + // Get the app to initiate cleanup. + window_imp->mCallbacks->handleQuit(window_imp); + } + }); + } + else + { + LL_WARNS("Window") << "Rejected shutdown request - sender not from our installation directory. " + << "Sender: " << ll_convert_wide_to_string(sender_normalized_str) + << " Our: " << ll_convert_wide_to_string(our_normalized_str) << LL_ENDL; + } + } + else + { + LL_WARNS("Window") << "Received invalid WM_POST_UNINSTALL_ message" << LL_ENDL; + } + break; + } case WM_COMMAND: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_COMMAND"); @@ -3132,7 +3358,18 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_DISPLAYCHANGE: { - WINDOW_IMP_POST(window_imp->mCallbacks->handleDisplayChanged()); + LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DISPLAYCHANGE"); + window_imp->post([=]() { + window_imp->mCallbacks->handleDisplayChanged(); + // Note: WM_DISPLAYCHANGE was passing to WM_SETFOCUS + // which might have been unintended and was messing with zones. + // handleFocus was copied over and return 0 added, but + // handleFocus might be not needed here. + // handleFocus resets mouse, closes popups and keys, which + // we probablt should do on 'display change'. + window_imp->mCallbacks->handleFocus(window_imp); + }); + return 0; } case WM_SETFOCUS: diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h index aab2635a34..defa90d1a3 100644 --- a/indra/llwindow/llwindowwin32.h +++ b/indra/llwindow/llwindowwin32.h @@ -40,6 +40,12 @@ // Hack for async host by name #define LL_WM_HOST_RESOLVED (WM_APP + 1) +// For requesting shutdown on uninstall, +// make sure it does not conflict with messages like WM_DUMMY_ +inline constexpr UINT WM_POST_UNINSTALL_ = WM_USER + 0x0019; +inline constexpr DWORD WM_POST_UNINSTALL_MSG_SHUTDOWN = 1; +inline constexpr DWORD WM_POST_UNINSTALL_MSG_UPDATE = 2; + typedef void (*LLW32MsgCallback)(const MSG &msg); class LLWindowWin32 : public LLWindow @@ -232,6 +238,7 @@ protected: LPWSTR mIconResource; LPWSTR mIconSmallResource; bool mInputProcessingPaused; + bool mReceivedSCClose; // received SC_CLOSE and expecting WM_CLOSE // The following variables are for Language Text Input control. // They are all static, since one context is shared by all LLWindowWin32 diff --git a/indra/llxml/llcontrol.h b/indra/llxml/llcontrol.h index c2bcd20c85..c2a7c22bbc 100644 --- a/indra/llxml/llcontrol.h +++ b/indra/llxml/llcontrol.h @@ -326,7 +326,7 @@ public: { if(!group.controlExists(name)) { - LL_ERRS() << "Control named " << name << "not found." << LL_ENDL; + LL_ERRS() << "Control named " << name << " not found." << LL_ENDL; } bindToControl(group, name); diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 8e5e146c03..18a40bcf1e 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -243,7 +243,6 @@ set(viewer_SOURCE_FILES llfloaterhandler.cpp llfloaterhelpbrowser.cpp llfloaterhoverheight.cpp - llfloaterhowto.cpp llfloaterhud.cpp llfloaterimagepreview.cpp llfloaterimsessiontab.cpp @@ -923,7 +922,6 @@ set(viewer_HEADER_FILES llfloaterhandler.h llfloaterhelpbrowser.h llfloaterhoverheight.h - llfloaterhowto.h llfloaterhud.h llfloaterimagepreview.h llfloaterimnearbychat.h @@ -2182,7 +2180,7 @@ if (DARWIN) set(MACOSX_BUNDLE_BUNDLE_NAME "SecondLife") set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}") set(MACOSX_BUNDLE_BUNDLE_VERSION "${VIEWER_SHORT_VERSION}${VIEWER_MACOSX_PHASE}${VIEWER_REVISION}") - set(MACOSX_BUNDLE_COPYRIGHT "Copyright © Linden Research, Inc. 2020") + set(MACOSX_BUNDLE_COPYRIGHT "Copyright © Linden Research, Inc. 2026") set(MACOSX_BUNDLE_NSMAIN_NIB_FILE "SecondLife.nib") set(MACOSX_BUNDLE_NSPRINCIPAL_CLASS "LLApplication") @@ -2482,4 +2480,3 @@ if (LL_TESTS) endif (LL_TESTS) check_message_template(${VIEWER_BINARY_NAME}) - diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml index 7bcfecf9fa..952818144d 100644 --- a/indra/newview/app_settings/commands.xml +++ b/indra/newview/app_settings/commands.xml @@ -83,16 +83,6 @@ is_running_function="Floater.IsOpen" is_running_parameters="gestures" /> - <command name="howto" - available_in_toybox="true" - icon="Command_HowTo_Icon" - label_ref="Command_HowTo_Label" - tooltip_ref="Command_HowTo_Tooltip" - execute_function="Floater.ToggleOrBringToFront" - execute_parameters="guidebook" - is_running_function="Floater.IsOpen" - is_running_parameters="guidebook" - /> <command name="inventory" available_in_toybox="true" icon="Command_Inventory_Icon" diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index c69f4a0c21..ccaa12b8ef 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -492,7 +492,7 @@ <key>RecentJumpThresholdSecs</key> <map> <key>Comment</key> - <string>Seconds after a jump input during which finish-anim is suppressed to avoid interrupting rapid successive jumps.</string> + <string>Seconds after jump input during which landing finish-anim is suppressed to avoid interrupting rapid successive jumps.</string> <key>Persist</key> <integer>1</integer> <key>Type</key> @@ -3445,17 +3445,6 @@ <key>Value</key> <string>https://viewer-help.secondlife.com/[LANGUAGE]/[CHANNEL]/[VERSION]/[TOPIC][DEBUG_MODE]</string> </map> - <key>HowToHelpURL</key> - <map> - <key>Comment</key> - <string>URL for How To help content</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>String</string> - <key>Value</key> - <string>https://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/howto/index.html</string> - </map> <key>HomeSidePanelURL</key> <map> <key>Comment</key> @@ -3478,17 +3467,6 @@ <key>Value</key> <string>https://search.[GRID]/viewer/?query_term=[QUERY]&search_type=[TYPE][COLLECTION]&maturity=[MATURITY]&lang=[LANGUAGE]&g=[GODLIKE]&sid=[SESSION_ID]&rid=[REGION_ID]&pid=[PARCEL_ID]&channel=[CHANNEL]&version=[VERSION]&major=[VERSION_MAJOR]&minor=[VERSION_MINOR]&patch=[VERSION_PATCH]&build=[VERSION_BUILD]</string> </map> - <key>GuidebookURL</key> - <map> - <key>Comment</key> - <string>URL for Guidebook content</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>String</string> - <key>Value</key> - <string>http://guidebooks.secondlife.io/welcome/index.html</string> - </map> <key>HighResSnapshot</key> <map> <key>Comment</key> @@ -9063,6 +9041,17 @@ <key>Value</key> <integer>0</integer> </map> + <key>NametagOverWater</key> + <map> + <key>Comment</key> + <string>Render name tag over the transparent water while camera is above the water</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> <key>RenderInitError</key> <map> <key>Comment</key> @@ -13300,6 +13289,17 @@ <key>Value</key> <integer>0</integer> </map> + <key>SwitchToSharedEnvAfterTeleport</key> + <map> + <key>Comment</key> + <string>Switch to Shared Environment after teleport</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> <key>PreferredBrowserBehavior</key> <map> <key>Comment</key> diff --git a/indra/newview/app_settings/toolbars.xml b/indra/newview/app_settings/toolbars.xml index a1c9d6d9ee..f22c25f3a2 100644 --- a/indra/newview/app_settings/toolbars.xml +++ b/indra/newview/app_settings/toolbars.xml @@ -10,7 +10,6 @@ <command name="map"/> <command name="move"/> <command name="view"/> - <command name="howto"/> </bottom_toolbar> <left_toolbar button_display_mode="icons_only"> diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 5a94a2c6c6..54ad8c894b 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -1398,6 +1398,24 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) buildOverrideMatrix(viewer_data, joints_data, names_to_nodes, ident, ident); } + // Precompute the source bind pose in viewer coordinate space so each + // joint can later be remapped against its own GLTF rest matrix. This + // keeps bind-pose deltas local to the joint hierarchy. Without this, + // a source skeleton whose parent joints carry bind-pose rotations that + // the viewer skeleton does not have can leak those rotations into + // child joints with different local bases. + joint_node_mat4_map_t rotated_bind_matrices; + joint_node_mat4_map_t converted_bind_matrices; + if (inverse_count > 0) + { + for (S32 i = 0; i < joint_count && i < inverse_count; i++) + { + S32 joint = skin.mJoints[i]; + glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); + rotated_bind_matrices[joint] = rotateGltfMatrixToViewerSpace(original_bind_matrix); + } + } + for (S32 i = 0; i < joint_count; i++) { S32 joint = skin.mJoints[i]; @@ -1430,12 +1448,19 @@ void LLGLTFLoader::populateJointsFromSkin(S32 skin_idx) } else if (inverse_count > i) { - // Transalte existing bind matrix to viewer's overriden skeleton - glm::mat4 original_bind_matrix = glm::inverse(skin.mInverseBindMatricesData[i]); - glm::mat4 rotated_original = coord_system_rotation * original_bind_matrix; - glm::mat4 skeleton_transform = computeGltfToViewerSkeletonTransform(joints_data, joint, legal_name); - glm::mat4 tranlated_original = skeleton_transform * rotated_original; - glm::mat4 final_inverse_bind_matrix = glm::inverse(tranlated_original); + // Translate existing bind matrices to the viewer skeleton by + // preserving each joint's bind-pose delta from its own GLTF rest + // matrix. The previous world-space remap applied one skeleton + // transform directly to each bind matrix; when a source parent has + // bind-pose rotation that the viewer parent does not, that rotation + // could get baked into child inverse binds. Keeping the delta local + // fixes the resulting child joint twists. + glm::mat4 translated_original = computeViewerBindMatrix( + joints_data, + rotated_bind_matrices, + joint, + converted_bind_matrices); + glm::mat4 final_inverse_bind_matrix = glm::inverse(translated_original); LLMatrix4 gltf_transform = LLMatrix4(glm::value_ptr(final_inverse_bind_matrix)); LL_DEBUGS("GLTF_DEBUG") << "mInvBindMatrix name: " << legal_name << " Translated val: " << gltf_transform << LL_ENDL; @@ -1551,6 +1576,12 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map glm::quat rotation; glm::decompose(translated_joint, scale, rotation, translation_override, skew, perspective); + glm::mat4 viewer_rotation_scale(1.0f); + viewer_rotation_scale = glm::rotate(viewer_rotation_scale, glm::radians(viewer_data.mRotation[0]), glm::vec3(1, 0, 0)); + viewer_rotation_scale = glm::rotate(viewer_rotation_scale, glm::radians(viewer_data.mRotation[1]), glm::vec3(0, 1, 0)); + viewer_rotation_scale = glm::rotate(viewer_rotation_scale, glm::radians(viewer_data.mRotation[2]), glm::vec3(0, 0, 1)); + viewer_rotation_scale = glm::scale(viewer_rotation_scale, viewer_data.mScale); + // Viewer allows overrides, which are base joint with applied translation override. // fortunately normal bones use only translation, without rotation or scale node.mOverrideMatrix = glm::recompose(glm::vec3(1, 1, 1), glm::identity<glm::quat>(), translation_override, glm::vec3(0, 0, 0), glm::vec4(0, 0, 0, 1)); @@ -1566,13 +1597,14 @@ void LLGLTFLoader::buildOverrideMatrix(LLJointData& viewer_data, joints_data_map } else { - // This is likely incomplete or even wrong. - // Viewer Collision bones specify rotation and scale. - // Importer should apply rotation and scale to this matrix and save as needed - // then subsctruct them from bind matrix - // Todo: get models that use collision bones, made by different programs - - overriden_joint = glm::scale(overriden_joint, viewer_data.mScale); + // Collision volumes need the imported translation override, but + // their local rotation-scale basis must come from the raw viewer + // skeleton XML values. For non-uniform torso volumes, matching DAE + // requires viewer rotation followed by viewer scale. + overriden_joint = viewer_rotation_scale; + overriden_joint[3][0] = translation_override.x; + overriden_joint[3][1] = translation_override.y; + overriden_joint[3][2] = translation_override.z; node.mOverrideRestMatrix = parent_support_rest * overriden_joint; } } @@ -1651,6 +1683,69 @@ glm::mat4 LLGLTFLoader::buildGltfRestMatrix(S32 joint_node_index, const joints_d return data.mGltfMatrix; } +// Convert a GLTF-space transform into the viewer coordinate space used by +// skeleton override and bind-pose calculations. Keeping this conversion in +// one helper ensures bind and rest matrices use the same rotation path, +// including the optional XY rotation applied for compatible uploads. +glm::mat4 LLGLTFLoader::rotateGltfMatrixToViewerSpace(const glm::mat4& gltf_matrix) const +{ + glm::mat4 rotated = coord_system_rotation * gltf_matrix; + if (mApplyXYRotation) + { + rotated = coord_system_rotationxy * rotated; + } + return rotated; +} + +// Convert one GLTF bind matrix to the matching viewer bind matrix. The +// function derives the joint-local bind delta from the GLTF bind and rest +// matrices, then applies that same delta to the viewer override rest matrix. +// Results are cached because child calculations can request the same joint +// more than once while preserving local hierarchy behavior. +glm::mat4 LLGLTFLoader::computeViewerBindMatrix( + const joints_data_map_t& joints_data_map, + const joint_node_mat4_map_t& rotated_bind_matrices, + S32 gltf_node_index, + joint_node_mat4_map_t& converted_bind_matrices) const +{ + auto cached = converted_bind_matrices.find(gltf_node_index); + if (cached != converted_bind_matrices.end()) + { + return cached->second; + } + + auto node_iter = joints_data_map.find(gltf_node_index); + if (node_iter == joints_data_map.end()) + { + return glm::mat4(1.f); + } + + const JointNodeData& node_data = node_iter->second; + if (!node_data.mIsOverrideValid) + { + // No valid override, falling back to rest matrix. + glm::mat4 fallback_bind = rotateGltfMatrixToViewerSpace(node_data.mGltfRestMatrix); + converted_bind_matrices[gltf_node_index] = fallback_bind; + return fallback_bind; + } + + auto bind_iter = rotated_bind_matrices.find(gltf_node_index); + if (bind_iter == rotated_bind_matrices.end()) + { + converted_bind_matrices[gltf_node_index] = node_data.mOverrideRestMatrix; + return node_data.mOverrideRestMatrix; + } + + glm::mat4 gltf_joint_node = rotateGltfMatrixToViewerSpace(node_data.mGltfRestMatrix); + glm::mat4 gltf_joint_bind = bind_iter->second; + glm::mat4 viewer_joint_node = node_data.mOverrideRestMatrix; + glm::mat4 bind_delta = gltf_joint_bind * glm::inverse(gltf_joint_node); + glm::mat4 viewer_joint_bind = bind_delta * viewer_joint_node; + + converted_bind_matrices[gltf_node_index] = viewer_joint_bind; + return viewer_joint_bind; +} + // This function computes the transformation matrix needed to convert from GLTF skeleton space // to viewer skeleton space for a specific joint @@ -1903,4 +1998,3 @@ std::string LLGLTFLoader::getLodlessLabel(const LL::GLTF::Node& node) } return node.mName; } - diff --git a/indra/newview/gltf/llgltfloader.h b/indra/newview/gltf/llgltfloader.h index a847e567a6..d8aad79883 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -160,6 +160,8 @@ private: void buildOverrideMatrix(LLJointData& data, joints_data_map_t &gltf_nodes, joints_name_to_node_map_t &names_to_nodes, glm::mat4& parent_rest, glm::mat4& support_rest) const; glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const LL::GLTF::Skin& gltf_skin) const; glm::mat4 buildGltfRestMatrix(S32 joint_node_index, const joints_data_map_t& joint_data) const; + glm::mat4 rotateGltfMatrixToViewerSpace(const glm::mat4& gltf_matrix) const; + glm::mat4 computeViewerBindMatrix(const joints_data_map_t& joints_data_map, const joint_node_mat4_map_t& rotated_bind_matrices, S32 gltf_node_index, joint_node_mat4_map_t& converted_bind_matrices) const; glm::mat4 computeGltfToViewerSkeletonTransform(const joints_data_map_t& joints_data_map, S32 gltf_node_index, const std::string& joint_name) const; bool checkForXYrotation(const LL::GLTF::Skin& gltf_skin, S32 joint_idx, S32 bind_indx); void checkForXYrotation(const LL::GLTF::Skin& gltf_skin); diff --git a/indra/newview/licenses-linux.txt b/indra/newview/licenses-linux.txt index a199ed410c..8a410d22a3 100644 --- a/indra/newview/licenses-linux.txt +++ b/indra/newview/licenses-linux.txt @@ -632,3 +632,373 @@ supporting the PNG file format in commercial products. If you use this source code in a product, acknowledgment is not required but would be appreciated. +================= +Vivox SDK License +================= + +RSA Data Security, Inc. MD5 Message-Digest Algorithm + +Audio coding: Polycom(R) Siren14TM (ITU-T Rec. G.722.1 Annex C) + +Open Source Software Licensing +Each open source software component utilized by this product is subject to its own copyright and licensing terms, as listed below. + +************************************************************* +************************************************************* + +/** + * OpenAL cross platform audio library + * Copyright (C) 1999-2000 by authors. + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + * Or go to http://www.gnu.org/copyleft/lgpl.html + */ + +************************************************************* +************************************************************* +RTP code under Lesser General Public License + +/* + The oRTP library is an RTP (Realtime Transport Protocol - rfc3550) stack. + Copyright (C) 2001 Simon MORLAT simon.morlat@linphone.org + + 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; either + version 2.1 of the License, or (at your option) any later version. + + 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +************************************************************ +************************************************************* + +/* + * The Vovida Software License, Version 1.0 + * + * Copyright (c) 2000 Vovida Networks, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The names "VOCAL", "Vovida Open Communication Application Library", + * and "Vovida Open Communication Application Library (VOCAL)" must + * not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact vocal@vovida.org. + * + * 4. Products derived from this software may not be called "VOCAL", nor + * may "VOCAL" appear in their name, without prior written + * permission of Vovida Networks, Inc. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL VOVIDA + * NETWORKS, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT DAMAGES + * IN EXCESS OF $1,000, NOR FOR ANY INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * + * This software consists of voluntary contributions made by Vovida + * Networks, Inc. and many individuals on behalf of Vovida Networks, + * Inc. For more information on Vovida Networks, Inc., please see + * + * + */ +************************************************************* +************************************************************* + +Internet Software Consortium code + +/* This is from the BIND 4.9.4 release, modified to compile by itself */ +/* Copyright (c) 1996 by Internet Software Consortium. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS + * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE + * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL + * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR + * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS + * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS + * SOFTWARE. + */ + +************************************************************* + +************************************************************* + +************************************************************ + +http://tinyxpath.sourceforge.net/ + +TinyXPath is covered by the zlib license : + + www.sourceforge.net/projects/tinyxpath + Copyright (c) 2002-2006 Yves Berquin (yvesb@users.sourceforge.net) + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any + damages arising from the use of this software. + + Permission is granted to anyone to use this software for any + purpose, including commercial applications, and to alter it and + redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must + not claim that you wrote the original software. If you use this + software in a product, an acknowledgment in the product documentation + would be appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and + must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any source distribution. + + +************************************************************ +************************************************************ + +THE FREE SOFTWARE FOUNDATION + +Any customer may request the source code for all open source portions of this product which are covered by the Free Software Foundation's General Public License (GPL), for a period of three years from purchase. Please contact the vendor from whom you obtained this product for instructions. A fee equivalent to the cost of making the code available may be charged. Alternatively, customers may choose to download desired GPL components directly from their original vendors. Specifically, this product contains the following GPL-licensed components: + + +From Vivox: + - Assorted software components. To request source, contact Vivox at: + Vivox, Inc. + Attn: customer support + 40 Speen Street Suite 402 + Framingham, MA 01701 + +DejaVu Fonts +MIT License + +Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. +Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) + + +Bitstream Vera Fonts Copyright +------------------------------ + +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is +a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the fonts accompanying this license ("Fonts") and associated +@@ -41,17 +42,17 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE +FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome +Foundation, and Bitstream Inc., shall not be used in advertising or +otherwise to promote the sale, use or other dealings in this Font Software +without prior written authorization from the Gnome Foundation or Bitstream +Inc., respectively. For further information, contact: fonts at gnome dot +org. + +Arev Fonts Copyright +------------------------------ + +Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the fonts accompanying this license ("Fonts") and +@@ -91,9 +92,96 @@ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the name of Tavmjong Bah shall not +be used in advertising or otherwise to promote the sale, use or other +dealings in this Font Software without prior written authorization +from Tavmjong Bah. For further information, contact: tavmjong @ free +. fr. + +Twemoji Fonts +MIT License + +Applies to "EmojiOne SVGinOT Font" code only +Copyright (c) 2022 Brad Erickson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. + +WebRTC + +Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Google fonts +SIL OPEN FONT LICENSE Version 1.1 + +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/indra/newview/licenses-mac.txt b/indra/newview/licenses-mac.txt index 0bd9ebc5f1..c2f52e3b90 100644 --- a/indra/newview/licenses-mac.txt +++ b/indra/newview/licenses-mac.txt @@ -593,3 +593,205 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +============== +DejaVu Fonts +============== +MIT License + +Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. +Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) + + +Bitstream Vera Fonts Copyright +------------------------------ + +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is +a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the fonts accompanying this license ("Fonts") and associated +@@ -41,17 +42,17 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE +FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome +Foundation, and Bitstream Inc., shall not be used in advertising or +otherwise to promote the sale, use or other dealings in this Font Software +without prior written authorization from the Gnome Foundation or Bitstream +Inc., respectively. For further information, contact: fonts at gnome dot +org. + +Arev Fonts Copyright +------------------------------ + +Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the fonts accompanying this license ("Fonts") and +@@ -91,9 +92,96 @@ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the name of Tavmjong Bah shall not +be used in advertising or otherwise to promote the sale, use or other +dealings in this Font Software without prior written authorization +from Tavmjong Bah. For further information, contact: tavmjong @ free +. fr. + +============== +Twemoji Fonts +============== +MIT License + +Applies to "EmojiOne SVGinOT Font" code only +Copyright (c) 2022 Brad Erickson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. + +============== +WebRTC +============== + +Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +============== +Google fonts +============== +SIL OPEN FONT LICENSE Version 1.1 + +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + diff --git a/indra/newview/licenses-win32.txt b/indra/newview/licenses-win32.txt index b56f1bf2eb..2d4bd8a4b6 100644 --- a/indra/newview/licenses-win32.txt +++ b/indra/newview/licenses-win32.txt @@ -667,3 +667,205 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +============== +DejaVu Fonts +============== +MIT License + +Fonts are (c) Bitstream (see below). DejaVu changes are in public domain. +Glyphs imported from Arev fonts are (c) Tavmjong Bah (see below) + + +Bitstream Vera Fonts Copyright +------------------------------ + +Copyright (c) 2003 by Bitstream, Inc. All Rights Reserved. Bitstream Vera is +a trademark of Bitstream, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of the fonts accompanying this license ("Fonts") and associated +@@ -41,17 +42,17 @@ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF +THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE +FONT SOFTWARE. + +Except as contained in this notice, the names of Gnome, the Gnome +Foundation, and Bitstream Inc., shall not be used in advertising or +otherwise to promote the sale, use or other dealings in this Font Software +without prior written authorization from the Gnome Foundation or Bitstream +Inc., respectively. For further information, contact: fonts at gnome dot +org. + +Arev Fonts Copyright +------------------------------ + +Copyright (c) 2006 by Tavmjong Bah. All Rights Reserved. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the fonts accompanying this license ("Fonts") and +@@ -91,9 +92,96 @@ FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +Except as contained in this notice, the name of Tavmjong Bah shall not +be used in advertising or otherwise to promote the sale, use or other +dealings in this Font Software without prior written authorization +from Tavmjong Bah. For further information, contact: tavmjong @ free +. fr. + +============== +Twemoji Fonts +============== +MIT License + +Applies to "EmojiOne SVGinOT Font" code only +Copyright (c) 2022 Brad Erickson + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. + +============== +WebRTC +============== + +Copyright (c) 2011, The WebRTC project authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + * Neither the name of Google nor the names of its contributors may + be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +============== +Google fonts +============== +SIL OPEN FONT LICENSE Version 1.1 + +Copyright 2020 The Inter Project Authors (https://github.com/rsms/inter) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 3ab87cac13..31814d8f4f 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -44,6 +44,7 @@ #include "llchicletbar.h" #include "llconsole.h" #include "lldonotdisturbnotificationstorage.h" +#include "llenvironment.h" #include "llfirstuse.h" #include "llfloatercamera.h" #include "llfloaterimcontainer.h" @@ -2669,17 +2670,20 @@ void LLAgent::onAnimStop(const LLUUID& id) else if (id == ANIM_AGENT_PRE_JUMP || id == ANIM_AGENT_LAND || id == ANIM_AGENT_MEDIUM_LAND) { // FIRE-34049/FIRE-34273/https://github.com/secondlife/viewer/issues/4218 - // Avoid forcing AGENT_CONTROL_FINISH_ANIM, which can short-circuit the next pre-jump - // during rapid successive jumps. + // Avoid forcing AGENT_CONTROL_FINISH_ANIM on landing, which can short-circuit the + // next pre-jump during rapid successive jumps. + // Do not suppress pre-jump finish, otherwise a quick tap from standing can stall. // TODO: a more robust fix would require knowing which specific animation finished, // information that is not currently provided by the simulator. + const bool is_landing_anim = (id == ANIM_AGENT_LAND || id == ANIM_AGENT_MEDIUM_LAND); const bool up_pos = (mControlFlags & AGENT_CONTROL_UP_POS) != 0; const F64 now = LLTimer::getTotalSeconds(); const F64 elapsed = now - mLastJumpInputTime; - static LLCachedControl<F32> recent_jump_threshold_secs(gSavedSettings, "RecentJumpThresholdSecs"); + static LLCachedControl<F32> recent_jump_threshold_secs(gSavedSettings, "RecentJumpThresholdSecs", 1.0); const bool recent_jump = (mLastJumpInputTime > 0.0) && (elapsed < recent_jump_threshold_secs); + const bool suppress_finish = is_landing_anim && recent_jump; - if (!up_pos && !recent_jump) + if (!up_pos && !suppress_finish) { setControlFlags(AGENT_CONTROL_FINISH_ANIM); } @@ -4169,6 +4173,11 @@ void LLAgent::handleTeleportFinished() mRegionp->setCapabilitiesReceivedCallback(boost::bind(&LLAgent::onCapabilitiesReceivedAfterTeleport)); } } + static LLCachedControl<bool> shared_env_on_teleport(gSavedSettings, "SwitchToSharedEnvAfterTeleport", true); + if (shared_env_on_teleport) + { + LLEnvironment::instance().setSharedEnvironment(); + } LLPerfStats::tunables.autoTuneTimeout = true; } diff --git a/indra/newview/llagentlistener.cpp b/indra/newview/llagentlistener.cpp index 5ddb87558a..73368c1bcc 100644 --- a/indra/newview/llagentlistener.cpp +++ b/indra/newview/llagentlistener.cpp @@ -43,6 +43,7 @@ #include "llviewernetwork.h" #include "llviewerobject.h" #include "llviewerobjectlist.h" +#include "llviewerjointattachment.h" #include "llviewerregion.h" #include "llvoavatarself.h" #include "llsdutil.h" @@ -195,6 +196,12 @@ LLAgentListener::LLAgentListener(LLAgent &agent) &LLAgentListener::getNearbyObjectsList, llsd::map("reply", LLSD())); + add("getAttachedObjectsList", + "Return attached objects list with information about each object\n" + "reply contains \"attachments\" result key", + &LLAgentListener::getAttachedObjectsList, + llsd::map("reply", LLSD())); + add("getAgentScreenPos", "Return screen position of the [\"avatar_id\"] avatar or own avatar if not specified\n" "reply contains \"x\", \"y\" coordinates and \"onscreen\" flag to indicate if it's actually in within the current window\n" @@ -772,6 +779,41 @@ void LLAgentListener::getNearbyObjectsList(LLSD const& event_data) } } +void LLAgentListener::getAttachedObjectsList(LLSD const& event_data) +{ + Response response(LLSD(), event_data); + response["attachments"] = LLSD::emptyArray(); + + if (!isAgentAvatarValid()) + { + return; + } + + for (const auto& [attachment_point_index, attachment_point] : gAgentAvatarp->mAttachmentPoints) + { + if (!attachment_point) + { + continue; + } + + for (const auto& attachment_object_ptr : attachment_point->mAttachedObjects) + { + if (LLViewerObject* attachment_object = attachment_object_ptr.get()) + { + response["attachments"].append(llsd::map( + "object_id", attachment_object->getID(), + "inventory_item_id", attachment_object->getAttachmentItemID(), + "name", attachment_object->getAttachmentItemName(), + "attachment_point", attachment_point->getName(), + "attachment_point_index", attachment_point_index, + "position", ll_sd_from_vector3(attachment_object->getPosition()), + "rotation", ll_sd_from_quaternion(attachment_object->getRotation()), + "is_temporary", attachment_object->isTempAttachment())); + } + } + } +} + void LLAgentListener::getAgentScreenPos(LLSD const& event_data) { Response response(LLSD(), event_data); diff --git a/indra/newview/llagentlistener.h b/indra/newview/llagentlistener.h index b5bea8c0bd..031fc75e46 100644 --- a/indra/newview/llagentlistener.h +++ b/indra/newview/llagentlistener.h @@ -68,6 +68,7 @@ private: void getID(LLSD const& event_data); void getNearbyAvatarsList(LLSD const& event_data); void getNearbyObjectsList(LLSD const& event_data); + void getAttachedObjectsList(LLSD const& event_data); void getAgentScreenPos(LLSD const& event_data); LLViewerObject * findObjectClosestTo( const LLVector3 & position, bool sit_target = false ) const; diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 8aa28e1b09..4f549314af 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -980,7 +980,9 @@ void recovered_item_link_cb(const LLUUID& item_id, LLWearableType::EType type, L if (!holder->isMostRecent()) { LL_WARNS() << "HP " << holder->index() << " skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; - // runway skip here? + + // If we were signalled to stop then we shouldn't do anything else except poll for when it's safe to delete ourselves + return; } LL_INFOS("Avatar") << "HP " << holder->index() << " recovered item link for type " << type << LL_ENDL; @@ -1020,7 +1022,6 @@ void recovered_item_cb(const LLUUID& item_id, LLWearableType::EType type, LLView { if (!holder->isMostRecent()) { - // runway skip here? LL_WARNS() << self_av_string() << "skipping because LLWearableHolding pattern is invalid (superceded by later outfit request)" << LL_ENDL; // If we were signalled to stop then we shouldn't do anything else except poll for when it's safe to delete ourselves diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 59056381e1..11e6767980 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2012, Linden Research, Inc. + * Copyright (C) 2026, 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 @@ -66,6 +66,7 @@ #include "llconversationlog.h" #if LL_WINDOWS #include "lldxhardware.h" +#include <shellapi.h> #endif #include "lltexturestats.h" #include "lltrace.h" @@ -383,6 +384,8 @@ const std::string START_MARKER_FILE_NAME("SecondLife.start_marker"); const std::string ERROR_MARKER_FILE_NAME("SecondLife.error_marker"); const std::string LOGOUT_MARKER_FILE_NAME("SecondLife.logout_marker"); const std::string WATCHDOG_MARKER_FILE_NAME("SecondLife.watchdog_marker"); +const std::string INITED_MARKER_FILE_NAME("SecondLife.inited_marker"); +const std::string CLOSE_EVENT_MARKER_FILE_NAME("SecondLife.close_marker"); static std::string gLaunchFileOnQuit; //---------------------------------------------------------------------------- @@ -688,6 +691,12 @@ LLAppViewer::LLAppViewer() gLoggedInTime.stop(); + // Locking this early is needed to prevent multiple instances and + // to log, but it also means that early paths such as SLURL handling + // can invoke processMarkerFiles() and potentially clear markers + // from previous runs before those stats are reported. + // Todo: improve this. Perhaps store stats 'permanently' to be reported + // on next login and only login cleans stats up? processMarkerFiles(); // // OK to write stuff to logs now, we've now crash reported if necessary @@ -938,6 +947,16 @@ bool LLAppViewer::init() LL_WARNS("InitInfo") << "initHardwareTest() failed." << LL_ENDL; // quit immediately LL_PROFILER_FRAME_END; + LLSingletonBase::deleteAll(); + cleanupConsole(); + delete mSettingsLocationList; + if (!mSecondInstance) + { + // Stats from previous session will likely be lost, but this should + // be fine as this is likely first run for this version. + // Todo: Might be smarter to have an exit code for a cleaner shutdown + removeMarkerFiles(); + } return false; } LL_INFOS("InitInfo") << "Hardware test initialization done." << LL_ENDL ; @@ -2133,6 +2152,7 @@ bool LLAppViewer::cleanup() LLWorld::deleteSingleton(); LLVoiceClient::deleteSingleton(); LLUI::deleteSingleton(); + LLWatchdog::deleteSingleton(); // It's not at first obvious where, in this long sequence, a generic cleanup // call OUGHT to go. So let's say this: as we migrate cleanup from @@ -2277,6 +2297,9 @@ void errorHandler(const std::string& title_string, const std::string& message_st case LLError::LLUserWarningMsg::ERROR_MISSING_FILES: LLAppViewer::instance()->createErrorMarker(LAST_EXEC_MISSING_FILES); break; + case LLError::LLUserWarningMsg::ERROR_INIT_FAILED: + LLAppViewer::instance()->createErrorMarker(LAST_EXEC_INIT); + break; default: break; } @@ -2301,6 +2324,12 @@ void errorHandler(const std::string& title_string, const std::string& message_st } } +namespace +{ + std::string getStartupLogFileName(); + std::string getOldLogFileName(const std::string& log_file); +} + void LLAppViewer::initLoggingAndGetLastDuration() { // @@ -2326,13 +2355,10 @@ void LLAppViewer::initLoggingAndGetLastDuration() else { // Remove the last ".old" log file. - std::string old_log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, - "SecondLife.old"); + std::string log_file = getStartupLogFileName(); + std::string old_log_file = getOldLogFileName(log_file); LLFile::remove(old_log_file); - // Get name of the log file - std::string log_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, - "SecondLife.log"); /* * Before touching any log files, compute the duration of the last run * by comparing the ctime of the previous start marker file with the ctime @@ -2379,7 +2405,7 @@ void LLAppViewer::initLoggingAndGetLastDuration() // Rename current log file to ".old" LLFile::rename(log_file, old_log_file); - // Set the log file to SecondLife.log + // Set the log file. LLError::logToFile(log_file); LL_INFOS() << "Started logging to " << log_file << LL_ENDL; if (!duration_log_msg.empty()) @@ -2518,6 +2544,76 @@ namespace LLStringUtil::null, OSMB_OK); } + + std::string getStartupLogFileName() + { + if (LLControlVariable* user_log_file = gSavedSettings.getControl("UserLogFile")) + { + std::string log_file = user_log_file->getValue().asString(); + if (!log_file.empty()) + { + return log_file; + } + } + +#if LL_WINDOWS + int argc = 0; + LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc); + if (argv) + { + std::string log_file; + for (int i = 1; i < argc; ++i) + { + std::string option = ll_convert_wide_to_string(argv[i]); + if ((option == "--logfile" || option == "-logfile" || option == "/logfile") && + i + 1 < argc) + { + log_file = ll_convert_wide_to_string(argv[i + 1]); + } + else if (option.compare(0, 10, "--logfile=") == 0) + { + log_file = option.substr(10); + } + else if (option.compare(0, 9, "-logfile=") == 0) + { + log_file = option.substr(9); + } + else if (option.compare(0, 9, "/logfile:") == 0) + { + log_file = option.substr(9); + } + } + LocalFree(argv); + + if (!log_file.empty()) + { + return log_file; + } + } +#endif + + return gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife.log"); + } + + std::string getOldLogFileName(const std::string& log_file) + { + std::string old_log_file = log_file; + size_t separator = old_log_file.find_last_of("/\\"); + size_t basename_start = (separator == std::string::npos) ? 0 : separator + 1; + size_t extension = old_log_file.find_last_of('.'); + + if (extension != std::string::npos && + extension > basename_start) + { + old_log_file.replace(extension, std::string::npos, ".old"); + } + else + { + old_log_file += ".old"; + } + + return old_log_file; + } } // anonymous namespace // Set a named control temporarily for this session, as when set via the command line --set option. @@ -2932,7 +3028,23 @@ bool LLAppViewer::initConfiguration() { if (sendURLToOtherInstance(start_slurl.getSLURLString())) { - // successfully handed off URL to existing instance, exit + // Successfully handed off URL to existing instance. + // Returning 'false' gets treated as a failure to init, + // without cleanup, so instead clear markers and app here. + // Do not save settings. + // Might be smarter to have an exit code for a more reliable + // "early exit, needs cleanup" case. + LLSingletonBase::deleteAll(); + cleanupConsole(); + delete mSettingsLocationList; + if (!mSecondInstance) + { + // Todo: Unfortunately, if we are doing this, stats and + // markers from previous session were already processed, + // cleared yet haven't been reported and will be lost. + // Consider a way to save those. + removeMarkerFiles(); + } return false; } } @@ -2979,6 +3091,11 @@ bool LLAppViewer::initConfiguration() LLTrans::getString("MBAlreadyRunning"), LLStringUtil::null, OSMB_OK); + + // Since returning 'false' is basically an error without cleanup, + // do cleanup here. No need to worry about marker files here. + LLSingletonBase::deleteAll(); + cleanupConsole(); return false; } @@ -3936,6 +4053,9 @@ void LLAppViewer::processMarkerFiles() // - Freeze (SecondLife.exec_marker present, not locked) // - LLError Crash (SecondLife.llerror_marker present) // - Other Crash (SecondLife.error_marker present) + // - Watchdog freeze (SecondLife.watchdog_marker present) + // - Failed to initialize (SecondLife.inited_marker not present) + // - Potentially killed by task manager (SecondLife.close_marker present) // These checks should also remove these files for the last 2 cases if they currently exist std::ostringstream marker_log_stream; @@ -4048,6 +4168,8 @@ void LLAppViewer::processMarkerFiles() // Bugsplat will set correct state in bugsplatSendLog. std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME); std::string watchdog_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME); + std::string inited_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, INITED_MARKER_FILE_NAME); + std::string close_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME); if(LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB)) { S32 marker_code = getMarkerErrorCode(error_marker_file); @@ -4105,6 +4227,69 @@ void LLAppViewer::processMarkerFiles() removeWatchdogMarker(); } } + else + { + if (LAST_EXEC_UNKNOWN == gLastExecEvent + || LAST_EXEC_LOGOUT_UNKNOWN == gLastExecEvent) + { + // If viewer crashed after a freeze was detected, + // crash still takes precendence. + // So only check watchdog marker if there is no error marker. + if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) + { + // watchdog marker gets created if we detect a freeze, + // so if viwer did not stop gracefully, and we know it wasn't a crash, + // we have no other info, check watchdog. + if (markerIsSameVersion(watchdog_marker_file)) + { + gLastExecEvent = LAST_EXEC_UNKNOWN == gLastExecEvent ? LAST_EXEC_FROZE : LAST_EXEC_LOGOUT_FROZE; + LL_INFOS("MarkerFile") << "Watchdog marker '" << watchdog_marker_file << "' found, setting LastExecEvent to FROZE" + << LL_ENDL; + } + } + // If 'close' marker is found, viewer either started shutdown but + // failed, or viewer got killed by task manager. + // Marker does not indicate that viewer was closed or is closing, + // just that 'close' was requested before viewer died. + else if (LLAPRFile::isExist(close_marker_file, NULL, LL_APR_RB)) + { + // For now treat as 'other' cause. + // Unfortunately we can't for certain distinguish task + // manager's case from other shutdown problems, so we + // have to report both. + // Todo: if this bears noticeable fruits, make a new state later. + // New categories need server/web side support. + if (markerIsSameVersion(close_marker_file)) + { + gLastExecEvent = LAST_EXEC_UNKNOWN == gLastExecEvent ? LAST_EXEC_OTHER_CRASH : LAST_EXEC_LOGOUT_CRASH; + LL_INFOS("MarkerFile") << "'Close' marker '" << close_marker_file << "' found, setting LastExecEvent to CRASH" + << LL_ENDL; + } + } + else if ((LAST_EXEC_UNKNOWN == gLastExecEvent) + && !LLAPRFile::isExist(inited_marker_file, NULL, LL_APR_RB)) + { + // Viewer didn't get to a login screen. + gLastExecEvent = LAST_EXEC_INIT; + LL_INFOS("MarkerFile") << "'Inited' marker '" + << inited_marker_file + << "' not found, assuming that init crashed." + << LL_ENDL; + } + } + } + if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) + { + removeWatchdogMarker(); + } + if (LLAPRFile::isExist(inited_marker_file, NULL, LL_APR_RB)) + { + removeInitedMarker(); + } + if (LLAPRFile::isExist(close_marker_file, NULL, LL_APR_RB)) + { + removeCloseRequestMarker(); + } #if LL_DARWIN if (!mSecondInstance && gLastExecEvent != LAST_EXEC_NORMAL) @@ -4148,6 +4333,7 @@ void LLAppViewer::removeMarkerFiles() { LL_WARNS("MarkerFile") << "logout marker '"<<mLogoutMarkerFileName<<"' not open"<< LL_ENDL; } + removeCloseRequestMarker(); removeWatchdogMarker(); } else @@ -5589,6 +5775,59 @@ bool LLAppViewer::errorMarkerExists() const return LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB); } +void LLAppViewer::createCloseRequestMarker() const +{ + // WINDOW THREAD! since we need this to act fast. + // This does not indicate that viewer was closed or is closing, + // but that 'close' was requested. + if (!mSecondInstance) + { + std::string close_marker = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME); + + LLAPRFile file; + file.open(close_marker, LL_APR_WB); + if (file.getFileHandle()) + { + recordMarkerVersion(file); + file.close(); + } + } +} + +void LLAppViewer::removeCloseRequestMarker() const +{ + if (!mSecondInstance) + { + std::string close_marker = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME); + LLFile::remove(close_marker, ENOENT); + } +} + +void LLAppViewer::createInitedMarker() const +{ + if (!mSecondInstance) + { + std::string inited_marker = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, INITED_MARKER_FILE_NAME); + + LLAPRFile file; + file.open(inited_marker, LL_APR_WB); + if (file.getFileHandle()) + { + recordMarkerVersion(file); + file.close(); + } + } +} + +void LLAppViewer::removeInitedMarker() const +{ + if (!mSecondInstance) + { + std::string inited_marker = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, INITED_MARKER_FILE_NAME); + LLFile::remove(inited_marker, ENOENT); + } +} + void LLAppViewer::createWatchdogMarker() const { if (!mSecondInstance) @@ -5604,12 +5843,13 @@ void LLAppViewer::createWatchdogMarker() const } } } + void LLAppViewer::removeWatchdogMarker() const { if (!mSecondInstance) { std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME); - LLFile::remove(error_marker_file); + LLFile::remove(error_marker_file, ENOENT); } } diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index d76e5015e9..b404ec2c03 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -17,7 +17,7 @@ * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2026, 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 @@ -76,7 +76,7 @@ typedef enum LAST_EXEC_LOGOUT_CRASH, LAST_EXEC_BAD_ALLOC, LAST_EXEC_MISSING_FILES, - LAST_EXEC_GRAPHICS_INIT, + LAST_EXEC_INIT, LAST_EXEC_UNKNOWN, LAST_EXEC_LOGOUT_UNKNOWN, LAST_EXEC_COUNT @@ -254,6 +254,10 @@ public: void createErrorMarker(eLastExecEvent error_code) const; bool errorMarkerExists() const; + void createCloseRequestMarker() const; + void removeCloseRequestMarker() const; + void createInitedMarker() const; + void removeInitedMarker() const; void createWatchdogMarker() const; void removeWatchdogMarker() const; @@ -274,6 +278,7 @@ protected: virtual bool initWindow(); // Initialize the viewer's window. virtual void initLoggingAndGetLastDuration(); // Initialize log files, logging system virtual void initConsole() {}; // Initialize OS level debugging console. + virtual void cleanupConsole() {}; // Cleanup OS level debugging console. virtual bool initHardwareTest() { return true; } // A false result indicates the app should quit. virtual bool initSLURLHandler(); virtual bool sendURLToOtherInstance(const std::string& url); diff --git a/indra/newview/llappviewermacosx-objc.h b/indra/newview/llappviewermacosx-objc.h index 3fbf4202f1..bfbb48dadb 100644 --- a/indra/newview/llappviewermacosx-objc.h +++ b/indra/newview/llappviewermacosx-objc.h @@ -31,5 +31,6 @@ #include <vector> void force_ns_sxeption(); +void register_url_schemes(); #endif // LL_LLAPPVIEWERMACOSX_OBJC_H diff --git a/indra/newview/llappviewermacosx-objc.mm b/indra/newview/llappviewermacosx-objc.mm index 96a6bc6edc..75d6b56e3e 100644 --- a/indra/newview/llappviewermacosx-objc.mm +++ b/indra/newview/llappviewermacosx-objc.mm @@ -38,3 +38,28 @@ void force_ns_sxeption() NSException *exception = [NSException exceptionWithName:@"Forced NSException" reason:nullptr userInfo:nullptr]; @throw exception; } + +void register_url_schemes() +{ + @autoreleasepool // Objective-C automatic memory tracking and release. + { + NSString *bundlePath = [[NSBundle mainBundle] bundlePath]; + NSURL *bundleURL = [NSURL fileURLWithPath:bundlePath]; + + // Force Launch Services to re-register this app bundle + OSStatus status = LSRegisterURL((__bridge CFURLRef)bundleURL, true); + + if (status == noErr) + { + // Explicitly set this app as the default handler for our URL schemes + NSArray *schemes = @[@"secondlife", @"x-grid-location-info"]; + NSString *bundleID = [[NSBundle mainBundle] bundleIdentifier]; + + for (NSString *scheme in schemes) + { + LSSetDefaultHandlerForURLScheme((__bridge CFStringRef)scheme, + (__bridge CFStringRef)bundleID); + } + } + } +} diff --git a/indra/newview/llappviewermacosx.cpp b/indra/newview/llappviewermacosx.cpp index 2bd9c8661a..830e2d4473 100644 --- a/indra/newview/llappviewermacosx.cpp +++ b/indra/newview/llappviewermacosx.cpp @@ -412,6 +412,21 @@ bool LLAppViewerMacOSX::restoreErrorTrap() return reset_count == 0; } +bool LLAppViewerMacOSX::initSLURLHandler() +{ + if (isSecondInstance()) + { + return false; + } + // Main secondlife:// registration is in info.plist, but macOS + // Launch Services caches URL scheme handlers, and a different + // viewer might still be registered. + // Register URL schemes with Launch Services on every launch + register_url_schemes(); + + return true; +} + std::string LLAppViewerMacOSX::generateSerialNumber() { char serial_md5[MD5HEX_STR_SIZE]; // Flawfinder: ignore diff --git a/indra/newview/llappviewermacosx.h b/indra/newview/llappviewermacosx.h index d50812a35e..35fc99dbd9 100644 --- a/indra/newview/llappviewermacosx.h +++ b/indra/newview/llappviewermacosx.h @@ -46,6 +46,7 @@ public: protected: virtual bool restoreErrorTrap(); + virtual bool initSLURLHandler(); std::string generateSerialNumber(); virtual bool initParseCommandLine(LLCommandLineParser& clp); diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 2e4e9e29d5..5184b0f025 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2026, 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 @@ -840,12 +840,7 @@ bool LLAppViewerWin32::cleanup() bool result = LLAppViewer::cleanup(); gDXHardware.cleanup(); - - if (mIsConsoleAllocated) - { - FreeConsole(); - mIsConsoleAllocated = false; - } + cleanupConsole(); return result; } @@ -929,6 +924,15 @@ void LLAppViewerWin32::initConsole() return LLAppViewer::initConsole(); } +void LLAppViewerWin32::cleanupConsole() +{ + if (mIsConsoleAllocated) + { + FreeConsole(); + mIsConsoleAllocated = false; + } +} + void write_debug_dx(const char* str) { std::string value = gDebugInfo["DXInfo"].asString(); @@ -1001,7 +1005,7 @@ bool LLAppViewerWin32::sendURLToOtherInstance(const std::string& url) if (other_window != NULL) { - LL_DEBUGS() << "Found other window with the name '" << getWindowTitle() << "'" << LL_ENDL; + LL_DEBUGS("AppInit") << "Found other window with the name '" << getWindowTitle() << "'" << LL_ENDL; COPYDATASTRUCT cds; const S32 SLURL_MESSAGE_TYPE = 0; cds.dwData = SLURL_MESSAGE_TYPE; @@ -1009,13 +1013,231 @@ bool LLAppViewerWin32::sendURLToOtherInstance(const std::string& url) cds.lpData = (void*)url.c_str(); LRESULT msg_result = SendMessage(other_window, WM_COPYDATA, NULL, (LPARAM)&cds); - LL_DEBUGS() << "SendMessage(WM_COPYDATA) to other window '" + LL_DEBUGS("AppInit") << "SendMessage(WM_COPYDATA) to other window '" << getWindowTitle() << "' returned " << msg_result << LL_ENDL; return true; } return false; } +bool LLAppViewerWin32::sendShutdownToOtherInstances(const std::wstring& install_dir) +{ + // Velopack installs viewer like this: + // %appdata%\Local\ChannelNameViewer\Update.exe // which is our uninstaller + // %appdata%\Local\ChannelNameViewer\SecondLifeViewer.exe // wrapper, redirects to main executable + // %appdata%\Local\ChannelNameViewer\current\SecondLifeViewer.exe // main executable + // For reliability don't expect install_dir to be actually in the base path, strip 'current' + + const std::wstring current_suffix = L"\\current"; + std::wstring normalized_path(install_dir); + if (normalized_path.length() >= current_suffix.length() && + _wcsicmp(normalized_path.c_str() + normalized_path.length() - current_suffix.length(), + current_suffix.c_str()) == 0) + { + normalized_path.resize(normalized_path.length() - current_suffix.length()); + } + + wchar_t window_class[256]; // Assume max length < 255 chars. + mbstowcs(window_class, sWindowClass, 255); + window_class[255] = 0; + + // Normalize the directory path + wchar_t our_dir_normalized[MAX_PATH]; + wchar_t* file_part = nullptr; + DWORD result = GetFullPathNameW(normalized_path.c_str(), MAX_PATH, our_dir_normalized, &file_part); + if (result == 0 || result >= MAX_PATH) + { + LL_WARNS() << "Failed to normalize our executable path" << LL_ENDL; + return false; + } + + // Remove trailing backslash if present + size_t dir_len = wcslen(our_dir_normalized); + if (dir_len > 0 && our_dir_normalized[dir_len - 1] == L'\\') + { + our_dir_normalized[dir_len - 1] = L'\0'; + dir_len--; + } + + // This message is meant for velopack, so we don't expect to have + // a window of our own, store any matching windows. + struct EnumData + { + const wchar_t* target_class; + const wchar_t* our_dir_normalized; + std::vector<HWND> found_windows; + }; + + EnumData enum_data; + enum_data.target_class = window_class; + enum_data.our_dir_normalized = our_dir_normalized; + + // Callback function to find all matching windows + auto find_windows_callback = [](HWND hwnd, LPARAM lParam) -> BOOL + { + EnumData* data = reinterpret_cast<EnumData*>(lParam); + wchar_t class_name[256]; + + if (GetClassName(hwnd, class_name, 256) > 0) + { + if (wcscmp(class_name, data->target_class) == 0) + { + // Get the process ID for this window + DWORD process_id = 0; + GetWindowThreadProcessId(hwnd, &process_id); + + // Open the process to query its executable path + HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id); + if (hProcess) + { + wchar_t exe_path[MAX_PATH]; + DWORD size = MAX_PATH; + if (QueryFullProcessImageNameW(hProcess, 0, exe_path, &size)) + { + // Normalize the other process's path + wchar_t other_dir_normalized[MAX_PATH]; + wchar_t* other_file_part = nullptr; + DWORD result = GetFullPathNameW(exe_path, MAX_PATH, other_dir_normalized, &other_file_part); + + if (result > 0 && result < MAX_PATH) + { + // Remove the filename part to get just the directory + // We are doing this to avoid incidents, like having + // multiple viewer version exes in the same folder. + if (other_file_part) + { + *other_file_part = L'\0'; + } + + // Remove trailing backslash if present + size_t other_dir_len = wcslen(other_dir_normalized); + if (other_dir_len > 0 && other_dir_normalized[other_dir_len - 1] == L'\\') + { + other_dir_normalized[other_dir_len - 1] = L'\0'; + other_dir_len--; + } + + // Strip "\current" suffix if present to normalize comparison + // This handles both release (with \current) and debug builds (without) + const std::wstring current_suffix = L"\\current"; + if (other_dir_len >= current_suffix.length()) + { + size_t offset = other_dir_len - current_suffix.length(); + if (_wcsicmp(other_dir_normalized + offset, current_suffix.c_str()) == 0) + { + other_dir_normalized[offset] = L'\0'; + } + } + + // Compare directories (case-insensitive) + if (_wcsicmp(other_dir_normalized, data->our_dir_normalized) == 0) + { + data->found_windows.push_back(hwnd); + } + } + } + CloseHandle(hProcess); + } + } + } + + return TRUE; // Continue enumeration + }; + + // Find all matching windows and send shutdown messages + EnumWindows(find_windows_callback, reinterpret_cast<LPARAM>(&enum_data)); + + if (enum_data.found_windows.empty()) + { + LL_DEBUGS("AppInit") << "No other instances found" << LL_ENDL; + return false; + } + + LL_INFOS("AppInit") << "Found " << (S32)(enum_data.found_windows.size()) << " other instance(s), sending shutdown messages" << LL_ENDL; + + // Get our own process ID to include in the message + DWORD our_process_id = GetCurrentProcessId(); + + constexpr UINT timeout_ms = 2000; // 2s. Viewer's message thread is supposed to be fast. + for (HWND other_window : enum_data.found_windows) + { + if (IsWindow(other_window)) + { + DWORD_PTR result = 0; + LRESULT send_result = SendMessageTimeout( + other_window, + WM_POST_UNINSTALL_, + static_cast<WPARAM>(our_process_id), + static_cast<LPARAM>(WM_POST_UNINSTALL_MSG_SHUTDOWN), + SMTO_ABORTIFHUNG | SMTO_BLOCK, + timeout_ms, + &result + ); + + if (send_result == 0) + { + DWORD error = GetLastError(); + if (error == ERROR_TIMEOUT) + { + LL_WARNS("AppInit") << "Shutdown message timed out for window " << std::hex << other_window << std::dec << LL_ENDL; + } + else + { + LL_WARNS("AppInit") << "Failed to send shutdown message to window " << std::hex << other_window + << ", error: " << error << std::dec << LL_ENDL; + } + + PostMessage(other_window, WM_CLOSE, 0, 0); + } + else + { + LL_DEBUGS("AppInit") << "Shutdown message sent successfully to window " << std::hex << other_window << std::dec << LL_ENDL; + } + } + } + + // Poll for up to 30 seconds, checking every 5 seconds + const S32 MAX_WAIT_TIME_MS = 60000; // 30 seconds + const S32 POLL_INTERVAL_MS = 5000; // 5 seconds + S32 elapsed_time_ms = 0; + size_t still_open_count = enum_data.found_windows.size(); + + while (elapsed_time_ms < MAX_WAIT_TIME_MS) + { + LL_INFOS("AppInit") << "Waiting for " << (S32)still_open_count << " instance(s) to close... (" + << (S32)(elapsed_time_ms / 1000) << "s elapsed)" << LL_ENDL; + + ms_sleep(POLL_INTERVAL_MS); + elapsed_time_ms += POLL_INTERVAL_MS; + + // Check if the specific windows we found still exist + // Don't enumerate all windows for new ones, assume that + // no instances were reused and assume user won't open + // the app again. For now just check our list. + still_open_count = 0; + for (HWND hwnd : enum_data.found_windows) + { + if (IsWindow(hwnd)) + { + still_open_count++; + } + } + + if (still_open_count == 0) + { + LL_INFOS("AppInit") << "All other instances have closed after " << (S32)(elapsed_time_ms / 1000) << " seconds" << LL_ENDL; + return false; + } + } + + if (still_open_count != 0) + { + LL_WARNS("AppInit") << "Proceeding with uninstall with " << (S32)still_open_count << " instance(s) still open." << LL_ENDL; + } + + return true; +} + std::string LLAppViewerWin32::generateSerialNumber() { diff --git a/indra/newview/llappviewerwin32.h b/indra/newview/llappviewerwin32.h index 53177f7f95..971907694a 100644 --- a/indra/newview/llappviewerwin32.h +++ b/indra/newview/llappviewerwin32.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2007&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2026, 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 @@ -46,10 +46,15 @@ public: bool reportCrashToBugsplat(void* pExcepInfo) override; bool reportCustomToBugsplat(const std::string& description) override; + // returns true if other windows were found and are still running. + static bool sendShutdownToOtherInstances(const std::wstring& install_dir); + protected: bool initWindow() override; // Override to initialize the viewer's window. void initLoggingAndGetLastDuration() override; // Override to clean stack_trace info. void initConsole() override; // Initialize OS level debugging console. + void cleanupConsole() override; + bool initHardwareTest() override; // Win32 uses DX9 to test hardware. bool initParseCommandLine(LLCommandLineParser& clp) override; diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index c1af09ebc7..af94c6d443 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -175,6 +175,49 @@ public: LLFloaterSidePanelContainer::showPanel("people", "panel_people", LLSD().with("people_panel_tab_name", "blocked_panel").with("blocked_to_select", getAvatarId())); } + else if (level == "report_abuse") + { + std::string time_string; + if (mTime > 0) // have frame time + { + time_t current_time = time_corrected(); + time_t message_time = (time_t)(current_time - LLFrameTimer::getElapsedSeconds() + mTime); + + // Report abuse shouldn't use AM/PM, use 24-hour time + time_string = "[" + LLTrans::getString("TimeMonth") + "]/[" + + LLTrans::getString("TimeDay") + "]/[" + + LLTrans::getString("TimeYear") + "] [" + + LLTrans::getString("TimeHour") + "]:[" + + LLTrans::getString("TimeMin") + "]"; + + LLSD substitution; + + substitution["datetime"] = (S32)message_time; + LLStringUtil::format(time_string, substitution); + } + else + { + // From history. This might be empty or not full. + // See LLChatLogParser::parse + time_string = getChild<LLTextBox>("time_box")->getValue().asString(); + + // Just add current date if not full. + // Should be fine since both times are supposed to be SLT. + if (!time_string.empty() && time_string.size() < 7) + { + time_string = "[" + LLTrans::getString("TimeMonth") + "]/[" + + LLTrans::getString("TimeDay") + "]/[" + + LLTrans::getString("TimeYear") + "] " + time_string; + + LLSD substitution; + // To avoid adding today's date to yesterday's timestamp, + // use creation time instead of current time + substitution["datetime"] = (S32)mCreationTime; + LLStringUtil::format(time_string, substitution); + } + } + LLFloaterReporter::showFromChatObj(getAvatarId(), time_string, mText); + } else if (level == "unblock") { LLMuteList::getInstance()->remove(LLMute(getAvatarId(), mFrom, LLMute::OBJECT)); @@ -461,7 +504,7 @@ public: time_string = getChild<LLTextBox>("time_box")->getValue().asString(); // Just add current date if not full. - // Should be fine since both times are supposed to be stl + // Should be fine since both times are supposed to be SLT. if (!time_string.empty() && time_string.size() < 7) { time_string = "[" + LLTrans::getString("TimeMonth") + "]/[" @@ -475,7 +518,7 @@ public: LLStringUtil::format(time_string, substitution); } } - LLFloaterReporter::showFromChat(mAvatarID, mFrom, time_string, mText); + LLFloaterReporter::showFromChatAv(mAvatarID, mFrom, time_string, mText); } else if(level == "block_unblock") { diff --git a/indra/newview/llchatitemscontainerctrl.cpp b/indra/newview/llchatitemscontainerctrl.cpp index 5ac4ce0d52..2e8fb752b8 100644 --- a/indra/newview/llchatitemscontainerctrl.cpp +++ b/indra/newview/llchatitemscontainerctrl.cpp @@ -35,8 +35,10 @@ #include "llcommandhandler.h" #include "llfloaterreg.h" #include "lllocalcliprect.h" +#include "llpanelblockedlist.h" #include "lltrans.h" #include "llfloaterimnearbychat.h" +#include "llfloaterreporter.h" #include "llfloaterworldmap.h" #include "llviewermenu.h" @@ -93,6 +95,32 @@ public: } return true; } + if (verb == "block") + { + if (params.size() > 2) + { + const std::string object_name = LLURI::unescape(params[2].asString()); + LLMute mute(object_id, object_name, LLMute::OBJECT); + LLMuteList::getInstance()->add(mute); + LLPanelBlockedList::showPanelAndSelect(mute.mID); + } + return true; + } + if (verb == "unblock") + { + if (params.size() > 2) + { + const std::string object_name = params[2].asString(); + LLMute mute(object_id, object_name, LLMute::OBJECT); + LLMuteList::getInstance()->remove(mute); + } + return true; + } + if (verb == "reportAbuse" && web == NULL) + { + LLFloaterReporter::showFromObject(object_id, LLUUID::null); + return true; + } return false; } diff --git a/indra/newview/lleventpoll.cpp b/indra/newview/lleventpoll.cpp index 9eddec55c5..836ff63429 100644 --- a/indra/newview/lleventpoll.cpp +++ b/indra/newview/lleventpoll.cpp @@ -331,26 +331,51 @@ namespace Details { // LLSD is too smart for it's own good and may act like a smart // pointer for the content of (*i), so instead of passing (*i) - // pass a prepared name and move ownership of "body", - // as we are not going to need "body" anywhere else. + // pass a prepared name and copy the body std::string msg_name = (*i)["message"].asString(); - // WARNING: This is a shallow copy! - // If something still retains the data (like in httpAdapter?) this might still - // result in a crash, if it does appear to be the case, make a deep copy or - // convert data to string and pass that string. - const LLSD body = (*i)["body"]; - (*i)["body"].clear(); - work = [this, msg_name, body]() + // Create a deep copy using binary serialization + try { - handleMessage(msg_name, body); - }; + std::stringstream body_stream; + LLSDSerialize::toBinary((*i)["body"], body_stream); + std::string body_str = body_stream.str(); + (*i)["body"].clear(); + work = [this, msg_name, body_str]() + { + try + { + LLSD body; + std::istringstream istr(body_str); + LLSDSerialize::fromBinary(body, istr, body_str.size()); + handleMessage(msg_name, body); + } + catch (std::bad_alloc&) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS("LLCoros") << "Bad memory allocation in handleMessage() for " << msg_name << LL_ENDL; + } + }; + } + catch (std::bad_alloc&) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS("LLCoros") << "Bad memory allocation in eventPollCoro for " << msg_name << LL_ENDL; + } } main_queue->post(work); } else { - handleMessage(*i); + try + { + handleMessage(*i); + } + catch (std::bad_alloc&) + { + LLError::LLUserWarningMsg::showOutOfMemory(); + LL_ERRS("LLCoros") << "Bad memory allocation in handleMessage() for " << (*i)["message"].asString() << LL_ENDL; + } } } } diff --git a/indra/newview/llfloaterbuycurrency.cpp b/indra/newview/llfloaterbuycurrency.cpp index e41f893c43..506cff31f4 100644 --- a/indra/newview/llfloaterbuycurrency.cpp +++ b/indra/newview/llfloaterbuycurrency.cpp @@ -316,16 +316,23 @@ void LLFloaterBuyCurrency::handleBuyCurrency(bool has_piof, bool has_target, con if (has_piof) { LLFloaterBuyCurrencyUI* ui = LLFloaterReg::showTypedInstance<LLFloaterBuyCurrencyUI>("buy_currency"); - if (has_target) + if (ui) { - ui->target(name, price); + if (has_target) + { + ui->target(name, price); + } + else + { + ui->noTarget(); + } + ui->updateUI(); + ui->collapsePanels(!has_target); } else { - ui->noTarget(); + LL_WARNS() << "Cannot instantiate buy_currency floater" << LL_ENDL; } - ui->updateUI(); - ui->collapsePanels(!has_target); } else { diff --git a/indra/newview/llfloaterhowto.cpp b/indra/newview/llfloaterhowto.cpp deleted file mode 100644 index 6a9f113d53..0000000000 --- a/indra/newview/llfloaterhowto.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/** - * @file llfloaterhowto.cpp - * @brief A variant of web floater meant to open guidebook - * - * $LicenseInfo:firstyear=2021&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2021, 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 "llviewerprecompiledheaders.h" - -#include "llfloaterhowto.h" - -#include "llfloaterreg.h" -#include "llviewercontrol.h" -#include "llweb.h" - - -constexpr S32 STACK_WIDTH = 300; -constexpr S32 STACK_HEIGHT = 505; // content will be 500 - -LLFloaterHowTo::LLFloaterHowTo(const Params& key) : - LLFloaterWebContent(key) -{ - mShowPageTitle = false; -} - -bool LLFloaterHowTo::postBuild() -{ - LLFloaterWebContent::postBuild(); - - return true; -} - -void LLFloaterHowTo::onOpen(const LLSD& key) -{ - LLFloaterWebContent::Params p(key); - if (!p.url.isProvided() || p.url.getValue().empty()) - { - std::string url = gSavedSettings.getString("GuidebookURL"); - p.url = LLWeb::expandURLSubstitutions(url, LLSD()); - } - p.show_chrome = false; - - LLFloaterWebContent::onOpen(p); - - if (p.preferred_media_size().isEmpty()) - { - // Elements from LLFloaterWebContent did not pick up restored size (save_rect) of LLFloaterHowTo - // set the stack size and position (alternative to preferred_media_size) - LLLayoutStack *stack = getChild<LLLayoutStack>("stack1"); - LLRect stack_rect = stack->getRect(); - stack->reshape(STACK_WIDTH, STACK_HEIGHT); - stack->setOrigin(stack_rect.mLeft, stack_rect.mTop - STACK_HEIGHT); - stack->updateLayout(); - } -} - -LLFloaterHowTo* LLFloaterHowTo::getInstance() -{ - return LLFloaterReg::getTypedInstance<LLFloaterHowTo>("guidebook"); -} - -bool LLFloaterHowTo::handleKeyHere(KEY key, MASK mask) -{ - bool handled = false; - - if (KEY_F1 == key ) - { - closeFloater(); - handled = true; - } - - return handled; -} diff --git a/indra/newview/llfloaterhowto.h b/indra/newview/llfloaterhowto.h deleted file mode 100644 index 9d7793817a..0000000000 --- a/indra/newview/llfloaterhowto.h +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @file llfloaterhowto.h - * @brief A variant of web floater meant to open guidebook - * - * $LicenseInfo:firstyear=2021&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2021, 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$ - */ - -#ifndef LL_LLFLOATERHOWTO_H -#define LL_LLFLOATERHOWTO_H - -#include "llfloaterwebcontent.h" - -class LLMediaCtrl; - - -class LLFloaterHowTo : - public LLFloaterWebContent -{ -public: - LOG_CLASS(LLFloaterHowTo); - - typedef LLFloaterWebContent::Params Params; - - LLFloaterHowTo(const Params& key); - - void onOpen(const LLSD& key) override; - - bool handleKeyHere(KEY key, MASK mask) override; - - static LLFloaterHowTo* getInstance(); - - bool matchesKey(const LLSD& key) override { return true; /*single instance*/ }; - -private: - bool postBuild() override; -}; - -#endif // LL_LLFLOATERHOWTO_H - diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index ef4252eba6..c2a9008420 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -158,15 +158,23 @@ void LLFloaterIMContainer::sessionIDUpdated(const LLUUID& old_session_id, const // Note however that the LLFloaterIMSession has its session id updated through a call to sessionInitReplyReceived() // and do not need to be deleted and recreated (trying this creates loads of problems). We do need however to suppress // its related mSessions record as it's indexed with the wrong id. - // Grabbing the updated LLFloaterIMSession and readding it in mSessions will eventually be done by addConversationListItem(). mSessions.erase(old_session_id); - // Delete the model and participants related to the old session - bool change_focus = removeConversationListItem(old_session_id); + // Remove the old conversation widget without changing focus - we'll immediately re-add with + // the new id and select it, avoiding an unnecessary focus switch to an adjacent conversation. + bool was_selected = removeConversationListItem(old_session_id, false); // Create a new conversation with the new id - addConversationListItem(new_session_id, change_focus); + addConversationListItem(new_session_id, was_selected); LLFloaterIMSessionTab::addToHost(new_session_id); + + // addToHost is a no-op for already-hosted floaters, so mSessions won't be + // updated by addFloater. Re-register manually so message flash works. + LLFloaterIMSessionTab* conversp = LLFloaterIMSessionTab::findConversation(new_session_id); + if (conversp && mSessions.find(new_session_id) == mSessions.end()) + { + mSessions[new_session_id] = conversp; + } } diff --git a/indra/newview/llfloaterimsessiontab.h b/indra/newview/llfloaterimsessiontab.h index b27ac1b8f9..3ca1187ec9 100644 --- a/indra/newview/llfloaterimsessiontab.h +++ b/indra/newview/llfloaterimsessiontab.h @@ -118,6 +118,8 @@ public: virtual void sessionVoiceOrIMStarted(const LLUUID& session_id) override {}; // Stub virtual void sessionIDUpdated(const LLUUID& old_session_id, const LLUUID& new_session_id) override {}; // Stub + bool isP2PSessionType() { return mIsP2PChat; } + protected: // callback for click on any items of the visual states menu diff --git a/indra/newview/llfloaterreporter.cpp b/indra/newview/llfloaterreporter.cpp index 7e7eb91636..6322dcd3c9 100644 --- a/indra/newview/llfloaterreporter.cpp +++ b/indra/newview/llfloaterreporter.cpp @@ -660,9 +660,9 @@ void LLFloaterReporter::showFromAvatar(const LLUUID& avatar_id, const std::strin } // static -void LLFloaterReporter::showFromChat(const LLUUID& avatar_id, const std::string& avatar_name, const std::string& time, const std::string& description) +void LLFloaterReporter::showFromChatObj(const LLUUID& object_id, const std::string& time, const std::string& description) { - show(avatar_id, avatar_name); + show(object_id, LLStringUtil::null, LLUUID::null); LLStringUtil::format_map_t args; args["[MSG_TIME]"] = time; @@ -671,8 +671,25 @@ void LLFloaterReporter::showFromChat(const LLUUID& avatar_id, const std::string& LLFloaterReporter *self = LLFloaterReg::findTypedInstance<LLFloaterReporter>("reporter"); if (self) { - std::string description = self->getString("chat_report_format", args); - self->getChild<LLUICtrl>("details_edit")->setValue(description); + std::string description_frmt = self->getString("chat_report_format", args); + self->getChild<LLUICtrl>("details_edit")->setValue(description_frmt); + } +} + +// static +void LLFloaterReporter::showFromChatAv(const LLUUID& avatar_id, const std::string& avatar_name, const std::string& time, const std::string& description) +{ + show(avatar_id, avatar_name); + + LLStringUtil::format_map_t args; + args["[MSG_TIME]"] = time; + args["[MSG_DESCRIPTION]"] = description; + + LLFloaterReporter* self = LLFloaterReg::findTypedInstance<LLFloaterReporter>("reporter"); + if (self) + { + std::string description_frmt = self->getString("chat_report_format", args); + self->getChild<LLUICtrl>("details_edit")->setValue(description_frmt); } } diff --git a/indra/newview/llfloaterreporter.h b/indra/newview/llfloaterreporter.h index 31b05235a6..446e7d63b6 100644 --- a/indra/newview/llfloaterreporter.h +++ b/indra/newview/llfloaterreporter.h @@ -93,7 +93,8 @@ public: static void showFromObject(const LLUUID& object_id, const LLUUID& experience_id = LLUUID::null); static void showFromAvatar(const LLUUID& avatar_id, const std::string avatar_name); - static void showFromChat(const LLUUID& avatar_id, const std::string& avatar_name, const std::string& time, const std::string& description); + static void showFromChatObj(const LLUUID& object_id, const std::string& time, const std::string& description); + static void showFromChatAv(const LLUUID& avatar_id, const std::string& avatar_name, const std::string& time, const std::string& description); static void showFromExperience(const LLUUID& experience_id); static void onClickSend (void *userdata); diff --git a/indra/newview/llhudnametag.cpp b/indra/newview/llhudnametag.cpp index 4327d281e5..5d20e722de 100644 --- a/indra/newview/llhudnametag.cpp +++ b/indra/newview/llhudnametag.cpp @@ -41,9 +41,9 @@ #include "llhudrender.h" #include "llui.h" #include "llviewercamera.h" +#include "llviewerregion.h" #include "llviewertexturelist.h" #include "llviewerobject.h" -#include "llvovolume.h" #include "llviewerwindow.h" #include "llstatusbar.h" #include "llmenugl.h" @@ -291,7 +291,22 @@ void LLHUDNameTag::renderText() + (x_pixel_vec * screen_offset.mV[VX]) + (y_pixel_vec * screen_offset.mV[VY]); - LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE); + // Check if an underwater name tag should be rendered over the water (while camera is above the water) + bool render_over_water = false; + static LLCachedControl<bool> nametag_over_water(gSavedSettings, "NametagOverWater", true); + if (nametag_over_water && + mSourceObject && + mSourceObject->getRegion() && + LLPipeline::sRenderTransparentWater && + !LLViewerCamera::getInstance()->cameraUnderWater()) + { + if (mSourceObject->getPositionAgent().mV[VZ] < mSourceObject->getRegion()->getWaterHeight()) + { + render_over_water = true; + } + } + LLGLDepthTest gls_depth(GL_TRUE, GL_FALSE, render_over_water ? GL_ALWAYS : GL_LEQUAL); + LLRect screen_rect; screen_rect.setCenterAndSize(0, static_cast<S32>(lltrunc(-mHeight / 2 + mOffsetY)), static_cast<S32>(lltrunc(mWidth)), static_cast<S32>(lltrunc(mHeight))); mRoundedRectImgp->draw3D(render_position, x_pixel_vec, y_pixel_vec, screen_rect, bg_color); diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 252eb32e7c..f8cc6885c4 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -3245,8 +3245,9 @@ void LLIMMgr::addMessage( return; } - // Fetch group chat history, enabled by default. - if (gSavedPerAccountSettings.getBOOL("FetchGroupChatHistory")) + // Fetch group chat or ad-hoc history, enabled by default. + static LLCachedControl<bool> fetch_chat_history(gSavedPerAccountSettings, "FetchGroupChatHistory", true); + if (fetch_chat_history && !session->isP2PSessionType()) { std::string chat_url = gAgent.getRegionCapability("ChatSessionRequest"); if (!chat_url.empty()) @@ -4089,8 +4090,9 @@ public: { im_floater->processSessionUpdate(body["session_info"]); - // Send request for chat history, if enabled. - if (gSavedPerAccountSettings.getBOOL("FetchGroupChatHistory")) + // Send request for chat history, if enabled. Skip for peer-to-peer IMs. + static LLCachedControl<bool> fetch_chat_history(gSavedPerAccountSettings, "FetchGroupChatHistory", true); + if (fetch_chat_history && !im_floater->isP2PSessionType()) { std::string url = gAgent.getRegionCapability("ChatSessionRequest"); if (!url.empty()) diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp index 0358233637..02f013ad3c 100644 --- a/indra/newview/lllogininstance.cpp +++ b/indra/newview/lllogininstance.cpp @@ -134,6 +134,7 @@ void LLLoginInstance::reconnect() { // Sort of like connect, only using the pre-existing // request params. + mAttemptComplete = false; std::vector<std::string> uris; LLGridManager::getInstance()->getLoginURIs(uris); mLoginModule->connect(uris.front(), mRequestData); diff --git a/indra/newview/llmediactrl.cpp b/indra/newview/llmediactrl.cpp index 4a808ba052..679157dc1e 100644 --- a/indra/newview/llmediactrl.cpp +++ b/indra/newview/llmediactrl.cpp @@ -62,9 +62,15 @@ #include "lllineeditor.h" #include "llfloaterwebcontent.h" #include "llwindowshade.h" +#include "lleventapi.h" +#include "llui.h" extern bool gRestoreGL; +const std::string PAGE_TEXT_EXTRACT_MARKER = "PAGE_TEXT_EXTRACT:"; + +class LLMediaCtrlListener; + static LLDefaultChildRegistry::Register<LLMediaCtrl> r("web_browser"); LLMediaCtrl::Params::Params() @@ -100,6 +106,7 @@ LLMediaCtrl::LLMediaCtrl( const Params& p) : mUpdateScrolls( false ), mTextureWidth ( 1024 ), mTextureHeight ( 1024 ), + mLoadingState( LOADING_STATE_INITIALIZING ), mClearCache(false), mHomePageMimeType(p.initial_mime_type), mErrorPageURL(p.error_page_url), @@ -1024,6 +1031,7 @@ void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) { LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_BEGIN, url is " << self->getNavigateURI() << LL_ENDL; hideNotification(); + mLoadingState = LOADING_STATE_LOADING; }; break; @@ -1034,6 +1042,7 @@ void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) { mHidingInitialLoad = false; } + mLoadingState = LOADING_STATE_LOADED; }; break; @@ -1062,6 +1071,7 @@ void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) { navigateTo(mErrorPageURL, HTTP_CONTENT_TEXT_HTML); }; + mLoadingState = LOADING_STATE_ERROR; }; break; @@ -1106,12 +1116,14 @@ void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) case MEDIA_EVENT_PLUGIN_FAILED: { LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED" << LL_ENDL; + mLoadingState = LOADING_STATE_ERROR; }; break; case MEDIA_EVENT_PLUGIN_FAILED_LAUNCH: { LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED_LAUNCH" << LL_ENDL; + mLoadingState = LOADING_STATE_ERROR; }; break; @@ -1194,6 +1206,33 @@ void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) case MEDIA_EVENT_DEBUG_MESSAGE: { LL_INFOS("media") << self->getDebugMessageText() << LL_ENDL; + + // Handle text extraction responses + std::string debug_text = self->getDebugMessageText(); + if (debug_text.find(PAGE_TEXT_EXTRACT_MARKER) != std::string::npos) + { + if (LLPluginClassMedia* plugin = getMediaPlugin()) + { + // Disable plugin debugging if it was used just for text extraction + static LLCachedControl<bool> media_debugging(gSavedSettings, "MediaPluginDebugging", false); + plugin->enableMediaPluginDebugging(media_debugging); + } + // Extract the pump name and page text + size_t marker_pos = debug_text.find(PAGE_TEXT_EXTRACT_MARKER); + if (marker_pos != std::string::npos) + { + std::string remaining = debug_text.substr(marker_pos + PAGE_TEXT_EXTRACT_MARKER.length()); + size_t colon_pos = remaining.find(':'); + if (colon_pos != std::string::npos) + { + std::string pump_name = remaining.substr(0, colon_pos); + std::string page_text = remaining.substr(colon_pos + 1); + + // Send the response directly to the specified pump + LLEventPumps::instance().obtain(pump_name).post(LLSD().with("text", page_text)); + } + } + } }; break; }; @@ -1274,3 +1313,148 @@ bool LLMediaCtrl::wantsReturnKey() const { return true; } + +std::string LLMediaCtrl::getMediaMimeType() +{ + return mMediaSource ? mMediaSource->getMimeType() : "unknown"; +} + +std::string LLMediaCtrl::getMediaLoadingStatus() +{ + if (!mMediaSource) + { + return "error"; + } + + switch (mLoadingState) + { + case LOADING_STATE_INITIALIZING: + return "initializing"; + case LOADING_STATE_LOADING: + return "loading"; + case LOADING_STATE_LOADED: + return "loaded"; + case LOADING_STATE_ERROR: + default: + return "error"; + } +} + +std::string LLMediaCtrl::getMediaTitle() +{ + if (mMediaSource) + { + if (LLPluginClassMedia* plugin = mMediaSource->getMediaPlugin()) + { + return plugin->getMediaName(); + } + } + return "unknown"; +} + +bool LLMediaCtrl::executeJavaScript(const std::string& script) +{ + if (mMediaSource && mMediaSource->hasMedia()) + { + mMediaSource->executeJavaScript(script); + return true; + } + return false; +} + +class LLMediaCtrlListener: public LLEventAPI +{ +public: + LLMediaCtrlListener(); + +private: + void getMediaInfo(const LLSD& request); + void getMediaText(const LLSD& request); + void replyError(const LLSD& request, const std::string& error); + LLMediaCtrl* findMediaCtrl(const std::string& path); +}; + +LLMediaCtrlListener::LLMediaCtrlListener(): + LLEventAPI("LLMediaAPI", "Acces to LLMediaCtrl(web_browse widget) info") +{ + add("getMediaInfo", + "Get information about the web_browser widget specified by [\"path\"].\n" + "Returns URL, MIME type, and loading status of the widget.", + &LLMediaCtrlListener::getMediaInfo, + llsd::map("path", LLSD(), "reply", LLSD())); + + add("getMediaText", + "Get text content from the web_browser widget specified by [\"path\"].\n" + "Returns the text content of the page or a portion of it.", + &LLMediaCtrlListener::getMediaText, + llsd::map("path", LLSD(), "reply", LLSD())); +} + +LLMediaCtrl* LLMediaCtrlListener::findMediaCtrl(const std::string& path) +{ + LLView* view = LLUI::getInstance()->resolvePath(LLUI::getInstance()->getRootView(), path); + if (!view) + { + return nullptr; + } + return dynamic_cast<LLMediaCtrl*>(view); +} + +void LLMediaCtrlListener::getMediaInfo(const LLSD& request) +{ + Response reply(LLSD(), request); + std::string path = request["path"]; + + LLMediaCtrl* media_ctrl = findMediaCtrl(path); + if (!media_ctrl) + { + reply["error"] = "Could not find web_browser widget at path: " + path; + return; + } + + reply["url"] = media_ctrl->getCurrentNavUrl(); + reply["mime_type"] = media_ctrl->getMediaMimeType(); + reply["status"] = media_ctrl->getMediaLoadingStatus(); + reply["title"] = media_ctrl->getMediaTitle(); +} + +void LLMediaCtrlListener::replyError(const LLSD& request, const std::string& error) +{ + Response reply(LLSD(), request); + reply["error"] = error; +} + +void LLMediaCtrlListener::getMediaText(const LLSD& request) +{ + std::string path = request["path"]; + + LLMediaCtrl* media_ctrl = findMediaCtrl(path); + if (!media_ctrl) + { + replyError(request, "Could not find web_browser widget at path: " + path); + return; + } + + LLPluginClassMedia* plugin = media_ctrl->getMediaPlugin(); + if (!plugin) + { + replyError(request, "Media plugin is not available for widget at path: " + path); + return; + } + + // Enable plugin debugging to capture console messages + plugin->enableMediaPluginDebugging(true); + std::string pump_name = request["reply"].asString(); + + // Execute JavaScript to extract page text, embedding pump name in the marker + const std::string text_extract_script = "console.log('" + PAGE_TEXT_EXTRACT_MARKER + pump_name + ":' + " + "(document.body ? (document.body.innerText ? document.body.innerText.substring(0, 1000).replace(/\\s+/g, ' ').trim() : " + "'No text content') : 'Document body not ready'));"; + + if (!media_ctrl->executeJavaScript(text_extract_script)) + { + replyError(request, "Failed to execute JavaScript for text extraction"); + } +} + +static LLMediaCtrlListener sMediaCtrlListener; diff --git a/indra/newview/llmediactrl.h b/indra/newview/llmediactrl.h index a644ef3071..e407bfbbcb 100644 --- a/indra/newview/llmediactrl.h +++ b/indra/newview/llmediactrl.h @@ -77,6 +77,14 @@ protected: public: virtual ~LLMediaCtrl(); + enum ELoadingState + { + LOADING_STATE_INITIALIZING = 0, + LOADING_STATE_LOADING = 1, + LOADING_STATE_LOADED = 2, + LOADING_STATE_ERROR = 3 + }; + void setBorderVisible( bool border_visible ); // For the tutorial window, we don't want to take focus on clicks, @@ -181,6 +189,11 @@ public: virtual bool acceptsTextInput() const { return true; } + std::string getMediaMimeType(); + std::string getMediaLoadingStatus(); + std::string getMediaTitle(); + bool executeJavaScript(const std::string& script); + protected: void convertInputCoords(S32& x, S32& y); @@ -217,6 +230,7 @@ public: viewer_media_t mMediaSource; S32 mTextureWidth, mTextureHeight; + ELoadingState mLoadingState; class LLWindowShade* mWindowShade; LLHandle<LLContextMenu> mContextMenuHandle; diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index b72301c566..36bcba2c5c 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -167,7 +167,8 @@ LLMuteList::LLMuteList() : mLoadState(ML_INITIAL), mLoadSource(MLS_NONE), mRequestStartTime(0.f), - mTriedCacheFallback(false) + mTriedCacheFallback(false), + mTriedRegionChangeRetry(false) { gGenericDispatcher.addHandler("emptymutelist", &sDispatchEmptyMuteList); @@ -857,6 +858,9 @@ void LLMuteList::requestFromServer(const LLUUID& agent_id) // Guard against potentially writing back to disk since we're not recovering our connection mLoadState = ML_LOADED; mLoadSource = MLS_FALLBACK_CACHE; + // This code path means we have disconnected/crashed before our request has been sent. + // As a result we do not NEED to do anything more than set these state values. + // cache() is liable to be called on shutdown, but since we've set a dirty state it will avoid writing to disk. return; } if (!gAgent.getRegion()) @@ -879,7 +883,7 @@ void LLMuteList::requestFromServer(const LLUUID& agent_id) void LLMuteList::cache(const LLUUID& agent_id) { // Write to disk even if empty, but never from degraded fallback state. - if (isLoaded() && mLoadSource != MLS_FALLBACK_CACHE) + if (isLoaded() && !isLoadedDegraded()) { const std::string filename = getCacheFilename(agent_id); saveToFile(filename); diff --git a/indra/newview/llmutelist.h b/indra/newview/llmutelist.h index 2781e9b177..aff23c72d1 100644 --- a/indra/newview/llmutelist.h +++ b/indra/newview/llmutelist.h @@ -129,9 +129,9 @@ public: // Load state accessors. bool isLoaded() const { return mLoadState == ML_LOADED; } // Loaded, but not necessarily from server. bool isFailed() const { return mLoadState == ML_FAILED; } // Unable to load any mute list. Server did not reply. - // Loaded from server, which is the only source we consider authoritative. - bool isLoadedFromServer() const { return isLoaded() && (mLoadSource == MLS_SERVER || mLoadSource == MLS_SERVER_EMPTY); } - // Loaded, but from cache. Would be nice to upgrade to a server load from here if possible. + // Loaded from an authoritative server response, including when the server directs us to use our cached copy. + bool isLoadedFromServer() const { return isLoaded() && (mLoadSource == MLS_SERVER || mLoadSource == MLS_SERVER_EMPTY || mLoadSource == MLS_SERVER_CACHE); } + // Loaded without an authoritative server response. Would be nice to upgrade to a server load from here if possible. bool isLoadedDegraded() const { return isLoaded() && !isLoadedFromServer(); } // Advance the load state machine, trying cache fallback if necessary. diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp index 345426824e..3488685f14 100644 --- a/indra/newview/llpanelface.cpp +++ b/indra/newview/llpanelface.cpp @@ -854,24 +854,56 @@ struct LLPanelFaceSetAlignedTEFunctor : public LLSelectedTEFunctor } // Also align GLTF material if any - S32 gltf_info_index = 0; // base texture + LLGLTFMaterial::TextureInfo gltf_info_index = mPanel->getPBRTextureInfo(); LLVector2 gltf_offset, gltf_scale; F32 gltf_rot; - if (facep->calcAlignedPlanarGLTF(mCenterFace, &gltf_offset, &gltf_scale, &gltf_rot, gltf_info_index)) + + if (gltf_info_index == LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT) { + // "Complete material" - update all texture transforms LLGLTFMaterial new_override; const LLTextureEntry* tep = object->getTE(te); if (tep && tep->getGLTFMaterialOverride()) { new_override = *tep->getGLTFMaterialOverride(); } + bool any_changed = false; + + for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i) + { + if (facep->calcAlignedPlanarGLTF(mCenterFace, &gltf_offset, &gltf_scale, &gltf_rot, i)) + { + LLGLTFMaterial::TextureTransform& transform = new_override.mTextureTransform[i]; + transform.mOffset.set(gltf_offset.mV[0], gltf_offset.mV[1]); + transform.mScale.set(gltf_scale.mV[0], gltf_scale.mV[1]); + transform.mRotation = gltf_rot; + any_changed = true; + } + } + + if (any_changed) + { + LLGLTFMaterialList::queueModify(object, te, &new_override); + } + } + else + { + if (facep->calcAlignedPlanarGLTF(mCenterFace, &gltf_offset, &gltf_scale, &gltf_rot, gltf_info_index)) + { + LLGLTFMaterial new_override; + const LLTextureEntry* tep = object->getTE(te); + if (tep && tep->getGLTFMaterialOverride()) + { + new_override = *tep->getGLTFMaterialOverride(); + } - LLGLTFMaterial::TextureTransform& transform = new_override.mTextureTransform[gltf_info_index]; - transform.mOffset.set(gltf_offset.mV[0], gltf_offset.mV[1]); - transform.mScale.set(gltf_scale.mV[0], gltf_scale.mV[1]); - transform.mRotation = gltf_rot; + LLGLTFMaterial::TextureTransform& transform = new_override.mTextureTransform[gltf_info_index]; + transform.mOffset.set(gltf_offset.mV[0], gltf_offset.mV[1]); + transform.mScale.set(gltf_scale.mV[0], gltf_scale.mV[1]); + transform.mRotation = gltf_rot; - LLGLTFMaterialList::queueModify(object, te, &new_override); + LLGLTFMaterialList::queueModify(object, te, &new_override); + } } } if (!set_aligned) @@ -1073,6 +1105,9 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/) // only turn on auto-adjust button if there is a media renderer and the media is loaded mBtnAlign->setEnabled(editable); + // enable if needed before changing selection + mComboMatMedia->setEnabledByValue("Materials", !has_pbr_material); + if (mComboMatMedia->getCurrentIndex() < MATMEDIA_MATERIAL) { // When selecting an object with a pbr and UI combo is not set, @@ -1677,7 +1712,6 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/) mCheckFullbright->setValue((S32)(fullbright_flag != 0)); mCheckFullbright->setEnabled(editable && !has_pbr_material); mCheckFullbright->setTentative(!identical_fullbright); - mComboMatMedia->setEnabledByValue("Materials", !has_pbr_material); } // Repeats per meter diff --git a/indra/newview/llpanelgrouproles.cpp b/indra/newview/llpanelgrouproles.cpp index e1f2d7588c..426a89fe6c 100644 --- a/indra/newview/llpanelgrouproles.cpp +++ b/indra/newview/llpanelgrouproles.cpp @@ -1938,9 +1938,11 @@ LLPanelGroupRolesSubTab::LLPanelGroupRolesSubTab() mRolesList(NULL), mAssignedMembersList(NULL), mAllowedActionsList(NULL), + mActionDescription(NULL), mRoleName(NULL), mRoleTitle(NULL), mRoleDescription(NULL), + mMembersNotLoadedLbl(NULL), mMemberVisibleCheck(NULL), mDeleteRoleButton(NULL), mCopyRoleButton(NULL), @@ -1969,6 +1971,7 @@ bool LLPanelGroupRolesSubTab::postBuildSubTab(LLView* root) mAssignedMembersList = parent->getChild<LLNameListCtrl>("role_assigned_members"); mAllowedActionsList = parent->getChild<LLScrollListCtrl>("role_allowed_actions"); mActionDescription = parent->getChild<LLTextEditor>("role_action_description"); + mMembersNotLoadedLbl = parent->getChild<LLUICtrl>("members_not_loaded"); mRoleName = parent->getChild<LLLineEditor>("role_name"); mRoleTitle = parent->getChild<LLLineEditor>("role_title"); @@ -2199,12 +2202,18 @@ void LLPanelGroupRolesSubTab::update(LLGroupChange gc) } } - if ((GC_ROLE_MEMBER_DATA == gc || GC_MEMBER_DATA == gc) - && gdatap - && gdatap->isMemberDataComplete() - && gdatap->isRoleMemberDataComplete()) + if (gdatap && gdatap->isMemberDataComplete()) { - buildMembersList(); + if ((GC_ROLE_MEMBER_DATA == gc || GC_MEMBER_DATA == gc) + && gdatap->isRoleMemberDataComplete()) + { + buildMembersList(); + } + mMembersNotLoadedLbl->setVisible(false); + } + else + { + mMembersNotLoadedLbl->setVisible(true); } } diff --git a/indra/newview/llpanelgrouproles.h b/indra/newview/llpanelgrouproles.h index e320efa1c7..18c0e5dfea 100644 --- a/indra/newview/llpanelgrouproles.h +++ b/indra/newview/llpanelgrouproles.h @@ -297,6 +297,7 @@ protected: LLLineEditor* mRoleTitle; LLTextEditor* mRoleDescription; + LLUICtrl* mMembersNotLoadedLbl; LLCheckBoxCtrl* mMemberVisibleCheck; LLButton* mDeleteRoleButton; LLButton* mCreateRoleButton; diff --git a/indra/newview/llpanelplaces.cpp b/indra/newview/llpanelplaces.cpp index 5c866905d6..5dd4960542 100644 --- a/indra/newview/llpanelplaces.cpp +++ b/indra/newview/llpanelplaces.cpp @@ -84,6 +84,7 @@ static const std::string LANDMARK_INFO_TYPE = "landmark"; static const std::string REMOTE_PLACE_INFO_TYPE = "remote_place"; static const std::string TELEPORT_HISTORY_INFO_TYPE = "teleport_history"; static const std::string LANDMARK_TAB_INFO_TYPE = "open_landmark_tab"; +static const std::string TELEPORT_HISTORY_TAB_INFO_TYPE = "open_teleport_history_tab"; // Support for secondlife:///app/parcel/{UUID}/about SLapps class LLParcelHandler : public LLCommandHandler @@ -411,6 +412,15 @@ void LLPanelPlaces::onOpen(const LLSD& key) // Update the buttons at the bottom of the panel updateVerbs(); } + else if (key_type == TELEPORT_HISTORY_TAB_INFO_TYPE) + { + // toggle twice, similar to LANDMARK_TAB_INFO_TYPE + togglePlaceInfoPanel(false); + mPlaceInfoType = key_type; + togglePlaceInfoPanel(false); + onTabSelected(); + updateVerbs(); + } else if (key_type == CREATE_PICK_TYPE) { LLUUID item_id = key["item_id"]; @@ -1104,6 +1114,18 @@ void LLPanelPlaces::togglePlaceInfoPanel(bool visible) } } } + else if (mPlaceInfoType == TELEPORT_HISTORY_TAB_INFO_TYPE) + { + mLandmarkInfo->setVisible(false); + mPlaceProfile->setVisible(false); + if (!visible) + { + if (LLPanel* teleport_history_panel = mTabContainer->getPanelByName("Teleport History")) + { + mTabContainer->selectTabPanel(teleport_history_panel); + } + } + } } // virtual diff --git a/indra/newview/llpanelprofilepicks.cpp b/indra/newview/llpanelprofilepicks.cpp index c9626bf9ea..6870d719af 100644 --- a/indra/newview/llpanelprofilepicks.cpp +++ b/indra/newview/llpanelprofilepicks.cpp @@ -622,6 +622,7 @@ bool LLPanelProfilePick::postBuild() { mPickName = getChild<LLLineEditor>("pick_name"); mPickDescription = getChild<LLTextEditor>("pick_desc"); + mPickLocation = getChild<LLLineEditor>("pick_location"); mSaveButton = getChild<LLButton>("save_changes_btn"); mCreateButton = getChild<LLButton>("create_changes_btn"); mCancelButton = getChild<LLButton>("cancel_changes_btn"); @@ -646,11 +647,20 @@ bool LLPanelProfilePick::postBuild() mPickDescription->setKeystrokeCallback(boost::bind(&LLPanelProfilePick::onPickChanged, this, _1)); mPickDescription->setFocusReceivedCallback(boost::bind(&LLPanelProfilePick::onDescriptionFocusReceived, this)); - getChild<LLUICtrl>("pick_location")->setEnabled(false); + mPickLocation->setEnabled(false); return true; } +void LLPanelProfilePick::reshape(S32 width, S32 height, bool called_from_parent) +{ + LLPanelProfilePropertiesProcessorTab::reshape(width, height, called_from_parent); + if (mPickLocation) + { + mPickLocation->setCursor(0); + } +} + void LLPanelProfilePick::onDescriptionFocusReceived() { if (!mIsEditing && getSelfProfile()) @@ -749,7 +759,14 @@ void LLPanelProfilePick::setPickLocation(const LLUUID &parcel_id, const std::str void LLPanelProfilePick::setPickLocation(const std::string& location) { - getChild<LLUICtrl>("pick_location")->setValue(location); + mPickLocation->setValue(location); + // Pick location can be set with a long 'substitute' value or + // just a long value. + // If user sets cursor at the end, application of the substitute + // value can shift text from visible are to the left. When text + // gets restored or set, text position isn't, so just drop cursor + // position. + mPickLocation->setCursor(0); mPickLocationStr = location; mLastRequestTimer.reset(); } @@ -896,6 +913,10 @@ void LLPanelProfilePick::sendParcelInfoRequest() void LLPanelProfilePick::processParcelInfo(const LLParcelData& parcel_data) { + // Region might have moved since the pick was saved; refresh the stored global position + // using the parcel info so map/teleport use the current location. + setPosGlobal(LLVector3d(parcel_data.global_x, parcel_data.global_y, parcel_data.global_z)); + setPickLocation(createLocationText(LLStringUtil::null, parcel_data.name, parcel_data.sim_name, getPosGlobal())); // We have received parcel info for the requested ID so clear it now. diff --git a/indra/newview/llpanelprofilepicks.h b/indra/newview/llpanelprofilepicks.h index 847ac57cea..b925f09f92 100644 --- a/indra/newview/llpanelprofilepicks.h +++ b/indra/newview/llpanelprofilepicks.h @@ -112,6 +112,8 @@ public: void setAvatarId(const LLUUID& avatar_id) override; + void reshape(S32 width, S32 height, bool called_from_parent = true) override; + void setPickId(const LLUUID& id) { mPickId = id; } virtual LLUUID& getPickId() { return mPickId; } @@ -228,13 +230,14 @@ public: protected: - LLTextureCtrl* mSnapshotCtrl; - LLLineEditor* mPickName; - LLTextEditor* mPickDescription; - LLButton* mSetCurrentLocationButton; - LLButton* mSaveButton; - LLButton* mCreateButton; - LLButton* mCancelButton; + LLTextureCtrl* mSnapshotCtrl = nullptr; + LLLineEditor* mPickName = nullptr; + LLTextEditor* mPickDescription = nullptr; + LLLineEditor* mPickLocation = nullptr; + LLButton* mSetCurrentLocationButton = nullptr; + LLButton* mSaveButton = nullptr; + LLButton* mCreateButton = nullptr; + LLButton* mCancelButton = nullptr; LLVector3d mPosGlobal; LLUUID mParcelId; diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 55f23eb021..b3ed65be82 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -832,6 +832,7 @@ bool idle_startup() set_startup_status(0.03f, msg.c_str(), gAgent.mMOTD.c_str()); do_startup_frame(); // LLViewerMedia::initBrowser(); + LLAppViewer::instance()->createInitedMarker(); LLStartUp::setStartupState( STATE_LOGIN_SHOW ); return false; } @@ -1353,6 +1354,10 @@ bool idle_startup() { set_startup_status(0.30f, LLTrans::getString("LoginInitializingWorld"), gAgent.mMOTD); do_startup_frame(); + + // close login UI before world UI is initialized, if it is still visible + LLPanelLogin::closePanel(); + // We should have an agent id by this point. llassert(!(gAgentID == LLUUID::null)); diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index 51ade60827..144940c705 100644 --- a/indra/newview/lltexturefetch.cpp +++ b/indra/newview/lltexturefetch.cpp @@ -1297,7 +1297,7 @@ bool LLTextureFetchWorker::doWork(S32 param) else { mCanUseCapability = false; - if (gDisconnected) + if (gDisconnected || LLAppViewer::isExiting()) { // We lost connection or are shutting down. mCanUseHTTP = false; @@ -1315,6 +1315,12 @@ bool LLTextureFetchWorker::doWork(S32 param) else { mCanUseCapability = false; + if (gDisconnected || LLAppViewer::isExiting()) + { + // We lost connection or are shutting down. + mCanUseHTTP = false; + return true; // abort + } mRegionRetryAttempt++; mRegionRetryTimer.setTimerExpirySec(CAP_MISSING_EXPIRATION_DELAY); // This will happen if not logged in or if a region deoes not have HTTP Texture enabled diff --git a/indra/newview/llurlfloaterdispatchhandler.cpp b/indra/newview/llurlfloaterdispatchhandler.cpp index 9bee4870be..cf0657ea2f 100644 --- a/indra/newview/llurlfloaterdispatchhandler.cpp +++ b/indra/newview/llurlfloaterdispatchhandler.cpp @@ -29,15 +29,9 @@ #include "llurlfloaterdispatchhandler.h" #include "llfloaterreg.h" -#include "llfloaterhowto.h" #include "llfloaterwebcontent.h" #include "llsdserialize.h" -#include "llviewercontrol.h" #include "llviewergenericmessage.h" -#include "llweb.h" - -// Example: -// llOpenFloater("guidebook", "http://page.com", []); // values specified by server side's dispatcher // for llopenfloater @@ -50,20 +44,12 @@ const std::string KEY_URL("floater_url"); const std::string KEY_PARAMS("floater_params"); // Supported floaters -const std::string FLOATER_GUIDEBOOK("guidebook"); -const std::string FLOATER_HOW_TO("how_to"); // alias for guidebook const std::string FLOATER_WEB_CONTENT("web_content"); // All arguments are palceholders! Server side will need to add validation first. // Web content universal argument const std::string KEY_TRUSTED_CONTENT("trusted_content"); -// Guidebook specific arguments -const std::string KEY_WIDTH("width"); -const std::string KEY_HEGHT("height"); -const std::string KEY_CAN_CLOSE("can_close"); -const std::string KEY_TITLE("title"); - // web_content specific arguments const std::string KEY_SHOW_PAGE_TITLE("show_page_title"); const std::string KEY_ALLOW_ADRESS_ENTRY("allow_address_entry"); // It is not recomended to set this to true if trusted content is allowed @@ -143,48 +129,7 @@ bool LLUrlFloaterDispatchHandler::operator()(const LLDispatcher *, const std::st LLFloaterWebContent::Params params; params.url = url; - if (floater == FLOATER_GUIDEBOOK || floater == FLOATER_HOW_TO) - { - LL_DEBUGS("URLFloater") << "Opening how_to floater with parameters: " << message << LL_ENDL; - if (command_params.isMap()) // by default is undefines - { - params.trusted_content = command_params.has(KEY_TRUSTED_CONTENT) ? command_params[KEY_TRUSTED_CONTENT].asBoolean() : false; - - // Script's side argument list can't include other lists, neither - // there is a LLRect type, so expect just width and height - if (command_params.has(KEY_WIDTH) && command_params.has(KEY_HEGHT)) - { - LLRect rect(0, command_params[KEY_HEGHT].asInteger(), command_params[KEY_WIDTH].asInteger(), 0); - params.preferred_media_size.setValue(rect); - } - } - - // Some locations will have customized guidebook, which this function easists for - // only one instance of guidebook can exist at a time, so if this command arrives, - // we need to close previous guidebook then reopen it. - - LLFloater* instance = LLFloaterReg::findInstance("guidebook"); - if (instance) - { - instance->closeHostedFloater(); - } - - LLFloaterReg::toggleInstanceOrBringToFront("guidebook", params); - - if (command_params.isMap()) - { - LLFloater* instance = LLFloaterReg::findInstance("guidebook"); - if (command_params.has(KEY_CAN_CLOSE)) - { - instance->setCanClose(command_params[KEY_CAN_CLOSE].asBoolean()); - } - if (command_params.has(KEY_TITLE)) - { - instance->setTitle(command_params[KEY_TITLE].asString()); - } - } - } - else if (floater == FLOATER_WEB_CONTENT) + if (floater == FLOATER_WEB_CONTENT) { LL_DEBUGS("URLFloater") << "Opening web_content floater with parameters: " << message << LL_ENDL; if (command_params.isMap()) // by default is undefines, might be better idea to init params from command_params diff --git a/indra/newview/llvelopack.cpp b/indra/newview/llvelopack.cpp index 90eb977bba..26f3f41db6 100644 --- a/indra/newview/llvelopack.cpp +++ b/indra/newview/llvelopack.cpp @@ -43,6 +43,7 @@ #include "Velopack.h" #if LL_WINDOWS +#include "llappviewerwin32.h" #include <windows.h> #include <shlobj.h> #include <shobjidl.h> @@ -238,7 +239,7 @@ static bool custom_download_asset(void* user_data, { // The asset has already been downloaded at the coroutine level (before vpkc_download_updates). // This callback just copies the pre-downloaded file to where Velopack expects it. - // We cannot use getRawAndSuspend here — coroutine context is lost through the Rust FFI boundary. + // We cannot use getRawAndSuspend here - coroutine context is lost through the Rust FFI boundary. if (sPreDownloadedAssetPath.empty()) { LL_WARNS("Velopack") << "No pre-downloaded asset available" << LL_ENDL; @@ -878,8 +879,12 @@ static void on_before_uninstall(void* user_data, const char* app_version) unregister_protocol_handler(PROTOCOL_SECONDLIFE); unregister_protocol_handler(PROTOCOL_GRID_INFO); - unregister_uninstall_info(); remove_shortcuts(app_name); + + std::wstring install_dir = get_install_dir(); + LLAppViewerWin32::sendShutdownToOtherInstances(install_dir); + + unregister_uninstall_info(); } static void on_log_message(void* user_data, const char* level, const char* message) @@ -998,7 +1003,7 @@ static void ensure_update_manager(bool allow_downgrade) LL_INFOS("Velopack") << "Auto-detect failed (" << ll_safe_string(err) << "), falling back to explicit locator" << LL_ENDL; - // Auto-detection failed — construct an explicit locator. + // Auto-detection failed - construct an explicit locator. // This handles legacy DMG installs that don't have Velopack's // install state (UpdateMac, sq.version) in the bundle. vpkc_locator_config_t locator = {}; @@ -1166,7 +1171,7 @@ static void on_downloading_closed(const LLSD& notification, const LLSD& response sDownloadingNotification = nullptr; if (sIsRequired) { - // User closed the downloading dialog during a required update — re-show it + // User closed the downloading dialog during a required update - re-show it show_downloading_notification(sTargetVersion); } } @@ -1255,12 +1260,12 @@ void velopack_check_for_updates(const std::string& required_version, const std:: // strictly lower than what we're running (e.g., a retracted build). bool has_required = !required_version.empty(); int ver_cmp = has_required ? compare_running_version(required_version) : 0; - bool allow_downgrade = ver_cmp > 0; // running > required → rollback scenario + bool allow_downgrade = ver_cmp > 0; // running > required -> rollback scenario ensure_update_manager(allow_downgrade); if (!sUpdateManager) return; - // Ask Velopack to check its feed — this is the source of truth + // Ask Velopack to check its feed - this is the source of truth vpkc_update_info_t* update_info = nullptr; vpkc_update_check_t result = vpkc_check_for_updates(sUpdateManager, &update_info); @@ -1286,7 +1291,7 @@ void velopack_check_for_updates(const std::string& required_version, const std:: sPendingCheckInfo = update_info; // Determine if this is mandatory: running version is below VVM's required floor - bool is_required = ver_cmp < 0; // running < required → must update + bool is_required = ver_cmp < 0; // running < required -> must update sIsRequired = is_required; if (is_required) @@ -1297,12 +1302,12 @@ void velopack_check_for_updates(const std::string& required_version, const std:: return; } - // Optional update — check user preference + // Optional update - check user preference U32 updater_setting = gSavedSettings.getU32("UpdaterServiceSetting"); if (updater_setting == 3) { - // "Install each update automatically" — download silently, apply on quit + // "Install each update automatically" - download silently, apply on quit LL_INFOS("Velopack") << "Optional update to " << target_version << ", downloading automatically (UpdaterServiceSetting=3)" << LL_ENDL; velopack_download_pending_update(); diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 82fc4c6d87..eef31c7048 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -85,7 +85,6 @@ #include "llfloatergroups.h" #include "llfloaterhelpbrowser.h" #include "llfloaterhoverheight.h" -#include "llfloaterhowto.h" #include "llfloaterhud.h" #include "llfloaterimagepreview.h" #include "llfloaterimsession.h" @@ -503,7 +502,6 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("search", "floater_search.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSearch>); LLFloaterReg::add("legacy_search", "floater_directory.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterDirectory>); LLFloaterReg::add("profile", "floater_profile.xml",(LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterProfile>); - LLFloaterReg::add("guidebook", "floater_how_to.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterHowTo>); LLFloaterReg::add("slapp_test", "floater_test_slapp.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterSLappTest>); LLFloaterUIPreviewUtil::registerFloater(); diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 554c7ebcb2..ff111dd1bc 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -5182,6 +5182,27 @@ void handle_link_objects() } } +void handle_unlink_objects() +{ + if (LLSelectMgr::getInstance()->getSelection()->isEmpty()) + { + LLPanel* visited_panel = LLFloaterSidePanelContainer::getPanel("places", "Teleport History"); + if (visited_panel && visited_panel->isInVisibleChain()) + { + LLFloaterReg::hideInstance("places"); + } + else + { + LLFloaterReg::toggleInstanceOrBringToFront("places"); + LLFloaterSidePanelContainer::showPanel("places", LLSD().with("type", "open_teleport_history_tab")); + } + } + else + { + LLSelectMgr::getInstance()->unlinkObjects(); + } +} + // You can return an object to its owner if it is on your land. class LLObjectReturn : public view_listener_t { @@ -8503,15 +8524,6 @@ class LLToolsEnableSaveToObjectInventory : public view_listener_t } }; -class LLToggleHowTo : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLFloaterReg::toggleInstanceOrBringToFront("guidebook"); - return true; - } -}; - class LLViewEnableMouselook : public view_listener_t { bool handleEvent(const LLSD& userdata) @@ -9649,6 +9661,16 @@ void handle_flush_name_caches() if (gCacheName) gCacheName->clear(); } +bool is_master_audio_muted() +{ + return LLAppViewer::instance()->getMasterSystemAudioMute(); +} + +void toggle_master_audio() +{ + LLAppViewer::instance()->setMasterSystemAudioMute(!is_master_audio_muted()); +} + class LLUploadCostCalculator : public view_listener_t { std::string mCostStr; @@ -9920,6 +9942,8 @@ void initialize_menus() view_listener_t::addMenu(new LLWorldEnableEnvPreset(), "World.EnableEnvPreset"); view_listener_t::addMenu(new LLWorldCheckBanLines() , "World.CheckBanLines"); view_listener_t::addMenu(new LLWorldShowBanLines() , "World.ShowBanLines"); + commit.add("World.ToggleMasterAudio", boost::bind(&toggle_master_audio)); + enable.add("World.IsMasterAudioMuted", boost::bind(&is_master_audio_muted)); // Tools menu view_listener_t::addMenu(new LLToolsSelectTool(), "Tools.SelectTool"); @@ -9936,7 +9960,7 @@ void initialize_menus() view_listener_t::addMenu(new LLToolsUseSelectionForGrid(), "Tools.UseSelectionForGrid"); view_listener_t::addMenu(new LLToolsSelectNextPartFace(), "Tools.SelectNextPart"); commit.add("Tools.Link", boost::bind(&handle_link_objects)); - commit.add("Tools.Unlink", boost::bind(&LLSelectMgr::unlinkObjects, LLSelectMgr::getInstance())); + commit.add("Tools.Unlink", boost::bind(&handle_unlink_objects)); view_listener_t::addMenu(new LLToolsStopAllAnimations(), "Tools.StopAllAnimations"); view_listener_t::addMenu(new LLToolsReleaseKeys(), "Tools.ReleaseKeys"); view_listener_t::addMenu(new LLToolsEnableReleaseKeys(), "Tools.EnableReleaseKeys"); @@ -9963,10 +9987,6 @@ void initialize_menus() view_listener_t::addMenu(new LLToolsEnablePathfindingRebakeRegion(), "Tools.EnablePathfindingRebakeRegion"); view_listener_t::addMenu(new LLToolsCheckSelectionLODMode(), "Tools.ToolsCheckSelectionLODMode"); - // Help menu - // most items use the ShowFloater method - view_listener_t::addMenu(new LLToggleHowTo(), "Help.ToggleHowTo"); - // Advanced menu view_listener_t::addMenu(new LLAdvancedToggleConsole(), "Advanced.ToggleConsole"); view_listener_t::addMenu(new LLAdvancedCheckConsole(), "Advanced.CheckConsole"); diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index f31befd1ab..dd02cca50a 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -1768,6 +1768,11 @@ void LLViewerRegion::killInvisibleObjects(F32 max_time) if(iter == mImpl->mActiveSet.end()) { iter = mImpl->mActiveSet.begin(); + if (iter == mImpl->mActiveSet.end()) + { + // Set became empty + break; + } } if((*iter)->getParentID() > 0) { diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 0f3f24d1af..dbf9fe6bf2 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -38,6 +38,7 @@ #include "llagent.h" #include "llagentcamera.h" +#include "llcallbacklist.h" #include "llcommandhandler.h" #include "llcommunicationchannel.h" #include "llfloaterreg.h" @@ -1466,12 +1467,32 @@ void LLViewerWindow::handleMouseLeave(LLWindow *window) LLToolTipMgr::instance().blockToolTips(); } +void LLViewerWindow::handlePreCloseRequest() +{ + // WINDOW THREAD! since we need this to act fast. + if (!LLApp::isExiting() && !LLApp::isStopped()) + { + LLAppViewer::instance()->createCloseRequestMarker(); + } + +} + bool LLViewerWindow::handleCloseRequest(LLWindow *window, bool from_user) { if (!LLApp::isExiting() && !LLApp::isStopped()) { if (from_user) { + // Task naamger kills viewer after 1 second, 3 seconds + // is overkill, but decided to be on a safe side. + doAfterInterval([]() + { + // if user quits, marker will be cleaned by cleanup, + // if user cancels quit, marker will be cleaned here, + // but if task manager kills us, marker stays. + LLAppViewer::instance()->removeCloseRequestMarker(); + }, 3.0f); + // User has indicated they want to close, but we may need to ask // about modified documents. LLAppViewer::instance()->userQuit(); diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h index ec28a3fc4a..ff5da371ec 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -202,6 +202,7 @@ public: /*virtual*/ bool handleUnicodeChar(llwchar uni_char, MASK mask); // NOT going to handle extended /*virtual*/ bool handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); /*virtual*/ bool handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); + /*virtual*/ void handlePreCloseRequest(); /*virtual*/ bool handleCloseRequest(LLWindow *window, bool from_user); /*virtual*/ bool handleSessionExit(LLWindow* window); /*virtual*/ void handleQuit(LLWindow *window); diff --git a/indra/newview/res/viewerRes.rc b/indra/newview/res/viewerRes.rc index dc2ba5f171..c9ca758555 100755 --- a/indra/newview/res/viewerRes.rc +++ b/indra/newview/res/viewerRes.rc @@ -157,7 +157,7 @@ BEGIN VALUE "FileDescription", "Second Life" VALUE "FileVersion", "${VIEWER_VERSION_MAJOR}.${VIEWER_VERSION_MINOR}.${VIEWER_VERSION_PATCH}.${VIEWER_VERSION_REVISION}" VALUE "InternalName", "Second Life" - VALUE "LegalCopyright", "Copyright (c) 2020, Linden Research, Inc." + VALUE "LegalCopyright", "Copyright (c) 2026, Linden Research, Inc." VALUE "OriginalFilename", "SecondLife.exe" VALUE "ProductName", "Second Life" VALUE "ProductVersion", "${VIEWER_VERSION_MAJOR}.${VIEWER_VERSION_MINOR}.${VIEWER_VERSION_PATCH}.${VIEWER_VERSION_REVISION}" diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index 1f4b12fc91..9a83176f26 100644 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -143,7 +143,6 @@ with the same filename but different name <texture name="Command_Destinations_Icon" file_name="toolbar_icons/destinations.png" preload="true" /> <texture name="Command_Gestures_Icon" file_name="toolbar_icons/gestures.png" preload="true" /> <texture name="Command_Grid_Status_Icon" file_name="toolbar_icons/grid_status.png" preload="true" /> - <texture name="Command_HowTo_Icon" file_name="toolbar_icons/howto.png" preload="true" /> <texture name="Command_Inventory_Icon" file_name="toolbar_icons/inventory.png" preload="true" /> <texture name="Command_Map_Icon" file_name="toolbar_icons/map.png" preload="true" /> <texture name="Command_Marketplace_Icon" file_name="toolbar_icons/marketplace.png" preload="true" /> diff --git a/indra/newview/skins/default/textures/toolbar_icons/howto.png b/indra/newview/skins/default/textures/toolbar_icons/howto.png Binary files differdeleted file mode 100644 index 8594d71113..0000000000 --- a/indra/newview/skins/default/textures/toolbar_icons/howto.png +++ /dev/null diff --git a/indra/newview/skins/default/xui/da/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/da/floater_live_lsleditor.xml index 0cc13fd736..8d843b4ceb 100644 --- a/indra/newview/skins/default/xui/da/floater_live_lsleditor.xml +++ b/indra/newview/skins/default/xui/da/floater_live_lsleditor.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <floater name="script ed float" title="SCRIPT: NYT SCRIPT"> <floater.string name="not_allowed"> - Du kan ikke se eller redigere dette script, da det er sat til "no copy". Du skal bruge fulde rettigheder for at kunne se og redigere et script i dette objekt. + Du kan ikke se eller redigere dette script, da det er sat til "no copy" eller "no modify". Du skal have disse tilladelser for at kunne se eller redigere et script i dette objekt. </floater.string> <floater.string name="script_running"> Kører diff --git a/indra/newview/skins/default/xui/da/panel_script_ed.xml b/indra/newview/skins/default/xui/da/panel_script_ed.xml index 3dec4bf101..bc48e43e3c 100644 --- a/indra/newview/skins/default/xui/da/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/da/panel_script_ed.xml @@ -4,7 +4,7 @@ Henter... </panel.string> <panel.string name="can_not_view"> - Du kan ikke se eller rette dette script, da det er sat til "no copy". Du skal have fulde rettigheder for at se eller rette et script i et objekt. + Du kan ikke se eller redigere dette script, da det er sat til "no copy" eller "no modify". Du skal have disse tilladelser for at kunne se eller redigere et script i dette objekt. </panel.string> <panel.string name="public_objects_can_not_run"> Offentlige objekter kan ikke afvikle scripts diff --git a/indra/newview/skins/default/xui/de/floater_how_to.xml b/indra/newview/skins/default/xui/de/floater_how_to.xml deleted file mode 100644 index caea221f83..0000000000 --- a/indra/newview/skins/default/xui/de/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="floater_how_to" title="INFOS"/> diff --git a/indra/newview/skins/default/xui/de/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/de/floater_live_lsleditor.xml index ae2dd4db67..308c22fa4b 100644 --- a/indra/newview/skins/default/xui/de/floater_live_lsleditor.xml +++ b/indra/newview/skins/default/xui/de/floater_live_lsleditor.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <floater name="script ed float" title="SKRIPT: NEUES SKRIPT"> <floater.string name="not_allowed"> - Dieses Skript kann nicht angezeigt oder bearbeitet werden, da als Berechtigung „kein kopieren" festgelegt wurde. Um ein Skript innerhalb eines Objektes anzuzeigen oder zu bearbeiten, benötigen Sie die vollständige Berechtigung. + Dieses Skript kann nicht angezeigt oder bearbeitet werden, da als Berechtigung „kein kopieren" oder „keine Änderungen" festgelegt wurde. Um ein Skript innerhalb eines Objektes anzuzeigen oder zu bearbeiten, benötigen Sie diese Berechtigungen. </floater.string> <floater.string name="script_running"> Läuft diff --git a/indra/newview/skins/default/xui/de/notifications.xml b/indra/newview/skins/default/xui/de/notifications.xml index 6ad71e0ad1..40b34d99ba 100644 --- a/indra/newview/skins/default/xui/de/notifications.xml +++ b/indra/newview/skins/default/xui/de/notifications.xml @@ -1127,7 +1127,6 @@ Falls Sie [SECOND_LIFE] zum ersten Mal verwenden, müssen Sie zuerst ein Konto e Ihr Avatar erscheint jeden Moment. Benutzen Sie die Pfeiltasten, um sich fortzubewegen. -Drücken Sie F1 für Hilfe oder für weitere Informationen über [SECOND_LIFE]. Bitte wählen Sie einen männlichen oder weiblichen Avatar. Sie können sich später noch umentscheiden. <usetemplate name="okcancelbuttons" notext="Weiblich" yestext="Männlich"/> diff --git a/indra/newview/skins/default/xui/de/panel_script_ed.xml b/indra/newview/skins/default/xui/de/panel_script_ed.xml index 8975cd40f8..90fd7568c3 100644 --- a/indra/newview/skins/default/xui/de/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/de/panel_script_ed.xml @@ -4,7 +4,7 @@ Wird geladen... </panel.string> <panel.string name="can_not_view"> - Dieses Skript kann nicht angezeigt oder bearbeitet werden, da als Berechtigung „kein kopieren" festgelegt wurde. Um ein Skript innerhalb eines Objektes anzuzeigen oder zu bearbeiten, benötigen Sie die vollständige Berechtigung. + Dieses Skript kann nicht angezeigt oder bearbeitet werden, da als Berechtigung „kein kopieren" oder „keine Änderungen" festgelegt wurde. Um ein Skript innerhalb eines Objektes anzuzeigen oder zu bearbeiten, benötigen Sie diese Berechtigungen. </panel.string> <panel.string name="public_objects_can_not_run"> Öffentliche Objekte können keine Skripts ausführen diff --git a/indra/newview/skins/default/xui/de/strings.xml b/indra/newview/skins/default/xui/de/strings.xml index d7b2ee57a8..bd26672ccf 100644 --- a/indra/newview/skins/default/xui/de/strings.xml +++ b/indra/newview/skins/default/xui/de/strings.xml @@ -5537,9 +5537,6 @@ Setzen Sie den Editorpfad in Anführungszeichen <string name="Command_Grid_Status_Label"> Grid-Status </string> - <string name="Command_HowTo_Label"> - Infos - </string> <string name="Command_Inventory_Label"> Inventar </string> @@ -5636,9 +5633,6 @@ Setzen Sie den Editorpfad in Anführungszeichen <string name="Command_Grid_Status_Tooltip"> Aktuellen Grid-Status anzeigen </string> - <string name="Command_HowTo_Tooltip"> - Wie führe ich gängige Aufgaben aus? - </string> <string name="Command_Inventory_Tooltip"> Ihr Eigentum anzeigen und benutzen </string> diff --git a/indra/newview/skins/default/xui/en/floater_how_to.xml b/indra/newview/skins/default/xui/en/floater_how_to.xml deleted file mode 100644 index 5b00d23faa..0000000000 --- a/indra/newview/skins/default/xui/en/floater_how_to.xml +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater - legacy_header_height="18" - can_resize="false" - can_minimize="false" - height="525" - layout="topleft" - name="floater_how_to" - single_instance="true" - save_visibility="true" - save_rect="true" - title="WELCOME ISLAND GUIDEBOOK" - width="310" - rel_x="-0.469309" - rel_y="-0.011166" - filename="floater_web_content.xml"/> diff --git a/indra/newview/skins/default/xui/en/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/en/floater_live_lsleditor.xml index e30c519c8a..da654fb638 100644 --- a/indra/newview/skins/default/xui/en/floater_live_lsleditor.xml +++ b/indra/newview/skins/default/xui/en/floater_live_lsleditor.xml @@ -15,7 +15,7 @@ width="508"> <floater.string name="not_allowed"> - You can not view or edit this script, since it has been set as "no copy". You need full permissions to view or edit a script inside an object. + You can not view or edit this script, since it has been set as "no copy" or "no modify". You need these permissions to view or edit a script inside an object. </floater.string> <floater.string name="script_running"> diff --git a/indra/newview/skins/default/xui/en/menu_login.xml b/indra/newview/skins/default/xui/en/menu_login.xml index 5fff9b7bc0..ec6be3e0c6 100644 --- a/indra/newview/skins/default/xui/en/menu_login.xml +++ b/indra/newview/skins/default/xui/en/menu_login.xml @@ -57,15 +57,6 @@ tear_off="true" name="Help"> <menu_item_call - label="Guidebook" - name="How To" - shortcut="F1"> - <menu_item_call.on_click - function="Help.ToggleHowTo" - parameter="" /> - </menu_item_call> - <menu_item_separator/> - <menu_item_call label="Knowledge Base" name="Knowledge Base"> <menu_item_call.on_click @@ -107,7 +98,14 @@ name="Report Bug"> <menu_item_call.on_click function="Advanced.ReportBug"/> - </menu_item_call> + </menu_item_call> + <menu_item_call + label="Set UI Size to Default" + name="Set UI Size to Default" + shortcut="control|alt|shift|R"> + <menu_item_call.on_click + function="View.DefaultUISize" /> + </menu_item_call> <menu_item_separator/> <menu_item_call label="About [APP_NAME]" diff --git a/indra/newview/skins/default/xui/en/menu_object_icon.xml b/indra/newview/skins/default/xui/en/menu_object_icon.xml index d43ce26e56..bf445b0d18 100644 --- a/indra/newview/skins/default/xui/en/menu_object_icon.xml +++ b/indra/newview/skins/default/xui/en/menu_object_icon.xml @@ -28,6 +28,14 @@ parameter="not_blocked" /> </menu_item_call> <menu_item_call + label="Report Abuse..." + layout="topleft" + name="Report Abuse"> + <menu_item_call.on_click + function="ObjectIcon.Action" + parameter="report_abuse" /> + </menu_item_call> + <menu_item_call label="Unblock" layout="topleft" name="Unblock"> diff --git a/indra/newview/skins/default/xui/en/menu_url_objectim.xml b/indra/newview/skins/default/xui/en/menu_url_objectim.xml index fb6081e1fb..f72d2fdd66 100644 --- a/indra/newview/skins/default/xui/en/menu_url_objectim.xml +++ b/indra/newview/skins/default/xui/en/menu_url_objectim.xml @@ -23,6 +23,13 @@ <menu_item_call.on_click function="Url.Unblock" /> </menu_item_call> + <menu_item_call + label="Report Abuse..." + layout="topleft" + name="Report Abuse"> + <menu_item_call.on_click + function="Url.ReportAbuseObj" /> + </menu_item_call> <menu_item_separator layout="topleft" /> <menu_item_call diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index ffe4bcebd5..3364b831b2 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -44,6 +44,13 @@ function="Tools.Link"/> </menu_item_call> <menu_item_call + label="Visited places..." + name="Visited Places" + shortcut="control|shift|L"> + <menu_item_call.on_click + function="Tools.Unlink"/> + </menu_item_call> + <menu_item_call label="Picks..." name="Picks"> <menu_item_call.on_click @@ -1003,6 +1010,17 @@ parameter="region" /> </menu_item_check> <menu_item_separator/> + <menu_item_check + label="Switch to Shared after teleport" + name="Switch to Shared after teleport"> + <menu_item_check.on_click + function="ToggleControl" + parameter="SwitchToSharedEnvAfterTeleport" /> + <menu_item_check.on_check + function="CheckControl" + parameter="SwitchToSharedEnvAfterTeleport" /> + </menu_item_check> + <menu_item_separator/> <menu_item_call label="My Environments..." name="my_environs"> @@ -1030,7 +1048,15 @@ parameter="pause_clouds" /> </menu_item_check> </menu> - + <menu_item_check + label="Mute/unmute sound" + name="toggle_sound" + shortcut="control|alt|shift|M"> + <menu_item_check.on_click + function="World.ToggleMasterAudio"/> + <menu_item_check.on_check + function="World.IsMasterAudioMuted"/> + </menu_item_check> <!-- <menu create_jump_keys="true" @@ -1804,14 +1830,6 @@ function="World.EnvPreset" label="Help" name="Help" tear_off="true"> - <menu_item_call - label="Guidebook" - name="How To" - shortcut="F1"> - <menu_item_call.on_click - function="Help.ToggleHowTo" - parameter="" /> - </menu_item_call> <!-- <menu_item_call label="Tutorial" name="Tutorial"> @@ -1819,7 +1837,6 @@ function="World.EnvPreset" function="Floater.Show" parameter="hud" /> </menu_item_call>--> - <menu_item_separator/> <menu_item_call label="Knowledge Base" name="Knowledge Base"> diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index a9adb772e5..42ec875bcd 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -3950,7 +3950,6 @@ Local Data you are deleting is shared between multiple grids, are you sure you w Your character will appear in a moment. Use arrow keys to walk. -Press the F1 key at any time for help or to learn more about [SECOND_LIFE]. Please choose the male or female avatar. You can change your mind later. <usetemplate name="okcancelbuttons" @@ -6744,6 +6743,14 @@ Your trash is overflowing. This may cause problems logging in. </notification> <notification + icon="notifytip.tga" + name="InventoryLimitReachedAIS" + type="notifytip"> +Your inventory is experiencing issues. Please, contact support. + <tag>fail</tag> + </notification> + + <notification icon="alertmodal.tga" name="InventoryLimitReachedAISAlert" type="alertmodal"> @@ -7678,7 +7685,7 @@ Message from [NAME]: type="notify"> <unique/> This land has damage enabled. -You can be hurt here. If you die, you will be teleported to your home location. +You can be hurt here. If you die, you might be teleported to your home location, region telehub or parcel landing point. </notification> <notification diff --git a/indra/newview/skins/default/xui/en/panel_group_roles.xml b/indra/newview/skins/default/xui/en/panel_group_roles.xml index 868d54401e..90697d5e4b 100644 --- a/indra/newview/skins/default/xui/en/panel_group_roles.xml +++ b/indra/newview/skins/default/xui/en/panel_group_roles.xml @@ -565,6 +565,18 @@ things in this group. There's a broad variety of Abilities. right="-1" name="role_assigned_members" top_pad="0" /> + <text + type="string" + height="125" + layout="topleft" + follows="left|top" + left="5" + name="members_not_loaded" + visible="false" + top_delta="3" + width="300"> + Members are not loaded + </text> <check_box height="15" label="Reveal members" diff --git a/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml b/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml index 96624e7aa2..4eafa09ac5 100644 --- a/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml +++ b/indra/newview/skins/default/xui/en/panel_outfit_gallery.xml @@ -17,7 +17,7 @@ You don't have any outfits yet. Try [secondlife:///app/search/all/ Search] </string> <string name="no_matched_outfits_msg"> - Didn't find what you're looking for? Try [secondlife:///app/search/all/[SEARCH_TERM] Search]. + No matching items. </string> <text type="string" diff --git a/indra/newview/skins/default/xui/en/panel_outfits_list.xml b/indra/newview/skins/default/xui/en/panel_outfits_list.xml index b38e2b2b50..b44d44e4ec 100644 --- a/indra/newview/skins/default/xui/en/panel_outfits_list.xml +++ b/indra/newview/skins/default/xui/en/panel_outfits_list.xml @@ -24,7 +24,7 @@ width="309"> <no_matched_tabs_text name="no_matched_outfits_msg" - value="Didn't find what you're looking for? Try [secondlife:///app/search/all/[SEARCH_TERM] Search]." + value="No matching items." v_pad="10"/> <no_visible_tabs_text name="no_outfits_msg" diff --git a/indra/newview/skins/default/xui/en/panel_script_ed.xml b/indra/newview/skins/default/xui/en/panel_script_ed.xml index f8761d2b24..4a7adbacde 100644 --- a/indra/newview/skins/default/xui/en/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/en/panel_script_ed.xml @@ -14,7 +14,7 @@ </panel.string> <panel.string name="can_not_view"> - You can not view or edit this script, since it has been set as "no copy". You need full permissions to view or edit a script inside an object. + You can not view or edit this script, since it has been set as "no copy" or "no modify". You need these permissions to view or edit a script inside an object. </panel.string> <panel.string name="public_objects_can_not_run"> diff --git a/indra/newview/skins/default/xui/en/panel_settings_sky_clouds.xml b/indra/newview/skins/default/xui/en/panel_settings_sky_clouds.xml index 23bbf45e88..f39f8cb782 100644 --- a/indra/newview/skins/default/xui/en/panel_settings_sky_clouds.xml +++ b/indra/newview/skins/default/xui/en/panel_settings_sky_clouds.xml @@ -138,7 +138,9 @@ min_val_x="-30" max_val_x="30" min_val_y="-30" - max_val_y="30" + max_val_y="30" + increment_x="0.01f" + increment_y="0.01f" logarithmic="true"/> <text diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index b8e71928dc..a6fb6db63d 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -2325,7 +2325,7 @@ For AI Character: Get the closest navigable point to the point provided. <string name="LeaveMouselook">Press ESC to return to World View</string> <!-- inventory --> - <string name="InventoryNoMatchingItems">Didn't find what you're looking for? Try [secondlife:///app/search/all/[SEARCH_TERM] Search].</string> + <string name="InventoryNoMatchingItems">No matching items.</string> <string name="InventoryNoMatchingRecentItems">Didn't find what you're looking for? Try [secondlife:///app/inventory/filters Show filters].</string> <string name="InventoryNoMatchingFavorites">You haven't marked any items as favorites.</string> <string name="PlacesNoMatchingItems">To add a place to your landmarks, click the star to the right of the location name.</string> @@ -4207,7 +4207,6 @@ name="Command_360_Capture_Label">360 snapshot</string> <string name="Command_Environments_Label">My Environments</string> <string name="Command_Gestures_Label">Gestures</string> <string name="Command_Grid_Status_Label">Grid status</string> - <string name="Command_HowTo_Label">Guidebook</string> <string name="Command_Inventory_Label">Inventory</string> <string name="Command_Map_Label">Map</string> <string name="Command_Marketplace_Label">Marketplace</string> @@ -4240,7 +4239,6 @@ name="Command_360_Capture_Tooltip">Capture a 360 equirectangular image</string> <string name="Command_Environments_Tooltip">My Environments</string> <string name="Command_Gestures_Tooltip">Gestures for your avatar</string> <string name="Command_Grid_Status_Tooltip">Show current Grid status</string> - <string name="Command_HowTo_Tooltip">How to do common tasks</string> <string name="Command_Inventory_Tooltip">View and use your belongings</string> <string name="Command_Map_Tooltip">Map of the world</string> <string name="Command_Marketplace_Tooltip">Go shopping</string> diff --git a/indra/newview/skins/default/xui/es/floater_how_to.xml b/indra/newview/skins/default/xui/es/floater_how_to.xml deleted file mode 100644 index 4a57dc3643..0000000000 --- a/indra/newview/skins/default/xui/es/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="floater_how_to" title="CÓMO"/> diff --git a/indra/newview/skins/default/xui/es/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/es/floater_live_lsleditor.xml index 9672f910ea..cda6d040cb 100644 --- a/indra/newview/skins/default/xui/es/floater_live_lsleditor.xml +++ b/indra/newview/skins/default/xui/es/floater_live_lsleditor.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <floater name="script ed float" title="SCRIPT: SCRIPT NUEVO"> <floater.string name="not_allowed"> - No puedes ver ni editar este script. Ha sido configurado como "no copiable". Necesitas todos los permisos para ver o editar un script que está dentro de un objeto. + No puedes ver ni editar este script. Ha sido configurado como "no copiable" o "no modificable". Necesitas estos permisos para ver o editar un script que está dentro de un objeto. </floater.string> <floater.string name="script_running"> Ejecutándose diff --git a/indra/newview/skins/default/xui/es/notifications.xml b/indra/newview/skins/default/xui/es/notifications.xml index 739391b965..5dee9eb3a8 100644 --- a/indra/newview/skins/default/xui/es/notifications.xml +++ b/indra/newview/skins/default/xui/es/notifications.xml @@ -1119,7 +1119,6 @@ Puedes revisar tu conexión a Internet y volver a intentarlo en unos minutos, pu Tu personaje aparecerá en un momento. Para caminar, usa las teclas del cursor. -En cualquier momento, puedes pulsar la tecla F1 para conseguir ayuda o para aprender más acerca de [SECOND_LIFE]. Por favor, elige el avatar masculino o femenino. Puedes cambiar más adelante tu elección. <usetemplate name="okcancelbuttons" notext="Mujer" yestext="Varón"/> diff --git a/indra/newview/skins/default/xui/es/panel_script_ed.xml b/indra/newview/skins/default/xui/es/panel_script_ed.xml index 89e9304693..ad8631d078 100644 --- a/indra/newview/skins/default/xui/es/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/es/panel_script_ed.xml @@ -4,7 +4,7 @@ Cargando... </panel.string> <panel.string name="can_not_view"> - No puedes ver ni editar este script. Ha sido configurado como "no copiable". Necesitas todos los permisos para ver o editar un script que está dentro de un objeto. + No puedes ver ni editar este script. Ha sido configurado como "no copiable" o "no modificable". Necesitas estos permisos para ver o editar un script que está dentro de un objeto. </panel.string> <panel.string name="public_objects_can_not_run"> Los objetos públicos no pueden ejecutar scripts diff --git a/indra/newview/skins/default/xui/es/strings.xml b/indra/newview/skins/default/xui/es/strings.xml index 86e454b83e..1bfab01108 100644 --- a/indra/newview/skins/default/xui/es/strings.xml +++ b/indra/newview/skins/default/xui/es/strings.xml @@ -5439,9 +5439,6 @@ Inténtalo incluyendo la ruta de acceso al editor entre comillas <string name="Command_Grid_Status_Label"> Estado del Grid </string> - <string name="Command_HowTo_Label"> - Cómo - </string> <string name="Command_Inventory_Label"> Inventario </string> @@ -5538,9 +5535,6 @@ Inténtalo incluyendo la ruta de acceso al editor entre comillas <string name="Command_Grid_Status_Tooltip"> Mostrar el estado actual del Grid </string> - <string name="Command_HowTo_Tooltip"> - Cómo hacer las tareas habituales - </string> <string name="Command_Inventory_Tooltip"> Ver y usar tus pertenencias </string> diff --git a/indra/newview/skins/default/xui/fr/floater_how_to.xml b/indra/newview/skins/default/xui/fr/floater_how_to.xml deleted file mode 100644 index a414212ba0..0000000000 --- a/indra/newview/skins/default/xui/fr/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="floater_how_to" title="AIDE RAPIDE"/> diff --git a/indra/newview/skins/default/xui/fr/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/fr/floater_live_lsleditor.xml index a0d1197ea7..055265d2b9 100644 --- a/indra/newview/skins/default/xui/fr/floater_live_lsleditor.xml +++ b/indra/newview/skins/default/xui/fr/floater_live_lsleditor.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <floater name="script ed float" title="SCRIPT : NOUVEAU SCRIPT"> <floater.string name="not_allowed"> - Ce script ne peut pas être copié, visualisé ou modifié. Pour visualiser ou modifier un script à l'intérieur d'un objet, vous devez avoir les droits requis. + Ce script ne peut pas être visualisé ou modifié, car il est défini comme « non copiable » ou « non modifiable ». Pour visualiser ou modifier un script à l'intérieur d'un objet, vous devez avoir ces permissions. </floater.string> <floater.string name="script_running"> Exécution en cours diff --git a/indra/newview/skins/default/xui/fr/notifications.xml b/indra/newview/skins/default/xui/fr/notifications.xml index 587c88faad..9c79f288c1 100644 --- a/indra/newview/skins/default/xui/fr/notifications.xml +++ b/indra/newview/skins/default/xui/fr/notifications.xml @@ -1109,7 +1109,6 @@ Vérifiez votre connexion Internet et réessayez dans quelques minutes, cliquez Votre personnage va apparaître dans un moment. Pour marcher, utilisez les flèches de direction. -Appuyez sur F1 pour obtenir de l'aide ou en savoir plus sur [SECOND_LIFE]. Choisissez un avatar homme ou femme. Vous pourrez revenir sur votre décision plus tard. <usetemplate name="okcancelbuttons" notext="Femme" yestext="Homme"/> diff --git a/indra/newview/skins/default/xui/fr/panel_script_ed.xml b/indra/newview/skins/default/xui/fr/panel_script_ed.xml index 0a33463cbb..d54f5e6f2e 100644 --- a/indra/newview/skins/default/xui/fr/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/fr/panel_script_ed.xml @@ -4,7 +4,7 @@ Chargement... </panel.string> <panel.string name="can_not_view"> - Ce script ne peut pas être copié, visualisé ou modifié. Pour visualiser ou modifier un script à l'intérieur d'un objet, vous devez avoir les permissions requises. + Ce script ne peut pas être visualisé ou modifié, car il est défini comme « non copiable » ou « non modifiable ». Pour visualiser ou modifier un script à l'intérieur d'un objet, vous devez avoir ces permissions. </panel.string> <panel.string name="public_objects_can_not_run"> Les objets publics ne peuvent pas exécuter de scripts diff --git a/indra/newview/skins/default/xui/fr/strings.xml b/indra/newview/skins/default/xui/fr/strings.xml index 4fb5167d67..4a7a337d4c 100644 --- a/indra/newview/skins/default/xui/fr/strings.xml +++ b/indra/newview/skins/default/xui/fr/strings.xml @@ -5538,9 +5538,6 @@ Essayez avec le chemin d'accès à l'éditeur entre guillemets doubles <string name="Command_Grid_Status_Label"> État de la grille </string> - <string name="Command_HowTo_Label"> - Aide rapide - </string> <string name="Command_Inventory_Label"> Inventaire </string> @@ -5637,9 +5634,6 @@ Essayez avec le chemin d'accès à l'éditeur entre guillemets doubles <string name="Command_Grid_Status_Tooltip"> Afficher l’état actuel de la grille </string> - <string name="Command_HowTo_Tooltip"> - Comment effectuer les opérations courantes - </string> <string name="Command_Inventory_Tooltip"> Afficher et utiliser vos possessions </string> diff --git a/indra/newview/skins/default/xui/it/floater_how_to.xml b/indra/newview/skins/default/xui/it/floater_how_to.xml deleted file mode 100644 index 8f0e210571..0000000000 --- a/indra/newview/skins/default/xui/it/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="floater_how_to" title="ISTRUZIONI"/> diff --git a/indra/newview/skins/default/xui/it/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/it/floater_live_lsleditor.xml index c28df6c12e..af6be61bfb 100644 --- a/indra/newview/skins/default/xui/it/floater_live_lsleditor.xml +++ b/indra/newview/skins/default/xui/it/floater_live_lsleditor.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <floater name="script ed float" title="SCRIPT: NUOVO SCRIPT"> <floater.string name="not_allowed"> - Per impostazione, questo script non può essere visualizzato né modificato. Per visualizzare o modificare uno script all'interno di un oggetto, devi avere i necessari diritti di modifica. + Questo script non può essere visualizzato né modificato, perché è impostato come "non copiabile" o "non modificabile". Per visualizzare o modificare uno script all'interno di un oggetto, devi avere questi permessi. </floater.string> <floater.string name="script_running"> In esecuzione diff --git a/indra/newview/skins/default/xui/it/notifications.xml b/indra/newview/skins/default/xui/it/notifications.xml index f79cc1515b..284f947b18 100644 --- a/indra/newview/skins/default/xui/it/notifications.xml +++ b/indra/newview/skins/default/xui/it/notifications.xml @@ -1115,7 +1115,6 @@ Controlla la tua connessione Internet e riprova fra qualche minuto, oppure clicc Il tuo avatar apparirà fra un attimo. Usa le frecce per muoverti. -Premi F1 in qualunque momento per la guida o per apprendere altre cose di [SECOND_LIFE]. Scegli un avatar maschile o femminile. Puoi sempre cambiare idea più tardi. <usetemplate name="okcancelbuttons" notext="Femminile" yestext="Maschile"/> </notification> diff --git a/indra/newview/skins/default/xui/it/panel_script_ed.xml b/indra/newview/skins/default/xui/it/panel_script_ed.xml index 3cbdadbac8..f0c1c4862b 100644 --- a/indra/newview/skins/default/xui/it/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/it/panel_script_ed.xml @@ -4,7 +4,7 @@ Caricamento in corso... </panel.string> <panel.string name="can_not_view"> - Per impostazione, questo script non può essere visualizzato né modificato. Per visualizzare o modificare uno script all'interno di un oggetto, devi avere i necessari diritti di modifica. + Questo script non può essere visualizzato né modificato, perché è impostato come "non copiabile" o "non modificabile". Per visualizzare o modificare uno script all'interno di un oggetto, devi avere questi permessi. </panel.string> <panel.string name="public_objects_can_not_run"> Gli oggetti pubblici non possono eseguire gli script diff --git a/indra/newview/skins/default/xui/it/strings.xml b/indra/newview/skins/default/xui/it/strings.xml index 2ddb7d77d1..b4d6b2a315 100644 --- a/indra/newview/skins/default/xui/it/strings.xml +++ b/indra/newview/skins/default/xui/it/strings.xml @@ -5453,9 +5453,6 @@ Prova a racchiudere il percorso dell'editor in doppie virgolette. <string name="Command_Grid_Status_Label"> Stato della griglia </string> - <string name="Command_HowTo_Label"> - Istruzioni - </string> <string name="Command_Inventory_Label"> Inventario </string> @@ -5552,9 +5549,6 @@ Prova a racchiudere il percorso dell'editor in doppie virgolette. <string name="Command_Grid_Status_Tooltip"> Mostra stato griglia corrente </string> - <string name="Command_HowTo_Tooltip"> - Come eseguire le attività più comuni - </string> <string name="Command_Inventory_Tooltip"> Visualizza e usa le tue cose </string> diff --git a/indra/newview/skins/default/xui/ja/floater_how_to.xml b/indra/newview/skins/default/xui/ja/floater_how_to.xml deleted file mode 100644 index 480fb4649c..0000000000 --- a/indra/newview/skins/default/xui/ja/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="floater_how_to" title="ウェルカムアイランドガイドブック" filename="floater_web_content.xml"/>
\ No newline at end of file diff --git a/indra/newview/skins/default/xui/ja/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/ja/floater_live_lsleditor.xml index 770d7b4a13..0a98e67748 100644 --- a/indra/newview/skins/default/xui/ja/floater_live_lsleditor.xml +++ b/indra/newview/skins/default/xui/ja/floater_live_lsleditor.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <floater name="script ed float" title="スクリプト:新しいスクリプト"> <floater.string name="not_allowed"> - このスクリプトは「複製不可」のため、表示・編集することができません。オブジェクト内のスクリプトの表示・編集には、全権限が必要です。 + このスクリプトは「コピー不可」または「修正不可」のため、表示・編集することができません。オブジェクト内のスクリプトを表示・編集するには、これらの権限が必要です。 </floater.string> <floater.string name="script_running"> 実行中 diff --git a/indra/newview/skins/default/xui/ja/notifications.xml b/indra/newview/skins/default/xui/ja/notifications.xml index 9ec7a0de98..07d6c41213 100644 --- a/indra/newview/skins/default/xui/ja/notifications.xml +++ b/indra/newview/skins/default/xui/ja/notifications.xml @@ -2162,7 +2162,6 @@ https://wiki.secondlife.com/wiki/Adding_Spelling_Dictionaries </form> </notification> <notification name="WelcomeChooseSex">まもなく、あなたのアバターが表示されます。矢印キーを使用して歩きます。 -ヘルプが必要なときや、[SECOND_LIFE]について知りたいときは、F1キーを押してください。 男性あるいは女性のアバターを選択してください。この設定は後で変更できます。 <tag> confirm diff --git a/indra/newview/skins/default/xui/ja/panel_script_ed.xml b/indra/newview/skins/default/xui/ja/panel_script_ed.xml index 0861b5caf1..2236659164 100644 --- a/indra/newview/skins/default/xui/ja/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/ja/panel_script_ed.xml @@ -4,7 +4,7 @@ 読み込んでいます… </panel.string> <panel.string name="can_not_view"> - このスクリプトは「コピー不可」のため、表示・編集することができません。オブジェクト内のスクリプトの表示・編集には、全権限が必要です。 + このスクリプトは「コピー不可」または「修正不可」のため、表示・編集することができません。オブジェクト内のスクリプトを表示・編集するには、これらの権限が必要です。 </panel.string> <panel.string name="public_objects_can_not_run"> 公共オブジェクトで、スクリプトを実行することはできません。 diff --git a/indra/newview/skins/default/xui/ja/strings.xml b/indra/newview/skins/default/xui/ja/strings.xml index 7d618bc337..a67e58a42a 100644 --- a/indra/newview/skins/default/xui/ja/strings.xml +++ b/indra/newview/skins/default/xui/ja/strings.xml @@ -7143,9 +7143,6 @@ www.secondlife.com から最新バージョンをダウンロードしてくだã <string name="Command_Grid_Status_Label"> グリッド状況 </string> - <string name="Command_HowTo_Label"> - ハウツー - </string> <string name="Command_Inventory_Label"> インベントリ </string> @@ -7233,9 +7230,6 @@ www.secondlife.com から最新バージョンをダウンロードしてくだã <string name="Command_Grid_Status_Tooltip"> 現在のグリッドステータスを表示します。 </string> - <string name="Command_HowTo_Tooltip"> - 一般的タスクの実行方法を表示します。 - </string> <string name="Command_Inventory_Tooltip"> インベントリの内容を表示したり使用したりします。 </string> diff --git a/indra/newview/skins/default/xui/pl/floater_how_to.xml b/indra/newview/skins/default/xui/pl/floater_how_to.xml deleted file mode 100644 index 2c412de30a..0000000000 --- a/indra/newview/skins/default/xui/pl/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes" ?> -<floater name="floater_how_to" title="SAMOUCZEK" /> diff --git a/indra/newview/skins/default/xui/pl/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/pl/floater_live_lsleditor.xml index 1b3e0d0e74..08642d8a63 100644 --- a/indra/newview/skins/default/xui/pl/floater_live_lsleditor.xml +++ b/indra/newview/skins/default/xui/pl/floater_live_lsleditor.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes" ?> <floater name="script ed float" title="SKRYPT: NOWY SKRYPT"> <floater.string name="not_allowed"> - Nie posiadasz praw do podejrzenia lub edycji kodu tego skryptu, ponieważ ma on ustawione ograniczone zezwolenia. Musisz posiadać pełne prawa by móc zobaczyć lub edytować kod skryptu wewnątrz obiektu. + Nie możesz zobaczyć ani edytować tego skryptu, ponieważ ustawiono dla niego "brak kopiowania" lub "brak modyfikacji". Aby zobaczyć lub edytować skrypt wewnątrz obiektu, potrzebujesz tych uprawnień. </floater.string> <floater.string name="script_running"> Włączony diff --git a/indra/newview/skins/default/xui/pl/notifications.xml b/indra/newview/skins/default/xui/pl/notifications.xml index ad9d2ecf1d..a8ea3c7c1a 100644 --- a/indra/newview/skins/default/xui/pl/notifications.xml +++ b/indra/newview/skins/default/xui/pl/notifications.xml @@ -1397,7 +1397,6 @@ Możesz sprawdzić swoje połączenie z Internetem i spróbować ponownie za kil Twoja postać pojawi się za moment. Używaj strzałek żeby się poruszać. -Naciśnij F1 w dowolnej chwili po pomoc albo żeby dowiedzieć się więcej o [SECOND_LIFE]. Wybierz awatara właściwej płci. Ten wybór będzie można później zmienić. <usetemplate name="okcancelbuttons" notext="Kobieta" yestext="Mężczyzna" /> </notification> diff --git a/indra/newview/skins/default/xui/pl/panel_script_ed.xml b/indra/newview/skins/default/xui/pl/panel_script_ed.xml index 828f1c571f..c7115e8f64 100644 --- a/indra/newview/skins/default/xui/pl/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/pl/panel_script_ed.xml @@ -4,7 +4,7 @@ Ładowanie... </panel.string> <panel.string name="can_not_view"> - Nie posiadasz praw do zobaczenia lub edycji kodu tego skryptu, ponieważ ustawione zostały na niego ograniczenia. Musisz posiadać pełne prawa by móc zobaczyć lub edytować kod skryptu w zawartości obiektu. + Nie możesz zobaczyć ani edytować tego skryptu, ponieważ ustawiono dla niego "brak kopiowania" lub "brak modyfikacji". Aby zobaczyć lub edytować skrypt wewnątrz obiektu, potrzebujesz tych uprawnień. </panel.string> <panel.string name="public_objects_can_not_run"> Publiczne obiekty nie mogą uruchamiać skryptów diff --git a/indra/newview/skins/default/xui/pl/strings.xml b/indra/newview/skins/default/xui/pl/strings.xml index 7a61878618..dc7ca53aaa 100644 --- a/indra/newview/skins/default/xui/pl/strings.xml +++ b/indra/newview/skins/default/xui/pl/strings.xml @@ -4992,9 +4992,6 @@ Spróbuj załączyć ścieżkę do edytora w cytowaniu. <string name="Command_Grid_Status_Label"> Status świata </string> - <string name="Command_HowTo_Label"> - Samouczek - </string> <string name="Command_Inventory_Label"> Szafa </string> @@ -5076,9 +5073,6 @@ Spróbuj załączyć ścieżkę do edytora w cytowaniu. <string name="Command_Grid_Status_Tooltip"> Pokaż obecny status świata </string> - <string name="Command_HowTo_Tooltip"> - Jak wykonywać zwyczajne rzeczy - </string> <string name="Command_Inventory_Tooltip"> Przeglądaj i używaj rzeczy, jakie należą do Ciebie </string> diff --git a/indra/newview/skins/default/xui/pt/floater_how_to.xml b/indra/newview/skins/default/xui/pt/floater_how_to.xml deleted file mode 100644 index 15c4946cb0..0000000000 --- a/indra/newview/skins/default/xui/pt/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="floater_how_to" title="COMO"/> diff --git a/indra/newview/skins/default/xui/pt/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/pt/floater_live_lsleditor.xml index 635bc2d34b..8a6f4c61db 100644 --- a/indra/newview/skins/default/xui/pt/floater_live_lsleditor.xml +++ b/indra/newview/skins/default/xui/pt/floater_live_lsleditor.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <floater name="script ed float" title="SCRIPT: NOVO SCRIPT"> <floater.string name="not_allowed"> - Você não pode ver ou editar o script, uma vez que está definido como "não" copiar. Você precisa de permissões para ver ou editar um script dentro de um objeto. + Você não pode ver ou editar este script, pois ele está definido como "não copiável" ou "não modificável". Você precisa dessas permissões para ver ou editar um script dentro de um objeto. </floater.string> <floater.string name="script_running"> Executando diff --git a/indra/newview/skins/default/xui/pt/notifications.xml b/indra/newview/skins/default/xui/pt/notifications.xml index a3220bca54..153109fc1b 100644 --- a/indra/newview/skins/default/xui/pt/notifications.xml +++ b/indra/newview/skins/default/xui/pt/notifications.xml @@ -1108,7 +1108,6 @@ Cheque sua conexão e tente em alguns minutos, clique na Ajuda para acessar o [S Seu personagem irá aparecer num momento. Use as teclas de seta para andar. -Pressione a tecla F1 para ajuda ou aprender mais sobre [SECOND_LIFE]. Por favor, escolha se o seu avatar é feminino ou masculino. Você pode mudar de idéia depois. <usetemplate name="okcancelbuttons" notext="Feminino" yestext="Masculino"/> </notification> diff --git a/indra/newview/skins/default/xui/pt/panel_script_ed.xml b/indra/newview/skins/default/xui/pt/panel_script_ed.xml index c546adcf8b..e8dcb794e1 100644 --- a/indra/newview/skins/default/xui/pt/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/pt/panel_script_ed.xml @@ -4,7 +4,7 @@ Carregando... </panel.string> <panel.string name="can_not_view"> - Você não pode ver ou editar o script, uma vez que está definido como "não" copiar ". Você precisa de permissões para ver ou editar um script dentro de um objeto. + Você não pode ver ou editar este script, pois ele está definido como "não copiável" ou "não modificável". Você precisa dessas permissões para ver ou editar um script dentro de um objeto. </panel.string> <panel.string name="public_objects_can_not_run"> Objetos públicos não podem executar scripts diff --git a/indra/newview/skins/default/xui/pt/strings.xml b/indra/newview/skins/default/xui/pt/strings.xml index 543fd45573..3d03a71e81 100644 --- a/indra/newview/skins/default/xui/pt/strings.xml +++ b/indra/newview/skins/default/xui/pt/strings.xml @@ -5406,9 +5406,6 @@ Tente colocar o caminho do editor entre aspas. <string name="Command_Grid_Status_Label"> Status da grade </string> - <string name="Command_HowTo_Label"> - Como - </string> <string name="Command_Inventory_Label"> Inventário </string> @@ -5505,9 +5502,6 @@ Tente colocar o caminho do editor entre aspas. <string name="Command_Grid_Status_Tooltip"> Mostrar status da grade atual </string> - <string name="Command_HowTo_Tooltip"> - Como executar tarefas comuns - </string> <string name="Command_Inventory_Tooltip"> Exibir e usar seus pertences </string> diff --git a/indra/newview/skins/default/xui/ru/floater_how_to.xml b/indra/newview/skins/default/xui/ru/floater_how_to.xml deleted file mode 100644 index 52525e5d33..0000000000 --- a/indra/newview/skins/default/xui/ru/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="floater_how_to" title="ПОМОЩЬ"/> diff --git a/indra/newview/skins/default/xui/ru/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/ru/floater_live_lsleditor.xml index 30a196b1ca..cfe30a5333 100644 --- a/indra/newview/skins/default/xui/ru/floater_live_lsleditor.xml +++ b/indra/newview/skins/default/xui/ru/floater_live_lsleditor.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <floater name="script ed float" title="СКРИПТ: СОЗДАТЬ СКРИПТ"> <floater.string name="not_allowed"> - Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрана категория «не копируемые». Для просмотра или изменения скрипта в объекте нужны полные права доступа. + Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрано «не копировать» или «не изменять». Для просмотра или изменения скрипта в объекте нужны эти права доступа. </floater.string> <floater.string name="script_running"> Выполняется diff --git a/indra/newview/skins/default/xui/ru/notifications.xml b/indra/newview/skins/default/xui/ru/notifications.xml index e75fd1fd82..96eb917e39 100644 --- a/indra/newview/skins/default/xui/ru/notifications.xml +++ b/indra/newview/skins/default/xui/ru/notifications.xml @@ -1459,7 +1459,6 @@ Ваш персонаж появится через мгновение. Для ходьбы нажимайте клавиши со стрелками. -В любой момент можно нажать клавишу F1 для получения справки или информации о [SECOND_LIFE]. Выберите мужской или женский аватар. Этот выбор затем можно будет изменить. <usetemplate name="okcancelbuttons" notext="Женщина" yestext="Мужчина"/> </notification> diff --git a/indra/newview/skins/default/xui/ru/panel_script_ed.xml b/indra/newview/skins/default/xui/ru/panel_script_ed.xml index 54a1eaab75..78c4e41be2 100644 --- a/indra/newview/skins/default/xui/ru/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/ru/panel_script_ed.xml @@ -4,7 +4,7 @@ Загрузка... </panel.string> <panel.string name="can_not_view"> - Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрана категория «не копируемые». Для просмотра или изменения скрипта в объекте нужны полные права доступа. + Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрано «не копировать» или «не изменять». Для просмотра или изменения скрипта в объекте нужны эти права доступа. </panel.string> <panel.string name="public_objects_can_not_run"> Общедоступные объекты не могут запускать скрипты diff --git a/indra/newview/skins/default/xui/ru/strings.xml b/indra/newview/skins/default/xui/ru/strings.xml index 8433f893b5..86973c6ae7 100644 --- a/indra/newview/skins/default/xui/ru/strings.xml +++ b/indra/newview/skins/default/xui/ru/strings.xml @@ -5544,9 +5544,6 @@ support@secondlife.com. <string name="Command_Grid_Status_Label"> Состояние сетки </string> - <string name="Command_HowTo_Label"> - Инструкции - </string> <string name="Command_Inventory_Label"> Инвентарь </string> @@ -5643,9 +5640,6 @@ support@secondlife.com. <string name="Command_Grid_Status_Tooltip"> Показать текущее состояние сетки </string> - <string name="Command_HowTo_Tooltip"> - Выполнение типичных задач - </string> <string name="Command_Inventory_Tooltip"> Просмотр и использование вашего имущества </string> diff --git a/indra/newview/skins/default/xui/tr/floater_how_to.xml b/indra/newview/skins/default/xui/tr/floater_how_to.xml deleted file mode 100644 index a42fe0b122..0000000000 --- a/indra/newview/skins/default/xui/tr/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="floater_how_to" title="NASIL YAPILIR"/> diff --git a/indra/newview/skins/default/xui/tr/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/tr/floater_live_lsleditor.xml index 760948edae..baea64aa68 100644 --- a/indra/newview/skins/default/xui/tr/floater_live_lsleditor.xml +++ b/indra/newview/skins/default/xui/tr/floater_live_lsleditor.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <floater name="script ed float" title="KOMUT DOSYASI: YENİ KOMUT DOSYASI"> <floater.string name="not_allowed"> - Bu komut dosyasını görüntüleyemez veya düzenleyemezsiniz, çünkü "kopyalanamaz" olarak ayarlanmıştır. Bir nesnenin içerisindeki bir komut dosyasını görüntülemek veya düzenlemek için tam izinlere ihtiyacınız var. + Bu komut dosyasını görüntüleyemez veya düzenleyemezsiniz, çünkü "kopyalanamaz" veya "değiştirilemez" olarak ayarlanmıştır. Bir nesnenin içerisindeki bir komut dosyasını görüntülemek veya düzenlemek için bu izinlere ihtiyacınız var. </floater.string> <floater.string name="script_running"> Çalışıyor diff --git a/indra/newview/skins/default/xui/tr/notifications.xml b/indra/newview/skins/default/xui/tr/notifications.xml index 17d2969d19..de429f3292 100644 --- a/indra/newview/skins/default/xui/tr/notifications.xml +++ b/indra/newview/skins/default/xui/tr/notifications.xml @@ -1460,7 +1460,6 @@ Yeni bir ana konum ayarlamak isteyebilirsiniz. Karakteriniz birazdan görünecek. Yürümek için ok tuşlarını kullanın. -Yardım almak ya da [SECOND_LIFE] hakkında daha fazla bilgi edinmek için istediğiniz zaman F1 tuşuna basın. Lütfen bir erkek ya da kadın avatar seçin. Fikrinizi daha sonra değiştirebilirsiniz. <usetemplate name="okcancelbuttons" notext="Kadın" yestext="Erkek"/> </notification> diff --git a/indra/newview/skins/default/xui/tr/panel_script_ed.xml b/indra/newview/skins/default/xui/tr/panel_script_ed.xml index 83040d06e4..e3c11a9409 100644 --- a/indra/newview/skins/default/xui/tr/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/tr/panel_script_ed.xml @@ -4,7 +4,7 @@ Yükleniyor... </panel.string> <panel.string name="can_not_view"> - Bu komut dosyasını görüntüleyemez veya düzenleyemezsiniz, çünkü "kopyalanamaz" olarak ayarlanmıştır. Bir nesnenin içerisindeki bir komut dosyasını görüntülemek veya düzenlemek için tam izinlere ihtiyacınız var. + Bu komut dosyasını görüntüleyemez veya düzenleyemezsiniz, çünkü "kopyalanamaz" veya "değiştirilemez" olarak ayarlanmıştır. Bir nesnenin içerisindeki bir komut dosyasını görüntülemek veya düzenlemek için bu izinlere ihtiyacınız var. </panel.string> <panel.string name="public_objects_can_not_run"> Kamuya Açık Nesneler komut dosyalarını çalıştıramaz diff --git a/indra/newview/skins/default/xui/tr/strings.xml b/indra/newview/skins/default/xui/tr/strings.xml index e68d000179..3eaa6a6492 100644 --- a/indra/newview/skins/default/xui/tr/strings.xml +++ b/indra/newview/skins/default/xui/tr/strings.xml @@ -5539,9 +5539,6 @@ Düzenleyici yolunu çift tırnakla çevrelemeyi deneyin. <string name="Command_Grid_Status_Label"> Ağ durumu </string> - <string name="Command_HowTo_Label"> - Nasıl yapılır - </string> <string name="Command_Inventory_Label"> Envanter </string> @@ -5638,9 +5635,6 @@ Düzenleyici yolunu çift tırnakla çevrelemeyi deneyin. <string name="Command_Grid_Status_Tooltip"> Ağın mevcut durumunu göster </string> - <string name="Command_HowTo_Tooltip"> - Genel görevleri nasıl yapacağınız - </string> <string name="Command_Inventory_Tooltip"> Eşyalarınızı görüntüleyin ve kullanın </string> diff --git a/indra/newview/skins/default/xui/zh/floater_how_to.xml b/indra/newview/skins/default/xui/zh/floater_how_to.xml deleted file mode 100644 index e033327165..0000000000 --- a/indra/newview/skins/default/xui/zh/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater name="floater_how_to" title="簡易教學"/> diff --git a/indra/newview/skins/default/xui/zh/floater_live_lsleditor.xml b/indra/newview/skins/default/xui/zh/floater_live_lsleditor.xml index e971b166f7..8b4eeaa16d 100644 --- a/indra/newview/skins/default/xui/zh/floater_live_lsleditor.xml +++ b/indra/newview/skins/default/xui/zh/floater_live_lsleditor.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <floater name="script ed float" title="腳本: 新腳本"> <floater.string name="not_allowed"> - 你無法察看或編輯這腳本,它被設為「禁止複製」。 你需要完整權限去察看或編輯物件內的腳本。 + 你無法察看或編輯這腳本,因為它被設為「禁止複製」或「禁止修改」。你需要這些權限才能察看或編輯物件內的腳本。 </floater.string> <floater.string name="script_running"> 執行中 diff --git a/indra/newview/skins/default/xui/zh/notifications.xml b/indra/newview/skins/default/xui/zh/notifications.xml index 4d0f1cb85b..fd0b613dd6 100644 --- a/indra/newview/skins/default/xui/zh/notifications.xml +++ b/indra/newview/skins/default/xui/zh/notifications.xml @@ -1446,7 +1446,6 @@ 你的人物很快將會出現。 用方向鍵行走。 -任何時候你都可按 F1 鍵察看幫助,進一步瞭解 [SECOND_LIFE]。 請選擇男性或女性化身。 以後你仍可改變這個選擇。 <usetemplate name="okcancelbuttons" notext="女性" yestext="男性"/> </notification> diff --git a/indra/newview/skins/default/xui/zh/panel_script_ed.xml b/indra/newview/skins/default/xui/zh/panel_script_ed.xml index c64982b397..830165805f 100644 --- a/indra/newview/skins/default/xui/zh/panel_script_ed.xml +++ b/indra/newview/skins/default/xui/zh/panel_script_ed.xml @@ -4,7 +4,7 @@ 載入中... </panel.string> <panel.string name="can_not_view"> - 你無法察看或編輯這腳本,它被設為「禁止複製」。 你需要完整權限去察看或編輯物件內的腳本。 + 你無法察看或編輯這腳本,因為它被設為「禁止複製」或「禁止修改」。你需要這些權限才能察看或編輯物件內的腳本。 </panel.string> <panel.string name="public_objects_can_not_run"> 公開物件不能執行腳本 diff --git a/indra/newview/skins/default/xui/zh/strings.xml b/indra/newview/skins/default/xui/zh/strings.xml index cf6fa1d85f..1a094ea258 100644 --- a/indra/newview/skins/default/xui/zh/strings.xml +++ b/indra/newview/skins/default/xui/zh/strings.xml @@ -5531,9 +5531,6 @@ http://secondlife.com/support 求助解決問題。 <string name="Command_Grid_Status_Label"> 網格狀態 </string> - <string name="Command_HowTo_Label"> - 簡易教學 - </string> <string name="Command_Inventory_Label"> 收納區 </string> @@ -5630,9 +5627,6 @@ http://secondlife.com/support 求助解決問題。 <string name="Command_Grid_Status_Tooltip"> 顯示當前網格狀態 </string> - <string name="Command_HowTo_Tooltip"> - 如何完成常用的動作 - </string> <string name="Command_Inventory_Tooltip"> 察看並使用你擁有的物件 </string> |
