From 52ed305d78bbad19a08a6d0c28d7a5468fdf8dca Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 3 Mar 2026 19:49:38 +0200 Subject: #5084 Convert watchdog to a simpleton --- indra/newview/llappviewer.cpp | 1 + 1 file changed, 1 insertion(+) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index bae9749772..20dba648fd 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -1240,6 +1240,7 @@ bool LLAppViewer::init() // TODO: consider moving proxy initialization here or LLCopocedureManager after proxy initialization, may be implement // some other protection to make sure we don't use network before initializng proxy + LLWatchdog::createInstance(); /*----------------------------------------------------------------------*/ // nat 2016-06-29 moved the following here from the former mainLoop(). mMainloopTimeout = new LLWatchdogTimeout("mainloop"); -- cgit v1.3 From ba92b8d503d3976124b7583e878566d0107123ed Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Fri, 6 Mar 2026 03:32:23 +0200 Subject: #5084 Fix watchdog's simpleton init It's needed for initWindow() --- indra/newview/llappviewer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 20dba648fd..4bca656108 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -961,6 +961,7 @@ bool LLAppViewer::init() // Initialize event recorder LLViewerEventRecorder::createInstance(); + LLWatchdog::createInstance(); // // Initialize the window @@ -1240,7 +1241,6 @@ bool LLAppViewer::init() // TODO: consider moving proxy initialization here or LLCopocedureManager after proxy initialization, may be implement // some other protection to make sure we don't use network before initializng proxy - LLWatchdog::createInstance(); /*----------------------------------------------------------------------*/ // nat 2016-06-29 moved the following here from the former mainLoop(). mMainloopTimeout = new LLWatchdogTimeout("mainloop"); -- cgit v1.3 From 66dc10f0e9ab4f8d209f8a04671027c608daaffd Mon Sep 17 00:00:00 2001 From: "Jonathan \"Geenz\" Goodman" Date: Tue, 7 Apr 2026 19:12:59 -0400 Subject: Release/26.1.1 (#5530) * Integrate Velopack installer and update framework * Add Velopack update support for macOS and VVM integration * Update Velopack version and dependencies * Improve Velopack packaging for macOS * #5346 Uninstall older non-velopack viewer (#5363) * #5335 Fix silent uninstall asking about registry * #5346 Uninstall older non-velopack viewer * Use runtime viewer exe name, handle Velopack URL * Velopack download failure diagnostic (#5520) * Velopack download failure diagnostic * Fix up velopack downloading updates. Handle updates internally then hand them off to velopack. (#5524) * More velopack changes. Should download updates properly now. * Don't include NSI files * Restore optional updates, refine viewer restart behavior. (#5527) * Add support for optional updates. * Don't restart the viewer after the update unless it was optional. * Setup UpdaterServiceSetting with velopack properly. * Refine the restart behavior a bit - readd the old "the viewer must update" UX. * If the update is still downloading, close should just reopen the downloading dialog. --------- Co-authored-by: Jonathan "Geenz" Goodman * Remove SLVersionChecker from the viewer with velopack. (#5528) * Remove SLVersionChecker updater integration * Ensure that the portable install has the correct version number. * Don't produce shortcuts with VPK - we do this with our post install. * Bump viewer version from 26.1.0 to 26.1.1 * Potential fix for uninstaller not being functional. * Fix for UpdaterServiceSetting being ignored. * Filter for release channel when generating shortcuts. * Add some more logging for icons on Windows builds. * More VPK logging. * Move velopack packaging in CI to the sign and package step. * Enable velopack downgrade and skip older updates * Move the version required checking into velopack's checks. * Potential fix for downgrade prompts. * Make sure our macOS flow mirrors Windows. * Make sure to use the dev version of the mac sign and package. * p#553 Only one of two uninstallers displayed * #5346 Don't force user to shutdown velopack build for NSIS uninstall * #5346 Ignore option for the uninstall dialog * #5346 Fix early exit crash * #5346 Properly reset version flag. * Add some autodetect logic on macOS. * p#564 Clear legacy links * p#553 Handle uninstall records * p#549 Permit testing release notes on a test build * p#564 Remake nsis to velopack update flow * p#564 Remake nsis to velopack update flow #2 * p#564 Fix incorrect value type * p#553 Clear velopack's own registry entry in favor of a custom one * #5346 Resolve duplicated window class name * Bump to 2.1.0 of sign and package. --------- Co-authored-by: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> --- .github/workflows/build.yaml | 60 +- .github/workflows/tag-release.yaml | 36 +- autobuild.xml | 50 + indra/cmake/CMakeLists.txt | 1 + indra/cmake/Velopack.cmake | 68 ++ indra/lib/python/indra/util/llmanifest.py | 3 +- indra/llcommon/workqueue.cpp | 18 +- indra/llcommon/workqueue.h | 6 + indra/newview/CMakeLists.txt | 15 + indra/newview/VIEWER_VERSION.txt | 2 +- indra/newview/app_settings/settings.xml | 16 +- .../installers/windows/installer_template.nsi | 16 +- indra/newview/llappviewer.cpp | 90 +- indra/newview/llappviewer.h | 12 +- indra/newview/llappviewerwin32.cpp | 33 +- indra/newview/llappviewerwin32.h | 2 - indra/newview/lllogininstance.cpp | 81 +- indra/newview/lllogininstance.h | 6 - indra/newview/llpanellogin.cpp | 16 + indra/newview/llstartup.cpp | 89 +- indra/newview/llvelopack.cpp | 1201 ++++++++++++++++++++ indra/newview/llvelopack.h | 59 + indra/newview/llversioninfo.cpp | 2 +- indra/newview/llversioninfo.h | 4 +- indra/newview/llviewernetwork.cpp | 8 + indra/newview/llviewerstats.cpp | 6 +- indra/newview/llvvmquery.cpp | 189 +++ indra/newview/llvvmquery.h | 42 + .../newview/skins/default/xui/en/notifications.xml | 18 + indra/newview/tests/lllogininstance_test.cpp | 2 - indra/newview/viewer_manifest.py | 261 ++++- indra/viewer_components/login/lllogin.cpp | 62 - 32 files changed, 2191 insertions(+), 283 deletions(-) create mode 100644 indra/cmake/Velopack.cmake create mode 100644 indra/newview/llvelopack.cpp create mode 100644 indra/newview/llvelopack.h create mode 100644 indra/newview/llvvmquery.cpp create mode 100644 indra/newview/llvvmquery.h (limited to 'indra/newview/llappviewer.cpp') diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c55605da0d..866af3dbac 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -2,6 +2,14 @@ name: Build on: workflow_dispatch: + inputs: + installer_type: + description: 'Windows installer type' + type: choice + options: + - velopack + - nsis + default: 'velopack' pull_request: push: branches: ["main", "release/*", "project/*"] @@ -53,6 +61,20 @@ jobs: relnotes: ${{ steps.which-branch.outputs.relnotes }} imagename: ${{ steps.build.outputs.imagename }} configuration: ${{ matrix.configuration }} + # Windows Velopack outputs (passed to sign-pkg-windows) + velopack_pack_id: ${{ steps.build.outputs.velopack_pack_id }} + velopack_pack_version: ${{ steps.build.outputs.velopack_pack_version }} + velopack_pack_title: ${{ steps.build.outputs.velopack_pack_title }} + velopack_main_exe: ${{ steps.build.outputs.velopack_main_exe }} + velopack_exclude: ${{ steps.build.outputs.velopack_exclude }} + velopack_icon: ${{ steps.build.outputs.velopack_icon }} + velopack_installer_base: ${{ steps.build.outputs.velopack_installer_base }} + # macOS Velopack outputs (passed to sign-pkg-mac) + velopack_mac_pack_id: ${{ steps.build.outputs.velopack_mac_pack_id }} + velopack_mac_pack_version: ${{ steps.build.outputs.velopack_mac_pack_version }} + velopack_mac_pack_title: ${{ steps.build.outputs.velopack_mac_pack_title }} + velopack_mac_main_exe: ${{ steps.build.outputs.velopack_mac_main_exe }} + velopack_mac_bundle_id: ${{ steps.build.outputs.velopack_mac_bundle_id }} env: AUTOBUILD_ADDRSIZE: 64 AUTOBUILD_BUILD_ID: ${{ github.run_id }} @@ -84,6 +106,8 @@ jobs: # Only set variants to the one configuration: don't let build.sh loop # over variants, let GitHub distribute variants over multiple hosts. variants: ${{ matrix.configuration }} + # Pass USE_VELOPACK to CMake when using Velopack installer (default) - Windows and macOS + autobuild_configure_parameters: ${{ (contains(matrix.runner, 'windows') || contains(matrix.runner, 'macos')) && (github.event.inputs.installer_type || 'velopack') == 'velopack' && '-- -DUSE_VELOPACK:BOOL=ON' || '' }} steps: - name: Checkout code uses: actions/checkout@v6 @@ -126,6 +150,17 @@ jobs: with: token: ${{ github.token }} + - name: Setup .NET for Velopack + if: (runner.os == 'Windows' || runner.os == 'macOS') && (github.event.inputs.installer_type || 'velopack') == 'velopack' + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' + + - name: Install Velopack CLI + if: (runner.os == 'Windows' || runner.os == 'macOS') && (github.event.inputs.installer_type || 'velopack') == 'velopack' + shell: bash + run: dotnet tool install -g vpk + - name: Build id: build shell: bash @@ -310,13 +345,21 @@ jobs: steps: - name: Sign and package Windows viewer if: env.AZURE_KEY_VAULT_URI && env.AZURE_CERT_NAME && env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET && env.AZURE_TENANT_ID - uses: secondlife/viewer-build-util/sign-pkg-windows@v2.0.4 + uses: secondlife/viewer-build-util/sign-pkg-windows@v2.1.0 with: vault_uri: "${{ env.AZURE_KEY_VAULT_URI }}" cert_name: "${{ env.AZURE_CERT_NAME }}" client_id: "${{ env.AZURE_CLIENT_ID }}" client_secret: "${{ env.AZURE_CLIENT_SECRET }}" tenant_id: "${{ env.AZURE_TENANT_ID }}" + installer_type: "${{ github.event.inputs.installer_type || 'velopack' }}" + velopack_pack_id: "${{ needs.build.outputs.velopack_pack_id }}" + velopack_pack_version: "${{ needs.build.outputs.velopack_pack_version }}" + velopack_pack_title: "${{ needs.build.outputs.velopack_pack_title }}" + velopack_main_exe: "${{ needs.build.outputs.velopack_main_exe }}" + velopack_exclude: "${{ needs.build.outputs.velopack_exclude }}" + velopack_icon: "${{ needs.build.outputs.velopack_icon }}" + velopack_installer_base: "${{ needs.build.outputs.velopack_installer_base }}" sign-and-package-mac: env: @@ -349,7 +392,7 @@ jobs: - name: Sign and package Mac viewer if: env.SIGNING_CERT_MACOS && env.SIGNING_CERT_MACOS_IDENTITY && env.SIGNING_CERT_MACOS_PASSWORD && steps.note-creds.outputs.note_user && steps.note-creds.outputs.note_pass && steps.note-creds.outputs.note_team - uses: secondlife/viewer-build-util/sign-pkg-mac@v2 + uses: secondlife/viewer-build-util/sign-pkg-mac@v2.1.0 with: channel: ${{ needs.build.outputs.viewer_channel }} imagename: ${{ needs.build.outputs.imagename }} @@ -359,6 +402,11 @@ jobs: note_user: ${{ steps.note-creds.outputs.note_user }} note_pass: ${{ steps.note-creds.outputs.note_pass }} note_team: ${{ steps.note-creds.outputs.note_team }} + velopack_pack_id: "${{ needs.build.outputs.velopack_mac_pack_id }}" + velopack_pack_version: "${{ needs.build.outputs.velopack_mac_pack_version }}" + velopack_pack_title: "${{ needs.build.outputs.velopack_mac_pack_title }}" + velopack_main_exe: "${{ needs.build.outputs.velopack_mac_main_exe }}" + velopack_bundle_id: "${{ needs.build.outputs.velopack_mac_bundle_id }}" post-windows-symbols: env: @@ -439,6 +487,10 @@ jobs: with: pattern: "*-metadata" + - uses: actions/download-artifact@v4 + with: + pattern: "*-releases" + - name: Rename metadata run: | cp Windows-metadata/autobuild-package.xml Windows-autobuild-package.xml @@ -464,12 +516,14 @@ jobs: generate_release_notes: true target_commitish: ${{ github.sha }} append_body: true - fail_on_unmatched_files: true + fail_on_unmatched_files: false files: | macOS-installer/*.dmg Windows-installer/*.exe *-autobuild-package.xml *-viewer_version.txt + Windows-releases/* + macOS-releases/* - name: post release URL run: | diff --git a/.github/workflows/tag-release.yaml b/.github/workflows/tag-release.yaml index 2922065f99..0f826222a0 100644 --- a/.github/workflows/tag-release.yaml +++ b/.github/workflows/tag-release.yaml @@ -21,7 +21,9 @@ on: project: description: "Project Name (used for channel name in project builds, and tag name for all builds)" default: "hippo" - # TODO - add an input for selecting another sha to build other than head of branch + tag_override: + description: "Override the tag name (optional). If the tag already exists, a numeric suffix is appended." + required: false jobs: tag-release: @@ -34,7 +36,7 @@ jobs: NIGHTLY_DATE=$(date --rfc-3339=date) echo NIGHTLY_DATE=${NIGHTLY_DATE} >> ${GITHUB_ENV} echo TAG_ID="$(echo ${{ github.sha }} | cut -c1-8)-${{ inputs.project || '${NIGHTLY_DATE}' }}" >> ${GITHUB_ENV} - - name: Update Tag + - name: Create Tag uses: actions/github-script@v8 with: # use a real access token instead of GITHUB_TOKEN default. @@ -44,9 +46,27 @@ jobs: # this token will need to be renewed anually in January github-token: ${{ secrets.LL_TAG_RELEASE_TOKEN }} script: | - github.rest.git.createRef({ - owner: context.repo.owner, - repo: context.repo.repo, - ref: "refs/tags/${{ env.VIEWER_CHANNEL }}#${{ env.TAG_ID }}", - sha: context.sha - }) + const override = `${{ inputs.tag_override }}`.trim(); + const baseTag = override || `${{ env.VIEWER_CHANNEL }}#${{ env.TAG_ID }}`; + + // Try the base tag first, then append -2, -3, etc. if it already exists + let tag = baseTag; + for (let attempt = 1; ; attempt++) { + try { + await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/tags/${tag}`, + sha: context.sha + }); + core.info(`Created tag: ${tag}`); + break; + } catch (e) { + if (e.status === 422 && attempt < 10) { + core.info(`Tag '${tag}' already exists, trying next suffix...`); + tag = `${baseTag}-${attempt + 1}`; + } else { + throw e; + } + } + } diff --git a/autobuild.xml b/autobuild.xml index 6e48cbf509..9945672bd2 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2914,6 +2914,56 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors description Voxelized Hierarchical Approximate Convex Decomposition + velopack + + platforms + + windows64 + + archive + + creds + github + hash + 91abbc360640b5b2e0a4c001a36ad411a9a42602 + hash_algorithm + sha1 + url + https://api.github.com/repos/secondlife-3p/3p-velopack/releases/assets/380583560 + + name + windows64 + + darwin64 + + archive + + creds + github + hash + 05563a79bdeb83d66a72ac1e97587dc2a8f64511 + hash_algorithm + sha1 + url + https://api.github.com/repos/secondlife-3p/3p-velopack/releases/assets/380583554 + + name + darwin64 + + + license + MIT + license_file + LICENSES/velopack.txt + copyright + Velopack Ltd. + version + 40232ef.23500976684 + name + velopack + description + Velopack C/C++ Library + package_description diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index 2ba282bdb7..c10f6ec934 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -62,6 +62,7 @@ set(cmake_SOURCE_FILES UI.cmake UnixInstall.cmake Variables.cmake + Velopack.cmake VHACD.cmake ViewerMiscLibs.cmake VisualLeakDetector.cmake diff --git a/indra/cmake/Velopack.cmake b/indra/cmake/Velopack.cmake new file mode 100644 index 0000000000..a1dbe2cbe9 --- /dev/null +++ b/indra/cmake/Velopack.cmake @@ -0,0 +1,68 @@ +# -*- cmake -*- +# Velopack installer and update framework integration +# https://velopack.io/ + +include_guard() + +# USE_VELOPACK controls whether to use Velopack for installer packaging (instead of NSIS/DMG) +option(USE_VELOPACK "Use Velopack for installer packaging" OFF) + +if (WINDOWS) + include(Prebuilt) + use_prebuilt_binary(velopack) + + add_library(ll::velopack INTERFACE IMPORTED) + + target_include_directories(ll::velopack SYSTEM INTERFACE + ${LIBS_PREBUILT_DIR}/include/velopack + ) + + target_link_libraries(ll::velopack INTERFACE + ${ARCH_PREBUILT_DIRS_RELEASE}/velopack_libc.lib + ) + + # Windows system libraries required by Velopack + target_link_libraries(ll::velopack INTERFACE + winhttp + ole32 + shell32 + shlwapi + version + userenv + ws2_32 + bcrypt + ntdll + ) + + target_compile_definitions(ll::velopack INTERFACE LL_VELOPACK=1) + +elseif (DARWIN) + include(Prebuilt) + use_prebuilt_binary(velopack) + + add_library(ll::velopack INTERFACE IMPORTED) + + target_include_directories(ll::velopack SYSTEM INTERFACE + ${LIBS_PREBUILT_DIR}/include/velopack + ) + + target_link_libraries(ll::velopack INTERFACE + ${ARCH_PREBUILT_DIRS_RELEASE}/libvelopack_libc.a + ) + + # macOS system frameworks required by Velopack (Rust static library dependencies) + target_link_libraries(ll::velopack INTERFACE + "-framework Foundation" + "-framework Security" + "-framework SystemConfiguration" + "-framework AppKit" + "-framework CoreFoundation" + "-framework CoreServices" + "-framework IOKit" + "-liconv" + "-lresolv" + ) + + target_compile_definitions(ll::velopack INTERFACE LL_VELOPACK=1) + +endif() diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py index 1bd65eb57d..0ad0b6b1a9 100755 --- a/indra/lib/python/indra/util/llmanifest.py +++ b/indra/lib/python/indra/util/llmanifest.py @@ -157,7 +157,8 @@ BASE_ARGUMENTS=[ for use by a .bat file.""", default=None), dict(name='versionfile', - description="""The name of a file containing the full version number."""), + description="""The name of a file containing the full version number.""", + default=None), ] def usage(arguments, srctree=""): diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp index 67c23358ed..fbd92d089e 100644 --- a/indra/llcommon/workqueue.cpp +++ b/indra/llcommon/workqueue.cpp @@ -38,7 +38,8 @@ LL::WorkQueueBase::WorkQueueBase(const std::string& name, bool auto_shutdown) { // Register for "LLApp" events so we can implicitly close() on viewer shutdown std::string listener_name = "WorkQueue:" + getKey(); - LLEventPumps::instance().obtain("LLApp").listen( + LLEventPumps* pump = LLEventPumps::getInstance(); + pump->obtain("LLApp").listen( listener_name, [this](const LLSD& stat) { @@ -54,14 +55,25 @@ LL::WorkQueueBase::WorkQueueBase(const std::string& name, bool auto_shutdown) // Store the listener name so we can unregister in the destructor mListenerName = listener_name; + mPumpHandle = pump->getHandle(); } } LL::WorkQueueBase::~WorkQueueBase() { - if (!mListenerName.empty() && !LLEventPumps::wasDeleted()) + if (!mListenerName.empty() && !mPumpHandle.isDead()) { - LLEventPumps::instance().obtain("LLApp").stopListening(mListenerName); + // Due to shutdown order issues, use handle, not a singleton + // and ignore fiber issue. + try + { + LLEventPumps* pump = mPumpHandle.get(); + pump->obtain("LLApp").stopListening(mListenerName); + } + catch (const boost::fibers::lock_error&) + { + // Likely mutex is down, ignore + } } } diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index 573203a5b3..69f3286c1b 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -14,6 +14,7 @@ #include "llcoros.h" #include "llexception.h" +#include "llhandle.h" #include "llinstancetracker.h" #include "llinstancetrackersubclass.h" #include "threadsafeschedule.h" @@ -22,6 +23,9 @@ #include // std::function #include +class LLEventPumps; + + namespace LL { @@ -202,6 +206,8 @@ namespace LL // Name used for the LLApp event listener (empty if not registered) std::string mListenerName; + // Due to shutdown order issues, store by handle + LLHandle mPumpHandle; }; /***************************************************************************** diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 05c71f5f5d..1d1553355e 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -43,6 +43,7 @@ include(TinyEXR) include(ThreeJS) include(Tracy) include(UI) +include(Velopack) include(ViewerMiscLibs) include(ViewerManager) include(VisualLeakDetector) @@ -654,6 +655,7 @@ set(viewer_SOURCE_FILES llurllineeditorctrl.cpp llurlwhitelist.cpp llversioninfo.cpp + llvvmquery.cpp llviewchildren.cpp llviewerassetstats.cpp llviewerassetstorage.cpp @@ -1327,6 +1329,7 @@ set(viewer_HEADER_FILES llurllineeditorctrl.h llurlwhitelist.h llversioninfo.h + llvvmquery.h llviewchildren.h llviewerassetstats.h llviewerassetstorage.h @@ -1446,6 +1449,8 @@ if (DARWIN) LIST(APPEND viewer_SOURCE_FILES llappviewermacosx-objc.h) LIST(APPEND viewer_SOURCE_FILES llfilepicker_mac.mm) LIST(APPEND viewer_HEADER_FILES llfilepicker_mac.h) + LIST(APPEND viewer_SOURCE_FILES llvelopack.cpp) + LIST(APPEND viewer_HEADER_FILES llvelopack.h) set_source_files_properties( llappviewermacosx-objc.mm @@ -1508,16 +1513,19 @@ if (WINDOWS) list(APPEND viewer_SOURCE_FILES llappviewerwin32.cpp + llvelopack.cpp llwindebug.cpp ) set_source_files_properties( llappviewerwin32.cpp + llvelopack.cpp PROPERTIES COMPILE_DEFINITIONS "${VIEWER_CHANNEL_VERSION_DEFINES}" ) list(APPEND viewer_HEADER_FILES llappviewerwin32.h + llvelopack.h llwindebug.h ) @@ -1931,6 +1939,7 @@ if (WINDOWS) "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" + "--velopack=${USE_VELOPACK}" --build=${CMAKE_CURRENT_BINARY_DIR} --buildtype=$ "--channel=${VIEWER_CHANNEL}" @@ -2054,6 +2063,10 @@ if (USE_DISCORD) target_link_libraries(${VIEWER_BINARY_NAME} ll::discord_sdk ) endif () +if (TARGET ll::velopack) + target_link_libraries(${VIEWER_BINARY_NAME} ll::velopack ) +endif () + if( TARGET ll::intel_memops ) target_link_libraries(${VIEWER_BINARY_NAME} ll::intel_memops ) endif() @@ -2251,9 +2264,11 @@ if (DARWIN) --arch=${ARCH} --artwork=${ARTWORK_DIR} "--bugsplat=${BUGSPLAT_DB}" + --bundleid=${MACOSX_BUNDLE_GUI_IDENTIFIER} "--discord=${USE_DISCORD}" "--openal=${USE_OPENAL}" "--tracy=${USE_TRACY}" + "--velopack=${USE_VELOPACK}" --build=${CMAKE_CURRENT_BINARY_DIR} --buildtype=$ "--channel=${VIEWER_CHANNEL}" diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index 2aaedf9944..124b7a2cd0 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -26.1.0 +26.1.1 diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 0671aadee5..a4a9323892 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -4248,7 +4248,17 @@ Value 0.0.0 - + PreviousInstallChecked + + Comment + Whether viewer checked previous install on the same channel for NSIS + Persist + 1 + Type + Boolean + Value + 0 + LimitDragDistance Comment @@ -13090,11 +13100,11 @@ UpdaterShowReleaseNotes Comment - Enables displaying of the Release notes in a web floater after update. + Enables displaying of the Release notes in a web floater after update. 0 - don't show, 1 - show, 2 - show even for test viewers Persist 1 Type - Boolean + S32 Value 1 diff --git a/indra/newview/installers/windows/installer_template.nsi b/indra/newview/installers/windows/installer_template.nsi index ae40e8830f..07ed0d0824 100644 --- a/indra/newview/installers/windows/installer_template.nsi +++ b/indra/newview/installers/windows/installer_template.nsi @@ -939,21 +939,7 @@ Function .onInstSuccess Call CheckWindowsServPack # Warn if not on the latest SP before asking to launch. StrCmp $SKIP_AUTORUN "true" +2; - # Assumes SetOutPath $INSTDIR - # Run INSTEXE (our updater), passing VIEWER_EXE plus the command-line - # arguments built into our shortcuts. This gives the updater a chance - # to verify that the viewer we just installed is appropriate for the - # running system -- or, if not, to download and install a different - # viewer. For instance, if a user running 32-bit Windows installs a - # 64-bit viewer, it cannot run on this system. But since the updater - # is a 32-bit executable even in the 64-bit viewer package, the - # updater can detect the problem and adapt accordingly. - # Once everything is in order, the updater will run the specified - # viewer with the specified params. - # Quote the updater executable and the viewer executable because each - # must be a distinct command-line token, but DO NOT quote the language - # string because it must decompose into separate command-line tokens. - Exec '"$INSTDIR\$INSTEXE" precheck "$INSTDIR\$VIEWER_EXE" $SHORTCUT_LANG_PARAM' + Exec '"$INSTDIR\$VIEWER_EXE" $SHORTCUT_LANG_PARAM' # FunctionEnd diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 4bca656108..46e64c2a4e 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -98,6 +98,11 @@ #include "llurlmatch.h" #include "lltextutil.h" #include "lllogininstance.h" +#include "llvvmquery.h" + +#if LL_VELOPACK +#include "llvelopack.h" +#endif #include "llprogressview.h" #include "llvocache.h" #include "lldiskcache.h" @@ -379,9 +384,6 @@ const std::string ERROR_MARKER_FILE_NAME("SecondLife.error_marker"); const std::string LOGOUT_MARKER_FILE_NAME("SecondLife.logout_marker"); static std::string gLaunchFileOnQuit; -// Used on Win32 for other apps to identify our window (eg, win_setup) -const char* const VIEWER_WINDOW_CLASSNAME = "Second Life"; - //---------------------------------------------------------------------------- // List of entries from strings.xml to always replace @@ -653,7 +655,6 @@ LLAppViewer::LLAppViewer() mPurgeCacheOnExit(false), mPurgeUserDataOnExit(false), mSecondInstance(false), - mUpdaterNotFound(false), mSavedFinalSnapshot(false), mSavePerAccountSettings(false), // don't save settings on logout unless login succeeded. mQuitRequested(false), @@ -1116,68 +1117,17 @@ bool LLAppViewer::init() gGLActive = false; -#if LL_RELEASE_FOR_DOWNLOAD - // Skip updater if this is a non-interactive instance +//#if LL_RELEASE_FOR_DOWNLOAD + // Launch VVM update check 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); -#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; - } + initVVMUpdateCheck(); } else { LL_WARNS("InitInfo") << "Skipping updater check." << LL_ENDL; } -#endif //LL_RELEASE_FOR_DOWNLOAD +//#endif //LL_RELEASE_FOR_DOWNLOAD { // Iterate over --leap command-line options. But this is a bit tricky: if @@ -1715,6 +1665,16 @@ void LLAppViewer::flushLFSIO() bool LLAppViewer::cleanup() { +#if LL_VELOPACK + // Apply any pending Velopack update before shutdown + if (velopack_is_update_pending()) + { + LL_INFOS("AppInit") << "Applying pending Velopack update on shutdown..." << LL_ENDL; + velopack_apply_pending_update(velopack_should_restart_after_update()); + } + velopack_cleanup(); +#endif + //ditch LLVOAvatarSelf instance gAgentAvatarp = NULL; @@ -3159,7 +3119,7 @@ bool LLAppViewer::initWindow() LLViewerWindow::Params window_params; window_params .title(gWindowTitle) - .name(VIEWER_WINDOW_CLASSNAME) + .name(sWindowClass) .x(gSavedSettings.getS32("WindowX")) .y(gSavedSettings.getS32("WindowY")) .width(gSavedSettings.getU32("WindowWidth")) @@ -3284,16 +3244,6 @@ bool LLAppViewer::initWindow() return true; } -bool LLAppViewer::isUpdaterMissing() -{ - return mUpdaterNotFound; -} - -bool LLAppViewer::waitForUpdater() -{ - return !gSavedSettings.getBOOL("CmdLineSkipUpdater") && !mUpdaterNotFound && !gNonInteractive; -} - void LLAppViewer::writeDebugInfo(bool isStatic) { #if LL_WINDOWS && LL_BUGSPLAT diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index e1119419af..c977757e48 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -117,9 +117,6 @@ public: bool quitRequested() { return mQuitRequested; } bool logoutRequestSent() { return mLogoutRequestSent; } bool isSecondInstance() { return mSecondInstance; } - bool isUpdaterMissing(); // In use by tests - bool waitForUpdater(); - void writeDebugInfo(bool isStatic=true); void setServerReleaseNotesURL(const std::string& url) { mServerReleaseNotesURL = url; } @@ -287,6 +284,14 @@ protected: virtual void sendOutOfDiskSpaceNotification(); +protected: + + // NSIS relies on this to detect if viewer is up. + // NSIS's method is somewhat unreliable since window + // can close long before cleanup is done. + // sendURLToOtherInstance also relies on this to detect if viewer is up. + static constexpr const char* sWindowClass = "Second Life"; + private: bool doFrame(); @@ -327,7 +332,6 @@ private: static LLAppViewer* sInstance; bool mSecondInstance; // Is this a second instance of the app? - bool mUpdaterNotFound; // True when attempt to start updater failed std::string mMarkerFileName; LLAPRFile mMarkerFile; // A file created to indicate the app is running. diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 0620b625d9..94a5f7951e 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -72,6 +72,11 @@ #include #include +// Velopack installer and update framework +#if LL_VELOPACK +#include "llvelopack.h" +#endif + // Bugsplat (http://bugsplat.com) crash reporting tool #ifdef LL_BUGSPLAT #include "BugSplat.h" @@ -220,7 +225,6 @@ LONG WINAPI catchallCrashHandler(EXCEPTION_POINTERS * /*ExceptionInfo*/) return 0; } -const std::string LLAppViewerWin32::sWindowClass = "Second Life"; /* This function is used to print to the command line a text message @@ -424,6 +428,31 @@ int APIENTRY WINMAIN(HINSTANCE hInstance, PWSTR pCmdLine, int nCmdShow) { +#if LL_VELOPACK + // Velopack MUST be initialized first - it may handle install/uninstall + // commands and exit the process before we do anything else. + if (!velopack_initialize()) + { + // Velopack handled the invocation (install/uninstall hook) + + // Drop install related settings + gDirUtilp->initAppDirs("SecondLife"); + + std::string user_settings_path = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "settings.xml"); + LLControlGroup settings("global"); + if (settings.loadFromFile(user_settings_path)) + { + // If user reinstalls or updates, we want to recheck for nsis leftovers. + if (settings.controlExists("PreviousInstallChecked")) + { + settings.setBOOL("PreviousInstallChecked", false); + } + settings.saveToFile(user_settings_path, true); + } + return 0; + } +#endif + // Call Tracy first thing to have it allocate memory // https://github.com/wolfpld/tracy/issues/196 LL_PROFILER_FRAME_END; @@ -933,7 +962,7 @@ bool LLAppViewerWin32::restoreErrorTrap() bool LLAppViewerWin32::sendURLToOtherInstance(const std::string& url) { wchar_t window_class[256]; /* Flawfinder: ignore */ // Assume max length < 255 chars. - mbstowcs(window_class, sWindowClass.c_str(), 255); + mbstowcs(window_class, sWindowClass, 255); window_class[255] = 0; // Use the class instead of the window name. HWND other_window = FindWindow(window_class, NULL); diff --git a/indra/newview/llappviewerwin32.h b/indra/newview/llappviewerwin32.h index 3fad53ec72..0741758a0c 100644 --- a/indra/newview/llappviewerwin32.h +++ b/indra/newview/llappviewerwin32.h @@ -59,8 +59,6 @@ protected: std::string generateSerialNumber(); - static const std::string sWindowClass; - private: void disableWinErrorReporting(); diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp index e9d68723d3..0358233637 100644 --- a/indra/newview/lllogininstance.cpp +++ b/indra/newview/lllogininstance.cpp @@ -277,11 +277,6 @@ void LLLoginInstance::constructAuthParams(LLPointer user_credentia mRequestData["params"] = request_params; mRequestData["options"] = requested_options; mRequestData["http_params"] = http_params; -#if LL_RELEASE_FOR_DOWNLOAD - mRequestData["wait_for_updater"] = LLAppViewer::instance()->waitForUpdater(); -#else - mRequestData["wait_for_updater"] = false; -#endif } bool LLLoginInstance::handleLoginEvent(const LLSD& event) @@ -316,13 +311,6 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event) // Login has failed. // Figure out why and respond... LLSD response = event["data"]; - LLSD updater = response["updater"]; - - // Always provide a response to the updater, if in fact the updater - // contacted us, if in fact the ping contains a 'reply' key. Most code - // paths tell it not to proceed with updating. - ResponsePtr resp(std::make_shared - (LLSDMap("update", false), updater)); std::string reason_response = response["reason"].asString(); std::string message_response = response["message"].asString(); @@ -385,26 +373,15 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event) } else if(reason_response == "update") { - // This can happen if the user clicked Login quickly, before we heard - // back from the Viewer Version Manager, but login failed because - // login.cgi is insisting on a required update. We were called with an - // event that bundles both the login.cgi 'response' and the - // synchronization event from the 'updater'. + // login.cgi rejected login and requires an update. Since Velopack + // handles updates now, the best we can do here is tell the user + // to download the update manually via the release notes URL. std::string login_version = response["message_args"]["VERSION"]; - std::string vvm_version = updater["VERSION"]; - std::string relnotes = updater["URL"]; LL_WARNS("LLLogin") << "Login failed because an update to version " << login_version << " is required." << LL_ENDL; - // vvm_version might be empty because we might not have gotten - // SLVersionChecker's LoginSync handshake. But if it IS populated, it - // should (!) be the same as the version we got from login.cgi. - if ((! vvm_version.empty()) && vvm_version != login_version) - { - LL_WARNS("LLLogin") << "VVM update version " << vvm_version - << " differs from login version " << login_version - << "; presenting VVM version to match release notes URL" - << LL_ENDL; - login_version = vvm_version; - } + + // Try to use the release notes URL from the VVM query if available, + // otherwise fall back to constructing one from the version. + std::string relnotes = LLVersionInfo::instance().getReleaseNotes(); if (relnotes.empty() || relnotes.find("://") == std::string::npos) { relnotes = LLTrans::getString("RELEASE_NOTES_BASE_URL"); @@ -420,32 +397,11 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event) args["VERSION"] = login_version; args["URL"] = relnotes; - if (updater.isUndefined()) - { - // If the updater failed to shake hands, better advise the user to - // download the update him/herself. - LLNotificationsUtil::add( - "RequiredUpdate", - args, - updater, - boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2)); - } - else - { - // If we've heard from the updater that an update is required, - // then display the prompt that assures the user we'll take care - // of it. This is the one case in which we bind 'resp': - // instead of destroying our Response object (and thus sending a - // negative reply to the updater) as soon as we exit this - // function, bind our shared_ptr so it gets passed into - // syncWithUpdater. That ensures that the response is delayed - // until the user has responded to the notification. - LLNotificationsUtil::add( - "PauseForUpdate", - args, - updater, - boost::bind(&LLLoginInstance::syncWithUpdater, this, resp, _1, _2)); - } + LLNotificationsUtil::add( + "RequiredUpdate", + args, + LLSD(), + boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2)); } else if(reason_response == "mfa_challenge") { @@ -479,19 +435,6 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event) } } -void LLLoginInstance::syncWithUpdater(ResponsePtr resp, const LLSD& notification, const LLSD& response) -{ - LL_INFOS("LLLogin") << "LLLoginInstance::syncWithUpdater" << LL_ENDL; - // 'resp' points to an instance of LLEventAPI::Response that will be - // destroyed as soon as we return and the notification response functor is - // unregistered. Modify it so that it tells the updater to go ahead and - // perform the update. Naturally, if we allowed the user a choice as to - // whether to proceed or not, this assignment would reflect the user's - // selection. - (*resp)["update"] = true; - attemptComplete(); -} - void LLLoginInstance::handleLoginDisallowed(const LLSD& notification, const LLSD& response) { attemptComplete(); diff --git a/indra/newview/lllogininstance.h b/indra/newview/lllogininstance.h index 54ce51720f..551ad92d33 100644 --- a/indra/newview/lllogininstance.h +++ b/indra/newview/lllogininstance.h @@ -28,8 +28,6 @@ #define LL_LLLOGININSTANCE_H #include "lleventdispatcher.h" -#include "lleventapi.h" -#include // std::shared_ptr #include "llsecapi.h" class LLLogin; class LLEventStream; @@ -72,10 +70,7 @@ public: void saveMFAHash(LLSD const& response); private: - typedef std::shared_ptr ResponsePtr; void constructAuthParams(LLPointer user_credentials); - void updateApp(bool mandatory, const std::string& message); - bool updateDialogCallback(const LLSD& notification, const LLSD& response); bool handleLoginEvent(const LLSD& event); void handleLoginFailure(const LLSD& event); @@ -83,7 +78,6 @@ private: void handleDisconnect(const LLSD& event); void handleIndeterminate(const LLSD& event); void handleLoginDisallowed(const LLSD& notification, const LLSD& response); - void syncWithUpdater(ResponsePtr resp, const LLSD& notification, const LLSD& response); bool handleTOSResponse(bool v, const std::string& key); void showMFAChallange(const std::string& message); diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index 868e02f28b..0cfb3ec49a 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -37,6 +37,9 @@ #include "llappviewer.h" #include "llbutton.h" +#if LL_VELOPACK +#include "llvelopack.h" +#endif #include "llcheckboxctrl.h" #include "llcommandhandler.h" // for secondlife:///app/login/ #include "llcombobox.h" @@ -939,6 +942,19 @@ void LLPanelLogin::handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent ev // static void LLPanelLogin::onClickConnect(bool commit_fields) { +#if LL_VELOPACK + // In theory, you should never be able to get here. + // If there's a required update, try as you might you're not supposed to actually close the downloading update dialog. + // But just in case... + if (velopack_is_required_update_in_progress()) + { + LLSD args; + args["VERSION"] = velopack_get_required_update_version(); + LLNotificationsUtil::add("DownloadingUpdate", args); + return; + } +#endif + if (sInstance && sInstance->mCallback && !sInstance->mAlertNotif) { if (commit_fields) diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 018c23963c..9bdfbaedc8 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -29,6 +29,11 @@ #include "llappviewer.h" #include "llstartup.h" +#if LL_VELOPACK && LL_WINDOWS +#include "llvelopack.h" +#include +#endif + #if LL_WINDOWS # include // _spawnl() #else @@ -266,6 +271,7 @@ std::unique_ptr LLStartUp::sPhases(new LLViewerStats::P void login_show(); void login_callback(S32 option, void* userdata); +void uninstall_nsis_if_required(); void show_release_notes_if_required(); void show_first_run_dialog(); bool first_run_dialog_callback(const LLSD& notification, const LLSD& response); @@ -921,6 +927,7 @@ bool idle_startup() LL_DEBUGS("AppInit") << "PeekMessage processed" << LL_ENDL; #endif do_startup_frame(); + uninstall_nsis_if_required(); timeout.reset(); return false; } @@ -2605,6 +2612,67 @@ void release_notes_coro(const std::string url) LLWeb::loadURLInternal(url); } +/** +* Check if this is a fresh velopack install and +* if uninstallation of old viewer is needed. +*/ +void uninstall_nsis_if_required() +{ +#if LL_VELOPACK && LL_WINDOWS + bool checked_for_legacy_install = gSavedSettings.getBOOL("PreviousInstallChecked"); + if (checked_for_legacy_install) + { + return; + } + gSavedSettings.setBOOL("PreviousInstallChecked", true); + + LL_INFOS() << "Looking for previous NSIS installs" << LL_ENDL; + + S32 found_major = 0; + S32 found_minor = 0; + S32 found_patch = 0; + U64 found_build = 0; + + if (!get_nsis_version(found_major, found_minor, found_patch, found_build)) + { + return; + } + + LLVersionInfo* ver_inst = LLVersionInfo::getInstance(); + + if (found_major > ver_inst->getMajor()) + { + LL_INFOS() << "Found installed nsis version that is newer" << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL; + return; + } + + if (found_major == ver_inst->getMajor() + && found_minor > ver_inst->getMinor()) + { + LL_INFOS() << "Found installed nsis version that is newer" << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL; + return; + } + + if (found_major == ver_inst->getMajor() + && found_minor == ver_inst->getMinor() + && found_patch > ver_inst->getPatch()) + { + LL_INFOS() << "Found installed nsis version that is newer" << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL; + return; + } + + // Assume that nsis is going to be something like x.x.x, while velopack is x.x.(x+1), + // so there is no point to check build. + LL_INFOS() << "Found NSIS install " << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL; + + clear_nsis_links(); + + LLSD args; + args["VERSION"] = llformat("%d.%d.%d", found_major, found_minor, found_patch); + LLNotificationsUtil::add("FoundLegacyNsisInstallation", args); +#endif +} + void validate_release_notes_coro(const std::string url) { LLVersionInfo& versionInfo(LLVersionInfo::instance()); @@ -2638,15 +2706,24 @@ void show_release_notes_if_required() // below. If viewer release notes stop working, might be because that // LLEventMailDrop got moved out of LLVersionInfo and hasn't yet been // instantiated. - if (!release_notes_shown && (LLVersionInfo::instance().getChannelAndVersion() != gLastRunVersion) - && LLVersionInfo::instance().getViewerMaturity() != LLVersionInfo::TEST_VIEWER // don't show Release Notes for the test builds - && gSavedSettings.getBOOL("UpdaterShowReleaseNotes") - && !gSavedSettings.getBOOL("FirstLoginThisInstall")) + if (release_notes_shown + || LLVersionInfo::instance().getChannelAndVersion() == gLastRunVersion + || gSavedSettings.getBOOL("FirstLoginThisInstall")) // New users don't need to see release notes + { + return; + } + S32 mode = gSavedSettings.getS32("UpdaterShowReleaseNotes"); + if (mode == 0) + { + return; + } + if (mode == 2 // Show even for test builds + || LLVersionInfo::instance().getViewerMaturity() != LLVersionInfo::TEST_VIEWER) // don't show Release Notes for the test builds + { #if LL_RELEASE_FOR_DOWNLOAD - if (!gSavedSettings.getBOOL("CmdLineSkipUpdater") - && !LLAppViewer::instance()->isUpdaterMissing()) + if (!gSavedSettings.getBOOL("CmdLineSkipUpdater")) { // Instantiate a "relnotes" listener which assumes any arriving event // is the release notes URL string. Since "relnotes" is an diff --git a/indra/newview/llvelopack.cpp b/indra/newview/llvelopack.cpp new file mode 100644 index 0000000000..28e989c4ba --- /dev/null +++ b/indra/newview/llvelopack.cpp @@ -0,0 +1,1201 @@ +/** + * @file llvelopack.cpp + * @brief Velopack installer and update framework integration + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * 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 + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#if LL_VELOPACK + +#include "llviewerprecompiledheaders.h" +#include "llvelopack.h" +#include "llstring.h" +#include "llcorehttputil.h" +#include "llversioninfo.h" + +#include +#include +#include +#include "llnotificationsutil.h" +#include "llviewercontrol.h" +#include "llappviewer.h" +#include "llcoros.h" + +#include "Velopack.h" + +#if LL_WINDOWS +#include +#include +#include +#include +#include +#include + +#pragma comment(lib, "shlwapi.lib") +#pragma comment(lib, "ole32.lib") +#pragma comment(lib, "shell32.lib") +#endif // LL_WINDOWS + +// Common state +static std::string sUpdateUrl; +static std::function sProgressCallback; +static vpkc_update_manager_t* sUpdateManager = nullptr; +static vpkc_update_info_t* sPendingUpdate = nullptr; // Downloaded, ready to apply +static vpkc_update_info_t* sPendingCheckInfo = nullptr; // Checked, awaiting user response +static vpkc_update_source_t* sUpdateSource = nullptr; +static LLNotificationPtr sDownloadingNotification; +static bool sRestartAfterUpdate = false; +static bool sIsRequired = false; // Is the pending check a required update? +static std::string sReleaseNotesUrl; +static std::string sTargetVersion; // Velopack's actual target version + +// Forward declarations +static void show_required_update_prompt(); +static void show_downloading_notification(const std::string& version); +static void ensure_update_manager(bool allow_downgrade); +static void velopack_download_pending_update(); +static std::unordered_map sAssetUrlMap; // basename -> original absolute URL + +// +// Custom update source helpers +// + +static std::string extract_basename(const std::string& url) +{ + // Strip query params / fragment + std::string path = url; + auto qpos = path.find('?'); + if (qpos != std::string::npos) path = path.substr(0, qpos); + auto fpos = path.find('#'); + if (fpos != std::string::npos) path = path.substr(0, fpos); + + auto spos = path.rfind('/'); + if (spos != std::string::npos && spos + 1 < path.size()) + return path.substr(spos + 1); + return path; +} + +static void rewrite_asset_urls(boost::json::value& jv) +{ + if (jv.is_object()) + { + auto& obj = jv.as_object(); + auto it = obj.find("FileName"); + if (it != obj.end() && it->value().is_string()) + { + std::string filename(it->value().as_string()); + if (filename.find("://") != std::string::npos) + { + std::string basename = extract_basename(filename); + sAssetUrlMap[basename] = filename; + it->value() = basename; + LL_DEBUGS("Velopack") << "Rewrote FileName: " << basename << LL_ENDL; + } + } + for (auto& kv : obj) + { + rewrite_asset_urls(kv.value()); + } + } + else if (jv.is_array()) + { + for (auto& elem : jv.as_array()) + { + rewrite_asset_urls(elem); + } + } +} + +static std::string rewrite_release_feed(const std::string& json_str) +{ + boost::json::value jv = boost::json::parse(json_str); + rewrite_asset_urls(jv); + return boost::json::serialize(jv); +} + +static std::string download_url_raw(const std::string& url) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + auto httpAdapter = std::make_shared("VelopackSource", httpPolicy); + auto httpRequest = std::make_shared(); + auto httpOpts = std::make_shared(); + httpOpts->setFollowRedirects(true); + + LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts); + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + if (!status) + { + LL_WARNS("Velopack") << "HTTP request failed for " << url << ": " << status.toString() << LL_ENDL; + return {}; + } + + const LLSD::Binary& rawBody = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary(); + return std::string(rawBody.begin(), rawBody.end()); +} + +static bool download_url_to_file(const std::string& url, const std::string& local_path) +{ + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + auto httpAdapter = std::make_shared("VelopackDownload", httpPolicy); + auto httpRequest = std::make_shared(); + auto httpOpts = std::make_shared(); + httpOpts->setFollowRedirects(true); + httpOpts->setTransferTimeout(1200); + + LLSD result = httpAdapter->getRawAndSuspend(httpRequest, url, httpOpts); + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + if (!status) + { + LL_WARNS("Velopack") << "Download failed for " << url << ": " << status.toString() << LL_ENDL; + return false; + } + + const LLSD::Binary& rawBody = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS_RAW].asBinary(); + llofstream outFile(local_path, std::ios::binary | std::ios::trunc); + if (!outFile.is_open()) + { + LL_WARNS("Velopack") << "Failed to open file for writing: " << local_path << LL_ENDL; + return false; + } + outFile.write(reinterpret_cast(rawBody.data()), rawBody.size()); + outFile.close(); + return true; +} + +// +// Custom source callbacks +// + +static char* custom_get_release_feed(void* user_data, const char* releases_name) +{ + std::string base = sUpdateUrl; + if (!base.empty() && base.back() == '/') + base.pop_back(); + std::string url = base + "/" + releases_name; + LL_INFOS("Velopack") << "Fetching release feed: " << url << LL_ENDL; + + std::string json_str = download_url_raw(url); + if (json_str.empty()) + { + return nullptr; + } + + try + { + std::string rewritten = rewrite_release_feed(json_str); + char* result = static_cast(malloc(rewritten.size() + 1)); + if (result) + { + memcpy(result, rewritten.c_str(), rewritten.size() + 1); + } + return result; + } + catch (const std::exception& e) + { + LL_WARNS("Velopack") << "Failed to parse/rewrite release feed: " << e.what() << LL_ENDL; + // Return original unmodified feed as fallback + char* result = static_cast(malloc(json_str.size() + 1)); + if (result) + { + memcpy(result, json_str.c_str(), json_str.size() + 1); + } + return result; + } +} + +static void custom_free_release_feed(void* user_data, char* feed) +{ + free(feed); +} + +static std::string sPreDownloadedAssetPath; + +static bool custom_download_asset(void* user_data, + const vpkc_asset_t* asset, + const char* local_path, + size_t progress_callback_id) +{ + // 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. + if (sPreDownloadedAssetPath.empty()) + { + LL_WARNS("Velopack") << "No pre-downloaded asset available" << LL_ENDL; + return false; + } + + std::string filename = asset->FileName ? asset->FileName : ""; + LL_INFOS("Velopack") << "Download asset callback: filename=" << filename + << " local_path=" << local_path + << " size=" << asset->Size << LL_ENDL; + vpkc_source_report_progress(progress_callback_id, 0); + + std::ifstream src(sPreDownloadedAssetPath, std::ios::binary); + llofstream dst(local_path, std::ios::binary | std::ios::trunc); + if (!src.is_open() || !dst.is_open()) + { + LL_WARNS("Velopack") << "Failed to open files for copy" << LL_ENDL; + return false; + } + + dst << src.rdbuf(); + dst.close(); + src.close(); + + vpkc_source_report_progress(progress_callback_id, 100); + LL_INFOS("Velopack") << "Asset copy complete" << LL_ENDL; + return true; +} + +// +// Platform-specific helpers and hooks +// + +#if LL_WINDOWS + +static const wchar_t* PROTOCOL_SECONDLIFE = L"secondlife"; +static const wchar_t* PROTOCOL_GRID_INFO = L"x-grid-location-info"; +static std::wstring get_viewer_exe_name() +{ + return ll_convert(gDirUtilp->getExecutableFilename()); +} + +static std::wstring get_install_dir() +{ + wchar_t path[MAX_PATH]; + GetModuleFileNameW(NULL, path, MAX_PATH); + PathRemoveFileSpecW(path); + return path; +} + +static std::wstring get_app_name() +{ + // Match viewer_manifest.py app_name() logic: release channel uses "Viewer" + // suffix instead of "Release" for display purposes (shortcuts, uninstall, etc.) + std::wstring channel = LL_TO_WSTRING(LL_VIEWER_CHANNEL); + std::wstring release_suffix = L" Release"; + if (channel.size() >= release_suffix.size() && + channel.compare(channel.size() - release_suffix.size(), release_suffix.size(), release_suffix) == 0) + { + channel.replace(channel.size() - release_suffix.size(), release_suffix.size(), L" Viewer"); + } + return channel; +} + +static std::wstring get_app_name_oneword() +{ + std::wstring name = get_app_name(); + name.erase(std::remove(name.begin(), name.end(), L' '), name.end()); + return name; +} + +static std::wstring get_start_menu_path() +{ + wchar_t path[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_PROGRAMS, NULL, 0, path))) + { + return path; + } + return L""; +} + +static std::wstring get_desktop_path() +{ + wchar_t path[MAX_PATH]; + if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_DESKTOPDIRECTORY, NULL, 0, path))) + { + return path; + } + return L""; +} + +static bool create_shortcut(const std::wstring& shortcut_path, + const std::wstring& target_path, + const std::wstring& arguments, + const std::wstring& description, + const std::wstring& icon_path) +{ + HRESULT hr = CoInitialize(NULL); + if (FAILED(hr)) return false; + + IShellLinkW* shell_link = nullptr; + hr = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, + IID_IShellLinkW, (void**)&shell_link); + if (SUCCEEDED(hr)) + { + shell_link->SetPath(target_path.c_str()); + shell_link->SetArguments(arguments.c_str()); + shell_link->SetDescription(description.c_str()); + shell_link->SetIconLocation(icon_path.c_str(), 0); + + wchar_t work_dir[MAX_PATH]; + wcscpy_s(work_dir, target_path.c_str()); + PathRemoveFileSpecW(work_dir); + shell_link->SetWorkingDirectory(work_dir); + + IPersistFile* persist_file = nullptr; + hr = shell_link->QueryInterface(IID_IPersistFile, (void**)&persist_file); + if (SUCCEEDED(hr)) + { + hr = persist_file->Save(shortcut_path.c_str(), TRUE); + persist_file->Release(); + } + shell_link->Release(); + } + + CoUninitialize(); + return SUCCEEDED(hr); +} + +static void register_protocol_handler(const std::wstring& protocol, + const std::wstring& description, + const std::wstring& exe_path) +{ + std::wstring key_path = L"SOFTWARE\\Classes\\" + protocol; + HKEY hkey; + + if (RegCreateKeyExW(HKEY_CURRENT_USER, key_path.c_str(), 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hkey, NULL) == ERROR_SUCCESS) + { + RegSetValueExW(hkey, NULL, 0, REG_SZ, + (BYTE*)description.c_str(), (DWORD)((description.size() + 1) * sizeof(wchar_t))); + RegSetValueExW(hkey, L"URL Protocol", 0, REG_SZ, (BYTE*)L"", sizeof(wchar_t)); + RegCloseKey(hkey); + } + + std::wstring icon_key_path = key_path + L"\\DefaultIcon"; + if (RegCreateKeyExW(HKEY_CURRENT_USER, icon_key_path.c_str(), 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hkey, NULL) == ERROR_SUCCESS) + { + std::wstring icon_value = L"\"" + exe_path + L"\""; + RegSetValueExW(hkey, NULL, 0, REG_SZ, + (BYTE*)icon_value.c_str(), (DWORD)((icon_value.size() + 1) * sizeof(wchar_t))); + RegCloseKey(hkey); + } + + std::wstring cmd_key_path = key_path + L"\\shell\\open\\command"; + if (RegCreateKeyExW(HKEY_CURRENT_USER, cmd_key_path.c_str(), 0, NULL, + REG_OPTION_NON_VOLATILE, KEY_WRITE, NULL, &hkey, NULL) == ERROR_SUCCESS) + { + std::wstring cmd_value = L"\"" + exe_path + L"\" -url \"%1\""; + RegSetValueExW(hkey, NULL, 0, REG_EXPAND_SZ, + (BYTE*)cmd_value.c_str(), (DWORD)((cmd_value.size() + 1) * sizeof(wchar_t))); + RegCloseKey(hkey); + } +} + +void clear_nsis_links() +{ + wchar_t path[MAX_PATH]; + + // 1. The 'start' shortcuts set by nsis would be global, like app shortcut: + // C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Second Life Viewer\Second Life Viewer.lnk + // But it isn't just one link, it's a whole directory that needs to be removed. + if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_COMMON_PROGRAMS, NULL, 0, path))) + { + std::wstring start_menu_path = path; + std::wstring folder_path = start_menu_path + L"\\" + get_app_name(); + + std::error_code ec; + std::filesystem::path dir(folder_path); + if (std::filesystem::exists(dir, ec)) + { + std::filesystem::remove_all(dir, ec); + if (ec) + { + LL_WARNS("Velopack") << "Failed to remove NSIS start menu directory: " + << ll_convert_wide_to_string(folder_path) << LL_ENDL; + } + } + } + + // 2. Desktop link, also a global one. + // C:\Users\Public\Desktop + if (SUCCEEDED(SHGetFolderPathW(NULL, CSIDL_COMMON_DESKTOPDIRECTORY, NULL, 0, path))) + { + std::wstring desktop_path = path; + std::wstring shortcut_path = desktop_path + L"\\" + get_app_name() + L".lnk"; + if (!DeleteFileW(shortcut_path.c_str())) + { + DWORD error = GetLastError(); + if (error != ERROR_FILE_NOT_FOUND) + { + LL_WARNS("Velopack") << "Failed to delete NSIS desktop shortcut: " + << ll_convert_wide_to_string(shortcut_path) + << " (error: " << error << ")" << LL_ENDL; + } + } + } +} + +static void parse_version(const wchar_t* version_str, int& major, int& minor, int& patch, uint64_t& build) +{ + major = minor = patch = 0; + build = 0; + if (!version_str) return; + // Use swscanf for wide strings + swscanf(version_str, L"%d.%d.%d.%llu", &major, &minor, &patch, &build); +} + +bool get_nsis_version( + int& nsis_major, + int& nsis_minor, + int& nsis_patch, + uint64_t& nsis_build) +{ + // Test for presence of NSIS viewer registration, then + // attempt to read uninstall info + std::wstring app_name_oneword = get_app_name_oneword(); + std::wstring uninstall_key_path = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + app_name_oneword; + HKEY hkey; + LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, uninstall_key_path.c_str(), 0, KEY_READ, &hkey); + if (result != ERROR_SUCCESS) + { + return false; + } + + // Read DisplayVersion + wchar_t version_buf[64] = { 0 }; + DWORD version_buf_size = sizeof(version_buf); + DWORD type = 0; + LONG ver_rv = RegGetValueW(hkey, nullptr, L"DisplayVersion", RRF_RT_REG_SZ, &type, version_buf, &version_buf_size); + + if (ver_rv != ERROR_SUCCESS) + { + RegCloseKey(hkey); + return false; + } + + parse_version(version_buf, nsis_major, nsis_minor, nsis_patch, nsis_build); + + // Make sure it actually exists and not a dead entry. + wchar_t path_buffer[MAX_PATH] = { 0 }; + DWORD path_buf_size = sizeof(path_buffer); + LONG rv = RegGetValueW(hkey, nullptr, L"UninstallString", RRF_RT_REG_SZ, &type, path_buffer, &path_buf_size); + RegCloseKey(hkey); + if (rv != ERROR_SUCCESS) + { + return false; + } + size_t len = wcslen(path_buffer); + if (len > 0) + { + if (path_buffer[0] == L'\"') + { + // Likely to contain leading " + memmove(path_buffer, path_buffer + 1, len * sizeof(wchar_t)); + } + wchar_t* pos = wcsstr(path_buffer, L"uninst.exe"); + if (pos) + { + // Likely to contain trailing " + pos[wcslen(L"uninst.exe")] = L'\0'; + } + } + std::error_code ec; + std::filesystem::path path(path_buffer); + if (!std::filesystem::exists(path, ec)) + { + return false; + } + + // Todo: check codesigning? + + return true; +} + +static void unregister_protocol_handler(const std::wstring& protocol) +{ + std::wstring key_path = L"SOFTWARE\\Classes\\" + protocol; + 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) +{ + std::wstring app_name_oneword = get_app_name_oneword(); + // Clears velopack's recently created 'uninstall' registry entry. + // We are going to use a custom one. + // Note that velopack doesn't know about our custom entry. + std::wstring key_path = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + app_name_oneword; + RegDeleteTreeW(HKEY_CURRENT_USER, key_path.c_str()); + // Use a unique key name to avoid conflicts with any existing NSIS-based uninstall info, + // which can cause only one of the two entries to show up in the Add/Remove Programs list. + // The UI will show DisplayName, so the key name itself is not important to be user-friendly. + key_path = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Vlpk" + app_name_oneword; + 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 exe_path = install_dir + L"\\" + get_viewer_exe_name(); + // Update.exe lives one level above the current\ directory where the viewer exe runs + std::filesystem::path update_exe = std::filesystem::path(install_dir).parent_path() / L"Update.exe"; + std::wstring uninstall_cmd = L"\"" + update_exe.wstring() + L"\" --uninstall"; + + RegSetValueExW(hkey, L"DisplayName", 0, REG_SZ, + (BYTE*)app_name.c_str(), (DWORD)((app_name.size() + 1) * sizeof(wchar_t))); + RegSetValueExW(hkey, L"DisplayVersion", 0, REG_SZ, + (BYTE*)version.c_str(), (DWORD)((version.size() + 1) * sizeof(wchar_t))); + RegSetValueExW(hkey, L"Publisher", 0, REG_SZ, + (BYTE*)L"Linden Research, Inc.", 44); + RegSetValueExW(hkey, L"UninstallString", 0, REG_SZ, + (BYTE*)uninstall_cmd.c_str(), (DWORD)((uninstall_cmd.size() + 1) * sizeof(wchar_t))); + RegSetValueExW(hkey, L"DisplayIcon", 0, REG_SZ, + (BYTE*)exe_path.c_str(), (DWORD)((exe_path.size() + 1) * sizeof(wchar_t))); + + std::wstring link_url = L"https://support.secondlife.com/contact-support/"; + RegSetValueExW(hkey, L"HelpLink", 0, REG_SZ, + (BYTE*)link_url.c_str(), (DWORD)((link_url.size() + 1) * sizeof(wchar_t))); + + link_url = L"https://secondlife.com/whatis/"; + RegSetValueExW(hkey, L"URLInfoAbout", 0, REG_SZ, + (BYTE*)link_url.c_str(), (DWORD)((link_url.size() + 1) * sizeof(wchar_t))); + + link_url = L"https://secondlife.com/support/downloads/"; + RegSetValueExW(hkey, L"URLUpdateInfo", 0, REG_SZ, + (BYTE*)link_url.c_str(), (DWORD)((link_url.size() + 1) * sizeof(wchar_t))); + + DWORD no_modify = 1; + RegSetValueExW(hkey, L"NoModify", 0, REG_DWORD, (BYTE*)&no_modify, sizeof(DWORD)); + RegSetValueExW(hkey, L"NoRepair", 0, REG_DWORD, (BYTE*)&no_modify, sizeof(DWORD)); + + // Format YYYYMMDD + wchar_t dateStr[9]; + time_t t = time(NULL); + struct tm tm; + localtime_s(&tm, &t); + wcsftime(dateStr, 9, L"%Y%m%d", &tm); + RegSetValueExW(hkey, L"InstallDate", 0, REG_SZ, (BYTE*)dateStr, (DWORD)((wcslen(dateStr) + 1) * sizeof(wchar_t))); // Let Windows fill in the install date + + // 800 MB, inaccurate, but for a rough idea. + // We can check folder size here, but it would take time and + // information is of low importance. + DWORD estimated_size = 800000; + RegSetValueExW(hkey, L"EstimatedSize", 0, REG_DWORD, (BYTE*)&estimated_size, sizeof(DWORD)); + + RegCloseKey(hkey); + } +} + +static void unregister_uninstall_info() +{ + std::wstring app_name_oneword = get_app_name_oneword(); + std::wstring key_path = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Vlpk" + app_name_oneword; + RegDeleteTreeW(HKEY_CURRENT_USER, key_path.c_str()); +} + +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; + std::wstring desktop_path = get_desktop_path(); + + CreateDirectoryW(start_menu_dir.c_str(), NULL); + + create_shortcut(start_menu_dir + L"\\" + app_name + L".lnk", + exe_path, L"", app_name, exe_path); + + create_shortcut(desktop_path + L"\\" + app_name + L".lnk", + exe_path, L"", app_name, exe_path); +} + +static void remove_shortcuts(const std::wstring& app_name) +{ + std::wstring start_menu_dir = get_start_menu_path() + L"\\" + app_name; + std::wstring desktop_path = get_desktop_path(); + + DeleteFileW((start_menu_dir + L"\\" + app_name + L".lnk").c_str()); + RemoveDirectoryW(start_menu_dir.c_str()); + DeleteFileW((desktop_path + L"\\" + app_name + L".lnk").c_str()); +} + +static void on_first_run(void* p_user_data, const char* app_version) +{ + // Velopack first executes 'after install' hook, then writes registry, + // then executes 'on first run' hook. + // As we need to clear velopack's 'uninstall' registry entry and use + // our own, clean it here instead of on_after_install. + + std::wstring install_dir = get_install_dir(); + std::wstring app_name = get_app_name(); + + int len = MultiByteToWideChar(CP_UTF8, 0, app_version, -1, NULL, 0); + std::wstring version(len, 0); + MultiByteToWideChar(CP_UTF8, 0, app_version, -1, &version[0], len); + + register_uninstall_info(install_dir, app_name, version); +} + +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(); + + register_protocol_handler(PROTOCOL_SECONDLIFE, L"URL:Second Life", exe_path); + register_protocol_handler(PROTOCOL_GRID_INFO, L"URL:Second Life", exe_path); + 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(); + + unregister_protocol_handler(PROTOCOL_SECONDLIFE); + unregister_protocol_handler(PROTOCOL_GRID_INFO); + unregister_uninstall_info(); + remove_shortcuts(app_name); +} + +static void on_log_message(void* user_data, const char* level, const char* message) +{ + OutputDebugStringA("[Velopack] "); + OutputDebugStringA(level); + OutputDebugStringA(": "); + OutputDebugStringA(message); + OutputDebugStringA("\n"); +} + +#elif LL_DARWIN + +// macOS-specific hooks +// TODO: Implement protocol handler registration via Launch Services +// TODO: Implement app bundle management + +static void on_first_run(void* user_data, const char* app_version) +{ +} + +static void on_after_install(void* user_data, const char* app_version) +{ + // macOS handles protocol registration via Info.plist CFBundleURLTypes + // No additional registration needed at runtime + LL_INFOS("Velopack") << "macOS post-install hook called for version: " << app_version << LL_ENDL; +} + +static void on_before_uninstall(void* user_data, const char* app_version) +{ + LL_INFOS("Velopack") << "macOS pre-uninstall hook called for version: " << app_version << LL_ENDL; +} + +static void on_log_message(void* user_data, const char* level, const char* message) +{ + LL_INFOS("Velopack") << "[" << level << "] " << message << LL_ENDL; +} + +#endif // LL_WINDOWS / LL_DARWIN + +// +// Common progress callback +// + +static void on_progress(void* user_data, size_t progress) +{ + if (sProgressCallback) + { + sProgressCallback(static_cast(progress)); + } +} + +static void on_vpk_log(void* p_user_data, + const char* psz_level, + const char* psz_message) +{ + LL_DEBUGS("Velopack") << ll_safe_string(psz_message) << LL_ENDL; +} + +// +// Version comparison helper +// + +// Compare running version against a VVM version string "major.minor.patch.build". +// Returns -1 if running < vvm, 0 if equal, 1 if running > vvm. +static int compare_running_version(const std::string& vvm_version) +{ + S32 major = 0, minor = 0, patch = 0; + U64 build = 0; + sscanf(vvm_version.c_str(), "%d.%d.%d.%llu", &major, &minor, &patch, &build); + + const LLVersionInfo& vi = LLVersionInfo::instance(); + S32 cur_major = vi.getMajor(); + S32 cur_minor = vi.getMinor(); + S32 cur_patch = vi.getPatch(); + U64 cur_build = vi.getBuild(); + + if (cur_major != major) return cur_major < major ? -1 : 1; + if (cur_minor != minor) return cur_minor < minor ? -1 : 1; + if (cur_patch != patch) return cur_patch < patch ? -1 : 1; + if (cur_build != build) return cur_build < build ? -1 : 1; + return 0; +} + +// +// Update manager lifecycle +// + +static void ensure_update_manager(bool allow_downgrade) +{ + if (sUpdateManager) + return; + + vpkc_update_options_t options = {}; + options.AllowVersionDowngrade = allow_downgrade; + options.ExplicitChannel = nullptr; + + if (!sUpdateSource) + { + sUpdateSource = vpkc_new_source_custom_callback( + custom_get_release_feed, + custom_free_release_feed, + custom_download_asset, + nullptr); + } + + vpkc_locator_config_t* locator_ptr = nullptr; + +#if LL_DARWIN + // Try auto-detection first (works when the app bundle was packaged by vpk + // and has UpdateMac + sq.version already present) + if (!vpkc_new_update_manager_with_source(sUpdateSource, &options, nullptr, &sUpdateManager)) + { + char err[512]; + vpkc_get_last_error(err, sizeof(err)); + LL_INFOS("Velopack") << "Auto-detect failed (" << ll_safe_string(err) + << "), falling back to explicit locator" << LL_ENDL; + + // 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 = {}; + + // The executable lives at /Contents/MacOS/ + // The app bundle root is two levels up from the executable directory. + std::string exe_dir = gDirUtilp->getExecutableDir(); + std::string bundle_root = exe_dir + "/../.."; + char resolved[PATH_MAX]; + if (realpath(bundle_root.c_str(), resolved)) + { + bundle_root = resolved; + } + + // Construct a version string in Velopack SemVer format: major.minor.patch-build + const LLVersionInfo& vi = LLVersionInfo::instance(); + std::string current_version = llformat("%d.%d.%d-%llu", + vi.getMajor(), vi.getMinor(), vi.getPatch(), vi.getBuild()); + + // Create a minimal sq.version manifest so Velopack knows our version. + // Proper vpk-packaged builds have this in the bundle already. + std::string manifest_path = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, "sq.version"); + { + std::string app_name = LLVersionInfo::instance().getChannel(); + std::string pack_id = app_name; + pack_id.erase(std::remove(pack_id.begin(), pack_id.end(), ' '), pack_id.end()); + + std::string nuspec = "\n" + "\n" + " \n" + " " + pack_id + "\n" + " " + current_version + "\n" + " " + app_name + "\n" + " \n" + "\n"; + + llofstream manifest_file(manifest_path, std::ios::trunc); + if (manifest_file.is_open()) + { + manifest_file << nuspec; + manifest_file.close(); + } + } + + std::string packages_dir = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, "velopack-packages"); + LLFile::mkdir(packages_dir); + + locator.RootAppDir = const_cast(bundle_root.c_str()); + locator.CurrentBinaryDir = const_cast(exe_dir.c_str()); + locator.ManifestPath = const_cast(manifest_path.c_str()); + locator.PackagesDir = const_cast(packages_dir.c_str()); + locator.UpdateExePath = nullptr; + locator.IsPortable = false; + + locator_ptr = &locator; + + LL_INFOS("Velopack") << "Explicit locator: RootAppDir=" << bundle_root + << " CurrentBinaryDir=" << exe_dir + << " Version=" << current_version << LL_ENDL; + + if (!vpkc_new_update_manager_with_source(sUpdateSource, &options, locator_ptr, &sUpdateManager)) + { + char err2[512]; + vpkc_get_last_error(err2, sizeof(err2)); + LL_WARNS("Velopack") << "Failed to create update manager: " << ll_safe_string(err2) << LL_ENDL; + } + } + return; +#endif + + // Windows: Velopack auto-detection works because the viewer is installed + // by Velopack's Setup.exe which creates the proper install structure. + if (!vpkc_new_update_manager_with_source(sUpdateSource, &options, nullptr, &sUpdateManager)) + { + char err[512]; + vpkc_get_last_error(err, sizeof(err)); + LL_WARNS("Velopack") << "Failed to create update manager: " << ll_safe_string(err) << LL_ENDL; + } +} + +// +// Public API - Cross-platform +// + +bool velopack_initialize() +{ + vpkc_set_logger(on_log_message, nullptr); + vpkc_app_set_auto_apply_on_startup(false); + +#if LL_WINDOWS || LL_DARWIN + vpkc_app_set_hook_first_run(on_first_run); + vpkc_app_set_hook_after_install(on_after_install); + vpkc_app_set_hook_before_uninstall(on_before_uninstall); +#endif + + vpkc_app_run(nullptr); + return true; +} + +// Downloads the update that was found during the check phase. +// Operates on sPendingCheckInfo which was set by velopack_check_for_updates. +static void velopack_download_pending_update() +{ + if (!sUpdateManager || !sPendingCheckInfo) + { + LL_WARNS("Velopack") << "No pending check info to download" << LL_ENDL; + return; + } + + LL_DEBUGS("Velopack") << "Setting up detailed logging"; + vpkc_set_logger(on_vpk_log, nullptr); + LL_CONT << LL_ENDL; + LL_INFOS("Velopack") << "Downloading update..." << LL_ENDL; + + // Pre-download the nupkg at the coroutine level where getRawAndSuspend works. + // The download callback inside the Rust FFI cannot use coroutine HTTP. + std::string asset_filename = sPendingCheckInfo->TargetFullRelease->FileName + ? sPendingCheckInfo->TargetFullRelease->FileName : ""; + std::string asset_url; + auto url_it = sAssetUrlMap.find(asset_filename); + if (url_it != sAssetUrlMap.end()) + { + asset_url = url_it->second; + } + else + { + std::string base = sUpdateUrl; + if (!base.empty() && base.back() == '/') + base.pop_back(); + asset_url = base + "/" + asset_filename; + } + + sPreDownloadedAssetPath = gDirUtilp->getExpandedFilename(LL_PATH_TEMP, asset_filename); + LL_INFOS("Velopack") << "Pre-downloading " << asset_url + << " to " << sPreDownloadedAssetPath << LL_ENDL; + + if (!download_url_to_file(asset_url, sPreDownloadedAssetPath)) + { + LL_WARNS("Velopack") << "Failed to pre-download update asset" << LL_ENDL; + sPreDownloadedAssetPath.clear(); + return; + } + + LL_INFOS("Velopack") << "Pre-download complete, handing to Velopack" << LL_ENDL; + if (vpkc_download_updates(sUpdateManager, sPendingCheckInfo, on_progress, nullptr)) + { + if (sPendingUpdate) + { + vpkc_free_update_info(sPendingUpdate); + } + sPendingUpdate = sPendingCheckInfo; + sPendingCheckInfo = nullptr; // Ownership transferred + LL_INFOS("Velopack") << "Update downloaded and pending" << LL_ENDL; + } + else + { + char descr[512]; + vpkc_get_last_error(descr, sizeof(descr)); + LL_WARNS("Velopack") << "Failed to download update: " << ll_safe_string((const char*)descr) << LL_ENDL; + } +} + +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 + show_downloading_notification(sTargetVersion); + } +} + +static void show_downloading_notification(const std::string& version) +{ + LLSD args; + args["VERSION"] = version; + sDownloadingNotification = LLNotificationsUtil::add("DownloadingUpdate", args, LLSD(), on_downloading_closed); +} + +static void dismiss_downloading_notification() +{ + if (sDownloadingNotification) + { + LLNotificationsUtil::cancel(sDownloadingNotification); + sDownloadingNotification = nullptr; + } +} + +static void on_required_update_response(const LLSD& notification, const LLSD& response) +{ + LL_INFOS("Velopack") << "Required update acknowledged, starting download" << LL_ENDL; + show_downloading_notification(sTargetVersion); + LLCoros::instance().launch("VelopackRequiredUpdate", []() + { + velopack_download_pending_update(); + dismiss_downloading_notification(); + if (velopack_is_update_pending()) + { + LL_INFOS("Velopack") << "Required update downloaded, quitting to apply" << LL_ENDL; + velopack_request_restart_after_update(); + LLAppViewer::instance()->requestQuit(); + } + }); +} + +static void on_optional_update_response(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotificationsUtil::getSelectedOption(notification, response); + if (option == 0) // "Install" + { + LL_INFOS("Velopack") << "User accepted optional update, starting download" << LL_ENDL; + show_downloading_notification(sTargetVersion); + LLCoros::instance().launch("VelopackOptionalUpdate", []() + { + velopack_download_pending_update(); + dismiss_downloading_notification(); + if (velopack_is_update_pending()) + { + LL_INFOS("Velopack") << "Optional update downloaded, quitting to apply" << LL_ENDL; + velopack_request_restart_after_update(); + LLAppViewer::instance()->requestQuit(); + } + }); + } + else + { + LL_INFOS("Velopack") << "User declined optional update (option=" << option << ")" << LL_ENDL; + // Free the check info since user declined + if (sPendingCheckInfo) + { + vpkc_free_update_info(sPendingCheckInfo); + sPendingCheckInfo = nullptr; + } + } +} + +static void show_required_update_prompt() +{ + LLSD args; + args["VERSION"] = sTargetVersion; + args["URL"] = sReleaseNotesUrl; + LLNotificationsUtil::add("PauseForUpdate", args, LLSD(), on_required_update_response); +} + +void velopack_check_for_updates(const std::string& required_version, const std::string& relnotes_url) +{ + if (sUpdateUrl.empty()) + { + LL_DEBUGS("Velopack") << "No update URL set, skipping update check" << LL_ENDL; + return; + } + + // Allow downgrades only for rollbacks: VVM requires a version that's + // 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 + ensure_update_manager(allow_downgrade); + if (!sUpdateManager) + return; + + // 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); + + if (result != UPDATE_AVAILABLE || !update_info) + { + LL_INFOS("Velopack") << "No update available from feed (result=" << result << ")" << LL_ENDL; + return; + } + + // Extract the actual target version from Velopack's feed + std::string target_version = update_info->TargetFullRelease->Version + ? update_info->TargetFullRelease->Version : ""; + LL_INFOS("Velopack") << "Update available: " << target_version + << " (required_version=" << required_version << ")" << LL_ENDL; + + // Store state for the prompt/download phase + sReleaseNotesUrl = relnotes_url; + sTargetVersion = target_version; + if (sPendingCheckInfo) + { + vpkc_free_update_info(sPendingCheckInfo); + } + 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 + sIsRequired = is_required; + + if (is_required) + { + LL_INFOS("Velopack") << "Required update (running below " << required_version + << "), prompting user for " << target_version << LL_ENDL; + show_required_update_prompt(); + return; + } + + // Optional update — check user preference + U32 updater_setting = gSavedSettings.getU32("UpdaterServiceSetting"); + + if (updater_setting == 3) + { + // "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(); + return; + } + + // Default / value 1: "Ask me when an optional update is ready to install" + LL_INFOS("Velopack") << "Optional update available (" << target_version << "), prompting user" << LL_ENDL; + LLSD args; + args["VERSION"] = target_version; + args["URL"] = relnotes_url; + LLNotificationsUtil::add("PromptOptionalUpdate", args, LLSD(), on_optional_update_response); +} + +std::string velopack_get_current_version() +{ + if (!sUpdateManager) + { + return ""; + } + + char version[64]; + size_t len = vpkc_get_current_version(sUpdateManager, version, sizeof(version)); + if (len > 0) + { + return std::string(version, len); + } + return ""; +} + +bool velopack_is_update_pending() +{ + return sPendingUpdate != nullptr; +} + +bool velopack_is_required_update_in_progress() +{ + return sIsRequired && sPendingCheckInfo != nullptr; +} + +std::string velopack_get_required_update_version() +{ + return sTargetVersion; +} + +bool velopack_should_restart_after_update() +{ + return sRestartAfterUpdate; +} + +void velopack_request_restart_after_update() +{ + sRestartAfterUpdate = true; +} + +void velopack_apply_pending_update(bool restart) +{ + if (!sUpdateManager || !sPendingUpdate || !sPendingUpdate->TargetFullRelease) + { + LL_WARNS("Velopack") << "Cannot apply update: no pending update or manager" << LL_ENDL; + return; + } + + LL_INFOS("Velopack") << "Applying pending update (restart=" << restart << ")" << LL_ENDL; + vpkc_wait_exit_then_apply_updates(sUpdateManager, + sPendingUpdate->TargetFullRelease, + false, + restart, + nullptr, 0); +} + +void velopack_cleanup() +{ + if (sUpdateManager) + { + vpkc_free_update_manager(sUpdateManager); + sUpdateManager = nullptr; + } + if (sUpdateSource) + { + vpkc_free_source(sUpdateSource); + sUpdateSource = nullptr; + } + if (sPendingUpdate) + { + vpkc_free_update_info(sPendingUpdate); + sPendingUpdate = nullptr; + } + if (sPendingCheckInfo) + { + vpkc_free_update_info(sPendingCheckInfo); + sPendingCheckInfo = nullptr; + } + sAssetUrlMap.clear(); +} + +void velopack_set_update_url(const std::string& url) +{ + sUpdateUrl = url; + LL_INFOS("Velopack") << "Update URL set to: " << url << LL_ENDL; +} + +void velopack_set_progress_callback(std::function callback) +{ + sProgressCallback = callback; +} + +#endif // LL_VELOPACK diff --git a/indra/newview/llvelopack.h b/indra/newview/llvelopack.h new file mode 100644 index 0000000000..d04d0db7dc --- /dev/null +++ b/indra/newview/llvelopack.h @@ -0,0 +1,59 @@ +/** + * @file llvelopack.h + * @brief Velopack installer and update framework integration + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * 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 + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVELOPACK_H +#define LL_LLVELOPACK_H + +#if LL_VELOPACK + +#include +#include + +bool velopack_initialize(); +void velopack_check_for_updates(const std::string& required_version, const std::string& relnotes_url); +std::string velopack_get_current_version(); +bool velopack_is_update_pending(); +bool velopack_is_required_update_in_progress(); +std::string velopack_get_required_update_version(); +bool velopack_should_restart_after_update(); +void velopack_request_restart_after_update(); +void velopack_apply_pending_update(bool restart = true); +void velopack_set_update_url(const std::string& url); +void velopack_set_progress_callback(std::function callback); +void velopack_cleanup(); + +#if LL_WINDOWS +void clear_nsis_links(); +bool get_nsis_version( + int& nsis_major, + int& nsis_minor, + int& nsis_patch, + uint64_t& nsis_build); +#endif + +#endif // LL_VELOPACK +#endif +// EOF diff --git a/indra/newview/llversioninfo.cpp b/indra/newview/llversioninfo.cpp index 4e8320b72a..178f10257d 100644 --- a/indra/newview/llversioninfo.cpp +++ b/indra/newview/llversioninfo.cpp @@ -54,7 +54,7 @@ LLVersionInfo::LLVersionInfo(): mWorkingChannelName(LL_TO_STRING(LL_VIEWER_CHANNEL)), build_configuration(LLBUILD_CONFIG), // set in indra/cmake/BuildVersion.cmake // instantiate an LLEventMailDrop with canonical name to listen for news - // from SLVersionChecker + // from the Viewer Version Manager mPump{new LLEventMailDrop("relnotes")}, // immediately listen on mPump, store arriving URL into mReleaseNotes mStore{new LLStoreListener(*mPump, mReleaseNotes)} diff --git a/indra/newview/llversioninfo.h b/indra/newview/llversioninfo.h index 237b37a084..a2b93597e6 100644 --- a/indra/newview/llversioninfo.h +++ b/indra/newview/llversioninfo.h @@ -112,8 +112,8 @@ private: std::string mReleaseNotes; // Store unique_ptrs to the next couple things so we don't have to explain // to every consumer of this header file all the details of each. - // mPump is the LLEventMailDrop on which we listen for SLVersionChecker to - // post the release-notes URL from the Viewer Version Manager. + // mPump is the LLEventMailDrop on which we listen for the + // release-notes URL from the Viewer Version Manager. std::unique_ptr mPump; // mStore is an adapter that stores the release-notes URL in mReleaseNotes. std::unique_ptr> mStore; diff --git a/indra/newview/llviewernetwork.cpp b/indra/newview/llviewernetwork.cpp index 890580ddff..6cb3aee20c 100644 --- a/indra/newview/llviewernetwork.cpp +++ b/indra/newview/llviewernetwork.cpp @@ -575,6 +575,7 @@ std::string LLGridManager::getGridLoginID() std::string LLGridManager::getUpdateServiceURL() { + auto env_update_service = LLStringUtil::getoptenv("SL_UPDATE_SERVICE"); std::string update_url_base = gSavedSettings.getString("CmdLineUpdateService");; if ( !update_url_base.empty() ) { @@ -582,6 +583,13 @@ std::string LLGridManager::getUpdateServiceURL() << "Update URL base overridden from command line: " << update_url_base << LL_ENDL; } + else if (env_update_service && env_update_service->find("http") != std::string::npos) + { + update_url_base = *env_update_service; + LL_INFOS("UpdaterService", "GridManager") + << "Update URL base overridden from SL_UPDATE_SERVICE environment variable: " << update_url_base + << LL_ENDL; + } else if ( mGridList[mGrid].has(GRID_UPDATE_SERVICE_URL) ) { update_url_base = mGridList[mGrid][GRID_UPDATE_SERVICE_URL].asString(); diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index d39d466205..5193514fe8 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -783,7 +783,11 @@ void send_viewer_stats(bool include_preferences) fail["failed_resends"] = (S32) gMessageSystem->mFailedResendPackets; fail["off_circuit"] = (S32) gMessageSystem->mOffCircuitPackets; fail["invalid"] = (S32) gMessageSystem->mInvalidOnCircuitPackets; - fail["missing_updater"] = (S32) LLAppViewer::instance()->isUpdaterMissing(); +#if LL_VELOPACK + fail["missing_updater"] = false; +#else + fail["missing_updater"] = true; +#endif LLSD &inventory = body["inventory"]; inventory["usable"] = gInventory.isInventoryUsable(); diff --git a/indra/newview/llvvmquery.cpp b/indra/newview/llvvmquery.cpp new file mode 100644 index 0000000000..12dcc1d04d --- /dev/null +++ b/indra/newview/llvvmquery.cpp @@ -0,0 +1,189 @@ +/** + * @file llvvmquery.cpp + * @brief Query the Viewer Version Manager (VVM) for update information + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * 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 + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llvvmquery.h" + +#include "llcorehttputil.h" +#include "llcoros.h" +#include "llevents.h" +#include "llviewernetwork.h" +#include "llversioninfo.h" +#include "llviewercontrol.h" +#include "llhasheduniqueid.h" +#include "lluri.h" +#include "llsys.h" + +#if LL_VELOPACK +#include "llvelopack.h" +#endif + +namespace +{ + std::string get_platform_string() + { +#if LL_WINDOWS + return "win64"; +#elif LL_DARWIN + return "mac64"; +#elif LL_LINUX + return "lnx64"; +#else + return "unknown"; +#endif + } + + std::string get_platform_version() + { + return LLOSInfo::instance().getOSVersionString(); + } + + std::string get_machine_id() + { + unsigned char id[MD5HEX_STR_SIZE]; + if (llHashedUniqueID(id)) + { + return std::string(reinterpret_cast(id)); + } + return "unknown"; + } + + void query_vvm_coro() + { + // Get base URL from grid manager + std::string base_url = LLGridManager::getInstance()->getUpdateServiceURL(); + + // We use this for dev testing when working with VVM and working on the updater. Not advisable to uncomment it. + //std::string base_url = "https://update.qa.secondlife.io/update"; + + if (base_url.empty()) + { + LL_WARNS("VVM") << "No update service URL configured" << LL_ENDL; + return; + } + + // Gather parameters for VVM query + std::string channel = LLVersionInfo::instance().getChannel(); + + // We use this for dev testing when working with VVM and working on the updater. Not advisable to uncomment it. + // std::string channel = "QA Target for Velopack"; + + std::string version = LLVersionInfo::instance().getVersion(); + std::string platform = get_platform_string(); + std::string platform_version = get_platform_version(); + std::string test_ok = gSavedSettings.getBOOL("UpdaterWillingToTest") ? "testok" : "testno"; + std::string machine_id = get_machine_id(); + + // Build URL: {base}/v1.2/{channel}/{version}/{platform}/{platform_version}/{testok}/{uuid} + std::string url = base_url + "/v1.2/" + + LLURI::escape(channel) + "/" + + LLURI::escape(version) + "/" + + platform + "/" + + LLURI::escape(platform_version) + "/" + + test_ok + "/" + + machine_id; + + LL_INFOS("VVM") << "Querying VVM: " << url << LL_ENDL; + + // Make HTTP GET request + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter = + std::make_shared("VVMQuery", httpPolicy); + LLCore::HttpRequest::ptr_t request = std::make_shared(); + + LLSD result = adapter->getAndSuspend(request, url); + + // Check HTTP status + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + if (!status) + { + if (status.getType() == 404) + { + LL_INFOS("VVM") << "Unmanaged channel, no updates available" << LL_ENDL; + return; + } + LL_WARNS("VVM") << "VVM query failed: " << status.toString() << LL_ENDL; + return; + } + + // Read whether this update is required or optional + bool update_required = result["required"].asBoolean(); + std::string relnotes = result["more_info"].asString(); + + // Extract update URL for current platform + LLSD platforms = result["platforms"]; + if (platforms.has(platform)) + { + std::string update_url = platforms[platform]["url"].asString(); +#if LL_VELOPACK + std::string velopack_url = platforms[platform]["velopack_url"].asString(); + U32 updater_service = gSavedSettings.getU32("UpdaterServiceSetting"); + std::string required_version = update_required ? result["version"].asString() : ""; + // Skip network check if no required version AND user only wants mandatory updates + if (!velopack_url.empty() && (update_required || updater_service != 0)) + { + LL_INFOS("VVM") << "Velopack feed URL: " << velopack_url + << " required_version: " << required_version << LL_ENDL; + velopack_set_update_url(velopack_url); + + LLCoros::instance().launch("VelopackUpdateCheck", + [required_version, relnotes]() + { + velopack_check_for_updates(required_version, relnotes); + }); + } + else if (!velopack_url.empty()) + { + LL_INFOS("VVM") << "Optional update skipped (UpdaterServiceSetting=0)" << LL_ENDL; + } + else +#endif + if (!update_url.empty()) + { + LL_INFOS("VVM") << "Update available at: " << update_url << LL_ENDL; + } + } + else + { + LL_INFOS("VVM") << "No update available for platform: " << platform << LL_ENDL; + } + + // Post release notes URL to the relnotes event pump + if (!relnotes.empty()) + { + LL_INFOS("VVM") << "Release notes URL: " << relnotes << LL_ENDL; + LLEventPumps::instance().obtain("relnotes").post(relnotes); + } + } +} + +void initVVMUpdateCheck() +{ + LL_INFOS("VVM") << "Initializing VVM update check" << LL_ENDL; + LLCoros::instance().launch("VVMUpdateCheck", &query_vvm_coro); +} diff --git a/indra/newview/llvvmquery.h b/indra/newview/llvvmquery.h new file mode 100644 index 0000000000..977d82af64 --- /dev/null +++ b/indra/newview/llvvmquery.h @@ -0,0 +1,42 @@ +/** + * @file llvvmquery.h + * @brief Query the Viewer Version Manager (VVM) for update information + * + * $LicenseInfo:firstyear=2025&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2025, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * 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 + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +#ifndef LL_LLVVMQUERY_H +#define LL_LLVVMQUERY_H + +/** + * Initialize the VVM update check. + * + * This launches a coroutine that queries the Viewer Version Manager (VVM) + * to check for available updates. If an update is available, it configures + * Velopack with the update URL and initiates the update check/download. + * + * The release notes URL from the VVM response is posted to the "relnotes" + * event pump for display. + */ +void initVVMUpdateCheck(); + +#endif // LL_LLVVMQUERY_H diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 93ee27b196..fcdea66500 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -200,6 +200,16 @@ No tutorial is currently available. yestext="OK"/> + + [APP_NAME] found an installation of an older version [VERSION]. To uninstall the older version, please follow [https://community.secondlife.com/knowledgebase/english/how-to-uninstall-and-reinstall-second-life-r524 this manual]. + + + + +Downloading update [VERSION]... +The viewer will restart once the download is complete. + + 3 and self.args['version'][3]: + pack_version += '-' + self.args['version'][3] + pack_title = self.app_name() # Display name with spaces + pack_dir = self.get_dst_prefix() + main_exe = self.final_exe() + installer_base = self.installer_base_name() + exclude_pattern = r'.*\.pdb|.*\.map|.*\.bat|.*\.exp|.*\.lib|.*\.nsi|.*\.tar\.xz|secondlife-bin\..*|.*_Setup\.exe|.*-Setup\.exe' + + # Channel-specific icon for the Velopack installer. + # CMake copies icons/{channel}/secondlife.ico to res/ll_icon.ico at configure time. + # Try the CMake-generated copy first, fall back to the source icon. + icon_path = os.path.join(self.get_src_prefix(), 'res', 'll_icon.ico') + if not os.path.exists(icon_path): + icon_path = os.path.join(self.get_src_prefix(), self.icon_path(), 'secondlife.ico') + + # In CI, defer Velopack packaging to the sign step where Azure credentials + # are available. Emit metadata as GitHub outputs so the sign step can run + # vpk pack with --signTemplate, producing a package with signed executables. + if os.getenv('GITHUB_ACTIONS'): + # Copy the icon into pack_dir so it's included in the Windows-app artifact + icon_filename = '' + if os.path.exists(icon_path): + icon_filename = os.path.basename(icon_path) + icon_dest = os.path.join(pack_dir, icon_filename) + shutil.copy2(icon_path, icon_dest) + print("Copied icon %s to %s" % (icon_path, icon_dest)) + else: + print("WARNING: Icon not found at %s" % icon_path) + + # Emit metadata for the sign step + self.set_github_output('velopack_pack_id', pack_id) + self.set_github_output('velopack_pack_version', pack_version) + self.set_github_output('velopack_pack_title', pack_title) + self.set_github_output('velopack_main_exe', main_exe) + self.set_github_output('velopack_icon', icon_filename) + self.set_github_output('velopack_installer_base', installer_base) + self.set_github_output('velopack_exclude', exclude_pattern) + # Set package_file so llmanifest's touched.bat logic doesn't crash + self.package_file = installer_base + '_Setup.exe' + print("CI mode: Velopack packaging deferred to sign step") + return + + # Local builds: run vpk pack directly (unsigned) + vpk_args = [ + 'vpk', 'pack', + '--packId', pack_id, + '--packVersion', pack_version, + '--packDir', pack_dir, + '--mainExe', main_exe, + '--packTitle', pack_title, + '--exclude', exclude_pattern, + # Suppress Velopack's built-in shortcut creation; we create our own + # shortcuts in llvelopack.cpp on_after_install hook instead. + '--shortcuts', '', + ] + + # Add icon — CMake copies the channel-appropriate secondlife.ico to res/ll_icon.ico + if os.path.exists(icon_path): + print("Using icon: %s" % icon_path) + vpk_args.extend(['--icon', icon_path]) + else: + print("WARNING: Icon not found at %s — Setup.exe will have no icon" % icon_path) + + print("Running Velopack packaging: %s" % ' '.join(vpk_args)) + + # Run vpk command + import subprocess + result = subprocess.run(vpk_args, cwd=os.path.dirname(pack_dir), capture_output=True, text=True) + if result.stdout: + print("vpk stdout: %s" % result.stdout) + if result.stderr: + print("vpk stderr: %s" % result.stderr) + if result.returncode != 0: + raise ManifestError("Velopack packaging failed with code %d" % result.returncode) + + # Velopack outputs to a Releases directory + releases_dir = os.path.join(os.path.dirname(pack_dir), 'Releases') + + # Move the setup exe INTO pack_dir so it's included in the Windows-app artifact + # IMPORTANT: Use hyphen format (-Setup.exe) to avoid the *_Setup.exe exclusion pattern + # in viewer_app output (line ~538). The underscore pattern excludes NSIS installers + # which are rebuilt during signing, but Velopack installers are created here. + # Velopack creates: {packId}-win-Setup.exe + velopack_setup = os.path.join(releases_dir, '%s-win-Setup.exe' % pack_id) + self.package_file = installer_base + '_Setup.exe' + our_setup = os.path.join(pack_dir, self.package_file) + if os.path.exists(velopack_setup): + shutil.move(velopack_setup, our_setup) + print("Moved %s to %s" % (velopack_setup, our_setup)) + + # Rename the portable zip to include the version number + # Velopack creates: {packId}-win-Portable.zip + velopack_portable = os.path.join(releases_dir, '%s-win-Portable.zip' % pack_id) + if os.path.exists(velopack_portable): + our_portable = os.path.join(releases_dir, installer_base + '_Portable.zip') + shutil.move(velopack_portable, our_portable) + print("Moved %s to %s" % (velopack_portable, our_portable)) + + # Output the Releases directory path for artifact upload (contains nupkg, RELEASES for updates) + self.set_github_output('velopack_releases', releases_dir) + + def nsis_package_finish(self): + """Package the viewer using NSIS installer (legacy)""" # a standard map of strings for replacing in the templates substitution_strings = { 'version' : '.'.join(self.args['version']), @@ -781,7 +889,7 @@ class Windows_x86_64_Manifest(ViewerManifest): substitution_strings['installer_file'] = installer_file version_vars = """ - !define INSTEXE "SLVersionChecker.exe" + !define INSTEXE "%(final_exe)s" !define VERSION "%(version_short)s" !define VERSION_LONG "%(version)s" !define VERSION_DASHES "%(version_dashes)s" @@ -967,15 +1075,6 @@ class Darwin_x86_64_Manifest(ViewerManifest): with self.prefix(src=self.icon_path(), dst="") : self.path("secondlife.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(), "secondlife.ico") - with self.prefix(src="vmp_icons", dst=""): - self.path("*.png") - self.path("*.gif") - with self.prefix(src_dst="cursors_mac"): self.path("*.tif") @@ -1127,6 +1226,123 @@ class Darwin_x86_64_Manifest(ViewerManifest): arcname=self.app_name() + ".app") self.set_github_output_path('viewer_app', tarpath) + # Generate Velopack update packages if enabled + # This creates the nupkg and RELEASES files needed for auto-updates + # Distribution is still via DMG, but updates use Velopack + if self.args.get('velopack', 'OFF') == 'ON': + self.velopack_package_finish() + + def velopack_package_finish(self): + """Generate Velopack update packages for macOS. + + This creates the nupkg and releases.json files needed for auto-updates. + Distribution is still via DMG - Velopack only handles the update infrastructure. + """ + # packId determines install identification - same as Windows for consistency + pack_id = self.app_name_oneword() # "SecondLife", "SecondLifeBeta", etc. + # Velopack requires SemVer2. Use major.minor.patch-buildnumber so that + # Velopack can distinguish builds and order them correctly. + pack_version = '.'.join(self.args['version'][:3]) + if len(self.args['version']) > 3 and self.args['version'][3]: + pack_version += '-' + self.args['version'][3] + pack_title = self.app_name() # Display name with spaces + + # The .app bundle path (e.g., "/path/to/Second Life Release.app") + app_bundle = self.get_dst_prefix() + # Bundle ID from args (e.g., "com.secondlife.viewer") + bundle_id = self.args.get('bundleid', 'com.secondlife.indra.viewer') + + # Icon path for macOS + icon_path = os.path.join(self.get_src_prefix(), self.icon_path(), 'secondlife.icns') + + # The main executable inside Contents/MacOS/ is named after the channel + main_exe = self.channel() + + # In CI, defer Velopack packaging to the sign step where code signing + # credentials are available. Emit metadata as GitHub outputs so the + # sign step can run vpk pack after signing the app bundle. + if os.getenv('GITHUB_ACTIONS'): + self.set_github_output('velopack_mac_pack_id', pack_id) + self.set_github_output('velopack_mac_pack_version', pack_version) + self.set_github_output('velopack_mac_pack_title', pack_title) + self.set_github_output('velopack_mac_main_exe', main_exe) + self.set_github_output('velopack_mac_bundle_id', bundle_id) + print("CI mode: macOS Velopack packaging deferred to sign step") + return + + # Local builds: run vpk pack directly (unsigned) + + # Parent directory containing the .app bundle - this is where we run vpk from + # and where the Releases directory will be created + work_dir = os.path.dirname(app_bundle) + + # Output directory for releases - clean it first to avoid version conflicts + releases_dir = os.path.join(work_dir, 'Releases') + if os.path.exists(releases_dir): + print("Cleaning existing Releases directory: %s" % releases_dir) + shutil.rmtree(releases_dir) + + # Build vpk command for macOS + # See: https://docs.velopack.io/reference/cli/content/vpk-osx + vpk_args = [ + 'vpk', 'pack', + '--packId', pack_id, + '--packVersion', pack_version, + '--packDir', app_bundle, + '--packTitle', pack_title, + '--mainExe', main_exe, # Executable name inside Contents/MacOS/ + '--bundleId', bundle_id, + '--outputDir', releases_dir, + '--noInst', # Don't generate .pkg installer - we use DMG for distribution + '--verbose', # Show detailed output + ] + + # Add icon if exists + if os.path.exists(icon_path): + vpk_args.extend(['--icon', icon_path]) + + print("Running Velopack packaging for macOS:") + print(" Command: %s" % ' '.join(vpk_args)) + print(" Working directory: %s" % work_dir) + print(" App bundle: %s" % app_bundle) + print(" Main executable: %s" % main_exe) + + # Run vpk command + result = subprocess.run(vpk_args, cwd=work_dir, capture_output=True, text=True) + + # Always print output for debugging + if result.stdout: + print("vpk stdout:\n%s" % result.stdout) + if result.stderr: + print("vpk stderr:\n%s" % result.stderr) + + if result.returncode != 0: + raise ManifestError("Velopack packaging failed with code %d" % result.returncode) + + # Verify the Releases directory was created and contains expected files + if not os.path.exists(releases_dir): + raise ManifestError("Velopack releases directory not found: %s" % releases_dir) + + # List what was created + releases_contents = os.listdir(releases_dir) + print("Velopack releases directory contents: %s" % releases_contents) + + # Verify we have the expected files (nupkg and releases JSON) + nupkg_files = [f for f in releases_contents if f.endswith('.nupkg')] + json_files = [f for f in releases_contents if f.endswith('.json')] + + if not nupkg_files: + raise ManifestError("No .nupkg files found in releases directory") + if not json_files: + raise ManifestError("No releases JSON files found in releases directory") + + print("Generated %d nupkg file(s): %s" % (len(nupkg_files), nupkg_files)) + print("Generated %d JSON file(s): %s" % (len(json_files), json_files)) + + # Output the Releases directory path for artifact upload + self.set_github_output('velopack_releases', releases_dir) + print("Velopack releases directory: %s" % releases_dir) + class LinuxManifest(ViewerManifest): build_data_json_platform = 'lnx' @@ -1324,6 +1540,7 @@ if __name__ == "__main__": dict(name='discord', description="""Indication discord social sdk libraries are needed""", default='OFF'), dict(name='openal', description="""Indication openal libraries are needed""", default='OFF'), dict(name='tracy', description="""Indication tracy profiler is enabled""", default='OFF'), + dict(name='velopack', description="""Use Velopack installer instead of NSIS""", default='OFF'), ] try: main(extra=extra_arguments) diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp index 37b70964c3..144f807852 100644 --- a/indra/viewer_components/login/lllogin.cpp +++ b/indra/viewer_components/login/lllogin.cpp @@ -34,7 +34,6 @@ #include "llcoros.h" #include "llevents.h" -#include "lleventfilter.h" #include "lleventcoro.h" #include "llexception.h" #include "stringize.h" @@ -133,16 +132,6 @@ void LLLogin::Impl::connect(const std::string& uri, const LLSD& login_params) LL_DEBUGS("LLLogin") << " connected with uri '" << uri << "', login_params " << login_params << LL_ENDL; } -namespace -{ -// Instantiate this rendezvous point at namespace scope so it's already -// present no matter how early the updater might post to it. -// Use an LLEventMailDrop, which has future-like semantics: regardless of the -// relative order in which post() or listen() are called, it delivers each -// post() event to its listener(s) until one of them consumes that event. -static LLEventMailDrop sSyncPoint("LoginSync"); -} - void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params) { LLSD printable_params = hidePasswd(login_params); @@ -225,58 +214,7 @@ void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params) } else { - // Synchronize here with the updater. We synchronize here rather - // than in the fail.login handler, which actually examines the - // response from login.cgi, because here we are definitely in a - // coroutine and can definitely use suspendUntilBlah(). Whoever's - // listening for fail.login might not be. - - // If the reason for login failure is that we must install a - // required update, we definitely want to pass control to the - // updater to manage that for us. We'll handle any other login - // failure ourselves, as usual. We figure that no matter where you - // are in the world, or what kind of network you're on, we can - // reasonably expect the Viewer Version Manager to respond more or - // less as quickly as login.cgi. This synchronization is only - // intended to smooth out minor races between the two services. - // But what if the updater crashes? Use a timeout so that - // eventually we'll tire of waiting for it and carry on as usual. - // Given the above, it can be a fairly short timeout, at least - // from a human point of view. - - // Since sSyncPoint is an LLEventMailDrop, we DEFINITELY want to - // consume the posted event. - LLCoros::OverrideConsuming oc(true); LLSD responses(mAuthResponse["responses"]); - LLSD updater; - - if (printable_params["wait_for_updater"].asBoolean()) - { - std::string reason_response = responses["data"]["reason"].asString(); - // Timeout should produce the isUndefined() object passed here. - if (reason_response == "update") - { - LL_INFOS("LLLogin") << "Login failure, waiting for sync from updater" << LL_ENDL; - updater = llcoro::suspendUntilEventOnWithTimeout(sSyncPoint, 10, LLSD()); - } - else - { - LL_DEBUGS("LLLogin") << "Login failure, waiting for sync from updater" << LL_ENDL; - updater = llcoro::suspendUntilEventOnWithTimeout(sSyncPoint, 3, LLSD()); - } - if (updater.isUndefined()) - { - LL_WARNS("LLLogin") << "Failed to hear from updater, proceeding with fail.login" - << LL_ENDL; - } - else - { - LL_DEBUGS("LLLogin") << "Got responses from updater and login.cgi" << LL_ENDL; - } - } - - // Let the fail.login handler deal with empty updater response. - responses["updater"] = updater; sendProgressEvent("offline", "fail.login", responses); } return; // Done! -- cgit v1.3 From 186320e448459e57bb13c06d2605504a5ad695e6 Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Tue, 12 May 2026 14:22:47 -0700 Subject: Remove Vivox-related code Remove the Vivox-related code altogether, including SLVoice. --- autobuild.xml | 58 - indra/cmake/Copy3rdPartyLibs.cmake | 60 - indra/cmake/ViewerMiscLibs.cmake | 2 - indra/newview/CMakeLists.txt | 16 - indra/newview/app_settings/settings.xml | 170 +- .../installers/windows/installer_template.nsi | 3 - indra/newview/llappviewer.cpp | 1 - indra/newview/llimview.cpp | 12 +- indra/newview/llspeakers.cpp | 2 +- indra/newview/llviewermenu.cpp | 1 - indra/newview/llviewerstats.cpp | 3 - indra/newview/llvoiceclient.cpp | 102 +- indra/newview/llvoiceclient.h | 11 +- indra/newview/llvoicevivox.cpp | 7926 -------------------- indra/newview/llvoicevivox.h | 1093 --- indra/newview/llvoicewebrtc.cpp | 6 - indra/newview/llvoicewebrtc.h | 1 - indra/newview/viewer_manifest.py | 28 - 18 files changed, 24 insertions(+), 9471 deletions(-) delete mode 100644 indra/newview/llvoicevivox.cpp delete mode 100644 indra/newview/llvoicevivox.h (limited to 'indra/newview/llappviewer.cpp') diff --git a/autobuild.xml b/autobuild.xml index 9945672bd2..1e315b68e5 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2192,64 +2192,6 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors description Generated headers and sources for OpenXR loader. - slvoice - - platforms - - darwin64 - - archive - - hash - 1e70b06fe6eb9796097010871b32d8e95167e373 - hash_algorithm - sha1 - url - https://automated-builds-secondlife-com.s3.amazonaws.com/gh/secondlife/3p-slvoice/slvoice-4.10.0000.32327.5fc3fe7c.5942f08-darwin64-5942f08.tar.zst - - name - darwin64 - - linux64 - - archive - - hash - 92b0ae08832bd0e99c34ef8f3e6346ad - url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/613/1289/slvoice-3.2.0002.10426.500605-linux64-500605.tar.bz2 - - name - linux64 - - windows64 - - archive - - hash - ddfb7c30d9756915e8b26f44e2ee3a69ee87fb9a - hash_algorithm - sha1 - url - https://automated-builds-secondlife-com.s3.amazonaws.com/gh/secondlife/3p-slvoice/slvoice-4.10.0000.32327.5fc3fe7c.5942f08-windows64-5942f08.tar.zst - - name - windows64 - - - license - Mixed - license_file - LICENSES/vivox_licenses.txt - copyright - 2010 Vivox, including audio coding using Polycom¨ Siren14TM (ITU-T Rec. G.722.1 Annex C) - version - 4.10.0000.32327.5fc3fe7c.5942f08 - name - slvoice - description - Vivox SDK components - sse2neon platforms diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index 680f2f3ac2..667a04a1c5 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -28,29 +28,6 @@ endmacro() # set up platform specific lists of files that need to be copied ################################################################### if(WINDOWS) - #******************************* - # VIVOX - *NOTE: no debug version - set(vivox_lib_dir "${ARCH_PREBUILT_DIRS_RELEASE}") - - # ND, it seems there is no such thing defined. At least when building a viewer - # Does this maybe matter on some LL buildserver? Otherwise this and the snippet using slvoice_src_dir - # can all go - if( ARCH_PREBUILT_BIN_RELEASE ) - set(slvoice_src_dir "${ARCH_PREBUILT_BIN_RELEASE}") - endif() - set(slvoice_files SLVoice.exe ) - if (ADDRESS_SIZE EQUAL 64) - list(APPEND vivox_libs - vivoxsdk_x64.dll - ortp_x64.dll - ) - else (ADDRESS_SIZE EQUAL 64) - list(APPEND vivox_libs - vivoxsdk.dll - ortp.dll - ) - endif (ADDRESS_SIZE EQUAL 64) - #******************************* # Misc shared libs @@ -159,12 +136,6 @@ if(WINDOWS) endforeach() elseif(DARWIN) - set(vivox_lib_dir "${ARCH_PREBUILT_DIRS_RELEASE}") - set(slvoice_files SLVoice) - set(vivox_libs - libortp.dylib - libvivoxsdk.dylib - ) set(debug_src_dir "${ARCH_PREBUILT_DIRS_DEBUG}") set(debug_files ) @@ -188,15 +159,6 @@ elseif(LINUX) set(SHARED_LIB_STAGING_DIR_RELWITHDEBINFO "${SHARED_LIB_STAGING_DIR}") set(SHARED_LIB_STAGING_DIR_RELEASE "${SHARED_LIB_STAGING_DIR}") - set(vivox_lib_dir "${ARCH_PREBUILT_DIRS_RELEASE}") - set(vivox_libs - libsndfile.so.1 - libortp.so - libvivoxoal.so.1 - libvivoxsdk.so - ) - set(slvoice_files SLVoice) - # *TODO - update this to use LIBS_PREBUILT_DIR and LL_ARCH_DIR variables # or ARCH_PREBUILT_DIRS set(debug_src_dir "${ARCH_PREBUILT_DIRS_DEBUG}") @@ -226,8 +188,6 @@ elseif(LINUX) else(WINDOWS) message(STATUS "WARNING: unrecognized platform for staging 3rd party libs, skipping...") - set(vivox_lib_dir "${CMAKE_SOURCE_DIR}/newview/vivox-runtime/i686-linux") - set(vivox_libs "") # *TODO - update this to use LIBS_PREBUILT_DIR and LL_ARCH_DIR variables # or ARCH_PREBUILT_DIRS set(debug_src_dir "${CMAKE_SOURCE_DIR}/../libraries/i686-linux/lib/debug") @@ -249,26 +209,6 @@ endif(WINDOWS) # Done building the file lists, now set up the copy commands. ################################################################ -# Curiously, slvoice_files are only copied to SHARED_LIB_STAGING_DIR_RELEASE. -# It's unclear whether this is oversight or intentional, but anyway leave the -# single copy_if_different command rather than using to_staging_dirs. - -if( slvoice_src_dir ) - copy_if_different( - ${slvoice_src_dir} - "${SHARED_LIB_STAGING_DIR_RELEASE}" - out_targets - ${slvoice_files} - ) - list(APPEND third_party_targets ${out_targets}) -endif() - -to_staging_dirs( - ${vivox_lib_dir} - third_party_targets - ${vivox_libs} - ) - to_staging_dirs( ${release_src_dir} third_party_targets diff --git a/indra/cmake/ViewerMiscLibs.cmake b/indra/cmake/ViewerMiscLibs.cmake index cae68fbc11..f91222aa75 100644 --- a/indra/cmake/ViewerMiscLibs.cmake +++ b/indra/cmake/ViewerMiscLibs.cmake @@ -16,8 +16,6 @@ if( NOT USE_CONAN ) use_prebuilt_binary(libhunspell) endif() -use_prebuilt_binary(slvoice) - use_prebuilt_binary(nanosvg) use_prebuilt_binary(viewer-fonts) use_prebuilt_binary(emoji_shortcodes) diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 1d1553355e..861712fb58 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -723,7 +723,6 @@ set(viewer_SOURCE_FILES llvoicechannel.cpp llvoiceclient.cpp llvoicevisualizer.cpp - llvoicevivox.cpp llvoicewebrtc.cpp llvoinventorylistener.cpp llvopartgroup.cpp @@ -1396,7 +1395,6 @@ set(viewer_HEADER_FILES llvoicechannel.h llvoiceclient.h llvoicevisualizer.h - llvoicevivox.h llvoicewebrtc.h llvoinventorylistener.h llvopartgroup.h @@ -1785,9 +1783,7 @@ if (WINDOWS) ${CMAKE_SOURCE_DIR}/../scripts/messages/message_template.msg ${SHARED_LIB_STAGING_DIR}/openjp2.dll ${SHARED_LIB_STAGING_DIR}/llwebrtc.dll - #${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/SLVoice.exe #${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/libsndfile-1.dll - #${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/vivoxoal.dll ${AUTOBUILD_INSTALL_DIR}/ca-bundle.crt ${CMAKE_CURRENT_SOURCE_DIR}/licenses-win32.txt ${CMAKE_CURRENT_SOURCE_DIR}/featuretable.txt @@ -1798,18 +1794,6 @@ if (WINDOWS) media_plugin_example ) - if (ADDRESS_SIZE EQUAL 64) - list(APPEND COPY_INPUT_DEPENDENCIES - ${SHARED_LIB_STAGING_DIR}/vivoxsdk_x64.dll - ${SHARED_LIB_STAGING_DIR}/ortp_x64.dll - ) - else (ADDRESS_SIZE EQUAL 64) - list(APPEND COPY_INPUT_DEPENDENCIES - ${SHARED_LIB_STAGING_DIR}/vivoxsdk.dll - ${SHARED_LIB_STAGING_DIR}/ortp.dll - ) - endif (ADDRESS_SIZE EQUAL 64) - if (TARGET ll::discord_sdk) list(APPEND COPY_INPUT_DEPENDENCIES ${SHARED_LIB_STAGING_DIR}/discord_partner_sdk.dll diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index d7026d2dce..c41a1e5b88 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -13498,141 +13498,8 @@ Value 1 - VivoxAutoPostCrashDumps - - Comment - If true, SLVoice will automatically send crash dumps directly to Vivox. - Persist - 1 - Type - Boolean - Value - 1 - - VivoxDebugLevel - - Comment - Logging level to use when launching the vivox daemon - Persist - 1 - Type - String - Value - 0 - - VivoxLogDirectory - - Comment - Default log path is Application Support/SecondLife/logs specify alternate absolute path here. - Persist - 1 - Type - String - Value - - - VivoxShutdownTimeout - - Comment - shutdown timeout in miliseconds. The amount of time to wait for the service to shutdown gracefully after the last disconnect - Persist - 1 - Type - String - Value - 5 - - VivoxDebugSIPURIHostName - - Comment - Hostname portion of vivox SIP URIs (empty string for the default). - Persist - 1 - Type - String - Value - - - VivoxDebugVoiceAccountServerURI - - Comment - URI to the vivox account management server (empty string for the default). - Persist - 1 - Type - String - Value - - - VivoxVoiceHost - - Comment - Client SLVoice host to connect to - Persist - 1 - Type - String - Value - 127.0.0.1 - - VivoxVoicePort - - Comment - Client SLVoice port to connect to - Persist - 1 - Type - U32 - Value - 44125 - - - VivoxVadAuto - - Comment - A flag indicating if the automatic VAD is enabled (1) or disabled (0). The individual settings are ignored if the auto-mode is enabled - Persist - 1 - Type - U32 - Value - 0 - - VivoxVadHangover - - Comment - The time (in milliseconds) that it takes or the VAD to switch back to silence from speech mode after the last speech frame has been detected - Persist - 1 - Type - U32 - Value - 2000 - - VivoxVadNoiseFloor - - Comment - A dimensionless value between 0 and 20000 (default 576) that controls the maximum level at which the noise floor may be set at by the VAD's noise tracking - Persist - 1 - Type - U32 - Value - 576 - - VivoxVadSensitivity - - Comment - A dimensionless value between 0 and 100, indicating the 'sensitivity of the VAD'. Increasing this value corresponds to decreasing the sensitivity of the VAD and 0 is turned off altogether - Persist - 1 - Type - U32 - Value - 0 - - VoiceCallsFriendsOnly - + VoiceCallsFriendsOnly + Comment (Deprecated) Only accept voice calls from residents on your friends list @@ -13709,17 +13576,6 @@ Value 0 - VoiceHost - - Comment - Client SLVoice host to connect to - Persist - 1 - Type - String - Value - 127.0.0.1 - VoiceImageLevel0 Comment @@ -13808,17 +13664,6 @@ Value Default - VoiceLogFile - - Comment - Log file to use when launching the voice daemon - Persist - 1 - Type - String - Value - - VoiceOutputAudioDevice Comment @@ -13841,17 +13686,6 @@ Value 10 - VoicePort - - Comment - Client SLVoice port to connect to - Persist - 1 - Type - U32 - Value - 44125 - VoiceEchoCancellation Comment diff --git a/indra/newview/installers/windows/installer_template.nsi b/indra/newview/installers/windows/installer_template.nsi index 07ed0d0824..cf12be91c3 100644 --- a/indra/newview/installers/windows/installer_template.nsi +++ b/indra/newview/installers/windows/installer_template.nsi @@ -865,9 +865,6 @@ Delete "$INSTDIR\trial\*.html" Delete "$INSTDIR\newview.exe" Delete "$INSTDIR\SecondLife.exe" -# MAINT-3099 workaround - prevent these log files, if present, from causing a user alert -Delete "$INSTDIR\VivoxVoiceService-*.log" - # Remove entire help directory RMDir /r "$INSTDIR\help" diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 46e64c2a4e..59b8ff5c70 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2983,7 +2983,6 @@ bool LLAppViewer::initConfiguration() { // This is the second instance of SL. Mute voice, // but make sure the setting is *not* persisted. - // Also see LLVivoxVoiceClient::voiceEnabled() LLControlVariable* enable_voice = gSavedSettings.getControl("EnableVoiceChat"); if (enable_voice) { diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index ad01e11d48..252eb32e7c 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -89,8 +89,7 @@ const S32 XL8_PADDING = 3; // XL8_START_TAG.size() + XL8_END_TAG.size() const static U32 SESSION_INITIALIZATION_TIMEOUT = 30; // This enum corresponds to the sim's and adds P2P_CHAT_SESSION, -// as webrtc uses the multiagent chat mechanism for p2p calls, -// instead of relying on vivox calling. +// as webrtc uses the multiagent chat mechanism for p2p calls. // Don't change this without consulting a server developer. enum EMultiAgentChatSessionType { @@ -1943,12 +1942,6 @@ void LLIMModel::sendMessage(const std::string& utf8_text, info = LLAvatarTracker::instance().getBuddyInfo(other_participant_id); U8 offline = (!info || info->isOnline()) ? IM_ONLINE : IM_OFFLINE; - // Old call to send messages to SLim client, no longer supported. - //if((offline == IM_OFFLINE) && (LLVoiceClient::getInstance()->isOnlineSIP(other_participant_id))) - //{ - // // User is online through the OOW connector, but not with a regular viewer. Try to send the message via SLVoice. - // sent = LLVoiceClient::getInstance()->sendTextMessage(other_participant_id, utf8_text); - //} if(!sent) { @@ -3088,8 +3081,7 @@ void LLIncomingCallDialog::processCallResponse(S32 response, const LLSD &payload { if (type == IM_SESSION_P2P_INVITE) { - // decline p2p voice, either via the vivox-style call mechanism - // or via the webrtc-style "decline p2p" mechanism. + // decline p2p voice, via the webrtc-style "decline p2p" mechanism. LLVoiceP2PIncomingCallInterfacePtr call = LLVoiceClient::getInstance()->getIncomingCallInterface(payload["voice_channel_info"]); if (call) { diff --git a/indra/newview/llspeakers.cpp b/indra/newview/llspeakers.cpp index f079c70c6c..c75314825c 100644 --- a/indra/newview/llspeakers.cpp +++ b/indra/newview/llspeakers.cpp @@ -438,7 +438,7 @@ void LLSpeakerMgr::update(bool resort_ok) { if(speakerp->mType == LLSpeaker::SPEAKER_EXTERNAL) { - // external speakers should be timed out when they leave the voice channel (since they only exist via SLVoice) + // external speakers should be timed out when they leave the voice channel speakerp->mStatus = LLSpeaker::STATUS_NOT_IN_CHANNEL; } else diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index dbcf4fbbf4..5a79aa91bf 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -127,7 +127,6 @@ #include "llviewerstatsrecorder.h" #include "llvlcomposition.h" #include "llvoavatarself.h" -#include "llvoicevivox.h" #include "llworld.h" #include "llworldmap.h" #include "pipeline.h" diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index 5193514fe8..4dcd1cc03a 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -62,7 +62,6 @@ #include "llsdserialize.h" #include "llsdutil.h" #include "llcorehttputil.h" -#include "llvoicevivox.h" #include "llinventorymodel.h" #include "lluiusage.h" #include "lltranslate.h" @@ -796,8 +795,6 @@ void send_viewer_stats(bool include_preferences) body["ui"] = LLUIUsage::instance().asLLSD(); - body["stats"]["voice"] = LLVoiceVivoxStats::getInstance()->read(); - // Misc stats, two strings and two ints // These are not expecticed to persist across multiple releases // Comment any changes with your name and the expected release revision diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index 71a9e71a9f..51a7204782 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -25,7 +25,6 @@ */ #include "llvoiceclient.h" -#include "llvoicevivox.h" #include "llvoicewebrtc.h" #include "llviewernetwork.h" #include "llviewercontrol.h" @@ -116,11 +115,7 @@ std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserv LLVoiceModuleInterface *getVoiceModule(const std::string &voice_server_type) { - if (voice_server_type == VIVOX_VOICE_SERVER_TYPE || voice_server_type.empty()) - { - return (LLVoiceModuleInterface *) LLVivoxVoiceClient::getInstance(); - } - else if (voice_server_type == WEBRTC_VOICE_SERVER_TYPE) + if (voice_server_type == WEBRTC_VOICE_SERVER_TYPE) { return (LLVoiceModuleInterface *) LLWebRTCVoiceClient::getInstance(); } @@ -166,7 +161,6 @@ void LLVoiceClient::init(LLPumpIO *pump) // Initialize all of the voice modules m_servicePump = pump; LLWebRTCVoiceClient::getInstance()->init(pump); - LLVivoxVoiceClient::getInstance()->init(pump); } void LLVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID) @@ -177,7 +171,6 @@ void LLVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &age } mRegionChangedCallbackSlot = gAgent.addRegionChangedCallback(boost::bind(&LLVoiceClient::onRegionChanged, this)); LLWebRTCVoiceClient::getInstance()->userAuthorized(user_id, agentID); - LLVivoxVoiceClient::getInstance()->userAuthorized(user_id, agentID); } void LLVoiceClient::handleSimulatorFeaturesReceived(const LLSD &simulatorFeatures) @@ -185,7 +178,7 @@ void LLVoiceClient::handleSimulatorFeaturesReceived(const LLSD &simulatorFeature std::string voiceServerType = simulatorFeatures["VoiceServerType"].asString(); if (voiceServerType.empty()) { - voiceServerType = VIVOX_VOICE_SERVER_TYPE; + voiceServerType = WEBRTC_VOICE_SERVER_TYPE; } if (mSpatialVoiceModule && !mNonSpatialVoiceModule) @@ -287,19 +280,14 @@ void LLVoiceClient::setHidden(bool hidden) { LL_INFOS("Voice") << "( " << (hidden ? "true" : "false") << " )" << LL_ENDL; LLWebRTCVoiceClient::getInstance()->setHidden(hidden); - LLVivoxVoiceClient::getInstance()->setHidden(hidden); } void LLVoiceClient::terminate() { - if (LLVivoxVoiceClient::instanceExists()) + if (LLWebRTCVoiceClient::instanceExists()) { LLWebRTCVoiceClient::getInstance()->terminate(); } - if (LLVivoxVoiceClient::instanceExists()) - { - LLVivoxVoiceClient::getInstance()->terminate(); - } mSpatialVoiceModule = NULL; m_servicePump = NULL; @@ -335,7 +323,6 @@ void LLVoiceClient::updateSettings() updateMicMuteLogic(); LLWebRTCVoiceClient::getInstance()->updateSettings(); - LLVivoxVoiceClient::getInstance()->updateSettings(); } //-------------------------------------------------- @@ -344,13 +331,11 @@ void LLVoiceClient::updateSettings() void LLVoiceClient::tuningStart() { LLWebRTCVoiceClient::getInstance()->tuningStart(); - LLVivoxVoiceClient::getInstance()->tuningStart(); } void LLVoiceClient::tuningStop() { LLWebRTCVoiceClient::getInstance()->tuningStop(); - LLVivoxVoiceClient::getInstance()->tuningStop(); } bool LLVoiceClient::inTuningMode() @@ -393,13 +378,11 @@ void LLVoiceClient::refreshDeviceLists(bool clearCurrentList) void LLVoiceClient::setCaptureDevice(const std::string& name) { - LLVivoxVoiceClient::getInstance()->setCaptureDevice(name); LLWebRTCVoiceClient::getInstance()->setCaptureDevice(name); } void LLVoiceClient::setRenderDevice(const std::string& name) { - LLVivoxVoiceClient::getInstance()->setRenderDevice(name); LLWebRTCVoiceClient::getInstance()->setRenderDevice(name); } @@ -421,13 +404,11 @@ const LLVoiceDeviceList& LLVoiceClient::getRenderDevices() void LLVoiceClient::getParticipantList(std::set &participants) const { LLWebRTCVoiceClient::getInstance()->getParticipantList(participants); - LLVivoxVoiceClient::getInstance()->getParticipantList(participants); } bool LLVoiceClient::isParticipant(const LLUUID &speaker_id) const { - return LLWebRTCVoiceClient::getInstance()->isParticipant(speaker_id) || - LLVivoxVoiceClient::getInstance()->isParticipant(speaker_id); + return LLWebRTCVoiceClient::getInstance()->isParticipant(speaker_id); } @@ -519,14 +500,12 @@ void LLVoiceClient::activateSpatialChannel(bool activate) bool LLVoiceClient::isCurrentChannel(const LLSD& channelInfo) { - return LLWebRTCVoiceClient::getInstance()->isCurrentChannel(channelInfo) || - LLVivoxVoiceClient::getInstance()->isCurrentChannel(channelInfo); + return LLWebRTCVoiceClient::getInstance()->isCurrentChannel(channelInfo); } bool LLVoiceClient::compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) { - return LLWebRTCVoiceClient::getInstance()->compareChannels(channelInfo1, channelInfo2) || - LLVivoxVoiceClient::getInstance()->compareChannels(channelInfo1, channelInfo2); + return LLWebRTCVoiceClient::getInstance()->compareChannels(channelInfo1, channelInfo2); } LLVoiceP2PIncomingCallInterfacePtr LLVoiceClient::getIncomingCallInterface(const LLSD& voice_call_info) @@ -554,8 +533,8 @@ LLVoiceP2POutgoingCallInterface *LLVoiceClient::getOutgoingCallInterface(const L if (voiceChannelInfo.has("voice_server_type") && voiceChannelInfo["voice_server_type"] != voice_server_type) { // there's a mismatch between what the peer is offering and what our server - // can handle, so downgrade to vivox - voice_server_type = VIVOX_VOICE_SERVER_TYPE; + // can handle, so default to webrtc + voice_server_type = WEBRTC_VOICE_SERVER_TYPE; } LLVoiceModuleInterface *module = getVoiceModule(voice_server_type); return dynamic_cast(module); @@ -568,13 +547,11 @@ LLVoiceP2POutgoingCallInterface *LLVoiceClient::getOutgoingCallInterface(const L void LLVoiceClient::setVoiceVolume(F32 volume) { LLWebRTCVoiceClient::getInstance()->setVoiceVolume(volume); - LLVivoxVoiceClient::getInstance()->setVoiceVolume(volume); } void LLVoiceClient::setMicGain(F32 gain) { LLWebRTCVoiceClient::getInstance()->setMicGain(gain); - LLVivoxVoiceClient::getInstance()->setMicGain(gain); } @@ -624,10 +601,6 @@ void LLVoiceClient::setVoiceEnabled(bool enabled) { LLWebRTCVoiceClient::getInstance()->setVoiceEnabled(enabled); } - if (LLVivoxVoiceClient::instanceExists()) - { - LLVivoxVoiceClient::getInstance()->setVoiceEnabled(enabled); - } } void LLVoiceClient::updateMicMuteLogic() @@ -647,7 +620,6 @@ void LLVoiceClient::updateMicMuteLogic() new_mic_mute = true; } LLWebRTCVoiceClient::getInstance()->setMuteMic(new_mic_mute); - LLVivoxVoiceClient::getInstance()->setMuteMic(new_mic_mute); } void LLVoiceClient::setMuteMic(bool muted) @@ -742,17 +714,12 @@ bool LLVoiceClient::getVoiceEnabled(const LLUUID& id) const std::string LLVoiceClient::getDisplayName(const LLUUID& id) const { std::string result = LLWebRTCVoiceClient::getInstance()->getDisplayName(id); - if (result.empty()) - { - result = LLVivoxVoiceClient::getInstance()->getDisplayName(id); - } return result; } bool LLVoiceClient::isVoiceWorking() const { - return LLVivoxVoiceClient::getInstance()->isVoiceWorking() || - LLWebRTCVoiceClient::getInstance()->isVoiceWorking(); + return LLWebRTCVoiceClient::getInstance()->isVoiceWorking(); } bool LLVoiceClient::isParticipantAvatar(const LLUUID& id) @@ -767,22 +734,19 @@ bool LLVoiceClient::isOnlineSIP(const LLUUID& id) bool LLVoiceClient::getIsSpeaking(const LLUUID& id) { - return LLWebRTCVoiceClient::getInstance()->getIsSpeaking(id) || - LLVivoxVoiceClient::getInstance()->getIsSpeaking(id); + return LLWebRTCVoiceClient::getInstance()->getIsSpeaking(id); } bool LLVoiceClient::getIsModeratorMuted(const LLUUID& id) { // don't bother worrying about p2p calls, as // p2p calls don't have mute. - return LLWebRTCVoiceClient::getInstance()->getIsModeratorMuted(id) || - LLVivoxVoiceClient::getInstance()->getIsModeratorMuted(id); + return LLWebRTCVoiceClient::getInstance()->getIsModeratorMuted(id); } F32 LLVoiceClient::getCurrentPower(const LLUUID& id) { - return std::fmax(LLVivoxVoiceClient::getInstance()->getCurrentPower(id), - LLWebRTCVoiceClient::getInstance()->getCurrentPower(id)); + return LLWebRTCVoiceClient::getInstance()->getCurrentPower(id); } bool LLVoiceClient::getOnMuteList(const LLUUID& id) @@ -794,13 +758,12 @@ bool LLVoiceClient::getOnMuteList(const LLUUID& id) F32 LLVoiceClient::getUserVolume(const LLUUID& id) { - return std::fmax(LLVivoxVoiceClient::getInstance()->getUserVolume(id), LLWebRTCVoiceClient::getInstance()->getUserVolume(id)); + return LLWebRTCVoiceClient::getInstance()->getUserVolume(id); } void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) { LLWebRTCVoiceClient::getInstance()->setUserVolume(id, volume); - LLVivoxVoiceClient::getInstance()->setUserVolume(id, volume); } //-------------------------------------------------- @@ -808,16 +771,11 @@ void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) { - LLVivoxVoiceClient::getInstance()->addObserver(observer); LLWebRTCVoiceClient::getInstance()->addObserver(observer); } void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) { - if (LLVivoxVoiceClient::instanceExists()) - { - LLVivoxVoiceClient::getInstance()->removeObserver(observer); - } if (LLWebRTCVoiceClient::instanceExists()) { LLWebRTCVoiceClient::getInstance()->removeObserver(observer); @@ -826,16 +784,11 @@ void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) void LLVoiceClient::addObserver(LLFriendObserver* observer) { - LLVivoxVoiceClient::getInstance()->addObserver(observer); LLWebRTCVoiceClient::getInstance()->addObserver(observer); } void LLVoiceClient::removeObserver(LLFriendObserver* observer) { - if (LLVivoxVoiceClient::instanceExists()) - { - LLVivoxVoiceClient::getInstance()->removeObserver(observer); - } if (LLWebRTCVoiceClient::instanceExists()) { LLWebRTCVoiceClient::getInstance()->removeObserver(observer); @@ -844,38 +797,17 @@ void LLVoiceClient::removeObserver(LLFriendObserver* observer) void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) { - LLVivoxVoiceClient::getInstance()->addObserver(observer); LLWebRTCVoiceClient::getInstance()->addObserver(observer); } void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) { - if (LLVivoxVoiceClient::instanceExists()) - { - LLVivoxVoiceClient::getInstance()->removeObserver(observer); - } if (LLWebRTCVoiceClient::instanceExists()) { LLWebRTCVoiceClient::getInstance()->removeObserver(observer); } } -std::string LLVoiceClient::sipURIFromID(const LLUUID &id) const -{ - if (mNonSpatialVoiceModule) - { - return mNonSpatialVoiceModule->sipURIFromID(id); - } - else if (mSpatialVoiceModule) - { - return mSpatialVoiceModule->sipURIFromID(id); - } - else - { - return std::string(); - } -} - LLSD LLVoiceClient::getP2PChannelInfoTemplate(const LLUUID& id) const { if (mNonSpatialVoiceModule) @@ -908,7 +840,7 @@ class LLViewerRequiredVoiceVersion : public LLHTTPNode const LLSD& context, const LLSD& input) const { - std::string voice_server_type = "vivox"; + std::string voice_server_type = "webrtc"; if (input.has("body") && input["body"].has("voice_server_type")) { voice_server_type = input["body"]["voice_server_type"].asString(); @@ -916,11 +848,7 @@ class LLViewerRequiredVoiceVersion : public LLHTTPNode LLVoiceModuleInterface *voiceModule = NULL; - if (voice_server_type == "vivox" || voice_server_type.empty()) - { - voiceModule = (LLVoiceModuleInterface *) LLVivoxVoiceClient::getInstance(); - } - else if (voice_server_type == "webrtc") + if (voice_server_type == "webrtc" || voice_server_type.empty()) { voiceModule = (LLVoiceModuleInterface *) LLWebRTCVoiceClient::getInstance(); } diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h index 788ea3b3b3..70bce055f9 100644 --- a/indra/newview/llvoiceclient.h +++ b/indra/newview/llvoiceclient.h @@ -106,7 +106,7 @@ struct LLVoiceVersionInfo /// @class LLVoiceP2POutgoingCallInterface /// @brief Outgoing call interface /// -/// For providers that support P2P signaling (vivox) +/// For providers that support P2P signaling ///////////////////////////////// class LLVoiceP2POutgoingCallInterface @@ -121,7 +121,7 @@ class LLVoiceP2POutgoingCallInterface /// @class LLVoiceP2PIncomingCallInterface /// @brief Incoming call interface /// -/// For providers that support P2P signaling (vivox) +/// For providers that support P2P signaling ///////////////////////////////// class LLVoiceP2PIncomingCallInterface { @@ -180,7 +180,6 @@ public: virtual bool deviceSettingsAvailable()=0; virtual bool deviceSettingsUpdated() = 0; - // Requery the vivox daemon for the current list of input/output devices. // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed // (use this if you want to know when it's done). // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. @@ -280,7 +279,6 @@ public: virtual void removeObserver(LLVoiceClientParticipantObserver* observer)=0; //@} - virtual std::string sipURIFromID(const LLUUID &id) const=0; virtual LLSD getP2PChannelInfoTemplate(const LLUUID& id) const=0; //@} @@ -384,7 +382,7 @@ public: bool deviceSettingsAvailable(); bool deviceSettingsUpdated(); // returns true when the device list has been updated recently. - // Requery the vivox daemon for the current list of input/output devices. + // Requery the voice driver for the current list of input/output devices. // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed // (use this if you want to know when it's done). // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. @@ -488,7 +486,6 @@ public: static void addObserver(LLVoiceClientParticipantObserver* observer); static void removeObserver(LLVoiceClientParticipantObserver* observer); - std::string sipURIFromID(const LLUUID &id) const; LLSD getP2PChannelInfoTemplate(const LLUUID& id) const; ////////////////////////// @@ -513,7 +510,7 @@ protected: LLVoiceModuleInterface* mSpatialVoiceModule; LLVoiceModuleInterface* mNonSpatialVoiceModule; - LLSD mSpatialCredentials; // used to store spatial credentials for vivox + LLSD mSpatialCredentials; // used to store spatial credentials for the voice subsystem // so they're available when the region voice // server is retrieved. LLPumpIO *m_servicePump; diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp deleted file mode 100644 index 107a08fdf8..0000000000 --- a/indra/newview/llvoicevivox.cpp +++ /dev/null @@ -1,7926 +0,0 @@ - /** - * @file LLVivoxVoiceClient.cpp - * @brief Implementation of LLVivoxVoiceClient class which is the interface to the voice client process. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * 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 - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ - -#include "llviewerprecompiledheaders.h" -#include -#include "llvoicevivox.h" - -#include "llsdutil.h" - -// Linden library includes -#include "llavatarnamecache.h" -#include "llvoavatarself.h" -#include "llbufferstream.h" -#include "llfile.h" -#include "llmenugl.h" -#ifdef LL_USESYSTEMLIBS -# include "expat.h" -#else -# include "expat/expat.h" -#endif -#include "llcallbacklist.h" -#include "llviewerregion.h" -#include "llviewernetwork.h" // for gGridChoice -#include "llbase64.h" -#include "llviewercontrol.h" -#include "llappviewer.h" // for gDisconnected, gDisableVoice -#include "llprocess.h" - -// Viewer includes -#include "llmutelist.h" // to check for muted avatars -#include "llagent.h" -#include "llcachename.h" -#include "llimview.h" // for LLIMMgr -#include "llparcel.h" -#include "llviewerparcelmgr.h" -#include "llfirstuse.h" -#include "llspeakers.h" -#include "lltrans.h" -#include "llviewerwindow.h" -#include "llviewercamera.h" -#include "llversioninfo.h" - -#include "llviewernetwork.h" -#include "llnotificationsutil.h" - -#include "llcorehttputil.h" -#include "lleventfilter.h" - -#include "stringize.h" - -// for base64 decoding -#include "apr_base64.h" - -#define USE_SESSION_GROUPS 0 -#define VX_NULL_POSITION -2147483648.0 /*The Silence*/ - -extern LLMenuBarGL* gMenuBarView; -extern void handle_voice_morphing_subscribe(); - -const std::string VIVOX_VOICE_SERVER_TYPE = "vivox"; - -namespace { - const F32 VOLUME_SCALE_VIVOX = 0.01f; - - const F32 SPEAKING_TIMEOUT = 1.f; - - static const std::string VISIBLE_VOICE_SERVER_TYPE = "Vivox"; - - // Don't retry connecting to the daemon more frequently than this: - const F32 DAEMON_CONNECT_THROTTLE_SECONDS = 1.0f; - const int DAEMON_CONNECT_RETRY_MAX = 3; - - // Don't send positional updates more frequently than this: - const F32 UPDATE_THROTTLE_SECONDS = 0.5f; - - // Timeout for connection to Vivox - const F32 CONNECT_ATTEMPT_TIMEOUT = 300.0f; - const F32 CONNECT_DNS_TIMEOUT = 5.0f; - const int CONNECT_RETRY_MAX = 3; - - const F32 LOGIN_ATTEMPT_TIMEOUT = 30.0f; - const F32 LOGOUT_ATTEMPT_TIMEOUT = 5.0f; - const int LOGIN_RETRY_MAX = 3; - - const F32 PROVISION_RETRY_TIMEOUT = 2.0; - const int PROVISION_RETRY_MAX = 5; - - // Cosine of a "trivially" small angle - const F32 FOUR_DEGREES = 4.0f * (F_PI / 180.0f); - const F32 MINUSCULE_ANGLE_COS = (F32) cos(0.5f * FOUR_DEGREES); - - const F32 SESSION_JOIN_TIMEOUT = 30.0f; - - // Defines the maximum number of times(in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine() - // which is treated as normal. The is the number of frames to wait for a channel join before giving up. This was changed - // from the original count of 50 for two reason. Modern PCs have higher frame rates and sometimes the SLVoice process - // backs up processing join requests. There is a log statement that records when channel joins take longer than 100 frames. - const int MAX_NORMAL_JOINING_SPATIAL_NUM = 1500; - - // How often to check for expired voice fonts in seconds - const F32 VOICE_FONT_EXPIRY_INTERVAL = 10.f; - // Time of day at which Vivox expires voice font subscriptions. - // Used to replace the time portion of received expiry timestamps. - static const std::string VOICE_FONT_EXPIRY_TIME = "T05:00:00Z"; - - // Maximum length of capture buffer recordings in seconds. - const F32 CAPTURE_BUFFER_MAX_TIME = 10.f; - - const int ERROR_VIVOX_OBJECT_NOT_FOUND = 1001; - const int ERROR_VIVOX_NOT_LOGGED_IN = 1007; -} - -static int scale_mic_volume(float volume) -{ - // incoming volume has the range [0.0 ... 2.0], with 1.0 as the default. - // Map it to Vivox levels as follows: 0.0 -> 30, 1.0 -> 50, 2.0 -> 70 - return 30 + (int)(volume * 20.0f); -} - -static int scale_speaker_volume(float volume) -{ - // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. - // Map it to Vivox levels as follows: 0.0 -> 30, 0.5 -> 50, 1.0 -> 70 - return 30 + (int)(volume * 40.0f); - -} - - -/////////////////////////////////////////////////////////////////////////////////////////////// - -class LLVivoxVoiceClientMuteListObserver : public LLMuteListObserver -{ - /* virtual */ void onChange() { LLVivoxVoiceClient::getInstance()->muteListChanged();} -}; - - -void LLVoiceVivoxStats::reset() -{ - mStartTime = -1.0f; - mConnectCycles = 0; - mConnectTime = -1.0f; - mConnectAttempts = 0; - mProvisionTime = -1.0f; - mProvisionAttempts = 0; - mEstablishTime = -1.0f; - mEstablishAttempts = 0; -} - -LLVoiceVivoxStats::LLVoiceVivoxStats() -{ - reset(); -} - -LLVoiceVivoxStats::~LLVoiceVivoxStats() -{ -} - -void LLVoiceVivoxStats::connectionAttemptStart() -{ - if (!mConnectAttempts) - { - mStartTime = LLTimer::getTotalTime(); - mConnectCycles++; - } - mConnectAttempts++; -} - -void LLVoiceVivoxStats::connectionAttemptEnd(bool success) -{ - if ( success ) - { - mConnectTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; - } -} - -void LLVoiceVivoxStats::provisionAttemptStart() -{ - if (!mProvisionAttempts) - { - mStartTime = LLTimer::getTotalTime(); - } - mProvisionAttempts++; -} - -void LLVoiceVivoxStats::provisionAttemptEnd(bool success) -{ - if ( success ) - { - mProvisionTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; - } -} - -void LLVoiceVivoxStats::establishAttemptStart() -{ - if (!mEstablishAttempts) - { - mStartTime = LLTimer::getTotalTime(); - } - mEstablishAttempts++; -} - -void LLVoiceVivoxStats::establishAttemptEnd(bool success) -{ - if ( success ) - { - mEstablishTime = (LLTimer::getTotalTime() - mStartTime) / USEC_PER_SEC; - } -} - -LLSD LLVoiceVivoxStats::read() -{ - LLSD stats(LLSD::emptyMap()); - - stats["connect_cycles"] = LLSD::Integer(mConnectCycles); - stats["connect_attempts"] = LLSD::Integer(mConnectAttempts); - stats["connect_time"] = LLSD::Real(mConnectTime); - - stats["provision_attempts"] = LLSD::Integer(mProvisionAttempts); - stats["provision_time"] = LLSD::Real(mProvisionTime); - - stats["establish_attempts"] = LLSD::Integer(mEstablishAttempts); - stats["establish_time"] = LLSD::Real(mEstablishTime); - - return stats; -} - -static LLVivoxVoiceClientMuteListObserver mutelist_listener; -static bool sMuteListListener_listening = false; - -/////////////////////////////////////////////////////////////////////////////////////////////// -static LLProcessPtr sGatewayPtr; -static LLEventStream sGatewayPump("VivoxDaemonPump", true); - -static bool isGatewayRunning() -{ - return sGatewayPtr && sGatewayPtr->isRunning(); -} - -static void killGateway() -{ - if (sGatewayPtr) - { - LL_DEBUGS("Voice") << "SLVoice " << sGatewayPtr->getStatusString() << LL_ENDL; - - sGatewayPump.stopListening("VivoxDaemonPump"); - sGatewayPtr->kill(__FUNCTION__); - sGatewayPtr=NULL; - } - else - { - LL_DEBUGS("Voice") << "no gateway" << LL_ENDL; - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////// - -bool LLVivoxVoiceClient::sShuttingDown = false; -bool LLVivoxVoiceClient::sConnected = false; -LLPumpIO *LLVivoxVoiceClient::sPump = nullptr; - -LLVivoxVoiceClient::LLVivoxVoiceClient() : - mWriteOffset(0), - mHidden(true), - mSessionTerminateRequested(false), - mRelogRequested(false), - mTerminateDaemon(false), - mSpatialJoiningNum(0), - - mTuningMode(false), - mTuningEnergy(0.0f), - mTuningMicVolume(0), - mTuningMicVolumeDirty(true), - mTuningSpeakerVolume(50), // Set to 50 so the user can hear himself when he sets his mic volume - mTuningSpeakerVolumeDirty(true), - mDevicesListUpdated(false), - - mAudioSession(), // TBD - should be NULL - mNextAudioSession(), - - mCurrentParcelLocalID(0), - mConnectorEstablished(false), - mAccountLoggedIn(false), - mNumberOfAliases(0), - mCommandCookie(0), - mLoginRetryCount(0), - - mBuddyListMapPopulated(false), - mBlockRulesListReceived(false), - mAutoAcceptRulesListReceived(false), - - mCaptureDeviceDirty(false), - mRenderDeviceDirty(false), - mSpatialCoordsDirty(false), - mIsInitialized(false), - - mMuteMic(false), - mMuteMicDirty(false), - mFriendsListDirty(true), - - mEarLocation(0), - mSpeakerVolumeDirty(true), - mSpeakerMuteDirty(true), - mMicVolume(0), - mMicVolumeDirty(true), - - mVoiceEnabled(false), - mProcessChannels(false), - mWriteInProgress(false), - - mVoiceFontsReceived(false), - mVoiceFontsNew(false), - mVoiceFontListDirty(false), - - mCaptureBufferMode(false), - mCaptureBufferRecording(false), - mCaptureBufferRecorded(false), - mCaptureBufferPlaying(false), - mShutdownComplete(true), - mPlayRequestCount(0), - - mAvatarNameCacheConnection(), - mIsInTuningMode(false), - mIsInChannel(false), - mIsJoiningSession(false), - mIsWaitingForFonts(false), - mIsLoggingIn(false), - mIsLoggedIn(false), - mIsProcessingChannels(false), - mIsCoroutineActive(false), - mVivoxPump("vivoxClientPump") -{ - sShuttingDown = false; - sConnected = false; - sPump = nullptr; - - mSpeakerVolume = scale_speaker_volume(0); - - mVoiceVersion.serverVersion = ""; - mVoiceVersion.voiceServerType = VISIBLE_VOICE_SERVER_TYPE; - mVoiceVersion.internalVoiceServerType = VIVOX_VOICE_SERVER_TYPE; - mVoiceVersion.majorVersion = 1; - mVoiceVersion.minorVersion = 0; - mVoiceVersion.mBuildVersion = ""; - mVoiceVersion.serverVersion = ""; - - // gMuteListp isn't set up at this point, so we defer this until later. -// gMuteListp->addObserver(&mutelist_listener); - - -#if LL_DARWIN || LL_LINUX - // HACK: THIS DOES NOT BELONG HERE - // When the vivox daemon dies, the next write attempt on our socket generates a SIGPIPE, which kills us. - // This should cause us to ignore SIGPIPE and handle the error through proper channels. - // This should really be set up elsewhere. Where should it go? - signal(SIGPIPE, SIG_IGN); - - // Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes. - // Ignoring SIGCHLD should prevent zombies from being created. Alternately, we could use wait(), but I'd rather not do that. - signal(SIGCHLD, SIG_IGN); -#endif - - - gIdleCallbacks.addFunction(idle, this); -} - -//--------------------------------------------------- - -LLVivoxVoiceClient::~LLVivoxVoiceClient() -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - sShuttingDown = true; -} - -//--------------------------------------------------- - -void LLVivoxVoiceClient::init(LLPumpIO *pump) -{ - // constructor will set up LLVoiceClient::getInstance() - sPump = pump; - -// LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro", -// boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance())); - -} - -void LLVivoxVoiceClient::terminate() -{ - if (sShuttingDown) - { - return; - } - - // needs to be done manually here since we will not get another pass in - // coroutines... that mechanism is long since gone. - if (mIsLoggedIn) - { - logoutOfVivox(false); - } - - if(sConnected) - { - breakVoiceConnection(false); - sConnected = false; - } - else - { - mRelogRequested = false; - killGateway(); - } - - sShuttingDown = true; - sPump = NULL; -} - -//--------------------------------------------------- - -void LLVivoxVoiceClient::cleanUp() -{ - LL_DEBUGS("Voice") << LL_ENDL; - - deleteAllSessions(); - deleteAllVoiceFonts(); - deleteVoiceFontTemplates(); - LL_DEBUGS("Voice") << "exiting" << LL_ENDL; -} - -//--------------------------------------------------- - -const LLVoiceVersionInfo& LLVivoxVoiceClient::getVersion() -{ - return mVoiceVersion; -} - -//--------------------------------------------------- - -void LLVivoxVoiceClient::updateSettings() -{ - setVoiceEnabled(LLVoiceClient::getInstance()->voiceEnabled()); - setEarLocation(gSavedSettings.getS32("VoiceEarLocation")); - - std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); - setCaptureDevice(inputDevice); - std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); - setRenderDevice(outputDevice); - F32 mic_level = gSavedSettings.getF32("AudioLevelMic"); - setMicGain(mic_level); -} - -///////////////////////////// -// utility functions - -bool LLVivoxVoiceClient::writeString(const std::string &str) -{ - bool result = false; - LL_DEBUGS("LowVoice") << "sending:\n" << str << LL_ENDL; - - if(sConnected) - { - apr_status_t err; - apr_size_t size = (apr_size_t)str.size(); - apr_size_t written = size; - - //MARK: Turn this on to log outgoing XML - // LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL; - - // check return code - sockets will fail (broken, etc.) - err = apr_socket_send( - mSocket->getSocket(), - (const char*)str.data(), - &written); - - if(err == 0 && written == size) - { - // Success. - result = true; - } - else if (err == 0 && written != size) { - // Did a short write, log it for now - LL_WARNS("Voice") << ") short write on socket sending data to vivox daemon." << "Sent " << written << "bytes instead of " << size <getExpandedFilename(LL_PATH_LOGS, ""); - - // Transition to stateConnectorStarted when the connector handle comes back. - std::string vivoxLogLevel = gSavedSettings.getString("VivoxDebugLevel"); - if ( vivoxLogLevel.empty() ) - { - vivoxLogLevel = "0"; - } - LL_DEBUGS("Voice") << "creating connector with log level " << vivoxLogLevel << LL_ENDL; - - stream - << "" - << "V2 SDK" - << "" << mVoiceAccountServerURI << "" - << "Normal" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "" - << "" << logdir << "" - << "Connector" - << ".log" - << "" << vivoxLogLevel << "" - << "" - << "" << LLVersionInfo::instance().getChannel() << " " << LLVersionInfo::instance().getVersion() << "" - //<< "" //Name can cause problems per vivox. - << "12" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::connectorShutdown() -{ - if(mConnectorEstablished) - { - std::ostringstream stream; - stream - << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "" - << "\n\n\n"; - - mShutdownComplete = false; - mConnectorEstablished = false; - - writeString(stream.str()); - } - else - { - mShutdownComplete = true; - } -} - -void LLVivoxVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID) -{ - - mAccountDisplayName = user_id; - - LL_INFOS("Voice") << "name \"" << mAccountDisplayName << "\" , ID " << agentID << LL_ENDL; - - mAccountName = nameFromID(agentID); -} - -void LLVivoxVoiceClient::setLoginInfo( - const std::string& account_name, - const std::string& password, - const std::string& voice_sip_uri_hostname, - const std::string& voice_account_server_uri) -{ - mVoiceSIPURIHostName = voice_sip_uri_hostname; - mVoiceAccountServerURI = voice_account_server_uri; - - if(mAccountLoggedIn) - { - // Already logged in. - LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL; - - // Don't process another login. - return; - } - else if ( account_name != mAccountName ) - { - LL_WARNS("Voice") << "Mismatched account name! " << account_name - << " instead of " << mAccountName << LL_ENDL; - } - else - { - mAccountPassword = password; - } - - std::string debugSIPURIHostName = gSavedSettings.getString("VivoxDebugSIPURIHostName"); - - if( !debugSIPURIHostName.empty() ) - { - LL_INFOS("Voice") << "Overriding account server based on VivoxDebugSIPURIHostName: " - << debugSIPURIHostName << LL_ENDL; - mVoiceSIPURIHostName = debugSIPURIHostName; - } - - if( mVoiceSIPURIHostName.empty() ) - { - // we have an empty account server name - // so we fall back to hardcoded defaults - - if(LLGridManager::getInstance()->isInProductionGrid()) - { - // Use the release account server - mVoiceSIPURIHostName = "bhr.vivox.com"; - } - else - { - // Use the development account server - mVoiceSIPURIHostName = "bhd.vivox.com"; - } - LL_INFOS("Voice") << "Defaulting SIP URI host: " - << mVoiceSIPURIHostName << LL_ENDL; - - } - - std::string debugAccountServerURI = gSavedSettings.getString("VivoxDebugVoiceAccountServerURI"); - - if( !debugAccountServerURI.empty() ) - { - LL_INFOS("Voice") << "Overriding account server based on VivoxDebugVoiceAccountServerURI: " - << debugAccountServerURI << LL_ENDL; - mVoiceAccountServerURI = debugAccountServerURI; - } - - if( mVoiceAccountServerURI.empty() ) - { - // If the account server URI isn't specified, construct it from the SIP URI hostname - mVoiceAccountServerURI = "https://www." + mVoiceSIPURIHostName + "/api2/"; - LL_INFOS("Voice") << "Inferring account server based on SIP URI Host name: " - << mVoiceAccountServerURI << LL_ENDL; - } -} - -void LLVivoxVoiceClient::idle(void* user_data) -{ -} - -//========================================================================= -// the following are methods to support the coroutine implementation of the -// voice connection and processing. They should only be called in the context -// of a coroutine. -// -// - -typedef enum e_voice_control_coro_state -{ - VOICE_STATE_ERROR = -1, - VOICE_STATE_DONE = 0, - VOICE_STATE_TP_WAIT, // entry point - VOICE_STATE_START_DAEMON, - VOICE_STATE_PROVISION_ACCOUNT, - VOICE_STATE_START_SESSION, - VOICE_STATE_SESSION_RETRY, - VOICE_STATE_SESSION_ESTABLISHED, - VOICE_STATE_WAIT_FOR_CHANNEL, - VOICE_STATE_DISCONNECT, - VOICE_STATE_WAIT_FOR_EXIT, -} EVoiceControlCoroState; - -void LLVivoxVoiceClient::voiceControlCoro() -{ - int state = 0; - try - { - // state is passed as a reference instead of being - // a member due to unresolved issues with coroutine - // surviving longer than LLVivoxVoiceClient - voiceControlStateMachine(state); - } - catch (const LLCoros::Stop&) - { - LL_DEBUGS("LLVivoxVoiceClient") << "Received a shutdown exception" << LL_ENDL; - } - catch (const LLContinueError&) - { - LOG_UNHANDLED_EXCEPTION("LLVivoxVoiceClient"); - } - catch (...) - { - // Ideally for Windows need to log SEH exception instead or to set SEH - // handlers but bugsplat shows local variables for windows, which should - // be enough - LL_WARNS("Voice") << "voiceControlStateMachine crashed in state " << state << LL_ENDL; - throw; - } -} - -void LLVivoxVoiceClient::voiceControlStateMachine(S32 &coro_state) -{ - if (sShuttingDown) - { - return; - } - - LL_DEBUGS("Voice") << "starting" << LL_ENDL; - mIsCoroutineActive = true; - LLCoros::set_consuming(true); - - U32 retry = 0; - - coro_state = VOICE_STATE_TP_WAIT; - - do - { - if (sShuttingDown) - { - // Vivox singleton performed the exit, logged out, - // cleaned sockets, gateway and no longer cares - // about state of coroutine, so just stop - return; - } - - switch (coro_state) - { - case VOICE_STATE_TP_WAIT: - // starting point for voice - if (gAgent.getTeleportState() != LLAgent::TELEPORT_NONE) - { - LL_DEBUGS("Voice") << "Suspending voiceControlCoro() momentarily for teleport. Tuning: " << mTuningMode << ". Relog: " << mRelogRequested << LL_ENDL; - llcoro::suspendUntilTimeout(1.0); - } - else - { - coro_state = VOICE_STATE_START_DAEMON; - } - break; - - case VOICE_STATE_START_DAEMON: - LL_DEBUGS("Voice") << "Launching daemon" << LL_ENDL; - LLVoiceVivoxStats::getInstance()->reset(); - if (startAndLaunchDaemon()) - { - coro_state = VOICE_STATE_PROVISION_ACCOUNT; - } - else - { - coro_state = VOICE_STATE_SESSION_RETRY; - } - break; - - case VOICE_STATE_PROVISION_ACCOUNT: - if (provisionVoiceAccount()) - { - coro_state = VOICE_STATE_START_SESSION; - } - else - { - coro_state = VOICE_STATE_SESSION_RETRY; - } - break; - - case VOICE_STATE_START_SESSION: - if (establishVoiceConnection()) - { - coro_state = VOICE_STATE_SESSION_ESTABLISHED; - } - else - { - coro_state = VOICE_STATE_SESSION_RETRY; - } - break; - - case VOICE_STATE_SESSION_RETRY: - giveUp(); // cleans sockets and session - if (mRelogRequested) - { - // We failed to connect, give it a bit time before retrying. - retry++; - F32 full_delay = llmin(5.f * (F32)retry, 60.f); - F32 current_delay = 0.f; - LL_INFOS("Voice") << "Voice failed to establish session after " << retry - << " tries. Will attempt to reconnect in " << full_delay - << " seconds" << LL_ENDL; - while (current_delay < full_delay && !sShuttingDown) - { - // Assuming that a second has passed is not accurate, - // but we don't need accurancy here, just to make sure - // that some time passed and not to outlive voice itself - current_delay++; - llcoro::suspendUntilTimeout(1.f); - } - coro_state = VOICE_STATE_WAIT_FOR_EXIT; - } - else - { - coro_state = VOICE_STATE_DONE; - } - break; - - case VOICE_STATE_SESSION_ESTABLISHED: - { - // enable/disable the automatic VAD and explicitly set the initial values of - // the VAD variables ourselves when it is off - see SL-15072 for more details - // note: we set the other parameters too even if the auto VAD is on which is ok - unsigned int vad_auto = gSavedSettings.getU32("VivoxVadAuto"); - unsigned int vad_hangover = gSavedSettings.getU32("VivoxVadHangover"); - unsigned int vad_noise_floor = gSavedSettings.getU32("VivoxVadNoiseFloor"); - unsigned int vad_sensitivity = gSavedSettings.getU32("VivoxVadSensitivity"); - setupVADParams(vad_auto, vad_hangover, vad_noise_floor, vad_sensitivity); - - // watch for changes to the VAD settings via Debug Settings UI and act on them accordingly - gSavedSettings.getControl("VivoxVadAuto")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - gSavedSettings.getControl("VivoxVadHangover")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - gSavedSettings.getControl("VivoxVadNoiseFloor")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - gSavedSettings.getControl("VivoxVadSensitivity")->getSignal()->connect(boost::bind(&LLVivoxVoiceClient::onVADSettingsChange, this)); - - if (mTuningMode) - { - performMicTuning(); - } - - coro_state = VOICE_STATE_WAIT_FOR_CHANNEL; - } - break; - - case VOICE_STATE_WAIT_FOR_CHANNEL: - waitForChannel(); // todo: split into more states like login/fonts - coro_state = VOICE_STATE_DISCONNECT; - break; - - case VOICE_STATE_DISCONNECT: - LL_DEBUGS("Voice") << "lost channel RelogRequested=" << mRelogRequested << LL_ENDL; - endAndDisconnectSession(); - retry = 0; // Connected without issues - coro_state = VOICE_STATE_WAIT_FOR_EXIT; - break; - - case VOICE_STATE_WAIT_FOR_EXIT: - if (isGatewayRunning()) - { - LL_INFOS("Voice") << "waiting for SLVoice to exit" << LL_ENDL; - llcoro::suspendUntilTimeout(1.0); - } - else if (mRelogRequested && mVoiceEnabled) - { - LL_INFOS("Voice") << "will attempt to reconnect to voice" << LL_ENDL; - coro_state = VOICE_STATE_TP_WAIT; - } - else - { - coro_state = VOICE_STATE_DONE; - } - break; - - case VOICE_STATE_DONE: - break; - } - } while (coro_state > 0); - - if (sShuttingDown) - { - // LLVivoxVoiceClient might be already dead - return; - } - - mIsCoroutineActive = false; - LL_INFOS("Voice") << "exiting" << LL_ENDL; -} - -bool LLVivoxVoiceClient::endAndDisconnectSession() -{ - LL_DEBUGS("Voice") << LL_ENDL; - - breakVoiceConnection(true); - - killGateway(); - - return true; -} - -bool LLVivoxVoiceClient::callbackEndDaemon(const LLSD& data) -{ - if (!sShuttingDown && mVoiceEnabled) - { - LL_WARNS("Voice") << "SLVoice terminated " << ll_stream_notation_sd(data) << LL_ENDL; - terminateAudioSession(false); - closeSocket(); - cleanUp(); - LLVoiceClient::getInstance()->setUserPTTState(false); - gAgent.setVoiceConnected(false); - mRelogRequested = true; - } - sGatewayPump.stopListening("VivoxDaemonPump"); - return false; -} - -bool LLVivoxVoiceClient::startAndLaunchDaemon() -{ - //--------------------------------------------------------------------- - if (!LLVoiceClient::getInstance()->voiceEnabled()) - { - // Voice is locked out, we must not launch the vivox daemon. - LL_WARNS("Voice") << "voice disabled; not starting daemon" << LL_ENDL; - return false; - } - - if (!isGatewayRunning()) - { -#ifndef VIVOXDAEMON_REMOTEHOST - // Launch the voice daemon -#if LL_WINDOWS - // On windows use exe (not work or RO) directory - std::string exe_path = gDirUtilp->getExecutableDir(); - gDirUtilp->append(exe_path, "SLVoice.exe"); -#elif LL_DARWIN - // On MAC use resource directory - std::string exe_path = gDirUtilp->getAppRODataDir(); - gDirUtilp->append(exe_path, "SLVoice"); -#else - std::string exe_path = gDirUtilp->getExecutableDir(); - gDirUtilp->append(exe_path, "SLVoice"); -#endif - // See if the vivox executable exists - llstat s; - if (!LLFile::stat(exe_path, &s)) - { - // vivox executable exists. Build the command line and launch the daemon. - LLProcess::Params params; - params.executable = exe_path; - - // VOICE-88: Cycle through [portbase..portbase+portrange) on - // successive tries because attempting to relaunch (after manually - // disabling and then re-enabling voice) with the same port can - // cause SLVoice's bind() call to fail with EADDRINUSE. We expect - // that eventually the OS will time out previous ports, which is - // why we cycle instead of incrementing indefinitely. - - static LLCachedControl portbase(gSavedSettings, "VivoxVoicePort"); - static LLCachedControl host(gSavedSettings, "VivoxVoiceHost"); - static LLCachedControl loglevel(gSavedSettings, "VivoxDebugLevel"); - static LLCachedControl log_folder(gSavedSettings, "VivoxLogDirectory"); - static LLCachedControl shutdown_timeout(gSavedSettings, "VivoxShutdownTimeout"); - static const U32 portrange = 100; - static U32 portoffset = 0; - U32 port = 0; - - if (LLAppViewer::instance()->isSecondInstance()) - { - // Ideally need to know amount of instances and - // to increment instance_offset on EADDRINUSE. - // But for now just use rand - static U32 instance_offset = portrange * ll_rand(20); - port = portbase + portoffset + instance_offset; - } - else - { - // leave main thread with exclusive port set - port = portbase + portoffset; - } - portoffset = (portoffset + 1) % portrange; - params.args.add("-i"); - params.args.add(STRINGIZE(host() << ':' << port)); - - params.args.add("-ll"); - if (loglevel().empty()) - { - params.args.add("0"); - } - else - { - params.args.add(loglevel); - } - - params.args.add("-lf"); - if (log_folder().empty()) - { - params.args.add(gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "")); - } - else - { - params.args.add(log_folder); - } - - // set log file basename and .log - params.args.add("-lp"); - params.args.add("SLVoice"); - params.args.add("-ls"); - params.args.add(".log"); - - // rotate any existing log - std::string new_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SLVoice.log"); - std::string old_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SLVoice.old"); - if (gDirUtilp->fileExists(new_log)) - { - LLFile::rename(new_log, old_log); - } - - if (!shutdown_timeout().empty()) - { - params.args.add("-st"); - params.args.add(shutdown_timeout); - } - params.cwd = gDirUtilp->getAppRODataDir(); - -# ifdef VIVOX_HANDLE_ARGS - params.args.add("-ah"); - params.args.add(LLVivoxSecurity::getInstance()->accountHandle()); - - params.args.add("-ch"); - params.args.add(LLVivoxSecurity::getInstance()->connectorHandle()); -# endif // VIVOX_HANDLE_ARGS - - params.postend = sGatewayPump.getName(); - sGatewayPump.listen("VivoxDaemonPump", boost::bind(&LLVivoxVoiceClient::callbackEndDaemon, this, _1)); - - LL_INFOS("Voice") << "Launching SLVoice" << LL_ENDL; - LL_DEBUGS("Voice") << "SLVoice params " << params << LL_ENDL; - - sGatewayPtr = LLProcess::create(params); - - mDaemonHost = LLHost(host().c_str(), port); - } - else - { - LL_WARNS("Voice") << exe_path << " not found." << LL_ENDL; - return false; - } -#else - // SLIM SDK: port changed from 44124 to 44125. - // We can connect to a client gateway running on another host. This is useful for testing. - // To do this, launch the gateway on a nearby host like this: - // vivox-gw.exe -p tcp -i 0.0.0.0:44125 - // and put that host's IP address here. - mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost"), gSavedSettings.getU32("VivoxVoicePort")); -#endif - - // Dirty the states we'll need to sync with the daemon when it comes up. - mMuteMicDirty = true; - mMicVolumeDirty = true; - mSpeakerVolumeDirty = true; - mSpeakerMuteDirty = true; - // These only need to be set if they're not default (i.e. empty string). - mCaptureDeviceDirty = !mCaptureDevice.empty(); - mRenderDeviceDirty = !mRenderDevice.empty(); - - mMainSessionGroupHandle.clear(); - } - else - { - LL_DEBUGS("Voice") << " gateway running; not attempting to start" << LL_ENDL; - } - - //--------------------------------------------------------------------- - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - - LL_DEBUGS("Voice") << "Connecting to vivox daemon:" << mDaemonHost << LL_ENDL; - - int retryCount(0); - LLVoiceVivoxStats::getInstance()->reset(); - while (!sConnected && !sShuttingDown && retryCount++ <= DAEMON_CONNECT_RETRY_MAX) - { - LLVoiceVivoxStats::getInstance()->connectionAttemptStart(); - LL_DEBUGS("Voice") << "Attempting to connect to vivox daemon: " << mDaemonHost << LL_ENDL; - closeSocket(); - if (!mSocket) - { - mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); - } - - sConnected = mSocket->blockingConnect(mDaemonHost); - LLVoiceVivoxStats::getInstance()->connectionAttemptEnd(sConnected); - if (!sConnected) - { - llcoro::suspendUntilTimeout(DAEMON_CONNECT_THROTTLE_SECONDS); - } - } - - //--------------------------------------------------------------------- - if (sShuttingDown && !sConnected) - { - return false; - } - - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - - while (!sPump && !sShuttingDown) - { // Can't use the pump until we have it available. - llcoro::suspendUntilNextFrame(); - } - - if (sShuttingDown) - { - return false; - } - - // MBW -- Note to self: pumps and pipes examples in - // indra/test/io.cpp - // indra/test/llpipeutil.{cpp|h} - - // Attach the pumps and pipes - - LLPumpIO::chain_t readChain; - - readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket))); - readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser())); - - - sPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS); - - - //--------------------------------------------------------------------- - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - - // Initial devices query - getCaptureDevicesSendMessage(); - getRenderDevicesSendMessage(); - - mLoginRetryCount = 0; - - return true; -} - -bool LLVivoxVoiceClient::provisionVoiceAccount() -{ - LL_INFOS("Voice") << "Provisioning voice account." << LL_ENDL; - - while ((!gAgent.getRegion() || !gAgent.getRegion()->capabilitiesReceived()) && !sShuttingDown) - { - LL_DEBUGS("Voice") << "no capabilities for voice provisioning; waiting " << LL_ENDL; - // *TODO* Pump a message for wake up. - llcoro::suspend(); - } - - if (sShuttingDown) - { - return false; - } - - std::string url = gAgent.getRegionCapability("ProvisionVoiceAccountRequest"); - - LL_DEBUGS("Voice") << "region ready for voice provisioning; url=" << url << LL_ENDL; - - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter = std::make_shared("voiceAccountProvision", httpPolicy); - LLCore::HttpRequest::ptr_t httpRequest = std::make_shared(); - LLCore::HttpOptions::ptr_t httpOpts = std::make_shared(); - int retryCount(0); - - LLSD result; - bool provisioned = false; - do - { - LLVoiceVivoxStats::getInstance()->provisionAttemptStart(); - LLSD body; - body["voice_server_type"] = "vivox"; - result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOpts); - - if (sShuttingDown) - { - return false; - } - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (status == LLCore::HttpStatus(404)) - { - F32 timeout = pow(PROVISION_RETRY_TIMEOUT, static_cast(retryCount)); - LL_WARNS("Voice") << "Provision CAP 404. Retrying in " << timeout << " seconds. Retries: " << (S32)retryCount << LL_ENDL; - llcoro::suspendUntilTimeout(timeout); - - if (sShuttingDown) - { - return false; - } - } - else if (!status) - { - LL_WARNS("Voice") << "Unable to provision voice account." << LL_ENDL; - LLVoiceVivoxStats::getInstance()->provisionAttemptEnd(false); - return false; - } - else - { - provisioned = true; - } - } while (!provisioned && ++retryCount <= PROVISION_RETRY_MAX && !sShuttingDown); - - if (sShuttingDown && !provisioned) - { - return false; - } - - LLVoiceVivoxStats::getInstance()->provisionAttemptEnd(provisioned); - if (!provisioned) - { - LL_WARNS("Voice") << "Could not access voice provision cap after " << retryCount << " attempts." << LL_ENDL; - return false; - } - LL_DEBUGS("Voice") << "Voice Provision Result." << result << LL_ENDL; - std::string voiceSipUriHostname; - std::string voiceAccountServerUri; - std::string voiceUserName = result["username"].asString(); - std::string voicePassword = result["password"].asString(); - - if (result.has("voice_sip_uri_hostname")) - { - voiceSipUriHostname = result["voice_sip_uri_hostname"].asString(); - } - - // this key is actually misnamed -- it will be an entire URI, not just a hostname. - if (result.has("voice_account_server_name")) - { - voiceAccountServerUri = result["voice_account_server_name"].asString(); - } - - LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response" - << " user " << (voiceUserName.empty() ? "not set" : "set") - << " password " << (voicePassword.empty() ? "not set" : "set") - << " sip uri " << voiceSipUriHostname - << " account uri " << voiceAccountServerUri - << LL_ENDL; - - setLoginInfo(voiceUserName, voicePassword, voiceSipUriHostname, voiceAccountServerUri); - - return true; -} - -bool LLVivoxVoiceClient::establishVoiceConnection() -{ - if (!mVoiceEnabled && mIsInitialized) - { - LL_WARNS("Voice") << "cannot establish connection; enabled "<establishAttemptStart(); - connectorCreate(); - do - { - result = llcoro::suspendUntilEventOn(mVivoxPump); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - - if (result.has("connector")) - { - connected = LLSD::Boolean(result["connector"]); - LLVoiceVivoxStats::getInstance()->establishAttemptEnd(connected); - if (!connected) - { - if (result.has("retry") && ++retries <= CONNECT_RETRY_MAX && !sShuttingDown) - { - F32 timeout = (F32)LLSD::Real(result["retry"]); - timeout *= retries; - LL_INFOS("Voice") << "Retry connection to voice service in " << timeout << " seconds" << LL_ENDL; - llcoro::suspendUntilTimeout(timeout); - - if (mVoiceEnabled) // user may have switched it off - { - // try again - LLVoiceVivoxStats::getInstance()->establishAttemptStart(); - connectorCreate(); - } - else - { - // stop if they've turned off voice - giving_up = true; - } - } - else - { - giving_up=true; - } - } - } - LL_DEBUGS("Voice") << (connected ? "" : "not ") << "connected, " - << (giving_up ? "" : "not ") << "giving up" - << LL_ENDL; - } while (!connected && !giving_up && !sShuttingDown); - - if (giving_up) - { - LLSD args; - args["HOSTID"] = LLURI(mVoiceAccountServerURI).authority(); - LLNotificationsUtil::add("NoVoiceConnect", args); - } - - return connected; -} - -bool LLVivoxVoiceClient::breakVoiceConnection(bool corowait) -{ - LL_DEBUGS("Voice") << "( wait=" << corowait << ")" << LL_ENDL; - bool retval(true); - - mShutdownComplete = false; - connectorShutdown(); - - if (corowait) - { - LLSD timeoutResult(LLSDMap("connector", "timeout")); - - LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - - retval = result.has("connector"); - } - else - { - mRelogRequested = false; //stop the control coro - // If we are not doing a corowait then we must sleep until the connector has responded - // otherwise we may very well close the socket too early. -#if LL_WINDOWS - if (!mShutdownComplete) - { - // The situation that brings us here is a call from ::terminate() - // At this point message system is already down so we can't wait for - // the message, yet we need to receive "connector shutdown response". - // Either wait a bit and emulate it or check gMessageSystem for specific message - _sleep(1000); - if (sConnected) - { - sConnected = false; - LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false))); - mVivoxPump.post(vivoxevent); - } - mShutdownComplete = true; - } -#endif - } - - LL_DEBUGS("Voice") << "closing SLVoice socket" << LL_ENDL; - closeSocket(); // Need to do this now -- bad things happen if the destructor does it later. - cleanUp(); - sConnected = false; - - return retval; -} - -bool LLVivoxVoiceClient::loginToVivox() -{ - LLSD timeoutResult(LLSDMap("login", "timeout")); - - int loginRetryCount(0); - - bool response_ok(false); - bool account_login(false); - bool send_login(true); - - do - { - mIsLoggingIn = true; - if (send_login) - { - loginSendMessage(); - send_login = false; - } - - LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGIN_ATTEMPT_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - return false; - } - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - - if (result.has("login")) - { - std::string loginresp = result["login"]; - - if (((loginresp == "retry") || (loginresp == "timeout")) && !sShuttingDown) - { - LL_WARNS("Voice") << "login failed with status '" << loginresp << "' " - << " count " << loginRetryCount << "/" << LOGIN_RETRY_MAX - << LL_ENDL; - if (++loginRetryCount > LOGIN_RETRY_MAX) - { - // We've run out of retries - tell the user - LL_WARNS("Voice") << "too many login retries (" << loginRetryCount << "); giving up." << LL_ENDL; - LLSD args; - args["HOSTID"] = LLURI(mVoiceAccountServerURI).authority(); - mTerminateDaemon = true; - LLNotificationsUtil::add("NoVoiceConnect", args); - - mIsLoggingIn = false; - return false; - } - response_ok = false; - account_login = false; - send_login = true; - - // an exponential backoff gets too long too quickly; stretch it out, but not too much - F32 timeout = loginRetryCount * LOGIN_ATTEMPT_TIMEOUT; - - // tell the user there is a problem - LL_WARNS("Voice") << "login " << loginresp << " will retry login in " << timeout << " seconds." << LL_ENDL; - - if (!sShuttingDown) - { - // Todo: this is way to long, viewer can get stuck waiting during shutdown - // either make it listen to pump or split in smaller waits with checks for shutdown - llcoro::suspendUntilTimeout(timeout); - } - } - else if (loginresp == "failed") - { - mIsLoggingIn = false; - return false; - } - else if (loginresp == "response_ok") - { - response_ok = true; - } - else if (loginresp == "account_login") - { - account_login = true; - } - else if (sShuttingDown) - { - mIsLoggingIn = false; - return false; - } - } - - } while ((!response_ok || !account_login) && !sShuttingDown); - - if (sShuttingDown) - { - return false; - } - - mRelogRequested = false; - mIsLoggedIn = true; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); - - // Set up the mute list observer if it hasn't been set up already. - if ((!sMuteListListener_listening)) - { - LLMuteList::getInstance()->addObserver(&mutelist_listener); - sMuteListListener_listening = true; - } - - // Set the initial state of mic mute, local speaker volume, etc. - sendLocalAudioUpdates(); - mIsLoggingIn = false; - - return true; -} - -void LLVivoxVoiceClient::logoutOfVivox(bool wait) -{ - if (mIsLoggedIn) - { - // Ensure that we'll re-request provisioning before logging in again - mAccountPassword.clear(); - mVoiceAccountServerURI.clear(); - - logoutSendMessage(); - - if (wait) - { - LLSD timeoutResult(LLSDMap("logout", "timeout")); - LLSD result; - - do - { - LL_DEBUGS("Voice") - << "waiting for logout response on " - << mVivoxPump.getName() - << LL_ENDL; - - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - break; - } - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - // Don't get confused by prior queued events -- note that it's - // very important that mVivoxPump is an LLEventMailDrop, which - // does queue events. - } while (! result["logout"]); - } - else - { - LL_DEBUGS("Voice") << "not waiting for logout" << LL_ENDL; - } - - mIsLoggedIn = false; - } -} - - -bool LLVivoxVoiceClient::retrieveVoiceFonts() -{ - // Request the set of available voice fonts. - refreshVoiceEffectLists(true); - - mIsWaitingForFonts = true; - LLSD result; - do - { - result = llcoro::suspendUntilEventOn(mVivoxPump); - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - if (result.has("voice_fonts")) - break; - } while (true); - mIsWaitingForFonts = false; - - mVoiceFontExpiryTimer.start(); - mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); - - return result["voice_fonts"].asBoolean(); -} - -bool LLVivoxVoiceClient::requestParcelVoiceInfo() -{ - //_INFOS("Voice") << "Requesting voice info for Parcel" << LL_ENDL; - - LLViewerRegion * region = gAgent.getRegion(); - if (region == NULL || !region->capabilitiesReceived()) - { - LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not yet available, deferring" << LL_ENDL; - return false; - } - - // grab the cap. - std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest"); - if (url.empty()) - { - // Region dosn't have the cap. Stop probing. - LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest capability not available in this region" << LL_ENDL; - return false; - } - - // update the parcel - checkParcelChanged(true); - - LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL; - - LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); - LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter = std::make_shared("parcelVoiceInfoRequest", httpPolicy); - LLCore::HttpRequest::ptr_t httpRequest = std::make_shared(); - - LLSD result = httpAdapter->postAndSuspend(httpRequest, url, LLSD()); - - if (sShuttingDown) - { - return false; - } - - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) - { - // if a terminate request has been received, - // bail and go to the stateSessionTerminated - // state. If the cap request is still pending, - // the responder will check to see if we've moved - // to a new session and won't change any state. - LL_DEBUGS("Voice") << "terminate requested " << mSessionTerminateRequested - << " enabled " << mVoiceEnabled - << " initialized " << mIsInitialized - << LL_ENDL; - terminateAudioSession(true); - return false; - } - - if ((!status) || (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized))) - { - if (mSessionTerminateRequested || (!mVoiceEnabled && mIsInitialized)) - { - LL_WARNS("Voice") << "Session terminated." << LL_ENDL; - } - - LL_WARNS("Voice") << "No voice on parcel" << LL_ENDL; - sessionTerminate(); - return false; - } - - if (!result.has("voice_credentials")) - { - if (LLViewerParcelMgr::getInstance()->allowAgentVoice()) - { - LL_WARNS("Voice") << "No voice credentials" << LL_ENDL; - } - else - { - LL_DEBUGS("Voice") << "No voice credentials" << LL_ENDL; - } - } - - // set the spatial channel. If no voice credentials or uri are - // available, then we simply drop out of voice spatially. - return setSpatialChannel(result["voice_credentials"]); -} - -bool LLVivoxVoiceClient::addAndJoinSession(const sessionStatePtr_t &nextSession) -{ - mIsJoiningSession = true; - - sessionStatePtr_t oldSession = mAudioSession; - - LL_INFOS("Voice") << "Adding or joining voice session " << nextSession->mHandle << LL_ENDL; - - mAudioSession = nextSession; - if (!mAudioSession || !mAudioSession->mReconnect) - { - mNextAudioSession.reset(); - } - - // The old session may now need to be deleted. - reapSession(oldSession); - - if (mAudioSession) - { - if (!mAudioSession->mHandle.empty()) - { - // Connect to a session by session handle - - sessionMediaConnectSendMessage(mAudioSession); - } - else - { - // Connect to a session by URI - sessionCreateSendMessage(mAudioSession, true, false); - } - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); - - llcoro::suspend(); - - if (sShuttingDown) - { - return false; - } - - LLSD result; - - if (mSpatialJoiningNum == MAX_NORMAL_JOINING_SPATIAL_NUM) - { - // Notify observers to let them know there is problem with voice - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - LL_WARNS() << "There seems to be problem with connection to voice server. Disabling voice chat abilities." << LL_ENDL; - } - - // Increase mSpatialJoiningNum only for spatial sessions- it's normal to reach this case for - // example for p2p many times while waiting for response, so it can't be used to detect errors - if (mAudioSession && mAudioSession->mIsSpatial) - { - mSpatialJoiningNum++; - } - - if (!mVoiceEnabled && mIsInitialized) - { - LL_DEBUGS("Voice") << "Voice no longer enabled. Exiting" - << " enabled " << mVoiceEnabled - << " initialized " << mIsInitialized - << LL_ENDL; - mIsJoiningSession = false; - // User bailed out during connect -- jump straight to teardown. - terminateAudioSession(true); - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - return false; - } - else if (mSessionTerminateRequested) - { - LL_DEBUGS("Voice") << "Terminate requested" << LL_ENDL; - if (mAudioSession && !mAudioSession->mHandle.empty()) - { - // Only allow direct exits from this state in p2p calls (for cancelling an invite). - // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. - if (mAudioSession->mIsP2P) - { - terminateAudioSession(true); - mIsJoiningSession = false; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - return false; - } - } - } - - bool added(true); - bool joined(false); - - LLSD timeoutResult(LLSDMap("session", "timeout")); - - // We are about to start a whole new session. Anything that MIGHT still be in our - // maildrop is going to be stale and cause us much wailing and gnashing of teeth. - // Just flush it all out and start new. - mVivoxPump.discard(); - - // It appears that I need to wait for BOTH the SessionGroup.AddSession response and the SessionStateChangeEvent with state 4 - // before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck. - // For now, the SessionGroup.AddSession response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined. - // This is a cheap way to make sure both have happened before proceeding. - do - { - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, SESSION_JOIN_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - return false; - } - - LL_INFOS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - if (result.has("session")) - { - if (!mAudioSession) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while session is not initialized." << LL_ENDL; - continue; - } - if (result.has("handle") && result["handle"] != mAudioSession->mHandle) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; - continue; - } - - std::string message = result["session"].asString(); - - if ((message == "added") || (message == "created")) - { - added = true; - } - else if (message == "joined") - { - joined = true; - } - else if ((message == "failed") || (message == "removed") || (message == "timeout")) - { // we will get a removed message if a voice call is declined. - LL_INFOS("Voice") << "Result:" << result << LL_ENDL; - if (message == "failed") - { - int reason = result["reason"].asInteger(); - LL_WARNS("Voice") << "Add and join failed for reason " << reason << LL_ENDL; - - if ( (reason == ERROR_VIVOX_NOT_LOGGED_IN) - || (reason == ERROR_VIVOX_OBJECT_NOT_FOUND)) - { - LL_DEBUGS("Voice") << "Requesting reprovision and login." << LL_ENDL; - requestRelog(); - } - } - else - { - LL_WARNS("Voice") << "session '" << message << "' " - << LL_ENDL; - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - mIsJoiningSession = false; - return false; - } - } - } while (!added || !joined); - - mIsJoiningSession = false; - - if (mSpatialJoiningNum > 100) - { - LL_WARNS("Voice") << "There seems to be problem with connecting to a voice channel. Frames to join were " << mSpatialJoiningNum << LL_ENDL; - } - - mSpatialJoiningNum = 0; - - // Events that need to happen when a session is joined could go here. - // send an initial positional information immediately upon joining. - // - // do an initial update for position and the camera position, then send a - // positional update. - updatePosition(); - enforceTether(); - - // Dirty state that may need to be sync'ed with the daemon. - mMuteMicDirty = true; - mSpeakerVolumeDirty = true; - mSpatialCoordsDirty = true; - - sendPositionAndVolumeUpdate(); - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); - - return true; -} - -bool LLVivoxVoiceClient::terminateAudioSession(bool wait) -{ - - if (mAudioSession) - { - LL_INFOS("Voice") << "terminateAudioSession(" << wait << ") Terminating current voice session " << mAudioSession->mHandle << LL_ENDL; - - if (mIsLoggedIn) - { - if (!mAudioSession->mHandle.empty()) - { - -#if RECORD_EVERYTHING - // HACK: for testing only - // Save looped recording - std::string savepath("/tmp/vivoxrecording"); - { - time_t now = time(NULL); - const size_t BUF_SIZE = 64; - char time_str[BUF_SIZE]; /* Flawfinder: ignore */ - - strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); - savepath += time_str; - } - recordingLoopSave(savepath); -#endif - - sessionMediaDisconnectSendMessage(mAudioSession); - - if (wait) - { - LLSD result; - do - { - LLSD timeoutResult(LLSDMap("session", "timeout")); - - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, LOGOUT_ATTEMPT_TIMEOUT, timeoutResult); - - if (sShuttingDown) - { - return false; - } - - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - if (result.has("session")) - { - if (result.has("handle")) - { - if (result["handle"] != mAudioSession->mHandle) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; - continue; - } - } - - std::string message = result["session"].asString(); - if (message == "removed" || message == "timeout") - break; - } - } while (true); - - } - } - else - { - LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "Session " << mAudioSession->mHandle << " already terminated by logout." << LL_ENDL; - } - - sessionStatePtr_t oldSession = mAudioSession; - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); // needs mAudioSession for uri - mAudioSession.reset(); - - // The old session may now need to be deleted. - reapSession(oldSession); - } - else - { - LL_WARNS("Voice") << "terminateAudioSession(" << wait << ") with NULL mAudioSession" << LL_ENDL; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - } - - - // Always reset the terminate request flag when we get here. - // Some slower PCs have a race condition where they can switch to an incoming P2P call faster than the state machine leaves - // the region chat. - mSessionTerminateRequested = false; - - bool status=((mVoiceEnabled || !mIsInitialized) && !mRelogRequested && !sShuttingDown); - LL_DEBUGS("Voice") << "exiting" - << " VoiceEnabled " << mVoiceEnabled - << " IsInitialized " << mIsInitialized - << " RelogRequested " << mRelogRequested - << " ShuttingDown " << (sShuttingDown ? "True" : "False") - << " returning " << status - << LL_ENDL; - return status; -} - - -typedef enum e_voice_wait_for_channel_state -{ - VOICE_CHANNEL_STATE_LOGIN = 0, // entry point - VOICE_CHANNEL_STATE_CHECK_EFFECTS, - VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING, - VOICE_CHANNEL_STATE_PROCESS_CHANNEL, - VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY, - VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK, - VOICE_CHANNEL_STATE_LOGOUT, - VOICE_CHANNEL_STATE_RELOG, - VOICE_CHANNEL_STATE_DONE, -} EVoiceWaitForChannelState; - -bool LLVivoxVoiceClient::waitForChannel() -{ - LL_INFOS("Voice") << "Waiting for channel" << LL_ENDL; - - EVoiceWaitForChannelState state = VOICE_CHANNEL_STATE_LOGIN; - - do - { - if (sShuttingDown) - { - // terminate() forcefully disconects voice, no need for cleanup - return false; - } - - switch (state) - { - case VOICE_CHANNEL_STATE_LOGIN: - if (!loginToVivox()) - { - return false; - } - state = VOICE_CHANNEL_STATE_CHECK_EFFECTS; - break; - - case VOICE_CHANNEL_STATE_CHECK_EFFECTS: - if (LLVoiceClient::instance().getVoiceEffectEnabled()) - { - retrieveVoiceFonts(); - - if (sShuttingDown) - { - return false; - } - - // Request the set of available voice fonts. - refreshVoiceEffectLists(false); - } - -#if USE_SESSION_GROUPS - // Rider: This code is completely unchanged from the original state machine - // It does not seem to be in active use... but I'd rather not rip it out. - // create the main session group - setState(stateCreatingSessionGroup); - sessionGroupCreateSendMessage(); -#endif - - state = VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING; - break; - - case VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING: - llcoro::suspend(); - state = VOICE_CHANNEL_STATE_PROCESS_CHANNEL; - break; - - case VOICE_CHANNEL_STATE_PROCESS_CHANNEL: - if (mTuningMode) - { - performMicTuning(); - } - else if (mCaptureBufferMode) - { - recordingAndPlaybackMode(); - } - else if (mProcessChannels && ((mNextAudioSession == NULL) || checkParcelChanged())) - { - // the parcel is changed, or we have no pending audio sessions, - // so try to request the parcel voice info - // if we have the cap, we move to the appropriate state - requestParcelVoiceInfo(); //suspends for http reply - } - else if (sessionNeedsRelog(mNextAudioSession)) - { - LL_INFOS("Voice") << "Session requesting reprovision and login." << LL_ENDL; - requestRelog(); - break; - } - else if (mNextAudioSession) - { - if (!mNextAudioSession->mIsP2P && !mProcessChannels) - { - llcoro::suspend(); - break; - } - - try - { - sessionStatePtr_t joinSession = mNextAudioSession; - mNextAudioSession.reset(); - mIsProcessingChannels = true; - if (!runSession(joinSession)) //suspends - { - mIsProcessingChannels = false; - LL_DEBUGS("Voice") << "runSession returned false; leaving inner loop" << LL_ENDL; - break; - } - else - { - mIsProcessingChannels = false; - LL_DEBUGS("Voice") - << "runSession returned true to inner loop" - << " RelogRequested=" << mRelogRequested - << " VoiceEnabled=" << mVoiceEnabled - << LL_ENDL; - } - } - catch (const LLCoros::Stop&) - { - LL_DEBUGS("LLVivoxVoiceClient") << "Received a shutdown exception" << LL_ENDL; - } - catch (const LLContinueError&) - { - LOG_UNHANDLED_EXCEPTION("LLVivoxVoiceClient"); - } - catch (...) - { - // Ideally for Windows need to log SEH exception instead or to set SEH - // handlers but bugsplat shows local variables for windows, which should - // be enough - LL_WARNS("Voice") << "voiceControlStateMachine crashed in state VOICE_CHANNEL_STATE_PROCESS_CHANNEL" - << " mRelogRequested " << mRelogRequested - << " mVoiceEnabled " << mVoiceEnabled - << " mIsProcessingChannels " << mIsProcessingChannels - << " mProcessChannels " << mProcessChannels - << LL_ENDL; - throw; - } - } - - state = VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY; - break; - - case VOICE_CHANNEL_STATE_NEXT_CHANNEL_DELAY: - if (!mNextAudioSession) - { - llcoro::suspendUntilTimeout(1.0); - } - state = VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK; - break; - - case VOICE_CHANNEL_STATE_NEXT_CHANNEL_CHECK: - if (mVoiceEnabled && !mRelogRequested) - { - state = VOICE_CHANNEL_STATE_START_CHANNEL_PROCESSING; - break; - } - else - { - mIsProcessingChannels = false; - LL_DEBUGS("Voice") - << "leaving inner waitForChannel loop" - << " RelogRequested=" << mRelogRequested - << " VoiceEnabled=" << mVoiceEnabled - << LL_ENDL; - state = VOICE_CHANNEL_STATE_LOGOUT; - break; - } - - case VOICE_CHANNEL_STATE_LOGOUT: - logoutOfVivox(true /*bool wait*/); - if (mRelogRequested) - { - state = VOICE_CHANNEL_STATE_RELOG; - } - else - { - state = VOICE_CHANNEL_STATE_DONE; - } - break; - - case VOICE_CHANNEL_STATE_RELOG: - LL_DEBUGS("Voice") << "Relog Requested, restarting provisioning" << LL_ENDL; - if (!provisionVoiceAccount()) - { - if (sShuttingDown) - { - return false; - } - LL_WARNS("Voice") << "provisioning voice failed; giving up" << LL_ENDL; - giveUp(); - return false; - } - if (mVoiceEnabled && mRelogRequested && isGatewayRunning()) - { - state = VOICE_CHANNEL_STATE_LOGIN; - } - else - { - state = VOICE_CHANNEL_STATE_DONE; - } - break; - case VOICE_CHANNEL_STATE_DONE: - LL_DEBUGS("Voice") - << "exiting" - << " RelogRequested=" << mRelogRequested - << " VoiceEnabled=" << mVoiceEnabled - << LL_ENDL; - return !sShuttingDown; - } - } while (true); -} - -bool LLVivoxVoiceClient::runSession(const sessionStatePtr_t &session) -{ - LL_INFOS("Voice") << "running new voice session " << session->mHandle << LL_ENDL; - - if (sShuttingDown || !mProcessChannels) - { - return false; - } - - bool joined_session = addAndJoinSession(session); - - if (!joined_session) - { - notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); - - if (mSessionTerminateRequested) - { - LL_DEBUGS("Voice") << "runSession terminate requested " << LL_ENDL; - terminateAudioSession(true); - } - // if a relog has been requested then addAndJoineSession - // failed in a spectacular way and we need to back out. - // If this is not the case then we were simply trying to - // make a call and the other party rejected it. - return !mRelogRequested; - } - - notifyParticipantObservers(); - notifyVoiceFontObservers(); - - LLSD timeoutEvent(LLSDMap("timeout", LLSD::Boolean(true))); - - mIsInChannel = true; - mMuteMicDirty = true; - - while (!sShuttingDown - && mVoiceEnabled - && isGatewayRunning() - && !mSessionTerminateRequested - && !mTuningMode - && mProcessChannels) - { - sendCaptureAndRenderDevices(); // suspends - - if (sShuttingDown) - { - return false; - } - - if (mSessionTerminateRequested) - { - break; - } - - if (mAudioSession && mAudioSession->mParticipantsChanged) - { - mAudioSession->mParticipantsChanged = false; - notifyParticipantObservers(); - } - - if (!inSpatialChannel()) - { - // When in a non-spatial channel, never send positional updates. - mSpatialCoordsDirty = false; - } - else - { - updatePosition(); - - if (checkParcelChanged()) - { - // *RIDER: I think I can just return here if the parcel has changed - // and grab the new voice channel from the outside loop. - // - // if the parcel has changed, attempted to request the - // cap for the parcel voice info. If we can't request it - // then we don't have the cap URL so we do nothing and will - // recheck next time around - if (requestParcelVoiceInfo()) // suspends - { // The parcel voice URI has changed.. break out and reconnect. - break; - } - - if (sShuttingDown) - { - return false; - } - } - // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position) - enforceTether(); - } - sendPositionAndVolumeUpdate(); - - // Do notifications for expiring Voice Fonts. - if (mVoiceFontExpiryTimer.hasExpired()) - { - expireVoiceFonts(); - mVoiceFontExpiryTimer.setTimerExpirySec(VOICE_FONT_EXPIRY_INTERVAL); - } - - // send any requests to adjust mic and speaker settings if they have changed - sendLocalAudioUpdates(); - - mIsInitialized = true; - LLSD result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, UPDATE_THROTTLE_SECONDS, timeoutEvent); - - if (sShuttingDown) - { - return false; - } - - if (!result.has("timeout")) // logging the timeout event spams the log - { - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - } - if (result.has("session")) - { - if (result.has("handle")) - { - if (!mAudioSession) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while session is not initiated." << LL_ENDL; - continue; - } - if (result["handle"] != mAudioSession->mHandle) - { - LL_WARNS("Voice") << "Message for session handle \"" << result["handle"] << "\" while waiting for \"" << mAudioSession->mHandle << "\"." << LL_ENDL; - continue; - } - } - - std::string message = result["session"]; - - if (message == "removed") - { - LL_DEBUGS("Voice") << "session removed" << LL_ENDL; - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - break; - } - } - else if (result.has("login")) - { - std::string message = result["login"]; - if (message == "account_logout") - { - LL_DEBUGS("Voice") << "logged out" << LL_ENDL; - mIsLoggedIn = false; - mRelogRequested = true; - break; - } - } - } - - if (sShuttingDown) - { - return false; - } - - mIsInChannel = false; - LL_DEBUGS("Voice") << "terminating at end of runSession" << LL_ENDL; - terminateAudioSession(true); - - return true; -} - -void LLVivoxVoiceClient::sendCaptureAndRenderDevices() -{ - if (mCaptureDeviceDirty || mRenderDeviceDirty) - { - std::ostringstream stream; - - buildSetCaptureDevice(stream); - buildSetRenderDevice(stream); - - if (!stream.str().empty()) - { - writeString(stream.str()); - } - - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - } -} - -void LLVivoxVoiceClient::recordingAndPlaybackMode() -{ - LL_INFOS("Voice") << "In voice capture/playback mode." << LL_ENDL; - - while (true) - { - LLSD command; - do - { - command = llcoro::suspendUntilEventOn(mVivoxPump); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(command) << LL_ENDL; - } while (!command.has("recplay")); - - if (command["recplay"].asString() == "quit") - { - mCaptureBufferMode = false; - break; - } - else if (command["recplay"].asString() == "record") - { - voiceRecordBuffer(); - } - else if (command["recplay"].asString() == "playback") - { - voicePlaybackBuffer(); - } - } - - LL_INFOS("Voice") << "Leaving capture/playback mode." << LL_ENDL; - mCaptureBufferRecording = false; - mCaptureBufferRecorded = false; - mCaptureBufferPlaying = false; - - return; -} - -int LLVivoxVoiceClient::voiceRecordBuffer() -{ - LLSD timeoutResult(LLSDMap("recplay", "stop")); - - LL_INFOS("Voice") << "Recording voice buffer" << LL_ENDL; - - LLSD result; - - captureBufferRecordStartSendMessage(); - notifyVoiceFontObservers(); - - do - { - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - } while (!result.has("recplay")); - - mCaptureBufferRecorded = true; - - captureBufferRecordStopSendMessage(); - mCaptureBufferRecording = false; - - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - return true; - /*TODO expand return to move directly into play*/ -} - -int LLVivoxVoiceClient::voicePlaybackBuffer() -{ - LLSD timeoutResult(LLSDMap("recplay", "stop")); - - LL_INFOS("Voice") << "Playing voice buffer" << LL_ENDL; - - LLSD result; - - do - { - captureBufferPlayStartSendMessage(mPreviewVoiceFont); - - // Store the voice font being previewed, so that we know to restart if it changes. - mPreviewVoiceFontLast = mPreviewVoiceFont; - - do - { - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - result = llcoro::suspendUntilEventOnWithTimeout(mVivoxPump, CAPTURE_BUFFER_MAX_TIME, timeoutResult); - LL_DEBUGS("Voice") << "event=" << ll_stream_notation_sd(result) << LL_ENDL; - } while (!result.has("recplay")); - - if (result["recplay"] == "playback") - continue; // restart playback... May be a font change. - - break; - } while (true); - - // Stop playing. - captureBufferPlayStopSendMessage(); - mCaptureBufferPlaying = false; - - // Update UI, should really use a separate callback. - notifyVoiceFontObservers(); - - return true; -} - - -bool LLVivoxVoiceClient::performMicTuning() -{ - LL_INFOS("Voice") << "Entering voice tuning mode." << LL_ENDL; - mIsInTuningMode = true; - - while (mTuningMode && !sShuttingDown) - { - llcoro::suspendUntilTimeout(UPDATE_THROTTLE_SECONDS); - } - - mIsInTuningMode = false; - - //--------------------------------------------------------------------- - return true; -} - -//========================================================================= - -void LLVivoxVoiceClient::closeSocket(void) -{ - mSocket.reset(); - sConnected = false; - mConnectorEstablished = false; - mAccountLoggedIn = false; -} - -void LLVivoxVoiceClient::loginSendMessage() -{ - std::ostringstream stream; - - bool autoPostCrashDumps = gSavedSettings.getBOOL("VivoxAutoPostCrashDumps"); - - stream - << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "" << mAccountName << "" - << "" << mAccountPassword << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "VerifyAnswer" - << "false" - << "0" - << "Application" - << "5" - << (autoPostCrashDumps?"true":"") - << "\n\n\n"; - - LL_INFOS("Voice") << "Attempting voice login" << LL_ENDL; - writeString(stream.str()); -} - -void LLVivoxVoiceClient::logout() -{ - // Ensure that we'll re-request provisioning before logging in again - mAccountPassword.clear(); - mVoiceAccountServerURI.clear(); - - logoutSendMessage(); -} - -void LLVivoxVoiceClient::logoutSendMessage() -{ - if(mAccountLoggedIn) - { - LL_INFOS("Voice") << "Attempting voice logout" << LL_ENDL; - std::ostringstream stream; - stream - << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "" - << "\n\n\n"; - - mAccountLoggedIn = false; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::sessionGroupCreateSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "creating session group" << LL_ENDL; - - stream - << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "Normal" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::sessionCreateSendMessage(const sessionStatePtr_t &session, bool startAudio, bool startText) -{ - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI - << " with voice font: " << session->mVoiceFontID << " (" << font_index << ")" - << LL_ENDL; - - session->mCreateInProgress = true; - if(startAudio) - { - session->mMediaConnectInProgress = true; - } - - std::ostringstream stream; - stream - << "mSIPURI << "\" action=\"Session.Create.1\">" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "" << session->mSIPURI << ""; - - static const std::string allowed_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - "0123456789" - "-._~"; - - if(!session->mHash.empty()) - { - stream - << "" << LLURI::escape(session->mHash, allowed_chars) << "" - << "SHA1UserName"; - } - - stream - << "" << (startAudio?"true":"false") << "" - << "" << (startText?"true":"false") << "" - << "" << mChannelName << "" - << "\n\n\n"; - LL_WARNS("Voice") << "Session.Create: " << stream.str() << LL_ENDL; - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(const sessionStatePtr_t &session, bool startAudio, bool startText) -{ - LL_DEBUGS("Voice") << "Requesting create: " << session->mSIPURI << LL_ENDL; - - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("Voice") << "With voice font: " << session->mVoiceFontID << " (" << font_index << ")" << LL_ENDL; - - session->mCreateInProgress = true; - if(startAudio) - { - session->mMediaConnectInProgress = true; - } - - std::string password; - if(!session->mHash.empty()) - { - static const std::string allowed_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - "0123456789" - "-._~" - ; - password = LLURI::escape(session->mHash, allowed_chars); - } - - std::ostringstream stream; - stream - << "mSIPURI << "\" action=\"SessionGroup.AddSession.1\">" - << "" << session->mGroupHandle << "" - << "" << session->mSIPURI << "" - << "" << mChannelName << "" - << "" << (startAudio?"true":"false") << "" - << "" << (startText?"true":"false") << "" - << "" << password << "" - << "SHA1UserName" - << "\n\n\n" - ; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionMediaConnectSendMessage(const sessionStatePtr_t &session) -{ - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("Voice") << "Connecting audio to session handle: " << session->mHandle - << " with voice font: " << session->mVoiceFontID << " (" << font_index << ")" - << LL_ENDL; - - session->mMediaConnectInProgress = true; - - std::ostringstream stream; - - stream - << "mHandle << "\" action=\"Session.MediaConnect.1\">" - << "" << session->mGroupHandle << "" - << "" << session->mHandle << "" - << "Audio" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionTextConnectSendMessage(const sessionStatePtr_t &session) -{ - LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL; - - std::ostringstream stream; - - stream - << "mHandle << "\" action=\"Session.TextConnect.1\">" - << "" << session->mGroupHandle << "" - << "" << session->mHandle << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionTerminate() -{ - mSessionTerminateRequested = true; -} - -void LLVivoxVoiceClient::requestRelog() -{ - mSessionTerminateRequested = true; - mRelogRequested = true; -} - - -void LLVivoxVoiceClient::leaveAudioSession() -{ - if(mAudioSession) - { - LL_DEBUGS("Voice") << "leaving session: " << mAudioSession->mSIPURI << LL_ENDL; - - if(!mAudioSession->mHandle.empty()) - { - -#if RECORD_EVERYTHING - // HACK: for testing only - // Save looped recording - std::string savepath("/tmp/vivoxrecording"); - { - time_t now = time(NULL); - const size_t BUF_SIZE = 64; - char time_str[BUF_SIZE]; /* Flawfinder: ignore */ - - strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); - savepath += time_str; - } - recordingLoopSave(savepath); -#endif - - sessionMediaDisconnectSendMessage(mAudioSession); - } - else - { - LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "called with no active session" << LL_ENDL; - } - sessionTerminate(); -} - -void LLVivoxVoiceClient::sessionTerminateSendMessage(const sessionStatePtr_t &session) -{ - std::ostringstream stream; - - sessionGroupTerminateSendMessage(session); - return; - /* - LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL; - stream - << "" - << "" << session->mHandle << "" - << "\n\n\n"; - - writeString(stream.str()); - */ -} - -void LLVivoxVoiceClient::sessionGroupTerminateSendMessage(const sessionStatePtr_t &session) -{ - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Sending SessionGroup.Terminate with handle " << session->mGroupHandle << LL_ENDL; - stream - << "" - << "" << session->mGroupHandle << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::sessionMediaDisconnectSendMessage(const sessionStatePtr_t &session) -{ - std::ostringstream stream; - sessionGroupTerminateSendMessage(session); - return; - /* - LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL; - stream - << "" - << "" << session->mGroupHandle << "" - << "" << session->mHandle << "" - << "Audio" - << "\n\n\n"; - - writeString(stream.str()); - */ - -} - - -void LLVivoxVoiceClient::getCaptureDevicesSendMessage() -{ - std::ostringstream stream; - stream - << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::getRenderDevicesSendMessage() -{ - std::ostringstream stream; - stream - << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::clearCaptureDevices() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - mCaptureDevices.clear(); -} - -void LLVivoxVoiceClient::addCaptureDevice(const LLVoiceDevice& device) -{ - LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; - mCaptureDevices.push_back(device); -} - -LLVoiceDeviceList& LLVivoxVoiceClient::getCaptureDevices() -{ - return mCaptureDevices; -} - -void LLVivoxVoiceClient::setCaptureDevice(const std::string& name) -{ - if(name == "Default") - { - if(!mCaptureDevice.empty()) - { - mCaptureDevice.clear(); - mCaptureDeviceDirty = true; - } - } - else - { - if(mCaptureDevice != name) - { - mCaptureDevice = name; - mCaptureDeviceDirty = true; - } - } -} -void LLVivoxVoiceClient::setDevicesListUpdated(bool state) -{ - mDevicesListUpdated = state; -} - -void LLVivoxVoiceClient::clearRenderDevices() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - mRenderDevices.clear(); -} - -void LLVivoxVoiceClient::addRenderDevice(const LLVoiceDevice& device) -{ - LL_DEBUGS("Voice") << "display: '" << device.display_name << "' device: '" << device.full_name << "'" << LL_ENDL; - mRenderDevices.push_back(device); -} - -LLVoiceDeviceList& LLVivoxVoiceClient::getRenderDevices() -{ - return mRenderDevices; -} - -void LLVivoxVoiceClient::setRenderDevice(const std::string& name) -{ - if(name == "Default") - { - if(!mRenderDevice.empty()) - { - mRenderDevice.clear(); - mRenderDeviceDirty = true; - } - } - else - { - if(mRenderDevice != name) - { - mRenderDevice = name; - mRenderDeviceDirty = true; - } - } - -} - -void LLVivoxVoiceClient::tuningStart() -{ - LL_DEBUGS("Voice") << "Starting tuning" << LL_ENDL; - mTuningMode = true; - if (!mIsCoroutineActive) - { - LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro", - boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance())); - } - else if (mIsInChannel) - { - LL_DEBUGS("Voice") << "no channel" << LL_ENDL; - sessionTerminate(); - } -} - -void LLVivoxVoiceClient::tuningStop() -{ - mTuningMode = false; - // force a renegotiation. - mCurrentParcelLocalID = 0; - mCurrentRegionName = ""; -} - -bool LLVivoxVoiceClient::inTuningMode() -{ - return mIsInTuningMode; -} - -void LLVivoxVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop) -{ - mTuningAudioFile = name; - std::ostringstream stream; - stream - << "" - << "" << mTuningAudioFile << "" - << "" << (loop?"1":"0") << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::tuningRenderStopSendMessage() -{ - std::ostringstream stream; - stream - << "" - << "" << mTuningAudioFile << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::tuningCaptureStartSendMessage(int loop) -{ - LL_DEBUGS("Voice") << "sending CaptureAudioStart" << LL_ENDL; - - std::ostringstream stream; - stream - << "" - << "-1" - << "" << loop << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::tuningCaptureStopSendMessage() -{ - LL_DEBUGS("Voice") << "sending CaptureAudioStop" << LL_ENDL; - - std::ostringstream stream; - stream - << "" - << "\n\n\n"; - - writeString(stream.str()); - - mTuningEnergy = 0.0f; -} - -void LLVivoxVoiceClient::tuningSetMicVolume(float volume) -{ - int scaled_volume = scale_mic_volume(volume); - - if(scaled_volume != mTuningMicVolume) - { - mTuningMicVolume = scaled_volume; - mTuningMicVolumeDirty = true; - } -} - -void LLVivoxVoiceClient::tuningSetSpeakerVolume(float volume) -{ - int scaled_volume = scale_speaker_volume(volume); - - if(scaled_volume != mTuningSpeakerVolume) - { - mTuningSpeakerVolume = scaled_volume; - mTuningSpeakerVolumeDirty = true; - } -} - -float LLVivoxVoiceClient::tuningGetEnergy(void) -{ - return mTuningEnergy; -} - -bool LLVivoxVoiceClient::deviceSettingsAvailable() -{ - bool result = true; - - if(!sConnected) - result = false; - - if(mRenderDevices.empty()) - result = false; - - return result; -} -bool LLVivoxVoiceClient::deviceSettingsUpdated() -{ - bool updated = mDevicesListUpdated; - if (mDevicesListUpdated) - { - // a hot swap event or a polling of the audio devices has been parsed since the last redraw of the input and output device panel. - mDevicesListUpdated = false; // toggle the setting - } - return updated; -} - -void LLVivoxVoiceClient::refreshDeviceLists(bool clearCurrentList) -{ - if(clearCurrentList) - { - clearCaptureDevices(); - clearRenderDevices(); - } - getCaptureDevicesSendMessage(); - getRenderDevicesSendMessage(); -} - -void LLVivoxVoiceClient::daemonDied() -{ - // The daemon died, so the connection is gone. Reset everything and start over. - LL_WARNS("Voice") << "Connection to vivox daemon lost. Resetting state."<< LL_ENDL; - - //TODO: Try to relaunch the daemon -} - -void LLVivoxVoiceClient::giveUp() -{ - // All has failed. Clean up and stop trying. - LL_WARNS("Voice") << "Terminating Voice Service" << LL_ENDL; - closeSocket(); - cleanUp(); -} - -static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel) -{ - F32 nat[3], nup[3], nl[3]; // the new at, up, left vectors and the new position and velocity -// F32 nvel[3]; - F64 npos[3]; - - // The original XML command was sent like this: - /* - << "" - << "" << pos[VX] << "" - << "" << pos[VZ] << "" - << "" << pos[VY] << "" - << "" - << "" - << "" << mAvatarVelocity[VX] << "" - << "" << mAvatarVelocity[VZ] << "" - << "" << mAvatarVelocity[VY] << "" - << "" - << "" - << "" << l.mV[VX] << "" - << "" << u.mV[VX] << "" - << "" << a.mV[VX] << "" - << "" - << "" - << "" << l.mV[VZ] << "" - << "" << u.mV[VY] << "" - << "" << a.mV[VZ] << "" - << "" - << "" - << "" << l.mV [VY] << "" - << "" << u.mV [VZ] << "" - << "" << a.mV [VY] << "" - << ""; - */ - -#if 1 - // This was the original transform done when building the XML command - nat[0] = left.mV[VX]; - nat[1] = up.mV[VX]; - nat[2] = at.mV[VX]; - - nup[0] = left.mV[VZ]; - nup[1] = up.mV[VY]; - nup[2] = at.mV[VZ]; - - nl[0] = left.mV[VY]; - nl[1] = up.mV[VZ]; - nl[2] = at.mV[VY]; - - npos[0] = pos.mdV[VX]; - npos[1] = pos.mdV[VZ]; - npos[2] = pos.mdV[VY]; - -// nvel[0] = vel.mV[VX]; -// nvel[1] = vel.mV[VZ]; -// nvel[2] = vel.mV[VY]; - - for(int i=0;i<3;++i) { - at.mV[i] = nat[i]; - up.mV[i] = nup[i]; - left.mV[i] = nl[i]; - pos.mdV[i] = npos[i]; - } - - // This was the original transform done in the SDK - nat[0] = at.mV[2]; - nat[1] = 0; // y component of at vector is always 0, this was up[2] - nat[2] = -1 * left.mV[2]; - - // We override whatever the application gives us - nup[0] = 0; // x component of up vector is always 0 - nup[1] = 1; // y component of up vector is always 1 - nup[2] = 0; // z component of up vector is always 0 - - nl[0] = at.mV[0]; - nl[1] = 0; // y component of left vector is always zero, this was up[0] - nl[2] = -1 * left.mV[0]; - - npos[2] = pos.mdV[2] * -1.0; - npos[1] = pos.mdV[1]; - npos[0] = pos.mdV[0]; - - for(int i=0;i<3;++i) { - at.mV[i] = nat[i]; - up.mV[i] = nup[i]; - left.mV[i] = nl[i]; - pos.mdV[i] = npos[i]; - } -#else - // This is the compose of the two transforms (at least, that's what I'm trying for) - nat[0] = at.mV[VX]; - nat[1] = 0; // y component of at vector is always 0, this was up[2] - nat[2] = -1 * up.mV[VZ]; - - // We override whatever the application gives us - nup[0] = 0; // x component of up vector is always 0 - nup[1] = 1; // y component of up vector is always 1 - nup[2] = 0; // z component of up vector is always 0 - - nl[0] = left.mV[VX]; - nl[1] = 0; // y component of left vector is always zero, this was up[0] - nl[2] = -1 * left.mV[VY]; - - npos[0] = pos.mdV[VX]; - npos[1] = pos.mdV[VZ]; - npos[2] = pos.mdV[VY] * -1.0; - - nvel[0] = vel.mV[VX]; - nvel[1] = vel.mV[VZ]; - nvel[2] = vel.mV[VY]; - - for(int i=0;i<3;++i) { - at.mV[i] = nat[i]; - up.mV[i] = nup[i]; - left.mV[i] = nl[i]; - pos.mdV[i] = npos[i]; - } - -#endif -} - -void LLVivoxVoiceClient::setHidden(bool hidden) -{ - mHidden = hidden; - - if (mHidden && inSpatialChannel()) - { - // get out of the channel entirely - leaveAudioSession(); - } - else - { - sendPositionAndVolumeUpdate(); - } -} - -void LLVivoxVoiceClient::sendPositionAndVolumeUpdate(void) -{ - std::ostringstream stream; - - if (mSpatialCoordsDirty && inSpatialChannel()) - { - LLVector3 l, u, a, vel; - LLVector3d pos; - - mSpatialCoordsDirty = false; - - // Always send both speaker and listener positions together. - stream << "" - << "" << getAudioSessionHandle() << ""; - - stream << ""; - - LLMatrix3 avatarRot = mAvatarRot.getMatrix3(); - -// LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL; - l = avatarRot.getLeftRow(); - u = avatarRot.getUpRow(); - a = avatarRot.getFwdRow(); - - pos = mAvatarPosition; - vel = mAvatarVelocity; - - // SLIM SDK: the old SDK was doing a transform on the passed coordinates that the new one doesn't do anymore. - // The old transform is replicated by this function. - oldSDKTransform(l, u, a, pos, vel); - - if (mHidden) - { - for (int i=0;i<3;++i) - { - pos.mdV[i] = VX_NULL_POSITION; - } - } - - stream - << "" - << "" << pos.mdV[VX] << "" - << "" << pos.mdV[VY] << "" - << "" << pos.mdV[VZ] << "" - << "" - << "" - << "" << vel.mV[VX] << "" - << "" << vel.mV[VY] << "" - << "" << vel.mV[VZ] << "" - << "" - << "" - << "" << a.mV[VX] << "" - << "" << a.mV[VY] << "" - << "" << a.mV[VZ] << "" - << "" - << "" - << "" << u.mV[VX] << "" - << "" << u.mV[VY] << "" - << "" << u.mV[VZ] << "" - << "" - << "" - << "" << l.mV [VX] << "" - << "" << l.mV [VY] << "" - << "" << l.mV [VZ] << "" - << "" - ; - - stream << ""; - - stream << ""; - - LLVector3d earPosition; - LLVector3 earVelocity; - LLMatrix3 earRot; - - switch(mEarLocation) - { - case earLocCamera: - default: - earPosition = mCameraPosition; - earVelocity = mCameraVelocity; - earRot = mCameraRot; - break; - - case earLocAvatar: - earPosition = mAvatarPosition; - earVelocity = mAvatarVelocity; - earRot = avatarRot; - break; - - case earLocMixed: - earPosition = mAvatarPosition; - earVelocity = mAvatarVelocity; - earRot = mCameraRot; - break; - } - - l = earRot.getLeftRow(); - u = earRot.getUpRow(); - a = earRot.getFwdRow(); - - pos = earPosition; - vel = earVelocity; - - - oldSDKTransform(l, u, a, pos, vel); - - if (mHidden) - { - for (int i=0;i<3;++i) - { - pos.mdV[i] = VX_NULL_POSITION; - } - } - - stream - << "" - << "" << pos.mdV[VX] << "" - << "" << pos.mdV[VY] << "" - << "" << pos.mdV[VZ] << "" - << "" - << "" - << "" << vel.mV[VX] << "" - << "" << vel.mV[VY] << "" - << "" << vel.mV[VZ] << "" - << "" - << "" - << "" << a.mV[VX] << "" - << "" << a.mV[VY] << "" - << "" << a.mV[VZ] << "" - << "" - << "" - << "" << u.mV[VX] << "" - << "" << u.mV[VY] << "" - << "" << u.mV[VZ] << "" - << "" - << "" - << "" << l.mV [VX] << "" - << "" << l.mV [VY] << "" - << "" << l.mV [VZ] << "" - << "" - ; - - stream << ""; - - stream << "1"; //do not generate responses for update requests - stream << "\n\n\n"; - } - - if(mAudioSession && (mAudioSession->mVolumeDirty || mAudioSession->mMuteDirty)) - { - participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); - - mAudioSession->mVolumeDirty = false; - mAudioSession->mMuteDirty = false; - - for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) - { - participantStatePtr_t p(iter->second); - - if(p->mVolumeDirty) - { - // Can't set volume/mute for yourself - if(!p->mIsSelf) - { - // scale from the range 0.0-1.0 to vivox volume in the range 0-100 - S32 volume = ll_round(p->mVolume / VOLUME_SCALE_VIVOX); - bool mute = p->mOnMuteList; - - if(mute) - { - // SetParticipantMuteForMe doesn't work in p2p sessions. - // If we want the user to be muted, set their volume to 0 as well. - // This isn't perfect, but it will at least reduce their volume to a minimum. - volume = 0; - // Mark the current volume level as set to prevent incoming events - // changing it to 0, so that we can return to it when unmuting. - p->mVolumeSet = true; - } - - if(volume == 0) - { - mute = true; - } - - LL_DEBUGS("Voice") << "Setting volume/mute for avatar " << p->mAvatarID << " to " << volume << (mute?"/true":"/false") << LL_ENDL; - - // SLIM SDK: Send both volume and mute commands. - - // Send a "volume for me" command for the user. - stream << "" - << "" << getAudioSessionHandle() << "" - << "" << p->mURI << "" - << "" << volume << "" - << "\n\n\n"; - - if(!mAudioSession->mIsP2P) - { - // Send a "mute for me" command for the user - // Doesn't work in P2P sessions - stream << "" - << "" << getAudioSessionHandle() << "" - << "" << p->mURI << "" - << "" << (mute?"1":"0") << "" - << "Audio" - << "\n\n\n"; - } - } - - p->mVolumeDirty = false; - } - } - } - - std::string update(stream.str()); - if(!update.empty()) - { - LL_DEBUGS("VoiceUpdate") << "sending update " << update << LL_ENDL; - writeString(update); - } - -} - -void LLVivoxVoiceClient::buildSetCaptureDevice(std::ostringstream &stream) -{ - if(mCaptureDeviceDirty) - { - LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL; - - stream - << "" - << "" << mCaptureDevice << "" - << "" - << "\n\n\n"; - - mCaptureDeviceDirty = false; - } -} - -void LLVivoxVoiceClient::buildSetRenderDevice(std::ostringstream &stream) -{ - if(mRenderDeviceDirty) - { - LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL; - - stream - << "" - << "" << mRenderDevice << "" - << "" - << "\n\n\n"; - mRenderDeviceDirty = false; - } -} - -void LLVivoxVoiceClient::sendLocalAudioUpdates() -{ - // Check all of the dirty states and then send messages to those needing to be changed. - // Tuningmode hands its own mute settings. - std::ostringstream stream; - - if (mMuteMicDirty && !mTuningMode) - { - mMuteMicDirty = false; - - // Send a local mute command. - - LL_INFOS("Voice") << "Sending MuteLocalMic command with parameter " << (mMuteMic ? "true" : "false") << LL_ENDL; - - stream << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "" << (mMuteMic ? "true" : "false") << "" - << "\n\n\n"; - - } - - if (mSpeakerMuteDirty && !mTuningMode) - { - const char *muteval = ((mSpeakerVolume <= scale_speaker_volume(0)) ? "true" : "false"); - - mSpeakerMuteDirty = false; - - LL_INFOS("Voice") << "Setting speaker mute to " << muteval << LL_ENDL; - - stream << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "" << muteval << "" - << "\n\n\n"; - - } - - if (mSpeakerVolumeDirty) - { - mSpeakerVolumeDirty = false; - - LL_INFOS("Voice") << "Setting speaker volume to " << mSpeakerVolume << LL_ENDL; - - stream << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "" << mSpeakerVolume << "" - << "\n\n\n"; - - } - - if (mMicVolumeDirty) - { - mMicVolumeDirty = false; - - LL_INFOS("Voice") << "Setting mic volume to " << mMicVolume << LL_ENDL; - - stream << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "" << mMicVolume << "" - << "\n\n\n"; - } - - - if (!stream.str().empty()) - { - writeString(stream.str()); - } -} - -/** - * Because of the recurring voice cutout issues (SL-15072) we are going to try - * to disable the automatic VAD (Voice Activity Detection) and set the associated - * parameters directly. We will expose them via Debug Settings and that should - * let us iterate on a collection of values that work for us. Hopefully! - * - * From the VIVOX Docs: - * - * VadAuto: A flag indicating if the automatic VAD is enabled (1) or disabled (0) - * - * VadHangover: The time (in milliseconds) that it takes - * for the VAD to switch back to silence from speech mode after the last speech - * frame has been detected. - * - * VadNoiseFloor: A dimensionless value between 0 and - * 20000 (default 576) that controls the maximum level at which the noise floor - * may be set at by the VAD's noise tracking. Too low of a value will make noise - * tracking ineffective (A value of 0 disables noise tracking and the VAD then - * relies purely on the sensitivity property). Too high of a value will make - * long speech classifiable as noise. - * - * VadSensitivity: A dimensionless value between 0 and - * 100, indicating the 'sensitivity of the VAD'. Increasing this value corresponds - * to decreasing the sensitivity of the VAD (i.e. '0' is most sensitive, - * while 100 is 'least sensitive') - */ -void LLVivoxVoiceClient::setupVADParams(unsigned int vad_auto, - unsigned int vad_hangover, - unsigned int vad_noise_floor, - unsigned int vad_sensitivity) -{ - std::ostringstream stream; - - LL_INFOS("Voice") << "Setting the automatic VAD to " - << (vad_auto ? "True" : "False") - << " and discrete values to" - << " VadHangover = " << vad_hangover - << ", VadSensitivity = " << vad_sensitivity - << ", VadNoiseFloor = " << vad_noise_floor - << LL_ENDL; - - // Create a request to set the VAD parameters: - stream << "" - << "" << vad_auto << "" - << "" << vad_hangover << "" - << "" << vad_sensitivity << "" - << "" << vad_noise_floor << "" - << "\n\n\n"; - - if (!stream.str().empty()) - { - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::onVADSettingsChange() -{ - // pick up the VAD variables (one of which was changed) - unsigned int vad_auto = gSavedSettings.getU32("VivoxVadAuto"); - unsigned int vad_hangover = gSavedSettings.getU32("VivoxVadHangover"); - unsigned int vad_noise_floor = gSavedSettings.getU32("VivoxVadNoiseFloor"); - unsigned int vad_sensitivity = gSavedSettings.getU32("VivoxVadSensitivity"); - - // build a VAD params change request and send it to SLVoice - setupVADParams(vad_auto, vad_hangover, vad_noise_floor, vad_sensitivity); -} - -///////////////////////////// -// Response/Event handlers - -void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID) -{ - LLSD result = LLSD::emptyMap(); - - if(statusCode == 0) - { - // Connector created, move forward. - if (connectorHandle == LLVivoxSecurity::getInstance()->connectorHandle()) - { - LL_INFOS("Voice") << "Voice connector succeeded, Vivox SDK version is " << versionID << " connector handle " << connectorHandle << LL_ENDL; - mVoiceVersion.serverVersion = versionID; - mConnectorEstablished = true; - mTerminateDaemon = false; - - result["connector"] = LLSD::Boolean(true); - } - else - { - // This shouldn't happen - we are somehow out of sync with SLVoice - // or possibly there are two things trying to run SLVoice at once - // or someone is trying to hack into it. - LL_WARNS("Voice") << "Connector returned wrong handle " - << "(" << connectorHandle << ")" - << " expected (" << LLVivoxSecurity::getInstance()->connectorHandle() << ")" - << LL_ENDL; - result["connector"] = LLSD::Boolean(false); - // Give up. - mTerminateDaemon = true; - } - } - else if (statusCode == 10028) // web request timeout prior to login - { - // this is usually fatal, but a long timeout might work - result["connector"] = LLSD::Boolean(false); - result["retry"] = LLSD::Real(CONNECT_ATTEMPT_TIMEOUT); - - LL_WARNS("Voice") << "Voice connection failed" << LL_ENDL; - } - else if (statusCode == 10006) // name resolution failure - a shorter retry may work - { - // some networks have slower DNS, but a short timeout might let it catch up - result["connector"] = LLSD::Boolean(false); - result["retry"] = LLSD::Real(CONNECT_DNS_TIMEOUT); - - LL_WARNS("Voice") << "Voice connection DNS lookup failed" << LL_ENDL; - } - else // unknown failure - give up - { - LL_WARNS("Voice") << "Voice connection failure ("<< statusCode << "): " << statusString << LL_ENDL; - mTerminateDaemon = true; - result["connector"] = LLSD::Boolean(false); - } - - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases) -{ - LLSD result = LLSD::emptyMap(); - - LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL; - - // Status code of 20200 means "bad password". We may want to special-case that at some point. - - if ( statusCode == HTTP_UNAUTHORIZED ) - { - // Login failure which is probably caused by the delay after a user's password being updated. - LL_INFOS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; - result["login"] = LLSD::String("retry"); - } - else if(statusCode != 0) - { - LL_WARNS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; - result["login"] = LLSD::String("failed"); - } - else - { - // Login succeeded, move forward. - mAccountLoggedIn = true; - mNumberOfAliases = numberOfAliases; - result["login"] = LLSD::String("response_ok"); - } - - mVivoxPump.post(result); - -} - -void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) -{ - sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId)); - - if(session) - { - session->mCreateInProgress = false; - } - - if(statusCode != 0) - { - LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL; - if(session) - { - session->mErrorStatusCode = statusCode; - session->mErrorStatusString = statusString; - if(session == mAudioSession) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "failed") - ("reason", LLSD::Integer(statusCode))); - - mVivoxPump.post(vivoxevent); - } - else - { - reapSession(session); - } - } - } - else - { - LL_INFOS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL; - if(session) - { - setSessionHandle(session, sessionHandle); - } - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "created")); - - mVivoxPump.post(vivoxevent); - } -} - -void LLVivoxVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) -{ - sessionStatePtr_t session(findSessionBeingCreatedByURI(requestId)); - - if(session) - { - session->mCreateInProgress = false; - } - - if(statusCode != 0) - { - LL_WARNS("Voice") << "SessionGroup.AddSession response failure (" << statusCode << "): " << statusString << LL_ENDL; - if(session) - { - session->mErrorStatusCode = statusCode; - session->mErrorStatusString = statusString; - if(session == mAudioSession) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "failed")); - - mVivoxPump.post(vivoxevent); - } - else - { - reapSession(session); - } - } - } - else - { - LL_DEBUGS("Voice") << "SessionGroup.AddSession response received (success), session handle is " << sessionHandle << LL_ENDL; - if(session) - { - setSessionHandle(session, sessionHandle); - } - - LLSD vivoxevent(LLSDMap("handle", LLSD::String(sessionHandle)) - ("session", "added")); - - mVivoxPump.post(vivoxevent); - - } -} - -void LLVivoxVoiceClient::sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString) -{ - sessionStatePtr_t session(findSession(requestId)); - // 1026 is session already has media, somehow mediaconnect was called twice on the same session. - // set the session info to reflect that the user is already connected. - if (statusCode == 1026) - { - session->mVoiceActive = true; - session->mMediaConnectInProgress = false; - session->mMediaStreamState = streamStateConnected; - //session->mTextStreamState = streamStateConnected; - session->mErrorStatusCode = 0; - } - else if (statusCode != 0) - { - LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL; - if (session) - { - session->mMediaConnectInProgress = false; - session->mErrorStatusCode = statusCode; - session->mErrorStatusString = statusString; - } - } - else - { - LL_DEBUGS("Voice") << "Session.Connect response received (success)" << LL_ENDL; - } -} - -void LLVivoxVoiceClient::logoutResponse(int statusCode, std::string &statusString) -{ - if(statusCode != 0) - { - LL_WARNS("Voice") << "Account.Logout response failure: " << statusString << LL_ENDL; - // Should this ever fail? do we care if it does? - } - LLSD vivoxevent(LLSDMap("logout", LLSD::Boolean(true))); - - mVivoxPump.post(vivoxevent); -} - -void LLVivoxVoiceClient::connectorShutdownResponse(int statusCode, std::string &statusString) -{ - if(statusCode != 0) - { - LL_WARNS("Voice") << "Connector.InitiateShutdown response failure: " << statusString << LL_ENDL; - // Should this ever fail? do we care if it does? - } - - sConnected = false; - mShutdownComplete = true; - - LLSD vivoxevent(LLSDMap("connector", LLSD::Boolean(false))); - - mVivoxPump.post(vivoxevent); -} - -void LLVivoxVoiceClient::sessionAddedEvent( - std::string &uriString, - std::string &alias, - std::string &sessionHandle, - std::string &sessionGroupHandle, - bool isChannel, - bool incoming, - std::string &nameString, - std::string &applicationString) -{ - sessionStatePtr_t session; - - LL_INFOS("Voice") << "session " << uriString << ", alias " << alias << ", name " << nameString << " handle " << sessionHandle << LL_ENDL; - - session = addSession(uriString, sessionHandle); - if(session) - { - session->mGroupHandle = sessionGroupHandle; - session->mIsChannel = isChannel; - session->mIncoming = incoming; - session->mAlias = alias; - - // Generate a caller UUID -- don't need to do this for channels - if(!session->mIsChannel) - { - if(IDFromName(session->mSIPURI, session->mCallerID)) - { - // Normal URI(base64-encoded UUID) - } - else if(!session->mAlias.empty() && IDFromName(session->mAlias, session->mCallerID)) - { - // Wrong URI, but an alias is available. Stash the incoming URI as an alternate - session->mAlternateSIPURI = session->mSIPURI; - - // and generate a proper URI from the ID. - setSessionURI(session, sipURIFromID(session->mCallerID)); - } - else - { - LL_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL; - session->mCallerID.generate(session->mSIPURI); - session->mSynthesizedCallerID = true; - - // Can't look up the name in this case -- we have to extract it from the URI. - std::string namePortion = nameFromsipURI(session->mSIPURI); - if(namePortion.empty()) - { - // Didn't seem to be a SIP URI, just use the whole provided name. - namePortion = nameString; - } - - // Some incoming names may be separated with an underscore instead of a space. Fix this. - LLStringUtil::replaceChar(namePortion, '_', ' '); - - // Act like we just finished resolving the name (this stores it in all the right places) - avatarNameResolved(session->mCallerID, namePortion); - } - - LL_INFOS("Voice") << "caller ID: " << session->mCallerID << LL_ENDL; - - if(!session->mSynthesizedCallerID) - { - // If we got here, we don't have a proper name. Initiate a lookup. - lookupName(session->mCallerID); - } - } - } -} - -void LLVivoxVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle) -{ - LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL; - -#if USE_SESSION_GROUPS - if(mMainSessionGroupHandle.empty()) - { - // This is the first (i.e. "main") session group. Save its handle. - mMainSessionGroupHandle = sessionGroupHandle; - } - else - { - LL_DEBUGS("Voice") << "Already had a session group handle " << mMainSessionGroupHandle << LL_ENDL; - } -#endif -} - -void LLVivoxVoiceClient::joinedAudioSession(const sessionStatePtr_t &session) -{ - LL_DEBUGS("Voice") << "Joined Audio Session" << LL_ENDL; - if(mAudioSession != session) - { - sessionStatePtr_t oldSession = mAudioSession; - - mAudioSession = session; - - // The old session may now need to be deleted. - reapSession(oldSession); - } - - // This is the session we're joining. - if(mIsJoiningSession) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle)) - ("session", "joined")); - - mVivoxPump.post(vivoxevent); - - // Add the current user as a participant here. - participantStatePtr_t participant(session->addParticipant(sipURIFromName(mAccountName))); - if(participant) - { - participant->mIsSelf = true; - lookupName(participant->mAvatarID); - - LL_INFOS("Voice") << "added self as participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - } - - if(!session->mIsChannel) - { - // this is a p2p session. Make sure the other end is added as a participant. - participantStatePtr_t participant(session->addParticipant(session->mSIPURI)); - if(participant) - { - if(participant->mAvatarIDValid) - { - lookupName(participant->mAvatarID); - } - else if(!session->mName.empty()) - { - participant->mDisplayName = session->mName; - avatarNameResolved(participant->mAvatarID, session->mName); - } - - // TODO: Question: Do we need to set up mAvatarID/mAvatarIDValid here? - LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - } - } - } -} - -void LLVivoxVoiceClient::sessionRemovedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle) -{ - LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL; - - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - leftAudioSession(session); - - // This message invalidates the session's handle. Set it to empty. - clearSessionHandle(session); - - // This also means that the session's session group is now empty. - // Terminate the session group so it doesn't leak. - sessionGroupTerminateSendMessage(session); - - // Reset the media state (we now have no info) - session->mMediaStreamState = streamStateUnknown; - //session->mTextStreamState = streamStateUnknown; - - // Conditionally delete the session - reapSession(session); - } - else - { - // Already reaped this session. - LL_DEBUGS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL; - } - -} - -void LLVivoxVoiceClient::reapSession(const sessionStatePtr_t &session) -{ - if(session) - { - - if(session->mCreateInProgress) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (create in progress)" << LL_ENDL; - } - else if(session->mMediaConnectInProgress) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (connect in progress)" << LL_ENDL; - } - else if(session == mAudioSession) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the current session)" << LL_ENDL; - } - else if(session == mNextAudioSession) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the next session)" << LL_ENDL; - } - else - { - // We don't have a reason to keep tracking this session, so just delete it. - LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL; - deleteSession(session); - } - } - else - { -// LL_DEBUGS("Voice") << "session is NULL" << LL_ENDL; - } -} - -// Returns true if the session seems to indicate we've moved to a region on a different voice server -bool LLVivoxVoiceClient::sessionNeedsRelog(const sessionStatePtr_t &session) -{ - bool result = false; - - if(session) - { - // Only make this check for spatial channels (so it won't happen for group or p2p calls) - if(session->mIsSpatial) - { - std::string::size_type atsign; - - atsign = session->mSIPURI.find("@"); - - if(atsign != std::string::npos) - { - std::string urihost = session->mSIPURI.substr(atsign + 1); - if(stricmp(urihost.c_str(), mVoiceSIPURIHostName.c_str())) - { - // The hostname in this URI is different from what we expect. This probably means we need to relog. - - // We could make a ProvisionVoiceAccountRequest and compare the result with the current values of - // mVoiceSIPURIHostName and mVoiceAccountServerURI to be really sure, but this is a pretty good indicator. - - result = true; - } - } - } - } - - return result; -} - -void LLVivoxVoiceClient::leftAudioSession(const sessionStatePtr_t &session) -{ - if (mAudioSession == session) - { - LLSD vivoxevent(LLSDMap("handle", LLSD::String(session->mHandle)) - ("session", "removed")); - - mVivoxPump.post(vivoxevent); - } -} - -void LLVivoxVoiceClient::accountLoginStateChangeEvent( - std::string &accountHandle, - int statusCode, - std::string &statusString, - int state) -{ - LLSD levent = LLSD::emptyMap(); - - /* - According to Mike S., status codes for this event are: - login_state_logged_out=0, - login_state_logged_in = 1, - login_state_logging_in = 2, - login_state_logging_out = 3, - login_state_resetting = 4, - login_state_error=100 - */ - - LL_DEBUGS("Voice") << "state change event: " << state << LL_ENDL; - switch(state) - { - case 1: - levent["login"] = LLSD::String("account_login"); - - mVivoxPump.post(levent); - break; - case 2: - break; - - case 3: - levent["login"] = LLSD::String("account_loggingOut"); - - mVivoxPump.post(levent); - break; - - case 4: - break; - - case 100: - LL_WARNS("Voice") << "account state event error" << LL_ENDL; - break; - - case 0: - levent["login"] = LLSD::String("account_logout"); - - mVivoxPump.post(levent); - break; - - default: - //Used to be a commented out warning - LL_WARNS("Voice") << "unknown account state event: " << state << LL_ENDL; - break; - } -} - -void LLVivoxVoiceClient::mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType) -{ - LLSD result; - - if (mediaCompletionType == "AuxBufferAudioCapture") - { - mCaptureBufferRecording = false; - result["recplay"] = "end"; - } - else if (mediaCompletionType == "AuxBufferAudioRender") - { - // Ignore all but the last stop event - if (--mPlayRequestCount <= 0) - { - mCaptureBufferPlaying = false; - result["recplay"] = "end"; -// result["recplay"] = "done"; - } - } - else - { - LL_WARNS("Voice") << "Unknown MediaCompletionType: " << mediaCompletionType << LL_ENDL; - } - - if (!result.isUndefined()) - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::mediaStreamUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - int statusCode, - std::string &statusString, - int state, - bool incoming) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - - LL_DEBUGS("Voice") << "session " << sessionHandle << ", status code " << statusCode << ", string \"" << statusString << "\"" << LL_ENDL; - - if(session) - { - // We know about this session - - // Save the state for later use - session->mMediaStreamState = state; - - switch(statusCode) - { - case 0: - case HTTP_OK: - // generic success - // Don't change the saved error code (it may have been set elsewhere) - break; - default: - // save the status code for later - session->mErrorStatusCode = statusCode; - break; - } - - switch(state) - { - case streamStateDisconnecting: - case streamStateIdle: - // Standard "left audio session", Vivox state 'disconnected' - session->mVoiceActive = false; - session->mMediaConnectInProgress = false; - leftAudioSession(session); - break; - - case streamStateConnected: - session->mVoiceActive = true; - session->mMediaConnectInProgress = false; - joinedAudioSession(session); - case streamStateConnecting: // do nothing, but prevents a warning getting into the logs. - break; - - case streamStateRinging: - if(incoming) - { - // Send the voice chat invite to the GUI layer - // TODO: Question: Should we correlate with the mute list here? - session->mIMSessionID = LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, session->mCallerID); - session->mVoiceInvitePending = true; - if(session->mName.empty()) - { - lookupName(session->mCallerID); - } - else - { - // Act like we just finished resolving the name - avatarNameResolved(session->mCallerID, session->mName); - } - } - break; - - default: - LL_WARNS("Voice") << "unknown state " << state << LL_ENDL; - break; - - } - - } - else - { - // session disconnectintg and disconnected events arriving after we have already left the session. - LL_DEBUGS("Voice") << "session " << sessionHandle << " not found"<< LL_ENDL; - } -} - -void LLVivoxVoiceClient::participantAddedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - std::string &nameString, - std::string &displayNameString, - int participantType) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - participantStatePtr_t participant(session->addParticipant(uriString)); - if(participant) - { - participant->mAccountName = nameString; - - LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - - if(participant->mAvatarIDValid) - { - // Initiate a lookup - lookupName(participant->mAvatarID); - } - else - { - // If we don't have a valid avatar UUID, we need to fill in the display name to make the active speakers floater work. - std::string namePortion = nameFromsipURI(uriString); - if(namePortion.empty()) - { - // Problem with the SIP URI, fall back to the display name - namePortion = displayNameString; - } - if(namePortion.empty()) - { - // Problems with both of the above, fall back to the account name - namePortion = nameString; - } - - // Set the display name (which is a hint to the active speakers window not to do its own lookup) - participant->mDisplayName = namePortion; - avatarNameResolved(participant->mAvatarID, namePortion); - } - } - } -} - -void LLVivoxVoiceClient::participantRemovedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - std::string &nameString) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - participantStatePtr_t participant(session->findParticipant(uriString)); - if(participant) - { - session->removeParticipant(participant); - } - else - { - LL_DEBUGS("Voice") << "unknown participant " << uriString << LL_ENDL; - } - } - else - { - // a late arriving event on a session we have already left. - LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; - } -} - - -void LLVivoxVoiceClient::participantUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - bool isModeratorMuted, - bool isSpeaking, - int volume, - F32 energy) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - participantStatePtr_t participant(session->findParticipant(uriString)); - - if(participant) - { - //LL_INFOS("Voice") << "Participant Update for " << participant->mDisplayName << LL_ENDL; - - participant->mIsSpeaking = isSpeaking; - participant->mIsModeratorMuted = isModeratorMuted; - - // SLIM SDK: convert range: ensure that energy is set to zero if is_speaking is false - if (isSpeaking) - { - participant->mSpeakingTimeout.reset(); - participant->mPower = energy; - } - else - { - participant->mPower = 0.0f; - } - - // Ignore incoming volume level if it has been explicitly set, or there - // is a volume or mute change pending. - if ( !participant->mVolumeSet && !participant->mVolumeDirty) - { - participant->mVolume = (F32)volume * VOLUME_SCALE_VIVOX; - } - - // *HACK: mantipov: added while working on EXT-3544 - /* - Sometimes LLVoiceClient::participantUpdatedEvent callback is called BEFORE - LLViewerChatterBoxSessionAgentListUpdates::post() sometimes AFTER. - - participantUpdatedEvent updates voice participant state in particular participantState::mIsModeratorMuted - Originally we wanted to update session Speaker Manager to fire LLSpeakerVoiceModerationEvent to fix the EXT-3544 bug. - Calling of the LLSpeakerMgr::update() method was added into LLIMMgr::processAgentListUpdates. - - But in case participantUpdatedEvent() is called after LLViewerChatterBoxSessionAgentListUpdates::post() - voice participant mIsModeratorMuted is changed after speakers are updated in Speaker Manager - and event is not fired. - - So, we have to call LLSpeakerMgr::update() here. - */ - LLVoiceChannel* voice_cnl = LLVoiceChannel::getCurrentVoiceChannel(); - - // ignore session ID of local chat - if (voice_cnl && voice_cnl->getSessionID().notNull()) - { - LLSpeakerMgr* speaker_manager = LLIMModel::getInstance()->getSpeakerManager(voice_cnl->getSessionID()); - if (speaker_manager) - { - speaker_manager->update(true); - - // also initialize voice moderate_mode depend on Agent's participant. See EXT-6937. - // *TODO: remove once a way to request the current voice channel moderation mode is implemented. - if (gAgent.getID() == participant->mAvatarID) - { - speaker_manager->initVoiceModerateMode(); - } - } - } - } - else - { - LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL; - } - } - else - { - LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; - } -} - -void LLVivoxVoiceClient::messageEvent( - std::string &sessionHandle, - std::string &uriString, - std::string &alias, - std::string &messageHeader, - std::string &messageBody, - std::string &applicationString) -{ - LL_DEBUGS("Voice") << "Message event, session " << sessionHandle << " from " << uriString << LL_ENDL; -// LL_DEBUGS("Voice") << " header " << messageHeader << ", body: \n" << messageBody << LL_ENDL; - - LL_INFOS("Voice") << "Vivox raw message:" << std::endl << messageBody << LL_ENDL; - - if(messageHeader.find(HTTP_CONTENT_TEXT_HTML) != std::string::npos) - { - std::string message; - - { - const std::string startMarker = ", try looking for a instead. - start = messageBody.find(startSpan); - start = messageBody.find(startMarker2, start); - end = messageBody.find(endSpan); - - if(start != std::string::npos) - { - start += startMarker2.size(); - - if(end != std::string::npos) - end -= start; - - message.assign(messageBody, start, end); - } - } - } - -// LL_DEBUGS("Voice") << " raw message = \n" << message << LL_ENDL; - - // strip formatting tags - { - std::string::size_type start; - std::string::size_type end; - - while((start = message.find('<')) != std::string::npos) - { - if((end = message.find('>', start + 1)) != std::string::npos) - { - // Strip out the tag - message.erase(start, (end + 1) - start); - } - else - { - // Avoid an infinite loop - break; - } - } - } - - // Decode ampersand-escaped chars - { - std::string::size_type mark = 0; - - // The text may contain text encoded with <, >, and & - mark = 0; - while((mark = message.find("<", mark)) != std::string::npos) - { - message.replace(mark, 4, "<"); - mark += 1; - } - - mark = 0; - while((mark = message.find(">", mark)) != std::string::npos) - { - message.replace(mark, 4, ">"); - mark += 1; - } - - mark = 0; - while((mark = message.find("&", mark)) != std::string::npos) - { - message.replace(mark, 5, "&"); - mark += 1; - } - } - - // strip leading/trailing whitespace (since we always seem to get a couple newlines) - LLStringUtil::trim(message); - -// LL_DEBUGS("Voice") << " stripped message = \n" << message << LL_ENDL; - - sessionStatePtr_t session(findSession(sessionHandle)); - if(session) - { - bool is_do_not_disturb = gAgent.isDoNotDisturb(); - bool is_muted = LLMuteList::getInstance()->isMuted(session->mCallerID, session->mName, LLMute::flagTextChat); - bool is_linden = LLMuteList::isLinden(session->mName); - LLChat chat; - - chat.mMuted = is_muted && !is_linden; - - if(!chat.mMuted) - { - chat.mFromID = session->mCallerID; - chat.mFromName = session->mName; - chat.mSourceType = CHAT_SOURCE_AGENT; - - if(is_do_not_disturb && !is_linden) - { - // TODO: Question: Return do not disturb mode response here? Or maybe when session is started instead? - } - - LL_DEBUGS("Voice") << "adding message, name " << session->mName << " session " << session->mIMSessionID << ", target " << session->mCallerID << LL_ENDL; - LLIMMgr::getInstance()->addMessage(session->mIMSessionID, - session->mCallerID, - session->mName.c_str(), - message.c_str(), - false, - LLStringUtil::null, // default arg - IM_NOTHING_SPECIAL, // default arg - 0, // default arg - LLUUID::null, // default arg - LLVector3::zero); // default arg - } - } - } -} - -void LLVivoxVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - - if(session) - { - participantStatePtr_t participant(session->findParticipant(uriString)); - if(participant) - { - if (!stricmp(notificationType.c_str(), "Typing")) - { - // Other end started typing - // TODO: The proper way to add a typing notification seems to be LLIMMgr::processIMTypingStart(). - // It requires some info for the message, which we don't have here. - } - else if (!stricmp(notificationType.c_str(), "NotTyping")) - { - // Other end stopped typing - // TODO: The proper way to remove a typing notification seems to be LLIMMgr::processIMTypingStop(). - // It requires some info for the message, which we don't have here. - } - else - { - LL_DEBUGS("Voice") << "Unknown notification type " << notificationType << "for participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; - } - } - else - { - LL_DEBUGS("Voice") << "Unknown participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; - } - } - else - { - LL_DEBUGS("Voice") << "Unknown session handle " << sessionHandle << LL_ENDL; - } -} - -void LLVivoxVoiceClient::voiceServiceConnectionStateChangedEvent(int statusCode, std::string &statusString, std::string &build_id) -{ - // We don't generally need to process this. However, one occurence is when we first connect, and so it is the - // earliest opportunity to learn what we're connected to. - if (statusCode) - { - LL_WARNS("Voice") << "VoiceServiceConnectionStateChangedEvent statusCode: " << statusCode << - "statusString: " << statusString << LL_ENDL; - return; - } - if (build_id.empty()) - { - return; - } - mVoiceVersion.mBuildVersion = build_id; -} - -void LLVivoxVoiceClient::auxAudioPropertiesEvent(F32 energy) -{ - LL_DEBUGS("VoiceEnergy") << "got energy " << energy << LL_ENDL; - mTuningEnergy = energy; -} - -void LLVivoxVoiceClient::muteListChanged() -{ - // The user's mute list has been updated. Go through the current participant list and sync it with the mute list. - if(mAudioSession) - { - participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); - - for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) - { - participantStatePtr_t p(iter->second); - - // Check to see if this participant is on the mute list already - if(p->updateMuteState()) - mAudioSession->mVolumeDirty = true; - } - } -} - -///////////////////////////// -// Managing list of participants -LLVivoxVoiceClient::participantState::participantState(const std::string &uri) : - mURI(uri), - mPTT(false), - mIsSpeaking(false), - mIsModeratorMuted(false), - mLastSpokeTimestamp(0.f), - mPower(0.f), - mVolume(LLVoiceClient::VOLUME_DEFAULT), - mUserVolume(0), - mOnMuteList(false), - mVolumeSet(false), - mVolumeDirty(false), - mAvatarIDValid(false), - mIsSelf(false) -{ -} - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::addParticipant(const std::string &uri) -{ - participantStatePtr_t result; - bool useAlternateURI = false; - - // Note: this is mostly the body of LLVivoxVoiceClient::sessionState::findParticipant(), but since we need to know if it - // matched the alternate SIP URI (so we can add it properly), we need to reproduce it here. - { - participantMap::iterator iter = mParticipantsByURI.find(uri); - - if(iter == mParticipantsByURI.end()) - { - if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) - { - // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. - // Use mSIPURI instead, since it will be properly encoded. - iter = mParticipantsByURI.find(mSIPURI); - useAlternateURI = true; - } - } - - if(iter != mParticipantsByURI.end()) - { - result = iter->second; - } - } - - if(!result) - { - // participant isn't already in one list or the other. - result = std::make_shared(useAlternateURI?mSIPURI:uri); - mParticipantsByURI.insert(participantMap::value_type(result->mURI, result)); - mParticipantsChanged = true; - - // Try to do a reverse transform on the URI to get the GUID back. - { - LLUUID id; - if(LLVivoxVoiceClient::getInstance()->IDFromName(result->mURI, id)) - { - result->mAvatarIDValid = true; - result->mAvatarID = id; - } - else - { - // Create a UUID by hashing the URI, but do NOT set mAvatarIDValid. - // This indicates that the ID will not be in the name cache. - result->mAvatarID.generate(uri); - } - } - - if(result->updateMuteState()) - { - mMuteDirty = true; - } - - mParticipantsByUUID.insert(participantUUIDMap::value_type(result->mAvatarID, result)); - - if (LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID, result->mVolume)) - { - result->mVolumeDirty = true; - mVolumeDirty = true; - } - - LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL; - } - - return result; -} - -bool LLVivoxVoiceClient::participantState::updateMuteState() -{ - bool result = false; - - bool isMuted = LLMuteList::getInstance()->isMuted(mAvatarID, LLMute::flagVoiceChat); - if(mOnMuteList != isMuted) - { - mOnMuteList = isMuted; - mVolumeDirty = true; - result = true; - } - return result; -} - -bool LLVivoxVoiceClient::participantState::isAvatar() -{ - return mAvatarIDValid; -} - -void LLVivoxVoiceClient::sessionState::removeParticipant(const LLVivoxVoiceClient::participantStatePtr_t &participant) -{ - if(participant) - { - participantMap::iterator iter = mParticipantsByURI.find(participant->mURI); - participantUUIDMap::iterator iter2 = mParticipantsByUUID.find(participant->mAvatarID); - - LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL; - - if(iter == mParticipantsByURI.end()) - { - LL_WARNS("Voice") << "Internal error: participant " << participant->mURI << " not in URI map" << LL_ENDL; - } - else if(iter2 == mParticipantsByUUID.end()) - { - LL_WARNS("Voice") << "Internal error: participant ID " << participant->mAvatarID << " not in UUID map" << LL_ENDL; - } - else if(iter->second != iter2->second) - { - LL_WARNS("Voice") << "Internal error: participant mismatch!" << LL_ENDL; - } - else - { - mParticipantsByURI.erase(iter); - mParticipantsByUUID.erase(iter2); - - mParticipantsChanged = true; - } - } -} - -void LLVivoxVoiceClient::sessionState::removeAllParticipants() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - - while(!mParticipantsByURI.empty()) - { - removeParticipant(mParticipantsByURI.begin()->second); - } - - if(!mParticipantsByUUID.empty()) - { - LL_WARNS("Voice") << "Internal error: empty URI map, non-empty UUID map" << LL_ENDL; - } -} - -/*static*/ -void LLVivoxVoiceClient::sessionState::VerifySessions() -{ - std::set::iterator it = mSession.begin(); - while (it != mSession.end()) - { - if ((*it).expired()) - { - LL_WARNS("Voice") << "Expired session found! removing" << LL_ENDL; - it = mSession.erase(it); - } - else - ++it; - } -} - - -void LLVivoxVoiceClient::getParticipantList(std::set &participants) -{ - if(mProcessChannels && mAudioSession) - { - for(participantUUIDMap::iterator iter = mAudioSession->mParticipantsByUUID.begin(); - iter != mAudioSession->mParticipantsByUUID.end(); - iter++) - { - participants.insert(iter->first); - } - } -} - -bool LLVivoxVoiceClient::isParticipant(const LLUUID &speaker_id) -{ - if(mProcessChannels && mAudioSession) - { - return (mAudioSession->mParticipantsByUUID.find(speaker_id) != mAudioSession->mParticipantsByUUID.end()); - } - return false; -} - - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::findParticipant(const std::string &uri) -{ - participantStatePtr_t result; - - participantMap::iterator iter = mParticipantsByURI.find(uri); - - if(iter == mParticipantsByURI.end()) - { - if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) - { - // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. - // Look up the other URI - iter = mParticipantsByURI.find(mSIPURI); - } - } - - if(iter != mParticipantsByURI.end()) - { - result = iter->second; - } - - return result; -} - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::sessionState::findParticipantByID(const LLUUID& id) -{ - participantStatePtr_t result; - participantUUIDMap::iterator iter = mParticipantsByUUID.find(id); - - if(iter != mParticipantsByUUID.end()) - { - result = iter->second; - } - - return result; -} - -LLVivoxVoiceClient::participantStatePtr_t LLVivoxVoiceClient::findParticipantByID(const LLUUID& id) -{ - participantStatePtr_t result; - - if(mAudioSession) - { - result = mAudioSession->findParticipantByID(id); - } - - return result; -} - - - -// Check for parcel boundary crossing -bool LLVivoxVoiceClient::checkParcelChanged(bool update) -{ - LLViewerRegion *region = gAgent.getRegion(); - LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - - if(region && parcel) - { - S32 parcelLocalID = parcel->getLocalID(); - std::string regionName = region->getName(); - - // LL_DEBUGS("Voice") << "Region name = \"" << regionName << "\", parcel local ID = " << parcelLocalID << ", cap URI = \"" << capURI << "\"" << LL_ENDL; - - // The region name starts out empty and gets filled in later. - // Also, the cap gets filled in a short time after the region cross, but a little too late for our purposes. - // If either is empty, wait for the next time around. - if(!regionName.empty()) - { - if((parcelLocalID != mCurrentParcelLocalID) || (regionName != mCurrentRegionName)) - { - // We have changed parcels. Initiate a parcel channel lookup. - if (update) - { - mCurrentParcelLocalID = parcelLocalID; - mCurrentRegionName = regionName; - } - return true; - } - } - } - return false; -} - -bool LLVivoxVoiceClient::switchChannel( - std::string uri, - bool spatial, - bool no_reconnect, - bool is_p2p, - std::string hash) -{ - bool needsSwitch = !mIsInChannel; - - if (mIsInChannel) - { - if (mSessionTerminateRequested) - { - // If a terminate has been requested, we need to compare against where the URI we're already headed to. - if(mNextAudioSession) - { - if (mNextAudioSession->mSIPURI != uri) - { - needsSwitch = true; - } - } - else - { - // mNextAudioSession is null -- this probably means we're on our way back to spatial. - if(!uri.empty()) - { - // We do want to process a switch in this case. - needsSwitch = true; - } - } - } - else - { - // Otherwise, compare against the URI we're in now. - if(mAudioSession) - { - if(mAudioSession->mSIPURI != uri) - { - needsSwitch = true; - } - } - else - { - if(!uri.empty()) - { - // mAudioSession is null -- it's not clear what case would cause this. - // For now, log it as a warning and see if it ever crops up. - LL_WARNS("Voice") << "No current audio session... Forcing switch" << LL_ENDL; - needsSwitch = true; - } - } - } - } - - if(needsSwitch) - { - if(uri.empty()) - { - // Leave any channel we may be in - LL_DEBUGS("Voice") << "leaving channel" << LL_ENDL; - - sessionStatePtr_t oldSession = mNextAudioSession; - mNextAudioSession.reset(); - - // The old session may now need to be deleted. - reapSession(oldSession); - - // If voice was on, turn it off - if (LLVoiceClient::getInstance()->getUserPTTState()) - { - LLVoiceClient::getInstance()->setUserPTTState(false); - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - } - else - { - LL_DEBUGS("Voice") << "switching to channel " << uri << LL_ENDL; - - mNextAudioSession = addSession(uri); - mNextAudioSession->mHash = hash; - mNextAudioSession->mIsSpatial = spatial; - mNextAudioSession->mReconnect = !no_reconnect; - mNextAudioSession->mIsP2P = is_p2p; - } - - if (mIsInChannel) - { - // If we're already in a channel, or if we're joining one, terminate - // so we can rejoin with the new session data. - sessionTerminate(); - } - } - - return needsSwitch; -} - -void LLVivoxVoiceClient::joinSession(const sessionStatePtr_t &session) -{ - mNextAudioSession = session; - - if (mIsInChannel) - { - // If we're already in a channel, or if we're joining one, terminate - // so we can rejoin with the new session data. - sessionTerminate(); - } -} - -void LLVivoxVoiceClient::setNonSpatialChannel(const LLSD& channelInfo, bool notify_on_first_join, bool hangup_on_last_leave) -{ - switchChannel(channelInfo["channel_uri"].asString(), false, false, false, channelInfo["channel_credentials"].asString()); -} - -bool LLVivoxVoiceClient::setSpatialChannel(const LLSD& channelInfo) -{ - mSpatialSessionURI = channelInfo["channel_uri"].asString(); - mSpatialSessionCredentials = channelInfo["channel_credentials"].asString(); - if (!mProcessChannels) - { - // we're not even processing channels (another provider is) so - // save the credentials aside and exit - return false; - } - - LL_DEBUGS("Voice") << "got spatial channel uri: \"" << mSpatialSessionURI << "\"" << LL_ENDL; - - if((mIsInChannel && mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial))) - { - // User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels. - LL_INFOS("Voice") << "in non-spatial chat, not switching channels" << LL_ENDL; - return false; - } - else - { - return switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials); - } -} - -void LLVivoxVoiceClient::callUser(const LLUUID &uuid) -{ - std::string userURI = sipURIFromID(uuid); - mProcessChannels = true; - - switchChannel(userURI, false, true, true); -} - -void LLVivoxVoiceClient::hangup() { leaveChannel(); } - - -LLVoiceP2PIncomingCallInterfacePtr LLVivoxVoiceClient::getIncomingCallInterface(const LLSD &voice_call_info) -{ - return std::make_shared(voice_call_info); -} - -bool LLVivoxVoiceClient::answerInvite(const std::string &sessionHandle) -{ - // this is only ever used to answer incoming p2p call invites. - - sessionStatePtr_t session(findSession(sessionHandle)); - if (session) - { - session->mIsSpatial = false; - session->mReconnect = false; - session->mIsP2P = true; - mProcessChannels = true; - joinSession(session); - return true; - } - - return false; -} - -void LLVivoxVoiceClient::declineInvite(const std::string &sessionHandle) -{ - sessionStatePtr_t session(findSession(sessionHandle)); - if (session) - { - sessionMediaDisconnectSendMessage(session); - } -} - -bool LLVivoxVoiceClient::isVoiceWorking() const -{ - - //Added stateSessionTerminated state to avoid problems with call in parcels with disabled voice (EXT-4758) - // Condition with joining spatial num was added to take into account possible problems with connection to voice - // server(EXT-4313). See bug descriptions and comments for MAX_NORMAL_JOINING_SPATIAL_NUM for more info. - return (mSpatialJoiningNum < MAX_NORMAL_JOINING_SPATIAL_NUM) && mIsLoggedIn; -} - -// Returns true if the indicated participant in the current audio session is really an SL avatar. -// Currently this will be false only for PSTN callers into group chats, and PSTN p2p calls. -bool LLVivoxVoiceClient::isParticipantAvatar(const LLUUID &id) -{ - bool result = true; - sessionStatePtr_t session(findSession(id)); - - if(session) - { - // this is a p2p session with the indicated caller, or the session with the specified UUID. - if(session->mSynthesizedCallerID) - result = false; - } - else - { - // Didn't find a matching session -- check the current audio session for a matching participant - if(mAudioSession) - { - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->isAvatar(); - } - } - } - - return result; -} - -// Returns true if calling back the session URI after the session has closed is possible. -// Currently this will be false only for PSTN P2P calls. -bool LLVivoxVoiceClient::isSessionCallBackPossible(const LLUUID &session_id) -{ - bool result = true; - sessionStatePtr_t session(findSession(session_id)); - - if(session != NULL) - { - result = session->isCallBackPossible(); - } - - return result; -} - -// Returns true if the session can accept text IM's. -// Currently this will be false only for PSTN P2P calls. -bool LLVivoxVoiceClient::isSessionTextIMPossible(const LLUUID &session_id) -{ - bool result = true; - sessionStatePtr_t session(findSession(session_id)); - - if(session != NULL) - { - result = session->isTextIMPossible(); - } - - return result; -} - -void LLVivoxVoiceClient::leaveNonSpatialChannel() -{ - LL_DEBUGS("Voice") << "Request to leave spacial channel." << LL_ENDL; - - // Make sure we don't rejoin the current session. - sessionStatePtr_t oldNextSession(mNextAudioSession); - mNextAudioSession.reset(); - - // Most likely this will still be the current session at this point, but check it anyway. - reapSession(oldNextSession); - - verifySessionState(); - - sessionTerminate(); -} - -void LLVivoxVoiceClient::processChannels(bool process) -{ - mCurrentParcelLocalID = -1; - mCurrentRegionName.clear(); - mProcessChannels = process; -} - -bool LLVivoxVoiceClient::isCurrentChannel(const LLSD &channelInfo) -{ - if (!mProcessChannels - || (channelInfo.has("voice_server_type") && channelInfo["voice_server_type"].asString() != VIVOX_VOICE_SERVER_TYPE) - || mSessionTerminateRequested) - { - return false; - } - // favor the next audio session, as that's the one we're bringing up. - sessionStatePtr_t session = mNextAudioSession; - if (!session) - { - session = mAudioSession; - } - if (session) - { - if (!channelInfo["session_handle"].asString().empty()) - { - return session->mHandle == channelInfo["session_handle"].asString(); - } - return channelInfo["channel_uri"].asString() == session->mSIPURI; - } - return false; -} - -bool LLVivoxVoiceClient::compareChannels(const LLSD& channelInfo1, const LLSD& channelInfo2) -{ - return (!channelInfo1.has("voice_server_type") || (channelInfo1["voice_server_type"] == VIVOX_VOICE_SERVER_TYPE)) && - (!channelInfo2.has("voice_server_type") || (channelInfo2["voice_server_type"] == VIVOX_VOICE_SERVER_TYPE)) && - (channelInfo1["channel_uri"] == channelInfo2["channel_uri"]); -} - -bool LLVivoxVoiceClient::inProximalChannel() -{ - bool result = false; - - if (mIsInChannel && !mSessionTerminateRequested) - { - result = inSpatialChannel(); - } - - return result; -} - -std::string LLVivoxVoiceClient::sipURIFromID(const LLUUID &id) const -{ - std::string result; - result = "sip:"; - result += nameFromID(id); - result += "@"; - result += mVoiceSIPURIHostName; - - return result; -} - -LLSD LLVivoxVoiceClient::getP2PChannelInfoTemplate(const LLUUID& id) const -{ - LLSD result; - result["channel_uri"] = sipURIFromID(id); - result["voice_server_type"] = VIVOX_VOICE_SERVER_TYPE; - return result; -} - -std::string LLVivoxVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar) -{ - std::string result; - if(avatar) - { - result = "sip:"; - result += nameFromID(avatar->getID()); - result += "@"; - result += mVoiceSIPURIHostName; - } - - return result; -} - -std::string LLVivoxVoiceClient::nameFromID(const LLUUID &uuid) const -{ - std::string result; - - if (uuid.isNull()) { - //VIVOX, the uuid emtpy look for the mURIString and return that instead. - //result.assign(uuid.mURIStringName); - LLStringUtil::replaceChar(result, '_', ' '); - return result; - } - // Prepending this apparently prevents conflicts with reserved names inside the vivox code. - result = "x"; - - // Base64 encode and replace the pieces of base64 that are less compatible - // with e-mail local-parts. - // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet" - result += LLBase64::encode(uuid.mData, UUID_BYTES); - LLStringUtil::replaceChar(result, '+', '-'); - LLStringUtil::replaceChar(result, '/', '_'); - - // If you need to transform a GUID to this form on the macOS command line, this will do so: - // echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-') - - // The reverse transform can be done with: - // echo 'x5mkTKmxDTuGnjWyC__WfMg==' |cut -b 2- -|tr '_-' '/+' |openssl base64 -d|xxd -p - - return result; -} - -bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) -{ - bool result = false; - - // SLIM SDK: The "name" may actually be a SIP URI such as: "sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com" - // If it is, convert to a bare name before doing the transform. - std::string name = nameFromsipURI(inName); - - // Doesn't look like a SIP URI, assume it's an actual name. - if(name.empty()) - name = inName; - - // This will only work if the name is of the proper form. - // As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is: - // "xFnPP04IpREWNkuw1cOXlhw==" - - if((name.size() == 25) && (name[0] == 'x') && (name[23] == '=') && (name[24] == '=')) - { - // The name appears to have the right form. - - // Reverse the transforms done by nameFromID - std::string temp = name; - LLStringUtil::replaceChar(temp, '-', '+'); - LLStringUtil::replaceChar(temp, '_', '/'); - - U8 rawuuid[UUID_BYTES + 1]; - int len = apr_base64_decode_binary(rawuuid, temp.c_str() + 1); - if(len == UUID_BYTES) - { - // The decode succeeded. Stuff the bits into the result's UUID - memcpy(uuid.mData, rawuuid, UUID_BYTES); - result = true; - } - } - - if(!result) - { - // VIVOX: not a standard account name, just copy the URI name mURIString field - // and hope for the best. bpj - uuid.setNull(); // VIVOX, set the uuid field to nulls - } - - return result; -} - -std::string LLVivoxVoiceClient::sipURIFromName(std::string &name) -{ - std::string result; - result = "sip:"; - result += name; - result += "@"; - result += mVoiceSIPURIHostName; - -// LLStringUtil::toLower(result); - - return result; -} - -std::string LLVivoxVoiceClient::nameFromsipURI(const std::string &uri) -{ - std::string result; - - std::string::size_type sipOffset, atOffset; - sipOffset = uri.find("sip:"); - atOffset = uri.find("@"); - if((sipOffset != std::string::npos) && (atOffset != std::string::npos)) - { - result = uri.substr(sipOffset + 4, atOffset - (sipOffset + 4)); - } - - return result; -} - -bool LLVivoxVoiceClient::inSpatialChannel(void) -{ - bool result = false; - - if(mAudioSession) - { - result = mAudioSession->mIsSpatial; - } - - return result; -} - - -LLSD LLVivoxVoiceClient::getAudioSessionChannelInfo() -{ - LLSD result; - - if (mAudioSession) - { - result = mAudioSession->getVoiceChannelInfo(); - } - - return result; -} - -std::string LLVivoxVoiceClient::getAudioSessionHandle() -{ - std::string result; - - if(mAudioSession) - result = mAudioSession->mHandle; - - return result; -} - - -///////////////////////////// -// Sending updates of current state - -void LLVivoxVoiceClient::enforceTether(void) -{ - LLVector3d tethered = mCameraRequestedPosition; - - // constrain 'tethered' to within 50m of mAvatarPosition. - { - F32 max_dist = 50.0f; - LLVector3d camera_offset = mCameraRequestedPosition - mAvatarPosition; - F32 camera_distance = (F32)camera_offset.magVec(); - if(camera_distance > max_dist) - { - tethered = mAvatarPosition + - (max_dist / camera_distance) * camera_offset; - } - } - - if(dist_vec_squared(mCameraPosition, tethered) > 0.01) - { - mCameraPosition = tethered; - mSpatialCoordsDirty = true; - } -} - -void LLVivoxVoiceClient::updatePosition(void) -{ - - LLViewerRegion *region = gAgent.getRegion(); - if(region && isAgentAvatarValid()) - { - LLMatrix3 rot; - LLVector3d pos; - LLQuaternion qrot; - - // TODO: If camera and avatar velocity are actually used by the voice system, we could compute them here... - // They're currently always set to zero. - - // Send the current camera position to the voice code - rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis()); - pos = gAgent.getRegion()->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin()); - - LLVivoxVoiceClient::getInstance()->setCameraPosition( - pos, // position - LLVector3::zero, // velocity - rot); // rotation matrix - - // Send the current avatar position to the voice code - qrot = gAgentAvatarp->getRootJoint()->getWorldRotation(); - pos = gAgentAvatarp->getPositionGlobal(); - - // TODO: Can we get the head offset from outside the LLVOAvatar? - // pos += LLVector3d(mHeadOffset); - pos += LLVector3d(0.f, 0.f, 1.f); - - LLVivoxVoiceClient::getInstance()->setAvatarPosition( - pos, // position - LLVector3::zero, // velocity - qrot); // rotation matrix - } -} - -void LLVivoxVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) -{ - mCameraRequestedPosition = position; - - if(mCameraVelocity != velocity) - { - mCameraVelocity = velocity; - mSpatialCoordsDirty = true; - } - - if(mCameraRot != rot) - { - mCameraRot = rot; - mSpatialCoordsDirty = true; - } -} - -void LLVivoxVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot) -{ - if(dist_vec_squared(mAvatarPosition, position) > 0.01) - { - mAvatarPosition = position; - mSpatialCoordsDirty = true; - } - - if(mAvatarVelocity != velocity) - { - mAvatarVelocity = velocity; - mSpatialCoordsDirty = true; - } - - // If the two rotations are not exactly equal test their dot product - // to get the cos of the angle between them. - // If it is too small, don't update. - F32 rot_cos_diff = llabs(dot(mAvatarRot, rot)); - if ((mAvatarRot != rot) && (rot_cos_diff < MINUSCULE_ANGLE_COS)) - { - mAvatarRot = rot; - mSpatialCoordsDirty = true; - } -} - -bool LLVivoxVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name) -{ - bool result = false; - - if(region) - { - name = region->getName(); - } - - if(!name.empty()) - result = true; - - return result; -} - -void LLVivoxVoiceClient::leaveChannel(void) -{ - if (mIsInChannel) - { - LL_DEBUGS("Voice") << "leaving channel for teleport/logout" << LL_ENDL; - mChannelName.clear(); - } - sessionTerminate(); -} - -void LLVivoxVoiceClient::setMuteMic(bool muted) -{ - if(mMuteMic != muted) - { - mMuteMic = muted; - mMuteMicDirty = true; - } -} - -void LLVivoxVoiceClient::setVoiceEnabled(bool enabled) -{ - LL_DEBUGS("Voice") - << "( " << (enabled ? "enabled" : "disabled") << " )" - << " was "<< (mVoiceEnabled ? "enabled" : "disabled") - << " coro "<< (mIsCoroutineActive ? "active" : "inactive") - << LL_ENDL; - - if (enabled != mVoiceEnabled) - { - // TODO: Refactor this so we don't call into LLVoiceChannel, but simply - // use the status observer - mVoiceEnabled = enabled; - LLVoiceClientStatusObserver::EStatusType status; - - if (enabled) - { - LL_DEBUGS("Voice") << "enabling" << LL_ENDL; - LLVoiceChannel::getCurrentVoiceChannel()->activate(); - status = LLVoiceClientStatusObserver::STATUS_VOICE_ENABLED; - - if (!mIsCoroutineActive) - { - LLCoros::instance().launch("LLVivoxVoiceClient::voiceControlCoro", - boost::bind(&LLVivoxVoiceClient::voiceControlCoro, LLVivoxVoiceClient::getInstance())); - } - else - { - LL_DEBUGS("Voice") << "coro should be active.. not launching" << LL_ENDL; - } - } - else - { - // Turning voice off looses your current channel -- this makes sure the UI isn't out of sync when you re-enable it. - LLVoiceChannel::getCurrentVoiceChannel()->deactivate(); - gAgent.setVoiceConnected(false); - status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED; - mCurrentParcelLocalID = -1; - mCurrentRegionName.clear(); - } - - notifyStatusObservers(status); - } - else - { - LL_DEBUGS("Voice") << " no-op" << LL_ENDL; - } -} - - -void LLVivoxVoiceClient::setEarLocation(S32 loc) -{ - if(mEarLocation != loc) - { - LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL; - - mEarLocation = loc; - mSpatialCoordsDirty = true; - } -} - -void LLVivoxVoiceClient::setVoiceVolume(F32 volume) -{ - int scaled_volume = scale_speaker_volume(volume); - - if(scaled_volume != mSpeakerVolume) - { - int min_volume = scale_speaker_volume(0); - if((scaled_volume == min_volume) || (mSpeakerVolume == min_volume)) - { - mSpeakerMuteDirty = true; - } - - mSpeakerVolume = scaled_volume; - mSpeakerVolumeDirty = true; - } -} - -void LLVivoxVoiceClient::setMicGain(F32 volume) -{ - int scaled_volume = scale_mic_volume(volume); - - if(scaled_volume != mMicVolume) - { - mMicVolume = scaled_volume; - mMicVolumeDirty = true; - } -} - -///////////////////////////// -// Accessors for data related to nearby speakers - -std::string LLVivoxVoiceClient::getDisplayName(const LLUUID& id) -{ - std::string result; - if (mProcessChannels) - { - participantStatePtr_t participant(findParticipantByID(id)); - if (participant) - { - result = participant->mDisplayName; - } - } - - return result; -} - - - -bool LLVivoxVoiceClient::getIsSpeaking(const LLUUID& id) -{ - bool result = false; - if (mProcessChannels) - { - participantStatePtr_t participant(findParticipantByID(id)); - if (participant) - { - if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) - { - participant->mIsSpeaking = false; - } - result = participant->mIsSpeaking; - } - } - - return result; -} - -bool LLVivoxVoiceClient::getIsModeratorMuted(const LLUUID& id) -{ - bool result = false; - if (!mProcessChannels) - { - return false; - } - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mIsModeratorMuted; - } - - return result; -} - -F32 LLVivoxVoiceClient::getCurrentPower(const LLUUID& id) -{ - F32 result = 0; - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mPower; - } - - return result; -} - - - -bool LLVivoxVoiceClient::getUsingPTT(const LLUUID& id) -{ - bool result = false; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - // I'm not sure what the semantics of this should be. - // Does "using PTT" mean they're configured with a push-to-talk button? - // For now, we know there's no PTT mechanism in place, so nobody is using it. - } - - return result; -} - -// External accessors. -F32 LLVivoxVoiceClient::getUserVolume(const LLUUID& id) -{ - // Minimum volume will be returned for users with voice disabled - F32 result = LLVoiceClient::VOLUME_MIN; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mVolume; - - // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. - // LL_DEBUGS("Voice") << "mVolume = " << result << " for " << id << LL_ENDL; - } - - return result; -} - -void LLVivoxVoiceClient::setUserVolume(const LLUUID& id, F32 volume) -{ - if(mAudioSession) - { - participantStatePtr_t participant(findParticipantByID(id)); - if (participant && !participant->mIsSelf) - { - if (!is_approx_equal(volume, LLVoiceClient::VOLUME_DEFAULT)) - { - // Store this volume setting for future sessions if it has been - // changed from the default - LLSpeakerVolumeStorage::getInstance()->storeSpeakerVolume(id, volume); - } - else - { - // Remove stored volume setting if it is returned to the default - LLSpeakerVolumeStorage::getInstance()->removeSpeakerVolume(id); - } - - participant->mVolume = llclamp(volume, LLVoiceClient::VOLUME_MIN, LLVoiceClient::VOLUME_MAX); - participant->mVolumeDirty = true; - mAudioSession->mVolumeDirty = true; - } - } -} - -std::string LLVivoxVoiceClient::getGroupID(const LLUUID& id) -{ - std::string result; - - participantStatePtr_t participant(findParticipantByID(id)); - if(participant) - { - result = participant->mGroupID; - } - - return result; -} - -void LLVivoxVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame) -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL; - - if(!mMainSessionGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "" - << "" << mMainSessionGroupHandle << "" - << "Start" - << "" << deltaFramesPerControlFrame << "" - << "" << "" << "" - << "false" - << "" << seconds << "" - << "\n\n\n"; - - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::recordingLoopSave(const std::string& filename) -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Flush)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "" - << "" << mMainSessionGroupHandle << "" - << "Flush" - << "" << filename << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::recordingStop() -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Stop)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "" - << "" << mMainSessionGroupHandle << "" - << "Stop" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::filePlaybackStart(const std::string& filename) -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Start)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "" - << "" << mMainSessionGroupHandle << "" - << "Start" - << "" << filename << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::filePlaybackStop() -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Stop)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "" - << "" << mMainSessionGroupHandle << "" - << "Stop" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::filePlaybackSetPaused(bool paused) -{ - // TODO: Implement once Vivox gives me a sample -} - -void LLVivoxVoiceClient::filePlaybackSetMode(bool vox, float speed) -{ - // TODO: Implement once Vivox gives me a sample -} - -//------------------------------------------------------------------------ -std::set> LLVivoxVoiceClient::sessionState::mSession; - - -LLVivoxVoiceClient::sessionState::sessionState() : - mErrorStatusCode(0), - mMediaStreamState(streamStateUnknown), - mCreateInProgress(false), - mMediaConnectInProgress(false), - mVoiceInvitePending(false), - mTextInvitePending(false), - mSynthesizedCallerID(false), - mIsChannel(false), - mIsSpatial(false), - mIsP2P(false), - mIncoming(false), - mVoiceActive(false), - mReconnect(false), - mVolumeDirty(false), - mMuteDirty(false), - mParticipantsChanged(false) -{ -} - -LLSD LLVivoxVoiceClient::sessionState::getVoiceChannelInfo() -{ - LLSD result; - - result["voice_server_type"] = VIVOX_VOICE_SERVER_TYPE; - result["channel_credentials"] = mHash; - result["channel_uri"] = mSIPURI; - result["session_handle"] = mHandle; - - return result; -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::createSession() -{ - sessionState::ptr_t ptr(new sessionState()); - - std::pair::iterator, bool> result = mSession.insert(ptr); - - if (result.second) - ptr->mMyIterator = result.first; - - return ptr; -} - -LLVivoxVoiceClient::sessionState::~sessionState() -{ - LL_INFOS("Voice") << "Destroying session handle=" << mHandle << " SIP=" << mSIPURI << LL_ENDL; - if (mMyIterator != mSession.end()) - mSession.erase(mMyIterator); - - removeAllParticipants(); -} - -bool LLVivoxVoiceClient::sessionState::isCallBackPossible() -{ - // This may change to be explicitly specified by vivox in the future... - // Currently, only PSTN P2P calls cannot be returned. - // Conveniently, this is also the only case where we synthesize a caller UUID. - return !mSynthesizedCallerID; -} - -bool LLVivoxVoiceClient::sessionState::isTextIMPossible() -{ - // This may change to be explicitly specified by vivox in the future... - return !mSynthesizedCallerID; -} - - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByHandle(const std::string &handle) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByHandle, _1, handle)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchCreatingSessionByURI(const std::string &uri) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByCreatingURI, _1, uri)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByURI(const std::string &uri) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testBySIPOrAlterateURI, _1, uri)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -/*static*/ -LLVivoxVoiceClient::sessionState::ptr_t LLVivoxVoiceClient::sessionState::matchSessionByParticipant(const LLUUID &participant_id) -{ - sessionStatePtr_t result; - - // *TODO: My kingdom for a lambda! - std::set::iterator it = std::find_if(mSession.begin(), mSession.end(), boost::bind(testByCallerId, _1, participant_id)); - - if (it != mSession.end()) - result = (*it).lock(); - - return result; -} - -void LLVivoxVoiceClient::sessionState::for_each(sessionFunc_t func) -{ - std::for_each(mSession.begin(), mSession.end(), boost::bind(for_eachPredicate, _1, func)); -} - -// simple test predicates. -// *TODO: These should be made into lambdas when we can pull the trigger on newer C++ features. -bool LLVivoxVoiceClient::sessionState::testByHandle(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string handle) -{ - ptr_t aLock(a.lock()); - - return aLock ? aLock->mHandle == handle : false; -} - -bool LLVivoxVoiceClient::sessionState::testByCreatingURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri) -{ - ptr_t aLock(a.lock()); - - return aLock ? (aLock->mCreateInProgress && (aLock->mSIPURI == uri)) : false; -} - -bool LLVivoxVoiceClient::sessionState::testBySIPOrAlterateURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri) -{ - ptr_t aLock(a.lock()); - - return aLock ? ((aLock->mSIPURI == uri) || (aLock->mAlternateSIPURI == uri)) : false; -} - - -bool LLVivoxVoiceClient::sessionState::testByCallerId(const LLVivoxVoiceClient::sessionState::wptr_t &a, LLUUID participantId) -{ - ptr_t aLock(a.lock()); - - return aLock ? ((aLock->mCallerID == participantId) || (aLock->mIMSessionID == participantId)) : false; -} - -/*static*/ -void LLVivoxVoiceClient::sessionState::for_eachPredicate(const LLVivoxVoiceClient::sessionState::wptr_t &a, sessionFunc_t func) -{ - ptr_t aLock(a.lock()); - - if (aLock) - func(aLock); - else - { - LL_WARNS("Voice") << "Stale handle in session map!" << LL_ENDL; - } -} - - - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const std::string &handle) -{ - sessionStatePtr_t result; - sessionMap::iterator iter = mSessionsByHandle.find(handle); - if(iter != mSessionsByHandle.end()) - { - result = iter->second; - } - - return result; -} - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSessionBeingCreatedByURI(const std::string &uri) -{ - sessionStatePtr_t result = sessionState::matchCreatingSessionByURI(uri); - - return result; -} - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::findSession(const LLUUID &participant_id) -{ - sessionStatePtr_t result = sessionState::matchSessionByParticipant(participant_id); - - return result; -} - -LLVivoxVoiceClient::sessionStatePtr_t LLVivoxVoiceClient::addSession(const std::string &uri, const std::string &handle) -{ - sessionStatePtr_t result; - - if(handle.empty()) - { - // No handle supplied. - // Check whether there's already a session with this URI - result = sessionState::matchSessionByURI(uri); - } - else // (!handle.empty()) - { - // Check for an existing session with this handle - sessionMap::iterator iter = mSessionsByHandle.find(handle); - - if(iter != mSessionsByHandle.end()) - { - result = iter->second; - } - } - - if(!result) - { - // No existing session found. - - LL_DEBUGS("Voice") << "adding new session: handle \"" << handle << "\" URI " << uri << LL_ENDL; - result = sessionState::createSession(); - result->mSIPURI = uri; - result->mHandle = handle; - - if (LLVoiceClient::instance().getVoiceEffectEnabled()) - { - result->mVoiceFontID = LLVoiceClient::instance().getVoiceEffectDefault(); - } - - if(!result->mHandle.empty()) - { - // *TODO: Rider: This concerns me. There is a path (via switchChannel) where - // we do not track the session. In theory this means that we could end up with - // a mAuidoSession that does not match the session tracked in mSessionsByHandle - mSessionsByHandle.insert(sessionMap::value_type(result->mHandle, result)); - } - } - else - { - // Found an existing session - - if(uri != result->mSIPURI) - { - // TODO: Should this be an internal error? - LL_DEBUGS("Voice") << "changing uri from " << result->mSIPURI << " to " << uri << LL_ENDL; - setSessionURI(result, uri); - } - - if(handle != result->mHandle) - { - if(handle.empty()) - { - // There's at least one race condition where where addSession was clearing an existing session handle, which caused things to break. - LL_DEBUGS("Voice") << "NOT clearing handle " << result->mHandle << LL_ENDL; - } - else - { - // TODO: Should this be an internal error? - LL_DEBUGS("Voice") << "changing handle from " << result->mHandle << " to " << handle << LL_ENDL; - setSessionHandle(result, handle); - } - } - - LL_DEBUGS("Voice") << "returning existing session: handle " << handle << " URI " << uri << LL_ENDL; - } - - verifySessionState(); - - return result; -} - -void LLVivoxVoiceClient::clearSessionHandle(const sessionStatePtr_t &session) -{ - if (session) - { - if (!session->mHandle.empty()) - { - sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); - if (iter != mSessionsByHandle.end()) - { - mSessionsByHandle.erase(iter); - } - } - else - { - LL_WARNS("Voice") << "Session has empty handle!" << LL_ENDL; - } - } - else - { - LL_WARNS("Voice") << "Attempt to clear NULL session!" << LL_ENDL; - } - -} - -void LLVivoxVoiceClient::setSessionHandle(const sessionStatePtr_t &session, const std::string &handle) -{ - // Have to remove the session from the handle-indexed map before changing the handle, or things will break badly. - - if(!session->mHandle.empty()) - { - // Remove session from the map if it should have been there. - sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); - if(iter != mSessionsByHandle.end()) - { - if(iter->second != session) - { - LL_WARNS("Voice") << "Internal error: session mismatch! Session may have been duplicated. Removing version in map." << LL_ENDL; - } - - mSessionsByHandle.erase(iter); - } - else - { - LL_WARNS("Voice") << "Attempt to remove session with handle " << session->mHandle << " not found in map!" << LL_ENDL; - } - } - - session->mHandle = handle; - - if(!handle.empty()) - { - mSessionsByHandle.insert(sessionMap::value_type(session->mHandle, session)); - } - - verifySessionState(); -} - -void LLVivoxVoiceClient::setSessionURI(const sessionStatePtr_t &session, const std::string &uri) -{ - // There used to be a map of session URIs to sessions, which made this complex.... - session->mSIPURI = uri; - - verifySessionState(); -} - -void LLVivoxVoiceClient::deleteSession(const sessionStatePtr_t &session) -{ - // Remove the session from the handle map - if(!session->mHandle.empty()) - { - sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); - if(iter != mSessionsByHandle.end()) - { - if(iter->second != session) - { - LL_WARNS("Voice") << "Internal error: session mismatch, removing session in map." << LL_ENDL; - } - mSessionsByHandle.erase(iter); - } - } - - // At this point, the session should be unhooked from all lists and all state should be consistent. - verifySessionState(); - - // If this is the current audio session, clean up the pointer which will soon be dangling. - if(mAudioSession == session) - { - mAudioSession.reset(); - } - - // ditto for the next audio session - if(mNextAudioSession == session) - { - mNextAudioSession.reset(); - } - -} - -void LLVivoxVoiceClient::deleteAllSessions() -{ - LL_DEBUGS("Voice") << LL_ENDL; - - while (!mSessionsByHandle.empty()) - { - const sessionStatePtr_t session = mSessionsByHandle.begin()->second; - deleteSession(session); - } - -} - -void LLVivoxVoiceClient::verifySessionState(void) -{ - LL_DEBUGS("Voice") << "Sessions in handle map=" << mSessionsByHandle.size() << LL_ENDL; - sessionState::VerifySessions(); -} - -void LLVivoxVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) -{ - mParticipantObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) -{ - mParticipantObservers.erase(observer); -} - -void LLVivoxVoiceClient::notifyParticipantObservers() -{ - for (observer_set_t::iterator it = mParticipantObservers.begin(); - it != mParticipantObservers.end(); - ) - { - LLVoiceClientParticipantObserver* observer = *it; - observer->onParticipantsChanged(); - // In case onParticipantsChanged() deleted an entry. - it = mParticipantObservers.upper_bound(observer); - } -} - -void LLVivoxVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) -{ - mStatusObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) -{ - mStatusObservers.erase(observer); -} - -void LLVivoxVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status) -{ - LL_DEBUGS("Voice") << "( " << LLVoiceClientStatusObserver::status2string(status) << " )" - << " mAudioSession=" << mAudioSession - << LL_ENDL; - - if(mAudioSession) - { - if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN) - { - switch(mAudioSession->mErrorStatusCode) - { - case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break; - case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break; - case 20715: - //invalid channel, we may be using a set of poorly cached - //info - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - case 1009: - //invalid username and password - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - } - - // Reset the error code to make sure it won't be reused later by accident. - mAudioSession->mErrorStatusCode = 0; - } - else if(status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL) - { - switch(mAudioSession->mErrorStatusCode) - { - case HTTP_NOT_FOUND: // NOT_FOUND - // *TODO: Should this be 503? - case 480: // TEMPORARILY_UNAVAILABLE - case HTTP_REQUEST_TIME_OUT: // REQUEST_TIMEOUT - // call failed because other user was not available - // treat this as an error case - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - - // Reset the error code to make sure it won't be reused later by accident. - mAudioSession->mErrorStatusCode = 0; - break; - } - } - } - - LLSD channel_info = getAudioSessionChannelInfo(); - LL_DEBUGS("Voice") - << " " << LLVoiceClientStatusObserver::status2string(status) - << ", session channelInfo " << channel_info - << ", proximal is " << inSpatialChannel() - << LL_ENDL; - - if (!mProcessChannels) - { - // we're not processing...another voice module is. - // so nobody wants to hear from us. - return; - } - for (status_observer_set_t::iterator it = mStatusObservers.begin(); - it != mStatusObservers.end(); - ) - { - LLVoiceClientStatusObserver* observer = *it; - observer->onChange(status, channel_info, inSpatialChannel()); - // In case onError() deleted an entry. - it = mStatusObservers.upper_bound(observer); - } - - // skipped to avoid speak button blinking - if ( status != LLVoiceClientStatusObserver::STATUS_JOINING - && status != LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL - && status != LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED) - { - bool voice_status = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); - - LL_WARNS("Voice") << "Setting voice connected " << (voice_status ? "True" : "False") << LL_ENDL; - gAgent.setVoiceConnected(voice_status); - - if (voice_status) - { - LLAppViewer::instance()->postToMainCoro([=]() { LLFirstUse::speak(true); }); - } - } -} - -void LLVivoxVoiceClient::addObserver(LLFriendObserver* observer) -{ - mFriendObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLFriendObserver* observer) -{ - mFriendObservers.erase(observer); -} - -void LLVivoxVoiceClient::notifyFriendObservers() -{ - for (friend_observer_set_t::iterator it = mFriendObservers.begin(); - it != mFriendObservers.end(); - ) - { - LLFriendObserver* observer = *it; - it++; - // The only friend-related thing we notify on is online/offline transitions. - observer->changed(LLFriendObserver::ONLINE); - } -} - -void LLVivoxVoiceClient::lookupName(const LLUUID &id) -{ - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(id, boost::bind(&LLVivoxVoiceClient::onAvatarNameCache, this, _1, _2)); -} - -void LLVivoxVoiceClient::onAvatarNameCache(const LLUUID& agent_id, - const LLAvatarName& av_name) -{ - mAvatarNameCacheConnection.disconnect(); - std::string display_name = av_name.getDisplayName(); - avatarNameResolved(agent_id, display_name); -} - -void LLVivoxVoiceClient::predAvatarNameResolution(const LLVivoxVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name) -{ - participantStatePtr_t participant(session->findParticipantByID(id)); - if (participant) - { - // Found -- fill in the name - participant->mAccountName = name; - // and post a "participants updated" message to listeners later. - session->mParticipantsChanged = true; - } - - // Check whether this is a p2p session whose caller name just resolved - if (session->mCallerID == id) - { - // this session's "caller ID" just resolved. Fill in the name. - session->mName = name; - if (session->mTextInvitePending) - { - session->mTextInvitePending = false; - - // We don't need to call LLIMMgr::getInstance()->addP2PSession() here. The first incoming message will create the panel. - } - if (session->mVoiceInvitePending) - { - session->mVoiceInvitePending = false; - LLIMMgr::getInstance()->inviteToSession( - session->mIMSessionID, - session->mName, - session->mCallerID, - session->mName, - IM_SESSION_P2P_INVITE, - LLIMMgr::INVITATION_TYPE_VOICE, - session->getVoiceChannelInfo()); - } - } -} - -void LLVivoxVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name) -{ - sessionState::for_each(boost::bind(predAvatarNameResolution, _1, id, name)); -} - -bool LLVivoxVoiceClient::setVoiceEffect(const LLUUID& id) -{ - if (!mAudioSession) - { - return false; - } - - if (!id.isNull()) - { - if (mVoiceFontMap.empty()) - { - LL_DEBUGS("Voice") << "Voice fonts not available." << LL_ENDL; - return false; - } - else if (mVoiceFontMap.find(id) == mVoiceFontMap.end()) - { - LL_DEBUGS("Voice") << "Invalid voice font " << id << LL_ENDL; - return false; - } - } - - // *TODO: Check for expired fonts? - mAudioSession->mVoiceFontID = id; - - // *TODO: Separate voice font defaults for spatial chat and IM? - gSavedPerAccountSettings.setString("VoiceEffectDefault", id.asString()); - - sessionSetVoiceFontSendMessage(mAudioSession); - notifyVoiceFontObservers(); - - return true; -} - -const LLUUID LLVivoxVoiceClient::getVoiceEffect() -{ - return mAudioSession ? mAudioSession->mVoiceFontID : LLUUID::null; -} - -LLSD LLVivoxVoiceClient::getVoiceEffectProperties(const LLUUID& id) -{ - LLSD sd; - - voice_font_map_t::iterator iter = mVoiceFontMap.find(id); - if (iter != mVoiceFontMap.end()) - { - sd["template_only"] = false; - } - else - { - // Voice effect is not in the voice font map, see if there is a template - iter = mVoiceFontTemplateMap.find(id); - if (iter == mVoiceFontTemplateMap.end()) - { - LL_WARNS("Voice") << "Voice effect " << id << "not found." << LL_ENDL; - return sd; - } - sd["template_only"] = true; - } - - voiceFontEntry *font = iter->second; - sd["name"] = font->mName; - sd["expiry_date"] = font->mExpirationDate; - sd["is_new"] = font->mIsNew; - - return sd; -} - -LLVivoxVoiceClient::voiceFontEntry::voiceFontEntry(LLUUID& id) : - mID(id), - mFontIndex(0), - mFontType(VOICE_FONT_TYPE_NONE), - mFontStatus(VOICE_FONT_STATUS_NONE), - mIsNew(false) -{ - mExpiryTimer.stop(); -} - -LLVivoxVoiceClient::voiceFontEntry::~voiceFontEntry() -{ -} - -void LLVivoxVoiceClient::refreshVoiceEffectLists(bool clear_lists) -{ - if (clear_lists) - { - mVoiceFontsReceived = false; - deleteAllVoiceFonts(); - deleteVoiceFontTemplates(); - } - - accountGetSessionFontsSendMessage(); - accountGetTemplateFontsSendMessage(); -} - -const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectList() const -{ - return mVoiceFontList; -} - -const voice_effect_list_t& LLVivoxVoiceClient::getVoiceEffectTemplateList() const -{ - return mVoiceFontTemplateList; -} - -void LLVivoxVoiceClient::addVoiceFont(const S32 font_index, - const std::string &name, - const std::string &description, - const LLDate &expiration_date, - bool has_expired, - const S32 font_type, - const S32 font_status, - const bool template_font) -{ - // Vivox SessionFontIDs are not guaranteed to remain the same between - // sessions or grids so use a UUID for the name. - - // If received name is not a UUID, fudge one by hashing the name and type. - LLUUID font_id; - if (LLUUID::validate(name)) - { - font_id = LLUUID(name); - } - else - { - font_id.generate(STRINGIZE(font_type << ":" << name)); - } - - voiceFontEntry *font = NULL; - - voice_font_map_t& font_map = template_font ? mVoiceFontTemplateMap : mVoiceFontMap; - voice_effect_list_t& font_list = template_font ? mVoiceFontTemplateList : mVoiceFontList; - - // Check whether we've seen this font before. - voice_font_map_t::iterator iter = font_map.find(font_id); - bool new_font = (iter == font_map.end()); - - // Override the has_expired flag if we have passed the expiration_date as a double check. - if (expiration_date.secondsSinceEpoch() < (LLDate::now().secondsSinceEpoch() + VOICE_FONT_EXPIRY_INTERVAL)) - { - has_expired = true; - } - - if (has_expired) - { - LL_DEBUGS("VoiceFont") << "Expired " << (template_font ? "Template " : "") - << expiration_date.asString() << " " << font_id - << " (" << font_index << ") " << name << LL_ENDL; - - // Remove existing session fonts that have expired since we last saw them. - if (!new_font && !template_font) - { - deleteVoiceFont(font_id); - } - return; - } - - if (new_font) - { - // If it is a new font create a new entry. - font = new voiceFontEntry(font_id); - } - else - { - // Not a new font, update the existing entry - font = iter->second; - } - - if (font) - { - font->mFontIndex = font_index; - // Use the description for the human readable name if available, as the - // "name" may be a UUID. - font->mName = description.empty() ? name : description; - font->mFontType = font_type; - font->mFontStatus = font_status; - - // If the font is new or the expiration date has changed the expiry timers need updating. - if (!template_font && (new_font || font->mExpirationDate != expiration_date)) - { - font->mExpirationDate = expiration_date; - - // Set the expiry timer to trigger a notification when the voice font can no longer be used. - font->mExpiryTimer.start(); - font->mExpiryTimer.setExpiryAt(expiration_date.secondsSinceEpoch() - VOICE_FONT_EXPIRY_INTERVAL); - - // Only flag new session fonts after the first time we have fetched the list. - if (mVoiceFontsReceived) - { - font->mIsNew = true; - mVoiceFontsNew = true; - } - } - - LL_DEBUGS("VoiceFont") << (template_font ? "Template " : "") - << font->mExpirationDate.asString() << " " << font->mID - << " (" << font->mFontIndex << ") " << name << LL_ENDL; - - if (new_font) - { - font_map.insert(voice_font_map_t::value_type(font->mID, font)); - font_list.insert(voice_effect_list_t::value_type(font->mName, font->mID)); - } - - mVoiceFontListDirty = true; - - // Debugging stuff - - if (font_type < VOICE_FONT_TYPE_NONE || font_type >= VOICE_FONT_TYPE_UNKNOWN) - { - LL_WARNS("VoiceFont") << "Unknown voice font type: " << font_type << LL_ENDL; - } - if (font_status < VOICE_FONT_STATUS_NONE || font_status >= VOICE_FONT_STATUS_UNKNOWN) - { - LL_WARNS("VoiceFont") << "Unknown voice font status: " << font_status << LL_ENDL; - } - } -} - -void LLVivoxVoiceClient::expireVoiceFonts() -{ - // *TODO: If we are selling voice fonts in packs, there are probably - // going to be a number of fonts with the same expiration time, so would - // be more efficient to just keep a list of expiration times rather - // than checking each font individually. - - bool have_expired = false; - bool expired_in_use = false; - - LLUUID current_effect = LLVoiceClient::instance().getVoiceEffectDefault(); - - voice_font_map_t::iterator iter; - for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter) - { - voiceFontEntry* voice_font = iter->second; - LLFrameTimer& expiry_timer = voice_font->mExpiryTimer; - - // Check for expired voice fonts - if (expiry_timer.getStarted() && expiry_timer.hasExpired()) - { - // Check whether it is the active voice font - if (voice_font->mID == current_effect) - { - // Reset to no voice effect. - setVoiceEffect(LLUUID::null); - expired_in_use = true; - } - - LL_DEBUGS("Voice") << "Voice Font " << voice_font->mName << " has expired." << LL_ENDL; - deleteVoiceFont(voice_font->mID); - have_expired = true; - } - } - - LLSD args; - args["URL"] = LLTrans::getString("voice_morphing_url"); - args["PREMIUM_URL"] = LLTrans::getString("premium_voice_morphing_url"); - - // Give a notification if any voice fonts have expired. - if (have_expired) - { - if (expired_in_use) - { - LLNotificationsUtil::add("VoiceEffectsExpiredInUse", args); - } - else - { - LLNotificationsUtil::add("VoiceEffectsExpired", args); - } - - // Refresh voice font lists in the UI. - notifyVoiceFontObservers(); - } -} - -void LLVivoxVoiceClient::deleteVoiceFont(const LLUUID& id) -{ - // Remove the entry from the voice font list. - voice_effect_list_t::iterator list_iter = mVoiceFontList.begin(); - while (list_iter != mVoiceFontList.end()) - { - if (list_iter->second == id) - { - LL_DEBUGS("VoiceFont") << "Removing " << id << " from the voice font list." << LL_ENDL; - list_iter = mVoiceFontList.erase(list_iter); - mVoiceFontListDirty = true; - } - else - { - ++list_iter; - } - } - - // Find the entry in the voice font map and erase its data. - voice_font_map_t::iterator map_iter = mVoiceFontMap.find(id); - if (map_iter != mVoiceFontMap.end()) - { - delete map_iter->second; - } - - // Remove the entry from the voice font map. - mVoiceFontMap.erase(map_iter); -} - -void LLVivoxVoiceClient::deleteAllVoiceFonts() -{ - mVoiceFontList.clear(); - - voice_font_map_t::iterator iter; - for (iter = mVoiceFontMap.begin(); iter != mVoiceFontMap.end(); ++iter) - { - delete iter->second; - } - mVoiceFontMap.clear(); -} - -void LLVivoxVoiceClient::deleteVoiceFontTemplates() -{ - mVoiceFontTemplateList.clear(); - - voice_font_map_t::iterator iter; - for (iter = mVoiceFontTemplateMap.begin(); iter != mVoiceFontTemplateMap.end(); ++iter) - { - delete iter->second; - } - mVoiceFontTemplateMap.clear(); -} - -S32 LLVivoxVoiceClient::getVoiceFontIndex(const LLUUID& id) const -{ - S32 result = 0; - if (!id.isNull()) - { - voice_font_map_t::const_iterator it = mVoiceFontMap.find(id); - if (it != mVoiceFontMap.end()) - { - result = it->second->mFontIndex; - } - else - { - LL_WARNS("VoiceFont") << "Selected voice font " << id << " is not available." << LL_ENDL; - } - } - return result; -} - -S32 LLVivoxVoiceClient::getVoiceFontTemplateIndex(const LLUUID& id) const -{ - S32 result = 0; - if (!id.isNull()) - { - voice_font_map_t::const_iterator it = mVoiceFontTemplateMap.find(id); - if (it != mVoiceFontTemplateMap.end()) - { - result = it->second->mFontIndex; - } - else - { - LL_WARNS("VoiceFont") << "Selected voice font template " << id << " is not available." << LL_ENDL; - } - } - return result; -} - -void LLVivoxVoiceClient::accountGetSessionFontsSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("VoiceFont") << "Requesting voice font list." << LL_ENDL; - - stream - << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::accountGetTemplateFontsSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("VoiceFont") << "Requesting voice font template list." << LL_ENDL; - - stream - << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::sessionSetVoiceFontSendMessage(const sessionStatePtr_t &session) -{ - S32 font_index = getVoiceFontIndex(session->mVoiceFontID); - LL_DEBUGS("VoiceFont") << "Requesting voice font: " << session->mVoiceFontID << " (" << font_index << "), session handle: " << session->mHandle << LL_ENDL; - - std::ostringstream stream; - - stream - << "" - << "" << session->mHandle << "" - << "" << font_index << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVivoxVoiceClient::accountGetSessionFontsResponse(int statusCode, const std::string &statusString) -{ - if (mIsWaitingForFonts) - { - // *TODO: We seem to get multiple events of this type. Should figure a way to advance only after - // receiving the last one. - LLSD result(LLSDMap("voice_fonts", LLSD::Boolean(true))); - - mVivoxPump.post(result); - } - notifyVoiceFontObservers(); - mVoiceFontsReceived = true; -} - -void LLVivoxVoiceClient::accountGetTemplateFontsResponse(int statusCode, const std::string &statusString) -{ - // Voice font list entries were updated via addVoiceFont() during parsing. - notifyVoiceFontObservers(); -} -void LLVivoxVoiceClient::addObserver(LLVoiceEffectObserver* observer) -{ - mVoiceFontObservers.insert(observer); -} - -void LLVivoxVoiceClient::removeObserver(LLVoiceEffectObserver* observer) -{ - mVoiceFontObservers.erase(observer); -} - -// method checks the item in VoiceMorphing menu for appropriate current voice font -bool LLVivoxVoiceClient::onCheckVoiceEffect(const std::string& voice_effect_name) -{ - LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface(); - if (NULL != effect_interfacep) - { - const LLUUID& currect_voice_effect_id = effect_interfacep->getVoiceEffect(); - - if (currect_voice_effect_id.isNull()) - { - if (voice_effect_name == "NoVoiceMorphing") - { - return true; - } - } - else - { - const LLSD& voice_effect_props = effect_interfacep->getVoiceEffectProperties(currect_voice_effect_id); - if (voice_effect_props["name"].asString() == voice_effect_name) - { - return true; - } - } - } - - return false; -} - -// method changes voice font for selected VoiceMorphing menu item -void LLVivoxVoiceClient::onClickVoiceEffect(const std::string& voice_effect_name) -{ - LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface(); - if (NULL != effect_interfacep) - { - if (voice_effect_name == "NoVoiceMorphing") - { - effect_interfacep->setVoiceEffect(LLUUID()); - return; - } - const voice_effect_list_t& effect_list = effect_interfacep->getVoiceEffectList(); - if (!effect_list.empty()) - { - for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it) - { - if (voice_effect_name == it->first) - { - effect_interfacep->setVoiceEffect(it->second); - return; - } - } - } - } -} - -// it updates VoiceMorphing menu items in accordance with purchased properties -void LLVivoxVoiceClient::updateVoiceMorphingMenu() -{ - if (mVoiceFontListDirty) - { - LLVoiceEffectInterface * effect_interfacep = LLVoiceClient::instance().getVoiceEffectInterface(); - if (effect_interfacep) - { - const voice_effect_list_t& effect_list = effect_interfacep->getVoiceEffectList(); - if (!effect_list.empty()) - { - LLMenuGL * voice_morphing_menup = gMenuBarView->findChildMenuByName("VoiceMorphing", true); - - if (NULL != voice_morphing_menup) - { - S32 items = voice_morphing_menup->getItemCount(); - if (items > 0) - { - voice_morphing_menup->erase(1, items - 3, false); - - S32 pos = 1; - for (voice_effect_list_t::const_iterator it = effect_list.begin(); it != effect_list.end(); ++it) - { - LLMenuItemCheckGL::Params p; - p.name = it->first; - p.label = it->first; - p.on_check.function(boost::bind(&LLVivoxVoiceClient::onCheckVoiceEffect, this, it->first)); - p.on_click.function(boost::bind(&LLVivoxVoiceClient::onClickVoiceEffect, this, it->first)); - LLMenuItemCheckGL * voice_effect_itemp = LLUICtrlFactory::create(p); - voice_morphing_menup->insert(pos++, voice_effect_itemp, false); - } - - voice_morphing_menup->needsArrange(); - } - } - } - } - } -} -void LLVivoxVoiceClient::notifyVoiceFontObservers() -{ - LL_DEBUGS("VoiceFont") << "Notifying voice effect observers. Lists changed: " << mVoiceFontListDirty << LL_ENDL; - - updateVoiceMorphingMenu(); - - for (voice_font_observer_set_t::iterator it = mVoiceFontObservers.begin(); - it != mVoiceFontObservers.end();) - { - LLVoiceEffectObserver* observer = *it; - observer->onVoiceEffectChanged(mVoiceFontListDirty); - // In case onVoiceEffectChanged() deleted an entry. - it = mVoiceFontObservers.upper_bound(observer); - } - mVoiceFontListDirty = false; - - // If new Voice Fonts have been added notify the user. - if (mVoiceFontsNew) - { - if (mVoiceFontsReceived) - { - LLNotificationsUtil::add("VoiceEffectsNew"); - } - mVoiceFontsNew = false; - } -} - -void LLVivoxVoiceClient::enablePreviewBuffer(bool enable) -{ - LLSD result; - mCaptureBufferMode = enable; - - if (enable) - result["recplay"] = "start"; - else - result["recplay"] = "quit"; - - mVivoxPump.post(result); - - if(mCaptureBufferMode && mIsInChannel) - { - LL_DEBUGS("Voice") << "no channel" << LL_ENDL; - sessionTerminate(); - } -} - -void LLVivoxVoiceClient::recordPreviewBuffer() -{ - if (!mCaptureBufferMode) - { - LL_DEBUGS("Voice") << "Not in voice effect preview mode, cannot start recording." << LL_ENDL; - mCaptureBufferRecording = false; - return; - } - - mCaptureBufferRecording = true; - - LLSD result(LLSDMap("recplay", "record")); - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::playPreviewBuffer(const LLUUID& effect_id) -{ - if (!mCaptureBufferMode) - { - LL_DEBUGS("Voice") << "Not in voice effect preview mode, no buffer to play." << LL_ENDL; - mCaptureBufferRecording = false; - return; - } - - if (!mCaptureBufferRecorded) - { - // Can't play until we have something recorded! - mCaptureBufferPlaying = false; - return; - } - - mPreviewVoiceFont = effect_id; - mCaptureBufferPlaying = true; - - LLSD result(LLSDMap("recplay", "playback")); - mVivoxPump.post(result); -} - -void LLVivoxVoiceClient::stopPreviewBuffer() -{ - mCaptureBufferRecording = false; - mCaptureBufferPlaying = false; - - LLSD result(LLSDMap("recplay", "quit")); - mVivoxPump.post(result); -} - -bool LLVivoxVoiceClient::isPreviewRecording() -{ - return (mCaptureBufferMode && mCaptureBufferRecording); -} - -bool LLVivoxVoiceClient::isPreviewPlaying() -{ - return (mCaptureBufferMode && mCaptureBufferPlaying); -} - -void LLVivoxVoiceClient::captureBufferRecordStartSendMessage() -{ if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Starting audio capture to buffer." << LL_ENDL; - - // Start capture - stream - << "" - << "" - << "\n\n\n"; - - // Unmute the mic - stream << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "false" - << "\n\n\n"; - - // Dirty the mute mic state so that it will get reset when we finishing previewing - mMuteMicDirty = true; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::captureBufferRecordStopSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Stopping audio capture to buffer." << LL_ENDL; - - // Mute the mic. Mic mute state was dirtied at recording start, so will be reset when finished previewing. - stream << "" - << "" << LLVivoxSecurity::getInstance()->connectorHandle() << "" - << "true" - << "\n\n\n"; - - // Stop capture - stream - << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::captureBufferPlayStartSendMessage(const LLUUID& voice_font_id) -{ - if(mAccountLoggedIn) - { - // Track how may play requests are sent, so we know how many stop events to - // expect before play actually stops. - ++mPlayRequestCount; - - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Starting audio buffer playback." << LL_ENDL; - - S32 font_index = getVoiceFontTemplateIndex(voice_font_id); - LL_DEBUGS("Voice") << "With voice font: " << voice_font_id << " (" << font_index << ")" << LL_ENDL; - - stream - << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "" << font_index << "" - << "" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVivoxVoiceClient::captureBufferPlayStopSendMessage() -{ - if(mAccountLoggedIn) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Stopping audio buffer playback." << LL_ENDL; - - stream - << "" - << "" << LLVivoxSecurity::getInstance()->accountHandle() << "" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -LLVivoxProtocolParser::LLVivoxProtocolParser() -{ - parser = XML_ParserCreate(NULL); - - reset(); -} - -void LLVivoxProtocolParser::reset() -{ - responseDepth = 0; - ignoringTags = false; - accumulateText = false; - energy = 0.f; - hasText = false; - hasAudio = false; - hasVideo = false; - terminated = false; - ignoreDepth = 0; - isChannel = false; - incoming = false; - enabled = false; - isEvent = false; - isLocallyMuted = false; - isModeratorMuted = false; - isSpeaking = false; - participantType = 0; - returnCode = -1; - state = 0; - statusCode = 0; - volume = 0; - textBuffer.clear(); - alias.clear(); - numberOfAliases = 0; - applicationString.clear(); -} - -//virtual -LLVivoxProtocolParser::~LLVivoxProtocolParser() -{ - if (parser) - XML_ParserFree(parser); -} - -static LLTrace::BlockTimerStatHandle FTM_VIVOX_PROCESS("Vivox Process"); - -// virtual -LLIOPipe::EStatus LLVivoxProtocolParser::process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump) -{ - LL_RECORD_BLOCK_TIME(FTM_VIVOX_PROCESS); - LLBufferStream istr(channels, buffer.get()); - std::ostringstream ostr; - while (istr.good()) - { - char buf[1024]; - istr.read(buf, sizeof(buf)); - mInput.append(buf, istr.gcount()); - } - - // Look for input delimiter(s) in the input buffer. If one is found, send the message to the xml parser. - size_t start = 0; - size_t delim; - while((delim = mInput.find("\n\n\n", start)) != std::string::npos) - { - - // Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser) - reset(); - - XML_ParserReset(parser, NULL); - XML_SetElementHandler(parser, ExpatStartTag, ExpatEndTag); - XML_SetCharacterDataHandler(parser, ExpatCharHandler); - XML_SetUserData(parser, this); - XML_Parse(parser, mInput.data() + start, static_cast(delim - start), false); - - - LL_DEBUGS("VivoxProtocolParser") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL; - start = delim + 3; - } - - if(start != 0) - mInput = mInput.substr(start); - - LL_DEBUGS("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL; - - if(!LLVivoxVoiceClient::sConnected) - { - // If voice has been disabled, we just want to close the socket. This does so. - LL_INFOS("Voice") << "returning STATUS_STOP" << LL_ENDL; - return STATUS_STOP; - } - - return STATUS_OK; -} - -void XMLCALL LLVivoxProtocolParser::ExpatStartTag(void *data, const char *el, const char **attr) -{ - if (data) - { - LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; - object->StartTag(el, attr); - } -} - -// -------------------------------------------------------------------------------- - -void XMLCALL LLVivoxProtocolParser::ExpatEndTag(void *data, const char *el) -{ - if (data) - { - LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; - object->EndTag(el); - } -} - -// -------------------------------------------------------------------------------- - -void XMLCALL LLVivoxProtocolParser::ExpatCharHandler(void *data, const XML_Char *s, int len) -{ - if (data) - { - LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; - object->CharData(s, len); - } -} - -// -------------------------------------------------------------------------------- - - -void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) -{ - // Reset the text accumulator. We shouldn't have strings that are inturrupted by new tags - textBuffer.clear(); - // only accumulate text if we're not ignoring tags. - accumulateText = !ignoringTags; - - if (responseDepth == 0) - { - isEvent = !stricmp("Event", tag); - - if (!stricmp("Response", tag) || isEvent) - { - // Grab the attributes - while (*attr) - { - const char *key = *attr++; - const char *value = *attr++; - - if (!stricmp("requestId", key)) - { - requestId = value; - } - else if (!stricmp("action", key)) - { - actionString = value; - } - else if (!stricmp("type", key)) - { - eventTypeString = value; - } - } - } - LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; - } - else - { - if (ignoringTags) - { - LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - } - else - { - LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; - - // Ignore the InputXml stuff so we don't get confused - if (!stricmp("InputXml", tag)) - { - ignoringTags = true; - ignoreDepth = responseDepth; - accumulateText = false; - - LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL; - } - else if (!stricmp("CaptureDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->clearCaptureDevices(); - } - else if (!stricmp("RenderDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->clearRenderDevices(); - } - else if (!stricmp("CaptureDevice", tag)) - { - deviceString.clear(); - } - else if (!stricmp("RenderDevice", tag)) - { - deviceString.clear(); - } - else if (!stricmp("SessionFont", tag)) - { - id = 0; - nameString.clear(); - descriptionString.clear(); - expirationDate = LLDate(); - hasExpired = false; - fontType = 0; - fontStatus = 0; - } - else if (!stricmp("TemplateFont", tag)) - { - id = 0; - nameString.clear(); - descriptionString.clear(); - expirationDate = LLDate(); - hasExpired = false; - fontType = 0; - fontStatus = 0; - } - else if (!stricmp("MediaCompletionType", tag)) - { - mediaCompletionType.clear(); - } - } - } - responseDepth++; -} - -// -------------------------------------------------------------------------------- - -void LLVivoxProtocolParser::EndTag(const char *tag) -{ - const std::string& string = textBuffer; - - responseDepth--; - - if (ignoringTags) - { - if (ignoreDepth == responseDepth) - { - LL_DEBUGS("VivoxProtocolParser") << "end of ignore" << LL_ENDL; - ignoringTags = false; - } - else - { - LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - } - } - - if (!ignoringTags) - { - LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - - // Closing a tag. Finalize the text we've accumulated and reset - if (!stricmp("ReturnCode", tag)) - returnCode = strtol(string.c_str(), NULL, 10); - else if (!stricmp("SessionHandle", tag)) - sessionHandle = string; - else if (!stricmp("SessionGroupHandle", tag)) - sessionGroupHandle = string; - else if (!stricmp("StatusCode", tag)) - statusCode = strtol(string.c_str(), NULL, 10); - else if (!stricmp("StatusString", tag)) - statusString = string; - else if (!stricmp("ParticipantURI", tag)) - uriString = string; - else if (!stricmp("Volume", tag)) - volume = strtol(string.c_str(), NULL, 10); - else if (!stricmp("Energy", tag)) - energy = (F32)strtod(string.c_str(), NULL); - else if (!stricmp("IsModeratorMuted", tag)) - isModeratorMuted = !stricmp(string.c_str(), "true"); - else if (!stricmp("IsSpeaking", tag)) - isSpeaking = !stricmp(string.c_str(), "true"); - else if (!stricmp("Alias", tag)) - alias = string; - else if (!stricmp("NumberOfAliases", tag)) - numberOfAliases = strtol(string.c_str(), NULL, 10); - else if (!stricmp("Application", tag)) - applicationString = string; - else if (!stricmp("ConnectorHandle", tag)) - connectorHandle = string; - else if (!stricmp("VersionID", tag)) - versionID = string; - else if (!stricmp("Version", tag)) - mBuildID = string; - else if (!stricmp("AccountHandle", tag)) - accountHandle = string; - else if (!stricmp("State", tag)) - state = strtol(string.c_str(), NULL, 10); - else if (!stricmp("URI", tag)) - uriString = string; - else if (!stricmp("IsChannel", tag)) - isChannel = !stricmp(string.c_str(), "true"); - else if (!stricmp("Incoming", tag)) - incoming = !stricmp(string.c_str(), "true"); - else if (!stricmp("Enabled", tag)) - enabled = !stricmp(string.c_str(), "true"); - else if (!stricmp("Name", tag)) - nameString = string; - else if (!stricmp("AudioMedia", tag)) - audioMediaString = string; - else if (!stricmp("ChannelName", tag)) - nameString = string; - else if (!stricmp("DisplayName", tag)) - displayNameString = string; - else if (!stricmp("Device", tag)) - deviceString = string; - else if (!stricmp("AccountName", tag)) - nameString = string; - else if (!stricmp("ParticipantType", tag)) - participantType = strtol(string.c_str(), NULL, 10); - else if (!stricmp("IsLocallyMuted", tag)) - isLocallyMuted = !stricmp(string.c_str(), "true"); - else if (!stricmp("MicEnergy", tag)) - energy = (F32)strtod(string.c_str(), NULL); - else if (!stricmp("ChannelName", tag)) - nameString = string; - else if (!stricmp("ChannelURI", tag)) - uriString = string; - else if (!stricmp("BuddyURI", tag)) - uriString = string; - else if (!stricmp("Presence", tag)) - statusString = string; - else if (!stricmp("CaptureDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->setDevicesListUpdated(true); - } - else if (!stricmp("RenderDevices", tag)) - { - LLVivoxVoiceClient::getInstance()->setDevicesListUpdated(true); - } - else if (!stricmp("CaptureDevice", tag)) - { - LLVivoxVoiceClient::getInstance()->addCaptureDevice(LLVoiceDevice(displayNameString, deviceString)); - } - else if (!stricmp("RenderDevice", tag)) - { - LLVivoxVoiceClient::getInstance()->addRenderDevice(LLVoiceDevice(displayNameString, deviceString)); - } - else if (!stricmp("BlockMask", tag)) - blockMask = string; - else if (!stricmp("PresenceOnly", tag)) - presenceOnly = string; - else if (!stricmp("AutoAcceptMask", tag)) - autoAcceptMask = string; - else if (!stricmp("AutoAddAsBuddy", tag)) - autoAddAsBuddy = string; - else if (!stricmp("MessageHeader", tag)) - messageHeader = string; - else if (!stricmp("MessageBody", tag)) - messageBody = string; - else if (!stricmp("NotificationType", tag)) - notificationType = string; - else if (!stricmp("HasText", tag)) - hasText = !stricmp(string.c_str(), "true"); - else if (!stricmp("HasAudio", tag)) - hasAudio = !stricmp(string.c_str(), "true"); - else if (!stricmp("HasVideo", tag)) - hasVideo = !stricmp(string.c_str(), "true"); - else if (!stricmp("Terminated", tag)) - terminated = !stricmp(string.c_str(), "true"); - else if (!stricmp("SubscriptionHandle", tag)) - subscriptionHandle = string; - else if (!stricmp("SubscriptionType", tag)) - subscriptionType = string; - else if (!stricmp("SessionFont", tag)) - { - LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, false); - } - else if (!stricmp("TemplateFont", tag)) - { - LLVivoxVoiceClient::getInstance()->addVoiceFont(id, nameString, descriptionString, expirationDate, hasExpired, fontType, fontStatus, true); - } - else if (!stricmp("ID", tag)) - { - id = strtol(string.c_str(), NULL, 10); - } - else if (!stricmp("Description", tag)) - { - descriptionString = string; - } - else if (!stricmp("ExpirationDate", tag)) - { - expirationDate = expiryTimeStampToLLDate(string); - } - else if (!stricmp("Expired", tag)) - { - hasExpired = !stricmp(string.c_str(), "1"); - } - else if (!stricmp("Type", tag)) - { - fontType = strtol(string.c_str(), NULL, 10); - } - else if (!stricmp("Status", tag)) - { - fontStatus = strtol(string.c_str(), NULL, 10); - } - else if (!stricmp("MediaCompletionType", tag)) - { - mediaCompletionType = string;; - } - - textBuffer.clear(); - accumulateText= false; - - if (responseDepth == 0) - { - // We finished all of the XML, process the data - processResponse(tag); - } - } -} - -// -------------------------------------------------------------------------------- - -void LLVivoxProtocolParser::CharData(const char *buffer, int length) -{ - /* - This method is called for anything that isn't a tag, which can be text you - want that lies between tags, and a lot of stuff you don't want like file formatting - (tabs, spaces, CR/LF, etc). - - Only copy text if we are in accumulate mode... - */ - if (accumulateText) - textBuffer.append(buffer, length); -} - -// -------------------------------------------------------------------------------- - -LLDate LLVivoxProtocolParser::expiryTimeStampToLLDate(const std::string& vivox_ts) -{ - // *HACK: Vivox reports the time incorrectly. LLDate also only parses a - // subset of valid ISO 8601 dates (only handles Z, not offsets). - // So just use the date portion and fix the time here. - std::string time_stamp = vivox_ts.substr(0, 10); - time_stamp += VOICE_FONT_EXPIRY_TIME; - - LL_DEBUGS("VivoxProtocolParser") << "Vivox timestamp " << vivox_ts << " modified to: " << time_stamp << LL_ENDL; - - return LLDate(time_stamp); -} - -// -------------------------------------------------------------------------------- - -void LLVivoxProtocolParser::processResponse(std::string tag) -{ - LL_DEBUGS("VivoxProtocolParser") << tag << LL_ENDL; - - // SLIM SDK: the SDK now returns a statusCode of "200" (OK) for success. This is a change vs. previous SDKs. - // According to Mike S., "The actual API convention is that responses with return codes of 0 are successful, regardless of the status code returned", - // so I believe this will give correct behavior. - - if(returnCode == 0) - statusCode = 0; - - if (isEvent) - { - const char *eventTypeCstr = eventTypeString.c_str(); - LL_DEBUGS("LowVoice") << eventTypeCstr << LL_ENDL; - - if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent")) - { - // These happen so often that logging them is pretty useless. - LL_DEBUGS("LowVoice") << "Updated Params: " << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << isModeratorMuted << ", " << isSpeaking << ", " << volume << ", " << energy << LL_ENDL; - LLVivoxVoiceClient::getInstance()->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy); - } - else if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent")) - { - LLVivoxVoiceClient::getInstance()->accountLoginStateChangeEvent(accountHandle, statusCode, statusString, state); - } - else if (!stricmp(eventTypeCstr, "SessionAddedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 - sip:confctl-1408789@bhr.vivox.com - true - false - - - */ - LLVivoxVoiceClient::getInstance()->sessionAddedEvent(uriString, alias, sessionHandle, sessionGroupHandle, isChannel, incoming, nameString, applicationString); - } - else if (!stricmp(eventTypeCstr, "SessionRemovedEvent")) - { - LLVivoxVoiceClient::getInstance()->sessionRemovedEvent(sessionHandle, sessionGroupHandle); - } - else if (!stricmp(eventTypeCstr, "SessionGroupUpdatedEvent")) - { - //nothng useful to process for this event, but we should not WARN that we have received it. - } - else if (!stricmp(eventTypeCstr, "SessionGroupAddedEvent")) - { - LLVivoxVoiceClient::getInstance()->sessionGroupAddedEvent(sessionGroupHandle); - } - else if (!stricmp(eventTypeCstr, "MediaStreamUpdatedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 - 200 - OK - 2 - false - - */ - LLVivoxVoiceClient::getInstance()->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming); - } - else if (!stricmp(eventTypeCstr, "MediaCompletionEvent")) - { - /* - - - AuxBufferAudioCapture - - */ - LLVivoxVoiceClient::getInstance()->mediaCompletionEvent(sessionGroupHandle, mediaCompletionType); - } - else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==4 - sip:xI5auBZ60SJWIk606-1JGRQ==@bhr.vivox.com - xI5auBZ60SJWIk606-1JGRQ== - - 0 - - */ - LL_DEBUGS("LowVoice") << "Added Params: " << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << nameString << ", " << displayNameString << ", " << participantType << LL_ENDL; - LLVivoxVoiceClient::getInstance()->participantAddedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString, displayNameString, participantType); - } - else if (!stricmp(eventTypeCstr, "ParticipantRemovedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==4 - sip:xtx7YNV-3SGiG7rA1fo5Ndw==@bhr.vivox.com - xtx7YNV-3SGiG7rA1fo5Ndw== - - */ - LL_DEBUGS("LowVoice") << "Removed params:" << sessionHandle << ", " << sessionGroupHandle << ", " << uriString << ", " << alias << ", " << nameString << LL_ENDL; - - LLVivoxVoiceClient::getInstance()->participantRemovedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString); - } - else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent")) - { - // These are really spammy in tuning mode - LLVivoxVoiceClient::getInstance()->auxAudioPropertiesEvent(energy); - } - else if (!stricmp(eventTypeCstr, "MessageEvent")) - { - //TODO: This probably is not received any more, it was used to support SLim clients - LLVivoxVoiceClient::getInstance()->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString); - } - else if (!stricmp(eventTypeCstr, "SessionNotificationEvent")) - { - //TODO: This probably is not received any more, it was used to support SLim clients - LLVivoxVoiceClient::getInstance()->sessionNotificationEvent(sessionHandle, uriString, notificationType); - } - else if (!stricmp(eventTypeCstr, "SessionUpdatedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 - sip:confctl-9@bhd.vivox.com - 0 - 50 - 1 - 0 - 000 - 0 - - */ - // We don't need to process this, but we also shouldn't warn on it, since that confuses people. - } - else if (!stricmp(eventTypeCstr, "SessionGroupRemovedEvent")) - { - // We don't need to process this, but we also shouldn't warn on it, since that confuses people. - } - else if (!stricmp(eventTypeCstr, "VoiceServiceConnectionStateChangedEvent")) - { - LLVivoxVoiceClient::getInstance()->voiceServiceConnectionStateChangedEvent(statusCode, statusString, mBuildID); - } - else if (!stricmp(eventTypeCstr, "AudioDeviceHotSwapEvent")) - { - /* - - RenderDeviceChanged< / EventType> - - Speakers(Turtle Beach P11 Headset)< / Device> - Speakers(Turtle Beach P11 Headset)< / DisplayName> - SpecificDevice< / Type> - < / RelevantDevice> - < / Event> - */ - // an audio device was removed or added, fetch and update the local list of audio devices. - LLVivoxVoiceClient::getInstance()->getCaptureDevicesSendMessage(); - LLVivoxVoiceClient::getInstance()->getRenderDevicesSendMessage(); - } - else - { - LL_WARNS("VivoxProtocolParser") << "Unknown event type " << eventTypeString << LL_ENDL; - } - } - else - { - const char *actionCstr = actionString.c_str(); - LL_DEBUGS("LowVoice") << actionCstr << LL_ENDL; - - if (!stricmp(actionCstr, "Session.Set3DPosition.1")) - { - // We don't need to process these - } - else if (!stricmp(actionCstr, "Connector.Create.1")) - { - LLVivoxVoiceClient::getInstance()->connectorCreateResponse(statusCode, statusString, connectorHandle, versionID); - } - else if (!stricmp(actionCstr, "Account.Login.1")) - { - LLVivoxVoiceClient::getInstance()->loginResponse(statusCode, statusString, accountHandle, numberOfAliases); - } - else if (!stricmp(actionCstr, "Session.Create.1")) - { - LLVivoxVoiceClient::getInstance()->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle); - } - else if (!stricmp(actionCstr, "SessionGroup.AddSession.1")) - { - LLVivoxVoiceClient::getInstance()->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle); - } - else if (!stricmp(actionCstr, "Session.Connect.1")) - { - LLVivoxVoiceClient::getInstance()->sessionConnectResponse(requestId, statusCode, statusString); - } - else if (!stricmp(actionCstr, "Account.Logout.1")) - { - LLVivoxVoiceClient::getInstance()->logoutResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Connector.InitiateShutdown.1")) - { - LLVivoxVoiceClient::getInstance()->connectorShutdownResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Account.GetSessionFonts.1")) - { - LLVivoxVoiceClient::getInstance()->accountGetSessionFontsResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Account.GetTemplateFonts.1")) - { - LLVivoxVoiceClient::getInstance()->accountGetTemplateFontsResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Aux.SetVadProperties.1")) - { - // both values of statusCode (old and more recent) indicate valid requests - if (statusCode != 0 && statusCode != 200) - { - LL_WARNS("Voice") << "Aux.SetVadProperties.1 request failed: " - << "statusCode: " << statusCode - << " and " - << "statusString: " << statusString - << LL_ENDL; - } - } - /* - else if (!stricmp(actionCstr, "Account.ChannelGetList.1")) - { - LLVoiceClient::getInstance()->channelGetListResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Connector.AccountCreate.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.MuteLocalMic.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.MuteLocalSpeaker.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.SetLocalMicVolume.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.SetLocalSpeakerVolume.1")) - { - - } - else if (!stricmp(actionCstr, "Session.ListenerSetPosition.1")) - { - - } - else if (!stricmp(actionCstr, "Session.SpeakerSetPosition.1")) - { - - } - else if (!stricmp(actionCstr, "Session.AudioSourceSetPosition.1")) - { - - } - else if (!stricmp(actionCstr, "Session.GetChannelParticipants.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelCreate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelUpdate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelDelete.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelCreateAndInvite.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelFolderCreate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelFolderUpdate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelFolderDelete.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelAddModerator.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelDeleteModerator.1")) - { - - } - */ - } -} - -LLVivoxSecurity::LLVivoxSecurity() -{ - // This size is an arbitrary choice; Vivox does not care - // Use a multiple of three so that there is no '=' padding in the base64 (purely an esthetic choice) - #define VIVOX_TOKEN_BYTES 9 - U8 random_value[VIVOX_TOKEN_BYTES]; - - for (int b = 0; b < VIVOX_TOKEN_BYTES; b++) - { - random_value[b] = ll_rand() & 0xff; - } - mConnectorHandle = LLBase64::encode(random_value, VIVOX_TOKEN_BYTES); - - for (int b = 0; b < VIVOX_TOKEN_BYTES; b++) - { - random_value[b] = ll_rand() & 0xff; - } - mAccountHandle = LLBase64::encode(random_value, VIVOX_TOKEN_BYTES); -} - -LLVivoxSecurity::~LLVivoxSecurity() -{ -} - -bool LLVivoxVoiceP2PIncomingCall::answerInvite() { return LLVivoxVoiceClient::getInstance()->answerInvite(mCallInfo["session_handle"]); } - -void LLVivoxVoiceP2PIncomingCall::declineInvite() { LLVivoxVoiceClient::getInstance()->declineInvite(mCallInfo["session_handle"]); } diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h deleted file mode 100644 index 3167705528..0000000000 --- a/indra/newview/llvoicevivox.h +++ /dev/null @@ -1,1093 +0,0 @@ -/** - * @file llvoicevivox.h - * @brief Declaration of LLDiamondwareVoiceClient class which is the interface to the voice client process. - * - * $LicenseInfo:firstyear=2001&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * 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 - * - * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA - * $/LicenseInfo$ - */ -#ifndef LL_VOICE_VIVOX_H -#define LL_VOICE_VIVOX_H - -class LLVOAvatar; -class LLVivoxProtocolParser; - -#include "lliopipe.h" -#include "llpumpio.h" -#include "llchainio.h" -#include "lliosocket.h" -#include "v3math.h" -#include "llframetimer.h" -#include "llviewerregion.h" -#include "llcallingcard.h" // for LLFriendObserver -#include "lleventcoro.h" -#include "llcoros.h" -#include - -#ifdef LL_USESYSTEMLIBS -# include "expat.h" -#else -# include "expat/expat.h" -#endif -#include "llvoiceclient.h" - -class LLAvatarName; -class LLVivoxVoiceClientMuteListObserver; - -extern const std::string VIVOX_VOICE_SERVER_TYPE; - -class LLVivoxVoiceP2PIncomingCall : public LLVoiceP2PIncomingCallInterface -{ - public: - LLVivoxVoiceP2PIncomingCall(const LLSD& call_info) : mCallInfo(call_info) {} - ~LLVivoxVoiceP2PIncomingCall() override {} - - bool answerInvite() override; - void declineInvite() override; - - protected: - LLSD mCallInfo; -}; - -class LLVivoxVoiceClient : public LLSingleton, - virtual public LLVoiceModuleInterface, - virtual public LLVoiceEffectInterface, - virtual public LLVoiceP2POutgoingCallInterface -{ - LLSINGLETON(LLVivoxVoiceClient); - LOG_CLASS(LLVivoxVoiceClient); - virtual ~LLVivoxVoiceClient(); - -public: - /// @name LLVoiceModuleInterface virtual implementations - /// @see LLVoiceModuleInterface - //@{ - void init(LLPumpIO *pump) override; // Call this once at application startup (creates connector) - void terminate() override; // Call this to clean up during shutdown - - const LLVoiceVersionInfo& getVersion() override; - - void updateSettings() override; // call after loading settings and whenever they change - - // Returns true if vivox has successfully logged in and is not in error state - bool isVoiceWorking() const override; - - void setHidden(bool hidden) override; // virtual - - ///////////////////// - /// @name Tuning - //@{ - void tuningStart() override; - void tuningStop() override; - bool inTuningMode() override; - - void tuningSetMicVolume(float volume) override; - void tuningSetSpeakerVolume(float volume) override; - float tuningGetEnergy(void) override; - - //@} - - ///////////////////// - /// @name Devices - //@{ - // This returns true when it's safe to bring up the "device settings" dialog in the prefs. - // i.e. when the daemon is running and connected, and the device lists are populated. - bool deviceSettingsAvailable() override; - bool deviceSettingsUpdated() override; //return if the list has been updated and never fetched, only to be called from the voicepanel. - - // Requery the vivox daemon for the current list of input/output devices. - // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed - // (use this if you want to know when it's done). - // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. - void refreshDeviceLists(bool clearCurrentList = true) override; - - void setCaptureDevice(const std::string& name) override; - void setRenderDevice(const std::string& name) override; - - LLVoiceDeviceList& getCaptureDevices() override; - LLVoiceDeviceList& getRenderDevices() override; - //@} - - void getParticipantList(std::set &participants) override; - bool isParticipant(const LLUUID& speaker_id) override; - - // Send a text message to the specified user, initiating the session if necessary. - // virtual bool sendTextMessage(const LLUUID& participant_id, const std::string& message) const {return false;}; - - // Returns true if calling back the session URI after the session has closed is possible. - // Currently this will be false only for PSTN P2P calls. - // NOTE: this will return true if the session can't be found. - bool isSessionCallBackPossible(const LLUUID &session_id) override; - - // Returns true if the session can accepte text IM's. - // Currently this will be false only for PSTN P2P calls. - // NOTE: this will return true if the session can't be found. - bool isSessionTextIMPossible(const LLUUID &session_id) override; - - //////////////////////////// - /// @name Channel stuff - //@{ - // returns true iff the user is currently in a proximal (local spatial) channel. - // Note that gestures should only fire if this returns true. - bool inProximalChannel() override; - - void setNonSpatialChannel(const LLSD& channelInfo, - bool notify_on_first_join, - bool hangup_on_last_leave) override; - - bool setSpatialChannel(const LLSD& channelInfo) override; - - void leaveNonSpatialChannel() override; - - void processChannels(bool process) override; - - void leaveChannel(void); - - bool isCurrentChannel(const LLSD &channelInfo) override; - bool compareChannels(const LLSD &channelInfo1, const LLSD &channelInfo2) override; - - //@} - - - ////////////////////////// - /// @name LLVoiceP2POutgoingCallInterface - //@{ - // start a voice channel with the specified user - void callUser(const LLUUID &uuid) override; - void hangup() override; - - //@} - - LLVoiceP2POutgoingCallInterface *getOutgoingCallInterface() override { return this; } - - LLVoiceP2PIncomingCallInterfacePtr getIncomingCallInterface(const LLSD &voice_call_info) override; - - bool answerInvite(const std::string &sessionHandle); - void declineInvite(const std::string &sessionHandle); - - ///////////////////////// - /// @name Volume/gain - //@{ - void setVoiceVolume(F32 volume) override; - void setMicGain(F32 volume) override; - //@} - - ///////////////////////// - /// @name enable disable voice and features - //@{ - void setVoiceEnabled(bool enabled) override; - void setMuteMic(bool muted) override; // Set the mute state of the local mic. - //@} - - ////////////////////////// - /// @name nearby speaker accessors - //@{ - std::string getDisplayName(const LLUUID& id) override; - bool isParticipantAvatar(const LLUUID &id) override; - bool getIsSpeaking(const LLUUID& id) override; - bool getIsModeratorMuted(const LLUUID& id) override; - F32 getCurrentPower(const LLUUID& id) override; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... - F32 getUserVolume(const LLUUID& id) override; - void setUserVolume(const LLUUID& id, F32 volume) override; // set's volume for specified agent, from 0-1 (where .5 is nominal) - //@} - - // authorize the user - void userAuthorized(const std::string& user_id, - const LLUUID &agentID) override; - - ////////////////////////////// - /// @name Status notification - //@{ - void addObserver(LLVoiceClientStatusObserver* observer) override; - void removeObserver(LLVoiceClientStatusObserver* observer) override; - void addObserver(LLFriendObserver* observer) override; - void removeObserver(LLFriendObserver* observer) override; - void addObserver(LLVoiceClientParticipantObserver* observer) override; - void removeObserver(LLVoiceClientParticipantObserver* observer) override; - //@} - - std::string sipURIFromID(const LLUUID &id) const override; - LLSD getP2PChannelInfoTemplate(const LLUUID& id) const override; - //@} - - /// @name LLVoiceEffectInterface virtual implementations - /// @see LLVoiceEffectInterface - //@{ - - ////////////////////////// - /// @name Accessors - //@{ - bool setVoiceEffect(const LLUUID& id) override; - const LLUUID getVoiceEffect() override; - LLSD getVoiceEffectProperties(const LLUUID& id) override; - - void refreshVoiceEffectLists(bool clear_lists) override; - const voice_effect_list_t& getVoiceEffectList() const override; - const voice_effect_list_t& getVoiceEffectTemplateList() const override; - //@} - - ////////////////////////////// - /// @name Status notification - //@{ - void addObserver(LLVoiceEffectObserver* observer) override; - void removeObserver(LLVoiceEffectObserver* observer) override; - //@} - - ////////////////////////////// - /// @name Effect preview buffer - //@{ - void enablePreviewBuffer(bool enable) override; - void recordPreviewBuffer() override; - void playPreviewBuffer(const LLUUID& effect_id = LLUUID::null) override; - void stopPreviewBuffer() override; - - bool isPreviewRecording() override; - bool isPreviewPlaying() override; - //@} - - //@} - - bool onCheckVoiceEffect(const std::string& voice_effect_name); - void onClickVoiceEffect(const std::string& voice_effect_name); - -protected: - ////////////////////// - // Vivox Specific definitions - - friend class LLVivoxVoiceClientMuteListObserver; - friend class LLVivoxVoiceClientFriendsObserver; - - - enum streamState - { - streamStateUnknown = 0, - streamStateIdle = 1, - streamStateConnected = 2, - streamStateRinging = 3, - streamStateConnecting = 6, // same as Vivox session_media_connecting enum - streamStateDisconnecting = 7, //Same as Vivox session_media_disconnecting enum - }; - - struct participantState - { - public: - participantState(const std::string &uri); - - bool updateMuteState(); // true if mute state has changed - bool isAvatar(); - - std::string mURI; - LLUUID mAvatarID; - std::string mAccountName; - std::string mDisplayName; - LLFrameTimer mSpeakingTimeout; - F32 mLastSpokeTimestamp; - F32 mPower; - F32 mVolume; - std::string mGroupID; - int mUserVolume; - bool mPTT; - bool mIsSpeaking; - bool mIsModeratorMuted; - bool mOnMuteList; // true if this avatar is on the user's mute list (and should be muted) - bool mVolumeSet; // true if incoming volume messages should not change the volume - bool mVolumeDirty; // true if this participant needs a volume command sent (either mOnMuteList or mUserVolume has changed) - bool mAvatarIDValid; - bool mIsSelf; - }; - typedef std::shared_ptr participantStatePtr_t; - typedef std::weak_ptr participantStateWptr_t; - - typedef std::map participantMap; - typedef std::map participantUUIDMap; - - struct sessionState - { - public: - typedef std::shared_ptr ptr_t; - typedef std::weak_ptr wptr_t; - - typedef std::function sessionFunc_t; - - static ptr_t createSession(); - ~sessionState(); - - LLSD getVoiceChannelInfo(); - - participantStatePtr_t addParticipant(const std::string &uri); - void removeParticipant(const participantStatePtr_t &participant); - void removeAllParticipants(); - - participantStatePtr_t findParticipant(const std::string &uri); - participantStatePtr_t findParticipantByID(const LLUUID& id); - - static ptr_t matchSessionByHandle(const std::string &handle); - static ptr_t matchCreatingSessionByURI(const std::string &uri); - static ptr_t matchSessionByURI(const std::string &uri); - static ptr_t matchSessionByParticipant(const LLUUID &participant_id); - - bool isCallBackPossible(); - bool isTextIMPossible(); - bool isSpatial() { return mIsSpatial; } - - static void for_each(sessionFunc_t func); - - std::string mHandle; - std::string mGroupHandle; - std::string mSIPURI; - std::string mAlias; - std::string mName; - std::string mAlternateSIPURI; - std::string mHash; // Channel password - std::string mErrorStatusString; - std::queue mTextMsgQueue; - - LLUUID mIMSessionID; - LLUUID mCallerID; - int mErrorStatusCode; - int mMediaStreamState; - bool mCreateInProgress; // True if a Session.Create has been sent for this session and no response has been received yet. - bool mMediaConnectInProgress; // True if a Session.MediaConnect has been sent for this session and no response has been received yet. - bool mVoiceInvitePending; // True if a voice invite is pending for this session (usually waiting on a name lookup) - bool mTextInvitePending; // True if a text invite is pending for this session (usually waiting on a name lookup) - bool mSynthesizedCallerID; // True if the caller ID is a hash of the SIP URI -- this means we shouldn't do a name lookup. - bool mIsChannel; // True for both group and spatial channels (false for p2p, PSTN) - bool mIsSpatial; // True for spatial channels - bool mIsP2P; - bool mIncoming; - bool mVoiceActive; - bool mReconnect; // Whether we should try to reconnect to this session if it's dropped - - // Set to true when the volume/mute state of someone in the participant list changes. - // The code will have to walk the list to find the changed participant(s). - bool mVolumeDirty; - bool mMuteDirty; - - bool mParticipantsChanged; - participantMap mParticipantsByURI; - participantUUIDMap mParticipantsByUUID; - - LLUUID mVoiceFontID; - - static void VerifySessions(); - - private: - sessionState(); - - static std::set> mSession; // canonical list of outstanding sessions. - std::set::iterator mMyIterator; // used for delete - - static void for_eachPredicate(const wptr_t &a, sessionFunc_t func); - - static bool testByHandle(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string handle); - static bool testByCreatingURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri); - static bool testBySIPOrAlterateURI(const LLVivoxVoiceClient::sessionState::wptr_t &a, std::string uri); - static bool testByCallerId(const LLVivoxVoiceClient::sessionState::wptr_t &a, LLUUID participantId); - - }; - typedef std::shared_ptr sessionStatePtr_t; - - typedef std::map sessionMap; - - /////////////////////////////////////////////////////// - // Private Member Functions - ////////////////////////////////////////////////////// - - - - ////////////////////////////// - /// @name TVC/Server management and communication - //@{ - // Call this if the connection to the daemon terminates unexpectedly. It will attempt to reset everything and relaunch. - void daemonDied(); - - // Call this if we're just giving up on voice (can't provision an account, etc.). It will clean up and go away. - void giveUp(); - - // write to the tvc - bool writeString(const std::string &str); - - void connectorCreate(); - void connectorShutdown(); - void closeSocket(void); - -// void requestVoiceAccountProvision(S32 retries = 3); - void setLoginInfo( - const std::string& account_name, - const std::string& password, - const std::string& voice_sip_uri_hostname, - const std::string& voice_account_server_uri); - void loginSendMessage(); - void logout(); - void logoutSendMessage(); - - - //@} - - //------------------------------------ - // tuning - - void tuningRenderStartSendMessage(const std::string& name, bool loop); - void tuningRenderStopSendMessage(); - - void tuningCaptureStartSendMessage(int duration); - void tuningCaptureStopSendMessage(); - - //---------------------------------- - // devices - void clearCaptureDevices(); - void addCaptureDevice(const LLVoiceDevice& device); - void clearRenderDevices(); - void setDevicesListUpdated(bool state); - void addRenderDevice(const LLVoiceDevice& device); - void buildSetAudioDevices(std::ostringstream &stream); - - void getCaptureDevicesSendMessage(); - void getRenderDevicesSendMessage(); - - // local audio updates, mic mute, speaker mute, mic volume and speaker volumes - void sendLocalAudioUpdates(); - - ///////////////////////////// - // Response/Event handlers - void connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID); - void loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases); - void sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); - void sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); - void sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString); - void logoutResponse(int statusCode, std::string &statusString); - void connectorShutdownResponse(int statusCode, std::string &statusString); - - void accountLoginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state); - void mediaCompletionEvent(std::string &sessionGroupHandle, std::string &mediaCompletionType); - void mediaStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, int statusCode, std::string &statusString, int state, bool incoming); - void sessionAddedEvent(std::string &uriString, std::string &alias, std::string &sessionHandle, std::string &sessionGroupHandle, bool isChannel, bool incoming, std::string &nameString, std::string &applicationString); - void sessionGroupAddedEvent(std::string &sessionGroupHandle); - void sessionRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle); - void participantAddedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString, std::string &displayNameString, int participantType); - void participantRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString); - void participantUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, bool isModeratorMuted, bool isSpeaking, int volume, F32 energy); - void voiceServiceConnectionStateChangedEvent(int statusCode, std::string &statusString, std::string &build_id); - void auxAudioPropertiesEvent(F32 energy); - void messageEvent(std::string &sessionHandle, std::string &uriString, std::string &alias, std::string &messageHeader, std::string &messageBody, std::string &applicationString); - void sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType); - - void muteListChanged(); - - ///////////////////////////// - // VAD changes - // disable auto-VAD and configure VAD parameters explicitly - void setupVADParams(unsigned int vad_auto, unsigned int vad_hangover, unsigned int vad_noise_floor, unsigned int vad_sensitivity); - void onVADSettingsChange(); - - ///////////////////////////// - // Sending updates of current state - void updatePosition(void); - void setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot); - void setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLQuaternion &rot); - bool channelFromRegion(LLViewerRegion *region, std::string &name); - - void setEarLocation(S32 loc); - - - ///////////////////////////// - // Accessors for data related to nearby speakers - - // MBW -- XXX -- Not sure how to get this data out of the TVC - bool getUsingPTT(const LLUUID& id); - std::string getGroupID(const LLUUID& id); // group ID if the user is in group chat (empty string if not applicable) - - ///////////////////////////// - // Recording controls - void recordingLoopStart(int seconds = 3600, int deltaFramesPerControlFrame = 200); - void recordingLoopSave(const std::string& filename); - void recordingStop(); - - // Playback controls - void filePlaybackStart(const std::string& filename); - void filePlaybackStop(); - void filePlaybackSetPaused(bool paused); - void filePlaybackSetMode(bool vox = false, float speed = 1.0f); - - participantStatePtr_t findParticipantByID(const LLUUID& id); - - -#if 0 - //////////////////////////////////////// - // voice sessions. - typedef std::set sessionSet; - - typedef sessionSet::iterator sessionIterator; - sessionIterator sessionsBegin(void); - sessionIterator sessionsEnd(void); -#endif - - sessionStatePtr_t findSession(const std::string &handle); - sessionStatePtr_t findSessionBeingCreatedByURI(const std::string &uri); - sessionStatePtr_t findSession(const LLUUID &participant_id); - - sessionStatePtr_t addSession(const std::string &uri, const std::string &handle = std::string()); - void clearSessionHandle(const sessionStatePtr_t &session); - void setSessionHandle(const sessionStatePtr_t &session, const std::string &handle); - void setSessionURI(const sessionStatePtr_t &session, const std::string &uri); - void deleteSession(const sessionStatePtr_t &session); - void deleteAllSessions(void); - - void verifySessionState(void); - - void joinedAudioSession(const sessionStatePtr_t &session); - void leftAudioSession(const sessionStatePtr_t &session); - - // This is called in several places where the session _may_ need to be deleted. - // It contains logic for whether to delete the session or keep it around. - void reapSession(const sessionStatePtr_t &session); - - // Returns true if the session seems to indicate we've moved to a region on a different voice server - bool sessionNeedsRelog(const sessionStatePtr_t &session); - - - ////////////////////////////////////// - // buddy list stuff, needed for SLIM later - struct buddyListEntry - { - buddyListEntry(const std::string &uri); - std::string mURI; - std::string mDisplayName; - LLUUID mUUID; - bool mOnlineSL; - bool mOnlineSLim; - bool mCanSeeMeOnline; - bool mHasBlockListEntry; - bool mHasAutoAcceptListEntry; - bool mNameResolved; - bool mInSLFriends; - bool mInVivoxBuddies; - }; - - typedef std::map buddyListMap; - - ///////////////////////////// - // session control messages - - void accountListBlockRulesSendMessage(); - void accountListAutoAcceptRulesSendMessage(); - - void sessionGroupCreateSendMessage(); - void sessionCreateSendMessage(const sessionStatePtr_t &session, bool startAudio = true, bool startText = false); - void sessionGroupAddSessionSendMessage(const sessionStatePtr_t &session, bool startAudio = true, bool startText = false); - void sessionMediaConnectSendMessage(const sessionStatePtr_t &session); // just joins the audio session - void sessionTextConnectSendMessage(const sessionStatePtr_t &session); // just joins the text session - void sessionTerminateSendMessage(const sessionStatePtr_t &session); - void sessionGroupTerminateSendMessage(const sessionStatePtr_t &session); - void sessionMediaDisconnectSendMessage(const sessionStatePtr_t &session); - // void sessionTextDisconnectSendMessage(sessionState *session); - - - - // Pokes the state machine to leave the audio session next time around. - void sessionTerminate(); - - // Pokes the state machine to shut down the connector and restart it. - void requestRelog(); - - // Does the actual work to get out of the audio session - void leaveAudioSession(); - - friend class LLVivoxVoiceClientCapResponder; - - - void lookupName(const LLUUID &id); - void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); - void avatarNameResolved(const LLUUID &id, const std::string &name); - static void predAvatarNameResolution(const LLVivoxVoiceClient::sessionStatePtr_t &session, LLUUID id, std::string name); - - boost::signals2::connection mAvatarNameCacheConnection; - - ///////////////////////////// - // Voice fonts - - void addVoiceFont(const S32 id, - const std::string &name, - const std::string &description, - const LLDate &expiration_date, - bool has_expired, - const S32 font_type, - const S32 font_status, - const bool template_font = false); - void accountGetSessionFontsResponse(int statusCode, const std::string &statusString); - void accountGetTemplateFontsResponse(int statusCode, const std::string &statusString); - -private: - - LLVoiceVersionInfo mVoiceVersion; - - // Coroutine support methods - //--- - void voiceControlCoro(); - void voiceControlStateMachine(S32 &coro_state); - - bool endAndDisconnectSession(); - - bool callbackEndDaemon(const LLSD& data); - bool startAndLaunchDaemon(); - bool provisionVoiceAccount(); - bool establishVoiceConnection(); - bool breakVoiceConnection(bool wait); - bool loginToVivox(); - void logoutOfVivox(bool wait); - bool retrieveVoiceFonts(); - - bool requestParcelVoiceInfo(); - - bool addAndJoinSession(const sessionStatePtr_t &nextSession); - bool terminateAudioSession(bool wait); - - bool waitForChannel(); - bool runSession(const sessionStatePtr_t &session); - - void recordingAndPlaybackMode(); - int voiceRecordBuffer(); - int voicePlaybackBuffer(); - - bool performMicTuning(); - //--- - /// Clean up objects created during a voice session. - void cleanUp(); - - bool mSessionTerminateRequested; - bool mRelogRequested; - // Number of times (in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine(). - // The larger it is the greater is possibility there is a problem with connection to voice server. - // Introduced while fixing EXT-4313. - int mSpatialJoiningNum; - - static void idle(void *user_data); - - LLHost mDaemonHost; - LLSocket::ptr_t mSocket; - - // We should kill the voice daemon in case of connection alert - bool mTerminateDaemon; - - friend class LLVivoxProtocolParser; - - std::string mAccountName; - std::string mAccountPassword; - std::string mAccountDisplayName; - - bool mTuningMode; - float mTuningEnergy; - std::string mTuningAudioFile; - int mTuningMicVolume; - bool mTuningMicVolumeDirty; - int mTuningSpeakerVolume; - bool mTuningSpeakerVolumeDirty; - bool mDevicesListUpdated; // set to true when the device list has been updated - // and false when the panelvoicedevicesettings has queried for an update status. - - std::string mSpatialSessionURI; - std::string mSpatialSessionCredentials; - - std::string mMainSessionGroupHandle; // handle of the "main" session group. - - std::string mChannelName; // Name of the channel to be looked up - sessionStatePtr_t mAudioSession; // Session state for the current audio session - - sessionStatePtr_t mNextAudioSession; // Session state for the audio session we're trying to join - - S32 mCurrentParcelLocalID; // Used to detect parcel boundary crossings - std::string mCurrentRegionName; // Used to detect parcel boundary crossings - - bool mConnectorEstablished; // set by "Create Connector" response - bool mAccountLoggedIn; // set by login message - int mNumberOfAliases; - U32 mCommandCookie; - - std::string mVoiceAccountServerURI; - std::string mVoiceSIPURIHostName; - - int mLoginRetryCount; - - sessionMap mSessionsByHandle; // Active sessions, indexed by session handle. Sessions which are being initiated may not be in this map. -#if 0 - sessionSet mSessions; // All sessions, not indexed. This is the canonical session list. -#endif - - bool mBuddyListMapPopulated; - bool mBlockRulesListReceived; - bool mAutoAcceptRulesListReceived; - buddyListMap mBuddyListMap; - - LLVoiceDeviceList mCaptureDevices; - LLVoiceDeviceList mRenderDevices; - - std::string mCaptureDevice; - std::string mRenderDevice; - bool mCaptureDeviceDirty; - bool mRenderDeviceDirty; - - bool mIsInitialized; - bool mShutdownComplete; - - bool checkParcelChanged(bool update = false); - bool switchChannel(std::string uri = std::string(), bool spatial = true, bool no_reconnect = false, bool is_p2p = false, std::string hash = ""); - void joinSession(const sessionStatePtr_t &session); - - std::string nameFromID(const LLUUID &id) const; - bool IDFromName(const std::string name, LLUUID &uuid); - std::string sipURIFromAvatar(LLVOAvatar *avatar); - std::string sipURIFromName(std::string &name); - - // Returns the name portion of the SIP URI if the string looks vaguely like a SIP URI, or an empty string if not. - std::string nameFromsipURI(const std::string &uri); - - bool inSpatialChannel(void); - LLSD getAudioSessionChannelInfo(); - std::string getAudioSessionHandle(); - - void sendPositionAndVolumeUpdate(void); - - void sendCaptureAndRenderDevices(); - void buildSetCaptureDevice(std::ostringstream &stream); - void buildSetRenderDevice(std::ostringstream &stream); - - - void sendFriendsListUpdates(); - -#if 0 - // start a text IM session with the specified user - // This will be asynchronous, the session may be established at a future time. - sessionStatePtr_t startUserIMSession(const LLUUID& uuid); -#endif - - void enforceTether(void); - - bool mSpatialCoordsDirty; - - LLVector3d mCameraPosition; - LLVector3d mCameraRequestedPosition; - LLVector3 mCameraVelocity; - LLMatrix3 mCameraRot; - - LLVector3d mAvatarPosition; - LLVector3 mAvatarVelocity; - LLQuaternion mAvatarRot; - - bool mMuteMic; - bool mMuteMicDirty; - bool mHidden; //Set to true during teleport to hide the agent's position. - - // Set to true when the friends list is known to have changed. - bool mFriendsListDirty; - - enum - { - earLocCamera = 0, // ear at camera - earLocAvatar, // ear at avatar - earLocMixed // ear at avatar location/camera direction - }; - - S32 mEarLocation; - - bool mSpeakerVolumeDirty; - bool mSpeakerMuteDirty; - int mSpeakerVolume; - - int mMicVolume; - bool mMicVolumeDirty; - - bool mVoiceEnabled; - bool mProcessChannels; - bool mWriteInProgress; - std::string mWriteString; - size_t mWriteOffset; - - typedef std::set observer_set_t; - observer_set_t mParticipantObservers; - - void notifyParticipantObservers(); - - typedef std::set status_observer_set_t; - status_observer_set_t mStatusObservers; - - void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status); - - typedef std::set friend_observer_set_t; - friend_observer_set_t mFriendObservers; - void notifyFriendObservers(); - - // Voice Fonts - - void expireVoiceFonts(); - void deleteVoiceFont(const LLUUID& id); - void deleteAllVoiceFonts(); - void deleteVoiceFontTemplates(); - - S32 getVoiceFontIndex(const LLUUID& id) const; - S32 getVoiceFontTemplateIndex(const LLUUID& id) const; - - void accountGetSessionFontsSendMessage(); - void accountGetTemplateFontsSendMessage(); - void sessionSetVoiceFontSendMessage(const sessionStatePtr_t &session); - - void updateVoiceMorphingMenu(); - void notifyVoiceFontObservers(); - - typedef enum e_voice_font_type - { - VOICE_FONT_TYPE_NONE = 0, - VOICE_FONT_TYPE_ROOT = 1, - VOICE_FONT_TYPE_USER = 2, - VOICE_FONT_TYPE_UNKNOWN - } EVoiceFontType; - - typedef enum e_voice_font_status - { - VOICE_FONT_STATUS_NONE = 0, - VOICE_FONT_STATUS_FREE = 1, - VOICE_FONT_STATUS_NOT_FREE = 2, - VOICE_FONT_STATUS_UNKNOWN - } EVoiceFontStatus; - - struct voiceFontEntry - { - voiceFontEntry(LLUUID& id); - ~voiceFontEntry(); - - LLUUID mID; - S32 mFontIndex; - std::string mName; - LLDate mExpirationDate; - S32 mFontType; - S32 mFontStatus; - bool mIsNew; - - LLFrameTimer mExpiryTimer; - }; - - bool mVoiceFontsReceived; - bool mVoiceFontsNew; - bool mVoiceFontListDirty; - voice_effect_list_t mVoiceFontList; - voice_effect_list_t mVoiceFontTemplateList; - - typedef std::map voice_font_map_t; - voice_font_map_t mVoiceFontMap; - voice_font_map_t mVoiceFontTemplateMap; - - typedef std::set voice_font_observer_set_t; - voice_font_observer_set_t mVoiceFontObservers; - - LLFrameTimer mVoiceFontExpiryTimer; - - - // Audio capture buffer - - void captureBufferRecordStartSendMessage(); - void captureBufferRecordStopSendMessage(); - void captureBufferPlayStartSendMessage(const LLUUID& voice_font_id = LLUUID::null); - void captureBufferPlayStopSendMessage(); - - bool mCaptureBufferMode; // Disconnected from voice channels while using the capture buffer. - bool mCaptureBufferRecording; // A voice sample is being captured. - bool mCaptureBufferRecorded; // A voice sample is captured in the buffer ready to play. - bool mCaptureBufferPlaying; // A voice sample is being played. - - LLTimer mCaptureTimer; - LLUUID mPreviewVoiceFont; - LLUUID mPreviewVoiceFontLast; - S32 mPlayRequestCount; - bool mIsInTuningMode; - bool mIsInChannel; - bool mIsJoiningSession; - bool mIsWaitingForFonts; - bool mIsLoggingIn; - bool mIsLoggedIn; - bool mIsProcessingChannels; - bool mIsCoroutineActive; - - // This variables can last longer than vivox in coroutines so we need them as static - static bool sShuttingDown; - static bool sConnected; - static LLPumpIO* sPump; - - LLEventMailDrop mVivoxPump; -}; - - -/** - * @class LLVivoxProtocolParser - * @brief This class helps construct new LLIOPipe specializations - * @see LLIOPipe - * - * THOROUGH_DESCRIPTION - */ -class LLVivoxProtocolParser : public LLIOPipe -{ - LOG_CLASS(LLVivoxProtocolParser); -public: - LLVivoxProtocolParser(); - virtual ~LLVivoxProtocolParser(); - -protected: - /* @name LLIOPipe virtual implementations - */ - //@{ - /** - * @brief Process the data in buffer - */ - virtual EStatus process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump); - //@} - - std::string mInput; - - // Expat control members - XML_Parser parser; - int responseDepth; - bool ignoringTags; - bool isEvent; - int ignoreDepth; - - // Members for processing responses. The values are transient and only valid within a call to processResponse(). - int returnCode; - int statusCode; - std::string statusString; - std::string requestId; - std::string actionString; - std::string connectorHandle; - std::string versionID; - std::string mBuildID; - std::string accountHandle; - std::string sessionHandle; - std::string sessionGroupHandle; - std::string alias; - std::string applicationString; - - // Members for processing events. The values are transient and only valid within a call to processResponse(). - std::string eventTypeString; - int state; - std::string uriString; - bool isChannel; - bool incoming; - bool enabled; - std::string nameString; - std::string audioMediaString; - std::string deviceString; - std::string displayNameString; - int participantType; - bool isLocallyMuted; - bool isModeratorMuted; - bool isSpeaking; - int volume; - F32 energy; - std::string messageHeader; - std::string messageBody; - std::string notificationType; - bool hasText; - bool hasAudio; - bool hasVideo; - bool terminated; - std::string blockMask; - std::string presenceOnly; - std::string autoAcceptMask; - std::string autoAddAsBuddy; - int numberOfAliases; - std::string subscriptionHandle; - std::string subscriptionType; - S32 id; - std::string descriptionString; - LLDate expirationDate; - bool hasExpired; - S32 fontType; - S32 fontStatus; - std::string mediaCompletionType; - - // Members for processing text between tags - std::string textBuffer; - bool accumulateText; - - void reset(); - - void processResponse(std::string tag); - - static void XMLCALL ExpatStartTag(void *data, const char *el, const char **attr); - static void XMLCALL ExpatEndTag(void *data, const char *el); - static void XMLCALL ExpatCharHandler(void *data, const XML_Char *s, int len); - - void StartTag(const char *tag, const char **attr); - void EndTag(const char *tag); - void CharData(const char *buffer, int length); - LLDate expiryTimeStampToLLDate(const std::string& vivox_ts); - -}; - -class LLVivoxSecurity : public LLSingleton -{ - LLSINGLETON(LLVivoxSecurity); - virtual ~LLVivoxSecurity(); - - public: - std::string connectorHandle() { return mConnectorHandle; }; - std::string accountHandle() { return mAccountHandle; }; - - private: - std::string mConnectorHandle; - std::string mAccountHandle; -}; - -class LLVoiceVivoxStats : public LLSingleton -{ - LLSINGLETON(LLVoiceVivoxStats); - LOG_CLASS(LLVoiceVivoxStats); - virtual ~LLVoiceVivoxStats(); - - private: - F64SecondsImplicit mStartTime; - - U32 mConnectCycles; - - F64 mConnectTime; - U32 mConnectAttempts; - - F64 mProvisionTime; - U32 mProvisionAttempts; - - F64 mEstablishTime; - U32 mEstablishAttempts; - - public: - - void reset(); - void connectionAttemptStart(); - void connectionAttemptEnd(bool success); - void provisionAttemptStart(); - void provisionAttemptEnd(bool success); - void establishAttemptStart(); - void establishAttemptEnd(bool success); - LLSD read(); -}; - -#endif //LL_VIVOX_VOICE_CLIENT_H - diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index 333046adce..59e634b130 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -2342,12 +2342,6 @@ void LLWebRTCVoiceClient::avatarNameResolved(const LLUUID &id, const std::string sessionState::for_each(boost::bind(predAvatarNameResolution, _1, id, name)); } -// Leftover from vivox PTSN -std::string LLWebRTCVoiceClient::sipURIFromID(const LLUUID& id) const -{ - return id.asString(); -} - LLSD LLWebRTCVoiceClient::getP2PChannelInfoTemplate(const LLUUID& id) const { return LLSD(); diff --git a/indra/newview/llvoicewebrtc.h b/indra/newview/llvoicewebrtc.h index 8efbd1778f..be384f0de0 100644 --- a/indra/newview/llvoicewebrtc.h +++ b/indra/newview/llvoicewebrtc.h @@ -87,7 +87,6 @@ public: // Returns true if WebRTC has successfully logged in and is not in error state bool isVoiceWorking() const override; - std::string sipURIFromID(const LLUUID &id) const override; LLSD getP2PChannelInfoTemplate(const LLUUID& id) const override; void setHidden(bool hidden) override; // virtual diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index c33f4f140a..8e59c26316 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -576,14 +576,6 @@ class Windows_x86_64_Manifest(ViewerManifest): self.path_optional("vcruntime140_1.dll") self.path_optional("vcruntime140_threads.dll") - # SLVoice executable - with self.prefix(src=os.path.join(pkgdir, 'bin', 'release')): - self.path("SLVoice.exe") - - # Vivox libraries - self.path("vivoxsdk_x64.dll") - self.path("ortp_x64.dll") - # BugSplat if self.args.get('bugsplat'): self.path("BsSndRpt64.exe") @@ -1139,16 +1131,6 @@ class Darwin_x86_64_Manifest(ViewerManifest): # Need to get the llcommon dll from any of the build directories as well. libfile_parent = self.get_dst_prefix() dylibs=[] - # SLVoice executable - with self.prefix(src=os.path.join(pkgdir, 'bin', 'release')): - self.path("SLVoice") - - # Vivox libraries - for libfile in ( - 'libortp.dylib', - 'libvivoxsdk.dylib', - ): - self.path2basename(relpkgdir, libfile) # Discord social SDK if self.args['discord'] == 'ON': @@ -1475,7 +1457,6 @@ class Linux_i686_Manifest(LinuxManifest): self.path("libdirect-1.4.so.5*") self.path("libalut.so*") self.path("libopenal.so*") - self.path("libopenal.so", "libvivoxoal.so.1") # vivox's sdk expects this soname # KLUDGE: As of 2012-04-11, the 'fontconfig' package installs # libfontconfig.so.1.4.4, along with symlinks libfontconfig.so.1 # and libfontconfig.so. Before we added support for library-file @@ -1505,15 +1486,6 @@ class Linux_i686_Manifest(LinuxManifest): print("tcmalloc files not found, skipping") pass - # Vivox runtimes - with self.prefix(src=relpkgdir, dst="bin"): - self.path("SLVoice") - with self.prefix(src=relpkgdir, dst="lib"): - self.path("libortp.so") - self.path("libsndfile.so.1") - #self.path("libvivoxoal.so.1") # no - we'll re-use the viewer's own OpenAL lib - self.path("libvivoxsdk.so") - self.strip_binaries() -- cgit v1.3 From bbc850997cd095188127d971f6cb90052615849a Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Mon, 4 May 2026 23:31:14 +0300 Subject: #5756 Improve LLUICtrlFactory's performance --- indra/llui/lluictrlfactory.cpp | 2 +- indra/llui/lluictrlfactory.h | 11 ++++++++--- indra/newview/llappviewer.cpp | 2 ++ 3 files changed, 11 insertions(+), 4 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/llui/lluictrlfactory.cpp b/indra/llui/lluictrlfactory.cpp index 33ffc3dfc6..740f187ed8 100644 --- a/indra/llui/lluictrlfactory.cpp +++ b/indra/llui/lluictrlfactory.cpp @@ -67,7 +67,7 @@ public: static LLDefaultChildRegistry::Register r1("locate"); // Build time optimization, generate this once in .cpp file -template class LLUICtrlFactory* LLSingleton::getInstance(); +template class LLUICtrlFactory* LLSimpleton::getInstance(); //----------------------------------------------------------------------------- // LLUICtrlFactory() diff --git a/indra/llui/lluictrlfactory.h b/indra/llui/lluictrlfactory.h index f44b4ba4dc..75cee5f004 100644 --- a/indra/llui/lluictrlfactory.h +++ b/indra/llui/lluictrlfactory.h @@ -81,12 +81,13 @@ class LLWidgetNameRegistry // Build time optimization, generate this once in .cpp file #ifndef LLUICTRLFACTORY_CPP -extern template class LLUICtrlFactory* LLSingleton::getInstance(); +extern template class LLUICtrlFactory* LLSimpleton::getInstance(); #endif -class LLUICtrlFactory : public LLSingleton +class LLUICtrlFactory : public LLSimpleton { - LLSINGLETON(LLUICtrlFactory); +public: + LLUICtrlFactory(); ~LLUICtrlFactory(); // only partial specialization allowed in inner classes, so use extra dummy parameter @@ -313,6 +314,10 @@ template LLChildRegistry::Register::Register(const char* tag, LLWidgetCreatorFunc func) : LLChildRegistry::StaticRegistrar(tag, func == nullptr ? (LLWidgetCreatorFunc)&LLUICtrlFactory::defaultBuilder : func) { + if (!LLUICtrlFactory::instanceExists()) + { + LLUICtrlFactory::createInstance(); + } // add this widget to various registries LLUICtrlFactory::instance().registerWidget(typeid(T), typeid(typename T::Params), tag); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index bae9749772..8343cc3218 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2183,6 +2183,8 @@ bool LLAppViewer::cleanup() // deleteSingleton() methods. LLSingletonBase::deleteAll(); + LLUICtrlFactory::deleteSingleton(); + LLSplashScreen::hide(); LL_INFOS() << "Goodbye!" << LL_ENDL; -- cgit v1.3 From f269701ed77d9f863053e9d4534337444c5a4b37 Mon Sep 17 00:00:00 2001 From: Mnikolenko Productengine Date: Tue, 26 May 2026 22:41:24 +0300 Subject: #5368 add the timer for doFrame --- indra/newview/llappviewer.cpp | 5 +++++ indra/newview/llstatslistener.cpp | 3 +++ indra/newview/llviewerstats.cpp | 2 ++ indra/newview/llviewerstats.h | 2 ++ 4 files changed, 12 insertions(+) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 59056381e1..0b46e2ccc3 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -150,6 +150,7 @@ #include #include #include +#include #if LL_WINDOWS # include // For _SH_DENYWR in processMarkerFiles @@ -1288,7 +1289,11 @@ bool LLAppViewer::frame() { try { + const auto start = std::chrono::steady_clock::now(); ret = doFrame(); + const auto end = std::chrono::steady_clock::now(); + const U64 doframe_time_us = (U64)std::chrono::duration_cast(end - start).count(); + LLTrace::sample(LLStatViewer::DOFRAME_TIME_US, doframe_time_us); } catch (const LLContinueError&) { diff --git a/indra/newview/llstatslistener.cpp b/indra/newview/llstatslistener.cpp index 47b025cd8b..b0f8d54231 100644 --- a/indra/newview/llstatslistener.cpp +++ b/indra/newview/llstatslistener.cpp @@ -101,6 +101,9 @@ void LLStatsListener::getPerfData(LLSD const & evt) setPeriodArray(frametime, "frame_time_ms", last, recording, num_periods, LLStatViewer::FRAMETIME, [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + setPeriodArray(frametime, "do_frame_time_us", last, recording, num_periods, LLStatViewer::DOFRAME_TIME_US, + [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); + setPeriodArray(frametime, "frame_time_jitter_ms", last, recording, num_periods, LLStatViewer::FRAMETIME_JITTER, [](LLTrace::Recording& period, const auto& stat) { return period.getMean(stat); }); diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index 6490fe76a2..aa9ec9c56f 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -232,6 +232,8 @@ LLTrace::SampleStatHandle FRAMETIME_JITTER_EVENTS("frametimeevents", "Numbe FRAMETIME_JITTER_EVENTS_PER_MINUTE("frametimeeventspm", "Average number of frametime events per minute."), FRAMETIME_JITTER_EVENTS_LAST_MINUTE("frametimeeventslastmin", "Number of frametime events in the last minute."); +LLTrace::SampleStatHandle DOFRAME_TIME_US("doframetimeus", "doFrame wall time in microseconds."); + LLTrace::SampleStatHandle NOTRMALIZED_FRAMETIME_JITTER_SESSION("normalizedframetimejitter", "Normalized frametime jitter over the session."); LLTrace::SampleStatHandle NFTV("nftv", "Normalized frametime variation."); LLTrace::SampleStatHandle NORMALIZED_FRAMTIME_JITTER_PERIOD("normalizedframetimejitterperiod", "Normalized frametime jitter over the last 5 seconds."); diff --git a/indra/newview/llviewerstats.h b/indra/newview/llviewerstats.h index f670dadc27..7da74e716b 100644 --- a/indra/newview/llviewerstats.h +++ b/indra/newview/llviewerstats.h @@ -223,6 +223,8 @@ extern LLTrace::SampleStatHandle FRAMETIME_JITTER_EVENTS, FRAMETIME_JITTER_EVENTS_PER_MINUTE, FRAMETIME_JITTER_EVENTS_LAST_MINUTE; +extern LLTrace::SampleStatHandle DOFRAME_TIME_US; + extern LLTrace::SampleStatHandle NOTRMALIZED_FRAMETIME_JITTER_SESSION; extern LLTrace::SampleStatHandle NFTV; extern LLTrace::SampleStatHandle NORMALIZED_FRAMTIME_JITTER_PERIOD; -- cgit v1.3 From cef8c85e2c70adbc42d9c2e4894633ad657f355d Mon Sep 17 00:00:00 2001 From: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com> Date: Tue, 4 Aug 2026 19:39:54 +0300 Subject: p#682 Improve logging for system events and session (#6086) --- indra/llcommon/llwatchdog.cpp | 5 +++ indra/llcommon/llwatchdog.h | 2 + indra/llui/llfloater.cpp | 2 + indra/llwebrtc/llwebrtc.cpp | 4 ++ indra/llwindow/llwindowcallbacks.cpp | 2 +- indra/llwindow/llwindowcallbacks.h | 2 +- indra/llwindow/llwindowwin32.cpp | 71 ++++++++++++++++++++++++++++++++++-- indra/newview/llappviewer.cpp | 15 ++++++-- indra/newview/llviewerwindow.cpp | 7 +++- indra/newview/llviewerwindow.h | 2 +- 10 files changed, 100 insertions(+), 12 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/llcommon/llwatchdog.cpp b/indra/llcommon/llwatchdog.cpp index 66b565c763..886f19366c 100644 --- a/indra/llcommon/llwatchdog.cpp +++ b/indra/llcommon/llwatchdog.cpp @@ -116,6 +116,11 @@ bool LLWatchdogTimeout::isAlive() const return (mTimer.getStarted() && !mTimer.hasExpired()); } +bool LLWatchdogTimeout::started() const +{ + return mTimer.getStarted(); +} + void LLWatchdogTimeout::reset() { mTimer.setTimerExpirySec(mTimeout); diff --git a/indra/llcommon/llwatchdog.h b/indra/llcommon/llwatchdog.h index f138fbccb0..d55bf434f3 100644 --- a/indra/llcommon/llwatchdog.h +++ b/indra/llcommon/llwatchdog.h @@ -47,6 +47,7 @@ public: // This may mean that resources used by // isAlive and other method may need synchronization. virtual bool isAlive() const = 0; + virtual bool started() const = 0; virtual void reset() = 0; virtual void start(); virtual void stop(); @@ -66,6 +67,7 @@ public: virtual ~LLWatchdogTimeout(); bool isAlive() const override; + bool started() const override; void reset() override; void start() override { start(""); } void stop() override; diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 9361358ced..6de2c18620 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -569,6 +569,8 @@ void LLFloater::storeRectControl() void LLFloater::storeVisibilityControl() { + // Todo: this is a bit pricey, gets called each frame + // on LLAppViewer::idle(), optimize! if( !sQuitting && mVisibilityControl.size() > 1 ) { getControlGroup()->setBOOL( mVisibilityControl, getVisible() ); diff --git a/indra/llwebrtc/llwebrtc.cpp b/indra/llwebrtc/llwebrtc.cpp index 3d58e4e1ce..80f2c46332 100644 --- a/indra/llwebrtc/llwebrtc.cpp +++ b/indra/llwebrtc/llwebrtc.cpp @@ -748,6 +748,7 @@ void LLWebRTCImpl::updateDevices() char name[webrtc::kAdmMaxDeviceNameSize]; char guid[webrtc::kAdmMaxGuidSize]; mDeviceModule->PlayoutDeviceName(index, name, guid); + RTC_LOG(LS_VERBOSE) << "updateDevices: playout device [" << index << "] name='" << name << "' guid='" << guid << "'"; mPlayoutDeviceList.emplace_back(name, guid); } @@ -766,9 +767,12 @@ void LLWebRTCImpl::updateDevices() char name[webrtc::kAdmMaxDeviceNameSize]; char guid[webrtc::kAdmMaxGuidSize]; mDeviceModule->RecordingDeviceName(index, name, guid); + RTC_LOG(LS_VERBOSE) << "updateDevices: recording device [" << index << "] name='" << name << "' guid='" << guid << "'"; mRecordingDeviceList.emplace_back(name, guid); } + RTC_LOG(LS_INFO) << "updateDevices, playout count: " << renderDeviceCount << "; capture count: " << captureDeviceCount; + for (auto &observer : mVoiceDevicesObserverList) { observer->OnDevicesChanged(mPlayoutDeviceList, mRecordingDeviceList); diff --git a/indra/llwindow/llwindowcallbacks.cpp b/indra/llwindow/llwindowcallbacks.cpp index 7331f50ba0..4b804c82cc 100644 --- a/indra/llwindow/llwindowcallbacks.cpp +++ b/indra/llwindow/llwindowcallbacks.cpp @@ -190,7 +190,7 @@ bool LLWindowCallbacks::handleTimerEvent(LLWindow *window) return false; } -bool LLWindowCallbacks::handleDeviceChange(LLWindow *window) +bool LLWindowCallbacks::handleDeviceChange(LLWindow *window, const std::string& change_type) { return false; } diff --git a/indra/llwindow/llwindowcallbacks.h b/indra/llwindow/llwindowcallbacks.h index 59dcdd3ade..6d1990e92b 100644 --- a/indra/llwindow/llwindowcallbacks.h +++ b/indra/llwindow/llwindowcallbacks.h @@ -68,7 +68,7 @@ public: virtual void handleWindowUnblock(LLWindow *window); // window coming back after taking over CPU for a while virtual void handleDataCopy(LLWindow *window, S32 data_type, void *data); virtual bool handleTimerEvent(LLWindow *window); - virtual bool handleDeviceChange(LLWindow *window); + virtual bool handleDeviceChange(LLWindow *window, const std::string& change_type); virtual bool handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32 window_width, S32 window_height); virtual bool handleDisplayChanged(); virtual bool handleWindowDidChangeScreen(LLWindow *window); diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index 6230cc3026..562dfc55ab 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -452,6 +452,16 @@ struct LLWindowWin32::LLWindowWin32Thread : public LL::ThreadPool } }); } + + // For mainWindowProc, it should not unpause watchdog if it was paused + void pingWindowTimeout(std::string_view state) + { + if (mWindowTimeout && mWindowTimeout->started()) + { + mWindowTimeout->setTimeout(WINDOW_TIMEOUT_SEC); + mWindowTimeout->ping(state); + } + } private: // These timeout related functions are strictly for the thread. void resumeTimeout(std::string_view state) @@ -2413,18 +2423,45 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_DEVICECHANGE: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DEVICECHANGE"); + window_imp->mWindowThread->pingWindowTimeout("WM_DEVICECHANGE"); + + // Log detailed device change information + std::string change_type = "UNKNOWN"; + switch (w_param) + { + case DBT_DEVICEARRIVAL: change_type = "DBT_DEVICEARRIVAL"; break; + case DBT_DEVICEREMOVECOMPLETE: change_type = "DBT_DEVICEREMOVECOMPLETE"; break; + case DBT_DEVNODES_CHANGED: change_type = "DBT_DEVNODES_CHANGED"; break; + case DBT_DEVICEQUERYREMOVE: change_type = "DBT_DEVICEQUERYREMOVE"; break; + case DBT_DEVICEQUERYREMOVEFAILED: change_type = "DBT_DEVICEQUERYREMOVEFAILED"; break; + case DBT_DEVICEREMOVEPENDING: change_type = "DBT_DEVICEREMOVEPENDING"; break; + case DBT_CONFIGCHANGED: change_type = "DBT_CONFIGCHANGED"; break; + } + if (w_param == DBT_DEVNODES_CHANGED || w_param == DBT_DEVICEARRIVAL) { - WINDOW_IMP_POST(window_imp->mCallbacks->handleDeviceChange(window_imp)); + WINDOW_IMP_POST(window_imp->mCallbacks->handleDeviceChange(window_imp, change_type)); return 1; } + else if (l_param) + { + const auto* hdr = reinterpret_cast(l_param); + if (hdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) + { + // Might need to register for monitor device notifications + // to get this message when monitor is suspended or resumed. + // TODO: log monitor suspending and resuming. + LL_INFOS("Window") << "DEVICEINTERFACE: " << change_type << LL_ENDL; + } + } break; } case WM_PAINT: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_PAINT"); + window_imp->mWindowThread->pingWindowTimeout("WM_PAINT"); GetUpdateRect(window_imp->mWindowHandle, &update_rect, FALSE); update_width = update_rect.right - update_rect.left + 1; update_height = update_rect.bottom - update_rect.top + 1; @@ -2467,6 +2504,15 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ break; } + case WM_POWERBROADCAST: + { + // Might need to register for power broadcast interface + // Todo: log monitor suspending and resuming. + LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_POWERBROADCAST"); + LL_INFOS("Window") << "Received WM_POWERBROADCAST with wParam: 0x" << std::hex << (uintptr_t)w_param << " lParam: 0x" << (uintptr_t)l_param << std::dec << LL_ENDL; + break; + } + case WM_ACTIVATEAPP: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_ACTIVATEAPP"); @@ -2540,6 +2586,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_CLOSE: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_CLOSE"); + window_imp->mWindowThread->pingWindowTimeout("WM_CLOSE"); // todo: WM_CLOSE can be caused by user and by task manager, // distinguish these cases. // For now assume it is always user. @@ -2577,6 +2624,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ // Comes after WM_QUERYENDSESSION LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_ENDSESSION"); LL_INFOS("Window") << "Received WM_ENDSESSION with wParam: " << (U32)w_param << " lParam: " << (U32)l_param << LL_ENDL; + window_imp->mWindowThread->pingWindowTimeout("WM_ENDSESSION"); unsigned int end_session_flags = (U32)l_param; if (w_param == TRUE // if true, session is ending @@ -3112,6 +3160,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_DPICHANGED: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_DPICHANGED"); + window_imp->mWindowThread->pingWindowTimeout("WM_DPICHANGED"); LPRECT lprc_new_scale; F32 new_scale = F32(LOWORD(w_param)) / F32(USER_DEFAULT_SCREEN_DPI); lprc_new_scale = (LPRECT)l_param; @@ -3132,7 +3181,9 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_DISPLAYCHANGE: { + window_imp->mWindowThread->pingWindowTimeout("WM_DISPLAYCHANGE"); WINDOW_IMP_POST(window_imp->mCallbacks->handleDisplayChanged()); + break; } case WM_SETFOCUS: @@ -3170,6 +3221,9 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ case WM_SETTINGCHANGE: { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("mwp - WM_SETTINGCHANGE"); + // Can be called on OS user switching + LL_INFOS("Window") << "WM_SETTINGCHANGE, with wParam: 0x" << std::hex << (uintptr_t)w_param << " lParam: 0x" << (uintptr_t)l_param << std::dec << LL_ENDL; + window_imp->mWindowThread->pingWindowTimeout("WM_SETTINGCHANGE"); if (w_param == SPI_SETMOUSEVANISH) { if (!SystemParametersInfo(SPI_GETMOUSEVANISH, 0, &window_imp->mMouseVanish, 0)) @@ -5014,6 +5068,13 @@ inline LLWindowWin32::LLWindowWin32Thread::LLWindowWin32Thread() : LL::ThreadPool("Window Thread", 1, MAX_QUEUE_SIZE, false) { LL::ThreadPool::start(); + + // Set thread name for the window thread + // This will make it distinguishable in Visual Studio debugger + post([this]() + { + SetThreadDescription(GetCurrentThread(), L"LLWindowWin32 Thread"); + }); } /** @@ -5195,7 +5256,7 @@ void LLWindowWin32::LLWindowWin32Thread::run() } // Normally won't exist yet, but in case of re-init, make sure it's cleaned up - resumeTimeout("WindowThread"); + resumeTimeout("Window:WindowThread"); while (! getQueue().done()) { @@ -5206,23 +5267,25 @@ void LLWindowWin32::LLWindowWin32Thread::run() if (mWindowHandleThrd != 0) { - pingTimeout("messages"); MSG msg; BOOL status; if (mhDCThrd == 0) { + pingTimeout("Window:PeekMessage"); LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - PeekMessage"); logger.onChange("PeekMessage(", std::hex, mWindowHandleThrd, ")"); status = PeekMessage(&msg, mWindowHandleThrd, 0, 0, PM_REMOVE); } else { + pingTimeout("Window:GetMessage"); LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - GetMessage"); logger.always("GetMessage(", std::hex, mWindowHandleThrd, ")"); status = GetMessage(&msg, NULL, 0, 0); } if (status > 0) { + pingTimeout("Window:TranslateMessage"); logger.always("got MSG (", std::hex, msg.hwnd, ", ", msg.message, ", ", msg.wParam, ")"); TranslateMessage(&msg); @@ -5234,7 +5297,7 @@ void LLWindowWin32::LLWindowWin32Thread::run() { LL_PROFILE_ZONE_NAMED_CATEGORY_WIN32("w32t - Function Queue"); - pingTimeout("queue"); + pingTimeout("Window:Queue"); logger.onChange("runPending()"); //process any pending functions getQueue().runPending(); diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 0b46e2ccc3..0a2b8a7d3d 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -2989,13 +2989,20 @@ bool LLAppViewer::initConfiguration() if (mSecondInstance) { - // This is the second instance of SL. Mute voice, - // but make sure the setting is *not* persisted. + // This is the second concurrent instance of SL. + // Disable voice for this session only, user should + // be able to enable voice manually, after that it + // works the same way as on primary instance. LLControlVariable* enable_voice = gSavedSettings.getControl("EnableVoiceChat"); - if (enable_voice) + if (enable_voice && enable_voice->getValue().asBoolean()) { + LL_DEBUGS("AppInit") << "Disabling voice for this session only" << LL_ENDL; + // Will be saved as mValues[2] which does not get written to the file. + // This feels like a hack, but otherwise way too many controls have to + // be tracked manually instead of using xmls' control_name. const bool DO_NOT_PERSIST = false; - enable_voice->setValue(LLSD(false), DO_NOT_PERSIST); + LLSD::Boolean new_value = false; + enable_voice->setValue(new_value, DO_NOT_PERSIST); } } diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index dea96e2012..b06c129974 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -1777,7 +1777,7 @@ bool LLViewerWindow::handleTimerEvent(LLWindow *window) return false; } -bool LLViewerWindow::handleDeviceChange(LLWindow *window) +bool LLViewerWindow::handleDeviceChange(LLWindow *window, const std::string& change_type) { // give a chance to use a joystick after startup (hot-plugging) if (!LLViewerJoystick::getInstance()->isJoystickInitialized() ) @@ -1785,6 +1785,10 @@ bool LLViewerWindow::handleDeviceChange(LLWindow *window) LLViewerJoystick::getInstance()->init(true); return true; } + else + { + LL_INFOS("Window") << "Device change event: " << change_type << LL_ENDL; + } return false; } @@ -1806,6 +1810,7 @@ bool LLViewerWindow::handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32 bool LLViewerWindow::handleDisplayChanged() { + LL_INFOS("Window") << "Display change event" << LL_ENDL; LLFontGL::sResolutionGeneration++; return false; } diff --git a/indra/newview/llviewerwindow.h b/indra/newview/llviewerwindow.h index 5f1afe2cbe..c748f051dd 100644 --- a/indra/newview/llviewerwindow.h +++ b/indra/newview/llviewerwindow.h @@ -231,7 +231,7 @@ public: /*virtual*/ void handleWindowUnblock(LLWindow *window); /*virtual*/ void handleDataCopy(LLWindow *window, S32 data_type, void *data); /*virtual*/ bool handleTimerEvent(LLWindow *window); - /*virtual*/ bool handleDeviceChange(LLWindow *window); + /*virtual*/ bool handleDeviceChange(LLWindow *window, const std::string& change_type); /*virtual*/ bool handleDPIChanged(LLWindow *window, F32 ui_scale_factor, S32 window_width, S32 window_height); /*virtual*/ bool handleDisplayChanged(); /*virtual*/ bool handleWindowDidChangeScreen(LLWindow *window); -- cgit v1.3