From 3cb70cc3d70245c01764fadb7b0eb359d235a27b Mon Sep 17 00:00:00 2001 From: Maxim Nikolenko Date: Fri, 17 Apr 2026 21:18:37 +0300 Subject: #5664 remove non-ASCII7 characters from comments --- indra/newview/llvelopack.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'indra/newview/llvelopack.cpp') diff --git a/indra/newview/llvelopack.cpp b/indra/newview/llvelopack.cpp index 28e989c4ba..8975f7d369 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(); -- cgit v1.3 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: #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(-) (limited to 'indra/newview/llvelopack.cpp') diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index c185fc6c4a..3cb2911f9a 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 aab2635a34..afff3d5cb6 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 94a5f7951e..0dee17010c 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 0741758a0c..d0a3f2f5ce 100644 --- a/indra/newview/llappviewerwin32.h +++ b/indra/newview/llappviewerwin32.h @@ -45,6 +45,9 @@ public: 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 8975f7d369..d34d43cc48 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) -- cgit v1.3 From fc2e96135d0a55df5f3b668298fa72505d39413b Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 16 Jun 2026 18:26:40 +0300 Subject: p#587 Taskbar icon does not expand into app when opened --- indra/newview/llvelopack.cpp | 91 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 2 deletions(-) (limited to 'indra/newview/llvelopack.cpp') diff --git a/indra/newview/llvelopack.cpp b/indra/newview/llvelopack.cpp index 26f3f41db6..f76d68ed72 100644 --- a/indra/newview/llvelopack.cpp +++ b/indra/newview/llvelopack.cpp @@ -50,9 +50,12 @@ #include #include #include +#include +#include #pragma comment(lib, "shlwapi.lib") #pragma comment(lib, "ole32.lib") +#pragma comment(lib, "propsys.lib") #pragma comment(lib, "shell32.lib") #endif // LL_WINDOWS @@ -331,6 +334,17 @@ static std::wstring get_desktop_path() return L""; } +static std::wstring get_app_user_model_id() +{ + // Format: CompanyName.ProductName + // This ID is used for: + // 1. Registry registration (HKCU\Software\Classes\Applications\{exe}\AppUserModelID) + // 2. Shortcut property (IPropertyStore::SetValue with PKEY_AppUserModel_ID) + // 3. Runtime process ID (SetCurrentProcessExplicitAppUserModelID if needed) + // Must be consistent across all uses for proper taskbar grouping. + return L"LindenLab." + get_app_name_oneword(); +} + static HRESULT create_shortcut(const std::wstring& shortcut_path, const std::wstring& target_path, const std::wstring& arguments, @@ -355,6 +369,28 @@ static HRESULT create_shortcut(const std::wstring& shortcut_path, PathRemoveFileSpecW(work_dir); shell_link->SetWorkingDirectory(work_dir); + // Set AppUserModelID on the shortcut + IPropertyStore* prop_store = nullptr; + hr = shell_link->QueryInterface(IID_IPropertyStore, (void**)&prop_store); + if (SUCCEEDED(hr)) + { + PROPVARIANT pv; + PropVariantInit(&pv); + std::wstring app_id = get_app_user_model_id(); + hr = InitPropVariantFromString(app_id.c_str(), &pv); + if (SUCCEEDED(hr)) + { + hr = prop_store->SetValue(PKEY_AppUserModel_ID, pv); + PropVariantClear(&pv); + if (SUCCEEDED(hr)) + { + HRESULT hr_commit = prop_store->Commit(); + if (FAILED(hr_commit)) hr = hr_commit; + } + } + prop_store->Release(); + } + IPersistFile* persist_file = nullptr; hr = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file); if (SUCCEEDED(hr)) @@ -406,6 +442,42 @@ static void register_protocol_handler(const std::wstring& protocol, } } +static void register_app_user_model_id(const std::wstring& exe_name) +{ + // Register AppUserModelID for the executable + // This allows Windows to properly group taskbar items when users drag-and-drop + // the exe to pin it, without requiring runtime calls to SetCurrentProcessExplicitAppUserModelID + std::wstring key_path = L"SOFTWARE\\Classes\\Applications\\" + exe_name; + HKEY hkey; + + if (RegCreateKeyExW(HKEY_CURRENT_USER, key_path.c_str(), 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hkey, NULL) == ERROR_SUCCESS) + { + std::wstring app_user_model_id = get_app_user_model_id(); + LSTATUS status = RegSetValueExW(hkey, L"AppUserModelID", 0, REG_SZ, + (BYTE*)app_user_model_id.c_str(), + (DWORD)((app_user_model_id.size() + 1) * sizeof(wchar_t))); + RegCloseKey(hkey); + + if (status == ERROR_SUCCESS) + { + LL_DEBUGS("Velopack") << "Registered AppUserModelID: " + << ll_convert_wide_to_string(app_user_model_id) + << " for " << ll_convert_wide_to_string(exe_name) << LL_ENDL; + } + else + { + LL_WARNS("Velopack") << "Failed to set AppUserModelID (error " << status << ") for " + << ll_convert_wide_to_string(exe_name) << LL_ENDL; + } + } + else + { + LL_WARNS("Velopack") << "Failed to register AppUserModelID for " + << ll_convert_wide_to_string(exe_name) << LL_ENDL; + } +} + static bool get_shortcut_target(const std::wstring& lnk_path, std::wstring& target_path_str) { // Resolve the shortcut to check its target @@ -718,6 +790,12 @@ static void unregister_protocol_handler(const std::wstring& protocol) RegDeleteTreeW(HKEY_CURRENT_USER, key_path.c_str()); } +static void unregister_app_user_model_id(const std::wstring& exe_name) +{ + std::wstring key_path = L"SOFTWARE\\Classes\\Applications\\" + exe_name; + RegDeleteTreeW(HKEY_CURRENT_USER, key_path.c_str()); +} + static void register_uninstall_info(const std::wstring& install_dir, const std::wstring& app_name, const std::wstring& version) @@ -794,7 +872,9 @@ static void unregister_uninstall_info() RegDeleteTreeW(HKEY_CURRENT_USER, key_path.c_str()); } -static void create_shortcuts(const std::wstring& install_dir, const std::wstring& app_name) +static void create_shortcuts( + const std::wstring& install_dir, + const std::wstring& app_name) { std::wstring exe_path = install_dir + L"\\" + get_viewer_exe_name(); std::wstring start_menu_dir = get_start_menu_path() + L"\\" + app_name; @@ -866,19 +946,26 @@ static void on_after_install(void* user_data, const char* app_version) { std::wstring install_dir = get_install_dir(); std::wstring app_name = get_app_name(); - std::wstring exe_path = install_dir + L"\\" + get_viewer_exe_name(); + std::wstring exe_name = get_viewer_exe_name(); + std::wstring exe_path = install_dir + L"\\" + exe_name; register_protocol_handler(PROTOCOL_SECONDLIFE, L"URL:Second Life", exe_path); register_protocol_handler(PROTOCOL_GRID_INFO, L"URL:Second Life", exe_path); + + // Register AppUserModelID for taskbar pinning support + register_app_user_model_id(exe_name); + create_shortcuts(install_dir, app_name); } static void on_before_uninstall(void* user_data, const char* app_version) { std::wstring app_name = get_app_name(); + std::wstring exe_name = get_viewer_exe_name(); unregister_protocol_handler(PROTOCOL_SECONDLIFE); unregister_protocol_handler(PROTOCOL_GRID_INFO); + unregister_app_user_model_id(exe_name); remove_shortcuts(app_name); std::wstring install_dir = get_install_dir(); -- cgit v1.3