summaryrefslogtreecommitdiff
path: root/indra/llwindow/llwindowwin32.cpp
diff options
context:
space:
mode:
authorJonathan "Geenz" Goodman <geenz@lindenlab.com>2026-05-20 12:05:18 -0400
committerGitHub <noreply@github.com>2026-05-20 12:05:18 -0400
commit40cd1617a6a663fb921265101307454b490ec4ba (patch)
tree2f13741295c385172364e3fa940d7591f32fac4e /indra/llwindow/llwindowwin32.cpp
parent9c2ee024bef564ebb6e2e8942b43ffae1d99232c (diff)
parentdf844170600608b37c2725f7a263f1de3b055618 (diff)
Merge branch 'develop' into geenz/26.3-to-develop
Diffstat (limited to 'indra/llwindow/llwindowwin32.cpp')
-rw-r--r--indra/llwindow/llwindowwin32.cpp271
1 files changed, 254 insertions, 17 deletions
diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp
index c185fc6c4a..52e0cdd5d5 100644
--- a/indra/llwindow/llwindowwin32.cpp
+++ b/indra/llwindow/llwindowwin32.cpp
@@ -508,6 +508,7 @@ LLWindowWin32::LLWindowWin32(LLWindowCallbacks* callbacks,
:
LLWindow(callbacks, fullscreen, flags),
mAbsoluteCursorPosition(false),
+ mReceivedSCClose(false),
mMaxGLVersion(max_gl_version),
mMaxCores(max_cores)
{
@@ -1420,7 +1421,7 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo
catch (...)
{
LOG_UNHANDLED_EXCEPTION("ChoosePixelFormat");
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1431,7 +1432,7 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo
if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR),
&pfd))
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1469,7 +1470,7 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo
if (!SetPixelFormat(mhDC, pixel_format, &pfd))
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1477,14 +1478,14 @@ bool LLWindowWin32::switchContext(bool fullscreen, const LLCoordScreen& size, bo
if (!(mhRC = SafeCreateContext(mhDC)))
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
if (!wglMakeCurrent(mhDC, mhRC))
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1690,14 +1691,14 @@ const S32 max_format = (S32)num_formats - 1;
if (!mhDC)
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBDevContextErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBDevContextErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
if (!SetPixelFormat(mhDC, pixel_format, &pfd))
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtSetErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1729,7 +1730,7 @@ const S32 max_format = (S32)num_formats - 1;
{
LL_WARNS("Window") << "No wgl_ARB_pixel_format extension!" << LL_ENDL;
// cannot proceed without wgl_ARB_pixel_format extension, shutdown same as any other gGLManager.initGL() failure
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1738,7 +1739,7 @@ const S32 max_format = (S32)num_formats - 1;
if (!DescribePixelFormat(mhDC, pixel_format, sizeof(PIXELFORMATDESCRIPTOR),
&pfd))
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBPixelFmtDescErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1760,14 +1761,14 @@ const S32 max_format = (S32)num_formats - 1;
if (!wglMakeCurrent(mhDC, mhRC))
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextActErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
if (!gGLManager.initGL())
{
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
close();
return false;
}
@@ -1975,7 +1976,7 @@ void* LLWindowWin32::createSharedContext()
if (!rc && !(rc = wglCreateContext(mhDC)))
{
close();
- LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), 8/*LAST_EXEC_GRAPHICS_INIT*/);
+ LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBGLContextErr"), LLError::LLUserWarningMsg::ERROR_INIT_FAILED);
}
return rc;
@@ -2524,8 +2525,15 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_SYSCOMMAND:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SYSCOMMAND");
- switch (w_param)
+ switch (w_param & 0xFFF0)
{
+ case SC_CLOSE:
+ // User clicked close from system menu/taskbar or 'end process' from task manager
+ // Do nothing, will cause WM_CLOSE.
+ // If we don't get this message before WM_CLOSE, we are likely getting
+ // a kill from some external program. Win11 task manager Does cause SC_CLOSE.
+ window_imp->mReceivedSCClose = true;
+ break;
case SC_KEYMENU:
// Disallow the ALT key from triggering the default system menu.
return 0;
@@ -2540,9 +2548,30 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_CLOSE:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_CLOSE");
- // todo: WM_CLOSE can be caused by user and by task manager,
- // distinguish these cases.
- // For now assume it is always user.
+
+ window_imp->mCallbacks->handlePreCloseRequest(); // mark app as potentially closing
+ if (!window_imp->mReceivedSCClose)
+ {
+ // Some external program is trying to close the app.
+ // Assume that it's going to destroy process if it fails
+ // and try to fast-quit without confirmation or cleanup.
+ window_imp->post([=]()
+ {
+ // Check if app needs cleanup or can be closed immediately.
+ if (window_imp->mCallbacks->handleSessionExit(window_imp))
+ {
+ // Get the app to initiate cleanup.
+ window_imp->mCallbacks->handleQuit(window_imp);
+ }
+ });
+ return 0;
+ }
+ window_imp->mReceivedSCClose = false;
+
+ // There is no way to tell the difference between a user issued
+ // WM_CLOSE or task manager's WM_CLOSE.
+ // Assume it is a user and ask for confirmation, but create a marker file.
+ // If App keeps doing something after a second, or gets 'destroy' message clear the marker.
window_imp->post([=]()
{
// Will the app allow the window to close?
@@ -2564,6 +2593,16 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
}
return 0;
}
+ case WM_NCDESTROY:
+ LL_INFOS("Window") << "Received WM_NCDESTROY" << LL_ENDL;
+ break;
+ case WM_WTSSESSION_CHANGE:
+ {
+ // Detects Remote Desktop disconnects, fast user switching, session logoff
+ // w_param: WTS_CONSOLE_CONNECT, WTS_CONSOLE_DISCONNECT, WTS_SESSION_LOGOFF, etc.
+ LL_INFOS("Window") << "Received WM_WTSSESSION_CHANGE with wParam: " << (U32)w_param << LL_ENDL;
+ break;
+ }
case WM_QUERYENDSESSION:
{
// Generally means that OS is going to shut down or user is going to log off.
@@ -2585,8 +2624,10 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
|| (end_session_flags & ENDSESSION_CRITICAL) // will shutdown regardless of app state
|| (end_session_flags & ENDSESSION_LOGOFF)) // logoff, can delay shutdown
{
+ window_imp->mCallbacks->handlePreCloseRequest(); // mark app as closing
window_imp->post([=]()
{
+ LL_INFOS("Window") << "Shutting down due to session terminating" << LL_ENDL;
// Check if app needs cleanup or can be closed immediately.
if (window_imp->mCallbacks->handleSessionExit(window_imp))
{
@@ -2605,6 +2646,191 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
// if session is ending OS is going to take care of it.
return 0;
}
+ case WM_POWERBROADCAST:
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_POWERBROADCAST");
+ switch (w_param)
+ {
+ case PBT_APMSUSPEND:
+ LL_INFOS("Window") << "System is suspending (sleep/hibernate)" << LL_ENDL;
+ // System is about to enter sleep or hibernation
+ // Viewer can't function in hibernation, try to shut down.
+ // The system allows approximately two seconds for an
+ // application to handle this notification.
+ window_imp->post([=]()
+ {
+ LL_INFOS("Window") << "Shutting down due to system suspending (sleep/hibernate)" << LL_ENDL;
+ if (window_imp->mCallbacks->handleSessionExit(window_imp))
+ {
+ // Get the app to initiate cleanup.
+ window_imp->mCallbacks->handleQuit(window_imp);
+ }
+ });
+ ms_sleep(1000);
+ return TRUE;
+
+ case PBT_APMRESUMESUSPEND:
+ LL_INFOS("Window") << "System is resuming from suspend" << LL_ENDL;
+ // Shouldn't be up, but log just in case.
+ return TRUE;
+
+ case PBT_APMPOWERSTATUSCHANGE:
+ LL_INFOS("Window") << "Power status has changed" << LL_ENDL;
+ // Power status change (AC/battery)
+ // Viewer requires high performance, not much we can do.
+ // about it, but log for diagnostic purposes (example:
+ // OS trying to throw viewer at an iGPU after this message)
+ return TRUE;
+
+ default:
+ break;
+ }
+ break;
+ }
+ case WM_POST_UNINSTALL_:
+ {
+ LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_POST_UNINSTALL_");
+ // Other instance, likely velopack, requested we quit.
+ // Don't trust PID alone (can be spoofed), verify the
+ // path for security purposes before processing.
+ // Verifying path isn't a strong varranty, if this turns
+ // up to be a risk, we will want something more secure.
+ // See sendShutdownToOtherInstances for the sender.
+
+ // LPARAM contains message type.
+ DWORD message_type = static_cast<DWORD>(l_param);
+ if (message_type == WM_POST_UNINSTALL_MSG_SHUTDOWN || message_type == WM_POST_UNINSTALL_MSG_UPDATE)
+ {
+ DWORD sender_process_id = static_cast<DWORD>(w_param);
+
+ // Make sure something didn't just send us our own process
+ DWORD our_process_id = GetCurrentProcessId();
+ if (our_process_id == sender_process_id)
+ {
+ LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ from our own process, ignoring" << LL_ENDL;
+ break;
+ }
+
+ if (sender_process_id == 0)
+ {
+ LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ but couldn't get sender process ID" << LL_ENDL;
+ break;
+ }
+
+ // Open the existing sender process to verify its executable path
+ HANDLE hSenderProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, sender_process_id);
+ if (!hSenderProcess)
+ {
+ LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ but couldn't open sender process" << LL_ENDL;
+ break;
+ }
+
+ // Get the actual executable path of the sender
+ wchar_t sender_exe_path[MAX_PATH];
+ DWORD size = MAX_PATH;
+ bool got_sender_path = QueryFullProcessImageNameW(hSenderProcess, 0, sender_exe_path, &size) != 0;
+ CloseHandle(hSenderProcess);
+
+ if (!got_sender_path)
+ {
+ LL_WARNS("Window") << "Received WM_POST_UNINSTALL_ but couldn't query sender executable path" << LL_ENDL;
+ break;
+ }
+
+ // Extract directory from sender's executable path
+ wchar_t sender_dir[MAX_PATH];
+ wchar_t* file_part = nullptr;
+ DWORD result = GetFullPathNameW(sender_exe_path, MAX_PATH, sender_dir, &file_part);
+
+ if (result == 0 || result >= MAX_PATH)
+ {
+ LL_WARNS("Window") << "Failed to normalize sender executable path" << LL_ENDL;
+ break;
+ }
+
+ // Remove the filename to get just directory
+ if (file_part)
+ {
+ *file_part = L'\0';
+ }
+
+ // Remove trailing backslash
+ size_t sender_dir_len = wcslen(sender_dir);
+ if (sender_dir_len > 0 && sender_dir[sender_dir_len - 1] == L'\\')
+ {
+ sender_dir[sender_dir_len - 1] = L'\0';
+ sender_dir_len--;
+ }
+
+ // Remove "\current" suffix from sender's path if present
+ const std::wstring current_suffix = L"\\current";
+ std::wstring sender_normalized_str(sender_dir);
+ if (sender_normalized_str.length() >= current_suffix.length() &&
+ _wcsicmp(sender_normalized_str.c_str() + sender_normalized_str.length() - current_suffix.length(),
+ current_suffix.c_str()) == 0)
+ {
+ sender_normalized_str.resize(sender_normalized_str.length() - current_suffix.length());
+ }
+
+ // Get our executable directory for comparison
+ std::wstring our_wide = ll_convert<std::wstring>(gDirUtilp->getExecutableDir());
+
+ // Normalize our path
+ wchar_t our_normalized[MAX_PATH];
+ file_part = nullptr;
+
+ DWORD result2 = GetFullPathNameW(our_wide.c_str(), MAX_PATH, our_normalized, &file_part);
+
+ if (result2 == 0 || result2 >= MAX_PATH)
+ {
+ LL_WARNS("Window") << "Failed to normalize our executable path" << LL_ENDL;
+ break;
+ }
+
+ // Remove trailing backslash
+ size_t our_len = wcslen(our_normalized);
+ if (our_len > 0 && our_normalized[our_len - 1] == L'\\')
+ {
+ our_normalized[our_len - 1] = L'\0';
+ our_len--;
+ }
+
+ // Remove "\current" suffix from our path if present
+ std::wstring our_normalized_str(our_normalized);
+ if (our_normalized_str.length() >= current_suffix.length() &&
+ _wcsicmp(our_normalized_str.c_str() + our_normalized_str.length() - current_suffix.length(),
+ current_suffix.c_str()) == 0)
+ {
+ our_normalized_str.resize(our_normalized_str.length() - current_suffix.length());
+ }
+
+ // Compare the normalized base installation paths (case-insensitive)
+ if (_wcsicmp(sender_normalized_str.c_str(), our_normalized_str.c_str()) == 0)
+ {
+ window_imp->post([=]()
+ {
+ LL_INFOS("Window") << "Received valid shutdown request from verified same installation directory" << LL_ENDL;
+ // Check if app needs cleanup or can be closed immediately.
+ if (window_imp->mCallbacks->handleCloseRequest(window_imp, false))
+ {
+ // Get the app to initiate cleanup.
+ window_imp->mCallbacks->handleQuit(window_imp);
+ }
+ });
+ }
+ else
+ {
+ LL_WARNS("Window") << "Rejected shutdown request - sender not from our installation directory. "
+ << "Sender: " << ll_convert_wide_to_string(sender_normalized_str)
+ << " Our: " << ll_convert_wide_to_string(our_normalized_str) << LL_ENDL;
+ }
+ }
+ else
+ {
+ LL_WARNS("Window") << "Received invalid WM_POST_UNINSTALL_ message" << LL_ENDL;
+ }
+ break;
+ }
case WM_COMMAND:
{
LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_COMMAND");
@@ -3132,7 +3358,18 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_
case WM_DISPLAYCHANGE:
{
- WINDOW_IMP_POST(window_imp->mCallbacks->handleDisplayChanged());
+ LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DISPLAYCHANGE");
+ window_imp->post([=]() {
+ window_imp->mCallbacks->handleDisplayChanged();
+ // Note: WM_DISPLAYCHANGE was passing to WM_SETFOCUS
+ // which might have been unintended and was messing with zones.
+ // handleFocus was copied over and return 0 added, but
+ // handleFocus might be not needed here.
+ // handleFocus resets mouse, closes popups and keys, which
+ // we probablt should do on 'display change'.
+ window_imp->mCallbacks->handleFocus(window_imp);
+ });
+ return 0;
}
case WM_SETFOCUS: