summaryrefslogtreecommitdiff
path: root/indra/newview/llappviewerwin32.cpp
diff options
context:
space:
mode:
authorErik Kundiman <erik@megapahit.org>2026-09-01 18:35:23 +0800
committerErik Kundiman <erik@megapahit.org>2026-09-04 21:58:53 +0800
commit00bb3bb6f07660bd914d29a1389342bb3060d254 (patch)
tree431f574922494d2201718f13085f17bc6bd7fb42 /indra/newview/llappviewerwin32.cpp
parenta8636720129952c10cf49ab80da21bf8b340b96c (diff)
parent4ef9f8f14b35ed388c294d53767a0dca0e890924 (diff)
Merge remote-tracking branch 'secondlife/release/26.4' into 26.4
Diffstat (limited to 'indra/newview/llappviewerwin32.cpp')
-rw-r--r--indra/newview/llappviewerwin32.cpp322
1 files changed, 308 insertions, 14 deletions
diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp
index afff5f2cf2..77f00b05f3 100644
--- a/indra/newview/llappviewerwin32.cpp
+++ b/indra/newview/llappviewerwin32.cpp
@@ -4,7 +4,7 @@
*
* $LicenseInfo:firstyear=2007&license=viewerlgpl$
* Second Life Viewer Source Code
- * Copyright (C) 2010, Linden Research, Inc.
+ * Copyright (C) 2026, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
@@ -31,7 +31,7 @@
#endif
#include "llwin32headers.h"
-#if !LL_SDL
+#if !LL_SDL_WINDOW
#include "llwindowwin32.h" // *FIX: for setting gIconResource.
#endif
@@ -80,6 +80,7 @@
#if LL_VELOPACK
#include "llvelopack.h"
#endif
+#include "llversioninfovars.h"
// Bugsplat (http://bugsplat.com) crash reporting tool
#ifdef LL_BUGSPLAT
@@ -209,6 +210,13 @@ namespace
LLAppViewer* app = LLAppViewer::instance();
+ // Include mainloop watchdog state if available
+ std::string watchdog_state = app->getMainloopWatchdogState();
+ if (!watchdog_state.empty())
+ {
+ sBugSplatSender->setAttribute(WCSTR(L"WatchdogState"), WCSTR(watchdog_state));
+ }
+
if (!app->isSecondInstance() && !app->errorMarkerExists())
{
// If marker doesn't exist, create a marker with 'other' or 'logout' code for next launch
@@ -230,6 +238,8 @@ namespace
}
#endif // LL_BUGSPLAT
+extern bool gGPUBenchmarkMode;
+
namespace
{
void (*gOldTerminateHandler)() = NULL;
@@ -482,7 +492,7 @@ int APIENTRY WINMAIN(HINSTANCE hInstance,
DWORD heap_enable_lfh_error[MAX_HEAPS];
S32 num_heaps = 0;
-#if !LL_SDL
+#if !LL_SDL_WINDOW
LLWindowWin32::setDPIAwareness();
#endif
@@ -513,18 +523,27 @@ int APIENTRY WINMAIN(HINSTANCE hInstance,
#endif
#endif
-#if !LL_SDL
+#if !LL_SDL_WINDOW
// *FIX: global
gIconResource = MAKEINTRESOURCE(IDI_LL_ICON);
#endif
gIconSmallResource = MAKEINTRESOURCE(IDI_LL_ICON_SMALL);
+ // Benchmark subprocess mode before full init for LLFeatureManager::loadGPUClass().
+ {
+ std::wstring cmdLineStr(pCmdLine ? pCmdLine : L"");
+ if (cmdLineStr.find(L"--gpubenchmark") != std::wstring::npos)
+ {
+ gGPUBenchmarkMode = true;
+ }
+ }
+
LLAppViewerWin32* viewer_app_ptr = new LLAppViewerWin32(ll_convert_wide_to_string(pCmdLine).c_str());
gOldTerminateHandler = std::set_terminate(exceptionTerminateHandler);
// Set a debug info flag to indicate if multiple instances are running.
- bool found_other_instance = !create_app_mutex();
+ bool found_other_instance = gGPUBenchmarkMode || !create_app_mutex();
gDebugInfo["FoundOtherInstanceAtStartup"] = LLSD::Boolean(found_other_instance);
bool ok = viewer_app_ptr->init();
@@ -868,12 +887,7 @@ bool LLAppViewerWin32::cleanup()
bool result = LLAppViewer::cleanup();
gDXHardware.cleanup();
-
- if (mIsConsoleAllocated)
- {
- FreeConsole();
- mIsConsoleAllocated = false;
- }
+ cleanupConsole();
return result;
}
@@ -953,10 +967,22 @@ void LLAppViewerWin32::initLoggingAndGetLastDuration()
void LLAppViewerWin32::initConsole()
{
// pop up debug console
- mIsConsoleAllocated = create_console();
+ if (!gGPUBenchmarkMode)
+ {
+ mIsConsoleAllocated = create_console();
+ }
return LLAppViewer::initConsole();
}
+void LLAppViewerWin32::cleanupConsole()
+{
+ if (mIsConsoleAllocated)
+ {
+ FreeConsole();
+ mIsConsoleAllocated = false;
+ }
+}
+
void write_debug_dx(const char* str)
{
std::string value = gDebugInfo["DXInfo"].asString();
@@ -1029,7 +1055,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;
@@ -1037,13 +1063,281 @@ 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;
}
+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:
+ // %appdata%\Local\ChannelNameViewer\Update.exe // which is our uninstaller
+ // %appdata%\Local\ChannelNameViewer\SecondLifeViewer.exe // wrapper, redirects to main executable
+ // %appdata%\Local\ChannelNameViewer\current\SecondLifeViewer.exe // main executable
+ // For reliability don't expect install_dir to be actually in the base path, strip 'current'
+
+ const std::wstring current_suffix = L"\\current";
+ std::wstring normalized_path(install_dir);
+ if (normalized_path.length() >= current_suffix.length() &&
+ _wcsicmp(normalized_path.c_str() + normalized_path.length() - current_suffix.length(),
+ current_suffix.c_str()) == 0)
+ {
+ normalized_path.resize(normalized_path.length() - current_suffix.length());
+ }
+
+ wchar_t window_class[256]; // Assume max length < 255 chars.
+ mbstowcs(window_class, sWindowClass, 255);
+ window_class[255] = 0;
+
+ // Normalize the directory path
+ wchar_t our_dir_normalized[MAX_PATH];
+ wchar_t* file_part = nullptr;
+ DWORD result = GetFullPathNameW(normalized_path.c_str(), MAX_PATH, our_dir_normalized, &file_part);
+ if (result == 0 || result >= MAX_PATH)
+ {
+ LL_WARNS() << "Failed to normalize our executable path" << LL_ENDL;
+ return false;
+ }
+
+ // Remove trailing backslash if present
+ size_t dir_len = wcslen(our_dir_normalized);
+ if (dir_len > 0 && our_dir_normalized[dir_len - 1] == L'\\')
+ {
+ our_dir_normalized[dir_len - 1] = L'\0';
+ dir_len--;
+ }
+
+ // This message is meant for velopack, so we don't expect to have
+ // a window of our own, store any matching windows.
+ struct EnumData
+ {
+ const wchar_t* target_class;
+ const wchar_t* our_dir_normalized;
+ std::vector<HWND> found_windows;
+ };
+
+ EnumData enum_data;
+ enum_data.target_class = window_class;
+ enum_data.our_dir_normalized = our_dir_normalized;
+
+ // Callback function to find all matching windows
+ auto find_windows_callback = [](HWND hwnd, LPARAM lParam) -> BOOL
+ {
+ EnumData* data = reinterpret_cast<EnumData*>(lParam);
+ wchar_t class_name[256];
+
+ if (GetClassName(hwnd, class_name, 256) > 0)
+ {
+ if (wcscmp(class_name, data->target_class) == 0)
+ {
+ // Get the process ID for this window
+ DWORD process_id = 0;
+ GetWindowThreadProcessId(hwnd, &process_id);
+
+ // Open the process to query its executable path
+ HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_id);
+ if (hProcess)
+ {
+ wchar_t exe_path[MAX_PATH];
+ DWORD size = MAX_PATH;
+ if (QueryFullProcessImageNameW(hProcess, 0, exe_path, &size))
+ {
+ // Normalize the other process's path
+ wchar_t other_dir_normalized[MAX_PATH];
+ wchar_t* other_file_part = nullptr;
+ DWORD result = GetFullPathNameW(exe_path, MAX_PATH, other_dir_normalized, &other_file_part);
+
+ if (result > 0 && result < MAX_PATH)
+ {
+ // Remove the filename part to get just the directory
+ // We are doing this to avoid incidents, like having
+ // multiple viewer version exes in the same folder.
+ if (other_file_part)
+ {
+ *other_file_part = L'\0';
+ }
+
+ // Remove trailing backslash if present
+ size_t other_dir_len = wcslen(other_dir_normalized);
+ if (other_dir_len > 0 && other_dir_normalized[other_dir_len - 1] == L'\\')
+ {
+ other_dir_normalized[other_dir_len - 1] = L'\0';
+ other_dir_len--;
+ }
+
+ // Strip "\current" suffix if present to normalize comparison
+ // This handles both release (with \current) and debug builds (without)
+ const std::wstring current_suffix = L"\\current";
+ if (other_dir_len >= current_suffix.length())
+ {
+ size_t offset = other_dir_len - current_suffix.length();
+ if (_wcsicmp(other_dir_normalized + offset, current_suffix.c_str()) == 0)
+ {
+ other_dir_normalized[offset] = L'\0';
+ }
+ }
+
+ // Compare directories (case-insensitive)
+ if (_wcsicmp(other_dir_normalized, data->our_dir_normalized) == 0)
+ {
+ data->found_windows.push_back(hwnd);
+ }
+ }
+ }
+ CloseHandle(hProcess);
+ }
+ }
+ }
+
+ return TRUE; // Continue enumeration
+ };
+
+ // Find all matching windows and send shutdown messages
+ EnumWindows(find_windows_callback, reinterpret_cast<LPARAM>(&enum_data));
+
+ if (enum_data.found_windows.empty())
+ {
+ LL_DEBUGS("AppInit") << "No other instances found" << LL_ENDL;
+ return false;
+ }
+
+ LL_INFOS("AppInit") << "Found " << (S32)(enum_data.found_windows.size()) << " other instance(s), sending shutdown messages" << LL_ENDL;
+
+ // Get our own process ID to include in the message
+ DWORD our_process_id = GetCurrentProcessId();
+
+ constexpr UINT timeout_ms = 2000; // 2s. Viewer's message thread is supposed to be fast.
+ for (HWND other_window : enum_data.found_windows)
+ {
+ if (IsWindow(other_window))
+ {
+ DWORD_PTR result = 0;
+ LRESULT send_result = SendMessageTimeout(
+ other_window,
+ WM_POST_UNINSTALL_,
+ static_cast<WPARAM>(our_process_id),
+ static_cast<LPARAM>(WM_POST_UNINSTALL_MSG_SHUTDOWN),
+ SMTO_ABORTIFHUNG | SMTO_BLOCK,
+ timeout_ms,
+ &result
+ );
+
+ if (send_result == 0)
+ {
+ DWORD error = GetLastError();
+ if (error == ERROR_TIMEOUT)
+ {
+ LL_WARNS("AppInit") << "Shutdown message timed out for window " << std::hex << other_window << std::dec << LL_ENDL;
+ }
+ else
+ {
+ LL_WARNS("AppInit") << "Failed to send shutdown message to window " << std::hex << other_window
+ << ", error: " << error << std::dec << LL_ENDL;
+ }
+
+ PostMessage(other_window, WM_CLOSE, 0, 0);
+ }
+ else
+ {
+ LL_DEBUGS("AppInit") << "Shutdown message sent successfully to window " << std::hex << other_window << std::dec << LL_ENDL;
+ }
+ }
+ }
+
+ // Poll for up to 30 seconds, checking every 5 seconds
+ const S32 MAX_WAIT_TIME_MS = 60000; // 30 seconds
+ const S32 POLL_INTERVAL_MS = 5000; // 5 seconds
+ S32 elapsed_time_ms = 0;
+ size_t still_open_count = enum_data.found_windows.size();
+
+ while (elapsed_time_ms < MAX_WAIT_TIME_MS)
+ {
+ LL_INFOS("AppInit") << "Waiting for " << (S32)still_open_count << " instance(s) to close... ("
+ << (S32)(elapsed_time_ms / 1000) << "s elapsed)" << LL_ENDL;
+
+ ms_sleep(POLL_INTERVAL_MS);
+ elapsed_time_ms += POLL_INTERVAL_MS;
+
+ // Check if the specific windows we found still exist
+ // Don't enumerate all windows for new ones, assume that
+ // no instances were reused and assume user won't open
+ // the app again. For now just check our list.
+ still_open_count = 0;
+ for (HWND hwnd : enum_data.found_windows)
+ {
+ if (IsWindow(hwnd))
+ {
+ still_open_count++;
+ }
+ }
+
+ if (still_open_count == 0)
+ {
+ LL_INFOS("AppInit") << "All other instances have closed after " << (S32)(elapsed_time_ms / 1000) << " seconds" << LL_ENDL;
+ return false;
+ }
+ }
+
+ if (still_open_count != 0)
+ {
+ LL_WARNS("AppInit") << "Proceeding with uninstall with " << (S32)still_open_count << " instance(s) still open." << LL_ENDL;
+ }
+
+ return true;
+}
+
std::string LLAppViewerWin32::generateSerialNumber()
{