diff --git a/indra/cmake/BuildPackagesInfo.cmake b/indra/cmake/BuildPackagesInfo.cmake index c67d4ea2f848f83ce83edad78abaee77745c61d1..8b3fc9576d843d8e74e4af76b49c54451dd24385 100644 --- a/indra/cmake/BuildPackagesInfo.cmake +++ b/indra/cmake/BuildPackagesInfo.cmake @@ -15,5 +15,5 @@ add_custom_command(OUTPUT packages-info.txt COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/cmake/run_build_test.py -DAUTOBUILD_ADDRSIZE=${ADDRESS_SIZE} -DAUTOBUILD=${AUTOBUILD_EXECUTABLE} ${Python3_EXECUTABLE} - ${CMAKE_SOURCE_DIR}/../scripts/packages-formatter.py "${VIEWER_CHANNEL}" "${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}" > packages-info.txt + ${CMAKE_SOURCE_DIR}/../scripts/packages-formatter.py "${VIEWER_CHANNEL}" "${VIEWER_SHORT_VERSION}" > packages-info.txt ) diff --git a/indra/cmake/BuildVersion.cmake b/indra/cmake/BuildVersion.cmake index 51e050d3d3376f237112a0655b6e4e262e8eab5b..cc4b3bbc56df7a8a46e9440d56ef71e0f296768a 100644 --- a/indra/cmake/BuildVersion.cmake +++ b/indra/cmake/BuildVersion.cmake @@ -43,26 +43,29 @@ if(NOT DEFINED VIEWER_CHANNEL) endif() endif() +if(DEFINED VIEWER_CHANNEL) + string(REPLACE " " "" VIEWER_CHANNEL_ONEWORD ${VIEWER_CHANNEL}) +endif() + # Construct the viewer version number based on the indra/VIEWER_VERSION file if(NOT DEFINED VIEWER_SHORT_VERSION) # will be true in indra/, false in indra/newview/ set(VIEWER_VERSION_BASE_FILE "${CMAKE_SOURCE_DIR}/newview/VIEWER_VERSION.txt") if(EXISTS ${VIEWER_VERSION_BASE_FILE}) - file(STRINGS ${VIEWER_VERSION_BASE_FILE} VIEWER_SHORT_VERSION REGEX "^[0-9]+\\.[0-9]+\\.[0-9]+") - string(REGEX REPLACE "^([0-9]+)\\.[0-9]+\\.[0-9]+" "\\1" VIEWER_VERSION_MAJOR ${VIEWER_SHORT_VERSION}) - string(REGEX REPLACE "^[0-9]+\\.([0-9]+)\\.[0-9]+" "\\1" VIEWER_VERSION_MINOR ${VIEWER_SHORT_VERSION}) - string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.([0-9]+)" "\\1" VIEWER_VERSION_PATCH ${VIEWER_SHORT_VERSION}) + file(STRINGS ${VIEWER_VERSION_BASE_FILE} VIEWER_FILE_VERSION REGEX "^[0-9]+\\.[0-9]+") + string(REGEX REPLACE "^([0-9]+)\\.[0-9]+" "\\1" VIEWER_VERSION_MAJOR ${VIEWER_FILE_VERSION}) + string(REGEX REPLACE "^[0-9]+\\.([0-9]+)" "\\1" VIEWER_VERSION_MINOR ${VIEWER_FILE_VERSION}) if(REVISION_FROM_VCS) find_package(Git) endif() if((NOT REVISION_FROM_VCS) AND DEFINED ENV{CI_PIPELINE_ID}) - set(VIEWER_VERSION_REVISION $ENV{CI_PIPELINE_ID}) - message(STATUS "Revision (from environment): ${VIEWER_VERSION_REVISION}") + set(VIEWER_VERSION_PATCH $ENV{CI_PIPELINE_ID}) + message(STATUS "Revision (from environment): ${VIEWER_VERSION_PATCH}") elseif((NOT REVISION_FROM_VCS) AND DEFINED ENV{AUTOBUILD_BUILD_ID}) - set(VIEWER_VERSION_REVISION $ENV{AUTOBUILD_BUILD_ID}) - message(STATUS "Revision (from autobuild environment): ${VIEWER_VERSION_REVISION}") + set(VIEWER_VERSION_PATCH $ENV{AUTOBUILD_BUILD_ID}) + message(STATUS "Revision (from autobuild environment): ${VIEWER_VERSION_PATCH}") elseif(Git_FOUND) execute_process( COMMAND ${GIT_EXECUTABLE} rev-list HEAD --count @@ -71,24 +74,26 @@ if(NOT DEFINED VIEWER_SHORT_VERSION) # will be true in indra/, false in indra/ne OUTPUT_STRIP_TRAILING_WHITESPACE) if(GIT_REV_LIST_COUNT) - set(VIEWER_VERSION_REVISION ${GIT_REV_LIST_COUNT}) + set(VIEWER_VERSION_PATCH ${GIT_REV_LIST_COUNT}) else(GIT_REV_LIST_COUNT) - set(VIEWER_VERSION_REVISION 0) + set(VIEWER_VERSION_PATCH 0) endif(GIT_REV_LIST_COUNT) else() - set(VIEWER_VERSION_REVISION 0) + set(VIEWER_VERSION_PATCH 0) endif() - message(STATUS "Building '${VIEWER_CHANNEL}' Version ${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}") + message(STATUS "Building '${VIEWER_CHANNEL}' Version ${VIEWER_SHORT_VERSION}") + + if("${VIEWER_VERSION_PATCH}" STREQUAL "") + message(STATUS "Ultimate fallback, revision was blank or not set: will use 0") + set(VIEWER_VERSION_PATCH 0) + endif("${VIEWER_VERSION_PATCH}" STREQUAL "") + + set(VIEWER_SHORT_VERSION "${VIEWER_VERSION_MAJOR}.${VIEWER_VERSION_MINOR}.${VIEWER_VERSION_PATCH}") else(EXISTS ${VIEWER_VERSION_BASE_FILE}) message(SEND_ERROR "Cannot get viewer version from '${VIEWER_VERSION_BASE_FILE}'") endif(EXISTS ${VIEWER_VERSION_BASE_FILE}) - if("${VIEWER_VERSION_REVISION}" STREQUAL "") - message(STATUS "Ultimate fallback, revision was blank or not set: will use 0") - set(VIEWER_VERSION_REVISION 0) - endif("${VIEWER_VERSION_REVISION}" STREQUAL "") - - set(VIEWER_VERSION_AND_CHANNEL "${VIEWER_CHANNEL} ${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}") + set(VIEWER_VERSION_AND_CHANNEL "${VIEWER_CHANNEL} ${VIEWER_SHORT_VERSION}") endif(NOT DEFINED VIEWER_SHORT_VERSION) if (NOT DEFINED VIEWER_COMMIT_LONG_SHA) diff --git a/indra/cmake/ViewerManager.cmake b/indra/cmake/ViewerManager.cmake index 77cac3a187781c05f84af289e01474fbda71e30f..93cd6eb12558f721d658f7e0b88e0e781fa3ad44 100644 --- a/indra/cmake/ViewerManager.cmake +++ b/indra/cmake/ViewerManager.cmake @@ -1,4 +1,4 @@ include (Prebuilt) -if(NOT LINUX) - use_prebuilt_binary(viewer-manager) -endif() +# if(NOT LINUX) +# use_prebuilt_binary(viewer-manager) +# endif() diff --git a/indra/llfilesystem/lldir_win32.cpp b/indra/llfilesystem/lldir_win32.cpp index afd95b1afa94da904d3cc0c4bdebe661688e461f..7fa594f9163174b15be5a2d482ff1734ba59d496 100644 --- a/indra/llfilesystem/lldir_win32.cpp +++ b/indra/llfilesystem/lldir_win32.cpp @@ -381,7 +381,7 @@ bool LLDir_Win32::fileExists(const std::string& filename) const /*virtual*/ std::string LLDir_Win32::getLLPluginLauncher() { - return gDirUtilp->getExecutableDir() + gDirUtilp->getDirDelimiter() + + return gDirUtilp->getLLPluginDir() + gDirUtilp->getDirDelimiter() + "SLPlugin.exe"; } diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 0a0c2aec81243e80fa1bc5cb6a11c5abd3b74010..5a0597a36c1c7086f73da5180bf6c57580c767fa 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1549,11 +1549,16 @@ source_group("CMake Rules" FILES ViewerInstall.cmake) #build_data.json creation moved to viewer_manifest.py MAINT-6413 # the viewer_version.txt file created here is for passing to viewer_manifest and autobuild file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt" - "${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}\n") + "${VIEWER_SHORT_VERSION}\n") configure_file(llviewerbuildconfig.h.in llviewerbuildconfig.h @ONLY) LIST(APPEND viewer_HEADER_FILES ${CMAKE_CURRENT_BINARY_DIR}/llviewerbuildconfig.h) +if (WINDOWS) + LIST(APPEND viewer_SOURCE_FILES alsquirrelupdater.cpp) + LIST(APPEND viewer_HEADER_FILES alsquirrelupdater.h) +endif (WINDOWS) + if (DARWIN) LIST(APPEND viewer_SOURCE_FILES llappviewermacosx.cpp) LIST(APPEND viewer_SOURCE_FILES llappviewermacosx-objc.mm) @@ -1727,6 +1732,10 @@ if (WINDOWS) ${CMAKE_CURRENT_BINARY_DIR}/viewerRes.rc ) + configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/installers/windows/viewer.nuspec + ${CMAKE_CURRENT_BINARY_DIR}/viewer.nuspec + ) + list(APPEND viewer_RESOURCE_FILES windows.manifest ${CMAKE_CURRENT_BINARY_DIR}/viewerRes.rc @@ -2206,7 +2215,7 @@ set(ARTWORK_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE PATH "Path to artwork files.") if (LINUX) - set(product Alchemy-${ARCH}-${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}) + set(product Alchemy-${ARCH}-${VIEWER_SHORT_VERSION}) # These are the generated targets that are copied to package/ set(COPY_INPUT_DEPENDENCIES @@ -2287,10 +2296,10 @@ if (DARWIN) set(MACOSX_BUNDLE_INFO_STRING "${VIEWER_CHANNEL}") set(MACOSX_BUNDLE_ICON_FILE "alchemy.icns") set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.alchemyviewer.viewer") - set(MACOSX_BUNDLE_LONG_VERSION_STRING "${VIEWER_CHANNEL} ${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}") + set(MACOSX_BUNDLE_LONG_VERSION_STRING "${VIEWER_CHANNEL} ${VIEWER_SHORT_VERSION}") set(MACOSX_BUNDLE_BUNDLE_NAME "Alchemy") - set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}") - set(MACOSX_BUNDLE_BUNDLE_VERSION "${VIEWER_SHORT_VERSION}${VIEWER_VERSION_REVISION}") + set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${VIEWER_SHORT_VERSION}") + set(MACOSX_BUNDLE_BUNDLE_VERSION "${VIEWER_SHORT_VERSION}") set(MACOSX_BUNDLE_COPYRIGHT "Copyright (C) 2013-2021 Alchemy Development Group") set(MACOSX_BUNDLE_NSMAIN_NIB_FILE "Alchemy.nib") set(MACOSX_BUNDLE_NSPRINCIPAL_CLASS "LLApplication") diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index f5199477c52d78cde8dd8517794f43ad0e5abd47..12e41412939763fb74d07f838735c69ed5c3334f 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -6.6.9 +6.9 diff --git a/indra/newview/alsquirrelupdater.cpp b/indra/newview/alsquirrelupdater.cpp new file mode 100644 index 0000000000000000000000000000000000000000..73afe03fc746f78f17cd307c8a1ed8446443c6ad --- /dev/null +++ b/indra/newview/alsquirrelupdater.cpp @@ -0,0 +1,558 @@ +/** +* @file alsquirrelupdater.cpp +* @brief Quick Settings popdown panel +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Alchemy Viewer Source Code +* Copyright (C) 2013-2023, Alchemy Viewer Project. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* $/LicenseInfo$ +*/ + +#include "llviewerprecompiledheaders.h" + +#include "alsquirrelupdater.h" + +#include "llappviewer.h" +#include "llnotificationsutil.h" +#include "llversioninfo.h" +#include "llviewercontrol.h" + +#include "llcallbacklist.h" +#include "llprocess.h" +#include "llsdjson.h" +#include "llsdutil.h" +#include "llwin32headerslean.h" +#include "llstartup.h" + +static std::string win32_errorcode_to_string(LONG errorMessageID) +{ + if (errorMessageID == 0) + return std::string(); //No error message has been recorded + + LPWSTR messageBuffer = nullptr; + size_t size = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, errorMessageID, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), messageBuffer, 0, NULL); + + std::wstring message(messageBuffer, size); + + //Free the buffer. + LocalFree(messageBuffer); + + return ll_convert_wide_to_string(message); +} + +struct ALRegWriter +{ + static void setValueFailed(const LSTATUS& rc) { LL_WARNS() << "Failed to write reg value with error: " << win32_errorcode_to_string(rc) << LL_ENDL; } + static void checkSuccess(const LSTATUS& rc, std::function<void(const LSTATUS&)> on_failed = setValueFailed) + { + if (rc != ERROR_SUCCESS) on_failed(rc); + } + + ALRegWriter(HKEY parent_key, const std::wstring& key_name) : mSuccess(true) + { + checkSuccess(RegCreateKeyEx(parent_key, key_name.c_str(), NULL, NULL, NULL, KEY_ALL_ACCESS, NULL, &mResultKey, NULL), [&](const LSTATUS& rc) + { + mSuccess = false; + LL_WARNS() << "Failed to open " << ll_convert_wide_to_string(key_name) << " with error: " << win32_errorcode_to_string(rc) << LL_ENDL; + }); + } + + void setValue(const std::wstring& value, DWORD dwType = REG_SZ, LPCTSTR value_name = NULL) const + { + checkSuccess(RegSetValueEx(mResultKey, value_name, NULL, dwType, (LPBYTE) value.c_str(), (value.size() + 1) * sizeof(wchar_t))); + } + + void setValue(const std::string& value, DWORD dwType = REG_SZ, LPCTSTR value_name = NULL) const + { + setValue(ll_convert_string_to_wide(value), dwType, value_name); + } + + std::string getStringValue(const std::wstring& subkey_name, const std::wstring& value_name) + { + const DWORD BUFFER_SIZE = 512; + WCHAR outstr[BUFFER_SIZE]; + DWORD bufsize = BUFFER_SIZE; + checkSuccess(RegGetValue(mResultKey, subkey_name.c_str(), value_name.c_str(), RRF_RT_REG_SZ, nullptr, outstr, &bufsize), [&](const LSTATUS& rc) + { + LL_WARNS() << "Failed to read " << ll_convert_wide_to_string(value_name) << " from key " << ll_convert_wide_to_string(subkey_name) + << " with error: " << win32_errorcode_to_string(rc) << LL_ENDL; + return std::string(); + }); + std::wstring widestr(outstr, bufsize); + return ll_convert_wide_to_string(widestr); + } + + ALRegWriter createSubKey(const std::wstring& key_name) const + { + return ALRegWriter(mResultKey, key_name); + } + + void deleteTree(const wchar_t* name) const { RegDeleteTree(mResultKey, name); } + + ~ALRegWriter() + { + RegFlushKey(mResultKey); + RegCloseKey(mResultKey); + } + + operator bool() { return mSuccess; } + + bool mSuccess; + HKEY mResultKey; +}; + +// static +void ALUpdateUtils::updateSlurlRegistryKeys(const std::string& protocol, const std::string& name, const std::string& executable_path) +{ + // SecondLife slurls + std::wstring reg_path = ll_convert_string_to_wide(llformat("Software\\Classes\\%s", protocol.c_str())); + if (auto regpath = ALRegWriter(HKEY_CURRENT_USER, reg_path)) + { + regpath.setValue(name); + + ALRegWriter::checkSuccess(RegSetValueEx(regpath.mResultKey, TEXT("URL Protocol"), NULL, REG_SZ, NULL, 0)); + + if (auto defaulticon = regpath.createSubKey(TEXT("DefaultIcon"))) + { + defaulticon.setValue(executable_path); + } + + if (auto shell = regpath.createSubKey(TEXT("shell"))) + { + shell.setValue(TEXT("open")); + + if (auto open = shell.createSubKey(TEXT("open"))) + { + open.setValue(LLVersionInfo::instance().getChannel(), REG_SZ, TEXT("FriendlyAppName")); + + if (auto command = open.createSubKey(TEXT("command"))) + { + std::string open_cmd_string = llformat("\"%s\" -url \"%s\"", executable_path.c_str(), "%1"); + command.setValue(open_cmd_string, REG_EXPAND_SZ); + } + } + } + } +} + +// static +bool ALUpdateUtils::handleCommandLineParse(LLControlGroupCLP& clp) +{ + if(!ALUpdateHandler::isSupported()) return true; + + bool is_install = clp.hasOption("squirrel-install"); + bool is_update = clp.hasOption("squirrel-updated"); + bool is_uninstall = clp.hasOption("squirrel-uninstall"); + if (is_install || is_update || is_uninstall) + { + std::string install_dir = gDirUtilp->getExecutableDir(); + size_t path_end = install_dir.find_last_of('\\'); + if (path_end != std::string::npos) + { + install_dir = install_dir.substr(0, path_end); + } + + if (is_install) + { + std::string executable_path(install_dir); + gDirUtilp->append(executable_path, gDirUtilp->getExecutableFilename()); + + updateSlurlRegistryKeys("secondlife", "URL:Second Life", executable_path); + updateSlurlRegistryKeys("x-grid-info", "URL:Hypergrid", executable_path); + updateSlurlRegistryKeys("x-grid-location-info", "URL:Hypergrid", executable_path); + } + else if (is_uninstall) // uninstall + { + // Delete SecondLife and Hypergrid slurls + if (auto classes = ALRegWriter(HKEY_CURRENT_USER, TEXT("Software\\Classes"))) + { + auto appname = classes.getStringValue(TEXT("secondlife\\shell\\open"), TEXT("FriendlyAppName")); + if (appname.find(LLVersionInfo::instance().getChannel(), 0) != std::string::npos) + { + classes.deleteTree(TEXT("secondlife")); + } + appname = classes.getStringValue(TEXT("x-grid-info\\shell\\open"), TEXT("FriendlyAppName")); + if (appname.find(LLVersionInfo::instance().getChannel(), 0) != std::string::npos) + { + classes.deleteTree(TEXT("x-grid-info")); + } + appname = classes.getStringValue(TEXT("x-grid-location-info\\shell\\open"), TEXT("FriendlyAppName")); + if (appname.find(LLVersionInfo::instance().getChannel(), 0) != std::string::npos) + { + classes.deleteTree(TEXT("x-grid-location-info")); + } + } + } + + std::string updater_path = install_dir; + gDirUtilp->append(updater_path, "Update.exe"); + if (LLFile::isfile(updater_path)) + { + LLProcess::Params process_params; + process_params.executable = updater_path; + + if (is_install) + { + process_params.args.add("--createShortcut"); + } + else if (is_uninstall) + { + process_params.args.add("--removeShortcut"); + } + process_params.args.add(gDirUtilp->getExecutableFilename()); + if (is_update) + { + process_params.args.add("--updateOnly"); + } + process_params.args.add("--shortcut-locations"); + process_params.args.add("Desktop,StartMenu"); + process_params.attached = false; + process_params.autokill = false; + LLProcess::create(process_params); + } + else + { + LL_WARNS() << "Squirrel not found or viewer is not running in squirrel directory" << LL_ENDL; + } + LLAppViewer::instance()->removeDumpDir(); + LLAppViewer::instance()->removeMarkerFiles(); + return false; + } + return true; +} + +ALUpdateHandler::ALUpdateHandler() + : mUpdaterDonePump("SquirrelUpdate", true) + , mUpdateAction(E_NO_ACTION) + , mUpdateCallback(nullptr) +{ + if (gSavedSettings.getControl("AlchemyUpdateServiceURL")) + { + mUpdateURL = gSavedSettings.getString("AlchemyUpdateServiceURL"); + } + else + { + std::string channel = LLVersionInfo::instance().getChannel(); + channel.erase(std::remove_if(channel.begin(), channel.end(), isspace), channel.end()); + + mUpdateURL = llformat("http://update.alchemyviewer.net/windows%s/channel/%s/", std::to_string(ADDRESS_SIZE).c_str(), channel.c_str()); + } + LL_INFOS() << "Update service url: " << mUpdateURL << LL_ENDL; + + doPeriodically(boost::bind(&ALUpdateHandler::periodicUpdateCheck, this), gSavedSettings.getF32("AlchemyUpdateCheckInterval")); +} + +bool ALUpdateHandler::periodicUpdateCheck() +{ + check(); + return LLApp::isExiting(); +} + +bool ALUpdateHandler::start(EUpdateAction update_action, update_callback_t callback) +{ + mUpdateAction = update_action; + mUpdateCallback = callback; + + mUpdatePumpListenerName = LLEventPump::inventName("SquirrelEvent"); + mUpdaterDonePump.listen(mUpdatePumpListenerName, boost::bind(&ALUpdateHandler::processDone, this, _1)); + + std::string updater_dir = gDirUtilp->getExecutableDir(); + size_t path_end = updater_dir.find_last_of('\\'); + if (path_end != std::string::npos) + { + updater_dir = updater_dir.substr(0, path_end); + } + + std::string updater_path = updater_dir; + gDirUtilp->append(updater_path, "Update.exe"); + + if (LLFile::isfile(updater_path)) + { + // Okay, launch child. + LLProcess::Params params; + params.executable = updater_path; + params.cwd = updater_dir; + if (mUpdateAction == E_CHECK) + { + params.args.add("--checkForUpdate"); + params.args.add(mUpdateURL); + } + else if (mUpdateAction == E_DOWNLOAD) + { + params.args.add("--download"); + params.args.add(mUpdateURL); + } + else if (mUpdateAction == E_INSTALL) + { + params.autokill = false; + params.attached = false; + params.args.add("--update"); + params.args.add(mUpdateURL); + } + else if (mUpdateAction == E_QUIT_INSTALL) + { + params.autokill = false; + params.attached = false; + params.args.add("--processStartAndWait"); + params.args.add(gDirUtilp->getExecutableFilename()); + } + params.files.add(LLProcess::FileParam()); // stdin + params.files.add(LLProcess::FileParam("pipe")); // stdout + params.files.add(LLProcess::FileParam()); // stderr + params.postend = mUpdaterDonePump.getName(); + mUpdater = LLProcess::create(params); + } + + if (!mUpdater) + { + LL_WARNS() << "Failed to run updater at path '" << updater_path << "'" << LL_ENDL; + mUpdaterDonePump.stopListening(mUpdatePumpListenerName); + mUpdater = nullptr; + return false; + } + return true; +} + +bool ALUpdateHandler::processDone(const LLSD& data) +{ + bool success = false; + if (data.has("data")) + { + S32 exit_code = data["data"].asInteger(); + if (exit_code == 0) + { + success = true; + } + } + if (success) + { + LLSD update_data = LLSD::emptyMap(); + if (mUpdateAction != E_INSTALL || mUpdateAction != E_QUIT_INSTALL) + { + LLProcess::ReadPipe& childout(mUpdater->getReadPipe(LLProcess::STDOUT)); + if (childout.size()) + { + std::string output = childout.read(childout.size()); + size_t found = output.find_first_of('{', 0); + if (found != std::string::npos) + { + output = output.substr(found, std::string::npos); + + nlohmann::json update_info; + try + { + update_info = nlohmann::json::parse(output); + } + catch (nlohmann::json::exception &e) + { + LL_WARNS() << "Exception during json processing: " << e.what() << LL_ENDL; + if (mUpdateCallback != nullptr) + { + mUpdateCallback(update_data); + } + mUpdaterDonePump.stopListening(mUpdatePumpListenerName); + mUpdater = nullptr; + mUpdateAction = E_NO_ACTION; + return false; + } + + if (update_info.is_object()) + { + update_data = LlsdFromJson(update_info); + LL_INFOS() << "Outdata from updater test: " << ll_pretty_print_sd(update_data) << LL_ENDL; + } + } + } + } + + mUpdaterDonePump.stopListening(mUpdatePumpListenerName); + mUpdater = nullptr; + if (mUpdateCallback != nullptr) + { + mUpdateCallback(update_data); + } + } + else + { + LL_WARNS() << "Process error for listener " << mUpdatePumpListenerName << " with data " << ll_pretty_print_sd(data) << LL_ENDL; + mUpdaterDonePump.stopListening(mUpdatePumpListenerName); + mUpdateAction = E_NO_ACTION; + mUpdater = nullptr; + } + + mUpdateAction = E_NO_ACTION; + return true; +} + +bool ALUpdateHandler::check() +{ + if (!mUpdater && mUpdateAction == E_NO_ACTION) + { + LL_INFOS() << "Discovering viewer update..." << LL_ENDL; + return start(ALUpdateHandler::E_CHECK, + std::bind(&ALUpdateHandler::updateCheckFinished, this, std::placeholders::_1)); + } + else + { + LL_INFOS() << "Not checking update because mUpdater=" << mUpdater << " and mUpdateAction=" << mUpdateAction << LL_ENDL; + } + return false; +} + +bool ALUpdateHandler::download() +{ + if (!mUpdater) + { + LL_INFOS() << "Downloading a new update!" << LL_ENDL; + return start(ALUpdateHandler::E_DOWNLOAD, + std::bind(&ALUpdateHandler::updateDownloadFinished, this, std::placeholders::_1)); + } + return false; +} + +bool ALUpdateHandler::install() +{ + if (!mUpdater) + { + return start(ALUpdateHandler::E_INSTALL, + std::bind(&ALUpdateHandler::updateInstallFinished, this, std::placeholders::_1)); + } + return false; +} + +void ALUpdateHandler::restartToNewVersion() +{ + if (!mUpdater) + { + start(ALUpdateHandler::E_QUIT_INSTALL, update_callback_t()); + LLAppViewer::instance()->requestQuit(); + } +} + +void ALUpdateHandler::updateCheckFinished(const LLSD& data) +{ + if (data.emptyMap()) return; + ALVersionInfo cur_ver(LLVersionInfo::instance().getMajor(), LLVersionInfo::instance().getMinor(), LLVersionInfo::instance().getPatch()); + ALVersionInfo new_ver; + if (data.has("futureVersion")) new_ver.parse(data["futureVersion"].asString()); + + if (new_ver > cur_ver) + { + mSavedUpdateInfo = data; + LL_WARNS() << "New ver found: " << new_ver.version() << LL_ENDL; + static LLCachedControl<S32> update_preference(gSavedSettings, "AlchemyUpdatePreference", 0); + if (update_preference == 0) + { + install(); + } + else if (update_preference == 1) + { + download(); + } + else if (update_preference == 2) + { + LLSD args; + args["VIEWER_VER"] = llformat("%s %s", LLVersionInfo::instance().getChannel().c_str(), LLVersionInfo::instance().getShortVersion().c_str()); + args["VIEWER_UPDATES"] = llformat("%s", new_ver.version().c_str()); + LLSD payload; + payload["user_update_action"] = LLSD(E_DOWNLOAD_INSTALL); + LLNotificationsUtil::add("UpdateDownloadRequest", args, payload, boost::bind(&ALUpdateHandler::onUpdateNotification, this, _1, _2)); + } + } + else + { + LL_INFOS() << "No new update was found." << LL_ENDL; + mUpdateAction = E_NO_ACTION; + } +} + +void ALUpdateHandler::updateDownloadFinished(const LLSD& data) +{ + static LLCachedControl<S32> update_preference(gSavedSettings, "AlchemyUpdatePreference", 0); + if (update_preference == 1) + { + ALVersionInfo new_ver; + if (mSavedUpdateInfo.has("futureVersion")) new_ver.parse(mSavedUpdateInfo["futureVersion"].asString()); + + std::string releases; + if (mSavedUpdateInfo.has("releasesToApply")) + { + LLSD release_array = mSavedUpdateInfo["releasesToApply"]; + if (release_array.size() > 0) + { + for (LLSD::array_const_iterator it = release_array.beginArray(), end_it = release_array.endArray(); it != end_it; ++it) + { + auto release_entry = *it; + if (release_entry.isMap()) + { + releases += release_entry["version"].asString(); + } + } + } + } + LLSD args; + args["VIEWER_VER"] = llformat("%s %s", LLVersionInfo::instance().getChannel().c_str(), new_ver.version().c_str()); + args["VIEWER_UPDATES"] = releases; + LLSD payload; + payload["user_update_action"] = LLSD(E_DOWNLOADED); + LLNotificationsUtil::add("UpdateDownloaded", args, payload, boost::bind(&ALUpdateHandler::onUpdateNotification, this, _1, _2)); + } +} + +void ALUpdateHandler::updateInstallFinished(const LLSD& data) +{ + static LLCachedControl<S32> update_preference(gSavedSettings, "AlchemyUpdatePreference", 0); + // Autodownload and install + if (update_preference == 0) + { + ALVersionInfo new_ver; + if (mSavedUpdateInfo.has("futureVersion")) new_ver.parse(mSavedUpdateInfo["futureVersion"].asString()); + LLSD args; + args["VIEWER_VER"] = llformat("%s %s", LLVersionInfo::instance().getChannel().c_str(), LLVersionInfo::instance().getShortVersion().c_str()); + args["VIEWER_UPDATES"] = llformat("%s", new_ver.version().c_str()); + LLSD payload; + payload["user_update_action"] = LLSD(E_INSTALLED_RESTART); + LLNotificationsUtil::add((LLStartUp::getStartupState() < STATE_STARTED ? "UpdateInstalledRestart" : "UpdateInstalledRestartToast"), args, payload, boost::bind(&ALUpdateHandler::onUpdateNotification, this, _1, _2)); + } + // Autodownload and request install or Request download and request install + else if (update_preference == 1 || update_preference == 2) + { + restartToNewVersion(); + } +} + +void ALUpdateHandler::onUpdateNotification(const LLSD& notification, const LLSD& response) +{ + const S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) + { + S32 update_action = notification["payload"]["user_update_action"].asInteger(); + if (update_action == E_INSTALLED_RESTART) + { + restartToNewVersion(); + } + else if (update_action == E_DOWNLOAD || update_action == E_DOWNLOAD_INSTALL) + { + install(); + } + return; + } + mUpdateAction = E_NO_ACTION; + mSavedUpdateInfo = LLSD::emptyMap(); +} diff --git a/indra/newview/alsquirrelupdater.h b/indra/newview/alsquirrelupdater.h new file mode 100644 index 0000000000000000000000000000000000000000..1166333cdab3e4e64386a50ea26f29db1b7980dc --- /dev/null +++ b/indra/newview/alsquirrelupdater.h @@ -0,0 +1,174 @@ +/** +* @file alsquirrelupdater.h +* @brief Quick Settings popdown panel +* +* $LicenseInfo:firstyear=2013&license=viewerlgpl$ +* Alchemy Viewer Source Code +* Copyright (C) 2018, Alchemy Viewer Project. +* +* This library is free software; you can redistribute it and/or +* modify it under the terms of the GNU Lesser General Public +* License as published by the Free Software Foundation; +* version 2.1 of the License only. +* +* This library is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +* Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public +* License along with this library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* $/LicenseInfo$ +*/ + + +#include "llcommandlineparser.h" + +#include "llevents.h" +#include "lleventtimer.h" +#include "llprocess.h" +#include "llsingleton.h" + +class ALUpdateUtils +{ +public: + // static functions + static void updateSlurlRegistryKeys(const std::string& protocol, const std::string& name, const std::string& executable_path); + static bool handleCommandLineParse(LLControlGroupCLP& clp); +}; + +class ALUpdateHandler : public LLSingleton<ALUpdateHandler> +{ + LLSINGLETON(ALUpdateHandler); +public: + typedef enum EUpdateAction { + E_NO_ACTION = 0, + E_CHECK, + E_DOWNLOAD, + E_INSTALL, + E_QUIT_INSTALL + } e_update_action; + + typedef std::function<void(const LLSD& data)> update_callback_t; + + bool periodicUpdateCheck(); + + bool check(); + bool download(); + bool install(); + + void restartToNewVersion(); + + struct ALVersionInfo + { + ALVersionInfo() + : mMajor(0), mMinor(0), mPatch(0) + {} + ALVersionInfo(S32 major, S32 minor, S32 patch) + : mMajor(major), mMinor(minor), mPatch(patch) + {} + + bool parse(const std::string& instr) + { + std::istringstream inver(instr); + S32 ver_part; + try + { + inver >> ver_part; + mMajor = ver_part; + char c = inver.get(); // skip the period + if (c != '.') { return false; } + inver >> ver_part; + mMinor = ver_part; + c = inver.get(); // skip the hypen + if (c != '.') { return false; } + inver >> ver_part; + mPatch = ver_part; + } + catch (...) + { + return false; + } + return true; + } + + std::string version() + { + return llformat("%i.%i.%i", mMajor, mMinor, mPatch); + } + + bool operator == (const ALVersionInfo& rhs) const + { + return (mMajor == rhs.mMajor && mMinor == rhs.mMinor && mPatch == rhs.mPatch); + } + bool operator != (const ALVersionInfo& rhs) const + { + return (mMajor != rhs.mMajor || (mMinor != rhs.mMinor || mPatch != rhs.mPatch)); + } + bool operator < (const ALVersionInfo& rhs) const + { + return ((mMajor < rhs.mMajor) + || ((mMajor <= rhs.mMajor && mMinor < rhs.mMinor) + || (mMajor <= rhs.mMajor && mMinor <= rhs.mMinor && mPatch < rhs.mPatch))); + } + bool operator <= (const ALVersionInfo& rhs) const + { + return (*this < rhs) || (*this == rhs); + } + bool operator > (const ALVersionInfo& rhs) const + { + return ((mMajor > rhs.mMajor) + || ((mMajor >= rhs.mMajor && mMinor > rhs.mMinor) + || (mMajor >= rhs.mMajor && mMinor >= rhs.mMinor && mPatch > rhs.mPatch))); + } + bool operator >= (const ALVersionInfo& rhs) const + { + return (*this > rhs) || (*this == rhs); + } + + private: + S32 mMajor, mMinor, mPatch; + }; + + static bool isSupported() + { + std::string updater_path = gDirUtilp->getExecutableDir(); + size_t path_end = updater_path.find_last_of('\\'); + if (path_end != std::string::npos) + { + updater_path = updater_path.substr(0, path_end); + } + + gDirUtilp->append(updater_path, "Update.exe"); + return LLFile::isfile(updater_path); + } + +private: + bool start(EUpdateAction update_action, update_callback_t callback); + bool processDone(const LLSD& data); + + typedef enum EUserUpdateAction { + E_INSTALLED_RESTART = 0, + E_DOWNLOADED, + E_DOWNLOAD_INSTALL + } e_user_update_action; + + void updateCheckFinished(const LLSD& data); + void updateDownloadFinished(const LLSD& data); + void updateInstallFinished(const LLSD& data); + + void onUpdateNotification(const LLSD& notification, const LLSD& response); + + LLProcessPtr mUpdater; + + std::string mUpdatePumpListenerName; + LLEventStream mUpdaterDonePump; + + EUpdateAction mUpdateAction; + update_callback_t mUpdateCallback; + + LLSD mSavedUpdateInfo; + std::string mUpdateURL; +}; diff --git a/indra/newview/app_settings/cmd_line.xml b/indra/newview/app_settings/cmd_line.xml index e16a5c7e76014ece4bdc11a137a51c10aa9803e8..09698f60f072761a32800c749a92bfbfb1c18a8b 100644 --- a/indra/newview/app_settings/cmd_line.xml +++ b/indra/newview/app_settings/cmd_line.xml @@ -415,6 +415,40 @@ <key>map-to</key> <string>CmdLineSkipUpdater</string> </map> - + <key>squirrel-install</key> + <map> + <key>count</key> + <integer>1</integer> + <key>map-to</key> + <string>CmdLineSquirrelInstall</string> + </map> + <key>squirrel-firstrun</key> + <map> + <key>count</key> + <integer>0</integer> + <key>map-to</key> + <string>CmdLineSquirrelFirstRun</string> + </map> + <key>squirrel-updated</key> + <map> + <key>count</key> + <integer>1</integer> + <key>map-to</key> + <string>CmdLineSquirrelUpdated</string> + </map> + <key>squirrel-obsolete</key> + <map> + <key>count</key> + <integer>1</integer> + <key>map-to</key> + <string>CmdLineSquirrelObsolete</string> + </map> + <key>squirrel-uninstall</key> + <map> + <key>count</key> + <integer>1</integer> + <key>map-to</key> + <string>CmdLineSquirrelUninstall</string> + </map> </map> </llsd> diff --git a/indra/newview/app_settings/settings_alchemy.xml b/indra/newview/app_settings/settings_alchemy.xml index a68ff67be6aa929047f442d06a0dae7785500d57..734dbc79d03c5612b5a14b3e5800af50001bf18c 100644 --- a/indra/newview/app_settings/settings_alchemy.xml +++ b/indra/newview/app_settings/settings_alchemy.xml @@ -805,6 +805,28 @@ <key>Value</key> <integer>1</integer> </map> + <key>AlchemyUpdatePreference</key> + <map> + <key>Comment</key> + <string>Default behavior of updater 0(Download and install), 1(download and prompt for install), 2(Prompt for download and install)</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>S32</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>AlchemyUpdateCheckInterval</key> + <map> + <key>Comment</key> + <string>How frequently to check for updates in seconds.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>14400.0</real> + </map> <key>ChatAlerts</key> <map> <key>Comment</key> @@ -816,6 +838,61 @@ <key>Value</key> <boolean>0</boolean> </map> + <key>CmdLineSquirrelInstall</key> + <map> + <key>Comment</key> + <string>Command line flag for initial install from squirrel with version as arguement</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string /> + </map> + <key>CmdLineSquirrelFirstRun</key> + <map> + <key>Comment</key> + <string>Command line flag for initial application run from squirrel</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>CmdLineSquirrelUpdated</key> + <map> + <key>Comment</key> + <string>Command line flag for update from squirrel with version as arguement</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string /> + </map> + <key>CmdLineSquirrelObsolete</key> + <map> + <key>Comment</key> + <string>Command line flag for obsolete version from squirrel with version as arguement</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string /> + </map> + <key>CmdLineSquirrelUninstall</key> + <map> + <key>Comment</key> + <string>Command line flag for uninstall from squirrel with version as arguement</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string /> + </map> <key>GroupSnoozeTime</key> <map> <key>Comment</key> diff --git a/indra/newview/installers/windows/viewer.nuspec b/indra/newview/installers/windows/viewer.nuspec new file mode 100644 index 0000000000000000000000000000000000000000..af8fb411ab8028f558f3da4cd2e3a23192ff099d --- /dev/null +++ b/indra/newview/installers/windows/viewer.nuspec @@ -0,0 +1,16 @@ +<?xml version="1.0"?> +<package > + <metadata> + <id>${VIEWER_CHANNEL_ONEWORD}</id> + <version>${VIEWER_VERSION_MAJOR}.${VIEWER_VERSION_MINOR}.${VIEWER_VERSION_PATCH}</version> + <authors>Alchemy Development Group</authors> + <owners>Alchemy Development Group</owners> + <title>${VIEWER_CHANNEL}</title> + <description>${VIEWER_CHANNEL}</description> + <copyright>Copyright (C) 2013-2023 Alchemy Development Group</copyright> + <projectUrl>https://www.alchemyviewer.org</projectUrl> + </metadata> + <files> + <file src="Release\**\*.*" target="lib\net45\" exclude="Release\**\*.pdb;Release\**\*.vshost.*;Release\**\*.nsi;Release\**\*.bat;Release\**\*_Setup.exe;Release\**\*-bin.exe"/> + </files> +</package> diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index cf1c97707b79be759e6af71f5ee2dca8526a83d6..6f5fe3352cea4d73d7d9f9435e486a435890d9e5 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -31,6 +31,9 @@ #include "llappviewer.h" // Viewer includes +#if LL_WINDOWS +#include "alsquirrelupdater.h" +#endif #include "llregex.h" #include "llversioninfo.h" #include "llfeaturemanager.h" @@ -1138,73 +1141,23 @@ bool LLAppViewer::init() gGLActive = FALSE; -#if LL_RELEASE_FOR_DOWNLOAD && !defined(LL_LINUX) +#if LL_RELEASE_FOR_DOWNLOAD // Skip updater if this is a non-interactive instance if (!gSavedSettings.getBOOL("CmdLineSkipUpdater") && !gNonInteractive) { - LLProcess::Params updater; - updater.desc = "updater process"; - // Because it's the updater, it MUST persist beyond the lifespan of the - // viewer itself. - updater.autokill = false; - std::string updater_file; #if LL_WINDOWS - updater_file = "SLVersionChecker.exe"; - updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, updater_file); -#elif LL_DARWIN - updater_file = "SLVersionChecker"; - updater.executable = gDirUtilp->add(gDirUtilp->getAppRODataDir(), "updater", updater_file); -#else - updater_file = "SLVersionChecker"; - updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, updater_file); + // Init updater here + if (ALUpdateHandler::isSupported()) + { + ALUpdateHandler::getInstance()->check(); + } #endif - // add LEAP mode command-line argument to whichever of these we selected - updater.args.add("leap"); - // UpdaterServiceSettings - if (gSavedSettings.getBOOL("FirstLoginThisInstall")) - { - // Befor first login, treat this as 'manual' updates, - // updater won't install anything, but required updates - updater.args.add("0"); - } - else - { - updater.args.add(stringize(gSavedSettings.getU32("UpdaterServiceSetting"))); - } - // channel - updater.args.add(LLVersionInfo::instance().getChannel()); - // testok - updater.args.add(stringize(gSavedSettings.getBOOL("UpdaterWillingToTest"))); - // ForceAddressSize - updater.args.add(stringize(gSavedSettings.getU32("ForceAddressSize"))); - - try - { - // Run the updater. An exception from launching the updater should bother us. - LLLeap::create(updater, true); - mUpdaterNotFound = false; - } - catch (...) - { - LLUIString details = LLNotifications::instance().getGlobalString("LLLeapUpdaterFailure"); - details.setArg("[UPDATER_APP]", updater_file); - OSMessageBox( - details.getString(), - LLStringUtil::null, - OSMB_OK); - mUpdaterNotFound = true; - } } else { LL_WARNS("InitInfo") << "Skipping updater check." << LL_ENDL; } - if (mUpdaterNotFound) - { - LL_WARNS("InitInfo") << "Failed to launch updater. Skipping Leap commands." << LL_ENDL; - } - else { // Iterate over --leap command-line options. But this is a bit tricky: if // there's only one, it won't be an array at all. @@ -1230,13 +1183,6 @@ bool LLAppViewer::init() LLLeap::create("", leap.asString(), false); // exception=false } } - - if (gSavedSettings.getBOOL("QAMode") && gSavedSettings.getS32("QAModeEventHostPort") > 0) - { - LL_WARNS("InitInfo") << "QAModeEventHostPort DEPRECATED: " - << "lleventhost no longer supported as a dynamic library" - << LL_ENDL; - } #endif //LL_RELEASE_FOR_DOWNLOAD LLTextUtil::TextHelpers::iconCallbackCreationFunction = create_text_segment_icon_from_url_match; @@ -2613,6 +2559,12 @@ bool LLAppViewer::initConfiguration() return false; } +#if LL_WINDOWS + if (ALUpdateHandler::isSupported() && !ALUpdateUtils::handleCommandLineParse(clp)) + { + return false; + } +#endif // - selectively apply settings // If the user has specified a alternate settings file name. @@ -3201,7 +3153,7 @@ LLSD LLAppViewer::getViewerInfo() const // LLFloaterAbout. LLSD info; auto& versionInfo(LLVersionInfo::instance()); - info["VIEWER_VERSION"] = LLSDArray(versionInfo.getMajor())(versionInfo.getMinor())(versionInfo.getPatch())(versionInfo.getBuild()); + info["VIEWER_VERSION"] = LLSDArray(versionInfo.getMajor())(versionInfo.getMinor())(versionInfo.getPatch()); info["VIEWER_VERSION_STR"] = versionInfo.getVersion(); info["CHANNEL"] = versionInfo.getChannel(); info["ADDRESS_SIZE"] = ADDRESS_SIZE; diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 91fb124d2c201c81554c4e8b7b8cbd1e76c664b7..f536c9f98d87884a22c2f338727e560d7e1317be 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -761,7 +761,7 @@ void LLAppViewerWin32::initCrashReporting(bool reportFreeze) sentry_options_set_dsn(options, SENTRY_DSN); sentry_options_set_release(options, LL_VIEWER_CHANNEL_AND_VERSION); - std::string crashpad_path = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "crashpad_handler.exe"); + std::string crashpad_path = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "sentry", "crashpad_handler.exe"); sentry_options_set_handler_pathw(options, ll_convert_string_to_wide(crashpad_path).c_str()); std::string database_path = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "sentry"); diff --git a/indra/newview/llversioninfo.cpp b/indra/newview/llversioninfo.cpp index 35a9f07f37c6eaf91d2d8721a6853ce0ddce7378..c91b63e2358a2bc21034a012270c3e766ee887e0 100644 --- a/indra/newview/llversioninfo.cpp +++ b/indra/newview/llversioninfo.cpp @@ -36,8 +36,7 @@ #if ! defined(LL_VIEWER_CHANNEL) \ || ! defined(LL_VIEWER_VERSION_MAJOR) \ || ! defined(LL_VIEWER_VERSION_MINOR) \ - || ! defined(LL_VIEWER_VERSION_PATCH) \ - || ! defined(LL_VIEWER_VERSION_BUILD) + || ! defined(LL_VIEWER_VERSION_PATCH) #error "Channel or Version information is undefined" #endif @@ -95,7 +94,7 @@ S32 LLVersionInfo::getPatch() S32 LLVersionInfo::getBuild() { - return LL_VIEWER_VERSION_BUILD; + return 0; } const std::string& LLVersionInfo::getVersion() @@ -113,7 +112,7 @@ const std::string& LLVersionInfo::getChannelAndVersion() if (mVersionChannel.empty()) { // cache the version string - mVersionChannel = getChannel() + " " + getVersion(); + mVersionChannel = getChannel() + " " + getShortVersion(); } return mVersionChannel; diff --git a/indra/newview/llviewerbuildconfig.h.in b/indra/newview/llviewerbuildconfig.h.in index 141304158fe23f5c6bb231df499b1261bfcd2f8d..a408ccd56a508c8f3dcb7e3f4602a521caeeaefe 100644 --- a/indra/newview/llviewerbuildconfig.h.in +++ b/indra/newview/llviewerbuildconfig.h.in @@ -41,7 +41,6 @@ #define LL_VIEWER_VERSION_MAJOR @VIEWER_VERSION_MAJOR@ #define LL_VIEWER_VERSION_MINOR @VIEWER_VERSION_MINOR@ #define LL_VIEWER_VERSION_PATCH @VIEWER_VERSION_PATCH@ -#define LL_VIEWER_VERSION_BUILD @VIEWER_VERSION_REVISION@ // Sentry #define SENTRY_DSN "@SENTRY_DSN@" diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index 6f65655ab5385928493cebcd818b719508f16bda..7feea83287c9906715d49b0e45e64477d4c149a7 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -956,7 +956,7 @@ bool LLVivoxVoiceClient::startAndLaunchDaemon() #if LL_WINDOWS // On windows use exe (not work or RO) directory std::string exe_path = gDirUtilp->getExecutableDir(); - gDirUtilp->append(exe_path, "SLVoice.exe"); + gDirUtilp->append(exe_path, "voice", "SLVoice.exe"); #elif LL_DARWIN // On MAC use resource directory std::string exe_path = gDirUtilp->getAppRODataDir(); diff --git a/indra/newview/res/viewerRes.rc b/indra/newview/res/viewerRes.rc index c377639579df3d5e39189f201b9d4ccffef0d971..8f0f6190f1308b0f7155dd280012a3e0e78add44 100755 --- a/indra/newview/res/viewerRes.rc +++ b/indra/newview/res/viewerRes.rc @@ -136,8 +136,8 @@ TOOLNO CURSOR "llno.cur" // VS_VERSION_INFO VERSIONINFO - FILEVERSION ${VIEWER_VERSION_MAJOR},${VIEWER_VERSION_MINOR},${VIEWER_VERSION_PATCH},${VIEWER_VERSION_REVISION} - PRODUCTVERSION ${VIEWER_VERSION_MAJOR},${VIEWER_VERSION_MINOR},${VIEWER_VERSION_PATCH},${VIEWER_VERSION_REVISION} + FILEVERSION ${VIEWER_VERSION_MAJOR},${VIEWER_VERSION_MINOR},${VIEWER_VERSION_PATCH},0 + PRODUCTVERSION ${VIEWER_VERSION_MAJOR},${VIEWER_VERSION_MINOR},${VIEWER_VERSION_PATCH},0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -154,12 +154,13 @@ BEGIN BEGIN VALUE "CompanyName", "Alchemy Development Group" VALUE "FileDescription", "Alchemy" - VALUE "FileVersion", "${VIEWER_VERSION_MAJOR}.${VIEWER_VERSION_MINOR}.${VIEWER_VERSION_PATCH}.${VIEWER_VERSION_REVISION}" + VALUE "FileVersion", "${VIEWER_VERSION_MAJOR}.${VIEWER_VERSION_MINOR}.${VIEWER_VERSION_PATCH}" VALUE "InternalName", "Alchemy" - VALUE "LegalCopyright", "Copyright (C) 2013-2021 Alchemy Development Group" + VALUE "LegalCopyright", "Copyright (C) 2013-2023 Alchemy Development Group" VALUE "OriginalFilename", "Alchemy.exe" VALUE "ProductName", "Alchemy" - VALUE "ProductVersion", "${VIEWER_VERSION_MAJOR}.${VIEWER_VERSION_MINOR}.${VIEWER_VERSION_PATCH}.${VIEWER_VERSION_REVISION}" + VALUE "ProductVersion", "${VIEWER_VERSION_MAJOR}.${VIEWER_VERSION_MINOR}.${VIEWER_VERSION_PATCH}" + VALUE "SquirrelAwareVersion", "1" END END BLOCK "VarFileInfo" diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 586807e2cf1b1ea05d527a1861ed199c1ed799c3..1b734eab47b09c63c94c5fde6b3a3f695ad4df0e 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -12730,4 +12730,63 @@ Would you like to reset the graphic preferences? notext="Leave as-is" canceltext="Never reset"/> </notification> + + <notification + icon="alertmodal.tga" + name="UpdateDownloadRequest" + type="alertmodal"> +A new version has been detected. +Would you like to download and install [VIEWER_VER] with following updates now? + +[VIEWER_UPDATES] + <tag>confirm</tag> + <usetemplate + name="okcancelbuttons" + notext="Remind Me Later" + yestext="Ok"/> + </notification> + + <notification + icon="alertmodal.tga" + name="UpdateDownloaded" + type="alertmodal"> +[VIEWER_VER] has been downloaded. +Would you like to install [VIEWER_VER] for following updates now? + +[VIEWER_UPDATES] + <tag>confirm</tag> + <usetemplate + name="okcancelbuttons" + notext="Remind Me Later" + yestext="Ok"/> + </notification> + + <notification + icon="alertmodal.tga" + name="UpdateInstalledRestart" + type="alertmodal"> +Your viewer has been updated to the following version: +[VIEWER_VER] => [VIEWER_UPDATES] + +Would you like to restart now? + <tag>confirm</tag> + <usetemplate + name="okcancelbuttons" + notext="Remind Me Later" + yestext="Ok"/> + </notification> + <notification + icon="notify.tga" + name="UpdateInstalledRestartToast" + type="notify"> +Your viewer has been updated to the following version: +[VIEWER_VER] => [VIEWER_UPDATES] + +Would you like to restart now? + <tag>confirm</tag> + <usetemplate + name="okcancelbuttons" + notext="Remind Me Later" + yestext="Ok"/> + </notification> </notifications> diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index 15b69af4e6e97e2baf3138a24973acd122ec8564..b3f8bf6b70395d2310eb00936d0f518b6928e88a 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -35,7 +35,7 @@ <!-- about dialog/support string--> <string name="AboutHeader"> -[CHANNEL] [VIEWER_VERSION_0].[VIEWER_VERSION_1].[VIEWER_VERSION_2].[VIEWER_VERSION_3] ([ADDRESS_SIZE]bit) +[CHANNEL] [VIEWER_VERSION_0].[VIEWER_VERSION_1].[VIEWER_VERSION_2] ([ADDRESS_SIZE]bit) [[VIEWER_RELEASE_NOTES_URL] [ReleaseNotes]] </string> <string name="AboutCompiler">Compiler Version: [COMPILER] [COMPILER_VERSION]</string> diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 89c69bf0ed48d1d653baab176b4aed23f59d3482..bc003fc1f821f3d727dc7f60a7f808af58557881 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -439,22 +439,11 @@ def construct(self): # Find alchemy-bin.exe in the 'configuration' dir, then rename it to the result of final_exe. self.path(src='%s/alchemy-bin.exe' % self.args['configuration'], dst=self.final_exe()) - with self.prefix(src=os.path.join(pkgdir, "VMP")): - # include the compiled launcher scripts so that it gets included in the file_list - self.path('SLVersionChecker.exe') - - with self.prefix(dst="vmp_icons"): - with self.prefix(src=self.icon_path()): - self.path(src="alchemy.ico", dst="secondlife.ico") - #VMP Tkinter icons - with self.prefix(src="vmp_icons"): - self.path("*.png") - self.path("*.gif") - # Plugin host application - self.path2basename(os.path.join(os.pardir, - 'llplugin', 'slplugin', self.args['configuration']), - "slplugin.exe") + with self.prefix(dst="llplugin"): + self.path2basename(os.path.join(os.pardir, + 'llplugin', 'slplugin', self.args['configuration']), + "slplugin.exe") # Get shared libs from the shared libs staging directory with self.prefix(src=os.path.join(self.args['build'], os.pardir, @@ -482,34 +471,23 @@ def construct(self): else: self.path(src="kdu.dll", dst="kdu.dll") - # These need to be installed as a SxS assembly, currently a 'private' assembly. - # See http://msdn.microsoft.com/en-us/library/ms235291(VS.80).aspx - self.path("concrt140.dll") - self.path("msvcp140.dll") - self.path("msvcp140_1.dll") - self.path("msvcp140_2.dll") - self.path("msvcp140_atomic_wait.dll") - self.path("msvcp140_codecvt_ids.dll") - self.path("vccorlib140.dll") - self.path("vcruntime140.dll") - self.path("vcruntime140_1.dll") - # SLVoice executable - with self.prefix(src=os.path.join(pkgdir, 'bin', 'release')): - self.path("SLVoice.exe") + with self.prefix(dst="voice"): + with self.prefix(src=os.path.join(pkgdir, 'bin', 'release')): + self.path("SLVoice.exe") - # Vivox libraries - if (self.address_size == 64): - self.path("vivoxsdk_x64.dll") - self.path("ortp_x64.dll") - else: - self.path("vivoxsdk.dll") - self.path("ortp.dll") + # Vivox libraries + if (self.address_size == 64): + self.path("vivoxsdk_x64.dll") + self.path("ortp_x64.dll") + else: + self.path("vivoxsdk.dll") + self.path("ortp.dll") # Sentry if self.args['sentry'] == 'ON' or self.args['sentry'] == 'TRUE': self.path("sentry.dll") - with self.prefix(src=os.path.join(pkgdir, 'bin', 'release')): + with self.prefix(src=os.path.join(pkgdir, 'bin', 'release'), dst="sentry"): self.path("crashpad_handler.exe") if self.args['discord'] == 'ON' or self.args['discord'] == 'TRUE': @@ -626,139 +604,27 @@ def construct(self): if not self.is_packaging_viewer(): self.package_file = "copied_deps" - def nsi_file_commands(self, install=True): - def wpath(path): - if path.endswith('/') or path.endswith(os.path.sep): - path = path[:-1] - path = path.replace('/', '\\') - return path - - result = "" - dest_files = [pair[1] for pair in self.file_list if pair[0] and os.path.isfile(pair[1])] - # sort deepest hierarchy first - dest_files.sort(key=lambda f: (f.count(os.path.sep), f), reverse=True) - out_path = None - for pkg_file in dest_files: - rel_file = os.path.normpath(pkg_file.replace(self.get_dst_prefix()+os.path.sep,'')) - installed_dir = wpath(os.path.join('$INSTDIR', os.path.dirname(rel_file))) - pkg_file = wpath(os.path.normpath(pkg_file)) - if installed_dir != out_path: - if install: - out_path = installed_dir - result += 'SetOutPath ' + out_path + '\n' - if install: - result += 'File ' + pkg_file + '\n' - else: - result += 'Delete ' + wpath(os.path.join('$INSTDIR', rel_file)) + '\n' - - # at the end of a delete, just rmdir all the directories - if not install: - deleted_file_dirs = [os.path.dirname(pair[1].replace(self.get_dst_prefix()+os.path.sep,'')) for pair in self.file_list] - # find all ancestors so that we don't skip any dirs that happened to have no non-dir children - deleted_dirs = [] - for d in deleted_file_dirs: - deleted_dirs.extend(path_ancestors(d)) - # sort deepest hierarchy first - deleted_dirs.sort(key=lambda f: (f.count(os.path.sep), f), reverse=True) - prev = None - for d in deleted_dirs: - if d != prev: # skip duplicates - result += 'RMDir ' + wpath(os.path.join('$INSTDIR', os.path.normpath(d))) + '\n' - prev = d - - return result - def package_finish(self): - # a standard map of strings for replacing in the templates - substitution_strings = { - 'version' : '.'.join(self.args['version']), - 'version_short' : '.'.join(self.args['version'][:-1]), - 'version_dashes' : '-'.join(self.args['version']), - 'version_registry' : '%s(%s)' % - ('.'.join(self.args['version']), self.address_size), - 'final_exe' : self.final_exe(), - 'flags':'', - 'app_name':self.app_name(), - 'app_name_oneword':self.app_name_oneword() - } + nuget_exe = os.path.join(self.args['build'], os.pardir, 'packages', 'squirrel', 'nuget.exe') + self.run_command( + [nuget_exe, + 'pack', + '-Properties', 'NoWarn=NU5128', + os.path.join(self.args['build'], 'viewer.nuspec')]) + + squirrel_exe = os.path.join(self.args['build'], os.pardir, 'packages', 'squirrel', 'Squirrel.exe') + self.run_command( + [squirrel_exe, + 'releasify', + '--releaseDir', os.path.join(self.args['build'], 'Releases'), + '--framework', 'vcredist143-x64', + '--icon', os.path.join(self.args['source'], 'installers', 'windows', 'install_icon.ico'), + '--splashImage', os.path.join(self.args['source'], 'installers', 'windows', 'splash.gif'), + '--package', os.path.join(self.args['build'], '{}.{}.nupkg'.format(self.app_name_oneword(), '.'.join(self.args['version'])))]) installer_file = self.installer_base_name() + '_Setup.exe' - substitution_strings['installer_file'] = installer_file - - version_vars = """ - !define INSTEXE "SLVersionChecker.exe" - !define VERSION "%(version_short)s" - !define VERSION_LONG "%(version)s" - !define VERSION_DASHES "%(version_dashes)s" - !define VERSION_REGISTRY "%(version_registry)s" - !define VIEWER_EXE "%(final_exe)s" - """ % substitution_strings - - if self.channel_type() == 'release': - substitution_strings['caption'] = CHANNEL_VENDOR_BASE - else: - substitution_strings['caption'] = self.app_name() + ' ${VERSION}' - - inst_vars_template = """ - OutFile "%(installer_file)s" - !define INSTNAME "%(app_name_oneword)s" - !define SHORTCUT "%(app_name)s" - !define URLNAME "secondlife" - Caption "%(caption)s" - """ - - if(self.address_size == 64): - engage_registry="SetRegView 64" - program_files="!define MULTIUSER_USE_PROGRAMFILES64" - else: - engage_registry="SetRegView 32" - program_files="" - - tempfile = "alchemy_setup_tmp.nsi" - # the following replaces strings in the nsi template - # it also does python-style % substitution - self.replace_in("installers/windows/installer_template.nsi", tempfile, { - "%%VERSION%%":version_vars, - "%%SOURCE%%":self.get_src_prefix(), - "%%INST_VARS%%":inst_vars_template % substitution_strings, - "%%INSTALL_FILES%%":self.nsi_file_commands(True), - "%%PROGRAMFILES%%":program_files, - "%%ENGAGEREGISTRY%%":engage_registry, - "%%DELETE_FILES%%":self.nsi_file_commands(False)}) - - # If we're on a build machine, sign the code using our Authenticode certificate. JC - # note that the enclosing setup exe is signed later, after the makensis makes it. - # Unlike the viewer binary, the VMP filenames are invariant with respect to version, os, etc. - for exe in ( - self.final_exe(), - "SLVersionChecker.exe", - "llplugin/dullahan_host.exe", - ): - self.sign(exe) - - # Check two paths, one for Program Files, and one for Program Files (x86). - # Yay 64bit windows. - for ProgramFiles in 'ProgramFiles', 'ProgramFiles(x86)': - NSIS_path = os.path.expandvars(r'${%s}\NSIS\makensis.exe' % ProgramFiles) - if os.path.exists(NSIS_path): - break - installer_created=False - nsis_attempts=3 - nsis_retry_wait=15 - for attempt in range(nsis_attempts): - try: - self.run_command([NSIS_path, '/V2', self.dst_path_of(tempfile)]) - except ManifestError as err: - if attempt+1 < nsis_attempts: - print("nsis failed, waiting %d seconds before retrying" % nsis_retry_wait, file=sys.stderr) - time.sleep(nsis_retry_wait) - nsis_retry_wait*=2 - else: - # NSIS worked! Done! - break - else: - print("Maximum nsis attempts exceeded; giving up", file=sys.stderr) - raise + + os.rename(os.path.join('Releases', self.app_name_oneword() + 'Setup.exe'), os.path.join('Release', installer_file)) self.sign(installer_file) self.created_path(self.dst_path_of(installer_file)) @@ -863,15 +729,6 @@ def construct(self): with self.prefix(src=self.icon_path(), dst="") : self.path("alchemy.icns") - # Copy in the updater script and helper modules - self.path(src=os.path.join(pkgdir, 'VMP'), dst="updater") - - with self.prefix(src="", dst=os.path.join("updater", "icons")): - self.path2basename(self.icon_path(), "alchemy.ico") - with self.prefix(src="vmp_icons", dst=""): - self.path("*.png") - self.path("*.gif") - with self.prefix(src_dst="cursors_mac"): self.path("*.tif")