From ac2cbdcc02efb8af88d91f02ec1b58022e4bf439 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 11 Sep 2025 22:32:03 +0300 Subject: [PATCH 01/18] #4660 Unable to upload GLTF+bin with spaces in the name --- indra/newview/gltf/accessor.cpp | 5 +++++ indra/newview/gltf/llgltfloader.cpp | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/indra/newview/gltf/accessor.cpp b/indra/newview/gltf/accessor.cpp index 03f7331893..f0ad3fa594 100644 --- a/indra/newview/gltf/accessor.cpp +++ b/indra/newview/gltf/accessor.cpp @@ -158,6 +158,11 @@ bool Buffer::prep(Asset& asset) { std::string dir = gDirUtilp->getDirName(asset.mFilename); std::string bin_file = dir + gDirUtilp->getDirDelimiter() + mUri; + if (!gDirUtilp->fileExists(bin_file)) + { + // Characters might be escaped in the URI + bin_file = dir + gDirUtilp->getDirDelimiter() + LLURI::unescape(mUri); + } llifstream file(bin_file.c_str(), std::ios::binary); if (!file.is_open()) diff --git a/indra/newview/gltf/llgltfloader.cpp b/indra/newview/gltf/llgltfloader.cpp index dd1d327683..af00b8a022 100644 --- a/indra/newview/gltf/llgltfloader.cpp +++ b/indra/newview/gltf/llgltfloader.cpp @@ -652,6 +652,14 @@ std::string LLGLTFLoader::processTexture(S32 texture_index, const std::string& t filename = filename.substr(pos + 1); } + std::string dir = gDirUtilp->getDirName(mFilename); + std::string full_path = dir + gDirUtilp->getDirDelimiter() + filename; + if (!gDirUtilp->fileExists(full_path) && filename.find("data:") == std::string::npos) + { + // Uri might be escaped + filename = LLURI::unescape(filename); + } + LL_INFOS("GLTF_IMPORT") << "Found texture: " << filename << " for material: " << material_name << LL_ENDL; LLSD args; From 8eb015666e0224574013d1338ddaf70a19355e5e Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 11 Sep 2025 20:38:09 +0300 Subject: [PATCH 02/18] #4651 Handle window's sessions termination --- indra/llwindow/llwindowcallbacks.cpp | 8 ++++- indra/llwindow/llwindowcallbacks.h | 3 +- indra/llwindow/llwindowmacosx.cpp | 2 +- indra/llwindow/llwindowsdl.cpp | 4 +-- indra/llwindow/llwindowwin32.cpp | 49 +++++++++++++++++++++++++++- indra/newview/llappviewer.cpp | 2 +- indra/newview/llappviewer.h | 10 +++--- indra/newview/llviewerwindow.cpp | 35 +++++++++++++++++--- indra/newview/llviewerwindow.h | 3 +- 9 files changed, 99 insertions(+), 17 deletions(-) diff --git a/indra/llwindow/llwindowcallbacks.cpp b/indra/llwindow/llwindowcallbacks.cpp index 195f68e08b..7331f50ba0 100644 --- a/indra/llwindow/llwindowcallbacks.cpp +++ b/indra/llwindow/llwindowcallbacks.cpp @@ -68,7 +68,13 @@ void LLWindowCallbacks::handleMouseLeave(LLWindow *window) return; } -bool LLWindowCallbacks::handleCloseRequest(LLWindow *window) +bool LLWindowCallbacks::handleCloseRequest(LLWindow *window, bool from_user) +{ + //allow the window to close + return true; +} + +bool LLWindowCallbacks::handleSessionExit(LLWindow* window) { //allow the window to close return true; diff --git a/indra/llwindow/llwindowcallbacks.h b/indra/llwindow/llwindowcallbacks.h index d812f93524..59dcdd3ade 100644 --- a/indra/llwindow/llwindowcallbacks.h +++ b/indra/llwindow/llwindowcallbacks.h @@ -42,7 +42,8 @@ public: virtual bool handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); virtual void handleMouseLeave(LLWindow *window); // return true to allow window to close, which will then cause handleQuit to be called - virtual bool handleCloseRequest(LLWindow *window); + virtual bool handleCloseRequest(LLWindow *window, bool from_user); + virtual bool handleSessionExit(LLWindow* window); // window is about to be destroyed, clean up your business virtual void handleQuit(LLWindow *window); virtual bool handleRightMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp index dadbc83f45..3314fa42a7 100644 --- a/indra/llwindow/llwindowmacosx.cpp +++ b/indra/llwindow/llwindowmacosx.cpp @@ -610,7 +610,7 @@ void callQuitHandler() { if (gWindowImplementation && gWindowImplementation->getCallbacks()) { - if(gWindowImplementation->getCallbacks()->handleCloseRequest(gWindowImplementation)) + if(gWindowImplementation->getCallbacks()->handleCloseRequest(gWindowImplementation, true)) { gWindowImplementation->getCallbacks()->handleQuit(gWindowImplementation); } diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index 7433ad6bd2..05be319c0b 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -1881,7 +1881,7 @@ void LLWindowSDL::gatherInput() { // *FIX: More informative dialog? LL_INFOS() << "Could not recreate context after resize! Quitting..." << LL_ENDL; - if(mCallbacks->handleCloseRequest(this)) + if(mCallbacks->handleCloseRequest(this, false)) { // Get the app to initiate cleanup. mCallbacks->handleQuit(this); @@ -1931,7 +1931,7 @@ void LLWindowSDL::gatherInput() break; case SDL_QUIT: - if(mCallbacks->handleCloseRequest(this)) + if(mCallbacks->handleCloseRequest(this, true)) { // Get the app to initiate cleanup. mCallbacks->handleQuit(this); diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index b127c9b48f..9194628b44 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -2461,10 +2461,13 @@ 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->post([=]() { // Will the app allow the window to close? - if (window_imp->mCallbacks->handleCloseRequest(window_imp)) + if (window_imp->mCallbacks->handleCloseRequest(window_imp, true)) { // Get the app to initiate cleanup. window_imp->mCallbacks->handleQuit(window_imp); @@ -2482,6 +2485,50 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ } return 0; } + case WM_QUERYENDSESSION: + { + // Generally means that OS is going to shut down or user is going to log off. + // Can use ShutdownBlockReasonCreate here. + LL_INFOS("Window") << "Received WM_QUERYENDSESSION with wParam: " << (U32)w_param << " lParam: " << (U32)l_param << LL_ENDL; + return TRUE; // 1 = ok to end session. 0 no longer works by itself, use ShutdownBlockReasonCreate + } + case WM_ENDSESSION: + { + // OS session is shutting down, initiate cleanup. + // Comes after WM_QUERYENDSESSION + LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_ENDSESSION"); + LL_INFOS("Window") << "Received WM_ENDSESSION with wParam: " << (U32)w_param << " lParam: " << (U32)l_param << LL_ENDL; + unsigned int end_session_flags = (U32)w_param; + if (end_session_flags == 0) + { + // session is not actually ending + return 0; + } + + if ((end_session_flags & ENDSESSION_CLOSEAPP) + || (end_session_flags & ENDSESSION_CRITICAL) + || (end_session_flags & ENDSESSION_LOGOFF)) + { + 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); + // The app is responsible for calling destroyWindow when done with GL + } + }); + // Give app a second to finish up. That's not enough for a clean exit, + // but better than nothing. + // Todo: sync this better, some kind of waitForResult? Can't wait forever, + // but can potentially use ShutdownBlockReasonCreate for a bigger delay. + ms_sleep(1000); + } + // Don't need to post quit or destroy window, + // if session is ending OS is going to take care of it. + return 0; + } case WM_COMMAND: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_COMMAND"); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index ac9464d86e..b36ed46a39 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -4195,7 +4195,7 @@ void LLAppViewer::earlyExit(const std::string& name, const LLSD& substitutions) // case where we need the viewer to exit without any need for notifications void LLAppViewer::earlyExitNoNotify() { - LL_WARNS() << "app_early_exit with no notification: " << LL_ENDL; + LL_WARNS() << "app_early_exit with no notification." << LL_ENDL; gDoDisconnect = true; finish_early_exit( LLSD(), LLSD() ); } diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index e7de2d9b28..24d55a00fe 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -149,6 +149,12 @@ public: std::string getWindowTitle() const; // The window display name. void forceDisconnect(const std::string& msg); // Force disconnection, with a message to the user. + + // sendSimpleLogoutRequest does not create a marker file. + // Meant for lost network case, and for forced shutdowns, + // to at least attempt to remove the ghost from the world. + void sendSimpleLogoutRequest(); + void badNetworkHandler(); // Cause a crash state due to bad network packet. bool hasSavedFinalSnapshot() { return mSavedFinalSnapshot; } @@ -310,10 +316,6 @@ private: void sendLogoutRequest(); void disconnectViewer(); - // Does not create a marker file. For lost network case, - // to at least attempt to remove the ghost from the world. - void sendSimpleLogoutRequest(); - // *FIX: the app viewer class should be some sort of singleton, no? // Perhaps its child class is the singleton and this should be an abstract base. static LLAppViewer* sInstance; diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 0edb7c70ea..42ad82d731 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -1461,18 +1461,43 @@ void LLViewerWindow::handleMouseLeave(LLWindow *window) LLToolTipMgr::instance().blockToolTips(); } -bool LLViewerWindow::handleCloseRequest(LLWindow *window) +bool LLViewerWindow::handleCloseRequest(LLWindow *window, bool from_user) { if (!LLApp::isExiting() && !LLApp::isStopped()) { - // User has indicated they want to close, but we may need to ask - // about modified documents. - LLAppViewer::instance()->userQuit(); - // Don't quit immediately + if (from_user) + { + // User has indicated they want to close, but we may need to ask + // about modified documents. + LLAppViewer::instance()->userQuit(); + // Don't quit immediately + } + else + { + // OS is asking us to quit, assume we have time and start cleanup + LLAppViewer::instance()->requestQuit(); + } } return false; } +bool LLViewerWindow::handleSessionExit(LLWindow* window) +{ + if (!LLApp::isExiting() && !LLApp::isStopped()) + { + // Viewer received WM_ENDSESSION and app will be killed soon if it doesn't respond + LLAppViewer* app = LLAppViewer::instance(); + app->sendSimpleLogoutRequest(); + app->earlyExitNoNotify(); + + // Not viewer's fault, remove marker files so + // that statistics won't consider this to be a crash + app->removeMarkerFiles(); + return false; + } + return true; +} + void LLViewerWindow::handleQuit(LLWindow *window) { if (gNonInteractive) diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h index d55c2d3817..61aa84394c 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -197,7 +197,8 @@ public: /*virtual*/ bool handleUnicodeChar(llwchar uni_char, MASK mask); // NOT going to handle extended /*virtual*/ bool handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); /*virtual*/ bool handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); - /*virtual*/ bool handleCloseRequest(LLWindow *window); + /*virtual*/ bool handleCloseRequest(LLWindow *window, bool from_user); + /*virtual*/ bool handleSessionExit(LLWindow* window); /*virtual*/ void handleQuit(LLWindow *window); /*virtual*/ bool handleRightMouseDown(LLWindow *window, LLCoordGL pos, MASK mask); /*virtual*/ bool handleRightMouseUp(LLWindow *window, LLCoordGL pos, MASK mask); From 3b3c85d1f378fdc83c7f613d5ee269b5db0db125 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Sat, 13 Sep 2025 00:06:47 +0300 Subject: [PATCH 03/18] #4651 Try distinguishing freezes from crashes --- indra/newview/llappviewer.cpp | 9 ++++++++- indra/newview/llappviewerwin32.cpp | 12 ++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index b36ed46a39..253c1268d3 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -3945,8 +3945,15 @@ void LLAppViewer::processMarkerFiles() else if (marker_is_same_version) { // 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" << LL_ENDL; + 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' + gLastExecEvent = LAST_EXEC_FROZE; +#else gLastExecEvent = LAST_EXEC_OTHER_CRASH; +#endif // LL_WINDOWS + } else { diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 8477bd3044..c29c9a4919 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -172,6 +172,18 @@ namespace << '/' << loc.mV[1] << '/' << loc.mV[2]))); } + + if (!LLAppViewer::instance()->isSecondInstance()) + { + std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME); + if (!LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB)) + { + // If marker doesn't exist, create a marker with 'other' code for next launch + // otherwise don't override existing file + // Any unmarked crashes will be considered as freezes + LLAppViewer::instance()->createErrorMarker(LAST_EXEC_OTHER_CRASH); + } + } } // MDSCB_EXCEPTIONCODE return false; From f64a41f09e971e1305aa51e6a8220a31e479cfbc Mon Sep 17 00:00:00 2001 From: Andrey Lihatskiy <118752495+marchcat@users.noreply.github.com> Date: Mon, 15 Sep 2025 18:24:21 +0300 Subject: [PATCH 04/18] LLLeap: handle partial lines in stderr (#4678) --- indra/llcommon/llleap.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index 662a2511cd..ada6b9519e 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -188,6 +188,17 @@ public: << childout.peek(0, peeklen) << "..." << LL_ENDL; } + // Handle any remaining stderr data (partial lines) the same way as we do + // for stdout: log it. + LLProcess::ReadPipe& childerr(mChild->getReadPipe(LLProcess::STDERR)); + if (childerr.size()) + { + LLProcess::ReadPipe::size_type + peeklen((std::min)(LLProcess::ReadPipe::size_type(50), childerr.size())); + LL_WARNS("LLLeap") << "Final stderr " << childerr.size() << " bytes: " + << childerr.peek(0, peeklen) << "..." << LL_ENDL; + } + // Kill this instance. MUST BE LAST before return! delete this; return false; From 6e24218680c15bbf2c7529cb23481a1e39888ca9 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 16 Sep 2025 17:27:17 +0300 Subject: [PATCH 05/18] #789 Cannot set the material to Default or None After setting material to Blank when blank material is present in inventory --- indra/newview/lltexturectrl.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/indra/newview/lltexturectrl.cpp b/indra/newview/lltexturectrl.cpp index 5507ef517a..c3a98b33ff 100644 --- a/indra/newview/lltexturectrl.cpp +++ b/indra/newview/lltexturectrl.cpp @@ -1012,6 +1012,8 @@ void LLFloaterTexturePicker::onBtnSetToDefault(void* userdata) { self->setImageID( self->getDefaultImageAssetID() ); self->setTentative(false); + // Deselect in case inventory has a selected item with the same id + self->mInventoryPanel->getRootFolder()->clearSelection(); } self->commitIfImmediateSet(); } @@ -1023,6 +1025,8 @@ void LLFloaterTexturePicker::onBtnBlank(void* userdata) self->setCanApply(true, true); self->setImageID( self->getBlankImageAssetID() ); self->setTentative(false); + // Deselect in case inventory has a selected item with the same id + self->mInventoryPanel->getRootFolder()->clearSelection(); self->commitIfImmediateSet(); } @@ -1034,6 +1038,8 @@ void LLFloaterTexturePicker::onBtnNone(void* userdata) self->setCanApply(true, true); self->setImageID( LLUUID::null ); self->setTentative(false); + // Deselect in case inventory has a selected item with null id + self->mInventoryPanel->getRootFolder()->clearSelection(); self->commitIfImmediateSet(); } From c743ea2b6dc60312b29f2fb5972171ead26ee448 Mon Sep 17 00:00:00 2001 From: Roxanne Skelly Date: Tue, 16 Sep 2025 10:36:26 -0700 Subject: [PATCH 06/18] Fix indexing problem with mac devices (#4676) * Fix indexing problem with mac devices This resulted in the wrong device being selected. Also, fix a shutdown crash where recording was not being stopped, hence the recording thread was still running on shutdown and crashed because it lost access to resources. Fix an issue with p2p calls where they're coming up muted even though the button indicates they are unmuted. * Always refresh device list on notification of device changes Even when the selected device doesn't change, we need to re-deploy it as it might have had characteristics (sampling rate, etc.) changed. Also, we need to redeploy when the Default device has changed --- indra/llwebrtc/llwebrtc.cpp | 48 +++++++++++++++++++--------------- indra/llwebrtc/llwebrtc_impl.h | 6 +++-- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp index edba2bee9a..23e1076765 100644 --- a/indra/llwebrtc/llwebrtc.cpp +++ b/indra/llwebrtc/llwebrtc.cpp @@ -350,6 +350,16 @@ void LLWebRTCImpl::init() void LLWebRTCImpl::terminate() { + mWorkerThread->BlockingCall( + [this]() + { + if (mDeviceModule) + { + mDeviceModule->ForceStopRecording(); + mDeviceModule->StopPlayout(); + } + }); + for (auto &connection : mPeerConnections) { connection->terminate(); @@ -368,8 +378,6 @@ void LLWebRTCImpl::terminate() { if (mDeviceModule) { - mDeviceModule->StopRecording(); - mDeviceModule->StopPlayout(); mDeviceModule->Terminate(); } mDeviceModule = nullptr; @@ -442,11 +450,7 @@ void LLWebRTCImpl::unsetDevicesObserver(LLWebRTCDevicesObserver *observer) void LLWebRTCImpl::workerDeployDevices() { int16_t recordingDevice = RECORD_DEVICE_DEFAULT; -#if WEBRTC_WIN int16_t recording_device_start = 0; -#else - int16_t recording_device_start = 1; -#endif if (mRecordingDevice != "Default") { @@ -455,6 +459,12 @@ void LLWebRTCImpl::workerDeployDevices() if (mRecordingDeviceList[i].mID == mRecordingDevice) { recordingDevice = i; +#if !WEBRTC_WIN + // linux and mac devices range from 1 to the end of the list, with the index 0 being the + // 'default' device. Windows has a special 'default' device and other devices are indexed + // from 0 + recordingDevice++; +#endif break; } } @@ -479,11 +489,7 @@ void LLWebRTCImpl::workerDeployDevices() mDeviceModule->InitRecording(); int16_t playoutDevice = PLAYOUT_DEVICE_DEFAULT; -#if WEBRTC_WIN int16_t playout_device_start = 0; -#else - int16_t playout_device_start = 1; -#endif if (mPlayoutDevice != "Default") { for (int16_t i = playout_device_start; i < mPlayoutDeviceList.size(); i++) @@ -491,6 +497,12 @@ void LLWebRTCImpl::workerDeployDevices() if (mPlayoutDeviceList[i].mID == mPlayoutDevice) { playoutDevice = i; +#if !WEBRTC_WIN + // linux and mac devices range from 1 to the end of the list, with the index 0 being the + // 'default' device. Windows has a special 'default' device and other devices are indexed + // from 0 + playoutDevice++; +#endif break; } } @@ -546,20 +558,14 @@ void LLWebRTCImpl::workerDeployDevices() void LLWebRTCImpl::setCaptureDevice(const std::string &id) { - if (mRecordingDevice != id) - { - mRecordingDevice = id; - deployDevices(); - } + mRecordingDevice = id; + deployDevices(); } void LLWebRTCImpl::setRenderDevice(const std::string &id) { - if (mPlayoutDevice != id) - { - mPlayoutDevice = id; - deployDevices(); - } + mPlayoutDevice = id; + deployDevices(); } // updateDevices needs to happen on the worker thread. @@ -609,7 +615,7 @@ void LLWebRTCImpl::updateDevices() void LLWebRTCImpl::OnDevicesUpdated() { - deployDevices(); + updateDevices(); } diff --git a/indra/llwebrtc/llwebrtc_impl.h b/indra/llwebrtc/llwebrtc_impl.h index 51d42c82b2..dee081119b 100644 --- a/indra/llwebrtc/llwebrtc_impl.h +++ b/indra/llwebrtc/llwebrtc_impl.h @@ -239,8 +239,10 @@ public: return 0; } int32_t StopRecording() override { - if (tuning_) return 0; // if we're tuning, disregard the StopRecording we get from disabling the streams - return inner_->StopRecording(); + // ignore stop recording as webrtc.lib will send one when streams shut down, + // even if there are other streams in place. Start/Stop recording are entirely + // controlled by the app + return 0; } int32_t ForceStartRecording() { return inner_->StartRecording(); } int32_t ForceStopRecording() { return inner_->StopRecording(); } From 892fb2d6e2ffc92636f381d3e817f9c5a71657dc Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 16 Sep 2025 19:44:45 +0300 Subject: [PATCH 07/18] #4651 Fix marker presence check --- indra/newview/llappviewer.cpp | 6 ++++++ indra/newview/llappviewer.h | 1 + indra/newview/llappviewerwin32.cpp | 15 ++++++--------- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 253c1268d3..62a1aa9fb6 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -5403,6 +5403,12 @@ void LLAppViewer::createErrorMarker(eLastExecEvent error_code) const } } +bool LLAppViewer::errorMarkerExists() const +{ + std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME); + return LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB); +} + void LLAppViewer::outOfMemorySoftQuit() { if (!mQuitRequested) diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 24d55a00fe..fdaa14a77d 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -250,6 +250,7 @@ public: // Writes an error code into the error_marker file for use on next startup. void createErrorMarker(eLastExecEvent error_code) const; + bool errorMarkerExists() const; // Attempt a 'soft' quit with disconnect and saving of settings/cache. // Intended to be thread safe. diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index c29c9a4919..929cb8b4d5 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -173,16 +173,13 @@ namespace << '/' << loc.mV[2]))); } - if (!LLAppViewer::instance()->isSecondInstance()) + LLAppViewer* app = LLAppViewer::instance(); + if (!app->isSecondInstance() && !app->errorMarkerExists()) { - std::string error_marker_file = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ERROR_MARKER_FILE_NAME); - if (!LLAPRFile::isExist(error_marker_file, NULL, LL_APR_RB)) - { - // If marker doesn't exist, create a marker with 'other' code for next launch - // otherwise don't override existing file - // Any unmarked crashes will be considered as freezes - LLAppViewer::instance()->createErrorMarker(LAST_EXEC_OTHER_CRASH); - } + // If marker doesn't exist, create a marker with 'other' code for next launch + // otherwise don't override existing file + // Any unmarked crashes will be considered as freezes + app->createErrorMarker(LAST_EXEC_OTHER_CRASH); } } // MDSCB_EXCEPTIONCODE From e33b5c5e6c8db1759e34db43ea9defc5455ab6c7 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Wed, 17 Sep 2025 15:56:08 +0300 Subject: [PATCH 08/18] #4432 Change graphics quality via a debug setting --- indra/newview/app_settings/settings.xml | 15 +++++++++++++-- indra/newview/llappviewer.cpp | 1 + indra/newview/llfloaterpreference.cpp | 1 + indra/newview/llviewercontrol.cpp | 18 ++++++++++++++++++ 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index aa6a28f137..e1be629f15 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -9111,7 +9111,7 @@ RenderQualityPerformance Comment - Which graphics settings you've chosen + Which graphics settings you've chosen. Don't use this setting to change quality directly from debug settings. Persist 1 Type @@ -9119,7 +9119,18 @@ Value 1 - + DebugQualityPerformance + + Comment + Allows to change performance quality directly from debug settings. + Persist + 1 + Type + U32 + Value + 1 + + RenderReflectionDetail Comment diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 62a1aa9fb6..b7852d8549 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1263,6 +1263,7 @@ bool LLAppViewer::init() LLViewerCamera::createInstance(); LL::GLTFSceneManager::createInstance(); + gSavedSettings.setU32("DebugQualityPerformance", gSavedSettings.getU32("RenderQualityPerformance")); #if LL_WINDOWS if (!mSecondInstance) diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 734cc73331..5a39d41478 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -1367,6 +1367,7 @@ void LLFloaterPreference::onChangeQuality(const LLSD& data) } mLastQualityLevel = level; LLFeatureManager::getInstance()->setGraphicsLevel(level, true); + gSavedSettings.setU32("DebugQualityPerformance", level); refreshEnabledGraphics(); refresh(); } diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index d39805f4c7..75cc76fee6 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -68,6 +68,8 @@ #include "llrender.h" #include "llnavigationbar.h" #include "llnotificationsutil.h" +#include "llfloaterpreference.h" +#include "llfloaterreg.h" #include "llfloatertools.h" #include "llpaneloutfitsinventory.h" #include "llpanellogin.h" @@ -149,6 +151,21 @@ static bool handleDebugAvatarJointsChanged(const LLSD& newvalue) return true; } +static bool handleDebugQualityPerformanceChanged(const LLSD& newvalue) +{ + // control was set directly or after adjusting Preference setting, no need to update + if (gSavedSettings.getU32("RenderQualityPerformance") != gSavedSettings.getU32("DebugQualityPerformance")) + { + LLFloaterPreference* instance = LLFloaterReg::getTypedInstance("preferences"); + if (instance) + { + gSavedSettings.setU32("RenderQualityPerformance", newvalue.asInteger()); + instance->onChangeQuality(newvalue); + } + } + return true; +} + static bool handleAvatarHoverOffsetChanged(const LLSD& newvalue) { if (isAgentAvatarValid()) @@ -946,6 +963,7 @@ void settings_setup_listeners() setting_setup_signal_listener(gSavedSettings, "SpellCheckDictionary", handleSpellCheckChanged); setting_setup_signal_listener(gSavedSettings, "LoginLocation", handleLoginLocationChanged); setting_setup_signal_listener(gSavedSettings, "DebugAvatarJoints", handleDebugAvatarJointsChanged); + setting_setup_signal_listener(gSavedSettings, "DebugQualityPerformance", handleDebugQualityPerformanceChanged); setting_setup_signal_listener(gSavedSettings, "TargetFPS", handleTargetFPSChanged); setting_setup_signal_listener(gSavedSettings, "AutoTuneFPS", handleAutoTuneFPSChanged); From bf0f60039b33bb0b1f93431864fd6b51c2a20b5b Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 17 Sep 2025 17:19:45 +0300 Subject: [PATCH 09/18] #4691 Log WebRTC termination for diagnostic purposes --- indra/newview/llvoicewebrtc.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index b26a48fd5f..b04f9837ec 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -288,6 +288,8 @@ void LLWebRTCVoiceClient::terminate() return; } + LL_INFOS("Voice") << "Terminating WebRTC" << LL_ENDL; + mVoiceEnabled = false; llwebrtc::terminate(); From 42b09b236bae797303af94fc003466ca649259a2 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 17 Sep 2025 18:24:35 +0300 Subject: [PATCH 10/18] #4685 EventQueueGet shouldn't use llcorehttp's backoff-retry logic - Event poll already has own backoff logic with 10 retries, using llcorehttp's one on top makes no sense. - Better logging coverage - Better in-code documentation - Add timing-based error detection --- indra/newview/lleventpoll.cpp | 64 +++++++++++++++++++++++++---------- indra/newview/lleventpoll.h | 25 +++++++++++++- 2 files changed, 70 insertions(+), 19 deletions(-) diff --git a/indra/newview/lleventpoll.cpp b/indra/newview/lleventpoll.cpp index c6fea1ba82..86c58a3497 100644 --- a/indra/newview/lleventpoll.cpp +++ b/indra/newview/lleventpoll.cpp @@ -54,13 +54,6 @@ namespace Details void stop(); private: - // We will wait RETRY_SECONDS + (errorCount * RETRY_SECONDS_INC) before retrying after an error. - // This means we attempt to recover relatively quickly but back off giving more time to recover - // until we finally give up after MAX_EVENT_POLL_HTTP_ERRORS attempts. - static const F32 EVENT_POLL_ERROR_RETRY_SECONDS; - static const F32 EVENT_POLL_ERROR_RETRY_SECONDS_INC; - static const S32 MAX_EVENT_POLL_HTTP_ERRORS; - void eventPollCoro(std::string url); void handleMessage(const LLSD &content); @@ -76,9 +69,13 @@ namespace Details }; - const F32 LLEventPollImpl::EVENT_POLL_ERROR_RETRY_SECONDS = 15.f; // ~ half of a normal timeout. - const F32 LLEventPollImpl::EVENT_POLL_ERROR_RETRY_SECONDS_INC = 5.f; // ~ half of a normal timeout. - const S32 LLEventPollImpl::MAX_EVENT_POLL_HTTP_ERRORS = 10; // ~5 minutes, by the above rules. + // We will wait RETRY_SECONDS + (errorCount * RETRY_SECONDS_INC) before retrying after an error. + // This means we attempt to recover relatively quickly but back off giving more time to recover + // until we finally give up after MAX_EVENT_POLL_HTTP_ERRORS attempts. + constexpr F32 EVENT_POLL_ERROR_RETRY_SECONDS = 15.f; // ~ half of a normal timeout. + constexpr F32 EVENT_POLL_ERROR_RETRY_SECONDS_INC = 5.f; // ~ half of a normal timeout. + constexpr S32 MAX_EVENT_POLL_HTTP_ERRORS = 10; // ~5 minutes, by the above rules. + constexpr F64 MIN_SECONDS_PASSED = 10.0; // Minimum time we expect the server to hold the request. int LLEventPollImpl::sNextCounter = 1; @@ -151,11 +148,17 @@ namespace Details LLSD acknowledge; int errorCount = 0; int counter = mCounter; // saved on the stack for logging. + LLTimer message_time; LL_DEBUGS("LLEventPollImpl") << " <" << counter << "> entering coroutine." << LL_ENDL; mAdapter = httpAdapter; + // This is a loop with its own waitToRetry implementation, + // so disable retries. + LLCore::HttpOptions::ptr_t httpOpts(new LLCore::HttpOptions); + httpOpts->setRetries(0); + LL::WorkQueue::ptr_t main_queue = nullptr; // HACK -- grab the mainloop workqueue to move execution of the handler @@ -172,11 +175,13 @@ namespace Details request["ack"] = acknowledge; request["done"] = mDone; + message_time.reset(); + // LL_DEBUGS("LLEventPollImpl::eventPollCoro") << "<" << counter << "> request = " // << LLSDXMLStreamer(request) << LL_ENDL; LL_DEBUGS("LLEventPollImpl") << " <" << counter << "> posting and yielding." << LL_ENDL; - LLSD result = httpAdapter->postAndSuspend(mHttpRequest, url, request); + LLSD result = httpAdapter->postAndSuspend(mHttpRequest, url, request, httpOpts); // LL_DEBUGS("LLEventPollImpl::eventPollCoro") << "<" << counter << "> result = " // << LLSDXMLStreamer(result) << LL_ENDL; @@ -194,11 +199,30 @@ namespace Details if (!status) { - if (status == LLCore::HttpStatus(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_OPERATION_TIMEDOUT)) - { // A standard timeout response we get this when there are no events. - LL_DEBUGS("LLEventPollImpl") << "All is very quiet on target server. It may have gone idle?" << LL_ENDL; - errorCount = 0; - continue; + if (status == LLCore::HttpStatus(LLCore::HttpStatus::EXT_CURL_EASY, CURLE_OPERATION_TIMEDOUT) // A standard timeout, no events. + || status == LLCore::HttpStatus(HTTP_BAD_GATEWAY) // An expected 'No events' case. + || status == LLCore::HttpStatus(HTTP_INTERNAL_ERROR) + || status == LLCore::HttpStatus(HTTP_SERVICE_UNAVAILABLE) + || status == LLCore::HttpStatus(HTTP_GATEWAY_TIME_OUT)) + { + if (message_time.getElapsedSeconds() < MIN_SECONDS_PASSED) + { + // Server is supposed to hold request for 20 to 30 seconds. + // If it didn't hold the request at least for 10s, treat as an error. + LL_WARNS("LLEventPollImpl") << "Response arrived too early, status: " << status.toTerseString() + << ", time passed: " << message_time.getElapsedSeconds() << LL_ENDL; + } + else + { + // Timeout, expected and means 'no events'. Request is to be re-issued immediately. + // Current definition of a timeout is any of : + // - libcurl easy 28 status code + // - Linden 499 special http status code + // - RFC - standard 502 - 504 http status codes + LL_DEBUGS("LLEventPollImpl") << "No events, from: " << mSenderIp <<" status: " << (S32)status.getStatus() << LL_ENDL; + errorCount = 0; + continue; + } } else if ((status == LLCore::HttpStatus(LLCore::HttpStatus::LLCORE, LLCore::HE_OP_CANCELED)) || (status == LLCore::HttpStatus(HTTP_NOT_FOUND))) @@ -206,13 +230,13 @@ namespace Details // some cases the server gets ahead of the viewer and will // return a 404 error (Not Found) before the cancel event // comes back in the queue - LL_WARNS("LLEventPollImpl") << "Canceling coroutine" << LL_ENDL; + LL_WARNS("LLEventPollImpl") << "<" << counter << "> Canceling coroutine, status: " << status.toTerseString() << LL_ENDL; break; } else if (!status.isHttpStatus()) { /// Some LLCore or LIBCurl error was returned. This is unlikely to be recoverable - LL_WARNS("LLEventPollImpl") << "Critical error from poll request returned from libraries. Canceling coroutine." << LL_ENDL; + LL_WARNS("LLEventPollImpl") << "<" << counter << "> Critical error from poll request returned from libraries. Canceling coroutine." << LL_ENDL; break; } LL_WARNS("LLEventPollImpl") << "<" << counter << "> Error result from LLCoreHttpUtil::HttpCoroHandler. Code " @@ -255,6 +279,10 @@ namespace Details LL_WARNS("LLEventPollImpl") << "< " << counter << "> Forcing disconnect due to stalled main region event poll." << LL_ENDL; LLAppViewer::instance()->forceDisconnect(LLTrans::getString("AgentLostConnection")); } + else + { + LL_WARNS("LLEventPollImpl") << "< " << counter << "> Stopping event poll for " << mSenderIp << " due to failures." << LL_ENDL; + } break; } } diff --git a/indra/newview/lleventpoll.h b/indra/newview/lleventpoll.h index bb407b3799..ea186aa803 100644 --- a/indra/newview/lleventpoll.h +++ b/indra/newview/lleventpoll.h @@ -40,7 +40,30 @@ namespace Details class LLEventPoll - ///< implements the viewer side of server-to-viewer pushed events. + ///< Implements the viewer side of server-to-viewer pushed events. + /// + /// This class implements the sole consumer of the EventQueueGet capability + /// and delivers data, including llsd-encoded llmessage payloads, from + /// simulator to viewer. + /// + /// https://wiki.secondlife.com/wiki/EventQueueGet + /// The wiki page is neither complete nor entirely correct. Request timeouts + /// became the de facto method of returning an empty event set to the viewer. + /// But the timeout behavior was never defined. It was simply whatever + /// behavior a given grid implementation implemented. + /// + /// In SL's case, the path may include reverse proxies, http caches, http and + /// socks proxies, transparent hijacking, and other horrors. A pitfall for + /// implementors. + /// + /// Current definition of a timeout is any of : + /// - libcurl easy 28 status code + /// - Linden 499 special http status code + /// - RFC - standard 502 - 504 http status codes + /// If requests are failing too quickly with the above errors, they are treated + /// as actual errors and not an empty payload. These will count towards a final + /// error declaration and can lead to disconnection from a simulator or the + /// entire grid. { public: LLEventPoll(const std::string& pollURL, const LLHost& sender); From 1cda0a1a450853671e9961de67aea065f0a260ea Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Wed, 17 Sep 2025 21:48:09 +0300 Subject: [PATCH 11/18] #4687 Fix audio device selection showing a bunch of zeros --- indra/newview/llpanelvoicedevicesettings.cpp | 29 +++++++++++++++++-- .../default/xui/en/panel_sound_devices.xml | 4 +++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/indra/newview/llpanelvoicedevicesettings.cpp b/indra/newview/llpanelvoicedevicesettings.cpp index 60877494e7..d8d6bcf5fd 100644 --- a/indra/newview/llpanelvoicedevicesettings.cpp +++ b/indra/newview/llpanelvoicedevicesettings.cpp @@ -240,13 +240,38 @@ void LLPanelVoiceDeviceSettings::refresh() if(mCtrlInputDevices) { mCtrlInputDevices->removeall(); - mCtrlInputDevices->add(getLocalizedDeviceName(mInputDevice), mInputDevice, ADD_BOTTOM); + auto it = mLocalizedDeviceNames.find(mInputDevice); + if (it != mLocalizedDeviceNames.end()) + { + mCtrlInputDevices->add(getLocalizedDeviceName(mInputDevice), mInputDevice, ADD_BOTTOM); + } + else + { + // Display name generaly doesn't match value. + // Value is an id so it's not nessesary readable, + // might not even be valid (disconnected usb). + // Until we get the data, don't change the device, + // otherwise box might override the control. + // But show a readable placeholder. + // Combo is disabled so it's safe to show + // a placeholder. + mCtrlInputDevices->add(getString("device_not_loaded"), mInputDevice, ADD_BOTTOM); + } mCtrlInputDevices->setValue(mInputDevice); } if(mCtrlOutputDevices) { mCtrlOutputDevices->removeall(); - mCtrlOutputDevices->add(getLocalizedDeviceName(mOutputDevice), mOutputDevice, ADD_BOTTOM); + auto it = mLocalizedDeviceNames.find(mOutputDevice); + if (it != mLocalizedDeviceNames.end()) + { + mCtrlOutputDevices->add(getLocalizedDeviceName(mOutputDevice), mOutputDevice, ADD_BOTTOM); + } + else + { + // Don't change the device, only the label + mCtrlOutputDevices->add(getString("device_not_loaded"), mOutputDevice, ADD_BOTTOM); + } mCtrlOutputDevices->setValue(mOutputDevice); } } 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 7598f7d7e5..b858d0ed02 100644 --- a/indra/newview/skins/default/xui/en/panel_sound_devices.xml +++ b/indra/newview/skins/default/xui/en/panel_sound_devices.xml @@ -20,6 +20,10 @@ name="name_default_system_device"> Default System Device + + Device not loaded + Date: Thu, 18 Sep 2025 21:37:12 +0300 Subject: [PATCH 12/18] NSIS build fix (#4698) --- .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 4bf2af644a..7248ee3ad8 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -310,7 +310,7 @@ jobs: steps: - name: Sign and package Windows viewer if: env.AZURE_KEY_VAULT_URI && env.AZURE_CERT_NAME && env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET && env.AZURE_TENANT_ID - uses: secondlife/viewer-build-util/sign-pkg-windows@v2 + uses: secondlife/viewer-build-util/sign-pkg-windows@v2.0.4 with: vault_uri: "${{ env.AZURE_KEY_VAULT_URI }}" cert_name: "${{ env.AZURE_CERT_NAME }}" From 95725f709d615a9848b3381290276cd9b2254f32 Mon Sep 17 00:00:00 2001 From: Alexander Gavriliuk Date: Thu, 24 Oct 2024 00:36:48 +0200 Subject: [PATCH 13/18] #2577 Worn transparent prims no longer show with 'Highlight transparent' --- indra/newview/pipeline.cpp | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 2fe67a5457..67014a3716 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -11524,21 +11524,24 @@ public: } }; - +// Called from LLViewHighlightTransparent when "Highlight Transparent" is toggled void LLPipeline::rebuildDrawInfo() { - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + const U32 types_to_traverse[] = { - LLViewerRegion* region = *iter; + LLViewerRegion::PARTITION_VOLUME, + LLViewerRegion::PARTITION_BRIDGE, + LLViewerRegion::PARTITION_AVATAR + }; - LLOctreeDirty dirty; - - LLSpatialPartition* part = region->getSpatialPartition(LLViewerRegion::PARTITION_VOLUME); - dirty.traverse(part->mOctree); - - part = region->getSpatialPartition(LLViewerRegion::PARTITION_BRIDGE); - dirty.traverse(part->mOctree); + LLOctreeDirty dirty; + for (LLViewerRegion* region : LLWorld::getInstance()->getRegionList()) + { + for (U32 type : types_to_traverse) + { + LLSpatialPartition* part = region->getSpatialPartition(type); + dirty.traverse(part->mOctree); + } } } From e918d240fd516d9116f0d1c170df34039c63a3ac Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 19 Sep 2025 01:00:43 +0300 Subject: [PATCH 14/18] #4679 Logging for crash on updateMenuOptions Logs say that viewer closed settings picker and immediately crashed Yet callstack indicates that some inventory was right clicked, which shouldn't be possible if picker already closed. May be some click is closing the picker and opens menu at the same time, but it's better to gather information first. --- indra/llui/llfolderview.cpp | 1 + indra/newview/llsettingspicker.cpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp index b664065532..db4ab8487e 100644 --- a/indra/llui/llfolderview.cpp +++ b/indra/llui/llfolderview.cpp @@ -1510,6 +1510,7 @@ bool LLFolderView::handleRightMouseDown( S32 x, S32 y, MASK mask ) && ( count > 0 && (hasVisibleChildren()) ))) && // show menu only if selected items are visible !hide_folder_menu) { + LL_INFOS("Inventory") << "Opening inventory menu from path: " << getPathname() << LL_ENDL; if (mCallbackRegistrar) { mCallbackRegistrar->pushScope(); diff --git a/indra/newview/llsettingspicker.cpp b/indra/newview/llsettingspicker.cpp index 619882dc5e..5fc39b0dd8 100644 --- a/indra/newview/llsettingspicker.cpp +++ b/indra/newview/llsettingspicker.cpp @@ -152,7 +152,11 @@ void LLFloaterSettingsPicker::onClose(bool app_quitting) owner->setFocus(true); } mSettingItemID.setNull(); - mInventoryPanel->getRootFolder()->clearSelection(); + mInventoryPanel->clearSelection(); + if (mInventoryPanel->getRootFolder()) + { + mInventoryPanel->getRootFolder()->clearSelection(); + } } void LLFloaterSettingsPicker::setValue(const LLSD& value) From 0fb6dc22ff925ff56b99d137221f3936e51ae34f Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 19 Sep 2025 14:10:38 +0300 Subject: [PATCH 15/18] Fix builds after update of build-variables branch --- .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 8b82a66b3d..1d6a23dc4d 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -97,7 +97,7 @@ jobs: uses: actions/checkout@v4 with: repository: secondlife/build-variables - ref: universal + ref: master path: .build-variables - name: Checkout master-message-template From acd879477508e8389607d5eeb0e91531fd7703e9 Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Thu, 18 Sep 2025 20:39:31 +0300 Subject: [PATCH 16/18] #4671 Some locales are missing AM/PM labels --- indra/newview/llstartup.cpp | 21 ++++++++++++++++++- .../newview/skins/default/xui/de/strings.xml | 4 ++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index bf7c58a3cb..3786a9b160 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -439,7 +439,26 @@ bool idle_startup() system = osString.substr (begIdx, endIdx - begIdx); system += "Locale"; - LLStringUtil::setLocale (LLTrans::getString(system)); + std::string locale = LLTrans::getString(system); + if (locale != LLStringUtil::getLocale()) // is there a reason to do this on repeat? + { + LLStringUtil::setLocale(locale); + + // Not all locales have AMPM, test it + if (LLStringOps::sAM.empty()) // Might already be overriden from LLAppViewer::init() + { + LLDate datetime(0.0); + std::string val = datetime.toHTTPDateString("%p"); + if (val.empty()) + { + LL_DEBUGS("InitInfo") << "Current locale \"" << locale << "\" " + << "doesn't support AM/PM time format" << LL_ENDL; + // fallback to declarations in strings.xml + LLStringOps::sAM = LLTrans::getString("dateTimeAM"); + LLStringOps::sPM = LLTrans::getString("dateTimePM"); + } + } + } //note: Removing this line will cause incorrect button size in the login screen. -- bao. gTextureList.updateImages(0.01f) ; diff --git a/indra/newview/skins/default/xui/de/strings.xml b/indra/newview/skins/default/xui/de/strings.xml index f0945234a6..d7b2ee57a8 100644 --- a/indra/newview/skins/default/xui/de/strings.xml +++ b/indra/newview/skins/default/xui/de/strings.xml @@ -5074,10 +5074,10 @@ Bitte überprüfen Sie http://status.secondlifegrid.net, um herauszufinden, ob e [MDAY] - Uhr + AM - Uhr + PM [AMOUNT] US$ From 569d7c6a8b1e44b66dad16a3367547cdf3395620 Mon Sep 17 00:00:00 2001 From: Hecklezz Date: Fri, 19 Sep 2025 18:49:25 +1000 Subject: [PATCH 17/18] Fix OpenJPEG uploading small textures by correctly accounting for the maximum numresolutions --- indra/llimagej2coj/llimagej2coj.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/indra/llimagej2coj/llimagej2coj.cpp b/indra/llimagej2coj/llimagej2coj.cpp index 5fa53657a0..7cfadb889d 100644 --- a/indra/llimagej2coj/llimagej2coj.cpp +++ b/indra/llimagej2coj/llimagej2coj.cpp @@ -554,11 +554,6 @@ public: } - if (!opj_setup_encoder(encoder, ¶meters, image)) - { - return false; - } - U32 width_tiles = (rawImageIn.getWidth() >> 6); U32 height_tiles = (rawImageIn.getHeight() >> 6); @@ -572,6 +567,19 @@ public: height_tiles = 1; } + if (width_tiles == 1 || height_tiles == 1) + { + // Images with either dimension less than 32 need less number of resolutions otherwise they error + int min_dim = rawImageIn.getWidth() < rawImageIn.getHeight() ? rawImageIn.getWidth() : rawImageIn.getHeight(); + int max_res = 1 + (int)floor(log2(min_dim)); + parameters.numresolution = max_res; + } + + if (!opj_setup_encoder(encoder, ¶meters, image)) + { + return false; + } + U32 tile_count = width_tiles * height_tiles; U32 data_size_guess = tile_count * TILE_SIZE; From d7bd769129100332791569cbffa07f54d2fca991 Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Mon, 22 Sep 2025 15:46:04 +0300 Subject: [PATCH 18/18] #4715 disable ZoomIn an object item when the region is not connected to the current region --- indra/llui/lltextbase.cpp | 20 ++++++++++++++ indra/llui/lltextbase.h | 3 +++ indra/newview/llchathistory.cpp | 9 +++++-- indra/newview/llviewermenu.cpp | 2 +- indra/newview/llviewerobject.cpp | 45 ++++++++++++++++++++++++++++++++ indra/newview/llviewerobject.h | 3 +++ 6 files changed, 79 insertions(+), 3 deletions(-) diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 3fe0df1848..69729fa421 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -185,6 +185,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mURLClickSignal(NULL), mIsFriendSignal(NULL), mIsObjectBlockedSignal(NULL), + mIsObjectReachableSignal(NULL), mMaxTextByteLength( p.max_text_length ), mFont(p.font), mFontShadow(p.font_shadow), @@ -290,6 +291,7 @@ LLTextBase::~LLTextBase() delete mURLClickSignal; delete mIsFriendSignal; delete mIsObjectBlockedSignal; + delete mIsObjectReachableSignal; } void LLTextBase::initFromParams(const LLTextBase::Params& p) @@ -2281,6 +2283,15 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) unblockButton->setVisible(is_blocked); } } + + if (mIsObjectReachableSignal) + { + bool is_reachable = *(*mIsObjectReachableSignal)(LLUUID(LLUrlAction::getObjectId(url))); + if (LLView* zoom_btn = menu->getChild("zoom_in")) + { + zoom_btn->setEnabled(is_reachable); + } + } menu->show(x, y); LLMenuGL::showPopup(this, menu, x, y); } @@ -3387,6 +3398,15 @@ boost::signals2::connection LLTextBase::setIsObjectBlockedCallback(const is_bloc return mIsObjectBlockedSignal->connect(cb); } +boost::signals2::connection LLTextBase::setIsObjectReachableCallback(const is_obj_reachable_signal_t::slot_type& cb) +{ + if (!mIsObjectReachableSignal) + { + mIsObjectReachableSignal = new is_obj_reachable_signal_t(); + } + return mIsObjectReachableSignal->connect(cb); +} + // // LLTextSegment // diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 8ca653acb9..07cd9a1ee5 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -325,6 +325,7 @@ public: typedef boost::signals2::signal is_friend_signal_t; typedef boost::signals2::signal is_blocked_signal_t; + typedef boost::signals2::signal is_obj_reachable_signal_t; struct LineSpacingParams : public LLInitParam::ChoiceBlock { @@ -535,6 +536,7 @@ public: boost::signals2::connection setURLClickedCallback(const commit_signal_t::slot_type& cb); boost::signals2::connection setIsFriendCallback(const is_friend_signal_t::slot_type& cb); boost::signals2::connection setIsObjectBlockedCallback(const is_blocked_signal_t::slot_type& cb); + boost::signals2::connection setIsObjectReachableCallback(const is_obj_reachable_signal_t::slot_type& cb); void setWordWrap(bool wrap); LLScrollContainer* getScrollContainer() const { return mScroller; } @@ -783,6 +785,7 @@ protected: // Used to check if user with given ID is avatar's friend is_friend_signal_t* mIsFriendSignal; is_blocked_signal_t* mIsObjectBlockedSignal; + is_obj_reachable_signal_t* mIsObjectReachableSignal; LLUIString mLabel; // text label that is visible when no user text provided }; diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index 2facc43811..c1af09ebc7 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -215,7 +215,8 @@ public: LLUUID obj_id = mObjectData["object_id"]; if (obj_id.notNull()) { - return nullptr != gObjectList.findObject(mAvatarID); + LLViewerObject* object = gObjectList.findObject(obj_id); + return object && object->isReachable(); } return false; } @@ -1118,7 +1119,11 @@ LLChatHistory::LLChatHistory(const LLChatHistory::Params& p) mEditor = LLUICtrlFactory::create(editor_params, this); mEditor->setIsFriendCallback(LLAvatarActions::isFriend); mEditor->setIsObjectBlockedCallback(boost::bind(&LLMuteList::isMuted, LLMuteList::getInstance(), _1, _2, 0)); - + mEditor->setIsObjectReachableCallback([](const LLUUID& obj_id) + { + LLViewerObject* object = gObjectList.findObject(obj_id); + return object && object->isReachable(); + }); } LLSD LLChatHistory::getValue() const diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index f7688b762f..34a3745222 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -6516,7 +6516,7 @@ bool handle_zoom_to_object(const LLUUID& object_id) LLViewerObject* object = gObjectList.findObject(object_id); - if (object) + if (object && object->isReachable()) { gAgentCamera.setFocusOnAvatar(false, ANIMATE); diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 9e77b40a45..ce93e6f371 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -7717,6 +7717,51 @@ void LLViewerObject::clearTEWaterExclusion(const U8 te) } } +bool LLViewerObject::isReachable() +{ + LLViewerRegion* agent_region = gAgent.getRegion(); + LLViewerRegion* object_region = getRegion(); + + if (!agent_region || !object_region) + { + return false; + } + if (agent_region == object_region) + { + return true; + } + + std::unordered_set visited; + std::queue pending; + visited.insert(agent_region); + pending.push(agent_region); + + while (!pending.empty()) + { + LLViewerRegion* current = pending.front(); + pending.pop(); + + std::vector neighbors; + current->getNeighboringRegions(neighbors); + + for (LLViewerRegion* neighbor : neighbors) + { + if (!neighbor) continue; + + if (neighbor == object_region) + { + return true; + } + // region's neighbors were not checked + if (visited.insert(neighbor).second) + { + pending.push(neighbor); + } + } + } + return false; +} + class ObjectPhysicsProperties : public LLHTTPNode { public: diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h index 2b52ea2076..66ed3b8dfa 100644 --- a/indra/newview/llviewerobject.h +++ b/indra/newview/llviewerobject.h @@ -249,6 +249,9 @@ public: // Accessor functions LLViewerRegion* getRegion() const { return mRegionp; } + // Check if object is reachable from agent region by traversing loaded neighboring regions + bool isReachable(); + bool isSelected() const { return mUserSelected; } // Check whole linkset bool isAnySelected() const;