From b0f4a147aa27ea256bd042043f0211b827f7b8df Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 13 Apr 2026 12:40:05 +0300 Subject: [PATCH 001/112] #2888 show underwater nametags when camera is above the water --- indra/newview/app_settings/settings.xml | 11 +++++++++++ indra/newview/llhudnametag.cpp | 19 +++++++++++++++++-- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index a4a93238926..045308c7434 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -9019,6 +9019,17 @@ Value 0 + NametagOverWater + + Comment + Render name tag over the transparent water while camera is above the water + Persist + 1 + Type + Boolean + Value + 1 + RenderInitError Comment diff --git a/indra/newview/llhudnametag.cpp b/indra/newview/llhudnametag.cpp index 4327d281e58..5d20e722de6 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 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(lltrunc(-mHeight / 2 + mOffsetY)), static_cast(lltrunc(mWidth)), static_cast(lltrunc(mHeight))); mRoundedRectImgp->draw3D(render_position, x_pixel_vec, y_pixel_vec, screen_rect, bg_color); From 4926a066dd5c502d9d2c155a323936ea6e8cb8e0 Mon Sep 17 00:00:00 2001 From: Gai Clary Date: Mon, 13 Apr 2026 23:37:08 +0200 Subject: [PATCH 002/112] #5639 Initialize LLJointData support category from avatar skeleton (#5632) The glTF importer branches on LLJointData::mSupport when it decides whether a collision volume should inherit the current rest matrix or the support-rest matrix. That value was never copied from the avatar skeleton XML into LLJointData, so the importer was effectively making that decision on uninitialized state. --- indra/llappearance/llavatarappearance.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index dab18c240d5..d2862a52153 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -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(); From 3ca4bf1ac0a3b26e961579a86869181104c11b5f Mon Sep 17 00:00:00 2001 From: Gai Clary Date: Mon, 13 Apr 2026 23:45:38 +0200 Subject: [PATCH 003/112] #5639 Fix glTF collision volume bind reconstruction The glTF importer rebuilt collision volume override-rest matrices from the imported translation override and viewer-side scale only. That dropped the collision volume rotation defined in avatar_skeleton.xml and produced incorrect inverse bind matrices for rotated collision volumes, especially the torso volumes that use non-uniform scale. The result was a visible mismatch between rigged mesh imported from .glb and the equivalent .dae upload. --- indra/llappearance/llavatarappearance.cpp | 8 ++++---- indra/newview/gltf/llgltfloader.cpp | 21 ++++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index d2862a52153..c8fd2cba70c 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; diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 5a94a2c6c68..4e52c82e866 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -1551,6 +1551,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(), translation_override, glm::vec3(0, 0, 0), glm::vec4(0, 0, 0, 1)); @@ -1566,13 +1572,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; } } From 933f1e844e4b21f56b95ae7f37cce410ea5f1743 Mon Sep 17 00:00:00 2001 From: Cosmic Linden Date: Mon, 13 Apr 2026 17:12:55 -0700 Subject: [PATCH 004/112] secondlife/viewer#5634: Apply version string from janus server if available. Also display the type of connection the version is applicable to. --- indra/newview/llvoicewebrtc.cpp | 69 +++++++++++++++++++++++++++++++++ indra/newview/llvoicewebrtc.h | 5 +++ 2 files changed, 74 insertions(+) diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index 3a700423b30..333046adce1 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -343,6 +343,39 @@ const LLVoiceVersionInfo& LLWebRTCVoiceClient::getVersion() return mVoiceVersion; } +// -------------------------------------------------- + +void LLWebRTCVoiceClient::updateVersion() +{ + sessionStatePtr_t session = mNextSession.get() ? mNextSession : mSession; + + if (session) + { + // A WebRTC session can be connected to multiple servers at once. To more easily disambiguate which server version is being printed, show the connection type. In most cases, this shouldn't matter and the Janus version should be the same for all connections. Janus versions are also logged for each connection. + mVoiceVersion.serverVersion = session->getVersion(); + if (session->isCallbackPossible()) + { + mVoiceVersion.mBuildVersion = "ad-hoc"; + } + else if (session->isEstate()) + { + mVoiceVersion.mBuildVersion = "estate"; + } + else if (session->isSpatial()) + { + mVoiceVersion.mBuildVersion = "parcel"; + } + else + { + mVoiceVersion.mBuildVersion = mVoiceVersion.serverVersion; + } + } + else + { + mVoiceVersion.serverVersion = mVoiceVersion.mBuildVersion = ""; + } +} + //--------------------------------------------------- void LLWebRTCVoiceClient::updateSettings() @@ -2054,6 +2087,22 @@ void LLWebRTCVoiceClient::sessionState::revive() mShuttingDown = false; } +const std::string LLWebRTCVoiceClient::sessionState::getVersion() const +{ + // Prefer the version of a primary connection which has already received a version string over the data channel. If that does not make sense, fall back to any non-empty version string we can find. + bool primary = true; + do + { + for (auto& connection : mWebRTCConnections) { + if (connection->isPrimary() == primary && connection->getVersion().length()) { + return connection->getVersion(); + } + } + primary = !primary; + } while (!primary); + return ""; +} + //========================================================================= // the following are methods to support the coroutine implementation of the // voice connection and processing. They should only be called in the context @@ -2250,6 +2299,11 @@ void LLWebRTCVoiceClient::deleteSession(const sessionStatePtr_t &session) { mNextSession.reset(); } + + if (!sShuttingDown) + { + updateVersion(); + } } @@ -2625,6 +2679,10 @@ void LLVoiceWebRTCConnection::sendData(const std::string &data) } } +const std::string& LLVoiceWebRTCConnection::getVersion() { + return mServerVersion; +} + // Tell the simulator that we're shutting down a voice connection. // The simulator will pass this on to the Secondlife WebRTC server. void LLVoiceWebRTCConnection::breakVoiceConnectionCoro(connectionPtr_t connection) @@ -3048,6 +3106,7 @@ bool LLVoiceWebRTCConnection::connectionStateMachine() // An object where each key is an agent id. (in the future, we may allow // integer indices into an agentid list, populated on join commands. For size. // Each key will point to a json object with keys identifying what's updated. +// 'V' - voice server version (string) // 'p' - audio source power (level/volume) (int8 as int) // 'j' - object of join data (currently only a boolean 'p' marking a primary participant) // 'l' - boolean, always true if exists. @@ -3108,6 +3167,16 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b boost::json::object participant_obj = participant_elem.value().as_object(); + if (participant_obj.contains("V") && participant_obj["V"].is_string() && agent_id == gAgentID) + { + // sendJoin was called on the connection. The voice server has responded with the new version string. Set it here. + mServerVersion = participant_obj["V"].as_string().c_str(); + LLWebRTCVoiceClient::getInstance()->updateVersion(); + LL_DEBUGS("Voice") << "Received version string \"" << participant_obj["V"].as_string().c_str() + << "\" for connection: primary=" << mPrimary << ", spatial=" << isSpatial() + << ", region=" << mRegionID << ", mChannelID=" << mChannelID << LL_ENDL; + } + LLWebRTCVoiceClient::participantStatePtr_t participant = LLWebRTCVoiceClient::getInstance()->findParticipantByID(mChannelID, agent_id); bool joined = false; diff --git a/indra/newview/llvoicewebrtc.h b/indra/newview/llvoicewebrtc.h index 2ce575852ab..8efbd1778f4 100644 --- a/indra/newview/llvoicewebrtc.h +++ b/indra/newview/llvoicewebrtc.h @@ -80,6 +80,7 @@ class LLWebRTCVoiceClient : public LLSingleton, static bool isShuttingDown() { return sShuttingDown; } const LLVoiceVersionInfo& getVersion() override; + void updateVersion(); void updateSettings() override; // call after loading settings and whenever they change @@ -285,6 +286,7 @@ class LLWebRTCVoiceClient : public LLSingleton, void shutdownAllConnections(); void revive(); + const std::string getVersion() const; static void processSessionStates(); @@ -609,6 +611,7 @@ class LLVoiceWebRTCConnection : void sendJoin(); void sendData(const std::string &data); + const std::string& getVersion(); void processIceUpdates(); @@ -623,6 +626,7 @@ class LLVoiceWebRTCConnection : bool connectionStateMachine(); virtual bool isSpatial() { return false; } + bool isPrimary() const { return mPrimary; } LLUUID getRegionID() { return mRegionID; } @@ -694,6 +698,7 @@ class LLVoiceWebRTCConnection : bool mPrimary; LLUUID mViewerSession; std::string mChannelID; + std::string mServerVersion; std::string mChannelSDP; std::string mRemoteChannelSDP; From ce24df418d0c22f1a2f5d779e4495be5d0b83dd3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 02:54:03 +0000 Subject: [PATCH 005/112] Bump BugSplat-Git/symbol-upload Bumps [BugSplat-Git/symbol-upload](https://github.com/bugsplat-git/symbol-upload) from 095d163ae9ceb006d286a731dcd35cf6a1b458c8 to 2a0d2b8cf9c54c494144048f25da863d93a02ccd. - [Release notes](https://github.com/bugsplat-git/symbol-upload/releases) - [Commits](https://github.com/bugsplat-git/symbol-upload/compare/095d163ae9ceb006d286a731dcd35cf6a1b458c8...2a0d2b8cf9c54c494144048f25da863d93a02ccd) --- updated-dependencies: - dependency-name: BugSplat-Git/symbol-upload dependency-version: 2a0d2b8cf9c54c494144048f25da863d93a02ccd dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 866af3dbac2..d98fd4b531b 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@2a0d2b8cf9c54c494144048f25da863d93a02ccd 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@2a0d2b8cf9c54c494144048f25da863d93a02ccd with: clientId: "${{ env.SYMBOL_UPLOAD_CLIENT_ID }}" clientSecret: "${{ env.SYMBOL_UPLOAD_CLIENT_SECRET }}" From b0d27d27a8aa6abb400c3508b13b9a16a4172cc2 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Tue, 14 Apr 2026 16:13:44 +0300 Subject: [PATCH 006/112] #2410 Update notification text to include Combat 2.0 options --- indra/newview/skins/default/xui/en/notifications.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index fcdea665007..27846c8d7cb 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -7678,7 +7678,7 @@ Message from [NAME]: type="notify"> 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. Date: Tue, 14 Apr 2026 23:56:45 -0300 Subject: [PATCH 007/112] Update viewer copyright year in About licenses --- autobuild.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autobuild.xml b/autobuild.xml index 9945672bd2c..6abb3cf48d6 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -3416,7 +3416,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors license_file docs/LICENSE-source.txt copyright - Copyright (c) 2020, Linden Research, Inc. + Copyright (c) 2026, Linden Research, Inc. version_file newview/viewer_version.txt name From 8a0ada7c9869aa9a5122e8bafd65deb2ba90c968 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Thu, 16 Apr 2026 19:38:30 +0300 Subject: [PATCH 008/112] #5647 fix for texture Mapping fields not being updated when selecting BP face after PBR face --- indra/newview/llpanelface.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp index 128ba42efd4..12e1ebaad63 100644 --- a/indra/newview/llpanelface.cpp +++ b/indra/newview/llpanelface.cpp @@ -1073,6 +1073,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 +1680,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 From 9272e04afd47c6dff7835a617403df0b7555728f Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 16 Apr 2026 20:35:05 +0300 Subject: [PATCH 009/112] #5489 Follow up for appearance fixes --- indra/newview/llappearancemgr.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 8aa28e1b091..4f549314af5 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 From 3cb70cc3d70245c01764fadb7b0eb359d235a27b Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Fri, 17 Apr 2026 21:18:37 +0300 Subject: [PATCH 010/112] #5664 remove non-ASCII7 characters from comments --- indra/llimagej2coj/llimagej2coj.cpp | 8 ++++---- indra/newview/llvelopack.cpp | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/indra/llimagej2coj/llimagej2coj.cpp b/indra/llimagej2coj/llimagej2coj.cpp index 7cfadb889dd..8d544b360bd 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/newview/llvelopack.cpp b/indra/newview/llvelopack.cpp index 28e989c4bae..8975f7d3695 100644 --- a/indra/newview/llvelopack.cpp +++ b/indra/newview/llvelopack.cpp @@ -238,7 +238,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; @@ -786,7 +786,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 = {}; @@ -954,7 +954,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); } } @@ -1043,12 +1043,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); @@ -1074,7 +1074,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) @@ -1085,12 +1085,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(); From f4be102c49c9e812abbff454f08bb679ac951266 Mon Sep 17 00:00:00 2001 From: Hecklezz Date: Fri, 17 Apr 2026 21:10:30 +1000 Subject: [PATCH 011/112] Fix experiences search and new classifieds content rating labels showing incorrectly --- indra/llui/llcombobox.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/llui/llcombobox.cpp b/indra/llui/llcombobox.cpp index 01463b49629..db6c5c291fe 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); From 14ca82d2e72ec2214d9ca2ee6effc919c6e27a54 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Sun, 19 Apr 2026 03:28:46 +0300 Subject: [PATCH 012/112] Revert "#1625 Blinding white screen when changing environments" This reverts commit 53af39b68e2dbc2f89096bbd987f1d0b8a12ace5. --- indra/llinventory/llsettingssky.cpp | 31 +++-------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/indra/llinventory/llsettingssky.cpp b/indra/llinventory/llsettingssky.cpp index 6f7510fef27..4957cf3c02d 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(); From 5e38c19d87e032c20295915445d18743c4ccb611 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Mon, 20 Apr 2026 22:58:43 +0300 Subject: [PATCH 013/112] Update velopack with a public build --- autobuild.xml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index 6abb3cf48d6..c7d8bd5e9ba 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2922,14 +2922,12 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive - creds - github hash - 91abbc360640b5b2e0a4c001a36ad411a9a42602 + df2260187110aa51c20101183e18a55f86e71090 hash_algorithm sha1 url - https://api.github.com/repos/secondlife-3p/3p-velopack/releases/assets/380583560 + https://github.com/secondlife-3p/3p-velopack/releases/download/v0.0.1535-r2/velopack-40232ef.24685318450-windows64-24685318450.tar.zst name windows64 @@ -2938,14 +2936,12 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive - creds - github hash - 05563a79bdeb83d66a72ac1e97587dc2a8f64511 + ead94386d7b9a143824d7ccedc86433127028a91 hash_algorithm sha1 url - https://api.github.com/repos/secondlife-3p/3p-velopack/releases/assets/380583554 + https://github.com/secondlife-3p/3p-velopack/releases/download/v0.0.1535-r2/velopack-40232ef.24685318450-darwin64-24685318450.tar.zst name darwin64 @@ -2958,7 +2954,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors copyright Velopack Ltd. version - 40232ef.23500976684 + 40232ef.24685318450 name velopack description From e03869c4482bdfab1b8b5a3170f96bcd8c4e2a3a Mon Sep 17 00:00:00 2001 From: Maycon Bekkers Date: Tue, 14 Apr 2026 17:13:36 -0300 Subject: [PATCH 014/112] Remove Guidebook entry points --- indra/newview/CMakeLists.txt | 3 - indra/newview/app_settings/commands.xml | 10 -- indra/newview/app_settings/settings.xml | 22 ----- indra/newview/app_settings/toolbars.xml | 1 - indra/newview/llfloaterhowto.cpp | 92 ------------------ indra/newview/llfloaterhowto.h | 58 ----------- indra/newview/llurlfloaterdispatchhandler.cpp | 57 +---------- indra/newview/llviewerfloaterreg.cpp | 2 - indra/newview/llviewermenu.cpp | 13 --- .../skins/default/textures/textures.xml | 1 - .../default/textures/toolbar_icons/howto.png | Bin 1306 -> 0 bytes .../skins/default/xui/de/floater_how_to.xml | 2 - .../skins/default/xui/de/notifications.xml | 1 - .../newview/skins/default/xui/de/strings.xml | 6 -- .../skins/default/xui/en/floater_how_to.xml | 16 --- .../skins/default/xui/en/menu_login.xml | 9 -- .../skins/default/xui/en/menu_viewer.xml | 9 -- .../skins/default/xui/en/notifications.xml | 1 - .../newview/skins/default/xui/en/strings.xml | 2 - .../skins/default/xui/es/floater_how_to.xml | 2 - .../skins/default/xui/es/notifications.xml | 1 - .../newview/skins/default/xui/es/strings.xml | 6 -- .../skins/default/xui/fr/floater_how_to.xml | 2 - .../skins/default/xui/fr/notifications.xml | 1 - .../newview/skins/default/xui/fr/strings.xml | 6 -- .../skins/default/xui/it/floater_how_to.xml | 2 - .../skins/default/xui/it/notifications.xml | 1 - .../newview/skins/default/xui/it/strings.xml | 6 -- .../skins/default/xui/ja/floater_how_to.xml | 2 - .../skins/default/xui/ja/notifications.xml | 1 - .../newview/skins/default/xui/ja/strings.xml | 6 -- .../skins/default/xui/pl/floater_how_to.xml | 2 - .../skins/default/xui/pl/notifications.xml | 1 - .../newview/skins/default/xui/pl/strings.xml | 6 -- .../skins/default/xui/pt/floater_how_to.xml | 2 - .../skins/default/xui/pt/notifications.xml | 1 - .../newview/skins/default/xui/pt/strings.xml | 6 -- .../skins/default/xui/ru/floater_how_to.xml | 2 - .../skins/default/xui/ru/notifications.xml | 1 - .../newview/skins/default/xui/ru/strings.xml | 6 -- .../skins/default/xui/tr/floater_how_to.xml | 2 - .../skins/default/xui/tr/notifications.xml | 1 - .../newview/skins/default/xui/tr/strings.xml | 6 -- .../skins/default/xui/zh/floater_how_to.xml | 2 - .../skins/default/xui/zh/notifications.xml | 1 - .../newview/skins/default/xui/zh/strings.xml | 6 -- 46 files changed, 1 insertion(+), 385 deletions(-) delete mode 100644 indra/newview/llfloaterhowto.cpp delete mode 100644 indra/newview/llfloaterhowto.h delete mode 100644 indra/newview/skins/default/textures/toolbar_icons/howto.png delete mode 100644 indra/newview/skins/default/xui/de/floater_how_to.xml delete mode 100644 indra/newview/skins/default/xui/en/floater_how_to.xml delete mode 100644 indra/newview/skins/default/xui/es/floater_how_to.xml delete mode 100644 indra/newview/skins/default/xui/fr/floater_how_to.xml delete mode 100644 indra/newview/skins/default/xui/it/floater_how_to.xml delete mode 100644 indra/newview/skins/default/xui/ja/floater_how_to.xml delete mode 100644 indra/newview/skins/default/xui/pl/floater_how_to.xml delete mode 100644 indra/newview/skins/default/xui/pt/floater_how_to.xml delete mode 100644 indra/newview/skins/default/xui/ru/floater_how_to.xml delete mode 100644 indra/newview/skins/default/xui/tr/floater_how_to.xml delete mode 100644 indra/newview/skins/default/xui/zh/floater_how_to.xml diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 1d1553355ef..17d344a3308 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 @@ -2496,4 +2494,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 7bcfecf9faa..952818144d7 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" /> - Value https://viewer-help.secondlife.com/[LANGUAGE]/[CHANNEL]/[VERSION]/[TOPIC][DEBUG_MODE] - HowToHelpURL - - Comment - URL for How To help content - Persist - 1 - Type - String - Value - https://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/howto/index.html - HomeSidePanelURL Comment @@ -3478,17 +3467,6 @@ Value 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] - GuidebookURL - - Comment - URL for Guidebook content - Persist - 1 - Type - String - Value - http://guidebooks.secondlife.io/welcome/index.html - HighResSnapshot Comment diff --git a/indra/newview/app_settings/toolbars.xml b/indra/newview/app_settings/toolbars.xml index a1c9d6d9ee5..f22c25f3a23 100644 --- a/indra/newview/app_settings/toolbars.xml +++ b/indra/newview/app_settings/toolbars.xml @@ -10,7 +10,6 @@ - diff --git a/indra/newview/llfloaterhowto.cpp b/indra/newview/llfloaterhowto.cpp deleted file mode 100644 index 6a9f113d533..00000000000 --- 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("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("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 9d7793817a6..00000000000 --- 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/llurlfloaterdispatchhandler.cpp b/indra/newview/llurlfloaterdispatchhandler.cpp index 9bee4870be0..cf0657ea2f6 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/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 82fc4c6d87f..eef31c70487 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); LLFloaterReg::add("legacy_search", "floater_directory.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("profile", "floater_profile.xml",(LLFloaterBuildFunc)&LLFloaterReg::build); - LLFloaterReg::add("guidebook", "floater_how_to.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("slapp_test", "floater_test_slapp.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterUIPreviewUtil::registerFloater(); diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index dbcf4fbbf41..8d6b8ec7139 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -8504,15 +8504,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) @@ -9964,10 +9955,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/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index ff5737ab49a..faf2bd9434d 100644 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -141,7 +141,6 @@ with the same filename but different name - diff --git a/indra/newview/skins/default/textures/toolbar_icons/howto.png b/indra/newview/skins/default/textures/toolbar_icons/howto.png deleted file mode 100644 index 8594d7111333ede852f8c3ea8dc533c205533b5d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1306 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh+1|-AI^@Rf|$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWuD@%u1Od5hW46K32*3xq68pHF_1f1wh>l3^w)^1&PVosU-?Y zsp*+{wo31J?^jaDOtDo8H}y5}EpSfF$n>ZxN)4{^3rViZPPR-@vbR&PsjvbXkegbP zs8ErclUHn2VXFi-*9yo63F|80+w{G(j&jGsVi$*~QY((ZbTz(!|lw(AB`n(!$xoz{Szb!qvsq z!q^a|*Cju>G&eP`1g19yq1O?oUQklVEdbi=l3J8mmYU*Ll%J~r_OewbZnv1@G!Lpb z1-Dx)aO%|uIz}H9u}BdO69T3l5EGtkfgE_kPt60S_99@i7TN#sIRgV@pQnpsNX4zB zKmY&RGn+CfpHOgfa|=_jX7PQ;FR(e`;^t5C0t+M#oj7rT!6@VYzFH-Ro&Pg_7>jNE zuN}VbNlO&hm9q*g#XN0}_5c6H{(p3o+jaqyz%_=hn}2?Oe$F*1agKfczdy4kOgJ!c z?%dd^vuB^~OT5r8Z-1}phr%A9mao$0c`-aDJ;EC5>{}k_Ur}J2;OH^!bHkOzEeB_t z=f`<@dpF@zpQU{4#5g{7smBUi($BF$$F!oNPghjEr`f2AsJ(jiD(3q7`1#Fe6$Cn%9xYumEmNVHfr*DfN643vdAGMc PsI>BQ^>bP0l+XkKM$*sz 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 caea221f83f..00000000000 --- a/indra/newview/skins/default/xui/de/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/indra/newview/skins/default/xui/de/notifications.xml b/indra/newview/skins/default/xui/de/notifications.xml index 6ad71e0ad16..40b34d99baa 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. diff --git a/indra/newview/skins/default/xui/de/strings.xml b/indra/newview/skins/default/xui/de/strings.xml index d7b2ee57a8e..bd26672ccfe 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 Grid-Status - - Infos - Inventar @@ -5636,9 +5633,6 @@ Setzen Sie den Editorpfad in Anführungszeichen Aktuellen Grid-Status anzeigen - - Wie führe ich gängige Aufgaben aus? - Ihr Eigentum anzeigen und benutzen 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 5b00d23faaf..00000000000 --- a/indra/newview/skins/default/xui/en/floater_how_to.xml +++ /dev/null @@ -1,16 +0,0 @@ - - diff --git a/indra/newview/skins/default/xui/en/menu_login.xml b/indra/newview/skins/default/xui/en/menu_login.xml index 5fff9b7bc0f..de967d25e35 100644 --- a/indra/newview/skins/default/xui/en/menu_login.xml +++ b/indra/newview/skins/default/xui/en/menu_login.xml @@ -56,15 +56,6 @@ label="Help" tear_off="true" name="Help"> - - - - diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index ffe4bcebd53..f7541f52ad0 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -1804,14 +1804,6 @@ function="World.EnvPreset" label="Help" name="Help" tear_off="true"> - - - - diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 27846c8d7cb..221bac329d9 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. 360 snapshot My Environments Gestures Grid status - Guidebook Inventory Map Marketplace @@ -4235,7 +4234,6 @@ name="Command_360_Capture_Tooltip">Capture a 360 equirectangular image My Environments Gestures for your avatar Show current Grid status - How to do common tasks View and use your belongings Map of the world Go shopping 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 4a57dc36437..00000000000 --- a/indra/newview/skins/default/xui/es/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/indra/newview/skins/default/xui/es/notifications.xml b/indra/newview/skins/default/xui/es/notifications.xml index 739391b9653..5dee9eb3a86 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. diff --git a/indra/newview/skins/default/xui/es/strings.xml b/indra/newview/skins/default/xui/es/strings.xml index 86e454b83ec..1bfab01108e 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 Estado del Grid - - Cómo - Inventario @@ -5538,9 +5535,6 @@ Inténtalo incluyendo la ruta de acceso al editor entre comillas Mostrar el estado actual del Grid - - Cómo hacer las tareas habituales - Ver y usar tus pertenencias 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 a414212ba06..00000000000 --- a/indra/newview/skins/default/xui/fr/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/indra/newview/skins/default/xui/fr/notifications.xml b/indra/newview/skins/default/xui/fr/notifications.xml index 587c88faad2..9c79f288c19 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. diff --git a/indra/newview/skins/default/xui/fr/strings.xml b/indra/newview/skins/default/xui/fr/strings.xml index 4fb5167d67b..4a7a337d4c2 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 État de la grille - - Aide rapide - Inventaire @@ -5637,9 +5634,6 @@ Essayez avec le chemin d'accès à l'éditeur entre guillemets doubles Afficher l’état actuel de la grille - - Comment effectuer les opérations courantes - Afficher et utiliser vos possessions 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 8f0e2105712..00000000000 --- a/indra/newview/skins/default/xui/it/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/indra/newview/skins/default/xui/it/notifications.xml b/indra/newview/skins/default/xui/it/notifications.xml index f79cc1515b3..284f947b18c 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. diff --git a/indra/newview/skins/default/xui/it/strings.xml b/indra/newview/skins/default/xui/it/strings.xml index 2ddb7d77d14..b4d6b2a3151 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. Stato della griglia - - Istruzioni - Inventario @@ -5552,9 +5549,6 @@ Prova a racchiudere il percorso dell'editor in doppie virgolette. Mostra stato griglia corrente - - Come eseguire le attività più comuni - Visualizza e usa le tue cose 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 480fb4649cf..00000000000 --- a/indra/newview/skins/default/xui/ja/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/indra/newview/skins/default/xui/ja/notifications.xml b/indra/newview/skins/default/xui/ja/notifications.xml index 9ec7a0de986..07d6c412138 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 まもなく、あなたのアバターが表示されます。矢印キーを使用して歩きます。 -ヘルプが必要なときや、[SECOND_LIFE]について知りたいときは、F1キーを押してください。 男性あるいは女性のアバターを選択してください。この設定は後で変更できます。 confirm diff --git a/indra/newview/skins/default/xui/ja/strings.xml b/indra/newview/skins/default/xui/ja/strings.xml index 7d618bc3377..a67e58a42ae 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 から最新バージョンをダウンロードしてくだ グリッド状況 - - ハウツー - インベントリ @@ -7233,9 +7230,6 @@ www.secondlife.com から最新バージョンをダウンロードしてくだ 現在のグリッドステータスを表示します。 - - 一般的タスクの実行方法を表示します。 - インベントリの内容を表示したり使用したりします。 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 2c412de30ab..00000000000 --- a/indra/newview/skins/default/xui/pl/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/indra/newview/skins/default/xui/pl/notifications.xml b/indra/newview/skins/default/xui/pl/notifications.xml index ad9d2ecf1d4..a8ea3c7c1a8 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ć. diff --git a/indra/newview/skins/default/xui/pl/strings.xml b/indra/newview/skins/default/xui/pl/strings.xml index 7a618786185..dc7ca53aaa6 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. Status świata - - Samouczek - Szafa @@ -5076,9 +5073,6 @@ Spróbuj załączyć ścieżkę do edytora w cytowaniu. Pokaż obecny status świata - - Jak wykonywać zwyczajne rzeczy - Przeglądaj i używaj rzeczy, jakie należą do Ciebie 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 15c4946cb03..00000000000 --- a/indra/newview/skins/default/xui/pt/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/indra/newview/skins/default/xui/pt/notifications.xml b/indra/newview/skins/default/xui/pt/notifications.xml index a3220bca54a..153109fc1bb 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. diff --git a/indra/newview/skins/default/xui/pt/strings.xml b/indra/newview/skins/default/xui/pt/strings.xml index 543fd45573f..3d03a71e814 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. Status da grade - - Como - Inventário @@ -5505,9 +5502,6 @@ Tente colocar o caminho do editor entre aspas. Mostrar status da grade atual - - Como executar tarefas comuns - Exibir e usar seus pertences 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 52525e5d333..00000000000 --- a/indra/newview/skins/default/xui/ru/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/indra/newview/skins/default/xui/ru/notifications.xml b/indra/newview/skins/default/xui/ru/notifications.xml index e75fd1fd822..96eb917e390 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]. Выберите мужской или женский аватар. Этот выбор затем можно будет изменить. diff --git a/indra/newview/skins/default/xui/ru/strings.xml b/indra/newview/skins/default/xui/ru/strings.xml index 8433f893b5a..86973c6ae73 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. Состояние сетки - - Инструкции - Инвентарь @@ -5643,9 +5640,6 @@ support@secondlife.com. Показать текущее состояние сетки - - Выполнение типичных задач - Просмотр и использование вашего имущества 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 a42fe0b1221..00000000000 --- a/indra/newview/skins/default/xui/tr/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/indra/newview/skins/default/xui/tr/notifications.xml b/indra/newview/skins/default/xui/tr/notifications.xml index 17d2969d196..de429f3292b 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. diff --git a/indra/newview/skins/default/xui/tr/strings.xml b/indra/newview/skins/default/xui/tr/strings.xml index e68d0001797..3eaa6a64925 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. Ağ durumu - - Nasıl yapılır - Envanter @@ -5638,9 +5635,6 @@ Düzenleyici yolunu çift tırnakla çevrelemeyi deneyin. Ağın mevcut durumunu göster - - Genel görevleri nasıl yapacağınız - Eşyalarınızı görüntüleyin ve kullanın 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 e033327165d..00000000000 --- a/indra/newview/skins/default/xui/zh/floater_how_to.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/indra/newview/skins/default/xui/zh/notifications.xml b/indra/newview/skins/default/xui/zh/notifications.xml index 4d0f1cb85b6..fd0b613dd62 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]。 請選擇男性或女性化身。 以後你仍可改變這個選擇。 diff --git a/indra/newview/skins/default/xui/zh/strings.xml b/indra/newview/skins/default/xui/zh/strings.xml index cf6fa1d85f0..1a094ea258f 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 求助解決問題。 網格狀態 - - 簡易教學 - 收納區 @@ -5630,9 +5627,6 @@ http://secondlife.com/support 求助解決問題。 顯示當前網格狀態 - - 如何完成常用的動作 - 察看並使用你擁有的物件 From f0b083c5e5223ec2e6c4f6a6761ce9480239d1de Mon Sep 17 00:00:00 2001 From: Darl Date: Mon, 20 Apr 2026 18:22:52 -0500 Subject: [PATCH 015/112] LLMuteList::isLoadedFromServer should also include MLS_SERVER_CACHE This fixes a bug where the user logs in with good cache, server issues use-cached response, and then the viewer would send another mutelist request on region change. Signed-off-by: Darl --- indra/newview/llmutelist.cpp | 2 +- indra/newview/llmutelist.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index b72301c5666..0015bd09fb3 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -879,7 +879,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 2781e9b1779..aff23c72d13 100644 --- a/indra/newview/llmutelist.h +++ b/indra/newview/llmutelist.h @@ -129,9 +129,9 @@ class LLMuteList : public LLSingleton // 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. From 4d400489662fcab5d255629d14a7a64385a89893 Mon Sep 17 00:00:00 2001 From: Darl Date: Mon, 20 Apr 2026 18:40:09 -0500 Subject: [PATCH 016/112] Cleanup for PR #5269 Constitutes zero behavioral changes. Signed-off-by: Darl --- indra/newview/llmutelist.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index 0015bd09fb3..36bcba2c5c5 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()) From 638c7f7bea1a1d345980fa385a36201f5b6cc64e Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 21 Apr 2026 22:50:55 +0300 Subject: [PATCH 017/112] #5629 Velopack allows uninstall while the viewer is running (#5662) Velopack's uninstall can't be canceled, so instead it now checks for presense of a running window, makes sure window matches velopack's path, then sends a shutdown message. Window gets the message, verifies path, initiates shutdown. --- indra/llwindow/llwindowwin32.cpp | 144 +++++++++++++++++++ indra/llwindow/llwindowwin32.h | 6 + indra/newview/llappviewerwin32.cpp | 222 ++++++++++++++++++++++++++++- indra/newview/llappviewerwin32.h | 3 + indra/newview/llvelopack.cpp | 7 +- 5 files changed, 379 insertions(+), 3 deletions(-) diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index c185fc6c4ac..3cb2911f9a9 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -2605,6 +2605,150 @@ 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_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(l_param); + if (message_type == WM_POST_UNINSTALL_MSG_SHUTDOWN || message_type == WM_POST_UNINSTALL_MSG_UPDATE) + { + DWORD sender_process_id = static_cast(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(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"); diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h index aab2635a34f..afff3d5cb69 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 diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 94a5f7951e4..0dee17010ce 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -969,7 +969,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; @@ -977,13 +977,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 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(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(&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(our_process_id), + static_cast(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 0741758a0c6..d0a3f2f5ce2 100644 --- a/indra/newview/llappviewerwin32.h +++ b/indra/newview/llappviewerwin32.h @@ -45,6 +45,9 @@ class LLAppViewerWin32 : public LLAppViewer bool reportCrashToBugsplat(void* pExcepInfo) 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. diff --git a/indra/newview/llvelopack.cpp b/indra/newview/llvelopack.cpp index 8975f7d3695..d34d43cc481 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 #include #include @@ -666,8 +667,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) From 0cef18164bb0b4ed892f6e9e5fef31fc0f5dfb03 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 21 Apr 2026 23:00:55 +0300 Subject: [PATCH 018/112] #1355 Permit smaller values in cloud scroll rate --- indra/llui/llxyvector.cpp | 11 ++++++++++- .../default/xui/en/panel_settings_sky_clouds.xml | 4 +++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/indra/llui/llxyvector.cpp b/indra/llui/llxyvector.cpp index 1521823ce2c..b3c38bf82d2 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/newview/skins/default/xui/en/panel_settings_sky_clouds.xml b/indra/newview/skins/default/xui/en/panel_settings_sky_clouds.xml index 23bbf45e884..f39f8cb782d 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"/> Date: Wed, 22 Apr 2026 20:41:59 +0300 Subject: [PATCH 019/112] p#475 Excess texture fetch shutdown logging --- indra/newview/lltexturefetch.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/indra/newview/lltexturefetch.cpp b/indra/newview/lltexturefetch.cpp index 51ade608272..144940c7053 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 From 71172402b6a1cf1fbaf3c5f3485ed869075e183b Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev Date: Fri, 6 Dec 2024 20:36:23 +0200 Subject: [PATCH 020/112] #2965 Indicate when role members aren't loaded --- indra/newview/llpanelgrouproles.cpp | 19 ++++++++++++++----- indra/newview/llpanelgrouproles.h | 1 + .../default/xui/en/panel_group_roles.xml | 12 ++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/indra/newview/llpanelgrouproles.cpp b/indra/newview/llpanelgrouproles.cpp index e1f2d7588c8..426a89fe6cf 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("role_assigned_members"); mAllowedActionsList = parent->getChild("role_allowed_actions"); mActionDescription = parent->getChild("role_action_description"); + mMembersNotLoadedLbl = parent->getChild("members_not_loaded"); mRoleName = parent->getChild("role_name"); mRoleTitle = parent->getChild("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 e320efa1c7b..18c0e5dfeaa 100644 --- a/indra/newview/llpanelgrouproles.h +++ b/indra/newview/llpanelgrouproles.h @@ -297,6 +297,7 @@ class LLPanelGroupRolesSubTab : public LLPanelGroupSubTab LLLineEditor* mRoleTitle; LLTextEditor* mRoleDescription; + LLUICtrl* mMembersNotLoadedLbl; LLCheckBoxCtrl* mMemberVisibleCheck; LLButton* mDeleteRoleButton; LLButton* mCreateRoleButton; 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 868d54401e4..90697d5e4b4 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" /> + + Members are not loaded + Date: Thu, 23 Apr 2026 10:53:10 -0700 Subject: [PATCH 021/112] secondlife/viewer#5702: Ensure non-visible preloaded media has PRIORITY_HIDDEN --- indra/newview/llmediactrl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llmediactrl.cpp b/indra/newview/llmediactrl.cpp index c7b60b2fd56..4a808ba052c 100644 --- a/indra/newview/llmediactrl.cpp +++ b/indra/newview/llmediactrl.cpp @@ -739,7 +739,7 @@ bool LLMediaCtrl::ensureMediaSourceExists() mMediaSource->setUsedInUI(true); mMediaSource->setHomeURL(mHomePageUrl, mHomePageMimeType); mMediaSource->setTarget(mTarget); - mMediaSource->setVisible( getVisible() ); + mMediaSource->setVisible( isInVisibleChain() ); mMediaSource->addObserver( this ); mMediaSource->setBackgroundColor( getBackgroundColor() ); mMediaSource->setTrustedBrowser(mTrusted); From 67807bcdede95f17a6257dc295d0fc672be4babc Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 22 Apr 2026 22:48:05 +0300 Subject: [PATCH 022/112] #5676 Expand PBR planar aligment to cover all channels --- indra/newview/llpanelface.cpp | 46 +++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp index 12e1ebaad63..26f13927583 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; - 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; + 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; + } + } - LLGLTFMaterialList::queueModify(object, te, &new_override); + 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; + + LLGLTFMaterialList::queueModify(object, te, &new_override); + } } } if (!set_aligned) From fca6e9fff61133f8de7de1c9cc291de0bccfd221 Mon Sep 17 00:00:00 2001 From: Hecklezz Date: Fri, 24 Apr 2026 19:17:26 +1000 Subject: [PATCH 023/112] Revert "Get rid of a duplicate notification" This reverts commit d60b28e4cb2c69acbbd01ca56b03c19c1df4041a. --- indra/newview/skins/default/xui/en/notifications.xml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 221bac329d9..d616d474c0e 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -6742,6 +6742,14 @@ Your trash is overflowing. This may cause problems logging in. yestext="Check trash folder"/> + +Your inventory is experiencing issues. Please, contact support. + fail + + Date: Mon, 27 Apr 2026 15:16:31 -0300 Subject: [PATCH 024/112] #1681 Respect custom logfile during startup --- indra/newview/llappviewer.cpp | 86 ++++++++++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 6 deletions(-) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 46e64c2a4ea..0b6ea72df4a 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -66,6 +66,7 @@ #include "llconversationlog.h" #if LL_WINDOWS #include "lldxhardware.h" +#include #endif #include "lltexturestats.h" #include "lltrace.h" @@ -2298,6 +2299,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() { // @@ -2323,13 +2330,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 @@ -2376,7 +2380,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()) @@ -2515,6 +2519,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. From 9b8648707c233659dededc3d97340046ee4a3caa Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 27 Apr 2026 21:59:21 +0300 Subject: [PATCH 025/112] #5690 fix viewer trying to fetch group chat history for p2p IM * #5690 fix viewer trying to fetch group chat history for p2p IM * #5690 cache "FetchGroupChatHistory" setting --- indra/newview/llfloaterimsessiontab.h | 2 ++ indra/newview/llimview.cpp | 10 ++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/indra/newview/llfloaterimsessiontab.h b/indra/newview/llfloaterimsessiontab.h index b27ac1b8f9e..3ca1187ec96 100644 --- a/indra/newview/llfloaterimsessiontab.h +++ b/indra/newview/llfloaterimsessiontab.h @@ -118,6 +118,8 @@ class LLFloaterIMSessionTab 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/llimview.cpp b/indra/newview/llimview.cpp index ad01e11d483..1d3668de923 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -3253,8 +3253,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 fetch_chat_history(gSavedPerAccountSettings, "FetchGroupChatHistory", true); + if (fetch_chat_history && !session->isP2PSessionType()) { std::string chat_url = gAgent.getRegionCapability("ChatSessionRequest"); if (!chat_url.empty()) @@ -4097,8 +4098,9 @@ class LLViewerChatterBoxSessionStartReply : public LLHTTPNode { 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 fetch_chat_history(gSavedPerAccountSettings, "FetchGroupChatHistory", true); + if (fetch_chat_history && !im_floater->isP2PSessionType()) { std::string url = gAgent.getRegionCapability("ChatSessionRequest"); if (!url.empty()) From e0a5e8fb777fc7744a89db0c9b53280aa0d5ddbb Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 28 Apr 2026 02:07:58 +0300 Subject: [PATCH 026/112] #5724 Crash initing a buy_currency floater Might be better to throw a 'missing files' error here, but we normally continue in such cases, so I'm just logging the incident. --- indra/newview/llfloaterbuycurrency.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/indra/newview/llfloaterbuycurrency.cpp b/indra/newview/llfloaterbuycurrency.cpp index e41f893c433..506cff31f41 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("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 { From a66e6c9a2936aa2126e0e355825e6a969369da53 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 23 Apr 2026 22:19:53 +0300 Subject: [PATCH 027/112] p#479 make sure running app is the one handling urls --- indra/newview/llappviewermacosx-objc.h | 1 + indra/newview/llappviewermacosx-objc.mm | 25 +++++++++++++++++++++++++ indra/newview/llappviewermacosx.cpp | 15 +++++++++++++++ indra/newview/llappviewermacosx.h | 1 + 4 files changed, 42 insertions(+) diff --git a/indra/newview/llappviewermacosx-objc.h b/indra/newview/llappviewermacosx-objc.h index 3fbf4202f1a..bfbb48dadbf 100644 --- a/indra/newview/llappviewermacosx-objc.h +++ b/indra/newview/llappviewermacosx-objc.h @@ -31,5 +31,6 @@ #include 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 96a6bc6edce..75d6b56e3eb 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 2bd9c8661a5..830e2d4473d 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 d50812a35ee..35fc99dbd90 100644 --- a/indra/newview/llappviewermacosx.h +++ b/indra/newview/llappviewermacosx.h @@ -46,6 +46,7 @@ class LLAppViewerMacOSX : public LLAppViewer protected: virtual bool restoreErrorTrap(); + virtual bool initSLURLHandler(); std::string generateSerialNumber(); virtual bool initParseCommandLine(LLCommandLineParser& clp); From ae9f6a5616f366f0d66bdf6e0ca7b59ca268b639 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Sun, 26 Apr 2026 14:44:28 +0300 Subject: [PATCH 028/112] p#5719 Detect hybernation I'm not sure if viewer should actually be shutting down on this, but as a minimum we should be updating or clearing marker files. If viewer crashes because of a hibernation, it isn't our problem. Viewer isn't built for that and we can't maintain 'heartbeats' in hibernation. --- indra/llwindow/llwindowwin32.cpp | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index 3cb2911f9a9..7e956983d02 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -2587,6 +2587,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ { 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 +2606,47 @@ 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_"); From 116e802796d797b2ce9d463adb8566f28a7e4c6f Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 29 Apr 2026 23:41:16 +0300 Subject: [PATCH 029/112] #5728 Update license files --- autobuild.xml | 8 +- indra/newview/licenses-linux.txt | 204 ++++++++++++++++++++++++++++++- indra/newview/licenses-mac.txt | 202 ++++++++++++++++++++++++++++++ indra/newview/licenses-win32.txt | 202 ++++++++++++++++++++++++++++++ 4 files changed, 611 insertions(+), 5 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index c7d8bd5e9ba..cc2352bbd24 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2459,11 +2459,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - e88a7c97a6843d43e0093388f211299ec2892790 + d1584b3a0011dbb741bc64d32e8bd28845ddd4da hash_algorithm sha1 url - https://github.com/secondlife/3p-viewer-fonts/releases/download/v1.1.0-r1/viewer_fonts-1.0.0.10204976553-common-10204976553.tar.zst + https://github.com/secondlife/3p-viewer-fonts/releases/download/v1.1.0-r2/viewer_fonts-1.0.0.25128287087-common-25128287087.tar.zst name common @@ -2474,9 +2474,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors license_file LICENSES/fonts.txt copyright - 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 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. version - 1.0.0.10204976553 + 1.0.0.25128287087 name viewer-fonts description diff --git a/indra/newview/licenses-linux.txt b/indra/newview/licenses-linux.txt index e53ba94a365..c4286e970f6 100644 --- a/indra/newview/licenses-linux.txt +++ b/indra/newview/licenses-linux.txt @@ -808,4 +808,206 @@ From Vivox: Vivox, Inc. Attn: customer support 40 Speen Street Suite 402 - Framingham, MA 01701 + 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 29b5a919bdc..0a98fbd5919 100644 --- a/indra/newview/licenses-mac.txt +++ b/indra/newview/licenses-mac.txt @@ -771,3 +771,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 eddc9a4475d..7219c0c9af8 100644 --- a/indra/newview/licenses-win32.txt +++ b/indra/newview/licenses-win32.txt @@ -845,3 +845,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. From 998c2bc8d924fe9179f4ee914ef2b65c6ae78302 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 6 Mar 2026 03:20:55 +0200 Subject: [PATCH 030/112] #5084 Improve watchdog's behavior --- indra/llcommon/llapp.h | 1 + indra/llcommon/llwatchdog.cpp | 68 +++++++-- indra/llcommon/llwatchdog.h | 19 ++- indra/newview/llappviewer.cpp | 142 +++++++++++++++--- indra/newview/llappviewer.h | 3 + indra/newview/llappviewerwin32.cpp | 50 +++++- indra/newview/llappviewerwin32.h | 1 + .../newview/skins/default/xui/en/strings.xml | 3 + 8 files changed, 246 insertions(+), 41 deletions(-) diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index ce09c566a9d..fef7dc80b3c 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -285,6 +285,7 @@ class LL_COMMON_API LLApp #ifdef LL_WINDOWS virtual bool reportCrashToBugsplat(void* pExcepInfo /*EXCEPTION_POINTERS*/) { return false; } + virtual bool reportCustomToBugsplat(const std::string& desription) { return false; } #endif public: diff --git a/indra/llcommon/llwatchdog.cpp b/indra/llcommon/llwatchdog.cpp index d3242a6c96c..1622aeb1808 100644 --- a/indra/llcommon/llwatchdog.cpp +++ b/indra/llcommon/llwatchdog.cpp @@ -173,6 +173,17 @@ void LLWatchdog::add(LLWatchdogEntry* e) { lockThread(); mSuspects.insert(e); + + if (!mFrozeList.empty()) + { + mFrozeList.erase(e); + if (mFrozeList.empty()) + { + // Clear error marker file if there is no frozen threads, + // viewer is responsive again. + mClearMarkerFnc(); + } + } unlockThread(); } @@ -183,7 +194,12 @@ void LLWatchdog::remove(LLWatchdogEntry* e) unlockThread(); } -void LLWatchdog::init(func_t set_error_state_callback) +void LLWatchdog::init( + create_marker_func_t error_state_callback, + clear_marker_func_t clear_marker_callback, + report_func_t report_callback, + notify_func_t notify_callback, + bool crash_on_freeze) { if (!mSuspectsAccessMutex && !mTimer) { @@ -196,7 +212,11 @@ void LLWatchdog::init(func_t set_error_state_callback) // start needs to use the mSuspectsAccessMutex mTimer->start(); } - mCreateMarkerFnc = set_error_state_callback; + mCreateMarkerFnc = error_state_callback; + mClearMarkerFnc = clear_marker_callback; + mCrashReportFnc = report_callback; + mNotifyFnc = notify_callback; + mCrashOnFreeze = crash_on_freeze; } void LLWatchdog::cleanup() @@ -251,21 +271,45 @@ void LLWatchdog::run() mTimer->stop(); } - // Sets error marker file - mCreateMarkerFnc(); - // Todo1: Warn user? - // Todo2: We probably want to report even if 5 seconds passed, just not error 'yet'. std::string last_state = (*result)->getLastState(); - if (last_state.empty()) + std::string description = "Watchdog timer for thread " + (*result)->getThreadName() + " expired"; + if (!last_state.empty()) { - LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName() - << " expired; assuming viewer is hung and crashing" << LL_ENDL; + description += " with state: " + last_state; + } + description += "; assuming viewer is hung and crashing"; + + if (!mCrashOnFreeze) + { + // Sets watchdog marker file + mCreateMarkerFnc(false); + // If it's mainloop and it somehow recovers, it will re-add itself + mSuspects.erase(*result); + mFrozeList.insert(*result); + LL_WARNS() << description << LL_ENDL; } else { - LL_ERRS() << "Watchdog timer for thread " << (*result)->getThreadName() - << " expired with state: " << last_state - << "; assuming viewer is hung and crashing" << LL_ENDL; + + if (!mCrashReportFnc(description)) + { + // Sets error marker file + mCreateMarkerFnc(true); + // If false is returned, then we failed to report the issue to bugsplat, + // instead, Notify user, then crash viewer. + // Todo: ask user if viewer should quit or wait? + mNotifyFnc(); + LL_ERRS() << description << LL_ENDL; + } + else + { + // Sets watchdog marker file + mCreateMarkerFnc(false); + // Already reported, don't report again. + // If it's mainloop and it somehow recovers, it will re-add itself + mSuspects.erase(result); + mFrozeList.insert(*result); + } } } } diff --git a/indra/llcommon/llwatchdog.h b/indra/llcommon/llwatchdog.h index 2100a908798..f138fbccb05 100644 --- a/indra/llcommon/llwatchdog.h +++ b/indra/llcommon/llwatchdog.h @@ -93,8 +93,16 @@ class LLWatchdog : public LLSimpleton void add(LLWatchdogEntry* e); void remove(LLWatchdogEntry* e); - typedef std::function func_t; - void init(func_t set_error_state_callback); + typedef std::function create_marker_func_t; + typedef std::function clear_marker_func_t; + typedef std::function report_func_t; + typedef std::function notify_func_t; + void init( + create_marker_func_t error_state_callback, + clear_marker_func_t clear_marker_callback, + report_func_t report_callback, + notify_func_t notify_callback, + bool crash_on_freeze); void run(); void cleanup(); @@ -105,14 +113,19 @@ class LLWatchdog : public LLSimpleton typedef std::set SuspectsRegistry; SuspectsRegistry mSuspects; + SuspectsRegistry mFrozeList; LLMutex* mSuspectsAccessMutex; LLWatchdogTimerThread* mTimer; U64 mLastClockCount; + bool mCrashOnFreeze; // At the moment watchdog expects app to set markers in mCreateMarkerFnc, // but technically can be used to set any error states or do some cleanup // or show warnings. - func_t mCreateMarkerFnc; + create_marker_func_t mCreateMarkerFnc; + clear_marker_func_t mClearMarkerFnc; + report_func_t mCrashReportFnc; + notify_func_t mNotifyFnc; }; #endif // LL_LLTHREADWATCHDOG_H diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 0b6ea72df4a..4aaf8411cca 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -383,6 +383,7 @@ const std::string MARKER_FILE_NAME("SecondLife.exec_marker"); 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"); static std::string gLaunchFileOnQuit; //---------------------------------------------------------------------------- @@ -3235,20 +3236,60 @@ bool LLAppViewer::initWindow() << " (setting = " << watchdog_enabled_setting << ")" << LL_ENDL; - if (use_watchdog) + // Watchdog reports to statistics via marker files, that is + // pointless without ability to write (!mSecondInstance) those files. + // If use_watchdog is set, watchdog also reports to bugspat. + if (use_watchdog || !mSecondInstance) { - LLWatchdog::getInstance()->init([]() - { - LLAppViewer* app = LLAppViewer::instance(); - if (app->logoutRequestSent()) + LLWatchdog::getInstance()->init( + [](bool final_marker) { - app->createErrorMarker(LAST_EXEC_LOGOUT_FROZE); - } - else + LLAppViewer* app = LLAppViewer::instance(); + // Without watchdog everything will be counted as + // either 'unknown' (no crash marker) or based of present crash marker + if (final_marker) + { + // watchdog is going to crash viewer, so crate a 'crash' marker + if (app->logoutRequestSent()) + { + app->createErrorMarker(LAST_EXEC_LOGOUT_FROZE); + } + else + { + app->createErrorMarker(LAST_EXEC_FROZE); + } + } + else + { + // not going to crash, just create a 'watchdog' marker + app->createWatchdogMarker(); + } + }, + []() { - app->createErrorMarker(LAST_EXEC_FROZE); - } - }); + LLAppViewer* app = LLAppViewer::instance(); + // in case process recovered from freeze, remove watchdog marker. + app->removeWatchdogMarker(); + }, + [](std::string &desc) + { +#if LL_WINDOWS && LL_BUGSPLAT + LLAppViewer* app = LLAppViewer::instance(); + app->writeDebugInfo(); + return app->reportCustomToBugsplat(desc); +#else + return false; +#endif + }, + []() + { + LLAppViewer* app = LLAppViewer::instance(); + app->sendLogoutRequest(); + // Might be better to ask user if user wants to terminate the app or wait. + OSMessageBox(LLTrans::getString("MBFreezeDetected"), LLTrans::getString("MBFatalError"), OSMB_OK); + }, + use_watchdog); + } LLNotificationsUI::LLNotificationManager::getInstance(); @@ -4021,13 +4062,8 @@ void LLAppViewer::processMarkerFiles() { // the file existed, is ours, and matched our version, so we can report on what it says LL_INFOS("MarkerFile") << "Exec marker '"<< mMarkerFileName << "' found; last exec crashed or froze" << LL_ENDL; -#if LL_WINDOWS && LL_BUGSPLAT - // bugsplat will set correct state in bugsplatSendLog - // Might be more accurate to rename this one into 'unknown' + // App terminated unexpectedly or froze, we don't know the cause yet. gLastExecEvent = LAST_EXEC_UNKNOWN; -#else - gLastExecEvent = LAST_EXEC_OTHER_CRASH; -#endif // LL_WINDOWS } else @@ -4080,23 +4116,29 @@ void LLAppViewer::processMarkerFiles() } LLAPRFile::remove(logout_marker_file); } - // and last refine based on whether or not a marker created during a non-llerr crash is found + // Refine based on whether or not a marker created during + // a crash is found or if wathdog caught a freeze. + // 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); if(LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB)) { S32 marker_code = getMarkerErrorCode(error_marker_file); if (marker_code >= 0) { - if (gLastExecEvent == LAST_EXEC_LOGOUT_FROZE) - { - gLastExecEvent = LAST_EXEC_LOGOUT_CRASH; - LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' crashed, setting LastExecEvent to LOGOUT_CRASH" << LL_ENDL; - } - else if (marker_code > 0 && marker_code < (S32)LAST_EXEC_COUNT) + if (marker_code > 0 && marker_code < (S32)LAST_EXEC_COUNT) { + // If we have a code, it takes precendence gLastExecEvent = (eLastExecEvent)marker_code; LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' crashed, setting LastExecEvent to " << gLastExecEvent << LL_ENDL; } + // if we have the marker, even without a code, it's a crash. + else if (gLastExecEvent == LAST_EXEC_LOGOUT_UNKNOWN + || gLastExecEvent == LAST_EXEC_LOGOUT_FROZE) + { + gLastExecEvent = LAST_EXEC_LOGOUT_CRASH; + LL_INFOS("MarkerFile") << "Error marker '" << error_marker_file << "' crashed, setting LastExecEvent to LOGOUT_CRASH" << LL_ENDL; + } else { gLastExecEvent = LAST_EXEC_OTHER_CRASH; @@ -4108,6 +4150,33 @@ void LLAppViewer::processMarkerFiles() LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' marker found, but versions did not match" << LL_ENDL; } LLAPRFile::remove(error_marker_file); + if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) + { + // If viewer crashed after a freeze was detected, + // crash still takes precendence. Just clear watchdog. + removeWatchdogMarker(); + } + } + else + { + // so only check watchdog marker if there is no error marker. + if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) + { + if (LAST_EXEC_UNKNOWN == gLastExecEvent + || LAST_EXEC_LOGOUT_UNKNOWN == gLastExecEvent) + { + // 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; + } + } + removeWatchdogMarker(); + } } #if LL_DARWIN @@ -4152,6 +4221,7 @@ void LLAppViewer::removeMarkerFiles() { LL_WARNS("MarkerFile") << "logout marker '"<getExpandedFilename(LL_PATH_LOGS, WATCHDOG_MARKER_FILE_NAME); + + LLAPRFile file; + file.open(error_marker, LL_APR_WB); + if (file.getFileHandle()) + { + recordMarkerVersion(file); + file.close(); + } + } +} +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); + } +} + void LLAppViewer::outOfMemorySoftQuit() { if (!mQuitRequested) diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index c977757e486..d76e5015e94 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -254,6 +254,9 @@ class LLAppViewer : public LLApp void createErrorMarker(eLastExecEvent error_code) const; bool errorMarkerExists() const; + void createWatchdogMarker() const; + void removeWatchdogMarker() const; + // Attempt a 'soft' quit with disconnect and saving of settings/cache. // Intended to be thread safe. // Good chance of viewer crashing either way, but better than alternatives. diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 0dee17010ce..a78a7fb95f3 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -119,6 +119,7 @@ namespace // MiniDmpSender pointer. As things stand, though, we must define an // actual function and store the pointer statically. static MiniDmpSender *sBugSplatSender = nullptr; + static std::string sBugsplatDesriptionField; bool bugsplatSendLog(UINT nCode, LPVOID lpVal1, LPVOID lpVal2) { @@ -155,8 +156,21 @@ namespace WCSTR(gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "settings_per_account.xml"))); } - // LL_ERRS message, when there is one - sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage())); + if (!sBugsplatDesriptionField.empty()) + { + // Can be set by watchdog or other code that detects a problem + // and wants to add some context to the crash report. + // Will be visible in the BugSplat web UI. + sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage())); + // This type of crash is not nessesarily a crash, or final. + // Prepare for the next one. + sBugsplatDesriptionField.clear(); + } + else + { + // LL_ERRS message, when there is one + sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage())); + } sBugSplatSender->setAttribute(WCSTR(L"OS"), WCSTR(LLOSInfo::instance().getOSStringSimple())); // In case we ever stop using email for this sBugSplatSender->setAttribute(WCSTR(L"AppState"), WCSTR(LLStartUp::getStartupStateString())); @@ -862,6 +876,38 @@ bool LLAppViewerWin32::reportCrashToBugsplat(void* pExcepInfo) return false; } +#if defined(LL_BUGSPLAT) +static int reportCustomToBugsplatFilter(EXCEPTION_POINTERS* pExcepInfo) +{ + if (sBugSplatSender) + { + sBugSplatSender->createReport(pExcepInfo); + } + return EXCEPTION_EXECUTE_HANDLER; +} +#endif + +bool LLAppViewerWin32::reportCustomToBugsplat(const std::string &description) +{ +#if defined(LL_BUGSPLAT) + if (sBugSplatSender) + { + sBugsplatDesriptionField = description; + + __try + { + // Generate a custom exception code + RaiseException(0xE0000001, 0, 0, NULL); + } + __except (reportCustomToBugsplatFilter(GetExceptionInformation())) + { + } + return true; + } +#endif // LL_BUGSPLAT + return false; +} + bool LLAppViewerWin32::initWindow() { // This is a workaround/hotfix for a change in Windows 11 24H2 (and possibly later) diff --git a/indra/newview/llappviewerwin32.h b/indra/newview/llappviewerwin32.h index d0a3f2f5ce2..ece4fef6fda 100644 --- a/indra/newview/llappviewerwin32.h +++ b/indra/newview/llappviewerwin32.h @@ -44,6 +44,7 @@ class LLAppViewerWin32 : public LLAppViewer bool cleanup() override; bool reportCrashToBugsplat(void* pExcepInfo) override; + bool reportCustomToBugsplat(const std::string& desription) override; // returns true if other windows were found and are still running. static bool sendShutdownToOtherInstances(const std::wstring& install_dir); diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 6a123d2a2a5..a4ff883c2b7 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -3008,6 +3008,9 @@ If this message persists, restart your computer. [APP_NAME] appears to have frozen or crashed on the previous run. Would you like to send a crash report? + + [APP_NAME] appears to have frozen. If this issue occurs regularly, please contact support at https://support.secondlife.com. + Notification [APP_NAME] is unable to detect DirectX 9.0b or greater. From e129eac3401f6af629ef96e91f4db62b3528cbc9 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 29 Apr 2026 01:20:11 +0300 Subject: [PATCH 031/112] #5719 Partial detection of shutdown from task manager We can't be absolutely certain about the source in this case, but at least try to distinguish termination caused by task manager from 'unknowns'. --- indra/llwindow/llwindowcallbacks.cpp | 4 ++ indra/llwindow/llwindowcallbacks.h | 2 + indra/llwindow/llwindowwin32.cpp | 61 ++++++++++++++++++++-- indra/llwindow/llwindowwin32.h | 1 + indra/newview/llappviewer.cpp | 76 ++++++++++++++++++++++++---- indra/newview/llappviewer.h | 2 + indra/newview/llviewerwindow.cpp | 21 ++++++++ indra/newview/llviewerwindow.h | 1 + 8 files changed, 152 insertions(+), 16 deletions(-) diff --git a/indra/llwindow/llwindowcallbacks.cpp b/indra/llwindow/llwindowcallbacks.cpp index 7331f50ba0c..62674337513 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 59dcdd3adee..457087448f3 100644 --- a/indra/llwindow/llwindowcallbacks.h +++ b/indra/llwindow/llwindowcallbacks.h @@ -41,6 +41,8 @@ class LLWindowCallbacks 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 7e956983d02..dd4f6387672 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) { @@ -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,6 +2624,7 @@ 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; @@ -3318,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 afff3d5cb69..defa90d1a32 100644 --- a/indra/llwindow/llwindowwin32.h +++ b/indra/llwindow/llwindowwin32.h @@ -238,6 +238,7 @@ class LLWindowWin32 : public LLWindow 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/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 4aaf8411cca..631804e13e1 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -384,6 +384,7 @@ 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 CLOSE_EVENT_MARKER_FILE_NAME("SecondLife.close_marker"); static std::string gLaunchFileOnQuit; //---------------------------------------------------------------------------- @@ -4121,6 +4122,7 @@ 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 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); @@ -4150,20 +4152,16 @@ void LLAppViewer::processMarkerFiles() LL_INFOS("MarkerFile") << "Error marker '"<< error_marker_file << "' marker found, but versions did not match" << LL_ENDL; } LLAPRFile::remove(error_marker_file); - if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) - { - // If viewer crashed after a freeze was detected, - // crash still takes precendence. Just clear watchdog. - removeWatchdogMarker(); - } } else { - // so only check watchdog marker if there is no error marker. - if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) + if (LAST_EXEC_UNKNOWN == gLastExecEvent + || LAST_EXEC_LOGOUT_UNKNOWN == gLastExecEvent) { - 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, @@ -4175,9 +4173,35 @@ void LLAppViewer::processMarkerFiles() << LL_ENDL; } } - removeWatchdogMarker(); + // 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; + } + } } } + if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) + { + removeWatchdogMarker(); + } + if (LLAPRFile::isExist(close_marker_file, NULL, LL_APR_RB)) + { + removeCloseRequestMarker(); + } #if LL_DARWIN if (!mSecondInstance && gLastExecEvent != LAST_EXEC_NORMAL) @@ -4221,6 +4245,7 @@ void LLAppViewer::removeMarkerFiles() { LL_WARNS("MarkerFile") << "logout marker '"<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 error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME); + LLFile::remove(error_marker_file); + } +} + void LLAppViewer::createWatchdogMarker() const { if (!mSecondInstance) @@ -5624,6 +5677,7 @@ void LLAppViewer::createWatchdogMarker() const } } } + void LLAppViewer::removeWatchdogMarker() const { if (!mSecondInstance) diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index d76e5015e94..87c885e4be3 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -254,6 +254,8 @@ class LLAppViewer : public LLApp void createErrorMarker(eLastExecEvent error_code) const; bool errorMarkerExists() const; + void createCloseRequestMarker() const; + void removeCloseRequestMarker() const; void createWatchdogMarker() const; void removeWatchdogMarker() const; diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 8695b969526..369a8871022 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 ec28a3fc4aa..ff5da371ec4 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -202,6 +202,7 @@ class LLViewerWindow : public LLWindowCallbacks /*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); From 6fb576a95cb813a39b48aa51dbf7935db2c7ce11 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 30 Apr 2026 01:39:30 +0300 Subject: [PATCH 032/112] #5084 Improve watchdog's behavior #2 --- indra/llcommon/llapp.h | 2 +- indra/llcommon/llwatchdog.cpp | 9 ++++++--- indra/newview/llappviewerwin32.cpp | 12 ++++++------ indra/newview/llappviewerwin32.h | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index fef7dc80b3c..3a855bc480c 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -285,7 +285,7 @@ class LL_COMMON_API LLApp #ifdef LL_WINDOWS virtual bool reportCrashToBugsplat(void* pExcepInfo /*EXCEPTION_POINTERS*/) { return false; } - virtual bool reportCustomToBugsplat(const std::string& desription) { return false; } + virtual bool reportCustomToBugsplat(const std::string& description) { return false; } #endif public: diff --git a/indra/llcommon/llwatchdog.cpp b/indra/llcommon/llwatchdog.cpp index 1622aeb1808..66b565c7634 100644 --- a/indra/llcommon/llwatchdog.cpp +++ b/indra/llcommon/llwatchdog.cpp @@ -191,6 +191,7 @@ void LLWatchdog::remove(LLWatchdogEntry* e) { lockThread(); mSuspects.erase(e); + mFrozeList.erase(e); unlockThread(); } @@ -284,8 +285,9 @@ void LLWatchdog::run() // Sets watchdog marker file mCreateMarkerFnc(false); // If it's mainloop and it somehow recovers, it will re-add itself - mSuspects.erase(*result); - mFrozeList.insert(*result); + LLWatchdogEntry* froze_entry = *result; + mSuspects.erase(result); + mFrozeList.insert(froze_entry); LL_WARNS() << description << LL_ENDL; } else @@ -307,8 +309,9 @@ void LLWatchdog::run() mCreateMarkerFnc(false); // Already reported, don't report again. // If it's mainloop and it somehow recovers, it will re-add itself + LLWatchdogEntry* froze_entry = *result; mSuspects.erase(result); - mFrozeList.insert(*result); + mFrozeList.insert(froze_entry); } } } diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index a78a7fb95f3..3dc98b83c4d 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -119,7 +119,7 @@ namespace // MiniDmpSender pointer. As things stand, though, we must define an // actual function and store the pointer statically. static MiniDmpSender *sBugSplatSender = nullptr; - static std::string sBugsplatDesriptionField; + static std::string sBugsplatDescriptionField; bool bugsplatSendLog(UINT nCode, LPVOID lpVal1, LPVOID lpVal2) { @@ -156,15 +156,15 @@ namespace WCSTR(gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "settings_per_account.xml"))); } - if (!sBugsplatDesriptionField.empty()) + if (!sBugsplatDescriptionField.empty()) { // Can be set by watchdog or other code that detects a problem // and wants to add some context to the crash report. // Will be visible in the BugSplat web UI. - sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage())); - // This type of crash is not nessesarily a crash, or final. + sBugSplatSender->setDefaultUserDescription(WCSTR(sBugsplatDescriptionField)); + // This type of crash is not necessarily a crash, or final. // Prepare for the next one. - sBugsplatDesriptionField.clear(); + sBugsplatDescriptionField.clear(); } else { @@ -892,7 +892,7 @@ bool LLAppViewerWin32::reportCustomToBugsplat(const std::string &description) #if defined(LL_BUGSPLAT) if (sBugSplatSender) { - sBugsplatDesriptionField = description; + sBugsplatDescriptionField = description; __try { diff --git a/indra/newview/llappviewerwin32.h b/indra/newview/llappviewerwin32.h index ece4fef6fda..ad61ae68d6e 100644 --- a/indra/newview/llappviewerwin32.h +++ b/indra/newview/llappviewerwin32.h @@ -44,7 +44,7 @@ class LLAppViewerWin32 : public LLAppViewer bool cleanup() override; bool reportCrashToBugsplat(void* pExcepInfo) override; - bool reportCustomToBugsplat(const std::string& desription) 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); From 30b897dfcdff27358745a87a3bd8d11c114bdf0e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 01:53:19 +0000 Subject: [PATCH 033/112] Bump actions/download-artifact from 4 to 8 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 8. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v8) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d98fd4b531b..c1eb9cf43c1 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -487,7 +487,7 @@ jobs: with: pattern: "*-metadata" - - uses: actions/download-artifact@v4 + - uses: actions/download-artifact@v8 with: pattern: "*-releases" From fa7b2292bb5ea3b75fc5b18a468c5ca80c9530ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 01:53:14 +0000 Subject: [PATCH 034/112] Bump BugSplat-Git/symbol-upload from 10.3.0 to 10.3.2 Bumps [BugSplat-Git/symbol-upload](https://github.com/bugsplat-git/symbol-upload) from 10.3.0 to 10.3.2. - [Release notes](https://github.com/bugsplat-git/symbol-upload/releases) - [Commits](https://github.com/bugsplat-git/symbol-upload/compare/2a0d2b8cf9c54c494144048f25da863d93a02ccd...33f604b631df9b4be22a06c52e46acccd2db596b) --- updated-dependencies: - dependency-name: BugSplat-Git/symbol-upload dependency-version: 10.3.2 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c1eb9cf43c1..cee977e6688 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@2a0d2b8cf9c54c494144048f25da863d93a02ccd + 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@2a0d2b8cf9c54c494144048f25da863d93a02ccd + uses: BugSplat-Git/symbol-upload@33f604b631df9b4be22a06c52e46acccd2db596b with: clientId: "${{ env.SYMBOL_UPLOAD_CLIENT_ID }}" clientSecret: "${{ env.SYMBOL_UPLOAD_CLIENT_SECRET }}" From d70047880d8d4e1d8e37ca6dfdc4a622837184f8 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Tue, 5 May 2026 00:06:15 +0300 Subject: [PATCH 035/112] #5682 add LEAP API to get info about web_browser widget --- indra/newview/llmediactrl.cpp | 184 ++++++++++++++++++++++++++++++++++ indra/newview/llmediactrl.h | 14 +++ 2 files changed, 198 insertions(+) diff --git a/indra/newview/llmediactrl.cpp b/indra/newview/llmediactrl.cpp index 4a808ba052c..679157dc1e3 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 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 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(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 a644ef30719..e407bfbbcbc 100644 --- a/indra/newview/llmediactrl.h +++ b/indra/newview/llmediactrl.h @@ -77,6 +77,14 @@ class LLMediaCtrl : 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 @@ class LLMediaCtrl : 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 @@ class LLMediaCtrl : viewer_media_t mMediaSource; S32 mTextureWidth, mTextureHeight; + ELoadingState mLoadingState; class LLWindowShade* mWindowShade; LLHandle mContextMenuHandle; From c828dc35db7d5673a629590da3ec2064d1394ad4 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Mon, 4 May 2026 23:26:16 +0300 Subject: [PATCH 036/112] #5084 Fix watchdog's cleanup --- indra/newview/llappviewer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 631804e13e1..f9f94b8f289 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2135,6 +2135,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 From 5b5c4e5f385dfdcbe001e20200b4d381ddcab379 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Mon, 4 May 2026 23:31:14 +0300 Subject: [PATCH 037/112] #5756 Improve LLUICtrlFactory's performance --- indra/llui/lluictrlfactory.cpp | 2 +- indra/llui/lluictrlfactory.h | 11 ++++++++--- indra/newview/llappviewer.cpp | 2 ++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/indra/llui/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp index 33ffc3dfc63..740f187ed8d 100644 --- a/indra/llui/lluictrlfactory.cpp +++ b/indra/llui/lluictrlfactory.cpp @@ -67,7 +67,7 @@ class LLUICtrlLocate : public LLUICtrl static LLDefaultChildRegistry::Register r1("locate"); // Build time optimization, generate this once in .cpp file -template class LLUICtrlFactory* LLSingleton::getInstance(); +template class LLUICtrlFactory* LLSimpleton::getInstance(); //----------------------------------------------------------------------------- // LLUICtrlFactory() diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h index f44b4ba4dcf..75cee5f004c 100644 --- a/indra/llui/lluictrlfactory.h +++ b/indra/llui/lluictrlfactory.h @@ -81,12 +81,13 @@ class LLWidgetNameRegistry // Build time optimization, generate this once in .cpp file #ifndef LLUICTRLFACTORY_CPP -extern template class LLUICtrlFactory* LLSingleton::getInstance(); +extern template class LLUICtrlFactory* LLSimpleton::getInstance(); #endif -class LLUICtrlFactory : public LLSingleton +class LLUICtrlFactory : public LLSimpleton { - LLSINGLETON(LLUICtrlFactory); +public: + LLUICtrlFactory(); ~LLUICtrlFactory(); // only partial specialization allowed in inner classes, so use extra dummy parameter @@ -313,6 +314,10 @@ template LLChildRegistry::Register::Register(const char* tag, LLWidgetCreatorFunc func) : LLChildRegistry::StaticRegistrar(tag, func == nullptr ? (LLWidgetCreatorFunc)&LLUICtrlFactory::defaultBuilder : func) { + if (!LLUICtrlFactory::instanceExists()) + { + LLUICtrlFactory::createInstance(); + } // add this widget to various registries LLUICtrlFactory::instance().registerWidget(typeid(T), typeid(typename T::Params), tag); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index f9f94b8f289..2e428550792 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2148,6 +2148,8 @@ bool LLAppViewer::cleanup() // deleteSingleton() methods. LLSingletonBase::deleteAll(); + LLUICtrlFactory::deleteSingleton(); + LLSplashScreen::hide(); LL_INFOS() << "Goodbye!" << LL_ENDL; From 281eb9b54b6918f25ec01e5484b631aa1294aca6 Mon Sep 17 00:00:00 2001 From: pepper <3782201+rohvani@users.noreply.github.com> Date: Mon, 6 Oct 2025 16:49:27 -0700 Subject: [PATCH 038/112] #5756 Performance optimizations around UI --- indra/llui/llpanel.cpp | 42 +++++++++++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp index 2100b23783b..d09fc3e22df 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 From 709dbea75e1f0749ec8fe9c8f46f077578a7fc82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 May 2026 01:53:08 +0000 Subject: [PATCH 039/112] Bump actions/github-script from 8 to 9 Bumps [actions/github-script](https://github.com/actions/github-script) from 8 to 9. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v8...v9) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '9' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/check-pr.yaml | 2 +- .github/workflows/tag-release.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/check-pr.yaml b/.github/workflows/check-pr.yaml index 08e907e83f5..68ca8244c09 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 0f826222a05..f1afb0f1a29 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 From 29335d26ddba21afcac7655dc7249ede6aae0c09 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 6 May 2026 00:22:26 +0300 Subject: [PATCH 040/112] #5232 Crash in handleMessage #2 LLSD are thread unsafe, don't pass them across threads. --- indra/newview/lleventpoll.cpp | 49 ++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/indra/newview/lleventpoll.cpp b/indra/newview/lleventpoll.cpp index f1b46f05331..a18fdf74c14 100644 --- a/indra/newview/lleventpoll.cpp +++ b/indra/newview/lleventpoll.cpp @@ -328,26 +328,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; + } } } } From 9f60b2b467d2e908fd8d6b48931b986eab21f261 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Thu, 7 May 2026 19:29:47 +0300 Subject: [PATCH 041/112] #5742 Add LEAP API to get info about scene-level attached objects --- indra/newview/llagentlistener.cpp | 42 +++++++++++++++++++++++++++++++ indra/newview/llagentlistener.h | 1 + 2 files changed, 43 insertions(+) diff --git a/indra/newview/llagentlistener.cpp b/indra/newview/llagentlistener.cpp index 5ddb87558ac..73368c1bcc6 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 b5bea8c0bde..031fc75e463 100644 --- a/indra/newview/llagentlistener.h +++ b/indra/newview/llagentlistener.h @@ -68,6 +68,7 @@ class LLAgentListener : public LLEventAPI 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; From 07306401ec97ffd48e814a8b2c0910bfbd162a53 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 7 May 2026 00:10:18 +0300 Subject: [PATCH 042/112] #5677 Left side of Profile's Pick's location string can be cut --- indra/newview/llpanelprofilepicks.cpp | 18 ++++++++++++++++-- indra/newview/llpanelprofilepicks.h | 3 +++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/indra/newview/llpanelprofilepicks.cpp b/indra/newview/llpanelprofilepicks.cpp index c9626bf9ea0..1c1b7a1cc45 100644 --- a/indra/newview/llpanelprofilepicks.cpp +++ b/indra/newview/llpanelprofilepicks.cpp @@ -622,6 +622,7 @@ bool LLPanelProfilePick::postBuild() { mPickName = getChild("pick_name"); mPickDescription = getChild("pick_desc"); + mPickLocation = getChild("pick_location"); mSaveButton = getChild("save_changes_btn"); mCreateButton = getChild("create_changes_btn"); mCancelButton = getChild("cancel_changes_btn"); @@ -646,11 +647,17 @@ bool LLPanelProfilePick::postBuild() mPickDescription->setKeystrokeCallback(boost::bind(&LLPanelProfilePick::onPickChanged, this, _1)); mPickDescription->setFocusReceivedCallback(boost::bind(&LLPanelProfilePick::onDescriptionFocusReceived, this)); - getChild("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); + mPickLocation->setCursor(0); +} + void LLPanelProfilePick::onDescriptionFocusReceived() { if (!mIsEditing && getSelfProfile()) @@ -749,7 +756,14 @@ void LLPanelProfilePick::setPickLocation(const LLUUID &parcel_id, const std::str void LLPanelProfilePick::setPickLocation(const std::string& location) { - getChild("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(); } diff --git a/indra/newview/llpanelprofilepicks.h b/indra/newview/llpanelprofilepicks.h index 847ac57cea5..1a378b470b2 100644 --- a/indra/newview/llpanelprofilepicks.h +++ b/indra/newview/llpanelprofilepicks.h @@ -112,6 +112,8 @@ class LLPanelProfilePick 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; } @@ -231,6 +233,7 @@ class LLPanelProfilePick LLTextureCtrl* mSnapshotCtrl; LLLineEditor* mPickName; LLTextEditor* mPickDescription; + LLLineEditor* mPickLocation; LLButton* mSetCurrentLocationButton; LLButton* mSaveButton; LLButton* mCreateButton; From 1d03b21c009cbcb222be834b3985c3c39891ec29 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 5 May 2026 23:13:12 +0300 Subject: [PATCH 043/112] #4802 Handle KDU crashes --- indra/llkdu/llimagej2ckdu.cpp | 42 +++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/indra/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp index e7ac6bdb31e..cf45f931684 100644 --- a/indra/llkdu/llimagej2ckdu.cpp +++ b/indra/llkdu/llimagej2ckdu.cpp @@ -158,6 +158,9 @@ class LLKDUDecodeState 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; @@ -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. From d2a3a157a023f8608c44dfd3b07e8986df88c416 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 8 May 2026 00:26:24 +0300 Subject: [PATCH 044/112] #5772 'Control not found' crash --- indra/llxml/llcontrol.h | 2 +- indra/newview/llagent.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/llxml/llcontrol.h b/indra/llxml/llcontrol.h index 5aa2b9715ed..ad283c89b18 100644 --- a/indra/llxml/llcontrol.h +++ b/indra/llxml/llcontrol.h @@ -321,7 +321,7 @@ class LLControlCache : public LLRefCount, public LLInstanceTracker recent_jump_threshold_secs(gSavedSettings, "RecentJumpThresholdSecs"); + static LLCachedControl recent_jump_threshold_secs(gSavedSettings, "RecentJumpThresholdSecs", 1.0); const bool recent_jump = (mLastJumpInputTime > 0.0) && (elapsed < recent_jump_threshold_secs); if (!up_pos && !recent_jump) From dfeed0c398f961bce8cdbc24fbfc1cab758a9fd4 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 8 May 2026 17:56:00 +0300 Subject: [PATCH 045/112] #5777 Exception handling for an LLSD formatter Attempt to catch an inventory and name cache shutdown crash that we see in bugsplat. --- indra/llcommon/llsdserialize_xml.cpp | 77 +++++++++++++++++++-- indra/llcommon/tests/llsdserialize_test.cpp | 52 ++++++++++++++ 2 files changed, 123 insertions(+), 6 deletions(-) diff --git a/indra/llcommon/llsdserialize_xml.cpp b/indra/llcommon/llsdserialize_xml.cpp index f399c51608e..af79fabe81c 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; + + // 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)) + { + LL_WARNS() << "LLSDXMLFormatter::format: Stream already in error state" << LL_ENDL; + ostr.precision(old_precision); + return -1; + } - std::string post; - if (options & LLSDFormatter::OPTIONS_PRETTY) + S32 rv = 0; + + try { - post = "\n"; + // 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 << "" << post; + rv = format_impl(data, ostr, options, 1); + ostr << "\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 << "" << post; - S32 rv = format_impl(data, ostr, options, 1); - ostr << "\n"; ostr.precision(old_precision); return rv; diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index fae9f7023f4..f52b3fec714 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: From da71b0bafe1e607c145218b178526f1eafb81019 Mon Sep 17 00:00:00 2001 From: Trish Date: Tue, 28 Apr 2026 19:38:31 -0400 Subject: [PATCH 046/112] fix picks from moved regions #5519 --- indra/newview/llpanelprofilepicks.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/indra/newview/llpanelprofilepicks.cpp b/indra/newview/llpanelprofilepicks.cpp index 1c1b7a1cc45..2e5b6707f1f 100644 --- a/indra/newview/llpanelprofilepicks.cpp +++ b/indra/newview/llpanelprofilepicks.cpp @@ -910,6 +910,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. From 6b945de4e55b471100b534fd4b1f02d1443b19ea Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 11 May 2026 23:35:24 +0300 Subject: [PATCH 047/112] #5784 fix crash at LLViewerRegion::killInvisibleObjects --- indra/newview/llviewerregion.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 42a587f3764..fd56a8d0c7a 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) { From a4e8f716a2cd682e1fd89eafbee45845648cdf72 Mon Sep 17 00:00:00 2001 From: Trish Date: Tue, 28 Apr 2026 19:34:37 -0400 Subject: [PATCH 048/112] FIX - Prejump regressions --- indra/newview/app_settings/settings.xml | 2 +- indra/newview/llagent.cpp | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 7c98572d076..dcae9b9e12a 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -492,7 +492,7 @@ RecentJumpThresholdSecs Comment - Seconds after a jump input during which finish-anim is suppressed to avoid interrupting rapid successive jumps. + Seconds after jump input during which landing finish-anim is suppressed to avoid interrupting rapid successive jumps. Persist 1 Type diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 3f5c0ad8437..123ffa676e5 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -2669,17 +2669,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 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); } From 127fe9bc88ad19d8cbf69be9e09bf9fa2e7abd7b Mon Sep 17 00:00:00 2001 From: santoslgl01-web Date: Wed, 29 Apr 2026 15:27:15 -0300 Subject: [PATCH 049/112] fix: replace non-ASCII source comments Resolves #5664 --- indra/llfilesystem/lldiskcache.cpp | 4 ++-- indra/llkdu/llimagej2ckdu.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/indra/llfilesystem/lldiskcache.cpp b/indra/llfilesystem/lldiskcache.cpp index dd7d1a043ff..ea33178a1e9 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/llkdu/llimagej2ckdu.cpp b/indra/llkdu/llimagej2ckdu.cpp index cf45f931684..21e5bc0ab43 100644 --- a/indra/llkdu/llimagej2ckdu.cpp +++ b/indra/llkdu/llimagej2ckdu.cpp @@ -231,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 From cdb90645e7b1040b4555a9ab8d34c26c301da491 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Sat, 9 May 2026 00:07:36 +0300 Subject: [PATCH 050/112] #5744 Prefill 'report' floater for objects' IMs --- indra/llui/lltextbase.cpp | 1 + indra/llui/llurlaction.cpp | 13 ++++- indra/llui/llurlaction.h | 1 + indra/newview/llchathistory.cpp | 47 ++++++++++++++++++- indra/newview/llchatitemscontainerctrl.cpp | 28 +++++++++++ indra/newview/llfloaterreporter.cpp | 25 ++++++++-- indra/newview/llfloaterreporter.h | 3 +- .../skins/default/xui/en/menu_object_icon.xml | 8 ++++ .../default/xui/en/menu_url_objectim.xml | 7 +++ 9 files changed, 124 insertions(+), 9 deletions(-) diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 9fcf2a5f10b..7565dafea8b 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -2285,6 +2285,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 8b320b59cc3..61a529c5d23 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 c4cfd0f3fb1..d110fe56d37 100644 --- a/indra/llui/llurlaction.h +++ b/indra/llui/llurlaction.h @@ -89,6 +89,7 @@ class LLUrlAction 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/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index c1af09ebc76..af94c6d4435 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -175,6 +175,49 @@ class LLChatHistoryHeader: public LLPanel 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("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 @@ class LLChatHistoryHeader: public LLPanel time_string = getChild("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 @@ class LLChatHistoryHeader: public LLPanel 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 5ac4ce0d522..2e8fb752b8b 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 @@ class LLObjectHandler : public LLCommandHandler } 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/llfloaterreporter.cpp b/indra/newview/llfloaterreporter.cpp index 7e7eb91636b..6322dcd3c97 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("reporter"); if (self) { - std::string description = self->getString("chat_report_format", args); - self->getChild("details_edit")->setValue(description); + std::string description_frmt = self->getString("chat_report_format", args); + self->getChild("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("reporter"); + if (self) + { + std::string description_frmt = self->getString("chat_report_format", args); + self->getChild("details_edit")->setValue(description_frmt); } } diff --git a/indra/newview/llfloaterreporter.h b/indra/newview/llfloaterreporter.h index 31b05235a65..446e7d63b61 100644 --- a/indra/newview/llfloaterreporter.h +++ b/indra/newview/llfloaterreporter.h @@ -93,7 +93,8 @@ class LLFloaterReporter 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/skins/default/xui/en/menu_object_icon.xml b/indra/newview/skins/default/xui/en/menu_object_icon.xml index d43ce26e56e..bf445b0d18e 100644 --- a/indra/newview/skins/default/xui/en/menu_object_icon.xml +++ b/indra/newview/skins/default/xui/en/menu_object_icon.xml @@ -27,6 +27,14 @@ function="ObjectIcon.Visible" parameter="not_blocked" /> + + + + + + Date: Wed, 13 May 2026 00:31:10 +0300 Subject: [PATCH 051/112] #5775 Update copyright date in cmake --- indra/newview/CMakeLists.txt | 2 +- indra/newview/res/viewerRes.rc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 17d344a3308..d2b679ee3cd 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -2194,7 +2194,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") diff --git a/indra/newview/res/viewerRes.rc b/indra/newview/res/viewerRes.rc index dc2ba5f1719..c9ca7585558 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}" From 5ffe81be36e55d12c926351c30036304cc6a106e Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Wed, 13 May 2026 12:01:24 +0300 Subject: [PATCH 052/112] #5801 add Mute sound menu option --- indra/newview/llviewermenu.cpp | 12 ++++++++++++ indra/newview/skins/default/xui/en/menu_viewer.xml | 10 +++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 8d6b8ec7139..46dfa618707 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -9641,6 +9641,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; @@ -9912,6 +9922,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"); diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index f7541f52ad0..591bf6c7124 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -1030,7 +1030,15 @@ parameter="pause_clouds" /> - + + + + - Didn't find what you're looking for? Try [secondlife:///app/search/all/[SEARCH_TERM] Search]. + No matching items. Didn't find what you're looking for? Try [secondlife:///app/inventory/filters Show filters]. You haven't marked any items as favorites. To add a place to your landmarks, click the star to the right of the location name. From 35b7766beed50d87fab486a3064218e434ef36fb Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Wed, 13 May 2026 20:23:41 +0300 Subject: [PATCH 056/112] #5804 Prevent group chat focus steal when opening adhoc IM --- indra/newview/llfloaterimcontainer.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index a0f2dbe1972..21f1ed831dd 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -155,15 +155,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; + } } From 9e7d3b1654798ab85c8a118dca04999addef854e Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Fri, 15 May 2026 19:27:23 +0300 Subject: [PATCH 057/112] #5802 Open teleport history with a keystroke --- indra/newview/llpanelplaces.cpp | 22 ++++++++++++++++++ indra/newview/llviewermenu.cpp | 23 ++++++++++++++++++- .../skins/default/xui/en/menu_viewer.xml | 7 ++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/indra/newview/llpanelplaces.cpp b/indra/newview/llpanelplaces.cpp index 5c866905d6a..5dd4960542e 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/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 46dfa618707..129d5477f1d 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -5183,6 +5183,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 { @@ -9940,7 +9961,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"); diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index 953398c7bd2..3364b831b23 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -43,6 +43,13 @@ + + + From e3fa81c975c80be5153572688d69e0cbe9d3cd5f Mon Sep 17 00:00:00 2001 From: Cosmic Linden Date: Thu, 14 May 2026 15:46:20 -0700 Subject: [PATCH 058/112] Fix display of ad-hoc session type in About and display p2p instead as appropriate --- indra/newview/llvoicewebrtc.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index 333046adce1..6d5a8eae6a1 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -353,9 +353,16 @@ void LLWebRTCVoiceClient::updateVersion() { // A WebRTC session can be connected to multiple servers at once. To more easily disambiguate which server version is being printed, show the connection type. In most cases, this shouldn't matter and the Janus version should be the same for all connections. Janus versions are also logged for each connection. mVoiceVersion.serverVersion = session->getVersion(); - if (session->isCallbackPossible()) + if (dynamic_cast(session.get())) { - mVoiceVersion.mBuildVersion = "ad-hoc"; + if (session->mHangupOnLastLeave) + { + mVoiceVersion.mBuildVersion = "p2p"; + } + else + { + mVoiceVersion.mBuildVersion = "ad-hoc"; + } } else if (session->isEstate()) { From ba619ac58c4d52a204445f6386b9339b9ec226d9 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 14 May 2026 23:17:29 +0300 Subject: [PATCH 059/112] #5810 Differentiate startup crashes from runtime crashes --- indra/llcommon/llerror.h | 1 + indra/llwindow/llwindowwin32.cpp | 24 +++++++------- indra/newview/llappviewer.cpp | 57 ++++++++++++++++++++++++++++++-- indra/newview/llappviewer.h | 4 ++- indra/newview/llstartup.cpp | 1 + 5 files changed, 72 insertions(+), 15 deletions(-) diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index 41893a35e56..0083865cf7e 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/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index dd4f6387672..52e0cdd5d56 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -1421,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; } @@ -1432,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; } @@ -1470,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; } @@ -1478,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; } @@ -1691,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; } @@ -1730,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; } @@ -1739,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; } @@ -1761,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; } @@ -1976,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; diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index cb140bc5238..098a345a9c0 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -384,6 +384,7 @@ 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; @@ -690,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 @@ -2290,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; } @@ -4044,6 +4054,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; @@ -4156,6 +4169,7 @@ 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)) { @@ -4226,12 +4240,26 @@ void LLAppViewer::processMarkerFiles() << 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(); @@ -5691,8 +5719,33 @@ void LLAppViewer::removeCloseRequestMarker() const { if (!mSecondInstance) { - std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, CLOSE_EVENT_MARKER_FILE_NAME); - LLFile::remove(error_marker_file, ENOENT); + 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); } } diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 13c4c5f6c0c..b404ec2c03c 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -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 @@ -256,6 +256,8 @@ class LLAppViewer : public LLApp void createCloseRequestMarker() const; void removeCloseRequestMarker() const; + void createInitedMarker() const; + void removeInitedMarker() const; void createWatchdogMarker() const; void removeWatchdogMarker() const; diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 9bdfbaedc8c..fb55f2d2062 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; } From f3247dcc5ee9f320cbd63c4942783cc5a965755f Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 15 May 2026 21:06:43 +0300 Subject: [PATCH 060/112] #5820 Add 'Set UI Size to Default' to help menu on login screen --- indra/newview/skins/default/xui/en/menu_login.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/indra/newview/skins/default/xui/en/menu_login.xml b/indra/newview/skins/default/xui/en/menu_login.xml index de967d25e35..ec6be3e0c66 100644 --- a/indra/newview/skins/default/xui/en/menu_login.xml +++ b/indra/newview/skins/default/xui/en/menu_login.xml @@ -98,7 +98,14 @@ name="Report Bug"> - + + + + Date: Fri, 15 May 2026 21:58:16 +0300 Subject: [PATCH 061/112] #3430 Fix incorectly stated permissions for scripts. --- indra/newview/skins/default/xui/da/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/da/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/de/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/de/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/en/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/en/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/es/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/es/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/fr/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/fr/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/it/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/it/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/ja/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/ja/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/pl/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/pl/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/pt/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/pt/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/ru/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/ru/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/tr/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/tr/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/zh/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/zh/panel_script_ed.xml | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) 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 0cc13fd7361..8d843b4ceb1 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 @@ - 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. 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 3dec4bf101b..bc48e43e3c6 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... - 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. Offentlige objekter kan ikke afvikle scripts 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 ae2dd4db678..308c22fa4b4 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 @@ - 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. Läuft 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 8975cd40f80..90fd7568c35 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... - 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. Öffentliche Objekte können keine Skripts ausführen 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 e30c519c8a6..da654fb6381 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"> - 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. 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 f8761d2b24d..4a7adbacdef 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 @@ - 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. 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 9672f910eae..cda6d040cb5 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 @@ - 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. Ejecutándose 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 89e93046938..ad8631d078a 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... - 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. Los objetos públicos no pueden ejecutar scripts 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 a0d1197ea7e..055265d2b9d 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 @@ - 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. Exécution en cours 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 0a33463cbbd..d54f5e6f2ef 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... - 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. Les objets publics ne peuvent pas exécuter de scripts 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 c28df6c12ed..af6be61bfb4 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 @@ - 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. In esecuzione 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 3cbdadbac82..f0c1c4862bb 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... - 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. Gli oggetti pubblici non possono eseguire gli script 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 770d7b4a13a..0a98e67748d 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 @@ - このスクリプトは「複製不可」のため、表示・編集することができません。オブジェクト内のスクリプトの表示・編集には、全権限が必要です。 + このスクリプトは「コピー不可」または「修正不可」のため、表示・編集することができません。オブジェクト内のスクリプトを表示・編集するには、これらの権限が必要です。 実行中 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 0861b5caf19..22366591644 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 @@ 読み込んでいます… - このスクリプトは「コピー不可」のため、表示・編集することができません。オブジェクト内のスクリプトの表示・編集には、全権限が必要です。 + このスクリプトは「コピー不可」または「修正不可」のため、表示・編集することができません。オブジェクト内のスクリプトを表示・編集するには、これらの権限が必要です。 公共オブジェクトで、スクリプトを実行することはできません。 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 1b3e0d0e745..08642d8a638 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 @@ - 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ń. Włączony 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 828f1c571f2..c7115e8f645 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... - 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ń. Publiczne obiekty nie mogą uruchamiać skryptów 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 635bc2d34b3..8a6f4c61db2 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 @@ - 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. Executando 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 c546adcf8bb..e8dcb794e18 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... - 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. Objetos públicos não podem executar scripts 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 30a196b1cac..cfe30a53334 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 @@ - Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрана категория «не копируемые». Для просмотра или изменения скрипта в объекте нужны полные права доступа. + Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрано «не копировать» или «не изменять». Для просмотра или изменения скрипта в объекте нужны эти права доступа. Выполняется 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 54a1eaab75c..78c4e41be2d 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 @@ Загрузка... - Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрана категория «не копируемые». Для просмотра или изменения скрипта в объекте нужны полные права доступа. + Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрано «не копировать» или «не изменять». Для просмотра или изменения скрипта в объекте нужны эти права доступа. Общедоступные объекты не могут запускать скрипты 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 760948edae7..baea64aa68d 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 @@ - 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. Çalışıyor 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 83040d06e4a..e3c11a94094 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... - 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. Kamuya Açık Nesneler komut dosyalarını çalıştıramaz 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 e971b166f73..8b4eeaa16d0 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 @@ - 你無法察看或編輯這腳本,它被設為「禁止複製」。 你需要完整權限去察看或編輯物件內的腳本。 + 你無法察看或編輯這腳本,因為它被設為「禁止複製」或「禁止修改」。你需要這些權限才能察看或編輯物件內的腳本。 執行中 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 c64982b397a..830165805fa 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 @@ 載入中... - 你無法察看或編輯這腳本,它被設為「禁止複製」。 你需要完整權限去察看或編輯物件內的腳本。 + 你無法察看或編輯這腳本,因為它被設為「禁止複製」或「禁止修改」。你需要這些權限才能察看或編輯物件內的腳本。 公開物件不能執行腳本 From d3e12a53ff947febfaad598183ecb07df717774b Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy Date: Fri, 15 May 2026 19:11:26 +0300 Subject: [PATCH 062/112] #5804 Prevent focus steal when removing unselected conversation widget --- indra/newview/llfloaterimcontainer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index 21f1ed831dd..10c56bdb46f 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -1956,7 +1956,7 @@ bool LLFloaterIMContainer::removeConversationListItem(const LLUUID& uuid, bool c mConversationEventQueue.erase(uuid); // Don't let the focus fall IW, select and refocus on the first conversation in the list - if (change_focus && isInVisibleChain()) + if (change_focus && is_widget_selected && isInVisibleChain()) { setFocus(true); if (new_selection) From a042022fa5c2f31013ec80d93baa5c749741373f Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 18 May 2026 18:53:37 +0300 Subject: [PATCH 063/112] #5785 fix login and world UI overlap --- indra/newview/lllogininstance.cpp | 1 + indra/newview/llstartup.cpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp index 0358233637e..02f013ad3c3 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 uris; LLGridManager::getInstance()->getLoginURIs(uris); mLoginModule->connect(uris.front(), mRequestData); diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index fb55f2d2062..f780e4f5bd5 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -1354,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)); From 447f30781ef7a42a5f72971d713ea716befedf95 Mon Sep 17 00:00:00 2001 From: Gaia Date: Sun, 10 May 2026 00:57:41 +0200 Subject: [PATCH 064/112] Fix GLTF bind matrix remapping --- .gitignore | 3 + indra/newview/gltf/llgltfloader.cpp | 93 ++++++++++++++++++++++++++--- indra/newview/gltf/llgltfloader.h | 2 + 3 files changed, 91 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index c4accf37b5b..62a6b462f93 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/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 4e52c82e866..129c3a3b7a0 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; @@ -1658,6 +1683,61 @@ 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; + auto bind_iter = rotated_bind_matrices.find(gltf_node_index); + if (bind_iter == rotated_bind_matrices.end() || !node_data.mIsOverrideValid) + { + 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 @@ -1910,4 +1990,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 a847e567a62..d8aad798836 100644 --- a/indra/newview/gltf/llgltfloader.h +++ b/indra/newview/gltf/llgltfloader.h @@ -160,6 +160,8 @@ class LLGLTFLoader : public LLModelLoader 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); From b2878acdb0035b0b8a2ad29ccd1ff817a6055709 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Mon, 18 May 2026 23:54:12 +0300 Subject: [PATCH 065/112] #5786 Fix a case of unitialized override matrix --- indra/newview/gltf/llgltfloader.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index 129c3a3b7a0..54ad8c894b1 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -1721,8 +1721,16 @@ glm::mat4 LLGLTFLoader::computeViewerBindMatrix( } 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() || !node_data.mIsOverrideValid) + if (bind_iter == rotated_bind_matrices.end()) { converted_bind_matrices[gltf_node_index] = node_data.mOverrideRestMatrix; return node_data.mOverrideRestMatrix; From d4d94580f50b0aa2d272399d2bbff529c3a37eca Mon Sep 17 00:00:00 2001 From: TommyTheTerrible <81168766+TommyTheTerrible@users.noreply.github.com> Date: Sun, 17 May 2026 11:45:39 -0400 Subject: [PATCH 066/112] Update OpenJPEG to 2.5.4 Latest version of OpenJPEG fixes a header issue that seems to be causing troubles with some older uploaded textures. Bug affected versions 2.5.1, 2.5.2 and 2.5.3. --- autobuild.xml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/autobuild.xml b/autobuild.xml index cc2352bbd24..fde03736bd7 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2019,11 +2019,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 94a72c6ddbfb23796ce913c55bc47c128542a582 + 8dc190451c5b7af0d72e7ab54f8fbde6dccf79c6 hash_algorithm sha1 url - https://github.com/secondlife/3p-openjpeg/releases/download/v2.5.3-r1/openjpeg-2.5.3.15590356935-darwin64-15590356935.tar.zst + https://github.com/secondlife/3p-openjpeg/releases/download/v2.5.4-r1/openjpeg-2.5.4.18754730947-darwin64-18754730947.tar.zst name darwin64 @@ -2033,11 +2033,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 751172af405f4a47a3aebb37729d62229cab6c07 + 9c89879f81ee0434e1f59c47d74a25958ae08e9e hash_algorithm sha1 url - https://github.com/secondlife/3p-openjpeg/releases/download/v2.5.3-r1/openjpeg-2.5.3.15590356935-linux64-15590356935.tar.zst + https://github.com/secondlife/3p-openjpeg/releases/download/v2.5.4-r1/openjpeg-2.5.4.18754730947-linux64-18754730947.tar.zst name linux64 @@ -2047,11 +2047,11 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - 8aab9cf250dfee252386e1c79b5205e6d3b3e19e + b78887212f18ae59dc7961e0e1af781e568bd92c hash_algorithm sha1 url - https://github.com/secondlife/3p-openjpeg/releases/download/v2.5.3-r1/openjpeg-2.5.3.15590356935-windows64-15590356935.tar.zst + https://github.com/secondlife/3p-openjpeg/releases/download/v2.5.4-r1/openjpeg-2.5.4.18754730947-windows64-18754730947.tar.zst name windows64 From df844170600608b37c2725f7a263f1de3b055618 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 19 May 2026 00:50:58 +0300 Subject: [PATCH 067/112] #5818 Fix setCursor crash reshape tried to setCursor before postBuild --- indra/llui/lllineeditor.cpp | 7 ++++--- indra/newview/llpanelprofilepicks.cpp | 5 ++++- indra/newview/llpanelprofilepicks.h | 16 ++++++++-------- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index ef62666918b..0eef842a30e 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -491,12 +491,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/newview/llpanelprofilepicks.cpp b/indra/newview/llpanelprofilepicks.cpp index 2e5b6707f1f..6870d719afe 100644 --- a/indra/newview/llpanelprofilepicks.cpp +++ b/indra/newview/llpanelprofilepicks.cpp @@ -655,7 +655,10 @@ bool LLPanelProfilePick::postBuild() void LLPanelProfilePick::reshape(S32 width, S32 height, bool called_from_parent) { LLPanelProfilePropertiesProcessorTab::reshape(width, height, called_from_parent); - mPickLocation->setCursor(0); + if (mPickLocation) + { + mPickLocation->setCursor(0); + } } void LLPanelProfilePick::onDescriptionFocusReceived() diff --git a/indra/newview/llpanelprofilepicks.h b/indra/newview/llpanelprofilepicks.h index 1a378b470b2..b925f09f92d 100644 --- a/indra/newview/llpanelprofilepicks.h +++ b/indra/newview/llpanelprofilepicks.h @@ -230,14 +230,14 @@ class LLPanelProfilePick protected: - LLTextureCtrl* mSnapshotCtrl; - LLLineEditor* mPickName; - LLTextEditor* mPickDescription; - LLLineEditor* mPickLocation; - 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; From 7a40e2702f7d2a81e674f66f637261a09de5b9ca Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Wed, 20 May 2026 12:45:31 -0400 Subject: [PATCH 068/112] Update llappviewer.cpp --- indra/newview/llappviewer.cpp | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 11e67679806..79752d23150 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -4207,27 +4207,6 @@ void LLAppViewer::processMarkerFiles() } } else - { - // so only check watchdog marker if there is no error marker. - if (LLAPRFile::isExist(watchdog_marker_file, NULL, LL_APR_RB)) - { - if (LAST_EXEC_UNKNOWN == gLastExecEvent - || LAST_EXEC_LOGOUT_UNKNOWN == gLastExecEvent) - { - // 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; - } - } - removeWatchdogMarker(); - } - } - else { if (LAST_EXEC_UNKNOWN == gLastExecEvent || LAST_EXEC_LOGOUT_UNKNOWN == gLastExecEvent) From a1d80779bf5aee5b7d2de5e924cfa60804f1e180 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 19 May 2026 22:17:00 +0300 Subject: [PATCH 069/112] #4823 WebRTC crashes --- indra/llwebrtc/llwebrtc.cpp | 87 +++++++++++++++++---------------- indra/newview/llvoicewebrtc.cpp | 32 ++++++++++-- 2 files changed, 74 insertions(+), 45 deletions(-) diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp index a286f75f424..f4ecce63a6c 100644 --- a/indra/llwebrtc/llwebrtc.cpp +++ b/indra/llwebrtc/llwebrtc.cpp @@ -841,22 +841,23 @@ void LLWebRTCPeerConnectionImpl::init(LLWebRTCImpl * webrtc_impl) void LLWebRTCPeerConnectionImpl::terminate() { mPendingJobs++; + webrtc::scoped_refptr self(this); mWebRTCImpl->PostSignalingTask( - [this]() + [self]() { - if (mPeerConnection) + if (self->mPeerConnection) { - if (mDataChannel) + if (self->mDataChannel) { { - mDataChannel->Close(); - mDataChannel = nullptr; + self->mDataChannel->Close(); + self->mDataChannel = nullptr; } } // to remove 'Secondlife is recording' icon from taskbar // if user was speaking - auto senders = mPeerConnection->GetSenders(); + auto senders = self->mPeerConnection->GetSenders(); for (auto& sender : senders) { auto track = sender->track(); @@ -866,24 +867,24 @@ void LLWebRTCPeerConnectionImpl::terminate() } } - mPeerConnection->Close(); - if (mLocalStream) + self->mPeerConnection->Close(); + if (self->mLocalStream) { - auto tracks = mLocalStream->GetAudioTracks(); + auto tracks = self->mLocalStream->GetAudioTracks(); for (auto& track : tracks) { - mLocalStream->RemoveTrack(track); + self->mLocalStream->RemoveTrack(track); } - mLocalStream = nullptr; + self->mLocalStream = nullptr; } - mPeerConnection = nullptr; + self->mPeerConnection = nullptr; - for (auto &observer : mSignalingObserverList) + for (auto &observer : self->mSignalingObserverList) { observer->OnPeerConnectionClosed(); } } - mPendingJobs--; + self->mPendingJobs--; }); } @@ -906,8 +907,9 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti mAnswerReceived = false; mPendingJobs++; + webrtc::scoped_refptr self(this); mWebRTCImpl->PostSignalingTask( - [this,options]() + [self,options]() { webrtc::PeerConnectionInterface::RTCConfiguration config; for (auto server : options.mServers) @@ -926,42 +928,42 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti config.set_min_port(60000); config.set_max_port(60100); - webrtc::PeerConnectionDependencies pc_dependencies(this); + webrtc::PeerConnectionDependencies pc_dependencies(self.get()); // Other thread manages mPeerConnectionFactory's lifetime and it can be reset // at any momment, create own scoped_refptr (atomic). - webrtc::scoped_refptr peer_connection_factory = mPeerConnectionFactory; + webrtc::scoped_refptr peer_connection_factory = self->mPeerConnectionFactory; if (peer_connection_factory == nullptr) { RTC_LOG(LS_ERROR) << __FUNCTION__ << "Error creating peer connection, factory doesn't exist"; // Too early? - mPendingJobs--; + self->mPendingJobs--; return; } auto error_or_peer_connection = peer_connection_factory->CreatePeerConnectionOrError(config, std::move(pc_dependencies)); if (error_or_peer_connection.ok()) { - mPeerConnection = std::move(error_or_peer_connection.value()); + self->mPeerConnection = std::move(error_or_peer_connection.value()); } else { RTC_LOG(LS_ERROR) << __FUNCTION__ << "Error creating peer connection: " << error_or_peer_connection.error().message(); - for (auto &observer : mSignalingObserverList) + for (auto &observer : self->mSignalingObserverList) { observer->OnRenegotiationNeeded(); } - mPendingJobs--; + self->mPendingJobs--; return; } webrtc::DataChannelInit init; init.ordered = true; - auto data_channel_or_error = mPeerConnection->CreateDataChannelOrError("SLData", &init); + auto data_channel_or_error = self->mPeerConnection->CreateDataChannelOrError("SLData", &init); if (data_channel_or_error.ok()) { - mDataChannel = std::move(data_channel_or_error.value()); + self->mDataChannel = std::move(data_channel_or_error.value()); - mDataChannel->RegisterObserver(this); + self->mDataChannel->RegisterObserver(self.get()); } webrtc::AudioOptions audioOptions; @@ -970,16 +972,16 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti audioOptions.noise_suppression = true; audioOptions.init_recording_on_send = false; - mLocalStream = peer_connection_factory->CreateLocalMediaStream("SLStream"); + self->mLocalStream = peer_connection_factory->CreateLocalMediaStream("SLStream"); webrtc::scoped_refptr audio_track( peer_connection_factory->CreateAudioTrack("SLAudio", peer_connection_factory->CreateAudioSource(audioOptions).get())); audio_track->set_enabled(false); - mLocalStream->AddTrack(audio_track); + self->mLocalStream->AddTrack(audio_track); - mPeerConnection->AddTrack(audio_track, {"SLStream"}); + self->mPeerConnection->AddTrack(audio_track, {"SLStream"}); - auto senders = mPeerConnection->GetSenders(); + auto senders = self->mPeerConnection->GetSenders(); for (auto &sender : senders) { @@ -995,7 +997,7 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti sender->SetParameters(params); } - auto receivers = mPeerConnection->GetReceivers(); + auto receivers = self->mPeerConnection->GetReceivers(); for (auto &receiver : receivers) { webrtc::RtpParameters params; @@ -1011,9 +1013,9 @@ bool LLWebRTCPeerConnectionImpl::initializeConnection(const LLWebRTCPeerConnecti } webrtc::PeerConnectionInterface::RTCOfferAnswerOptions offerOptions; - this->AddRef(); // CreateOffer will deref this when it's done. Without this, the callbacks never get called. - mPeerConnection->CreateOffer(this, offerOptions); - mPendingJobs--; + self->AddRef(); // CreateOffer will deref this when it's done. Without this, the callbacks never get called. + self->mPeerConnection->CreateOffer(self.get(), offerOptions); + self->mPendingJobs--; }); return true; @@ -1090,14 +1092,15 @@ void LLWebRTCPeerConnectionImpl::setMute(bool mute) mPendingJobs++; + webrtc::scoped_refptr self(this); mWebRTCImpl->PostSignalingTask( - [this, force_reset, enable]() + [self, force_reset, enable]() { - if (mPeerConnection) + if (self->mPeerConnection) { - auto senders = mPeerConnection->GetSenders(); + auto senders = self->mPeerConnection->GetSenders(); - RTC_LOG(LS_INFO) << __FUNCTION__ << (mMute ? "disabling" : "enabling") << " streams count " << senders.size(); + RTC_LOG(LS_INFO) << __FUNCTION__ << (self->mMute ? "disabling" : "enabling") << " streams count " << senders.size(); for (auto &sender : senders) { auto track = sender->track(); @@ -1113,7 +1116,7 @@ void LLWebRTCPeerConnectionImpl::setMute(bool mute) track->set_enabled(enable); } } - mPendingJobs--; + self->mPendingJobs--; } }); } @@ -1253,12 +1256,14 @@ void LLWebRTCPeerConnectionImpl::OnConnectionChange(webrtc::PeerConnectionInterf case webrtc::PeerConnectionInterface::PeerConnectionState::kConnected: { mPendingJobs++; - mWebRTCImpl->PostWorkerTask([this]() { - for (auto &observer : mSignalingObserverList) + webrtc::scoped_refptr self(this); + mWebRTCImpl->PostWorkerTask([self]() + { + for (auto &observer : self->mSignalingObserverList) { - observer->OnAudioEstablished(this); + observer->OnAudioEstablished(self.get()); } - mPendingJobs--; + self->mPendingJobs--; }); break; } diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index 3b98ca49ef0..30803cdc7e5 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -1678,6 +1678,10 @@ void LLWebRTCVoiceClient::setVoiceVolume(F32 volume) void LLWebRTCVoiceClient::predSetSpeakerVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 volume) { + if (session->mShuttingDown) + { + return; + } session->setSpeakerVolume(volume); } @@ -1906,6 +1910,10 @@ LLWebRTCVoiceClient::sessionState::sessionState() : void LLWebRTCVoiceClient::predUpdateOwnVolume(const LLWebRTCVoiceClient::sessionStatePtr_t &session, F32 audio_level) { + if (session->mShuttingDown) + { + return; + } participantStatePtr_t participant = session->findParticipantByID(gAgentID); if (participant) { @@ -1934,9 +1942,16 @@ void LLWebRTCVoiceClient::sessionState::sendData(const std::string &data) void LLWebRTCVoiceClient::sessionState::setMuteMic(bool muted) { mMuted = muted; + if (mShuttingDown) + { + return; + } for (auto &connection : mWebRTCConnections) { - connection->setMuteMic(muted); + if (!connection->isShuttingDown()) + { + connection->setMuteMic(muted); + } } } @@ -1945,7 +1960,10 @@ void LLWebRTCVoiceClient::sessionState::setSpeakerVolume(F32 volume) mSpeakerVolume = volume; for (auto &connection : mWebRTCConnections) { - connection->setSpeakerVolume(volume); + if (!connection->isShuttingDown()) + { + connection->setSpeakerVolume(volume); + } } } @@ -1957,7 +1975,10 @@ void LLWebRTCVoiceClient::sessionState::setUserVolume(const LLUUID &id, F32 volu } for (auto &connection : mWebRTCConnections) { - connection->setUserVolume(id, volume); + if (!connection->isShuttingDown()) + { + connection->setUserVolume(id, volume); + } } } @@ -1969,7 +1990,10 @@ void LLWebRTCVoiceClient::sessionState::setUserMute(const LLUUID &id, bool mute) } for (auto &connection : mWebRTCConnections) { - connection->setUserMute(id, mute); + if (!connection->isShuttingDown()) + { + connection->setUserMute(id, mute); + } } } /*static*/ From 9a8aacf05583af57cb007cd049d5562157f4ae90 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Thu, 21 May 2026 13:23:39 +0300 Subject: [PATCH 070/112] #5794 add Voice tab and update Sound & Media tab in Preferences --- indra/newview/llpanelvoicedevicesettings.cpp | 2 +- .../default/xui/en/floater_preferences.xml | 6 + .../xui/en/panel_preferences_sound.xml | 175 +------------- .../xui/en/panel_preferences_voice.xml | 222 ++++++++++++++++++ .../default/xui/en/panel_sound_devices.xml | 36 +-- 5 files changed, 243 insertions(+), 198 deletions(-) create mode 100644 indra/newview/skins/default/xui/en/panel_preferences_voice.xml diff --git a/indra/newview/llpanelvoicedevicesettings.cpp b/indra/newview/llpanelvoicedevicesettings.cpp index d8d6bcf5fd5..0e6b61f3168 100644 --- a/indra/newview/llpanelvoicedevicesettings.cpp +++ b/indra/newview/llpanelvoicedevicesettings.cpp @@ -149,7 +149,7 @@ void LLPanelVoiceDeviceSettings::draw() LLColor4 color; if (power_bar_idx < discrete_power) { - color = (power_bar_idx >= 3) ? LLUIColorTable::instance().getColor("OverdrivenColor") : LLUIColorTable::instance().getColor("SpeakingColor"); + color = (power_bar_idx >= 3) ? LLUIColorTable::instance().getColor("OverdrivenColor") : LLUIColorTable::instance().getColor("OutfitGalleryItemSelected"); } else { diff --git a/indra/newview/skins/default/xui/en/floater_preferences.xml b/indra/newview/skins/default/xui/en/floater_preferences.xml index b7d992bcb44..b2cfd40d757 100644 --- a/indra/newview/skins/default/xui/en/floater_preferences.xml +++ b/indra/newview/skins/default/xui/en/floater_preferences.xml @@ -107,6 +107,12 @@ layout="topleft" help_topic="preferences_audio_tab" name="audio" /> + @@ -379,6 +380,7 @@ layout="topleft" height="15" left="23" + top_delta="32" width="180" name="media_firstinteract_label" halign="right"> @@ -435,11 +437,12 @@ enabled_control="AudioStreamingMedia" value="true" follows="left|top" + layout="topleft" tool_tip="Uncheck this to hide media attached to other avatars nearby" label="Play media attached to other avatars" left="23" width="15" - top_delta="30" + top_delta="35" height="15"/> - - Microphone Noise Suppression - - - - - - - - - - Hear voice from - - - - - - - - - - - - - diff --git a/indra/newview/skins/default/xui/en/panel_preferences_voice.xml b/indra/newview/skins/default/xui/en/panel_preferences_voice.xml new file mode 100644 index 00000000000..6d8e39e03cf --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_preferences_voice.xml @@ -0,0 +1,222 @@ + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/panel_sound_devices.xml b/indra/newview/skins/default/xui/en/panel_sound_devices.xml index b858d0ed021..72fdf20ecaa 100644 --- a/indra/newview/skins/default/xui/en/panel_sound_devices.xml +++ b/indra/newview/skins/default/xui/en/panel_sound_devices.xml @@ -24,28 +24,6 @@ name="device_not_loaded"> Device not loaded - - Input @@ -64,7 +42,7 @@ control_name="VoiceInputAudioDevice" follows="left|top" layout="topleft" - left_pad="0" + left_pad="25" max_chars="128" name="voice_input_device" top_delta="-5" @@ -76,9 +54,9 @@ follows="left|top" height="15" layout="topleft" - left_pad="30" name="Output" - top_delta="5" + top_delta="35" + left="18" width="60"> Output @@ -87,7 +65,7 @@ height="23" follows="left|top" layout="topleft" - left_pad="0" + left_pad="25" max_chars="128" name="voice_output_device" top_delta="-4" @@ -99,7 +77,7 @@ follows="left|top" height="16" layout="topleft" - left_delta="-300" + left="18" name="My volume label" top_pad="14" width="200"> From e88380e464d7a0275556f99e38bd0f67358bcd4e Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 21 May 2026 22:50:34 +0300 Subject: [PATCH 071/112] #5858 Crash at LLFontFreetype::renderGlyph --- indra/llrender/llfontfreetype.cpp | 45 ++++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/indra/llrender/llfontfreetype.cpp b/indra/llrender/llfontfreetype.cpp index 075c496ec54..3c5682dfc9b 100644 --- a/indra/llrender/llfontfreetype.cpp +++ b/indra/llrender/llfontfreetype.cpp @@ -720,7 +720,50 @@ void LLFontFreetype::renderGlyph(EFontGlyphType bitmap_type, U32 glyph_index, ll llassert_always_msg(FT_Err_Ok == error, message.c_str()); } - llassert_always(! FT_Render_Glyph(mFTFace->glyph, gFontRenderMode) ); + // TODO: Make this more sturdy, make asserts/ll_errs conditional + // to non-critical characters. + // Temporarily leaving them for data gathering, but unicode chars + // like emojis should not cause the app to crash and should either + // fallback to some predetermined bitmap or simply return. + + // Verify glyph slot is valid + if (!mFTFace->glyph) + { + LL_ERRS() << "FT_Load_Glyph succeeded but glyph slot is null for wchar " << llformat("U+%xu", U32(wch)) << LL_ENDL; + return; + } + + // Check if bitmap buffer is already allocated + // It can potentially be preallocated for: + // 1. SVG/color glyphs rendered by FreeType's SVG_RendererHooks + // 2. Embedded bitmap fonts + // 3. Some Color emoji that use FT_LOAD_COLOR + if (!mFTFace->glyph->bitmap.buffer) + { + error = FT_Render_Glyph(mFTFace->glyph, gFontRenderMode); + if (error != FT_Err_Ok) + { + std::string render_message = llformat( + "Error %d (%s) rendering wchar %u glyph %u: format=%lu, pixel_mode=%d, render_mode=%d", + error, FT_Error_String(error), wch, glyph_index, + (unsigned long)mFTFace->glyph->format, mFTFace->glyph->bitmap.pixel_mode, gFontRenderMode); + + // Try with FT_RENDER_MODE_NORMAL as fallback + if (gFontRenderMode != FT_RENDER_MODE_NORMAL) + { + LL_WARNS_ONCE() << render_message << LL_ENDL; + error = FT_Render_Glyph(mFTFace->glyph, FT_RENDER_MODE_NORMAL); + if (error != FT_Err_Ok) + { + LL_ERRS() << "Fallback to FT_RENDER_MODE_NORMAL failed. " << render_message << LL_ENDL; + } + } + else + { + LL_ERRS() << render_message << LL_ENDL; + } + } + } mRenderGlyphCount++; } From 1357773bd47fc727a5ef055a0eff1c5ed21e124d Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 21 May 2026 22:54:52 +0300 Subject: [PATCH 072/112] #5859 Fix a missed null category check --- indra/newview/llaisapi.cpp | 62 +++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp index f67f2688a16..1c5530916f3 100644 --- a/indra/newview/llaisapi.cpp +++ b/indra/newview/llaisapi.cpp @@ -1758,35 +1758,41 @@ void AISUpdate::doUpdate() const LLUUID id = ucv_it->first; S32 version = static_cast(ucv_it->second); LLViewerInventoryCategory *cat = gInventory.getCategory(id); - LL_DEBUGS("Inventory") << "cat version update " << cat->getName() << " to version " << cat->getVersion() << LL_ENDL; - if (cat->getVersion() != version) - { - // the AIS version should be considered the true version. Adjust - // our local category model to reflect this version number. Otherwise - // it becomes possible to get stuck with the viewer being out of - // sync with the inventory system. Under normal circumstances - // inventory COF is maintained on the viewer through calls to - // LLInventoryModel::accountForUpdate when a changing operation - // is performed. This occasionally gets out of sync however. - if (version != LLViewerInventoryCategory::VERSION_UNKNOWN) - { - LL_WARNS() << "Possible version mismatch for category " << cat->getName() - << ", viewer version " << cat->getVersion() - << " AIS version " << version << " !!!Adjusting local version!!!" << LL_ENDL; - cat->setVersion(version); - } - else + // Update can be rather large and take time to process. + // By the time update gets to the category, it could + // could have been removed by the user + if (cat) + { + LL_DEBUGS("Inventory") << "cat " << cat->getName() << " version update from " << cat->getVersion() << " to AIS version " << version << LL_ENDL; + if (cat->getVersion() != version) { - // We do not account for update if version is UNKNOWN, so we shouldn't rise version - // either or viewer will get stuck on descendants count -1, try to refetch folder instead - // - // Todo: proper backoff? - - LL_WARNS() << "Possible version mismatch for category " << cat->getName() - << ", viewer version " << cat->getVersion() - << " AIS version " << version << " !!!Rerequesting category!!!" << LL_ENDL; - const S32 LONG_EXPIRY = 360; - cat->fetch(LONG_EXPIRY); + // the AIS version should be considered the true version. Adjust + // our local category model to reflect this version number. Otherwise + // it becomes possible to get stuck with the viewer being out of + // sync with the inventory system. Under normal circumstances + // inventory COF is maintained on the viewer through calls to + // LLInventoryModel::accountForUpdate when a changing operation + // is performed. This occasionally gets out of sync however. + if (version != LLViewerInventoryCategory::VERSION_UNKNOWN) + { + LL_WARNS() << "Possible version mismatch for category " << cat->getName() + << ", viewer version " << cat->getVersion() + << " AIS version " << version << " !!!Adjusting local version!!!" << LL_ENDL; + cat->setVersion(version); + } + else + { + // We do not account for update if version is UNKNOWN, so we shouldn't riase version + // either or viewer will get stuck on descendants count -1, try to refetch folder instead + // + // Todo: proper backoff? + + LL_WARNS() << "Possible version mismatch for category " << cat->getName() + << ", viewer version " << cat->getVersion() + << " AIS version " << version << " !!!Rerequesting category!!!" << LL_ENDL; + const S32 LONG_EXPIRY = 360; + cat->fetch(LONG_EXPIRY); + } } } } From 71fa58ca4eedd26069211f8c42bcec6fd3beb919 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 20 May 2026 22:57:30 +0300 Subject: [PATCH 073/112] #5809 Fix wording --- .../skins/default/xui/en/menu_conversation_log_view.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/newview/skins/default/xui/en/menu_conversation_log_view.xml b/indra/newview/skins/default/xui/en/menu_conversation_log_view.xml index 1e10a984daf..38d7f094457 100644 --- a/indra/newview/skins/default/xui/en/menu_conversation_log_view.xml +++ b/indra/newview/skins/default/xui/en/menu_conversation_log_view.xml @@ -35,8 +35,8 @@ parameter="sort_friends_on_top" /> + label="Show blocked" + name="show_blocked"> From 44b8fa43033eda21aaa8aa2014958effc233e044 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 20 May 2026 23:14:53 +0300 Subject: [PATCH 074/112] #5845 Crash in LLFloaterInspect on shutdown --- indra/newview/llfloaterinspect.cpp | 21 ++++++++++++--------- indra/newview/llmanipscale.cpp | 6 ++++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/indra/newview/llfloaterinspect.cpp b/indra/newview/llfloaterinspect.cpp index 163edf04269..77a98943aca 100644 --- a/indra/newview/llfloaterinspect.cpp +++ b/indra/newview/llfloaterinspect.cpp @@ -78,18 +78,21 @@ LLFloaterInspect::~LLFloaterInspect(void) { mCreatorNameCacheConnection.disconnect(); } - if(!LLFloaterReg::instanceVisible("build")) + if (!LLApp::isExiting()) { - if(LLToolMgr::getInstance()->getBaseTool() == LLToolCompInspect::getInstance()) + if (!LLFloaterReg::instanceVisible("build")) { - LLToolMgr::getInstance()->clearTransientTool(); + if (LLToolMgr::getInstance()->getBaseTool() == LLToolCompInspect::getInstance()) + { + LLToolMgr::getInstance()->clearTransientTool(); + } + // Switch back to basic toolset + LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); + } + else + { + LLFloaterReg::showInstance("build", LLSD(), true); } - // Switch back to basic toolset - LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); - } - else - { - LLFloaterReg::showInstance("build", LLSD(), true); } } diff --git a/indra/newview/llmanipscale.cpp b/indra/newview/llmanipscale.cpp index 66420d1cada..87b79fd18cb 100644 --- a/indra/newview/llmanipscale.cpp +++ b/indra/newview/llmanipscale.cpp @@ -1309,6 +1309,12 @@ void LLManipScale::updateSnapGuides(const LLBBox& bbox) LLVector3 grid_scale; LLQuaternion grid_rotation; LLSelectMgr::getInstance()->getGrid(grid_origin, grid_rotation, grid_scale); + LLViewerCamera* camera = LLViewerCamera::getInstance(); + if (!camera) + { + // can be null on shutdown + return; + } bool uniform = LLManipScale::getUniform(); From ec6fd95582a488dc156b0baa1487fc05f7f616f6 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 20 May 2026 23:30:07 +0300 Subject: [PATCH 075/112] #5846 Fix crash on pushBatch --- indra/newview/lldrawpool.cpp | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp index e60b3eb5dc5..cc702e8f564 100644 --- a/indra/newview/lldrawpool.cpp +++ b/indra/newview/lldrawpool.cpp @@ -440,7 +440,11 @@ void LLRenderPass::pushBatches(U32 type, bool texture, bool batch_textures) LLDrawInfo* pparams = *i; LLCullResult::increment_iterator(i, end); - pushBatch(*pparams, texture, batch_textures); + llassert(pparams); // figure out how null got here, it shouldn't be happening + if (pparams) + { + pushBatch(*pparams, texture, batch_textures); + } } } else @@ -459,7 +463,10 @@ void LLRenderPass::pushUntexturedBatches(U32 type) LLDrawInfo* pparams = *i; LLCullResult::increment_iterator(i, end); - pushUntexturedBatch(*pparams); + if (pparams) + { + pushUntexturedBatch(*pparams); + } } } @@ -479,7 +486,7 @@ void LLRenderPass::pushRiggedBatches(U32 type, bool texture, bool batch_textures LLDrawInfo* pparams = *i; LLCullResult::increment_iterator(i, end); - if (uploadMatrixPalette(pparams->mAvatar, pparams->mSkinInfo, lastAvatar, lastMeshId, skipLastSkin)) + if (pparams && uploadMatrixPalette(pparams->mAvatar, pparams->mSkinInfo, lastAvatar, lastMeshId, skipLastSkin)) { pushBatch(*pparams, texture, batch_textures); } @@ -504,7 +511,7 @@ void LLRenderPass::pushUntexturedRiggedBatches(U32 type) LLDrawInfo* pparams = *i; LLCullResult::increment_iterator(i, end); - if (uploadMatrixPalette(pparams->mAvatar, pparams->mSkinInfo, lastAvatar, lastMeshId, skipLastSkin)) + if (pparams && uploadMatrixPalette(pparams->mAvatar, pparams->mSkinInfo, lastAvatar, lastMeshId, skipLastSkin)) { pushUntexturedBatch(*pparams); } @@ -520,8 +527,11 @@ void LLRenderPass::pushMaskBatches(U32 type, bool texture, bool batch_textures) { LLDrawInfo* pparams = *i; LLCullResult::increment_iterator(i, end); - LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff); - pushBatch(*pparams, texture, batch_textures); + if (pparams) + { + LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff); + pushBatch(*pparams, texture, batch_textures); + } } } @@ -539,13 +549,16 @@ void LLRenderPass::pushRiggedMaskBatches(U32 type, bool texture, bool batch_text LLCullResult::increment_iterator(i, end); - llassert(pparams); - - LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff); + llassert(pparams); // figure out how null got here, it shouldn't be happening - if (uploadMatrixPalette(pparams->mAvatar, pparams->mSkinInfo, lastAvatar, lastMeshId, skipLastSkin)) + if (pparams) { - pushBatch(*pparams, texture, batch_textures); + LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff); + + if (uploadMatrixPalette(pparams->mAvatar, pparams->mSkinInfo, lastAvatar, lastMeshId, skipLastSkin)) + { + pushBatch(*pparams, texture, batch_textures); + } } } } From e63651672fe66fe2c60bfb6c823fe78ed6f1b28d Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 22 May 2026 19:20:42 +0300 Subject: [PATCH 076/112] #3430 Strip the 'inside an object' from message as it is not object specific --- indra/newview/skins/default/xui/da/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/da/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/de/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/de/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/en/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/en/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/es/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/es/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/fr/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/fr/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/it/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/it/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/ja/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/ja/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/pl/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/pl/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/pt/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/pt/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/ru/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/ru/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/tr/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/tr/panel_script_ed.xml | 2 +- indra/newview/skins/default/xui/zh/floater_live_lsleditor.xml | 2 +- indra/newview/skins/default/xui/zh/panel_script_ed.xml | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) 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 8d843b4ceb1..c6a78fff4e7 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 @@ - 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. + 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. 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 bc48e43e3c6..ae073300b1e 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... - 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. + 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. Offentlige objekter kan ikke afvikle scripts 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 308c22fa4b4..5b44c44b289 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 @@ - 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. + Dieses Skript kann nicht angezeigt oder bearbeitet werden, da als Berechtigung „kein kopieren" oder „keine Änderungen" festgelegt wurde. Um ein Skript anzuzeigen oder zu bearbeiten, benötigen Sie diese Berechtigungen. Läuft 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 90fd7568c35..f4ecfd23d60 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... - 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. + Dieses Skript kann nicht angezeigt oder bearbeitet werden, da als Berechtigung „kein kopieren" oder „keine Änderungen" festgelegt wurde. Um ein Skript anzuzeigen oder zu bearbeiten, benötigen Sie diese Berechtigungen. Öffentliche Objekte können keine Skripts ausführen 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 da654fb6381..599590972b3 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"> - 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. + 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. 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 4a7adbacdef..ef84a25a502 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 @@ - 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. + 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. 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 cda6d040cb5..9686dc8c2b3 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 @@ - 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. + 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. Ejecutándose 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 ad8631d078a..e19580533bf 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... - 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. + 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. Los objetos públicos no pueden ejecutar scripts 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 055265d2b9d..97644a51136 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 @@ - 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. + 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, vous devez avoir ces permissions. Exécution en cours 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 d54f5e6f2ef..f6bc55c189e 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... - 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. + 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, vous devez avoir ces permissions. Les objets publics ne peuvent pas exécuter de scripts 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 af6be61bfb4..84f668332e3 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 @@ - 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. + Questo script non può essere visualizzato né modificato, perché è impostato come "non copiabile" o "non modificabile". Per visualizzare o modificare uno script, devi avere questi permessi. In esecuzione 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 f0c1c4862bb..72e9b45321b 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... - 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. + Questo script non può essere visualizzato né modificato, perché è impostato come "non copiabile" o "non modificabile". Per visualizzare o modificare uno script, devi avere questi permessi. Gli oggetti pubblici non possono eseguire gli script 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 0a98e67748d..0813922635e 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 @@ - このスクリプトは「コピー不可」または「修正不可」のため、表示・編集することができません。オブジェクト内のスクリプトを表示・編集するには、これらの権限が必要です。 + このスクリプトは「コピー不可」または「修正不可」のため、表示・編集することができません。スクリプトを表示・編集するには、これらの権限が必要です。 実行中 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 22366591644..5cf04c75376 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 @@ 読み込んでいます… - このスクリプトは「コピー不可」または「修正不可」のため、表示・編集することができません。オブジェクト内のスクリプトを表示・編集するには、これらの権限が必要です。 + このスクリプトは「コピー不可」または「修正不可」のため、表示・編集することができません。スクリプトを表示・編集するには、これらの権限が必要です。 公共オブジェクトで、スクリプトを実行することはできません。 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 08642d8a638..0c59d90468d 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 @@ - 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ń. + Nie możesz zobaczyć ani edytować tego skryptu, ponieważ ustawiono dla niego "brak kopiowania" lub "brak modyfikacji". Aby zobaczyć lub edytować skrypt, potrzebujesz tych uprawnień. Włączony 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 c7115e8f645..01b88613a07 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... - 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ń. + Nie możesz zobaczyć ani edytować tego skryptu, ponieważ ustawiono dla niego "brak kopiowania" lub "brak modyfikacji". Aby zobaczyć lub edytować skrypt, potrzebujesz tych uprawnień. Publiczne obiekty nie mogą uruchamiać skryptów 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 8a6f4c61db2..a55dd1b406e 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 @@ - 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. + 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. Executando 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 e8dcb794e18..8d33035d02f 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... - 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. + 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. Objetos públicos não podem executar scripts 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 cfe30a53334..65dd67c78a2 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 @@ - Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрано «не копировать» или «не изменять». Для просмотра или изменения скрипта в объекте нужны эти права доступа. + Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрано «не копировать» или «не изменять». Для просмотра или изменения скрипта нужны эти права доступа. Выполняется 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 78c4e41be2d..6322917e8e8 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 @@ Загрузка... - Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрано «не копировать» или «не изменять». Для просмотра или изменения скрипта в объекте нужны эти права доступа. + Вы не можете просматривать и изменять этот скрипт, поскольку для него выбрано «не копировать» или «не изменять». Для просмотра или изменения скрипта нужны эти права доступа. Общедоступные объекты не могут запускать скрипты 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 baea64aa68d..c53e12fc0e5 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 @@ - 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. + Bu komut dosyasını görüntüleyemez veya düzenleyemezsiniz, çünkü "kopyalanamaz" veya "değiştirilemez" olarak ayarlanmıştır. Bir komut dosyasını görüntülemek veya düzenlemek için bu izinlere ihtiyacınız var. Çalışıyor 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 e3c11a94094..e037a79e99a 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... - 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. + Bu komut dosyasını görüntüleyemez veya düzenleyemezsiniz, çünkü "kopyalanamaz" veya "değiştirilemez" olarak ayarlanmıştır. Bir komut dosyasını görüntülemek veya düzenlemek için bu izinlere ihtiyacınız var. Kamuya Açık Nesneler komut dosyalarını çalıştıramaz 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 8b4eeaa16d0..ed7c127a6c9 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 @@ - 你無法察看或編輯這腳本,因為它被設為「禁止複製」或「禁止修改」。你需要這些權限才能察看或編輯物件內的腳本。 + 你無法察看或編輯這腳本,因為它被設為「禁止複製」或「禁止修改」。你需要這些權限才能察看或編輯腳本。 執行中 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 830165805fa..a7f071806cc 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 @@ 載入中... - 你無法察看或編輯這腳本,因為它被設為「禁止複製」或「禁止修改」。你需要這些權限才能察看或編輯物件內的腳本。 + 你無法察看或編輯這腳本,因為它被設為「禁止複製」或「禁止修改」。你需要這些權限才能察看或編輯腳本。 公開物件不能執行腳本 From f99e2233a40e3a7b88e6c54aa89a1bc0bcd76456 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 27 May 2026 09:33:09 -0700 Subject: [PATCH 077/112] Log failed delivery of crucial vewer-->simulator connection UDP messages (#5568) --- indra/newview/llstartup.cpp | 157 ++++++++++++++++++++++++------ indra/newview/llviewermessage.cpp | 21 +++- indra/newview/llviewerregion.cpp | 21 +++- 3 files changed, 165 insertions(+), 34 deletions(-) diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index b3ed65be822..6a85ca069a6 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -24,6 +24,72 @@ * $/LicenseInfo$ */ +// +// LOGIN AND CONNECTION SEQUENCE OVERVIEW +// ====================================== +// The Viewer connects to the SL service in two phases: HTTP authentication +// followed by UDP "Circuit" establishment to the first Simulator. +// +// PHASE 1: HTTP LOGIN (see lllogin.cpp, process_login_success_response()) +// ----------------------------------------------------------------------- +// Viewer sends an XMLRPC HTTP POST to LoginServer containing: +// - Credentials (first name, last name, password) +// - Client version, channel, MAC address, machine ID +// - Start location preferences +// +// Login-server responds with critical connection data: +// - agent_id, session_id, secure_session_id (authentication tokens) +// - Circuit_code (used to establish UDP Circuit with Simulator) +// - sim_ip, sim_port (Simulator address to connect to) +// - seed_capability (base URL for HTTP capability requests) +// - region_x, region_y (region grid coordinates) +// +// PHASE 2: UDP CIRCUIT ESTABLISHMENT (see idle_startup() state machine below) +// --------------------------------------------------------------------------- +// After HTTP login succeeds, Viewer establishes a UDP Circuit with the +// simulator. This also happens whenever the Viewer connects to new Simulators +// in the same session. The following UDP messages are exchanged: +// +// 1. UseCircuitCode (Viewer -> Simulator) +// - Sent in STATE_WORLD_INIT +// - Contains: Circuit_code, session_id, agent_id +// - Establishes the UDP Circuit with the Simulator +// +// 2. RegionHandshake (Simulator -> Viewer) +// - Handled by process_region_handshake() in llworld.cpp +// - Contains: region name, terrain textures, water height, region flags +// - Viewer responds with RegionHandshakeReply +// +// 3. CompleteAgentMovement (Viewer -> Simulator) +// - Sent in STATE_AGENT_SEND via send_complete_agent_movement() +// - Signals the Viewer is ready to enter the world +// +// 4. AgentMovementComplete (Simulator -> Viewer) +// - Handled by process_agent_movement_complete() in llviewermessage.cpp +// - Contains: final agent position, look_at direction, region handle +// - Sets gAgentMovementCompleted = true +// - Agent is now fully connected to the region +// +// STARTUP STATE MACHINE +// --------------------- +// The connection sequence is managed by idle_startup() which progresses +// through these key states: +// +// STATE_LOGIN_WAIT - Waiting for HTTP login response +// STATE_LOGIN_PROCESS_RESPONSE - Processing login response data +// STATE_WORLD_INIT - Send UseCircuitCode, enable UDP Circuit +// STATE_WORLD_WAIT - Wait for Circuit acknowledgment +// STATE_AGENT_SEND - Send CompleteAgentMovement +// STATE_AGENT_WAIT - Wait for AgentMovementComplete +// STATE_INVENTORY_SEND - Agent connected, begin loading inventory +// +// HTTP CAPABILITIES +// ----------------- +// After UDP connection, Viewer fetches "capability" URLs from the +// seed_capability endpoint. These provide HTTP endpoints for various +// services (inventory, textures, etc.) that supplement the UDP protocol. +// + #include "llviewerprecompiledheaders.h" #include "llappviewer.h" @@ -277,7 +343,6 @@ void show_first_run_dialog(); bool first_run_dialog_callback(const LLSD& notification, const LLSD& response); void set_startup_status(const F32 frac, const std::string& string, const std::string& msg); bool login_alert_status(const LLSD& notification, const LLSD& response); -void use_circuit_callback(void**, S32 result); void register_viewer_callbacks(LLMessageSystem* msg); void asset_callback_nothing(const LLUUID&, LLAssetType::EType, void*, S32); bool callback_choose_gender(const LLSD& notification, const LLSD& response); @@ -1719,13 +1784,48 @@ bool idle_startup() gUseCircuitCallbackCalled = false; msg->enableCircuit(gFirstSim, true); - // now, use the circuit info to tell simulator about us! + + // UDP CONNECTION STEP 1: Send UseCircuitCode + // This is the first UDP message sent to Simulator after HTTP login. + // It establishes the UDP circuit using the circuit_code received from + // LoginServer. Simulator will respond with an ACK, then send + // RegionHandshake message with region details. LL_INFOS("AppInit") << "viewer: UserLoginLocationReply() Enabling " << gFirstSim << " with code " << msg->mOurCircuitCode << LL_ENDL; msg->newMessageFast(_PREHASH_UseCircuitCode); msg->nextBlockFast(_PREHASH_CircuitCode); msg->addU32Fast(_PREHASH_Code, msg->mOurCircuitCode); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->addUUIDFast(_PREHASH_ID, gAgent.getID()); + + // build a lambda to be used as callback on ACK or timeout + void (*use_circuit_callback)(void**, S32) = [](void**, S32 result) + { + // bail if we're quitting. + if(LLApp::isExiting()) return; + if( !gUseCircuitCallbackCalled ) + { + gUseCircuitCallbackCalled = true; + if (result != LL_ERR_NOERR) + { + // Make sure user knows something bad happened. JC + LL_WARNS("AppInit") << "Backing up to login screen!" << LL_ENDL; + if (gRememberPassword) + { + LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); + } + else + { + LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); + } + reset_login(); + } + else + { + gGotUseCircuitCodeAck = true; + } + } + }; + msg->sendReliable( gFirstSim, gSavedSettings.getS32("UseCircuitCodeMaxRetries"), @@ -1743,6 +1843,9 @@ bool idle_startup() //--------------------------------------------------------------------- // World Wait //--------------------------------------------------------------------- + // UDP CONNECTION STEP 2: Wait for UseCircuitCode acknowledgment + // While waiting, Simulator also sends RegionHandshake (handled by + // process_region_handshake() in llworld.cpp) containing region info. if(STATE_WORLD_WAIT == LLStartUp::getStartupState()) { LL_DEBUGS("AppInit") << "Waiting for simulator ack...." << LL_ENDL; @@ -1758,13 +1861,16 @@ bool idle_startup() //--------------------------------------------------------------------- // Agent Send //--------------------------------------------------------------------- + // UDP CONNECTION STEP 3: Send CompleteAgentMovement + // After the circuit is established and RegionHandshake received, we signal + // to Simulator the Viewer is ready to enter the world. if (STATE_AGENT_SEND == LLStartUp::getStartupState()) { LL_DEBUGS("AppInit") << "Connecting to region..." << LL_ENDL; set_startup_status(0.60f, LLTrans::getString("LoginConnectingToRegion"), gAgent.mMOTD); do_startup_frame(); - // register with the message system so it knows we're - // expecting this message + // Register handler process_agent_movement_complete for AgentMovementComplete - + // the final UDP message confirming the agent is connected. LLMessageSystem* msg = gMessageSystem; msg->setHandlerFuncFast( _PREHASH_AgentMovementComplete, @@ -1807,12 +1913,17 @@ bool idle_startup() //--------------------------------------------------------------------- // Agent Wait //--------------------------------------------------------------------- + // UDP CONNECTION STEP 4: Wait for AgentMovementComplete + // Simulator responds with the agent's confirmed position and look_at + // direction. Once received, gAgentMovementCompleted is set true and the + // agent is fully connected to the region. if (STATE_AGENT_WAIT == LLStartUp::getStartupState()) { do_startup_frame(); if (gAgentMovementCompleted) { + // Connection complete - agent is now in-world LLStartUp::setStartupState( STATE_INVENTORY_SEND ); } do_startup_frame(); @@ -2811,34 +2922,6 @@ bool login_alert_status(const LLSD& notification, const LLSD& response) } -void use_circuit_callback(void**, S32 result) -{ - // bail if we're quitting. - if(LLApp::isExiting()) return; - if( !gUseCircuitCallbackCalled ) - { - gUseCircuitCallbackCalled = true; - if (result) - { - // Make sure user knows something bad happened. JC - LL_WARNS("AppInit") << "Backing up to login screen!" << LL_ENDL; - if (gRememberPassword) - { - LLNotificationsUtil::add("LoginPacketNeverReceived", LLSD(), LLSD(), login_alert_status); - } - else - { - LLNotificationsUtil::add("LoginPacketNeverReceivedNoTP", LLSD(), LLSD(), login_alert_status); - } - reset_login(); - } - else - { - gGotUseCircuitCodeAck = true; - } - } -} - void register_viewer_callbacks(LLMessageSystem* msg) { msg->setHandlerFuncFast(_PREHASH_LayerData, process_layer_data ); @@ -3731,6 +3814,14 @@ bool init_benefits(LLSD& response) return succ; } +// HTTP LOGIN RESPONSE PROCESSING +// Called after successful HTTP XMLRPC authentication. Extracts critical data +// from LoginServer response needed to establish the UDP connection: +// - agent_id, session_id, secure_session_id (authentication tokens) +// - circuit_code (used in UseCircuitCode UDP message) +// - sim_ip, sim_port (simulator address for UDP circuit) +// - seed_capability (URL for fetching HTTP capability endpoints) +// bool process_login_success_response() { LLSD response = LLLoginInstance::getInstance()->getResponse(); @@ -3853,6 +3944,8 @@ bool process_login_success_response() gAgentStartLocation.assign(text); } + // Extract UDP circuit parameters from login response. + // These are used in STATE_WORLD_INIT to establish the UDP circuit. text = response["circuit_code"].asString(); if(!text.empty()) { diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 812ba765511..1f1ff30b8f0 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -418,7 +418,26 @@ void send_complete_agent_movement(const LLHost& sim_host) msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); msg->addU32Fast(_PREHASH_CircuitCode, msg->mOurCircuitCode); - msg->sendReliable(sim_host); + + // build a lambda to be used as callback on ACK or timeout + void (*complete_agent_movement_callback)(void**, S32) = [](void**, S32 result) + { + if(LLApp::isExiting()) return; + if (result != LL_ERR_NOERR) + { + LL_WARNS("Messaging") << "CompleteAgentMovement failed with err=" << result << LL_ENDL; + } + }; + + // We use same retry strategy as UseCircuitCode because this is a crucial message + // that MUST arrive else we'll suffer a failed login/teleport/region-cross + msg->sendReliable( + sim_host, + gSavedSettings.getS32("UseCircuitCodeMaxRetries"), + false, + (F32Seconds)gSavedSettings.getF32("UseCircuitCodeTimeout"), + complete_agent_movement_callback, + NULL); } void process_logout_reply(LLMessageSystem* msg, void**) diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index dd02cca50a1..5f73d8e9012 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -3207,7 +3207,26 @@ void LLViewerRegion::unpackRegionHandshake() flags |= 0x00000002; //set the bit 1 to be 1 to tell sim the cache file is empty, no need to send cache probes. } msg->addU32("Flags", flags ); - msg->sendReliable(host); + + // build a lambda to be used as callback on ACK or timeout + void (*region_handshake_reply_callback)(void**, S32) = [](void**, S32 result) + { + if(LLApp::isExiting()) return; + if (result != LL_ERR_NOERR) + { + LL_WARNS("Messaging") << "RegionHandshakeReply failed with err=" << result << LL_ENDL; + } + }; + + // This is a crucial message for establishing a connection to a region + // (either the main region or a visible neighbor). + msg->sendReliable( + host, + gSavedSettings.getS32("UseCircuitCodeMaxRetries"), + false, + (F32Seconds)gSavedSettings.getF32("UseCircuitCodeTimeout"), + region_handshake_reply_callback, + NULL); mRegionTimer.reset(); //reset region timer. } From a1b68fb8ed6c2589dd13bd9ba1fab7050ba38226 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Wed, 27 May 2026 10:18:29 -0700 Subject: [PATCH 078/112] Drop fewer packets on UDP packet flood (#5565) * ACK received-but-unprocessed packets when queued * Prioritize packets between high- and low-priority (high-priority are: reliable packets that need an ACK) * Increase max size of LLPacketRing buffer --- indra/llmessage/llcircuit.cpp | 5 +- indra/llmessage/llpacketbuffer.h | 3 + indra/llmessage/llpacketring.cpp | 315 +++++------------------------ indra/llmessage/llpacketring.h | 88 +++----- indra/llmessage/message.cpp | 315 ++++++++++++++++++++++++++--- indra/llmessage/message.h | 97 ++++++++- indra/llmessage/net.cpp | 2 +- indra/newview/llappviewer.cpp | 79 ++++---- indra/newview/llstartup.cpp | 4 +- indra/newview/llviewermenu.cpp | 2 +- indra/newview/llviewerstats.cpp | 2 +- indra/newview/llviewerthrottle.cpp | 23 ++- indra/newview/llviewerthrottle.h | 4 +- indra/newview/llworld.cpp | 10 +- 14 files changed, 529 insertions(+), 420 deletions(-) diff --git a/indra/llmessage/llcircuit.cpp b/indra/llmessage/llcircuit.cpp index c2b1a2f069a..eaee7b0112c 100644 --- a/indra/llmessage/llcircuit.cpp +++ b/indra/llmessage/llcircuit.cpp @@ -346,8 +346,7 @@ S32 LLCircuitData::resendUnackedPackets(const F64Seconds now) packetp->mBuffer[0] |= LL_RESENT_FLAG; // tag packet id as being a resend - gMessageSystem->mPacketRing.sendPacket(packetp->mSocket, - (char *)packetp->mBuffer, packetp->mBufferLength, + gMessageSystem->sendPacketToSocket((char *)packetp->mBuffer, packetp->mBufferLength, packetp->mHost); mThrottles.throttleOverflow(TC_RESEND, packetp->mBufferLength * 8.f); @@ -973,7 +972,7 @@ bool LLCircuitData::updateWatchDogTimers(LLMessageSystem *msgsys) { // let's call this one a loss! mPacketsLost++; - gMessageSystem->mDroppedPackets++; + gMessageSystem->mLostPackets++; if(gMessageSystem->mVerboseLog) { std::ostringstream str; diff --git a/indra/llmessage/llpacketbuffer.h b/indra/llmessage/llpacketbuffer.h index ac4012d330d..b0505e7afd3 100644 --- a/indra/llmessage/llpacketbuffer.h +++ b/indra/llmessage/llpacketbuffer.h @@ -38,6 +38,9 @@ class LLPacketBuffer LLPacketBuffer(S32 hSocket); // receive a packet ~LLPacketBuffer(); + LLPacketBuffer(const LLPacketBuffer&) = default; + LLPacketBuffer& operator=(const LLPacketBuffer&) = default; + S32 getSize() const { return mSize; } const char *getData() const { return mData; } LLHost getHost() const { return mHost; } diff --git a/indra/llmessage/llpacketring.cpp b/indra/llmessage/llpacketring.cpp index b8284334eaa..bade413e61e 100644 --- a/indra/llmessage/llpacketring.cpp +++ b/indra/llmessage/llpacketring.cpp @@ -28,344 +28,121 @@ #include "llpacketring.h" -#if LL_WINDOWS - #include -#else - #include - #include -#endif - -// linden library includes #include "llerror.h" -#include "lltimer.h" -#include "llproxy.h" -#include "llrand.h" -#include "message.h" -#include "u64.h" -constexpr S16 MAX_BUFFER_RING_SIZE = 1024; +constexpr S16 MAX_BUFFER_RING_SIZE = 8192; + +// DANGER: don't adjust DEFAULT_BUFFER_RING_SIZE unless you know what +// you're doing. Its value affects the "buffer load rate" which is used +// to supply backpressure to an overloaded nework queue. constexpr S16 DEFAULT_BUFFER_RING_SIZE = 256; -LLPacketRing::LLPacketRing () - : mPacketRing(DEFAULT_BUFFER_RING_SIZE, nullptr) +LLPacketRing::LLPacketRing() + : mRing(DEFAULT_BUFFER_RING_SIZE, nullptr) { LLHost invalid_host; - for (size_t i = 0; i < mPacketRing.size(); ++i) + for (size_t i = 0; i < mRing.size(); ++i) { - mPacketRing[i] = new LLPacketBuffer(invalid_host, nullptr, 0); + mRing[i] = new LLPacketBuffer(invalid_host, nullptr, 0); } } -LLPacketRing::~LLPacketRing () +LLPacketRing::~LLPacketRing() { - for (auto packet : mPacketRing) + for (auto* packet : mRing) { delete packet; } - mPacketRing.clear(); + mRing.clear(); mNumBufferedPackets = 0; mNumBufferedBytes = 0; mHeadIndex = 0; } -S32 LLPacketRing::receivePacket (S32 socket, char *datap) -{ - bool drop = computeDrop(); - return (mNumBufferedPackets > 0) ? - receiveOrDropBufferedPacket(datap, drop) : - receiveOrDropPacket(socket, datap, drop); -} - -bool send_packet_helper(int socket, const char * datap, S32 data_size, LLHost host) -{ - if (!LLProxy::isSOCKSProxyEnabled()) - { - return send_packet(socket, datap, data_size, host.getAddress(), host.getPort()); - } - - char headered_send_buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE]; - - proxywrap_t *socks_header = static_cast(static_cast(&headered_send_buffer)); - socks_header->rsv = 0; - socks_header->addr = host.getAddress(); - socks_header->port = htons(host.getPort()); - socks_header->atype = ADDRESS_IPV4; - socks_header->frag = 0; - - memcpy(headered_send_buffer + SOCKS_HEADER_SIZE, datap, data_size); - - return send_packet( socket, - headered_send_buffer, - data_size + SOCKS_HEADER_SIZE, - LLProxy::getInstance()->getUDPProxy().getAddress(), - LLProxy::getInstance()->getUDPProxy().getPort()); -} - -bool LLPacketRing::sendPacket(int socket, const char * datap, S32 data_size, LLHost host) +void LLPacketRing::pushPacket(const LLPacketBuffer& packet) { - mActualBytesOut += data_size; - return send_packet_helper(socket, datap, data_size, host); -} - -void LLPacketRing::dropPackets (U32 num_to_drop) -{ - mPacketsToDrop += num_to_drop; -} - -void LLPacketRing::setDropPercentage (F32 percent_to_drop) -{ - mDropPercentage = percent_to_drop; -} - -bool LLPacketRing::computeDrop() -{ - bool drop= (mDropPercentage > 0.0f && (ll_frand(100.f) < mDropPercentage)); - if (drop) - { - ++mPacketsToDrop; - } - if (mPacketsToDrop > 0) - { - --mPacketsToDrop; - drop = true; - } - return drop; -} - -S32 LLPacketRing::receiveOrDropPacket(S32 socket, char *datap, bool drop) -{ - S32 packet_size = 0; - - // pull straight from socket - if (LLProxy::isSOCKSProxyEnabled()) + S16 ring_size = (S16)mRing.size(); + if (mNumBufferedPackets >= ring_size && ring_size < MAX_BUFFER_RING_SIZE) { - char buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE]; /* Flawfinder ignore */ - packet_size = receive_packet(socket, buffer); - if (packet_size > 0) - { - mActualBytesIn += packet_size; - } - - if (packet_size > SOCKS_HEADER_SIZE) - { - if (drop) - { - packet_size = 0; - } - else - { - // *FIX We are assuming ATYP is 0x01 (IPv4), not 0x03 (hostname) or 0x04 (IPv6) - packet_size -= SOCKS_HEADER_SIZE; // The unwrapped packet size - memcpy(datap, buffer + SOCKS_HEADER_SIZE, packet_size); - proxywrap_t * header = static_cast(static_cast(buffer)); - mLastSender.setAddress(header->addr); - mLastSender.setPort(ntohs(header->port)); - mLastReceivingIF = ::get_receiving_interface(); - } - } - else - { - packet_size = 0; - } - } - else - { - packet_size = receive_packet(socket, datap); - if (packet_size > 0) - { - mActualBytesIn += packet_size; - if (drop) - { - packet_size = 0; - } - else - { - mLastSender = ::get_sender(); - mLastReceivingIF = ::get_receiving_interface(); - } - } + expandRing(); + ring_size = (S16)mRing.size(); } - return packet_size; -} -S32 LLPacketRing::receiveOrDropBufferedPacket(char *datap, bool drop) -{ - assert(mNumBufferedPackets > 0); - S32 packet_size = 0; + LLPacketBuffer* slot = mRing[mHeadIndex]; + S32 old_size = slot->getSize(); - S16 ring_size = (S16)(mPacketRing.size()); - S16 packet_index = (mHeadIndex + ring_size - mNumBufferedPackets) % ring_size; - LLPacketBuffer* packet = mPacketRing[packet_index]; - packet_size = packet->getSize(); - mLastSender = packet->getHost(); - mLastReceivingIF = packet->getReceivingInterface(); + *slot = packet; - --mNumBufferedPackets; - mNumBufferedBytes -= packet_size; - if (mNumBufferedPackets == 0) - { - assert(mNumBufferedBytes == 0); - } + mHeadIndex = (mHeadIndex + 1) % ring_size; - if (!drop) + if (mNumBufferedPackets < ring_size) { - if (packet_size > 0) - { - memcpy(datap, packet->getData(), packet_size); - } - else - { - assert(false); - } + ++mNumBufferedPackets; + mNumBufferedBytes += packet.getSize(); } else { - packet_size = 0; + // Ring is at maximum capacity; oldest packet was overwritten. + // This is VERY BAD because we've already ACKed the packet we're loosing + // (if it was "reliable"). + LL_WARNS("PacketRing") << "buffer overflow at " << mNumBufferedPackets << " packets" << LL_ENDL; + mNumBufferedBytes += packet.getSize() - old_size; } - return packet_size; } -S32 LLPacketRing::bufferInboundPacket(S32 socket) +bool LLPacketRing::popPacket(LLPacketBuffer& packet) { - if (mNumBufferedPackets == mPacketRing.size() && mNumBufferedPackets < MAX_BUFFER_RING_SIZE) + if (mNumBufferedPackets <= 0) { - expandRing(); + return false; } - LLPacketBuffer* packet = mPacketRing[mHeadIndex]; - S32 old_packet_size = packet->getSize(); - S32 packet_size = 0; - if (LLProxy::isSOCKSProxyEnabled()) - { - char buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE]; /* Flawfinder ignore */ - packet_size = receive_packet(socket, buffer); - if (packet_size > 0) - { - mActualBytesIn += packet_size; - if (packet_size > SOCKS_HEADER_SIZE) - { - // *FIX We are assuming ATYP is 0x01 (IPv4), not 0x03 (hostname) or 0x04 (IPv6) + S16 ring_size = (S16)mRing.size(); + S16 tail_index = (mHeadIndex + ring_size - mNumBufferedPackets) % ring_size; - proxywrap_t * header = static_cast(static_cast(buffer)); - LLHost sender; - sender.setAddress(header->addr); - sender.setPort(ntohs(header->port)); + LLPacketBuffer* slot = mRing[tail_index]; + S32 packet_size = slot->getSize(); - packet_size -= SOCKS_HEADER_SIZE; // The unwrapped packet size - packet->init(buffer + SOCKS_HEADER_SIZE, packet_size, sender); + packet = *slot; - mHeadIndex = (mHeadIndex + 1) % (S16)(mPacketRing.size()); - if (mNumBufferedPackets < MAX_BUFFER_RING_SIZE) - { - ++mNumBufferedPackets; - mNumBufferedBytes += packet_size; - } - else - { - // we overwrote an older packet - mNumBufferedBytes += packet_size - old_packet_size; - } - } - else - { - packet_size = 0; - } - } - } - else - { - packet->init(socket); - packet_size = packet->getSize(); - if (packet_size > 0) - { - mActualBytesIn += packet_size; + --mNumBufferedPackets; + mNumBufferedBytes -= packet_size; - mHeadIndex = (mHeadIndex + 1) % (S16)(mPacketRing.size()); - if (mNumBufferedPackets < MAX_BUFFER_RING_SIZE) - { - ++mNumBufferedPackets; - mNumBufferedBytes += packet_size; - } - else - { - // we overwrote an older packet - mNumBufferedBytes += packet_size - old_packet_size; - } - } - } - return packet_size; -} + llassert(mNumBufferedPackets > 0 || mNumBufferedBytes == 0); -S32 LLPacketRing::drainSocket(S32 socket) -{ - // drain into buffer - S32 packet_size = 1; - S32 num_loops = 0; - S32 old_num_packets = mNumBufferedPackets; - while (packet_size > 0) - { - packet_size = bufferInboundPacket(socket); - ++num_loops; - } - S32 num_dropped_packets = (num_loops - 1 + old_num_packets) - mNumBufferedPackets; - if (num_dropped_packets > 0) - { - // It will eventually be accounted by mDroppedPackets - // and mPacketsLost, but track it here for logging purposes. - mNumDroppedPackets += num_dropped_packets; - } - return (S32)(mNumBufferedPackets); + return true; } bool LLPacketRing::expandRing() { - // compute larger size - constexpr S16 BUFFER_RING_EXPANSION = 256; - S16 old_size = (S16)(mPacketRing.size()); + constexpr S16 BUFFER_RING_EXPANSION = 512; + S16 old_size = (S16)mRing.size(); S16 new_size = llmin(old_size + BUFFER_RING_EXPANSION, MAX_BUFFER_RING_SIZE); if (new_size == old_size) { - // mPacketRing is already maxed out return false; } - // make a larger ring and copy packet pointers + // Lay existing entries out linearly in FIFO order starting at index 0. std::vector new_ring(new_size, nullptr); for (S16 i = 0; i < old_size; ++i) { S16 j = (mHeadIndex + i) % old_size; - new_ring[i] = mPacketRing[j]; + new_ring[i] = mRing[j]; } - // allocate new packets for the remainder of new_ring LLHost invalid_host; for (S16 i = old_size; i < new_size; ++i) { new_ring[i] = new LLPacketBuffer(invalid_host, nullptr, 0); } - // swap the rings and reset mHeadIndex - mPacketRing.swap(new_ring); + mRing.swap(new_ring); mHeadIndex = mNumBufferedPackets; return true; } F32 LLPacketRing::getBufferLoadRate() const { - // goes up to MAX_BUFFER_RING_SIZE return (F32)mNumBufferedPackets / (F32)DEFAULT_BUFFER_RING_SIZE; } - -void LLPacketRing::dumpPacketRingStats() -{ - mNumDroppedPacketsTotal += mNumDroppedPackets; - LL_INFOS("Messaging") << "Packet ring stats: " << std::endl - << "Buffered packets: " << mNumBufferedPackets << std::endl - << "Buffered bytes: " << mNumBufferedBytes << std::endl - << "Dropped packets current: " << mNumDroppedPackets << std::endl - << "Dropped packets total: " << mNumDroppedPacketsTotal << std::endl - << "Dropped packets percentage: " << mDropPercentage << "%" << std::endl - << "Actual in bytes: " << mActualBytesIn << std::endl - << "Actual out bytes: " << mActualBytesOut << LL_ENDL; - mNumDroppedPackets = 0; -} diff --git a/indra/llmessage/llpacketring.h b/indra/llmessage/llpacketring.h index 572dcbd271d..5315e5a5bda 100644 --- a/indra/llmessage/llpacketring.h +++ b/indra/llmessage/llpacketring.h @@ -1,7 +1,16 @@ /** * @file llpacketring.h - * @brief definition of LLPacketRing class for implementing a resend, - * drop, or delay in packet transmissions + * @brief LLPacketRing: a simple ring buffer for LLPacketBuffers. + * + * LLPacketRing stores incoming UDP packets that have already been received + * from the network socket. It has no socket or proxy awareness; callers + * push packets in with pushPacket() and retrieve them in FIFO order with + * popPacket(). + * + * The ring starts at DEFAULT_BUFFER_RING_SIZE slots and grows in increments + * of BUFFER_RING_EXPANSION up to MAX_BUFFER_RING_SIZE. Once at the ceiling, + * pushPacket() silently overwrites the oldest queued packet to make room for + * the incoming one. * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code @@ -29,9 +38,7 @@ #include -#include "llhost.h" #include "llpacketbuffer.h" -#include "llthrottle.h" class LLPacketRing @@ -40,71 +47,26 @@ class LLPacketRing LLPacketRing(); ~LLPacketRing(); - // receive one packet: either buffered or from the socket - S32 receivePacket (S32 socket, char *datap); - - // send one packet - bool sendPacket(int h_socket, const char * send_buffer, S32 buf_size, LLHost host); - - // drains packets from socket and returns final mNumBufferedPackets - S32 drainSocket(S32 socket); + // Copy 'packet' onto the tail of the ring, growing the ring or + // overwriting the oldest entry when the ring is at capacity. + void pushPacket(const LLPacketBuffer& packet); - void dropPackets(U32); - void setDropPercentage (F32 percent_to_drop); - - inline LLHost getLastSender() const; - inline LLHost getLastReceivingInterface() const; - - S32 getActualInBytes() const { return mActualBytesIn; } - S32 getActualOutBytes() const { return mActualBytesOut; } - S32 getAndResetActualInBits() { S32 bits = mActualBytesIn * 8; mActualBytesIn = 0; return bits;} - S32 getAndResetActualOutBits() { S32 bits = mActualBytesOut * 8; mActualBytesOut = 0; return bits;} + // Copy the head (oldest) packet into 'packet'. + // Returns true if a packet was available, false if the ring was empty. + bool popPacket(LLPacketBuffer& packet); S32 getNumBufferedPackets() const { return (S32)(mNumBufferedPackets); } - S32 getNumBufferedBytes() const { return mNumBufferedBytes; } - S32 getNumDroppedPackets() const { return mNumDroppedPacketsTotal + mNumDroppedPackets; } - - F32 getBufferLoadRate() const; // from 0 to 4 (0 - empty, 1 - default size is full) - void dumpPacketRingStats(); -protected: - // returns 'true' if we should intentionally drop a packet - bool computeDrop(); - - // returns packet_size of received packet, zero or less if no packet found - S32 receiveOrDropPacket(S32 socket, char *datap, bool drop); - S32 receiveOrDropBufferedPacket(char *datap, bool drop); + S32 getNumBufferedBytes() const { return mNumBufferedBytes; } - // returns packet_size of packet buffered - S32 bufferInboundPacket(S32 socket); + // Ratio of buffered packets to DEFAULT_BUFFER_RING_SIZE (0 = empty, 1 = nominal full). + F32 getBufferLoadRate() const; - // returns 'true' if ring was expanded +private: + // Returns true if the ring was expanded, false if already at the ceiling. bool expandRing(); -protected: - std::vector mPacketRing; - S16 mHeadIndex { 0 }; + std::vector mRing; + S16 mHeadIndex { 0 }; S16 mNumBufferedPackets { 0 }; - S32 mNumDroppedPackets { 0 }; - S32 mNumDroppedPacketsTotal { 0 }; - S32 mNumBufferedBytes { 0 }; - - S32 mActualBytesIn { 0 }; - S32 mActualBytesOut { 0 }; - F32 mDropPercentage { 0.0f }; // % of inbound packets to drop - U32 mPacketsToDrop { 0 }; // drop next inbound n packets - - // These are the sender and receiving_interface for the last packet delivered by receivePacket() - LLHost mLastSender; - LLHost mLastReceivingIF; + S32 mNumBufferedBytes { 0 }; }; - - -inline LLHost LLPacketRing::getLastSender() const -{ - return mLastSender; -} - -inline LLHost LLPacketRing::getLastReceivingInterface() const -{ - return mLastReceivingIF; -} diff --git a/indra/llmessage/message.cpp b/indra/llmessage/message.cpp index e2937490bab..b11e4598e7c 100644 --- a/indra/llmessage/message.cpp +++ b/indra/llmessage/message.cpp @@ -78,6 +78,8 @@ #include "lltransfertargetvfile.h" #include "llcorehttputil.h" #include "llpounceable.h" +#include "llproxy.h" +#include "llrand.h" // Constants //const char* MESSAGE_LOG_FILENAME = "message.log"; @@ -173,7 +175,7 @@ void LLMessageSystem::init() mTotalBytesIn = 0; mTotalBytesOut = 0; - mDroppedPackets = 0; // total dropped packets in + mLostPackets = 0; // total lost packets out mResentPackets = 0; // total resent packets out mFailedResendPackets = 0; // total resend failure packets out mOffCircuitPackets = 0; // total # of off-circuit packets rejected @@ -184,6 +186,13 @@ void LLMessageSystem::init() mIncomingCompressedSize = 0; mCurrentRecvPacketID = 0; + mActualBytesIn = 0; + mActualBytesOut = 0; + mDropPercentage = 0.0f; + mPacketsToDrop = 0; + mNumDroppedPackets = 0; + mNumDroppedPacketsTotal = 0; + mMessageFileVersionNumber = 0.f; mTimingCallback = NULL; @@ -508,18 +517,16 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count ) bool recv_reliable = false; bool recv_resent = false; - S32 acks = 0; + S32 num_acks = 0; S32 true_rcv_size = 0; U8* buffer = mTrueReceiveBuffer; - mTrueReceiveSize = mPacketRing.receivePacket(mSocket, (char *)mTrueReceiveBuffer); + mTrueReceiveSize = receivePacketOrDrop((char *)mTrueReceiveBuffer); // If you want to dump all received packets into SecondLife.log, uncomment this //dumpPacketToLog(); receive_size = mTrueReceiveSize; - mLastSender = mPacketRing.getLastSender(); - mLastReceivingIF = mPacketRing.getLastReceivingInterface(); if (receive_size < (S32) LL_MINIMUM_VALID_PACKET_SIZE) { @@ -535,24 +542,23 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count ) } else { - LLHost host; - LLCircuitData* cdp; - - // note if packet acks are appended. + // handle any packet ACKs (for outbound messages) found at tail of inbound message if(buffer[0] & LL_ACK_FLAG) { - acks += buffer[--receive_size]; + // Note: these ACKs may have already been handled if this was a buffered message + // but it doesn't hurt to handle them again. + num_acks += buffer[--receive_size]; true_rcv_size = receive_size; - if(receive_size >= ((S32)(acks * sizeof(TPACKETID) + LL_MINIMUM_VALID_PACKET_SIZE))) + if(receive_size >= ((S32)(num_acks * sizeof(TPACKETID) + LL_MINIMUM_VALID_PACKET_SIZE))) { - receive_size -= acks * sizeof(TPACKETID); + receive_size -= num_acks * sizeof(TPACKETID); } else { // mal-formed packet. ignore it and continue with // the next one LL_WARNS("Messaging") << "Malformed packet received. Packet size " - << receive_size << " with invalid no. of acks " << acks + << receive_size << " with invalid no. of acks " << num_acks << LL_ENDL; valid_packet = false; continue; @@ -562,20 +568,20 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count ) // process the message as normal mIncomingCompressedSize = zeroCodeExpand(&buffer, &receive_size); mCurrentRecvPacketID = ntohl(*((U32*)(&buffer[1]))); - host = getSender(); + LLHost host = getSender(); const bool resetPacketId = true; - cdp = findCircuit(host, resetPacketId); + LLCircuitData* cdp = findCircuit(host, resetPacketId); // At this point, cdp is now a pointer to the circuit that // this message came in on if it's valid, and NULL if the // circuit was bogus. - if(cdp && (acks > 0) && ((S32)(acks * sizeof(TPACKETID)) < (true_rcv_size))) + if(cdp && (num_acks > 0) && ((S32)(num_acks * sizeof(TPACKETID)) < (true_rcv_size))) { TPACKETID packet_id; U32 mem_id=0; - for(S32 i = 0; i < acks; ++i) + for(S32 i = 0; i < num_acks; ++i) { true_rcv_size -= sizeof(TPACKETID); memcpy(&mem_id, &mTrueReceiveBuffer[true_rcv_size], /* Flawfinder: ignore*/ @@ -630,7 +636,7 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count ) str << tbuf << "(unknown)" << (recv_reliable ? " reliable" : "") << " resent " - << ((acks > 0) ? "acks" : "") + << ((num_acks > 0) ? "acks" : "") << " DISCARD DUPLICATE"; LL_INFOS("Messaging") << str.str() << LL_ENDL; } @@ -680,7 +686,7 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count ) if ( valid_packet ) { - logValidMsg(cdp, host, recv_reliable, recv_resent, acks>0 ); + logValidMsg(cdp, host, recv_reliable, recv_resent, num_acks>0 ); valid_packet = mTemplateMessageReader->readMessage(buffer, host); } @@ -724,7 +730,7 @@ bool LLMessageSystem::checkMessages(LockMessageChecker&, S64 frame_count ) // Check to see if we need to print debug info if ((mt_sec - mCircuitPrintTime) > mCircuitPrintFreq) { - mPacketRing.dumpPacketRingStats(); + dumpPacketRingStats(); dumpCircuitInfo(); mCircuitPrintTime = mt_sec; } @@ -749,6 +755,10 @@ S32 LLMessageSystem::getReceiveBytes() const } } +F32 LLMessageSystem::getBufferLoadRate() const +{ + return llmax(mHighPriorityInbound.getBufferLoadRate(), mLowPriorityInbound.getBufferLoadRate()); +} void LLMessageSystem::processAcks(LockMessageChecker&, F32 collect_time) { @@ -822,7 +832,264 @@ void LLMessageSystem::processAcks(LockMessageChecker&, F32 collect_time) S32 LLMessageSystem::drainUdpSocket() { - return mPacketRing.drainSocket(mSocket); + S32 packet_size = 1; + S32 num_loops = 0; + S32 old_num_buffered_packets = getNumBufferedPackets(); + while (packet_size > 0) + { + packet_size = bufferInboundPacket(); + ++num_loops; + } + S32 num_dropped_packets = (num_loops - 1 + old_num_buffered_packets) - getNumBufferedPackets(); + if (num_dropped_packets > 0) + { + mNumDroppedPackets += num_dropped_packets; + } + return getNumBufferedPackets(); +} + +bool LLMessageSystem::computeDrop() +{ + bool drop = (mDropPercentage > 0.0f && (ll_frand(100.f) < mDropPercentage)); + if (drop) + { + ++mPacketsToDrop; + } + if (mPacketsToDrop > 0) + { + --mPacketsToDrop; + drop = true; + } + return drop; +} + +bool LLMessageSystem::isHighPriorityMessage(const LLPacketBuffer& pkt) const +{ + S32 size = pkt.getSize(); + if (size < LL_PACKET_ID_SIZE + 1) + { + return false; + } + + // We want to prioritize crucial messages use to establish viewer <--> simulator connection, + // which are all low-frequency. A simple approximation is to just prioritize all non high- + // frequency messages. + // + // High frequency messages use a single byte for message_id whereas all low- and medium- + // frequency messages have 255 at the first byte of the message_id (which is after the + // LL_PACKET_ID_SIZE bytes of packet_id). + return *((const U8*)pkt.getData() + LL_PACKET_ID_SIZE) == 255; +} + +void LLMessageSystem::dropPackets(U32 num_to_drop) +{ + mPacketsToDrop += num_to_drop; +} + +void LLMessageSystem::setDropPercentage(F32 percent_to_drop) +{ + mDropPercentage = percent_to_drop; +} + +S32 LLMessageSystem::receivePacketOrDrop(char* datap) +{ + if (getNumBufferedPackets() > 0) + { + LLHost invalid_host; + LLPacketBuffer pkt(invalid_host, nullptr, 0); + if (!mHighPriorityInbound.popPacket(pkt)) + { + mLowPriorityInbound.popPacket(pkt); + } + + S32 packet_size = pkt.getSize(); + mLastSender = pkt.getHost(); + mLastReceivingIF = pkt.getReceivingInterface(); + + if (packet_size > 0) + { + memcpy(datap, pkt.getData(), packet_size); + } + return packet_size; + } + + // Read directly from the socket. + bool drop = computeDrop(); + S32 packet_size = 0; + if (LLProxy::isSOCKSProxyEnabled()) + { + char buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE]; /* Flawfinder ignore */ + packet_size = receive_packet(mSocket, buffer); + if (packet_size > 0) + { + mActualBytesIn += packet_size; + } + if (packet_size > SOCKS_HEADER_SIZE) + { + if (drop) + { + packet_size = 0; + } + else + { + // *FIX We are assuming ATYP is 0x01 (IPv4), not 0x03 (hostname) or 0x04 (IPv6) + packet_size -= SOCKS_HEADER_SIZE; + memcpy(datap, buffer + SOCKS_HEADER_SIZE, packet_size); + proxywrap_t* header = static_cast(static_cast(buffer)); + mLastSender.setAddress(header->addr); + mLastSender.setPort(ntohs(header->port)); + mLastReceivingIF = ::get_receiving_interface(); + } + } + else + { + packet_size = 0; + } + } + else + { + packet_size = receive_packet(mSocket, datap); + if (packet_size > 0) + { + mActualBytesIn += packet_size; + if (drop) + { + packet_size = 0; + } + else + { + mLastSender = ::get_sender(); + mLastReceivingIF = ::get_receiving_interface(); + } + } + } + return packet_size; +} + +S32 LLMessageSystem::bufferInboundPacket() +{ + LLHost invalid_host; + LLPacketBuffer pkt(invalid_host, nullptr, 0); + S32 packet_size = 0; + + if (LLProxy::isSOCKSProxyEnabled()) + { + char buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE]; /* Flawfinder ignore */ + packet_size = receive_packet(mSocket, buffer); + if (packet_size > 0) + { + mActualBytesIn += packet_size; + if (packet_size > SOCKS_HEADER_SIZE) + { + // *FIX We are assuming ATYP is 0x01 (IPv4), not 0x03 (hostname) or 0x04 (IPv6) + proxywrap_t* header = static_cast(static_cast(buffer)); + LLHost sender; + sender.setAddress(header->addr); + sender.setPort(ntohs(header->port)); + packet_size -= SOCKS_HEADER_SIZE; + pkt.init(buffer + SOCKS_HEADER_SIZE, packet_size, sender); + } + else + { + packet_size = 0; + } + } + } + else + { + pkt.init(mSocket); + packet_size = pkt.getSize(); + if (packet_size > 0) + { + mActualBytesIn += packet_size; + } + } + + if (packet_size >= (S32)LL_MINIMUM_VALID_PACKET_SIZE && !computeDrop()) + { + const char* data = pkt.getData(); + LLCircuitData* cdp = mCircuitInfo.findCircuit(pkt.getHost()); + TPACKETID recv_packet_id = ntohl(*((U32*)(&data[1]))); + + // Harvest piggybacked ACKs for outbound messages from the packet tail of this inbound message + if (cdp && (data[0] & LL_ACK_FLAG)) + { + U8 num_acks = (U8)data[packet_size - 1]; + S32 true_rcv_size = packet_size - 1; + if (true_rcv_size >= (S32)(num_acks * sizeof(TPACKETID) + LL_MINIMUM_VALID_PACKET_SIZE)) + { + TPACKETID ack_id; + U32 mem_id = 0; + for (S32 i = 0; i < num_acks; ++i) + { + true_rcv_size -= sizeof(TPACKETID); + memcpy(&mem_id, &data[true_rcv_size], sizeof(TPACKETID)); /* Flawfinder: ignore */ + ack_id = ntohl(mem_id); + cdp->ackReliablePacket(ack_id); + } + if (!cdp->getUnackedPacketCount()) + { + mCircuitInfo.mUnackedCircuitMap.erase(cdp->mHost); + } + } + } + + // ACK inbound reliable packet ASAP + if (cdp && (data[0] & LL_RELIABLE_FLAG)) + { + cdp->collectRAck(recv_packet_id); + } + + if (isHighPriorityMessage(pkt)) + { + mHighPriorityInbound.pushPacket(pkt); + } + else + { + mLowPriorityInbound.pushPacket(pkt); + } + } + + return packet_size; +} + +bool LLMessageSystem::sendPacketToSocket(const char* datap, S32 data_size, LLHost host) +{ + mActualBytesOut += data_size; + if (!LLProxy::isSOCKSProxyEnabled()) + { + return send_packet(mSocket, datap, data_size, host.getAddress(), host.getPort()); + } + + char headered_send_buffer[NET_BUFFER_SIZE + SOCKS_HEADER_SIZE]; + + proxywrap_t* socks_header = static_cast(static_cast(&headered_send_buffer)); + socks_header->rsv = 0; + socks_header->addr = host.getAddress(); + socks_header->port = htons(host.getPort()); + socks_header->atype = ADDRESS_IPV4; + socks_header->frag = 0; + + memcpy(headered_send_buffer + SOCKS_HEADER_SIZE, datap, data_size); + + return send_packet(mSocket, + headered_send_buffer, + data_size + SOCKS_HEADER_SIZE, + LLProxy::getInstance()->getUDPProxy().getAddress(), + LLProxy::getInstance()->getUDPProxy().getPort()); +} + +void LLMessageSystem::dumpPacketRingStats() +{ + mNumDroppedPacketsTotal += mNumDroppedPackets; + LL_INFOS("Messaging") << "buffered_packets=" << getNumBufferedPackets() + << "buffered_bytes=" << (mHighPriorityInbound.getNumBufferedBytes() + mLowPriorityInbound.getNumBufferedBytes()) + << "recently_dropped=" << mNumDroppedPackets + << "total_dropped=" << mNumDroppedPacketsTotal + << "dropped_percentage=" << mDropPercentage << "%" + << "bytes_IN=" << mActualBytesIn + << "bytes_OUT=" << mActualBytesOut << LL_ENDL; + mNumDroppedPackets = 0; } void LLMessageSystem::copyMessageReceivedToSend() @@ -1277,7 +1544,7 @@ S32 LLMessageSystem::sendMessage(const LLHost &host) } bool success; - success = mPacketRing.sendPacket(mSocket, (char *)buf_ptr, buffer_length, host); + success = sendPacketToSocket((char *)buf_ptr, buffer_length, host); if (!success) { @@ -2607,7 +2874,7 @@ void LLMessageSystem::summarizeLogs(std::ostream& str) str << buffer << std::endl << std::endl; buffer = llformat( "SendPacket failures: %20d", mSendPacketFailureCount); str << buffer << std::endl; - buffer = llformat( "Dropped packets: %20d", mDroppedPackets); + buffer = llformat( "Dropped packets: %20d", getTotalNumDroppedPackets()); str << buffer << std::endl; buffer = llformat( "Resent packets: %20d", mResentPackets); str << buffer << std::endl; @@ -3333,7 +3600,7 @@ void LLMessageSystem::establishBidirectionalTrust(const LLHost &host, S64 frame_ void LLMessageSystem::dumpPacketToLog() { - LL_WARNS("Messaging") << "Packet Dump from:" << mPacketRing.getLastSender() << LL_ENDL; + LL_WARNS("Messaging") << "Packet Dump from:" << mLastSender << LL_ENDL; LL_WARNS("Messaging") << "Packet Size:" << mTrueReceiveSize << LL_ENDL; char line_buffer[256]; /* Flawfinder: ignore */ S32 i; diff --git a/indra/llmessage/message.h b/indra/llmessage/message.h index 14cdc48a07d..e81cfe16a90 100644 --- a/indra/llmessage/message.h +++ b/indra/llmessage/message.h @@ -119,6 +119,49 @@ class LLMessageStringTable : public LLSingleton // Repeat for number of messages in file // +// UDP Packet Buffer Layout +// +// Every UDP message sent or received by LLMessageSystem uses the following +// on-wire layout. Offsets are defined in EPacketHeaderLayout below. +// +// Byte(s) Name Description +// ------- ---- ----------- +// 0 Flags Bit-field (see flag constants below): +// 0x80 LL_ZERO_CODE_FLAG – body is zero-run-length encoded +// 0x40 LL_RELIABLE_FLAG – sender expects a packet ACK +// 0x20 LL_RESENT_FLAG – this is a retransmission +// 0x10 LL_ACK_FLAG – piggybacked ACKs are appended +// at the tail of this packet +// 1-4 Packet ID Sequence number, U32 in network (big-endian) byte order. +// 5 Offset Byte offset from PHL_NAME to the start of the message +// body (past the message-ID bytes). Zero for most messages. +// 6+ Message ID Variable-length message type identifier: +// High-frequency (1 byte, values 0x01–0xFE) +// Medium-frequency (2 bytes, 0xFF hh) +// Low-frequency (4 bytes, 0xFF 0xFF hh ll) +// ... Body Message block data, described by the message template. +// Present only when LL_ZERO_CODE_FLAG is clear; otherwise +// the body (everything after byte 5) is zero-coded (see +// below). The 6-byte header is never zero-coded. +// +// Optional ACK tail (present when LL_ACK_FLAG is set in the flags byte): +// +// ... ACK IDs N packet-sequence IDs being acknowledged, each a U32 in +// network byte order, packed contiguously immediately before +// the ACK count byte. Read in reverse: walk backwards from +// just before the count byte, 4 bytes at a time. +// last ACK Count U8 giving N, the number of appended ACK IDs (max 255). +// This is the very last byte of the UDP payload. +// +// Zero-coding (applied when LL_ZERO_CODE_FLAG is set): +// +// Runs of zero bytes in the body are replaced by a two-byte token: +// 0x00 N – represents (N + 1) zero bytes, for N in 1..254 +// 0x00 0x00 N – represents (256 + N) zero bytes (wrap/overflow case) +// A literal 0x00 byte that starts no run is encoded as 0x00 0x00 0x00. +// The six-byte packet header is excluded from zero-coding and is always +// transmitted as-is. + // Constants const S32 MAX_MESSAGE_INTERNAL_NAME_SIZE = 255; const S32 MAX_BUFFER_SIZE = NET_BUFFER_SIZE; @@ -291,8 +334,11 @@ class LLMessageSystem : public LLMessageSenderInterface bool mBlockUntrustedInterface; LLHost mUntrustedInterface; + protected: + LLPacketRing mHighPriorityInbound; + LLPacketRing mLowPriorityInbound; + public: - LLPacketRing mPacketRing; LLReliablePacketParams mReliablePacketParams; // Set this flag to true when you want *very* verbose logs. @@ -334,7 +380,7 @@ class LLMessageSystem : public LLMessageSenderInterface U32 mReliablePacketsIn; // total reliable packets in U32 mReliablePacketsOut; // total reliable packets out - U32 mDroppedPackets; // total dropped packets in + U32 mLostPackets; // total reliable outbound packets declared lost U32 mResentPackets; // total resent packets out U32 mFailedResendPackets; // total resend failure packets out U32 mOffCircuitPackets; // total # of off-circuit packets rejected @@ -420,6 +466,25 @@ class LLMessageSystem : public LLMessageSenderInterface // returns total number of buffered packets after the drain S32 drainUdpSocket(); + // Inbound Packet-loss simulation controls + void dropPackets(U32 num_to_drop); + void setDropPercentage(F32 percent_to_drop); + + // UDP byte-accounting + S32 getActualInBytes() const { return mActualBytesIn; } + S32 getActualOutBytes() const { return mActualBytesOut; } + S32 getAndResetActualInBits() { S32 bits = mActualBytesIn * 8; mActualBytesIn = 0; return bits; } + S32 getAndResetActualOutBits() { S32 bits = mActualBytesOut * 8; mActualBytesOut = 0; return bits; } + + // Get number of "dropped" inbound packets + S32 getTotalNumDroppedPackets() const { return mNumDroppedPacketsTotal + mNumDroppedPackets; } + + S32 getNumBufferedPackets() const { return mHighPriorityInbound.getNumBufferedPackets() + mLowPriorityInbound.getNumBufferedPackets(); } + void dumpPacketRingStats(); + + // Send datap to host via mSocket (with SOCKS proxy support if enabled). + bool sendPacketToSocket(const char* datap, S32 data_size, LLHost host); + bool isMessageFast(const char *msg); bool isMessage(const char *msg) { @@ -754,7 +819,7 @@ class LLMessageSystem : public LLMessageSenderInterface S32 getReceiveBytes() const; S32 getUnackedListSize() const { return mUnackedListSize; } - F32 getBufferLoadRate() const { return mPacketRing.getBufferLoadRate(); } + F32 getBufferLoadRate() const; //const char* getCurrentSMessageName() const { return mCurrentSMessageName; } //const char* getCurrentSBlockName() const { return mCurrentSBlockName; } @@ -898,6 +963,32 @@ class LLMessageSystem : public LLMessageSenderInterface S32 mIncomingCompressedSize; // original size of compressed msg (0 if uncomp.) TPACKETID mCurrentRecvPacketID; // packet ID of current receive packet (for reporting) + // Socket I/O helpers + + // Receive one packet: pop from ring if buffered, else read from mSocket. + // Sets mLastSender and mLastReceivingIF. + // Returns packet_size, or 0 if no packet or packet was dropped. + S32 receivePacketOrDrop(char* datap); + + // Read one raw packet from mSocket into inbound message queues + // Returns packet_size (0 if no packet was available). + S32 bufferInboundPacket(); + + // Returns true if the next inbound packet should be intentionally dropped. + bool computeDrop(); + + // Returns true if pkt carries a high-priority message and should be queued + // in mHighPriorityInbound. + bool isHighPriorityMessage(const LLPacketBuffer& pkt) const; + + // Packet-loss simulation and byte-accounting state + S32 mActualBytesIn; + S32 mActualBytesOut; + F32 mDropPercentage; // % of inbound packets to drop + U32 mPacketsToDrop; // drop next N inbound packets + S32 mNumDroppedPackets; // inbound + S32 mNumDroppedPacketsTotal;// inbound + LLMessageBuilder* mMessageBuilder; LLTemplateMessageBuilder* mTemplateMessageBuilder; LLSDMessageBuilder* mLLSDMessageBuilder; diff --git a/indra/llmessage/net.cpp b/indra/llmessage/net.cpp index 2be5a9e5b63..0df38d040af 100644 --- a/indra/llmessage/net.cpp +++ b/indra/llmessage/net.cpp @@ -326,7 +326,7 @@ S32 receive_packet(int hSocket, char * receiveBuffer) return 0; if (WSAECONNRESET == WSAGetLastError()) return 0; - LL_INFOS() << "receivePacket() failed, Error: " << WSAGetLastError() << LL_ENDL; + LL_INFOS() << "receive_packet() failed, Error: " << WSAGetLastError() << LL_ENDL; } return nRet; diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 79752d23150..7f06f991b0b 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -5894,7 +5894,6 @@ void LLAppViewer::idleNetwork() pingMainloopTimeout("idleNetwork"); gObjectList.mNumNewObjects = 0; - S32 total_decoded = 0; static LLCachedControl speed_test(gSavedSettings, "SpeedTest", false); if (!speed_test()) @@ -5902,64 +5901,56 @@ void LLAppViewer::idleNetwork() LL_PROFILE_ZONE_NAMED_CATEGORY_NETWORK("idle network"); //LL_RECORD_BLOCK_TIME(FTM_IDLE_NETWORK); // decode LLTimer check_message_timer; - // Read all available packets from network const S64 frame_count = gFrameCount; // U32->S64 - F32 total_time = 0.0f; + S32 total_decoded = 0; + // Process packets from network + LockMessageChecker lmc(gMessageSystem); + while (lmc.checkAllMessages(frame_count, gServicePump)) { - bool needs_drain = false; - LockMessageChecker lmc(gMessageSystem); - while (lmc.checkAllMessages(frame_count, gServicePump)) - { - if (gDoDisconnect) - { - // We're disconnecting, don't process any more messages from the server - // We're usually disconnecting due to either network corruption or a - // server going down, so this is OK. - break; - } - - total_decoded++; + ++total_decoded; - if (total_decoded > MESSAGE_MAX_PER_FRAME) + // Time-box processing of network packets to prevent framerate catastrophe + if (check_message_timer.getElapsedTimeF32() >= CheckMessagesMaxTime) + { + // Drain the socket buffer so we know how many messages remain to process + S32 num_buffered_packets = gMessageSystem->drainUdpSocket(); + if (num_buffered_packets > total_decoded) { - needs_drain = true; - break; + // Grow CheckMessagesMaxTime until we process more packets each frame than arrive. + // This might spiral out of control on very slow computers on fast networks when + // the bandwidth settings are too high. There is a mechanism for providing backpressure + // to network bandwidth but it may be inadequate for the task + // (see LLViewerThrottle::updateDynamicThrottle() for more details). + CheckMessagesMaxTime *= 1.035f; // 3.5% ~= 2x in 20 frames, ~8x in 60 frames } - - // Prevent slow packets from completely destroying the frame rate. - // This usually happens due to clumps of avatars taking huge amount - // of network processing time (which needs to be fixed, but this is - // a good limit anyway). - total_time = check_message_timer.getElapsedTimeF32(); - if (total_time >= CheckMessagesMaxTime) + else if (num_buffered_packets == 0) { - needs_drain = true; - break; + // Reset CheckMessagesMaxTime to default value + CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME; } + break; } - if (needs_drain || gMessageSystem->mPacketRing.getNumBufferedPackets() > 0) + + if (total_decoded > MESSAGE_MAX_PER_FRAME) { - // Rather than allow packets to silently backup on the socket - // we drain them into our own buffer so we know how many exist. - S32 num_buffered_packets = gMessageSystem->drainUdpSocket(); - if (num_buffered_packets > 0) - { - // Increase CheckMessagesMaxTime so that we will eventually catch up - CheckMessagesMaxTime *= 1.035f; // 3.5% ~= 2x in 20 frames, ~8x in 60 frames - } + // MESSAGE_MAX_PER_FRAME is very high (400) + // We expect to run out of time before reaching here, but just in case... + gMessageSystem->drainUdpSocket(); + break; } - else + + if (gDoDisconnect) { - // Reset CheckMessagesMaxTime to default value - CheckMessagesMaxTime = CHECK_MESSAGES_DEFAULT_MAX_TIME; + // We're disconnecting so no need to process packets. + break; } + } - // Handle per-frame message system processing. + // Handle per-frame message system processing. - static LLCachedControl ack_collection_time(gSavedSettings, "AckCollectTime", 0.1f); - lmc.processAcks(ack_collection_time()); - } + static LLCachedControl ack_collection_time(gSavedSettings, "AckCollectTime", 0.1f); + lmc.processAcks(ack_collection_time()); } add(LLStatViewer::NUM_NEW_OBJECTS, gObjectList.mNumNewObjects); diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 6a85ca069a6..cd4c9d40b1f 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -401,7 +401,7 @@ void do_startup_frame() break; } } - if (needs_drain || gMessageSystem->mPacketRing.getNumBufferedPackets() > 0) + if (needs_drain || gMessageSystem->getNumBufferedPackets() > 0) { gMessageSystem->drainUdpSocket(); } @@ -785,7 +785,7 @@ bool idle_startup() F32 dropPercent = gSavedSettings.getF32("PacketDropPercentage"); - msg->mPacketRing.setDropPercentage(dropPercent); + msg->setDropPercentage(dropPercent); } LL_INFOS("AppInit") << "Message System Initialized." << LL_ENDL; diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index ff111dd1bc7..7f36244685a 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -2298,7 +2298,7 @@ class LLAdvancedDropPacket : public view_listener_t { bool handleEvent(const LLSD& userdata) { - gMessageSystem->mPacketRing.dropPackets(1); + gMessageSystem->dropPackets(1); return true; } }; diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index 6490fe76a29..3b2796572be 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -791,7 +791,7 @@ void send_viewer_stats(bool include_preferences) LLSD &fail = body["stats"]["failures"]; fail["send_packet"] = (S32) gMessageSystem->mSendPacketFailureCount; - fail["dropped"] = (S32) gMessageSystem->mDroppedPackets; + fail["dropped"] = (S32) gMessageSystem->getTotalNumDroppedPackets(); fail["resent"] = (S32) gMessageSystem->mResentPackets; fail["failed_resends"] = (S32) gMessageSystem->mFailedResendPackets; fail["off_circuit"] = (S32) gMessageSystem->mOffCircuitPackets; diff --git a/indra/newview/llviewerthrottle.cpp b/indra/newview/llviewerthrottle.cpp index 3ccfbea6e23..90a60c7a941 100644 --- a/indra/newview/llviewerthrottle.cpp +++ b/indra/newview/llviewerthrottle.cpp @@ -45,8 +45,8 @@ using namespace LLOldEvents; const F32 MAX_FRACTIONAL = 1.5f; const F32 MIN_FRACTIONAL = 0.2f; -const F32 MIN_BANDWIDTH = 50.f; -const F32 MAX_BANDWIDTH = 6000.f; +const F32 MIN_BANDWIDTH = 50.f; // Kbps +const F32 MAX_BANDWIDTH = 6000.f; // Kbps const F32 STEP_FRACTIONAL = 0.1f; const F32 HIGH_BUFFER_LOAD_TRESHOLD = 1.f; const F32 LOW_BUFFER_LOAD_TRESHOLD = 0.8f; @@ -244,6 +244,8 @@ void LLViewerThrottle::sendToSim() const F32 LLViewerThrottle::getMaxBandwidthKbps() { + // Why are thse different than the constants with the same names higher up? + // Anybody know? -- Leviathan constexpr F32 MIN_BANDWIDTH = 100.0f; // 100 Kbps constexpr F32 MAX_BANDWIDTH = 10000.0f; // 10 Mbps @@ -309,6 +311,21 @@ void LLViewerThrottle::resetDynamicThrottle() void LLViewerThrottle::updateDynamicThrottle() { + // User configurable bandwidth settings are merely vague aspirations. Translating those + // to what should be sent to the servers is complicated. Servers will tend to spike data + // transmission upon arrival, packets will arrive faster than we can process them, and this + // can overflow the buffer, which causes packet loss. + // + // In an attempt to avoid catastrophe we periodically measure buffer load and packet loss + // and then transmit a modified desired bandwidth to the server. Unfortunately, this system + // does not work well for spikey data bursts because: + // (1) the response takes too long (up to 5 seconds) to kick in + // (2) once it starts it tends to ramp up too slowly + // (3) it doesn't know which region is providing the flood; it just assumes it is + // all from the main region + // + // TODO: fix those ^^^ problems + if (mUpdateTimer.getElapsedTimeF32() < DYNAMIC_UPDATE_DURATION) { return; @@ -347,5 +364,7 @@ void LLViewerThrottle::updateDynamicThrottle() LL_INFOS() << "Easing network throttle to " << mCurrentBandwidth << LL_ENDL; } + // Now that we've used mBufferLoadRate we reset it to zero because otherwise it only + // increases (see clamping behavior in setBufferLoadRate()). mBufferLoadRate = 0; } diff --git a/indra/newview/llviewerthrottle.h b/indra/newview/llviewerthrottle.h index ef898a97d75..7ff0a7d1f00 100644 --- a/indra/newview/llviewerthrottle.h +++ b/indra/newview/llviewerthrottle.h @@ -77,8 +77,8 @@ class LLViewerThrottle static const std::string sNames[TC_EOF]; protected: - F32 mMaxBandwidth; - F32 mCurrentBandwidth; + F32 mMaxBandwidth; // bps + F32 mCurrentBandwidth; // bps F32 mBufferLoadRate = 0; LLViewerThrottleGroup mCurrent; diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp index d02694de7dc..cad36828d1a 100644 --- a/indra/newview/llworld.cpp +++ b/indra/newview/llworld.cpp @@ -794,10 +794,10 @@ void LLWorld::updateNetStats() S32 packets_in = gMessageSystem->mPacketsIn - mLastPacketsIn; S32 packets_out = gMessageSystem->mPacketsOut - mLastPacketsOut; - S32 packets_lost = gMessageSystem->mDroppedPackets - mLastPacketsLost; + S32 packets_lost = gMessageSystem->mLostPackets - mLastPacketsLost; - F64Bits actual_in_bits(gMessageSystem->mPacketRing.getAndResetActualInBits()); - F64Bits actual_out_bits(gMessageSystem->mPacketRing.getAndResetActualOutBits()); + F64Bits actual_in_bits(gMessageSystem->getAndResetActualInBits()); + F64Bits actual_out_bits(gMessageSystem->getAndResetActualOutBits()); add(LLStatViewer::MESSAGE_SYSTEM_DATA_IN, actual_in_bits); add(LLStatViewer::MESSAGE_SYSTEM_DATA_OUT, actual_out_bits); @@ -815,7 +815,7 @@ void LLWorld::updateNetStats() mLastPacketsIn = gMessageSystem->mPacketsIn; mLastPacketsOut = gMessageSystem->mPacketsOut; - mLastPacketsLost = gMessageSystem->mDroppedPackets; + mLastPacketsLost = gMessageSystem->mLostPackets; } @@ -838,7 +838,7 @@ void LLWorld::printPacketsLost() << " packets lost: " << cdp->getPacketsLost() << LL_ENDL; } } - LL_INFOS() << "Packets dropped by Packet Ring: " << gMessageSystem->mPacketRing.getNumDroppedPackets() << LL_ENDL; + LL_INFOS() << "Packets dropped by Packet Ring: " << gMessageSystem->getTotalNumDroppedPackets() << LL_ENDL; } void LLWorld::processCoarseUpdate(LLMessageSystem* msg, void** user_data) From 0be27809566c1378fa613aa7c42bfd2406a9f764 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 28 May 2026 03:15:43 +0300 Subject: [PATCH 079/112] #5855 Added a way to check triangles for all lods --- indra/newview/llfloaterobjectweights.cpp | 228 ++++++++++++++++-- indra/newview/llfloaterobjectweights.h | 21 ++ indra/newview/llselectmgr.cpp | 1 - indra/newview/llselectmgr.h | 1 - indra/newview/llviewerobject.cpp | 26 ++ indra/newview/llviewerobject.h | 2 + indra/newview/llvovolume.cpp | 13 +- indra/newview/llvovolume.h | 1 + .../default/xui/en/floater_object_weights.xml | 172 +++++++++++-- 9 files changed, 416 insertions(+), 49 deletions(-) diff --git a/indra/newview/llfloaterobjectweights.cpp b/indra/newview/llfloaterobjectweights.cpp index bd8530abd7e..cf89518c03d 100644 --- a/indra/newview/llfloaterobjectweights.cpp +++ b/indra/newview/llfloaterobjectweights.cpp @@ -33,8 +33,10 @@ #include "lltextbox.h" #include "llagent.h" +#include "llcallbacklist.h" #include "llviewerparcelmgr.h" #include "llviewerregion.h" +#include "llmeshrepository.h" static const std::string lod_strings[4] = { @@ -74,6 +76,14 @@ bool LLCrossParcelFunctor::apply(LLViewerObject* obj) LLFloaterObjectWeights::LLFloaterObjectWeights(const LLSD& key) : LLFloater(key), + mWeightsDirty(false), + mSelectionDirty(true), + mSelectionMeshDirty(true), + mSelectionLastLOD(-1), + mSelectionLastTris(0), + mSelectionLastArea(0), + mLastActiveLODRequests(0), + mSelectionRefreshTime(0.), mSelectedObjects(NULL), mSelectedPrims(NULL), mSelectedDownloadWeight(NULL), @@ -88,6 +98,13 @@ LLFloaterObjectWeights::LLFloaterObjectWeights(const LLSD& key) mTrianglesShown(nullptr), mPixelArea(nullptr) { + mSelectionConnection = LLSelectMgr::getInstance()->mUpdateSignal.connect( + [this]() + { + mSelectionDirty = true; + mSelectionMeshDirty = true; // assume that mesh data will arrive with a delay. + } + ); } LLFloaterObjectWeights::~LLFloaterObjectWeights() @@ -114,6 +131,11 @@ bool LLFloaterObjectWeights::postBuild() mTrianglesShown = getChild("triangles_shown"); mPixelArea = getChild("pixel_area"); + mHighLodTris = getChild("high_lod_tris"); + mMediumLodTris = getChild("medium_lod_tris"); + mLowLodTris = getChild("low_lod_tris"); + mLowestLodTris = getChild("lowest_lod_tris"); + return true; } @@ -122,6 +144,14 @@ void LLFloaterObjectWeights::onOpen(const LLSD& key) { refresh(); updateLandImpacts(LLViewerParcelMgr::getInstance()->getFloatingParcelSelection()->getParcel()); + + onIdleRefresh(this); + gIdleCallbacks.addFunction(onIdleRefresh, this); +} + +void LLFloaterObjectWeights::onClose(bool app_quitting) +{ + gIdleCallbacks.deleteFunction(onIdleRefresh, this); } // virtual @@ -131,8 +161,9 @@ void LLFloaterObjectWeights::onWeightsUpdate(const SelectionCost& selection_cost mSelectedPhysicsWeight->setText(llformat("%.1f", selection_cost.mPhysicsCost)); mSelectedServerWeight->setText(llformat("%.1f", selection_cost.mSimulationCost)); - S32 render_cost = LLSelectMgr::getInstance()->getSelection()->getSelectedObjectRenderCost(); - mSelectedDisplayWeight->setText(llformat("%d", render_cost)); + // onWeightsUpdate comes from a coroutine. + // Postpone LLSelectMgr operations as getSelectedObjectRenderCost is not coroutine-safe + mWeightsDirty = true; toggleWeightsLoadingIndicators(false); } @@ -152,20 +183,13 @@ void LLFloaterObjectWeights::setErrorStatus(S32 status, const std::string& reaso void LLFloaterObjectWeights::draw() { - // Normally it's a bad idea to set text and visibility inside draw - // since it can cause rect updates go to different, already drawn elements, - // but floater is very simple and these elements are supposed to be isolated + // This logic might be a bit too expensive for draw(), + // but this floater needs to react fast, even if it's + // detrimental to performance. Having callbacks like + // 'tris changed' in every selected object might be + // more impactful on performance. LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - if (selection->isEmpty()) - { - const std::string text = getString("nothing_selected"); - mLodLevel->setText(text); - mTrianglesShown->setText(text); - mPixelArea->setText(text); - - toggleRenderLoadingIndicators(false); - } - else + if (!selection->isEmpty() && !mSelectionDirty) { S32 object_lod = -1; bool multiple_lods = false; @@ -192,27 +216,71 @@ void LLFloaterObjectWeights::draw() } } - if (multiple_lods) + if (mSelectionLastTris != total_tris) { - mLodLevel->setText(getString("multiple_lods")); - toggleRenderLoadingIndicators(false); + mSelectionDirty = true; } - else if (object_lod < 0) + else if (mSelectionLastArea != pixel_area) { - // nodes are waiting for data - toggleRenderLoadingIndicators(true); + mSelectionDirty = true; } - else + else if (multiple_lods) { - mLodLevel->setText(getString(lod_strings[object_lod])); - toggleRenderLoadingIndicators(false); + mSelectionDirty |= mSelectionLastLOD != -1; + } + else if (mSelectionLastLOD != object_lod) + { + mSelectionDirty = true; } - mTrianglesShown->setText(llformat("%d", total_tris)); - mPixelArea->setText(llformat("%ld", (S64)pixel_area)); // value capped at 10M } LLFloater::draw(); } +void LLFloaterObjectWeights::onIdleRefresh(void* user_data) +{ + LLFloaterObjectWeights* self = (LLFloaterObjectWeights*)user_data; + + F64 current_time = LLTimer::getTotalSeconds(); + S32 current_lod_requests = LLMeshRepoThread::sActiveLODRequests.load(); + // Can't look for a specific mesh (no callback mechanics and we + // need multiple ones), so just update periodically if something loads + self->mSelectionMeshDirty |= (self->mLastActiveLODRequests > current_lod_requests); + bool throttle_elapsed = (current_time - self->mSelectionRefreshTime) >= 2.0; + + if (self->mLastActiveLODRequests < current_lod_requests) + { + // we are interested in finished downloads, + // so don't refresh if more requests are getting added. + self->mLastActiveLODRequests = current_lod_requests; + } + + if (self->mSelectionDirty) + { + // Always refresh on selection changes + self->refreshDataFromSelection(); + self->mSelectionDirty = false; + self->mSelectionRefreshTime = current_time; + // refreshDataFromSelection could have indirectly initiated more requests + self->mLastActiveLODRequests = LLMeshRepoThread::sActiveLODRequests.load(); + } + else if (self->mSelectionMeshDirty && throttle_elapsed) + { + // LOD requests count changed or mesh needs lod data reload + self->refreshDataFromSelection(); + self->mSelectionRefreshTime = current_time; + self->mSelectionMeshDirty = false; + // refreshDataFromSelection could have indirectly initiated more requests + self->mLastActiveLODRequests = LLMeshRepoThread::sActiveLODRequests.load(); + } + else if (self->mWeightsDirty) // also done in refreshDataFromSelection + { + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + S32 render_cost = selection->getSelectedObjectRenderCost(); + self->mSelectedDisplayWeight->setText(llformat("%d", render_cost)); + self->mWeightsDirty = false; + } +} + void LLFloaterObjectWeights::updateLandImpacts(const LLParcel* parcel) { if (!parcel || LLSelectMgr::getInstance()->getSelection()->isEmpty()) @@ -240,6 +308,101 @@ void LLFloaterObjectWeights::updateLandImpacts(const LLParcel* parcel) } } +void LLFloaterObjectWeights::refreshDataFromSelection() +{ + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + if (selection->isEmpty()) + { + const std::string text = getString("nothing_selected"); + mLodLevel->setText(text); + mTrianglesShown->setText(text); + mPixelArea->setText(text); + + mHighLodTris->setText(text); + mMediumLodTris->setText(text); + mLowLodTris->setText(text); + mLowestLodTris->setText(text); + + toggleRenderLoadingIndicators(false); + toggleLODLoadingIndicators(false); + } + else + { + S32 object_lod = -1; + bool multiple_lods = false; + S32 total_tris = 0; + F32 pixel_area = 0; + S32 high_tris = 0; + S32 medium_tris = 0; + S32 low_tris = 0; + S32 lowest_tris = 0; + for (LLObjectSelection::valid_root_iterator iter = selection->valid_root_begin(); + iter != selection->valid_root_end(); ++iter) + { + LLViewerObject* object = (*iter)->getObject(); + S32 lod = object->getLOD(); + if (object_lod < 0) + { + object_lod = lod; + } + else if (object_lod != lod) + { + multiple_lods = true; + } + + if (object->isRootEdit()) + { + total_tris += object->recursiveGetTriangleCount(); + pixel_area += object->getPixelArea(); + object->recursiveGetLODTriangleCount(high_tris, medium_tris, low_tris, lowest_tris); + } + } + + mSelectionLastTris = total_tris; + mSelectionLastArea = pixel_area; + if (multiple_lods) + { + mSelectionLastLOD = -1; + } + else + { + mSelectionLastLOD = object_lod; + } + + if (multiple_lods) + { + mLodLevel->setText(getString("multiple_lods")); + toggleRenderLoadingIndicators(false); + toggleLODLoadingIndicators(false); + } + else if (object_lod < 0) + { + // nodes are waiting for data + toggleRenderLoadingIndicators(true); + toggleLODLoadingIndicators(true); + } + else + { + mLodLevel->setText(getString(lod_strings[object_lod])); + toggleRenderLoadingIndicators(false); + toggleLODLoadingIndicators(false); + } + mTrianglesShown->setText(llformat("%d", total_tris)); + mPixelArea->setText(llformat("%ld", (S64)pixel_area)); // value capped at 10M + mHighLodTris->setText(llformat("%d", high_tris)); + mMediumLodTris->setText(llformat("%d", medium_tris)); + mLowLodTris->setText(llformat("%d", low_tris)); + mLowestLodTris->setText(llformat("%d", lowest_tris)); + } + + if (mWeightsDirty) + { + S32 render_cost = selection->getSelectedObjectRenderCost(); + mSelectedDisplayWeight->setText(llformat("%d", render_cost)); + mWeightsDirty = false; + } +} + void LLFloaterObjectWeights::refresh() { LLSelectMgr* sel_mgr = LLSelectMgr::getInstance(); @@ -341,6 +504,19 @@ void LLFloaterObjectWeights::toggleRenderLoadingIndicators(bool visible) mPixelArea->setVisible(!visible); } +void LLFloaterObjectWeights::toggleLODLoadingIndicators(bool visible) +{ + childSetVisible("high_lod_tris_loading_indicator", visible); + childSetVisible("medium_lod_tris_loading_indicator", visible); + childSetVisible("low_lod_tris_loading_indicator", visible); + childSetVisible("lowest_lod_tris_loading_indicator", visible); + + mHighLodTris->setVisible(!visible); + mMediumLodTris->setVisible(!visible); + mLowLodTris->setVisible(!visible); + mLowestLodTris->setVisible(!visible); +} + void LLFloaterObjectWeights::updateIfNothingSelected() { const std::string text = getString("nothing_selected"); diff --git a/indra/newview/llfloaterobjectweights.h b/indra/newview/llfloaterobjectweights.h index bda625564ba..dac87d2a106 100644 --- a/indra/newview/llfloaterobjectweights.h +++ b/indra/newview/llfloaterobjectweights.h @@ -61,13 +61,17 @@ class LLFloaterObjectWeights : public LLFloater, LLAccountingCostObserver bool postBuild() override; void onOpen(const LLSD& key) override; + void onClose(bool app_quitting) override; void onWeightsUpdate(const SelectionCost& selection_cost) override; void setErrorStatus(S32 status, const std::string& reason) override; void draw() override; + static void onIdleRefresh(void* user_data); + void updateLandImpacts(const LLParcel* parcel); + void refreshDataFromSelection(); void refresh() override; private: @@ -76,9 +80,19 @@ class LLFloaterObjectWeights : public LLFloater, LLAccountingCostObserver void toggleWeightsLoadingIndicators(bool visible); void toggleLandImpactsLoadingIndicators(bool visible); void toggleRenderLoadingIndicators(bool visible); + void toggleLODLoadingIndicators(bool visible); void updateIfNothingSelected(); + bool mWeightsDirty; + bool mSelectionDirty; + bool mSelectionMeshDirty; + F64 mSelectionRefreshTime; + S32 mSelectionLastLOD; + S32 mSelectionLastTris; + F32 mSelectionLastArea; + S32 mLastActiveLODRequests; + LLTextBox *mSelectedObjects; LLTextBox *mSelectedPrims; @@ -95,6 +109,13 @@ class LLFloaterObjectWeights : public LLFloater, LLAccountingCostObserver LLTextBox *mLodLevel; LLTextBox *mTrianglesShown; LLTextBox *mPixelArea; + + LLTextBox *mHighLodTris; + LLTextBox *mMediumLodTris; + LLTextBox *mLowLodTris; + LLTextBox *mLowestLodTris; + + boost::signals2::scoped_connection mSelectionConnection; }; #endif //LL_LLFLOATEROBJECTWEIGHTS_H diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index 1b64eab3c0a..72f92785fcc 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -252,7 +252,6 @@ LLSelectMgr::LLSelectMgr() LLSelectMgr::~LLSelectMgr() { clearSelections(); - mSlectionLodModChangedConnection.disconnect(); } void LLSelectMgr::clearSelections() diff --git a/indra/newview/llselectmgr.h b/indra/newview/llselectmgr.h index 11aad3b806f..792a37297ff 100644 --- a/indra/newview/llselectmgr.h +++ b/indra/newview/llselectmgr.h @@ -943,7 +943,6 @@ class LLSelectMgr : public LLEditMenuHandler, public LLSimpleton bool mForceSelection; std::vector mPauseRequests; - boost::signals2::connection mSlectionLodModChangedConnection; }; // *DEPRECATED: For callbacks or observers, use diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 7c26cb3c9fa..57915cca85a 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -4034,6 +4034,11 @@ U32 LLViewerObject::getTriangleCount(S32* vcount) const return 0; } +U32 LLViewerObject::getLODTriangleCount(S32 lod) +{ + return 0; +} + U32 LLViewerObject::getHighLODTriangleCount() { return 0; @@ -4055,6 +4060,27 @@ U32 LLViewerObject::recursiveGetTriangleCount(S32* vcount) const return total_tris; } +void LLViewerObject::recursiveGetLODTriangleCount(S32& high_lod, S32& medium_lod, S32& low_lod, S32& lowest_lod) +{ + high_lod = (S32)getLODTriangleCount(LLModel::LOD_HIGH); + medium_lod = (S32)getLODTriangleCount(LLModel::LOD_MEDIUM); + low_lod = (S32)getLODTriangleCount(LLModel::LOD_LOW); + lowest_lod = (S32)getLODTriangleCount(LLModel::LOD_IMPOSTOR); + LLViewerObject::const_child_list_t& child_list = getChildren(); + for (LLViewerObject::const_child_list_t::const_iterator iter = child_list.begin(); + iter != child_list.end(); ++iter) + { + LLViewerObject* childp = *iter; + if (childp) + { + high_lod += (S32)childp->getLODTriangleCount(LLModel::LOD_HIGH); + medium_lod += (S32)childp->getLODTriangleCount(LLModel::LOD_MEDIUM); + low_lod += (S32)childp->getLODTriangleCount(LLModel::LOD_LOW); + lowest_lod += (S32)childp->getLODTriangleCount(LLModel::LOD_IMPOSTOR); + } + } +} + // This is using the stored surface area for each volume (which // defaults to 1.0 for the case of everything except a sculpt) and // then scaling it linearly based on the largest dimension in the diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h index 465e221ae47..54cdf046c72 100644 --- a/indra/newview/llviewerobject.h +++ b/indra/newview/llviewerobject.h @@ -427,10 +427,12 @@ class LLViewerObject virtual F32 getStreamingCost() const; virtual bool getCostData(LLMeshCostData& costs) const; virtual U32 getTriangleCount(S32* vcount = NULL) const; + virtual U32 getLODTriangleCount(S32 lod); virtual U32 getHighLODTriangleCount(); F32 recursiveGetScaledSurfaceArea() const; U32 recursiveGetTriangleCount(S32* vcount = NULL) const; + void recursiveGetLODTriangleCount(S32 &high_lod, S32 &medium_lod, S32 &low_lod, S32 &lowest_lod); void setObjectCost(F32 cost); F32 getObjectCost(); diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 3b41ccb6fc3..076e5eb22e8 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -4445,7 +4445,7 @@ U32 LLVOVolume::getTriangleCount(S32* vcount) const return count; } -U32 LLVOVolume::getHighLODTriangleCount() +U32 LLVOVolume::getLODTriangleCount(S32 lod) { U32 ret = 0; @@ -4453,16 +4453,16 @@ U32 LLVOVolume::getHighLODTriangleCount() if (!isSculpted()) { - LLVolume* ref = LLPrimitive::getVolumeManager()->refVolume(volume->getParams(), 3); + LLVolume* ref = LLPrimitive::getVolumeManager()->refVolume(volume->getParams(), lod); ret = ref->getNumTriangles(); LLPrimitive::getVolumeManager()->unrefVolume(ref); } else if (isMesh()) { - LLVolume* ref = LLPrimitive::getVolumeManager()->refVolume(volume->getParams(), 3); + LLVolume* ref = LLPrimitive::getVolumeManager()->refVolume(volume->getParams(), lod); if (!ref->isMeshAssetLoaded() || ref->getNumVolumeFaces() == 0) { - gMeshRepo.loadMesh(this, volume->getParams(), LLModel::LOD_HIGH); + gMeshRepo.loadMesh(this, volume->getParams(), lod); } ret = ref->getNumTriangles(); LLPrimitive::getVolumeManager()->unrefVolume(ref); @@ -4475,6 +4475,11 @@ U32 LLVOVolume::getHighLODTriangleCount() return ret; } +U32 LLVOVolume::getHighLODTriangleCount() +{ + return getLODTriangleCount(LLModel::LOD_HIGH); +} + //static void LLVOVolume::preUpdateGeom() { diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h index b6044bc3190..00d6138512e 100644 --- a/indra/newview/llvovolume.h +++ b/indra/newview/llvovolume.h @@ -155,6 +155,7 @@ class LLVOVolume : public LLViewerObject /*virtual*/ bool getCostData(LLMeshCostData& costs) const override; /*virtual*/ U32 getTriangleCount(S32* vcount = NULL) const override; + /*virtual*/ U32 getLODTriangleCount(S32 lod) override; /*virtual*/ U32 getHighLODTriangleCount() override; /*virtual*/ bool lineSegmentIntersect(const LLVector4a& start, const LLVector4a& end, S32 face = -1, // which face to check, -1 = ALL_SIDES diff --git a/indra/newview/skins/default/xui/en/floater_object_weights.xml b/indra/newview/skins/default/xui/en/floater_object_weights.xml index 709fbdd27e8..e68c00a8d4f 100644 --- a/indra/newview/skins/default/xui/en/floater_object_weights.xml +++ b/indra/newview/skins/default/xui/en/floater_object_weights.xml @@ -2,14 +2,14 @@ + width="205"> @@ -48,7 +48,7 @@ name="objects" top_pad="3" value="--" - width="40" /> + width="45" /> + width="45" /> + width="185"/> + width="45" /> + width="45" /> + width="45" /> + width="45" /> + width="185"/> + width="45" /> + width="45" /> + width="45" /> + width="45" /> + + width="45" /> + width="45" /> + width="45" /> + + + + + + + + + + + + + + + + From e0b3b7fc3f07e0823726deba920db2ee5c382746 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 26 May 2026 00:53:11 +0300 Subject: [PATCH 080/112] #3729 Reduce inventory memory overhead UI elements already cache display name, getDisplayName() calls are rare and only come with other calls. For items, any UI calls already engage internal getItem optimizations, so doing getItem in getDisplayName() has negligible performance overhead while avoiding storing a dupplicate of item's name. This also cuts performance overhead from allocations, so it's a net benefit performance wise on first load. System folders do utilize display names that are different from default names, but they are not panel specific, they are global localizations. Store those in the model, not in the bridge. Usage example: LLFolderViewItem::postBuild() already calls getName after getDisplayName(), so getItem will utilize mLastItem either way and the only refresh needed happens if user renames the item. Without the user the update happens only once. --- indra/newview/llinventorybridge.cpp | 97 ++++++++++++++++------------- indra/newview/llinventorybridge.h | 11 ++-- indra/newview/llinventorypanel.cpp | 15 ++--- indra/newview/llviewerinventory.cpp | 49 ++++++++++++++- indra/newview/llviewerinventory.h | 22 ++++++- 5 files changed, 134 insertions(+), 60 deletions(-) diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index c8ea14a11e9..8e4e70474f0 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2001&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 @@ -249,11 +249,27 @@ const std::string& LLInvFVBridge::getName() const const std::string& LLInvFVBridge::getDisplayName() const { - if(mDisplayName.empty()) + if(mSearchableName.empty()) { - buildDisplayName(); + // first request of display name, build search string and cache it for later use + buildSearchableName(); } - return mDisplayName; + + LLInventoryModel* model = getInventoryModel(); + if (model) + { + LLViewerInventoryCategory* cat = model->getCategory(mUUID); + if (cat) + { + return cat->getDisplayName(); + } + LLViewerInventoryItem* item = model->getItem(mUUID); + if (item) + { + return item->getName(); + } + } + return LLStringUtil::null; } std::string LLInvFVBridge::getSearchableDescription() const @@ -2035,22 +2051,22 @@ PermissionMask LLItemBridge::getPermissionMask() const } // virtual -void LLItemBridge::buildDisplayName() const +void LLItemBridge::buildSearchableName() const { - if (getItem()) + LLViewerInventoryItem* item = getItem(); + if (item) { - mDisplayName.assign(getItem()->getName()); + // for items, display name matches item name + mSearchableName.assign(item->getName()); } else { - mDisplayName.assign(LLStringUtil::null); + mSearchableName.assign(LLStringUtil::null); } - - mSearchableName.assign(mDisplayName); mSearchableName.append(getLabelSuffix()); LLStringUtil::toUpper(mSearchableName); - // Name set, so trigger a sort + // Searchable and name set, so trigger a sort LLInventorySort sorter = static_cast(mRootViewModel).getSorter(); if (mParent && !sorter.isByDate()) { @@ -2371,39 +2387,17 @@ void LLFolderBridge::selectItem() } } -void LLFolderBridge::buildDisplayName() const +void LLFolderBridge::buildSearchableName() const { - LLFolderType::EType preferred_type = getPreferredType(); - - // *TODO: to be removed when database supports multi language. This is a - // temporary attempt to display the inventory folder in the user locale. - // mantipov: *NOTE: be sure this code is synchronized with LLFriendCardsManager::findChildFolderUUID - // it uses the same way to find localized string - - // HACK: EXT - 6028 ([HARD CODED]? Inventory > Library > "Accessories" folder) - // Translation of Accessories folder in Library inventory folder - bool accessories = false; - if(getName() == "Accessories") + LLViewerInventoryCategory* cat = gInventory.getCategory(getUUID()); + if (cat) { - //To ensure that Accessories folder is in Library we have to check its parent folder. - //Due to parent LLFolderViewFloder is not set to this item yet we have to check its parent via Inventory Model - LLInventoryCategory* cat = gInventory.getCategory(getUUID()); - if(cat) - { - const LLUUID& parent_folder_id = cat->getParentUUID(); - accessories = (parent_folder_id == gInventory.getLibraryRootFolderID()); - } + mSearchableName.assign(cat->getDisplayName()); } - - //"Accessories" inventory category has folder type FT_NONE. So, this folder - //can not be detected as protected with LLFolderType::lookupIsProtectedType - mDisplayName.assign(getName()); - if (accessories || LLFolderType::lookupIsProtectedType(preferred_type)) + else { - LLTrans::findString(mDisplayName, std::string("InvFolder ") + getName(), LLSD()); + mSearchableName.assign(LLStringUtil::null); } - - mSearchableName.assign(mDisplayName); mSearchableName.append(getLabelSuffix()); LLStringUtil::toUpper(mSearchableName); @@ -2417,6 +2411,8 @@ void LLFolderBridge::buildDisplayName() const std::string LLFolderBridge::getLabelSuffix() const { + // Folders, unlike items, have context dependent suffixes + // that may change as the folder is loaded static LLCachedControl xui_debug(gSavedSettings, "DebugShowXUINames", 0); if (mIsLoading && mTimeSinceRequestStart.getElapsedTimeF32() >= FOLDER_LOADING_MESSAGE_DELAY) @@ -6576,18 +6572,29 @@ void LLCallingCardBridge::refreshFolderViewItem() void LLCallingCardBridge::checkSearchBySuffixChanges() { - if (!mDisplayName.empty()) + if (!mSearchableName.empty()) { - // changes in mDisplayName are processed by rename function and here it will be always same + LLViewerInventoryItem* item = getItem(); + if (!item) + { + // checkSearchBySuffixChanges is only used by friend list + // so if item is not found, we removed the calling card or + // are no longer friends. + mSearchableName.clear(); + return; + } + + // changes in display name are processed by rename function and here it will be always same // suffixes are also of fixed length, and we are processing change of one at a time, // so it should be safe to use length (note: mSearchableName is capitalized) - auto old_length = mSearchableName.length(); - auto new_length = mDisplayName.length() + getLabelSuffix().length(); + size_t old_length = mSearchableName.length(); + const std::string& display_name = item->getName(); + size_t new_length = display_name.length() + getLabelSuffix().length(); if (old_length == new_length) { return; } - mSearchableName.assign(mDisplayName); + mSearchableName.assign(display_name); mSearchableName.append(getLabelSuffix()); LLStringUtil::toUpper(mSearchableName); if (new_lengthupdateServer(false); model->updateItem(new_item); model->notifyObservers(); - buildDisplayName(); + buildSearchableName(); if (isAgentAvatarValid()) { diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index decb2c05287..68e7a7f80e6 100644 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2001&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 @@ -87,7 +87,7 @@ class LLInvFVBridge : public LLFolderViewModelItemInventory virtual const LLUUID& getUUID() const { return mUUID; } virtual const LLUUID& getThumbnailUUID() const { return LLUUID::null; } virtual bool isFavorite() const { return false; } - virtual void clearDisplayName() { mDisplayName.clear(); } + virtual void clearSearchableName() { mSearchableName.clear(); } virtual void restoreItem() {} virtual void restoreToWorld() {} @@ -202,12 +202,11 @@ class LLInvFVBridge : public LLFolderViewModelItemInventory LLInventoryType::EType mInvType; bool mIsLink; LLTimer mTimeSinceRequestStart; - mutable std::string mDisplayName; mutable std::string mSearchableName; void purgeItem(LLInventoryModel *model, const LLUUID &uuid); void removeObject(LLInventoryModel *model, const LLUUID &uuid); - virtual void buildDisplayName() const {} + virtual void buildSearchableName() const {} }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -266,7 +265,7 @@ class LLItemBridge : public LLInvFVBridge protected: bool confirmRemoveItem(const LLSD& notification, const LLSD& response); virtual bool isItemPermissive() const; - virtual void buildDisplayName() const; + virtual void buildSearchableName() const; void doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb); private: @@ -287,7 +286,7 @@ class LLFolderBridge : public LLInvFVBridge void callback_dropItemIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryItem* inv_item); void callback_dropCategoryIntoFolder(const LLSD& notification, const LLSD& response, LLInventoryCategory* inv_category); - virtual void buildDisplayName() const; + virtual void buildSearchableName() const; virtual void performAction(LLInventoryModel* model, std::string action); virtual void openItem(); diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index 06dd8304163..e87ef025c65 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2001&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 @@ -581,8 +581,9 @@ void LLInventoryPanel::itemChanged(const LLUUID& item_id, U32 mask, const LLInve LLInvFVBridge* bridge = (LLInvFVBridge*)view_item->getViewModelItem(); if(bridge) { - // Clear the display name first, so it gets properly re-built during refresh() - bridge->clearDisplayName(); + // Clear the searchable name first, so it gets + // properly re-built during refresh() + bridge->clearSearchableName(); view_item->refresh(); } @@ -1676,7 +1677,7 @@ void LLInventoryPanel::onSelectionChange(const std::deque& it LLFolderBridge* prev_bridge = (LLFolderBridge*)prev_folder_item->getViewModelItem(); if(prev_bridge) { - prev_bridge->clearDisplayName(); + prev_bridge->clearSearchableName(); prev_bridge->setShowDescendantsCount(false); prev_folder_item->refresh(); } @@ -1685,7 +1686,7 @@ void LLInventoryPanel::onSelectionChange(const std::deque& it LLFolderBridge* bridge = (LLFolderBridge*)folder_item->getViewModelItem(); if(bridge) { - bridge->clearDisplayName(); + bridge->clearSearchableName(); bridge->setShowDescendantsCount(true); folder_item->refresh(); mPreviousSelectedFolder = bridge->getUUID(); @@ -1700,7 +1701,7 @@ void LLInventoryPanel::onSelectionChange(const std::deque& it LLFolderBridge* prev_bridge = (LLFolderBridge*)prev_folder_item->getViewModelItem(); if(prev_bridge) { - prev_bridge->clearDisplayName(); + prev_bridge->clearSearchableName(); prev_bridge->setShowDescendantsCount(false); prev_folder_item->refresh(); } @@ -1720,7 +1721,7 @@ void LLInventoryPanel::updateFolderLabel(const LLUUID& folder_id) LLFolderBridge* bridge = (LLFolderBridge*)folder_item->getViewModelItem(); if(bridge) { - bridge->clearDisplayName(); + bridge->clearSearchableName(); bridge->setShowDescendantsCount(true); folder_item->refresh(); } diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index efa3f5cd1e5..707b2f19936 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2002&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2014, 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 @@ -673,6 +673,25 @@ void LLViewerInventoryCategory::setVersion(S32 version) mVersion = version; } +const std::string& LLViewerInventoryCategory::getDisplayName() const +{ + if (mNeedsDisplayNameUpdate) + { + buildDisplayName(); + } + if (!mDisplayName.empty()) + { + return mDisplayName; + } + return getName(); +} + +void LLViewerInventoryCategory::invalidateDisplayName() +{ + mNeedsDisplayNameUpdate = true; + mDisplayName.clear(); +} + bool LLViewerInventoryCategory::fetch(S32 expiry_seconds) { if((VERSION_UNKNOWN == getVersion()) @@ -889,6 +908,34 @@ void LLViewerInventoryCategory::localizeName() LLLocalizedInventoryItemsDictionary::getInstance()->localizeInventoryObjectName(mName); } +void LLViewerInventoryCategory::buildDisplayName() const +{ + // Secure and library folders can't be renamed, + // so we only need to do this once. + mNeedsDisplayNameUpdate = false; + + //"Accessories" inventory category has folder type FT_NONE. So, this folder + //can not be detected as protected with LLFolderType::lookupIsProtectedType + // + // HACK: EXT - 6028 ([HARD CODED]? Inventory > Library > "Accessories" folder) + // Translation of Accessories folder in Library inventory folder + LLFolderType::EType preferred_type = getPreferredType(); + + bool is_accessories = false; + if (getName() == "Accessories") + { + // To ensure that Accessories folder is in Library we have to check its parent folder. + const LLUUID& parent_folder_id = getParentUUID(); + is_accessories = (parent_folder_id == gInventory.getLibraryRootFolderID()); + } + + if (is_accessories || LLFolderType::lookupIsProtectedType(preferred_type)) + { + // All predefined folders have translations in strings.xml. + LLTrans::findString(mDisplayName, std::string("InvFolder ") + getName(), LLSD()); + } +} + // virtual bool LLViewerInventoryCategory::unpackMessage(const LLSD& category) { diff --git a/indra/newview/llviewerinventory.h b/indra/newview/llviewerinventory.h index a42bdaa2b0a..f14850da5a1 100644 --- a/indra/newview/llviewerinventory.h +++ b/indra/newview/llviewerinventory.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2002&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 @@ -209,6 +209,13 @@ class LLViewerInventoryCategory : public LLInventoryCategory S32 getVersion() const; void setVersion(S32 version); + const std::string& getDisplayName() const; + // The display name gets cached on demand, so needs a cleanup method. + // But in practice only secure folders' display name mismatches + // actual name, and those folders can't be renamed, so in practice + // this is useless unless we want to free memory. + void invalidateDisplayName(); + // Returns true if a fetch was issued (not nessesary in progress). // no requests will happen during expiry_seconds even if fetch completed bool fetch(S32 expiry_seconds = 10); @@ -253,6 +260,19 @@ class LLViewerInventoryCategory : public LLInventoryCategory S32 mDescendentCount; EFetchType mFetching; LLFrameTimer mDescendentsRequested; + + // Display names are generated on demand and cached. + // buildDisplayName is essentially a way to localize + // system and library folders. + // + // TODO: This is on demand and mutable because that's how it + // worked in inventory bridge, before it was moved. + // But system folders always get loaded, it's likely better + // to just generate from the get go. + // Consider merging with localizeName. + void buildDisplayName() const; + mutable std::string mDisplayName; + mutable bool mNeedsDisplayNameUpdate = true; }; class LLInventoryCallback : public LLRefCount From ec82c45f0ebae8c09691d09baacf5662c89d969d Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 26 May 2026 20:39:07 +0300 Subject: [PATCH 081/112] #3729 Don't store tooltips for inventory Tooltips are just names, there is no point buffering them. --- indra/llui/llfolderviewitem.cpp | 17 ++++++++++++++--- indra/llui/llfolderviewitem.h | 7 ++++++- indra/newview/llinventorypanel.cpp | 3 --- .../llpanelmarketplaceinboxinventory.cpp | 4 +--- indra/newview/llpanelobjectinventory.cpp | 4 +--- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index fcc1964bd6d..ac759393d06 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2001&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 @@ -248,7 +248,6 @@ bool LLFolderViewItem::postBuild() // it also sets search strings so it requires a filter reset mLabel = utf8str_to_wstring(vmi->getDisplayName()); mIsFavorite = vmi->isFavorite() && !vmi->isItemInTrash(); - setToolTip(vmi->getName()); // Dirty the filter flag of the model from the view (CHUI-849) vmi->dirtyFilter(); @@ -363,7 +362,6 @@ void LLFolderViewItem::refresh() mLabel = utf8str_to_wstring(vmi.getDisplayName()); mLabelFontBuffer.reset(); mIsFavorite = vmi.isFavorite() && !vmi.isItemInTrash(); - setToolTip(vmi.getName()); // icons are slightly expensive to get, can be optimized // see LLInventoryIcon::getIcon() mIcon = vmi.getIcon(); @@ -623,6 +621,19 @@ const std::string& LLFolderViewItem::getName( void ) const return getViewModelItem() ? getViewModelItem()->getName() : noName; } +const std::string LLFolderViewItem::getToolTip() const +{ + // Return the item name as tooltip without storing it + if (!LLView::sDebugUnicode) + { + if (const LLFolderViewModelItem* vmi = getViewModelItem()) + { + return vmi->getName(); + } + } + return LLView::getToolTip(); +} + // LLView functionality bool LLFolderViewItem::handleRightMouseDown( S32 x, S32 y, MASK mask ) { diff --git a/indra/llui/llfolderviewitem.h b/indra/llui/llfolderviewitem.h index 258a806b913..8d6de2fee30 100644 --- a/indra/llui/llfolderviewitem.h +++ b/indra/llui/llfolderviewitem.h @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2001&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 @@ -253,6 +253,11 @@ class LLFolderViewItem : public LLView // viewed. This method will ask the viewed object itself. const std::string& getName( void ) const; + // Override to provide lazy tooltip generation without memory overhead + // Inventory can consist of millions of items, yet most stay invisible, + // much less need to show a tooltip, so avoid storing tooltips. + virtual const std::string getToolTip() const; + // This method returns the label displayed on the view. This // method was primarily added to allow sorting on the folder // contents possible before the entire view has been constructed. diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index e87ef025c65..0a6c95f3099 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -198,7 +198,6 @@ LLFolderView * LLInventoryPanel::createFolderRoot(LLUUID root_id ) p.title = getLabel(); p.rect = LLRect(0, 0, getRect().getWidth(), 0); p.parent_panel = this; - p.tool_tip = p.name; p.listener = mInvFVBridgeBuilder->createBridge( LLAssetType::AT_CATEGORY, LLAssetType::AT_CATEGORY, LLInventoryType::IT_CATEGORY, @@ -1067,7 +1066,6 @@ LLFolderViewFolder * LLInventoryPanel::createFolderViewFolder(LLInvFVBridge * br params.name = bridge->getDisplayName(); params.root = mFolderRoot.get(); params.listener = bridge; - params.tool_tip = params.name; params.allow_drop = allow_drop; params.font_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultColor); @@ -1085,7 +1083,6 @@ LLFolderViewItem * LLInventoryPanel::createFolderViewItem(LLInvFVBridge * bridge params.root = mFolderRoot.get(); params.listener = bridge; params.rect = LLRect (0, 0, 0, 0); - params.tool_tip = params.name; params.font_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultColor); params.font_highlight_color = (bridge->isLibraryItem() ? sLibraryColor : sDefaultHighlightColor); diff --git a/indra/newview/llpanelmarketplaceinboxinventory.cpp b/indra/newview/llpanelmarketplaceinboxinventory.cpp index 557c7bbd7ba..cb2906d633d 100644 --- a/indra/newview/llpanelmarketplaceinboxinventory.cpp +++ b/indra/newview/llpanelmarketplaceinboxinventory.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2009&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 @@ -85,7 +85,6 @@ LLFolderViewFolder * LLInboxInventoryPanel::createFolderViewFolder(LLInvFVBridge params.name = bridge->getDisplayName(); params.root = mFolderRoot.get(); params.listener = bridge; - params.tool_tip = params.name; params.font_color = item_color; params.font_highlight_color = item_color; params.allow_drop = allow_drop; @@ -104,7 +103,6 @@ LLFolderViewItem * LLInboxInventoryPanel::createFolderViewItem(LLInvFVBridge * b params.root = mFolderRoot.get(); params.listener = bridge; params.rect = LLRect (0, 0, 0, 0); - params.tool_tip = params.name; params.font_color = item_color; params.font_highlight_color = item_color; diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp index d27ce81e4f0..126f6d3776f 100644 --- a/indra/newview/llpanelobjectinventory.cpp +++ b/indra/newview/llpanelobjectinventory.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2002&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 @@ -1611,7 +1611,6 @@ void LLPanelObjectInventory::createViewsForCategory(LLInventoryObject::object_li params.name = obj->getName(); params.root = mFolders; params.listener = bridge; - params.tool_tip = params.name; params.font_color = item_color; params.font_highlight_color = item_color; view = LLUICtrlFactory::create(params); @@ -1625,7 +1624,6 @@ void LLPanelObjectInventory::createViewsForCategory(LLInventoryObject::object_li params.listener = bridge; params.creation_date = bridge->getCreationDate(); params.rect = LLRect(); - params.tool_tip = params.name; params.font_color = item_color; params.font_highlight_color = item_color; view = LLUICtrlFactory::create(params); From 0a7b6a8e506bddc514deaf814abe611fc5600849 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 26 May 2026 21:51:59 +0300 Subject: [PATCH 082/112] #3729 Avoid storing inventory item names in UI outside of debug --- indra/llui/llfolderview.cpp | 3 ++- indra/llui/llfolderviewitem.cpp | 13 +++++---- indra/newview/llinventorypanel.cpp | 27 +++++++++++++++++-- .../llpanelmarketplaceinboxinventory.cpp | 26 ++++++++++++++++-- 4 files changed, 57 insertions(+), 12 deletions(-) diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp index db4ab8487e5..758b4bb1765 100644 --- a/indra/llui/llfolderview.cpp +++ b/indra/llui/llfolderview.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2001&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 @@ -1423,6 +1423,7 @@ bool LLFolderView::search(LLFolderViewItem* first_item, const std::string &searc } } + // Note: for inventory getSearchableName should already be 'upper' case. std::string current_item_label(search_item->getViewModelItem()->getSearchableName()); LLStringUtil::toUpper(current_item_label); auto search_string_length = llmin(upper_case_string.size(), current_item_label.size()); diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index ac759393d06..c8ef4216778 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -190,7 +190,7 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) mItemHeight(p.item_height), mControlLabelRotation(0.f), mDragAndDropTarget(false), - mLabel(utf8str_to_wstring(p.name)), + mLabel(utf8str_to_wstring(p.name)), // will be immediately reset in postBuild() mRoot(p.root), mViewModelItem(p.listener), mIsMouseOverTitle(false), @@ -244,8 +244,10 @@ bool LLFolderViewItem::postBuild() llassert(vmi); // not supposed to happen, if happens, find out why and fix if (vmi) { - // getDisplayName() is expensive (due to internal getLabelSuffix() and name building) - // it also sets search strings so it requires a filter reset + // First getDisplayName() is expensive due to internal + // lazy getLabelSuffix(), it is however needed as it sets + // search string, which can later determine visibility. + // Refreshing a search string also requires a filter reset. mLabel = utf8str_to_wstring(vmi->getDisplayName()); mIsFavorite = vmi->isFavorite() && !vmi->isItemInTrash(); @@ -253,11 +255,8 @@ bool LLFolderViewItem::postBuild() vmi->dirtyFilter(); } - // Don't do full refresh on constructor if it is possible to avoid + // Don't do full refresh on constructor if it is possible to avoid, // it significantly slows down bulk view creation. - // Todo: Ideally we need to move getDisplayName() out of constructor as well. - // Like: make a logic that will let filter update search string, - // while LLFolderViewItem::arrange() updates visual part mSuffixNeedsRefresh = true; mLabelWidthDirty = true; return true; diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index 0a6c95f3099..301a0a0cc92 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -1063,7 +1063,18 @@ LLFolderViewFolder * LLInventoryPanel::createFolderViewFolder(LLInvFVBridge * br { LLFolderViewFolder::Params params(mParams.folder); - params.name = bridge->getDisplayName(); +#ifndef LL_RELEASE_FOR_DOWNLOAD + // Only usable for debug and first call has a large + // overhead from search string construction. + // As inventory names aren't unique and can change, + // there is little we can use them for in release builds. + params.name = bridge->getName(); +#else + // We don't have a source of unique names and inventory + // items can reach millions in quantity, just use + // a short descriptor + params.name = "fld"; +#endif params.root = mFolderRoot.get(); params.listener = bridge; params.allow_drop = allow_drop; @@ -1078,7 +1089,19 @@ LLFolderViewItem * LLInventoryPanel::createFolderViewItem(LLInvFVBridge * bridge { LLFolderViewItem::Params params(mParams.item); - params.name = bridge->getDisplayName(); +#ifndef LL_RELEASE_FOR_DOWNLOAD + // Only usable for debug and first call has a large + // overhead from search string construction. + // As inventory names aren't unique, are large and can change, + // there is little we can use them for in release builds. + // Prefer shorter + params.name = bridge->getName(); +#else + // We don't have a source of unique names and inventory + // items can reach millions in quantity, just use + // a short descriptor + params.name = "itm"; +#endif params.creation_date = bridge->getCreationDate(); params.root = mFolderRoot.get(); params.listener = bridge; diff --git a/indra/newview/llpanelmarketplaceinboxinventory.cpp b/indra/newview/llpanelmarketplaceinboxinventory.cpp index cb2906d633d..e0dbd9acd21 100644 --- a/indra/newview/llpanelmarketplaceinboxinventory.cpp +++ b/indra/newview/llpanelmarketplaceinboxinventory.cpp @@ -82,7 +82,18 @@ LLFolderViewFolder * LLInboxInventoryPanel::createFolderViewFolder(LLInvFVBridge LLInboxFolderViewFolder::Params params; - params.name = bridge->getDisplayName(); +#ifndef LL_RELEASE_FOR_DOWNLOAD + // Only usable for debug and first call has a large + // overhead from search string construction. + // As inventory names aren't unique and can change, + // there is little we can use them for in release builds. + params.name = bridge->getName(); +#else + // We don't have a source of unique names and inventory + // items can reach millions in quantity, just use + // a short descriptor + params.name = "fld"; +#endif params.root = mFolderRoot.get(); params.listener = bridge; params.font_color = item_color; @@ -98,7 +109,18 @@ LLFolderViewItem * LLInboxInventoryPanel::createFolderViewItem(LLInvFVBridge * b LLInboxFolderViewItem::Params params; - params.name = bridge->getDisplayName(); +#ifndef LL_RELEASE_FOR_DOWNLOAD + // Only usable for debug and first call has a large + // overhead from search string construction. + // As inventory names aren't unique and can change, + // there is little we can use them for in release builds. + params.name = bridge->getName(); +#else + // We don't have a source of unique names and inventory + // items can reach millions in quantity, just use + // a short descriptor + params.name = "itm"; +#endif params.creation_date = bridge->getCreationDate(); params.root = mFolderRoot.get(); params.listener = bridge; From 34540cce4f2d504740ea997016b4b06b80ae7adf Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 28 May 2026 19:18:21 +0300 Subject: [PATCH 083/112] #3729 Don't initialize inbox views if inbox isn't visible --- indra/newview/skins/default/xui/en/panel_inbox_inventory.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/newview/skins/default/xui/en/panel_inbox_inventory.xml b/indra/newview/skins/default/xui/en/panel_inbox_inventory.xml index 18dcc866491..eeb37eddcf2 100644 --- a/indra/newview/skins/default/xui/en/panel_inbox_inventory.xml +++ b/indra/newview/skins/default/xui/en/panel_inbox_inventory.xml @@ -13,6 +13,7 @@ border="false" bevel_style="none" show_item_link_overlays="true" + preinitialize_views="false" > From edf982b7a4efbee1ba75349c460e7040e8d04428 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 29 May 2026 03:03:31 +0300 Subject: [PATCH 084/112] #5719 Detect hybernation #2 --- indra/llwindow/llwindowcallbacks.cpp | 8 ++++++++ indra/llwindow/llwindowcallbacks.h | 2 ++ indra/llwindow/llwindowwin32.cpp | 15 +++++++------- indra/newview/llappviewer.cpp | 29 +++++++++++++++++----------- indra/newview/llappviewer.h | 2 ++ indra/newview/llviewerwindow.cpp | 18 +++++++++++++++++ indra/newview/llviewerwindow.h | 2 ++ 7 files changed, 58 insertions(+), 18 deletions(-) diff --git a/indra/llwindow/llwindowcallbacks.cpp b/indra/llwindow/llwindowcallbacks.cpp index 62674337513..8d1eebe33d4 100644 --- a/indra/llwindow/llwindowcallbacks.cpp +++ b/indra/llwindow/llwindowcallbacks.cpp @@ -72,6 +72,14 @@ void LLWindowCallbacks::handlePreCloseRequest() { } +void LLWindowCallbacks::handleCloseRequestCanceled() +{ +} + +void LLWindowCallbacks::handleSuspendRequest() +{ +} + 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 457087448f3..390e3ff93a5 100644 --- a/indra/llwindow/llwindowcallbacks.h +++ b/indra/llwindow/llwindowcallbacks.h @@ -43,6 +43,8 @@ class LLWindowCallbacks 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(); + virtual void handleCloseRequestCanceled(); + virtual void handleSuspendRequest(); // 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 52e0cdd5d56..79cdf8b67a9 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -2657,21 +2657,22 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ // Viewer can't function in hibernation, try to shut down. // The system allows approximately two seconds for an // application to handle this notification. + + // Mark app as potentially closing, to minimize issues if OS does not recover. + window_imp->mCallbacks->handlePreCloseRequest(); 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); - } + window_imp->mCallbacks->handleSuspendRequest(); }); + // Window thread normally doesn't block main thread, but OS can suspend + // immediately if we don't wait. + // Keep OS from suspending to give a chance to send stats. 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. + window_imp->mCallbacks->handleCloseRequestCanceled(); return TRUE; case PBT_APMPOWERSTATUSCHANGE: diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 7f06f991b0b..6b5c0ea6b07 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -4055,8 +4055,10 @@ void LLAppViewer::processMarkerFiles() // - 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 + // - Potentially killed by task manager or computer + // didn't recover from hibernation (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; bool marker_is_same_version = true; @@ -4227,21 +4229,21 @@ void LLAppViewer::processMarkerFiles() } } // If 'close' marker is found, viewer either started shutdown but - // failed, or viewer got killed by task manager. + // failed, OS did not recover from hibernation 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. + // Unfortunately we can't reliably distinguish + // task manager's case from genuine shutdown, so we + // have to report all of them as the same thing. + // Todo: but we can distinguish hibernation, might want + // to simply not report it as an issue. 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" + gLastExecEvent = LAST_EXEC_OS_EVENT; + LL_INFOS("MarkerFile") << "'Close' marker '" << close_marker_file << "' found, setting LastExecEvent to OS_EVENT" << LL_ENDL; } } @@ -4472,6 +4474,11 @@ void LLAppViewer::abortQuit() mClosingFloaters = false; } +void LLAppViewer::sendViewerStatistics() +{ + send_viewer_stats(false); +} + void LLAppViewer::migrateCacheDirectory() { #if LL_WINDOWS || LL_DARWIN diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index b404ec2c03c..cde58b0850d 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -79,6 +79,7 @@ typedef enum LAST_EXEC_INIT, LAST_EXEC_UNKNOWN, LAST_EXEC_LOGOUT_UNKNOWN, + LAST_EXEC_OS_EVENT, LAST_EXEC_COUNT } eLastExecEvent; @@ -113,6 +114,7 @@ class LLAppViewer : public LLApp const LLSD& substitutions = LLSD()); // Display an error dialog and forcibly quit. void earlyExitNoNotify(); // Do not display error dialog then forcibly quit. void abortQuit(); // Called to abort a quit request. + void sendViewerStatistics(); bool quitRequested() { return mQuitRequested; } bool logoutRequestSent() { return mLogoutRequestSent; } diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index dbf9fe6bf24..abd7096f506 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -1474,7 +1474,25 @@ void LLViewerWindow::handlePreCloseRequest() { LLAppViewer::instance()->createCloseRequestMarker(); } +} +void LLViewerWindow::handleCloseRequestCanceled() +{ + // WINDOW THREAD! since we need this to act fast. + if (!LLApp::isExiting() && !LLApp::isStopped()) + { + LLAppViewer::instance()->removeCloseRequestMarker(); + } +} + +void LLViewerWindow::handleSuspendRequest() +{ + LLAppViewer::instance()->sendViewerStatistics(); + // Todo: this should send a disconnect request as viewer + // can't keep heartbeat up while suspended and will get + // disconnected within a minute. + // Add a disconnect here once 'prevent OS from sleeping' + // feature is ready. } bool LLViewerWindow::handleCloseRequest(LLWindow *window, bool from_user) diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h index ff5da371ec4..b68956d853b 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -203,6 +203,8 @@ class LLViewerWindow : public LLWindowCallbacks /*virtual*/ bool handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); /*virtual*/ bool handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); /*virtual*/ void handlePreCloseRequest(); + /*virtual*/ void handleCloseRequestCanceled(); + /*virtual*/ void handleSuspendRequest(); /*virtual*/ bool handleCloseRequest(LLWindow *window, bool from_user); /*virtual*/ bool handleSessionExit(LLWindow* window); /*virtual*/ void handleQuit(LLWindow *window); From c30ad11e4bdec787025e930a36f3522cf326cc43 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 1 Jun 2026 16:33:07 +0300 Subject: [PATCH 085/112] #5798 fix displaying outfit Search results and update default filtering --- indra/newview/app_settings/settings.xml | 2 +- indra/newview/lloutfitslist.cpp | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index ccaa12b8ef3..8086afb8658 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -14416,7 +14416,7 @@ Type Boolean Value - 0 + 1 OutfitOperationsTimeout diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp index 7db79c70106..a5137984f24 100644 --- a/indra/newview/lloutfitslist.cpp +++ b/indra/newview/lloutfitslist.cpp @@ -519,6 +519,22 @@ void LLOutfitsList::resetItemSelection(LLWearableItemsList* list, const LLUUID& list->resetSelection(); mItemSelected = false; signalSelectionOutfitUUID(category_id); + + // If filtering was applied while tab was collapsed, item visibility is updated but the parent tab height might not be updated. + // Force rearrange to recompute the height, when tab is expanded. + static LLCachedControl show_all_items(gSavedSettings, "OutfitListFilterFullList", 1); + if (!show_all_items) + { + outfits_map_t::const_iterator tab_iter = mOutfitsMap.find(category_id); + if (tab_iter != mOutfitsMap.end()) + { + LLOutfitAccordionCtrlTab* tab = tab_iter->second; + if (tab && tab->getDisplayChildren()) + { + list->notify(LLSD().with("rearrange", true)); + } + } + } } void LLOutfitsList::onChangeOutfitSelection(LLWearableItemsList* list, const LLUUID& category_id) @@ -1648,7 +1664,7 @@ bool LLOutfitListSortMenu::onEnable(LLSD::String param) } else if ("show_entire_outfit" == param) { - static LLCachedControl filter_mode(gSavedSettings, "OutfitListFilterFullList", 0); + static LLCachedControl filter_mode(gSavedSettings, "OutfitListFilterFullList", 1); return filter_mode; } From e8e4c5f6cc4f250cc5bc01d26ae0c2207a6554c4 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:25:01 +0300 Subject: [PATCH 086/112] #5877 Fix truncated Favorites bar text --- indra/newview/skins/default/xui/en/panel_navigation_bar.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/newview/skins/default/xui/en/panel_navigation_bar.xml b/indra/newview/skins/default/xui/en/panel_navigation_bar.xml index 4d924a6c244..fcfd2357b58 100644 --- a/indra/newview/skins/default/xui/en/panel_navigation_bar.xml +++ b/indra/newview/skins/default/xui/en/panel_navigation_bar.xml @@ -196,8 +196,8 @@ text_color="LtGray" tool_tip="Drag Landmarks here for quick access to your favorite places in Second Life!" top="13" - valign="bottom" - width="290"> + valign="bottom" + width="310"> Places you save to your favorites bar will appear here. From 3e4502cbfa5b9b1b749ea52ca0168f90ee7a5e98 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 3 Jun 2026 00:32:13 +0300 Subject: [PATCH 087/112] #3342 Fix "Cut" and "Copy" options for objects in notecards --- indra/llui/lltextbase.h | 1 + indra/llui/lltexteditor.h | 2 +- indra/newview/llviewertexteditor.cpp | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index bc39d9732c5..62f984b8fc9 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -327,6 +327,7 @@ class LLTextBase public: friend class LLTextSegment; friend class LLNormalTextSegment; + friend class LLEmbeddedItemSegment; friend class LLUICtrlFactory; typedef boost::signals2::signal is_friend_signal_t; diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index d9742db34d6..43f47a404c4 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -208,6 +208,7 @@ class LLTextEditor : void setShowContextMenu(bool show) { mShowContextMenu = show; } bool getShowContextMenu() const { return mShowContextMenu; } + void showContextMenu(S32 x, S32 y); void showEmojiHelper(); void hideEmojiHelper(); @@ -219,7 +220,6 @@ class LLTextEditor : LLWString getConvertedText() const; protected: - void showContextMenu(S32 x, S32 y); void drawPreeditMarker(); void removeCharOrTab(); diff --git a/indra/newview/llviewertexteditor.cpp b/indra/newview/llviewertexteditor.cpp index 95e34b4df16..a58d7546ce4 100644 --- a/indra/newview/llviewertexteditor.cpp +++ b/indra/newview/llviewertexteditor.cpp @@ -249,6 +249,25 @@ class LLEmbeddedItemSegment : public LLTextSegment /*virtual*/ bool canEdit() const { return false; } + /*virtual*/ bool handleRightMouseDown(S32 x, S32 y, MASK mask) + { + bool show_menu = !mEditor.hasSelection() + || (mEditor.mSelectionStart == mStart && mEditor.mSelectionEnd == mStart + 1); + if (show_menu && mEditor.getShowContextMenu()) + { + // User clicked an item, user expects the menu to be + // in 'context' for the item. Change selection to match. + // Todo: Might be better to 'smartly' deselect here + // and to have an object specific menu. + mEditor.setCursorPos(mStart + 1); + mEditor.mSelectionStart = mStart; + mEditor.mSelectionEnd = mStart + 1; + + mEditor.showContextMenu(x, y); + return true; + } + return false; + } /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask) { From 0797d76b7aec1e1b8fe45866cdb61147b3f531e5 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Sat, 30 May 2026 01:27:29 +0300 Subject: [PATCH 088/112] Make platform specific files visible in the IDE --- indra/llwindow/CMakeLists.txt | 117 ++++++++++++++++++++++++---------- indra/newview/CMakeLists.txt | 71 +++++++++++++++++++++ 2 files changed, 155 insertions(+), 33 deletions(-) diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt index 08b3df87abe..13a7592e8f2 100644 --- a/indra/llwindow/CMakeLists.txt +++ b/indra/llwindow/CMakeLists.txt @@ -48,6 +48,46 @@ set(viewer_HEADER_FILES llmousehandler.h ) +# Platform-specific files for IDE visibility (excluded from build on other platforms) +set(macosx_SOURCE_FILES + llkeyboardmacosx.cpp + llwindowmacosx.cpp + llwindowmacosx-objc.mm + llopenglview-objc.mm + ) + +set(macosx_HEADER_FILES + llkeyboardmacosx.h + llwindowmacosx.h + llwindowmacosx-objc.h + llopenglview-objc.h + llappdelegate-objc.h + ) + +set(linux_SOURCE_FILES + llkeyboardsdl.cpp + llwindowsdl.cpp + ) + +set(linux_HEADER_FILES + llkeyboardsdl.h + llwindowsdl.h + ) + +set(windows_SOURCE_FILES + llwindowwin32.cpp + lldxhardware.cpp + llkeyboardwin32.cpp + lldragdropwin32.cpp + ) + +set(windows_HEADER_FILES + llwindowwin32.h + lldxhardware.h + llkeyboardwin32.h + lldragdropwin32.h + ) + set(llwindow_LINK_LIBRARIES llcommon llimage @@ -63,14 +103,8 @@ set(llwindow_LINK_LIBRARIES # Libraries on which this library depends, needed for Linux builds # Sort by high-level to low-level if (LINUX) - list(APPEND viewer_SOURCE_FILES - llkeyboardsdl.cpp - llwindowsdl.cpp - ) - list(APPEND viewer_HEADER_FILES - llkeyboardsdl.h - llwindowsdl.h - ) + list(APPEND viewer_SOURCE_FILES ${linux_SOURCE_FILES}) + list(APPEND viewer_HEADER_FILES ${linux_HEADER_FILES}) if (BUILD_HEADLESS) set(llwindowheadless_LINK_LIBRARIES @@ -88,19 +122,8 @@ if (LINUX) endif (LINUX) if (DARWIN) - list(APPEND llwindow_SOURCE_FILES - llkeyboardmacosx.cpp - llwindowmacosx.cpp - llwindowmacosx-objc.mm - llopenglview-objc.mm - ) - list(APPEND llwindow_HEADER_FILES - llkeyboardmacosx.h - llwindowmacosx.h - llwindowmacosx-objc.h - llopenglview-objc.h - llappdelegate-objc.h - ) + list(APPEND llwindow_SOURCE_FILES ${macosx_SOURCE_FILES}) + list(APPEND llwindow_HEADER_FILES ${macosx_HEADER_FILES}) # We use a bunch of deprecated system APIs. set_source_files_properties( @@ -113,18 +136,8 @@ endif (DARWIN) if (WINDOWS) - list(APPEND llwindow_SOURCE_FILES - llwindowwin32.cpp - lldxhardware.cpp - llkeyboardwin32.cpp - lldragdropwin32.cpp - ) - list(APPEND llwindow_HEADER_FILES - llwindowwin32.h - lldxhardware.h - llkeyboardwin32.h - lldragdropwin32.h - ) + list(APPEND llwindow_SOURCE_FILES ${windows_SOURCE_FILES}) + list(APPEND llwindow_HEADER_FILES ${windows_HEADER_FILES}) list(APPEND llwindow_LINK_LIBRARIES comdlg32 # Common Dialogs for ChooseColor ole32 @@ -167,10 +180,48 @@ endif (llwindow_HEADER_FILES) list(APPEND viewer_SOURCE_FILES ${viewer_HEADER_FILES}) + # Collect all platform files for IDE visibility, excluding already-added current platform + set(ide_visibility_files "") + if (NOT DARWIN) + list(APPEND ide_visibility_files ${macosx_SOURCE_FILES} ${macosx_HEADER_FILES}) + endif() + if (NOT LINUX) + list(APPEND ide_visibility_files ${linux_SOURCE_FILES} ${linux_HEADER_FILES}) + endif() + if (NOT WINDOWS) + list(APPEND ide_visibility_files ${windows_SOURCE_FILES} ${windows_HEADER_FILES}) + endif() + add_library (llwindow ${llwindow_SOURCE_FILES} ${viewer_SOURCE_FILES} + # Add non-platform files for IDE visibility only + ${ide_visibility_files} + ) + +# Mark non-platform files as excluded from build +if (WINDOWS) + set_source_files_properties( + ${macosx_SOURCE_FILES} + ${linux_SOURCE_FILES} + PROPERTIES + HEADER_FILE_ONLY TRUE + ) +elseif (DARWIN) + set_source_files_properties( + ${windows_SOURCE_FILES} + ${linux_SOURCE_FILES} + PROPERTIES + HEADER_FILE_ONLY TRUE + ) +elseif (LINUX) + set_source_files_properties( + ${macosx_SOURCE_FILES} + ${windows_SOURCE_FILES} + PROPERTIES + HEADER_FILE_ONLY TRUE ) +endif() if (SDL_FOUND) set_property(TARGET llwindow diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 18a40bcf1eb..663b932f3b1 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1495,6 +1495,38 @@ if (DARWIN) list(APPEND viewer_SOURCE_FILES ${viewer_RESOURCE_FILES}) endif (DARWIN) +# Platform-specific files for IDE visibility (excluded from build on other platforms) +set(macosx_viewer_SOURCE_FILES + llappviewermacosx.cpp + llappviewermacosx-objc.mm + llfilepicker_mac.mm + llappdelegate-objc.mm + ) + +set(macosx_viewer_HEADER_FILES + llappviewermacosx.h + llappviewermacosx-objc.h + llfilepicker_mac.h + ) + +set(linux_viewer_SOURCE_FILES + llappviewerlinux.cpp + llappviewerlinux_api_dbus.cpp + ) + +set(linux_viewer_HEADER_FILES + ) + +set(windows_viewer_SOURCE_FILES + llappviewerwin32.cpp + llwindebug.cpp + ) + +set(windows_viewer_HEADER_FILES + llappviewerwin32.h + llwindebug.h + ) + if (LINUX) LIST(APPEND viewer_SOURCE_FILES llappviewerlinux.cpp) set_source_files_properties( @@ -1651,6 +1683,45 @@ if (WINDOWS) endif (WINDOWS) +# Add all platform-specific files to the target for IDE visibility +# Collect non-platform files for IDE visibility only +set(ide_visibility_files "") +if (NOT DARWIN) + list(APPEND ide_visibility_files ${macosx_viewer_SOURCE_FILES} ${macosx_viewer_HEADER_FILES}) +endif() +if (NOT LINUX) + list(APPEND ide_visibility_files ${linux_viewer_SOURCE_FILES} ${linux_viewer_HEADER_FILES}) +endif() +if (NOT WINDOWS) + list(APPEND ide_visibility_files ${windows_viewer_SOURCE_FILES} ${windows_viewer_HEADER_FILES}) +endif() + +list(APPEND viewer_SOURCE_FILES ${ide_visibility_files}) + +# Mark non-platform files as excluded from build +if (WINDOWS) + set_source_files_properties( + ${macosx_viewer_SOURCE_FILES} + ${linux_viewer_SOURCE_FILES} + PROPERTIES + HEADER_FILE_ONLY TRUE + ) +elseif (DARWIN) + set_source_files_properties( + ${windows_viewer_SOURCE_FILES} + ${linux_viewer_SOURCE_FILES} + PROPERTIES + HEADER_FILE_ONLY TRUE + ) +elseif (LINUX) + set_source_files_properties( + ${macosx_viewer_SOURCE_FILES} + ${windows_viewer_SOURCE_FILES} + PROPERTIES + HEADER_FILE_ONLY TRUE + ) +endif() + # Add the xui files. This is handy for searching for xui elements # from within the IDE. file(GLOB_RECURSE viewer_XUI_FILES LIST_DIRECTORIES FALSE From 1832026b5896968bac100e67235ad242a40f2157 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Sat, 30 May 2026 01:35:57 +0300 Subject: [PATCH 089/112] #5856 An option to prevent hibernation --- indra/newview/app_settings/settings.xml | 11 + indra/newview/llagent.cpp | 9 + indra/newview/llappviewer.cpp | 48 ++- indra/newview/llappviewer.h | 15 +- indra/newview/llappviewerlinux.cpp | 302 ++++++++++++++++++ indra/newview/llappviewerlinux.h | 10 + indra/newview/llappviewermacosx-objc.h | 1 + indra/newview/llappviewermacosx-objc.mm | 66 ++++ indra/newview/llappviewermacosx.cpp | 7 + indra/newview/llappviewermacosx.h | 1 + indra/newview/llappviewerwin32.cpp | 50 +++ indra/newview/llappviewerwin32.h | 1 + indra/newview/llviewerwindow.cpp | 30 +- .../xui/en/panel_preferences_setup.xml | 36 +++ 14 files changed, 578 insertions(+), 9 deletions(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 8086afb8658..b1e161182ee 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -14429,6 +14429,17 @@ Value 180 + OSHibernationMode + + Comment + Whether to prevent OS from hibernating. 0 - can hibernate; 1 - can't hibernate, can turn screen off; 2 - can't hibernate, can't turn screen off + Persist + 1 + Type + S32 + Value + 0 + HeightUnits Comment diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 31814d8f4f2..35d60e6595e 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -1552,6 +1552,8 @@ void LLAgent::setAFK() setControlFlags(AGENT_CONTROL_AWAY | AGENT_CONTROL_STOP); gAwayTimer.start(); } + + LLAppViewer::instance()->setPermitOSHibernation(true); } //----------------------------------------------------------------------------- @@ -1570,6 +1572,13 @@ void LLAgent::clearAFK() sendAnimationRequest(ANIM_AGENT_AWAY, ANIM_REQUEST_STOP); clearControlFlags(AGENT_CONTROL_AWAY); } + + if (isAgentAvatarValid()) + { + // Only set this if agent is inworld, login screen + // shouldn't prevent hibernation. + LLAppViewer::instance()->setPermitOSHibernation(false); + } } //----------------------------------------------------------------------------- diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 6b5c0ea6b07..4a6739bb40f 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -633,6 +633,12 @@ bool LLAppViewer::sendURLToOtherInstance(const std::string& url) return false; } +//virtual +void LLAppViewer::setOSHibernationMode(eHibernationMode mode) +{ + // See OS specific files +} + //---------------------------------------------------------------------------- // LLAppViewer definition @@ -4474,9 +4480,12 @@ void LLAppViewer::abortQuit() mClosingFloaters = false; } -void LLAppViewer::sendViewerStatistics() +void LLAppViewer::sendViewerStatistics(bool include_preferences) { - send_viewer_stats(false); + if (!gDisconnected) + { + send_viewer_stats(include_preferences); + } } void LLAppViewer::migrateCacheDirectory() @@ -5857,6 +5866,29 @@ void LLAppViewer::outOfMemorySoftQuit() } } +void LLAppViewer::setPermitOSHibernation(bool permit) +{ + if (permit) + { + if (mCurrentHibernationMode != LL_HIBERNATE_MODE_DEFAULT) + { + // Will call OS specific code to let OS hibernate when idle + setOSHibernationMode(LL_HIBERNATE_MODE_DEFAULT); + mCurrentHibernationMode = LL_HIBERNATE_MODE_DEFAULT; + } + } + else + { + static LLCachedControl os_hibernation_mode(gSavedSettings, "OSHibernationMode", 0); + eHibernationMode mode = static_cast(os_hibernation_mode()); + if (mode != LL_HIBERNATE_MODE_DEFAULT && mCurrentHibernationMode != mode) + { + setOSHibernationMode(mode); + mCurrentHibernationMode = mode; + } + } +} + void LLAppViewer::idleNameCache() { // Neither old nor new name cache can function before agent has a region @@ -6064,6 +6096,9 @@ void LLAppViewer::disconnectViewer() // Pass the connection state to LLUrlEntryParcel not to attempt // parcel info requests while disconnected. LLUrlEntryParcel::setDisconnected(gDisconnected); + + // Restore default OS hibernation mode + setPermitOSHibernation(true); } void LLAppViewer::forceErrorLLError() @@ -6329,6 +6364,15 @@ void LLAppViewer::handleLoginComplete() // we logged in successfully, so save settings on logout LL_INFOS() << "Login successful, per account settings will be saved on log out." << LL_ENDL; mSavePerAccountSettings=true; + + // Don't allow hibernation while we're running + setPermitOSHibernation(false); + // Track 'hibernation' mode changes + mOSHibernationModeChangeConnection = gSavedSettings.getControl("OSHibernationMode")->getSignal()->connect([](LLControlVariable* control, const LLSD& new_val, const LLSD& old_val) + { + // setPermitOSHibernation will sort itself out based on new mode. + LLAppViewer::instance()->setPermitOSHibernation(false); + }); } //virtual diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index cde58b0850d..6ecafae0363 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -114,7 +114,7 @@ class LLAppViewer : public LLApp const LLSD& substitutions = LLSD()); // Display an error dialog and forcibly quit. void earlyExitNoNotify(); // Do not display error dialog then forcibly quit. void abortQuit(); // Called to abort a quit request. - void sendViewerStatistics(); + void sendViewerStatistics(bool include_preferences); bool quitRequested() { return mQuitRequested; } bool logoutRequestSent() { return mLogoutRequestSent; } @@ -269,6 +269,8 @@ class LLAppViewer : public LLApp // Note: mQuitRequested can be aborted by user. void outOfMemorySoftQuit(); + virtual void setPermitOSHibernation(bool permit); + #ifdef LL_DISCORD static void initDiscordSocial(); static void updateDiscordActivity(); @@ -285,6 +287,14 @@ class LLAppViewer : public LLApp virtual bool initSLURLHandler(); virtual bool sendURLToOtherInstance(const std::string& url); + typedef enum + { + LL_HIBERNATE_MODE_DEFAULT = 0, // Use the platform's default behavior. + LL_HIBERNATE_MODE_PREVENT = 1, + LL_HIBERNATE_MODE_PREVENT_SCREEN = 2, + } eHibernationMode; + virtual void setOSHibernationMode(eHibernationMode mode); + virtual bool initParseCommandLine(LLCommandLineParser& clp) { return true; } // Allow platforms to specify the command line args. @@ -391,6 +401,9 @@ class LLAppViewer : public LLApp LLAppCoreHttp mAppCoreHttp; bool mIsFirstRun; + + eHibernationMode mCurrentHibernationMode = LL_HIBERNATE_MODE_DEFAULT; + boost::signals2::scoped_connection mOSHibernationModeChangeConnection; }; // Globals with external linkage. From viewer.h diff --git a/indra/newview/llappviewerlinux.cpp b/indra/newview/llappviewerlinux.cpp index 89d19d180b5..fe0d6f6e7da 100644 --- a/indra/newview/llappviewerlinux.cpp +++ b/indra/newview/llappviewerlinux.cpp @@ -58,6 +58,10 @@ namespace void (*gOldTerminateHandler)() = NULL; } +// Initialize static members +guint32 LLAppViewerLinux::sPowerInhibitCookie = 0; +bool LLAppViewerLinux::sPowerInhibitActive = false; + static void exceptionTerminateHandler() { @@ -117,6 +121,11 @@ LLAppViewerLinux::LLAppViewerLinux() LLAppViewerLinux::~LLAppViewerLinux() { + // Clean up any power management inhibition on exit + if (sPowerInhibitActive) + { + uninhibitPowerManagement(); + } } bool LLAppViewerLinux::init() @@ -329,6 +338,299 @@ bool LLAppViewerLinux::sendURLToOtherInstance(const std::string& url) } #endif // LL_DBUS_ENABLED + +void LLAppViewerLinux::setOSHibernationMode(eHibernationMode mode) +{ + if (mode == LL_HIBERNATE_MODE_DEFAULT) + { + // Allow OS to sleep/hibernate - remove any inhibition + if (sPowerInhibitActive) + { + uninhibitPowerManagement(); + LL_INFOS("OS") << "Permitted OS hibernation/sleep" << LL_ENDL; + } + } + else if (mode == LL_HIBERNATE_MODE_PREVENT) + { + // Prevent system sleep, but allow display to turn off + // Release any existing inhibition first to allow mode switching + if (sPowerInhibitActive) + { + uninhibitPowerManagement(); + } + + if (inhibitPowerManagement(false)) + { + LL_INFOS("OS") << "Prevented OS hibernation/sleep, display sleep allowed" << LL_ENDL; + } + else + { + LL_WARNS("OS") << "Failed to prevent OS hibernation/sleep" << LL_ENDL; + } + } + else if (mode == LL_HIBERNATE_MODE_PREVENT_SCREEN) + { + // Prevent both system and display sleep + // Release any existing inhibition first to allow mode switching + if (sPowerInhibitActive) + { + uninhibitPowerManagement(); + } + + if (inhibitPowerManagement(true)) + { + LL_INFOS("OS") << "Prevented OS hibernation/sleep and display sleep" << LL_ENDL; + } + else + { + LL_WARNS("OS") << "Failed to prevent OS hibernation/sleep and display sleep" << LL_ENDL; + } + } +} + +// TODO: This is AI Generated!!!, needs review and testing. +bool LLAppViewerLinux::inhibitPowerManagement(bool inhibit_display) +{ +#if LL_DBUS_ENABLED + // Try to use D-Bus to inhibit power management via various desktop environment APIs + // This works with GNOME, KDE, XFCE, and most modern Linux desktop environments + + if (!grab_dbus_syms(DBUSGLIB_DYLIB_DEFAULT_NAME)) + { + LL_WARNS("OS") << "Failed to load D-Bus symbols for power management" << LL_ENDL; + return false; + } + + GError* error = nullptr; + DBusGConnection* bus = lldbus_g_bus_get(DBUS_BUS_SESSION, &error); + + if (!bus) + { + LL_WARNS("OS") << "Failed to connect to D-Bus session bus: " + << (error ? error->message : "unknown error") << LL_ENDL; + if (error) + g_error_free(error); + return false; + } + + // Try multiple power management services in order of preference + // 1. org.freedesktop.PowerManagement (older standard) + // 2. org.gnome.SessionManager (GNOME) + // 3. org.kde.Solid.PowerManagement (KDE) + + const char* services[] = { + "org.freedesktop.PowerManagement", + "org.gnome.SessionManager", + "org.kde.Solid.PowerManagement" + }; + + const char* paths[] = { + "/org/freedesktop/PowerManagement/Inhibit", + "/org/gnome/SessionManager", + "/org/kde/Solid/PowerManagement" + }; + + const char* interfaces[] = { + "org.freedesktop.PowerManagement.Inhibit", + "org.gnome.SessionManager", + "org.kde.Solid.PowerManagement" + }; + + const char* methods[] = { + "Inhibit", + "Inhibit", + "inhibit" + }; + + bool success = false; + + for (int i = 0; i < 3 && !success; ++i) + { + DBusGProxy* proxy = lldbus_g_proxy_new_for_name( + bus, + services[i], + paths[i], + interfaces[i] + ); + + if (!proxy) + continue; + + error = nullptr; + guint32 cookie = 0; + + if (i == 0) // freedesktop.PowerManagement + { + // Inhibit(application_name: s, reason: s) -> cookie: u + success = lldbus_g_proxy_call( + proxy, + methods[i], + &error, + G_TYPE_STRING, "Second Life Viewer", + G_TYPE_STRING, inhibit_display ? + "Viewer active - preventing system and display sleep" : + "Viewer active - preventing system sleep", + G_TYPE_INVALID, + G_TYPE_UINT, &cookie, + G_TYPE_INVALID + ); + } + else if (i == 1) // GNOME SessionManager + { + // Inhibit(app_id: s, toplevel_xid: u, reason: s, flags: u) -> cookie: u + // flags: 4 = suspend, 8 = idle (display), 12 = both + guint32 flags = inhibit_display ? 12 : 4; + success = lldbus_g_proxy_call( + proxy, + methods[i], + &error, + G_TYPE_STRING, "SecondLifeViewer", + G_TYPE_UINT, 0, // toplevel_xid (0 = none) + G_TYPE_STRING, inhibit_display ? + "Viewer active - preventing system and display sleep" : + "Viewer active - preventing system sleep", + G_TYPE_UINT, flags, + G_TYPE_INVALID, + G_TYPE_UINT, &cookie, + G_TYPE_INVALID + ); + } + else if (i == 2) // KDE Solid + { + // Different method signature for KDE + success = lldbus_g_proxy_call( + proxy, + methods[i], + &error, + G_TYPE_INVALID, + G_TYPE_INT, &cookie, + G_TYPE_INVALID + ); + } + + if (success) + { + sPowerInhibitCookie = cookie; + sPowerInhibitActive = true; + LL_INFOS("OS") << "Successfully inhibited power management using " + << services[i] << LL_ENDL; + } + else if (error) + { + LL_DEBUGS("OS") << "Failed to inhibit via " << services[i] + << ": " << error->message << LL_ENDL; + g_error_free(error); + error = nullptr; + } + + g_object_unref(proxy); + } + + return success; + +#else // !LL_DBUS_ENABLED + LL_WARNS("OS") << "Power management control not available - D-Bus support not enabled" << LL_ENDL; + return false; +#endif +} + +void LLAppViewerLinux::uninhibitPowerManagement() +{ +#if LL_DBUS_ENABLED + if (!sPowerInhibitActive || sPowerInhibitCookie == 0) + { + return; + } + + if (!grab_dbus_syms(DBUSGLIB_DYLIB_DEFAULT_NAME)) + { + LL_WARNS("OS") << "Failed to load D-Bus symbols for power management uninhibit" << LL_ENDL; + return; + } + + GError* error = nullptr; + DBusGConnection* bus = lldbus_g_bus_get(DBUS_BUS_SESSION, &error); + + if (!bus) + { + if (error) + g_error_free(error); + return; + } + + // Try to uninhibit using all services that might have been used + const char* services[] = { + "org.freedesktop.PowerManagement", + "org.gnome.SessionManager", + "org.kde.Solid.PowerManagement" + }; + + const char* paths[] = { + "/org/freedesktop/PowerManagement/Inhibit", + "/org/gnome/SessionManager", + "/org/kde/Solid/PowerManagement" + }; + + const char* interfaces[] = { + "org.freedesktop.PowerManagement.Inhibit", + "org.gnome.SessionManager", + "org.kde.Solid.PowerManagement" + }; + + const char* methods[] = { + "UnInhibit", + "Uninhibit", + "uninhibit" + }; + + bool success = false; + + for (int i = 0; i < 3; ++i) + { + DBusGProxy* proxy = lldbus_g_proxy_new_for_name( + bus, + services[i], + paths[i], + interfaces[i] + ); + + if (!proxy) + continue; + + error = nullptr; + + if (lldbus_g_proxy_call( + proxy, + methods[i], + &error, + G_TYPE_UINT, sPowerInhibitCookie, + G_TYPE_INVALID, + G_TYPE_INVALID)) + { + success = true; + LL_INFOS("OS") << "Successfully uninhibited power management using " + << services[i] << LL_ENDL; + } + else if (error) + { + LL_DEBUGS("OS") << "Failed to uninhibit via " << services[i] + << ": " << error->message << LL_ENDL; + g_error_free(error); + error = nullptr; + } + + g_object_unref(proxy); + + if (success) + break; + } + + sPowerInhibitCookie = 0; + sPowerInhibitActive = false; + +#endif // LL_DBUS_ENABLED +} + void LLAppViewerLinux::initCrashReporting(bool reportFreeze) { std::string cmd =gDirUtilp->getExecutableDir(); diff --git a/indra/newview/llappviewerlinux.h b/indra/newview/llappviewerlinux.h index dde223878da..6ab3682515d 100644 --- a/indra/newview/llappviewerlinux.h +++ b/indra/newview/llappviewerlinux.h @@ -68,6 +68,16 @@ class LLAppViewerLinux : public LLAppViewer virtual bool initSLURLHandler(); virtual bool sendURLToOtherInstance(const std::string& url); + virtual void setOSHibernationMode(eHibernationMode mode); + +private: + // Power management state tracking + static guint32 sPowerInhibitCookie; + static bool sPowerInhibitActive; + + // Helper methods for power management + bool inhibitPowerManagement(bool inhibit_display); + void uninhibitPowerManagement(); }; #if LL_DBUS_ENABLED diff --git a/indra/newview/llappviewermacosx-objc.h b/indra/newview/llappviewermacosx-objc.h index bfbb48dadbf..13b80561933 100644 --- a/indra/newview/llappviewermacosx-objc.h +++ b/indra/newview/llappviewermacosx-objc.h @@ -32,5 +32,6 @@ void force_ns_sxeption(); void register_url_schemes(); +void set_os_hibernation_mode(int mode); #endif // LL_LLAPPVIEWERMACOSX_OBJC_H diff --git a/indra/newview/llappviewermacosx-objc.mm b/indra/newview/llappviewermacosx-objc.mm index 75d6b56e3eb..ab4ae428b98 100644 --- a/indra/newview/llappviewermacosx-objc.mm +++ b/indra/newview/llappviewermacosx-objc.mm @@ -29,6 +29,7 @@ #endif #import +#import #include #include "llappviewermacosx-objc.h" @@ -63,3 +64,68 @@ void register_url_schemes() } } } + +// Add these as static variables at file scope +static IOPMAssertionID gPowerAssertionID = kIOPMNullAssertionID; + +void set_os_hibernation_mode(int mode) +{ + // Release existing assertion + if (gPowerAssertionID != kIOPMNullAssertionID) + { + IOReturn result = IOPMAssertionRelease(gPowerAssertionID); + if (result == kIOReturnSuccess) + { + gPowerAssertionID = kIOPMNullAssertionID; + NSLog(@"Permitted OS hibernation/sleep"); + } + else + { + NSLog(@"Failed to release power assertion: %d", result); + } + } + + if (mode == 1) + { + // Prevent OS from sleeping/hibernating + CFStringRef assertionName = CFSTR("Second Life Viewer"); + // kIOPMAssertionTypeNoIdleSleep prevents idle sleep + IOReturn result = IOPMAssertionCreateWithName( + kIOPMAssertionTypeNoIdleSleep, + kIOPMAssertionLevelOn, + assertionName, + &gPowerAssertionID + ); + + if (result == kIOReturnSuccess) + { + NSLog(@"Prevented OS hibernation/sleep, allow display sleep"); + } + else + { + NSLog(@"Failed to create power assertion: %d", result); + } + } + else if (mode == 2) + { + // Prevent OS from sleeping/hibernating, prevent screen from going off + CFStringRef assertionName = CFSTR("Second Life Viewer"); + // kIOPMAssertionTypeNoIdleSleep prevents idle sleep + // kIOPMAssertionTypeNoDisplaySleep prevents display sleep + IOReturn result = IOPMAssertionCreateWithName( + kIOPMAssertionTypeNoDisplaySleep, + kIOPMAssertionLevelOn, + assertionName, + &gPowerAssertionID + ); + + if (result == kIOReturnSuccess) + { + NSLog(@"Prevented OS hibernation/sleep or screen from turning off"); + } + else + { + NSLog(@"Failed to create power assertion: %d", result); + } + } +} diff --git a/indra/newview/llappviewermacosx.cpp b/indra/newview/llappviewermacosx.cpp index 830e2d4473d..1c01d068522 100644 --- a/indra/newview/llappviewermacosx.cpp +++ b/indra/newview/llappviewermacosx.cpp @@ -125,6 +125,7 @@ bool pumpMainLoop() void cleanupViewer() { + set_os_hibernation_mode(0); // restore default OS hibernation behavior if(!LLApp::isError()) { if (gViewerAppPtr) @@ -427,6 +428,12 @@ bool LLAppViewerMacOSX::initSLURLHandler() return true; } +void LLAppViewerMacOSX::setOSHibernationMode(eHibernationMode mode) +{ + // pass to objective-c++ + set_os_hibernation_mode((int)mode); +} + 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 35fc99dbd90..1153bd4191c 100644 --- a/indra/newview/llappviewermacosx.h +++ b/indra/newview/llappviewermacosx.h @@ -47,6 +47,7 @@ class LLAppViewerMacOSX : public LLAppViewer protected: virtual bool restoreErrorTrap(); virtual bool initSLURLHandler(); + virtual void setOSHibernationMode(eHibernationMode mode); std::string generateSerialNumber(); virtual bool initParseCommandLine(LLCommandLineParser& clp); diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 5184b0f0256..25aeb2a26e2 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -1020,6 +1020,56 @@ bool LLAppViewerWin32::sendURLToOtherInstance(const std::string& url) return false; } +void LLAppViewerWin32::setOSHibernationMode(eHibernationMode mode) +{ + // ES_CONTINUOUS tells Windows to reset the idle timer + // and restore normal operation + // ES_SYSTEM_REQUIRED prevents system sleep/hibernation + // ES_DISPLAY_REQUIRED prevents display sleep + + if (mode == LL_HIBERNATE_MODE_DEFAULT) + { + // Allow OS to hibernate - clear the previous execution state flags + // ES_CONTINUOUS without other flags allows the system to idle normally + SetThreadExecutionState(ES_CONTINUOUS); + LL_INFOS("OS") << "Permitted OS hibernation/sleep" << LL_ENDL; + } + else if (mode == LL_HIBERNATE_MODE_PREVENT) + { + // Prevent OS from hibernating while viewer is running + // ES_CONTINUOUS | ES_SYSTEM_REQUIRED keeps the system awake + EXECUTION_STATE result = SetThreadExecutionState( + ES_CONTINUOUS | ES_SYSTEM_REQUIRED + ); + if (result == NULL) + { + LL_WARNS("OS") << "Failed to prevent OS hibernation, error: " << GetLastError() << LL_ENDL; + } + else + { + LL_INFOS("OS") << "Prevented OS hibernation, but allowed display sleep" << LL_ENDL; + } + } + else if (mode == LL_HIBERNATE_MODE_PREVENT_SCREEN) + { + // Prevent OS from hibernating or turning screen off while viewer is running + // ES_CONTINUOUS | ES_SYSTEM_REQUIRED keeps the system awake + // ES_DISPLAY_REQUIRED keeps the display on + EXECUTION_STATE result = SetThreadExecutionState( + ES_CONTINUOUS | ES_SYSTEM_REQUIRED | ES_DISPLAY_REQUIRED + ); + + if (result == NULL) + { + LL_WARNS("OS") << "Failed to prevent OS hibernation and display sleep, error: " << GetLastError() << LL_ENDL; + } + else + { + LL_INFOS("OS") << "Prevented OS hibernation/sleep" << LL_ENDL; + } + } +} + bool LLAppViewerWin32::sendShutdownToOtherInstances(const std::wstring& install_dir) { // Velopack installs viewer like this: diff --git a/indra/newview/llappviewerwin32.h b/indra/newview/llappviewerwin32.h index 971907694a3..7c3c7a475a4 100644 --- a/indra/newview/llappviewerwin32.h +++ b/indra/newview/llappviewerwin32.h @@ -62,6 +62,7 @@ class LLAppViewerWin32 : public LLAppViewer bool restoreErrorTrap() override; bool sendURLToOtherInstance(const std::string& url) override; + void setOSHibernationMode(eHibernationMode mode) override; std::string generateSerialNumber(); diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index abd7096f506..4229ffcfb54 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -1487,12 +1487,30 @@ void LLViewerWindow::handleCloseRequestCanceled() void LLViewerWindow::handleSuspendRequest() { - LLAppViewer::instance()->sendViewerStatistics(); - // Todo: this should send a disconnect request as viewer - // can't keep heartbeat up while suspended and will get - // disconnected within a minute. - // Add a disconnect here once 'prevent OS from sleeping' - // feature is ready. + static LLCachedControl os_hibernation_mode(gSavedSettings, "OSHibernationMode", 0); + if (os_hibernation_mode == 0) + { + LL_INFOS() << "Got a 'suspend' event from OS" << LL_ENDL; + // Viewer doesn't handle hibernation. + // Just send statistics. + LLAppViewer::instance()->sendViewerStatistics(false); + } + else + { + LL_INFOS() << "Got a 'suspend' event from OS, disconnecting" << LL_ENDL; + // Viewer is set to prevent hibernation if agent isn't away. + // If we got here, likely Agent 'went' away then viewer got + // a hibernation message. + // We have a limited timeframe. Sends stats then disconnect. + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + LLAppViewer::instance()->sendViewerStatistics(true); + LLAppViewer::instance()->metricsSend(!gDisconnected); + // Make sure to show a message. + LLAppViewer::instance()->forceDisconnect(LLTrans::getString("YouHaveBeenDisconnected")); + } + } } bool LLViewerWindow::handleCloseRequest(LLWindow *window, bool from_user) diff --git a/indra/newview/skins/default/xui/en/panel_preferences_setup.xml b/indra/newview/skins/default/xui/en/panel_preferences_setup.xml index 2036ed75caa..d5f2f69df7b 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_setup.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_setup.xml @@ -246,5 +246,41 @@ + + Prevent OS from hibernating if not Away: + + + + + + From 5543a2a6cb5d4b2405f5f2d581c3917a0f60b8f8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 2 Jun 2026 16:00:46 +0000 Subject: [PATCH 090/112] #5856 Localize OS hibernation preference strings across locales --- .../skins/default/xui/da/panel_preferences_setup.xml | 8 ++++++++ .../skins/default/xui/de/panel_preferences_setup.xml | 8 ++++++++ .../skins/default/xui/es/panel_preferences_setup.xml | 8 ++++++++ .../skins/default/xui/fr/panel_preferences_setup.xml | 8 ++++++++ .../skins/default/xui/it/panel_preferences_setup.xml | 8 ++++++++ .../skins/default/xui/ja/panel_preferences_setup.xml | 8 ++++++++ .../skins/default/xui/pl/panel_preferences_setup.xml | 8 ++++++++ .../skins/default/xui/pt/panel_preferences_setup.xml | 8 ++++++++ .../skins/default/xui/ru/panel_preferences_setup.xml | 8 ++++++++ .../skins/default/xui/tr/panel_preferences_setup.xml | 8 ++++++++ .../skins/default/xui/zh/panel_preferences_setup.xml | 8 ++++++++ 11 files changed, 88 insertions(+) diff --git a/indra/newview/skins/default/xui/da/panel_preferences_setup.xml b/indra/newview/skins/default/xui/da/panel_preferences_setup.xml index 7be9a9d5552..981c35800f0 100644 --- a/indra/newview/skins/default/xui/da/panel_preferences_setup.xml +++ b/indra/newview/skins/default/xui/da/panel_preferences_setup.xml @@ -45,4 +45,12 @@ + + Forhindr OS i at gå i dvale, hvis du ikke er væk: + + + + + + diff --git a/indra/newview/skins/default/xui/de/panel_preferences_setup.xml b/indra/newview/skins/default/xui/de/panel_preferences_setup.xml index a8509cabac4..ef7ac09dbae 100644 --- a/indra/newview/skins/default/xui/de/panel_preferences_setup.xml +++ b/indra/newview/skins/default/xui/de/panel_preferences_setup.xml @@ -36,4 +36,12 @@ Proxy-Einstellungen: