diff options
674 files changed, 25441 insertions, 12188 deletions
diff --git a/.editorconfig b/.editorconfig deleted file mode 100644 index 9ac24be468..0000000000 --- a/.editorconfig +++ /dev/null @@ -1,14 +0,0 @@ -root = true - -[*] -charset = utf-8 -indent_size = 4 -indent_style = space -insert_final_newline = true -trim_trailing_whitespace = true - -[Makefile] -indent_style = tab - -[*.{yml,yaml}] -indent_size = 2 diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs index 0851d20be0..c68131402b 100644 --- a/.git-blame-ignore-revs +++ b/.git-blame-ignore-revs @@ -5,7 +5,11 @@ a0b3021bdcf76859054fda8e30abb3ed47749e83 8444cd9562a6a7b755fcb075864e205122354192 863c541ce0b2e3e1e566cc88423d3e87aaedb6ca +743a1a6d8eabf069d95777c96e5b657cb8702593 +4a00da1ada89af6f313cee30f5177634b0b180a9 # Wrong line endings 1b67dd855c41f5a0cda7ec2a68d98071986ca703 6cc7dd09d5e69cf57e6de7fb568a0ad2693f9c9a e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 +# multiple problems +5370a6d323e14d7b4e32a3f41ef78f7744e361c5 diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2cb94c9c90..1804da749f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -18,6 +18,10 @@ jobs: env: # Build with a tag like "Second_Life#abcdef0" to generate a release page # (used for builds we are planning to deploy). + # Even though inputs.release_run is specified with type boolean, which + # correctly presents a checkbox, its *value* is a GH workflow string + # 'true' or 'false'. If you simply test github.event.inputs.release_run, + # it always evaluates as true because it's a non-empty string either way. # When you want to use a string variable as a workflow YAML boolean, it's # important to ensure it's the empty string when false. If you omit || '', # its value when false is "false", which is interpreted as true. @@ -44,6 +48,9 @@ jobs: matrix: runner: [windows-large, macos-12-xl] configuration: ${{ fromJSON(needs.setup.outputs.configurations) }} + include: + - runner: linux-large + configuration: ReleaseOS runs-on: ${{ matrix.runner }} outputs: viewer_channel: ${{ steps.build.outputs.viewer_channel }} @@ -120,6 +127,21 @@ jobs: ${{ runner.os }}-64-${{ matrix.configuration }}- ${{ runner.os }}-64- + - name: Install Linux dependencies + if: runner.os == 'Linux' + run: | + sudo apt update + sudo apt install -y \ + libpulse-dev libunwind-dev \ + libgl1-mesa-dev libglu1-mesa-dev libxinerama-dev \ + libxcursor-dev libxfixes-dev libgstreamer1.0-dev \ + libgstreamer-plugins-base1.0-dev ninja-build libxft-dev \ + llvm mold libpipewire-0.3-dev + + - name: Install windows dependencies + if: runner.os == 'Windows' + run: choco install nsis-unicode + - name: Determine source branch id: which-branch uses: secondlife/viewer-build-util/which-branch@v2 @@ -241,6 +263,13 @@ jobs: fi export PYTHON_COMMAND_NATIVE="$(native_path "$PYTHON_COMMAND")" + # Compile with clang, link with mold on linux. + if [[ "$RUNNER_OS" == "Linux" ]]; then + export CC=clang + export CXX=clang++ + export CMAKE_OPTIONS='-DLINK_WITH_MOLD=ON' + fi + ./build.sh # Each artifact is downloaded as a distinct .zip file. Multiple jobs @@ -269,6 +298,7 @@ jobs: path: | ${{ steps.build.outputs.viewer_app }} + # The other upload of nontrivial size is the symbol file. Use a distinct # artifact for that too. - name: Upload symbol file @@ -431,6 +461,10 @@ jobs: with: pattern: "*-metadata" + - uses: actions/download-artifact@v4 + with: + pattern: "LinuxOS-app" + - name: Rename metadata run: | cp Windows-metadata/autobuild-package.xml Windows-autobuild-package.xml @@ -451,7 +485,7 @@ jobs: Build ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} ${{ needs.build.outputs.viewer_channel }} ${{ needs.build.outputs.viewer_version }} - ${{ needs.build.outputs.relnotes }} + ${{ needs.setvar.outputs.relnotes }} prerelease: true generate_release_notes: true target_commitish: ${{ github.sha }} diff --git a/.gitignore b/.gitignore index 3b3666af84..c4accf37b5 100755 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,6 @@ debian/files debian/secondlife-appearance-utility* debian/secondlife-viewer* indra/.distcc -indra/cmake/* indra/out/* indra/packages/* diff --git a/autobuild.xml b/autobuild.xml index 95ee34439b..f0f96f025a 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -7,7 +7,7 @@ <string>autobuild</string> <key>installables</key> <map> - <key>SDL</key> + <key>SDL2</key> <map> <key>platforms</key> <map> @@ -16,9 +16,11 @@ <key>archive</key> <map> <key>hash</key> - <string>7ea2df03bfc35c06acf23dd9e734adac</string> + <string>6bbad9b30ee93749e4701313c537d5bac0850e2b</string> + <key>hash_algorithm</key> + <string>sha1</string> <key>url</key> - <string>http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/1103/2554/SDL-1.2.15-linux64-501092.tar.bz2</string> + <string>https://github.com/secondlife/3p-sdl2/releases/download/v2.30.6-r1/SDL2-2.30.6-linux64-10323260130.tar.zst</string> </map> <key>name</key> <string>linux64</string> @@ -27,16 +29,46 @@ <key>license</key> <string>lgpl</string> <key>license_file</key> - <string>LICENSES/SDL.txt</string> + <string>LICENSES/SDL2.txt</string> <key>copyright</key> - <string>Copyright (C) 1997-2012 Sam Lantinga</string> + <string>Copyright (C) 1997-2022 Sam Lantinga (slouken@libsdl.org)</string> <key>version</key> - <string>1.2.15</string> + <string>2.28.0</string> <key>name</key> - <string>SDL</string> + <string>SDL2</string> <key>description</key> <string>Simple DirectMedia Layer is a cross-platform multimedia library designed to provide low level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video framebuffer.</string> </map> + <key>fltk</key> + <map> + <key>platforms</key> + <map> + <key>linux64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>691fef2ddd57d7b6c26e87fc82d9ace3f54e078c</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-fltk/releases/download/v1.3.9-r1/fltk-1.3.9.8556992788-linux64-8556992788.tar.zst</string> + </map> + <key>name</key> + <string>linux</string> + </map> + </map> + <key>license</key> + <string>LGPL/fltk</string> + <key>license_file</key> + <string>LICENSES/fltk.txt</string> + <key>copyright</key> + <string>Copyright (C) fltk project</string> + <key>version</key> + <string>1.3.5</string> + <key>name</key> + <string>fltk</string> + </map> <key>apr_suite</key> <map> <key>platforms</key> @@ -284,12 +316,14 @@ <key>archive</key> <map> <key>hash</key> - <string>77c53daf558f51aec6e9f4bd9e930a103630ee7d</string> + <string>aea0bed0f953a9371b9091f09230b41597f891f7</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-cubemap_to_eqr_js/releases/download/v1.1.0-d7afe27/cubemaptoequirectangular-1.1.0-linux64-d7afe27.tar.zst</string> + <string>https://github.com/secondlife/3p-cubemap_to_eqr_js/releases/download/v1.1.0-cb8785a/cubemaptoequirectangular-1.1.0-linux64-cb8785a.tar.zst</string> </map> + <key>name</key> + <string>linux64</string> </map> <key>windows64</key> <map> @@ -817,34 +851,6 @@ <key>name</key> <string>gstreamer</string> </map> - <key>gtk-atk-pango-glib</key> - <map> - <key>platforms</key> - <map> - <key>linux64</key> - <map> - <key>archive</key> - <map> - <key>hash</key> - <string>de7bba8fd2275a11b077b124413065d0</string> - <key>url</key> - <string>http://automated-builds-secondlife-com.s3.amazonaws.com/hg/repo/p64_3p-gtk-atk-pango-glib/rev/314220/arch/Linux/installer/gtk_atk_pango_glib-0.1-linux64-314220.tar.bz2</string> - </map> - <key>name</key> - <string>linux64</string> - </map> - </map> - <key>license</key> - <string>lgpl</string> - <key>license_file</key> - <string>LICENSES/gtk-atk-pango-glib.txt</string> - <key>copyright</key> - <string>Copyright (various, see sources)</string> - <key>version</key> - <string>0.1</string> - <key>name</key> - <string>gtk-atk-pango-glib</string> - </map> <key>havok-source</key> <map> <key>platforms</key> @@ -869,10 +875,14 @@ <map> <key>archive</key> <map> + <key>creds</key> + <string>github</string> <key>hash</key> - <string>00d0333936a67059a43a6ec8ac38d564</string> + <string>ebfb82b6143874e7938b9d1e8a70d0a2e28aa818</string> + <key>hash_algorithm</key> + <string>sha1</string> <key>url</key> - <string>http://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/748/1563/havok_source-2012.1-2-linux64-500739.tar.bz2</string> + <string>https://api.github.com/repos/secondlife/3p-havok-source/releases/assets/108912599</string> </map> <key>name</key> <string>linux64</string> @@ -930,12 +940,14 @@ <key>archive</key> <map> <key>hash</key> - <string>35d6a617444fde9c8a5e998ef29dc43b95747637</string> + <string>23daab838f4b8f92e5dc1a2f6c568cb7b0cb43b7</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-jpeg_encoder_js/releases/download/v1.0-9165e47/jpegencoderbasic-1.0-linux64-9165e47.tar.zst</string> + <string>https://github.com/secondlife/3p-jpeg_encoder_js/releases/download/v1.0-790015a/jpegencoderbasic-1.0-linux64-790015a.tar.zst</string> </map> + <key>name</key> + <string>linux64</string> </map> <key>windows64</key> <map> @@ -1077,22 +1089,6 @@ <key>name</key> <string>windows64</string> </map> - <key>linux</key> - <map> - <key>archive</key> - <map> - <key>creds</key> - <string>github</string> - <key>hash</key> - <string>711b82f9f588d3a125af7dcd8c81f93d9c343a7d</string> - <key>hash_algorithm</key> - <string>sha1</string> - <key>url</key> - <string>https://api.github.com/repos/secondlife/3p-kdu/releases/assets/136774121</string> - </map> - <key>name</key> - <string>linux</string> - </map> </map> <key>license</key> <string>Kakadu</string> @@ -1592,6 +1588,66 @@ <key>name</key> <string>llphysicsextensions_tpv</string> </map> + <key>luau</key> + <map> + <key>platforms</key> + <map> + <key>darwin64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>e48f291e2eeb1dbab39f26db5ac9b4c2b2d1c57d</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-luau/releases/download/v0.638-r2/luau-0.638.0-r2-darwin64-10356321602.tar.zst</string> + </map> + <key>name</key> + <string>darwin64</string> + </map> + <key>linux64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>3a797d8dae685b25ca787111a370b99d9e57d77c</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-luau/releases/download/v0.638-r2/luau-0.638.0-r2-linux64-10356321602.tar.zst</string> + </map> + <key>name</key> + <string>linux64</string> + </map> + <key>windows64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>a8857313496622134b00899141bbe6542f754164</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-luau/releases/download/v0.638-r2/luau-0.638.0-r2-windows64-10356321602.tar.zst</string> + </map> + <key>name</key> + <string>windows64</string> + </map> + </map> + <key>license</key> + <string>MIT</string> + <key>license_file</key> + <string>LICENSES/luau.txt</string> + <key>copyright</key> + <string>Copyright (c) 2019-2023 Roblox Corporation, Copyright (c) 1994–2019 Lua.org, PUC-Rio.</string> + <key>version</key> + <string>0.638.0-r2</string> + <key>name</key> + <string>luau</string> + <key>description</key> + <string>Luau is a fast, small, safe, gradually typed embeddable scripting language derived from Lua.</string> + </map> <key>mesa</key> <map> <key>platforms</key> @@ -1636,33 +1692,33 @@ <key>name</key> <string>darwin64</string> </map> - <key>windows64</key> + <key>linux64</key> <map> <key>archive</key> <map> <key>hash</key> - <string>024ce689a6f13e66d0c7e431ac34071434e2365a</string> + <string>c947107c0aca46e94e22f66328a3cbbd01d99b36</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-meshoptimizer/releases/download/v210-r2/meshoptimizer-210.0.0-r2-windows64-10341021290.tar.zst</string> + <string>https://github.com/secondlife/3p-meshoptimizer/releases/download/v210-r2/meshoptimizer-210.0.0-r2-linux64-10341021290.tar.zst</string> </map> <key>name</key> - <string>windows64</string> + <string>linux64</string> </map> - <key>linux64</key> + <key>windows64</key> <map> <key>archive</key> <map> <key>hash</key> - <string>c947107c0aca46e94e22f66328a3cbbd01d99b36</string> + <string>024ce689a6f13e66d0c7e431ac34071434e2365a</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-meshoptimizer/releases/download/v210-r2/meshoptimizer-210.0.0-r2-linux64-10341021290.tar.zst</string> + <string>https://github.com/secondlife/3p-meshoptimizer/releases/download/v210-r2/meshoptimizer-210.0.0-r2-windows64-10341021290.tar.zst</string> </map> <key>name</key> - <string>linux64</string> + <string>windows64</string> </map> </map> <key>license</key> @@ -2017,6 +2073,23 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> </map> <key>open-libndofdev</key> <map> + <key>platforms</key> + <map> + <key>linux64</key> + <map> + <key>archive</key> + <map> + <key>hash</key> + <string>da16445129cc82d23d01709a912c99fd74388395</string> + <key>hash_algorithm</key> + <string>sha1</string> + <key>url</key> + <string>https://github.com/secondlife/3p-open-libndofdev/releases/download/v1.14-r3/open_libndofdev-0.14.10326946482-linux64-10326946482.tar.zst</string> + </map> + <key>name</key> + <string>linux64</string> + </map> + </map> <key>license</key> <string>BSD</string> <key>license_file</key> @@ -2024,7 +2097,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>copyright</key> <string>Copyright (c) 2008, Jan Ciger (jan.ciger (at) gmail.com)</string> <key>version</key> - <string>0.3</string> + <string>0.14.8730039102</string> <key>name</key> <string>open-libndofdev</string> <key>description</key> @@ -2353,12 +2426,14 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <key>archive</key> <map> <key>hash</key> - <string>9de1295b157c9913c28be81ff933c73493ecc132</string> + <string>982c0fa427458082ea9e3cb9603904210732b64e</string> <key>hash_algorithm</key> <string>sha1</string> <key>url</key> - <string>https://github.com/secondlife/3p-three_js/releases/download/v0.132.2-b8f6746/threejs-0.132.2-linux64-b8f6746.tar.zst</string> + <string>https://github.com/secondlife/3p-three_js/releases/download/v0.132.2-5da28d9/threejs-0.132.2-common-8454371083.tar.zst</string> </map> + <key>name</key> + <string>linux64</string> </map> <key>windows64</key> <map> @@ -3155,6 +3230,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string> <string>-G</string> <string>Ninja</string> <string>-DLL_TESTS=Off</string> + <string>-DUSE_OPENAL=On</string> </array> </map> <key>build</key> @@ -45,7 +45,7 @@ build_dir_Darwin() build_dir_Linux() { - echo build-linux-i686 + echo build-linux-x86_64 } build_dir_CYGWIN() @@ -163,6 +163,22 @@ pre_build() fi fi + if [[ "$arch" == "Linux" ]] + then + # RELEASE_CRASH_REPORTING is tuned on unconditionaly, this is fine but not for Linux as of now (due to missing breakpad/crashpad support) + RELEASE_CRASH_REPORTING=OFF + + # Builds turn on HAVOK even when config is ReleaseOS. + # This needs AUTOBUILD_GITHUB_TOKEN to be set in the environment. But this is not set for PRs apparently. + # Still this seemlingy works on Windows and Mac, why not on the Linux runner? Mystery to be solved elsewhere. + + + if [[ "$variant" == "ReleaseOS" ]] + then + HAVOK=OFF + fi + fi + if [ "${RELEASE_CRASH_REPORTING:-}" != "OFF" ] then case "$arch" in @@ -185,9 +201,17 @@ pre_build() # honor autobuild_configure_parameters same as sling-buildscripts eval_autobuild_configure_parameters=$(eval $(echo echo $autobuild_configure_parameters)) + # We build the viewer on Linux, but we haven't committed to support the + # Linux viewer. As of 2024-05-30, Linux build-time test infrastructure is + # not in place, so don't even bother running tests on Linux. + if [[ "$RUNNER_OS" == "Linux" ]] + then LL_TESTS=OFF + else LL_TESTS=ON + fi + "$autobuild" configure --quiet -c $variant \ ${eval_autobuild_configure_parameters:---} \ - -DLL_TESTS:BOOL=ON \ + -DLL_TESTS:BOOL=$LL_TESTS \ -DPACKAGE:BOOL=ON \ -DHAVOK:BOOL="$HAVOK" \ -DRELEASE_CRASH_REPORTING:BOOL="$RELEASE_CRASH_REPORTING" \ @@ -196,6 +220,7 @@ pre_build() -DVIEWER_CHANNEL:STRING="${viewer_channel}" \ -DGRID:STRING="\"$viewer_grid\"" \ -DTEMPLATE_VERIFIER_OPTIONS:STRING="$template_verifier_options" $template_verifier_master_url \ + $CMAKE_OPTIONS \ "${SIGNING[@]}" \ || fatal "$variant configuration failed" @@ -517,15 +542,6 @@ then fi fi -# Some of the uploads takes a long time to finish in the codeticket backend, -# causing the next codeticket upload attempt to fail. -# Inserting this after each potentially large upload may prevent those errors. -# JJ is making changes to Codeticket that we hope will eliminate this failure, then this can be removed -wait_for_codeticket() -{ - sleep $(( 60 * 6 )) -} - # check status and upload results to S3 if $succeeded then diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake index 39f26ced5d..5cb25368d2 100644 --- a/indra/cmake/00-Common.cmake +++ b/indra/cmake/00-Common.cmake @@ -15,6 +15,7 @@ include_guard() include(Variables) +include(Linker) # We go to some trouble to set LL_BUILD to the set of relevant compiler flags. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} $ENV{LL_BUILD}") @@ -111,51 +112,89 @@ if (WINDOWS) endif() endif (WINDOWS) - if (LINUX) - set(CMAKE_SKIP_RPATH TRUE) - - # EXTERNAL_TOS - # force this platform to accept TOS via external browser + set( CMAKE_BUILD_WITH_INSTALL_RPATH TRUE ) + set( CMAKE_INSTALL_RPATH $ORIGIN $ORIGIN/../lib ) + set(CMAKE_EXE_LINKER_FLAGS "-Wl,--exclude-libs,ALL") + + find_program(CCACHE_EXE ccache) + if(CCACHE_EXE AND NOT DISABLE_CCACHE) + set(CMAKE_C_COMPILER_LAUNCHER ${CCACHE_EXE} ) + set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE_EXE} ) + endif() - # LL_IGNORE_SIGCHLD - # don't catch SIGCHLD in our base application class for the viewer - some of - # our 3rd party libs may need their *own* SIGCHLD handler to work. Sigh! The - # viewer doesn't need to catch SIGCHLD anyway. + # LL_IGNORE_SIGCHLD + # don't catch SIGCHLD in our base application class for the viewer - some of + # our 3rd party libs may need their *own* SIGCHLD handler to work. Sigh! The + # viewer doesn't need to catch SIGCHLD anyway. add_compile_definitions( _REENTRANT - _FORTIFY_SOURCE=2 - EXTERNAL_TOS APPID=secondlife LL_IGNORE_SIGCHLD ) + + if( ENABLE_ASAN ) + add_compile_options(-U_FORTIFY_SOURCE + -fsanitize=address + --param asan-stack=0 + ) + add_link_options(-fsanitize=address) + else() + add_compile_definitions( _FORTIFY_SOURCE=2 ) + endif() + add_compile_options( - -fexceptions - -fno-math-errno - -fno-strict-aliasing - -fsigned-char - -msse2 - -mfpmath=sse - -pthread - -Wno-parentheses - -Wno-deprecated - -fvisibility=hidden + -fexceptions + -fno-math-errno + -fno-strict-aliasing + -fsigned-char + -msse2 + -mfpmath=sse + -pthread + -fvisibility=hidden + ) + + set(GCC_CLANG_COMPATIBLE_WARNINGS + -Wno-parentheses + -Wno-deprecated + -Wno-c++20-compat + -Wno-pessimizing-move + ) + + set(CLANG_WARNINGS + ${GCC_CLANG_COMPATIBLE_WARNINGS} + # Put clang specific warning configuration here + ) + + set(GCC_WARNINGS + ${GCC_CLANG_COMPATIBLE_WARNINGS} ) - if (ADDRESS_SIZE EQUAL 32) - add_compile_options(-march=pentium4) - endif (ADDRESS_SIZE EQUAL 32) + add_link_options( + -Wl,--no-keep-memory + -Wl,--build-id + -Wl,--no-undefined + ) + if (NOT GCC_DISABLE_FATAL_WARNINGS) + add_compile_options( -Werror ) + endif (NOT GCC_DISABLE_FATAL_WARNINGS) # this stops us requiring a really recent glibc at runtime add_compile_options(-fno-stack-protector) - # linking can be very memory-hungry, especially the final viewer link - set(CMAKE_CXX_LINK_FLAGS "-Wl,--no-keep-memory") - set(CMAKE_CXX_FLAGS_DEBUG "-fno-inline ${CMAKE_CXX_FLAGS_DEBUG}") + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + # ND: clang is a bit more picky than GCC, the latter seems to auto include -lstdc++ and -lm. The former not so and thus fails to link + add_link_options( + -lstdc++ + -lm + ) + add_compile_options(${CLANG_WARNINGS}) + else() + add_compile_options(${GCC_WARNINGS}) + endif() endif (LINUX) - if (DARWIN) # Warnings should be fatal -- thanks, Nicky Perian, for spotting reversed default set(CLANG_DISABLE_FATAL_WARNINGS OFF) @@ -177,15 +216,9 @@ if (DARWIN) # required for clang-15/xcode-15 since our boost package still uses deprecated std::unary_function/binary_function # see https://developer.apple.com/documentation/xcode-release-notes/xcode-15-release-notes#C++-Standard-Library add_compile_definitions(_LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION) -endif (DARWIN) -if (LINUX OR DARWIN) set(GCC_WARNINGS -Wall -Wno-sign-compare -Wno-trigraphs) - if (NOT GCC_DISABLE_FATAL_WARNINGS) - list(APPEND GCC_WARNINGS -Werror) - endif (NOT GCC_DISABLE_FATAL_WARNINGS) - list(APPEND GCC_WARNINGS -Wno-reorder -Wno-non-virtual-dtor ) if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 13) @@ -194,7 +227,4 @@ if (LINUX OR DARWIN) add_compile_options(${GCC_WARNINGS}) add_compile_options(-m${ADDRESS_SIZE}) -endif (LINUX OR DARWIN) - - - +endif () diff --git a/indra/cmake/Audio.cmake b/indra/cmake/Audio.cmake index 8c82749cab..6de33af314 100644 --- a/indra/cmake/Audio.cmake +++ b/indra/cmake/Audio.cmake @@ -21,11 +21,6 @@ if (WINDOWS) debug ${ARCH_PREBUILT_DIRS_DEBUG}/libvorbis.lib ) else (WINDOWS) - target_link_libraries(ll::vorbis INTERFACE - ${ARCH_PREBUILT_DIRS_RELEASE}/libogg.a - ${ARCH_PREBUILT_DIRS_RELEASE}/libvorbisenc.a - ${ARCH_PREBUILT_DIRS_RELEASE}/libvorbisfile.a - ${ARCH_PREBUILT_DIRS_RELEASE}/libvorbis.a - ) + target_link_libraries(ll::vorbis INTERFACE vorbisfile vorbis ogg vorbisenc ) endif (WINDOWS) diff --git a/indra/cmake/Boost.cmake b/indra/cmake/Boost.cmake index 8c5b946753..57f44dae59 100644 --- a/indra/cmake/Boost.cmake +++ b/indra/cmake/Boost.cmake @@ -28,12 +28,11 @@ if (WINDOWS) libboost_url-mt${addrsfx}) elseif (LINUX) target_link_libraries( ll::boost INTERFACE - boost_context-mt${addrsfx} boost_fiber-mt${addrsfx} + boost_context-mt${addrsfx} boost_filesystem-mt${addrsfx} boost_program_options-mt${addrsfx} boost_regex-mt${addrsfx} - boost_signals-mt${addrsfx} boost_system-mt${addrsfx} boost_thread-mt${addrsfx} boost_url-mt${addrsfx}) diff --git a/indra/cmake/CEFPlugin.cmake b/indra/cmake/CEFPlugin.cmake index 9b77becf29..555d2aebbf 100644 --- a/indra/cmake/CEFPlugin.cmake +++ b/indra/cmake/CEFPlugin.cmake @@ -33,4 +33,9 @@ elseif (DARWIN) ) elseif (LINUX) + target_link_libraries( ll::cef INTERFACE + libdullahan.a + cef + cef_dll_wrapper.a + ) endif (WINDOWS) diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index cc217b0563..16da388e61 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -15,7 +15,6 @@ set(cmake_SOURCE_FILES CEFPlugin.cmake CEFPlugin.cmake CMakeCopyIfDifferent.cmake - ConfigurePkgConfig.cmake CURL.cmake Copy3rdPartyLibs.cmake DBusGlib.cmake @@ -41,6 +40,7 @@ set(cmake_SOURCE_FILES LLTestCommand.cmake LLWindow.cmake Linking.cmake + Lualibs.cmake Meshoptimizer.cmake NDOF.cmake OPENAL.cmake diff --git a/indra/cmake/ConfigurePkgConfig.cmake b/indra/cmake/ConfigurePkgConfig.cmake deleted file mode 100644 index 9e798d663b..0000000000 --- a/indra/cmake/ConfigurePkgConfig.cmake +++ /dev/null @@ -1,74 +0,0 @@ -# -*- cmake -*- - -SET(DEBUG_PKG_CONFIG "YES") - -# Don't change this if manually set by user. -IF("$ENV{PKG_CONFIG_LIBDIR}" STREQUAL "") - - # Guess at architecture-specific system library paths. - if (ADDRESS_SIZE EQUAL 32) - SET(PKG_CONFIG_NO_MULTI_GUESS /usr/lib32 /usr/lib) - SET(PKG_CONFIG_NO_MULTI_LOCAL_GUESS /usr/local/lib32 /usr/local/lib) - SET(PKG_CONFIG_MULTI_GUESS /usr/lib/i386-linux-gnu) - SET(PKG_CONFIG_MULTI_LOCAL_GUESS /usr/local/lib/i386-linux-gnu) - else (ADDRESS_SIZE EQUAL 32) - SET(PKG_CONFIG_NO_MULTI_GUESS /usr/lib64 /usr/lib) - SET(PKG_CONFIG_NO_MULTI_LOCAL_GUESS /usr/local/lib64 /usr/local/lib) - SET(PKG_CONFIG_MULTI_GUESS /usr/local/lib/x86_64-linux-gnu) - SET(PKG_CONFIG_MULTI_LOCAL_GUESS /usr/local/lib/x86_64-linux-gnu) - endif (ADDRESS_SIZE EQUAL 32) - - # Use DPKG architecture, if available. - IF (${DPKG_ARCH}) - SET(PKG_CONFIG_MULTI_GUESS /usr/lib/${DPKG_ARCH}) - SET(PKG_CONFIG_MULTI_LOCAL_GUESS /usrlocal/lib/${DPKG_ARCH}) - ENDIF (${DPKG_ARCH}) - - # Explicitly include anything listed in PKG_CONFIG_PATH - string(REPLACE ":" ";" PKG_CONFIG_PATH_LIST "$ENV{PKG_CONFIG_PATH}") - FOREACH(PKG_CONFIG_DIR ${PKG_CONFIG_PATH_LIST}) - SET(VALID_PKG_LIBDIRS "${VALID_PKG_LIBDIRS}:${PKG_CONFIG_DIR}/pkgconfig") - ENDFOREACH(PKG_CONFIG_DIR) - - # Look for valid pkgconfig directories. - FIND_PATH(PKG_CONFIG_ENV pkgconfig ENV LD_LIBRARY_PATH) - FIND_PATH(PKG_CONFIG_MULTI pkgconfig HINT ${PKG_CONFIG_MULTI_GUESS}) - FIND_PATH(PKG_CONFIG_MULTI_LOCAL pkgconfig HINT ${PKG_CONFIG_MULTI_LOCAL_GUESS}) - FIND_PATH(PKG_CONFIG_NO_MULTI pkgconfig HINT ${PKG_CONFIG_NO_MULTI_GUESS}) - FIND_PATH(PKG_CONFIG_NO_MULTI_LOCAL pkgconfig HINT ${PKG_CONFIG_NO_MULTI_LOCAL_GUESS}) - - # Add anything we found to our list. - IF(NOT PKG_CONFIG_ENV STREQUAL PKG_CONFIG_ENV-NOTFOUND) - SET(VALID_PKG_LIBDIRS "${VALID_PKG_LIBDIRS}:${PKG_CONFIG_ENV}/pkgconfig") - ENDIF(NOT PKG_CONFIG_ENV STREQUAL PKG_CONFIG_ENV-NOTFOUND) - - IF(NOT PKG_CONFIG_MULTI STREQUAL PKG_CONFIG_MULTI-NOTFOUND) - SET(VALID_PKG_LIBDIRS "${VALID_PKG_LIBDIRS}:${PKG_CONFIG_MULTI}/pkgconfig") - ENDIF(NOT PKG_CONFIG_MULTI STREQUAL PKG_CONFIG_MULTI-NOTFOUND) - - IF(NOT PKG_CONFIG_MULTI_LOCAL STREQUAL PKG_CONFIG_MULTI_LOCAL-NOTFOUND) - SET(VALID_PKG_LIBDIRS "${VALID_PKG_LIBDIRS}:${PKG_CONFIG_MULTI_LOCAL}/pkgconfig") - ENDIF(NOT PKG_CONFIG_MULTI_LOCAL STREQUAL PKG_CONFIG_MULTI_LOCAL-NOTFOUND) - - IF(NOT PKG_CONFIG_NO_MULTI STREQUAL PKG_CONFIG_NO_MULTI-NOTFOUND) - SET(VALID_PKG_LIBDIRS "${VALID_PKG_LIBDIRS}:${PKG_CONFIG_NO_MULTI}/pkgconfig") - ENDIF(NOT PKG_CONFIG_NO_MULTI STREQUAL PKG_CONFIG_NO_MULTI-NOTFOUND) - - IF(NOT PKG_CONFIG_NO_MULTI_LOCAL STREQUAL PKG_CONFIG_NO_MULTI_LOCAL-NOTFOUND) - SET(VALID_PKG_LIBDIRS "${VALID_PKG_LIBDIRS}:${PKG_CONFIG_NO_MULTI_LOCAL}/pkgconfig") - ENDIF(NOT PKG_CONFIG_NO_MULTI_LOCAL STREQUAL PKG_CONFIG_NO_MULTI_LOCAL-NOTFOUND) - - # Also add some non-architecture specific package locations. - SET(VALID_PKG_LIBDIRS "${VALID_PKG_LIBDIRS}:/usr/share/pkgconfig:/usr/local/share/pkgconfig") - - # Remove first unwanted ':' - string(SUBSTRING ${VALID_PKG_LIBDIRS} 1 -1 VALID_PKG_LIBDIRS) - - # Set PKG_CONFIG_LIBDIR environment. - SET(ENV{PKG_CONFIG_LIBDIR} ${VALID_PKG_LIBDIRS}) -ENDIF("$ENV{PKG_CONFIG_LIBDIR}" STREQUAL "") - -IF(DEBUG_PKG_CONFIG) - MESSAGE(STATUS "Using PKG_CONFIG_LIBDIR=$ENV{PKG_CONFIG_LIBDIR}") -ENDIF(DEBUG_PKG_CONFIG) - diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index 6ac00fd131..ced012426f 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -214,16 +214,8 @@ elseif(LINUX) if( USE_AUTOBUILD_3P ) list( APPEND release_files - libatk-1.0.so - libfreetype.so.6.6.2 - libfreetype.so.6 - libopenjp2.so - libuuid.so.16 - libuuid.so.16.0.22 - libfontconfig.so.1.8.0 - libfontconfig.so.1 - libgmodule-2.0.so - libgobject-2.0.so + libapr-1.so.0 + libaprutil-1.so.0 ) if(LLCOMMON_LINK_SHARED) diff --git a/indra/cmake/FindPipeWire.cmake b/indra/cmake/FindPipeWire.cmake new file mode 100644 index 0000000000..868acf5ec1 --- /dev/null +++ b/indra/cmake/FindPipeWire.cmake @@ -0,0 +1,100 @@ +# cmake-format: off +# .rst: FindPipeWire +# ------- +# +# Try to find PipeWire on a Unix system. +# +# This will define the following variables: +# +# ``PIPEWIRE_FOUND`` True if (the requested version of) PipeWire is available +# ``PIPEWIRE_VERSION`` The version of PipeWire ``PIPEWIRE_LIBRARIES`` This can +# be passed to target_link_libraries() instead of the ``PipeWire::PipeWire`` +# target ``PIPEWIRE_INCLUDE_DIRS`` This should be passed to +# target_include_directories() if the target is not used for linking +# ``PIPEWIRE_COMPILE_FLAGS`` This should be passed to target_compile_options() +# if the target is not used for linking +# +# If ``PIPEWIRE_FOUND`` is TRUE, it will also define the following imported +# target: +# +# ``PipeWire::PipeWire`` The PipeWire library +# +# In general we recommend using the imported target, as it is easier to use. +# Bear in mind, however, that if the target is in the link interface of an +# exported library, it must be made available by the package config file. + +# ============================================================================= +# Copyright 2014 Alex Merry <alex.merry@kde.org> Copyright 2014 Martin Gräßlin +# <mgraesslin@kde.org> Copyright 2018-2020 Jan Grulich <jgrulich@redhat.com> +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the copyright notice, this list +# of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the copyright notice, this +# list of conditions and the following disclaimer in the documentation and/or +# other materials provided with the distribution. +# 3. The name of the author may not be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +# EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER +# IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ============================================================================= +# cmake-format: on + +# Use pkg-config to get the directories and then use these values in the FIND_PATH() and FIND_LIBRARY() calls +find_package(PkgConfig QUIET) + +pkg_search_module(PKG_PIPEWIRE QUIET libpipewire-0.3) +pkg_search_module(PKG_SPA QUIET libspa-0.2) + +set(PIPEWIRE_COMPILE_FLAGS "${PKG_PIPEWIRE_CFLAGS}" "${PKG_SPA_CFLAGS}") +set(PIPEWIRE_VERSION "${PKG_PIPEWIRE_VERSION}") + +find_path( + PIPEWIRE_INCLUDE_DIRS + NAMES pipewire/pipewire.h + HINTS ${PKG_PIPEWIRE_INCLUDE_DIRS} ${PKG_PIPEWIRE_INCLUDE_DIRS}/pipewire-0.3) + +find_path( + SPA_INCLUDE_DIRS + NAMES spa/param/props.h + HINTS ${PKG_SPA_INCLUDE_DIRS} ${PKG_SPA_INCLUDE_DIRS}/spa-0.2) + +find_library( + PIPEWIRE_LIBRARIES + NAMES pipewire-0.3 + HINTS ${PKG_PIPEWIRE_LIBRARY_DIRS}) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args( + PipeWire + FOUND_VAR PIPEWIRE_FOUND + REQUIRED_VARS PIPEWIRE_LIBRARIES PIPEWIRE_INCLUDE_DIRS SPA_INCLUDE_DIRS + VERSION_VAR PIPEWIRE_VERSION) + +if(PIPEWIRE_FOUND AND NOT TARGET PipeWire::PipeWire) + add_library(PipeWire::PipeWire UNKNOWN IMPORTED) + set_target_properties( + PipeWire::PipeWire + PROPERTIES IMPORTED_LOCATION "${PIPEWIRE_LIBRARIES}" + INTERFACE_COMPILE_OPTIONS "${PIPEWIRE_COMPILE_FLAGS}" + INTERFACE_INCLUDE_DIRECTORIES "${PIPEWIRE_INCLUDE_DIRS};${SPA_INCLUDE_DIRS}") +endif() + +mark_as_advanced(PIPEWIRE_LIBRARIES PIPEWIRE_INCLUDE_DIRS) + +include(FeatureSummary) +set_package_properties( + PipeWire PROPERTIES + URL "https://www.pipewire.org" + DESCRIPTION "PipeWire - multimedia processing") diff --git a/indra/cmake/FreeType.cmake b/indra/cmake/FreeType.cmake index 563491556d..47ac96f793 100644 --- a/indra/cmake/FreeType.cmake +++ b/indra/cmake/FreeType.cmake @@ -11,6 +11,8 @@ target_include_directories( ll::freetype SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/ if (WINDOWS) target_link_libraries( ll::freetype INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/freetype.lib) +elseif( LINUX ) + target_link_libraries( ll::freetype INTERFACE ${LIBS_PREBUILT_DIR}/lib/release/libfreetype.a ${LIBS_PREBUILT_DIR}/lib/release/libpng16.a) else() target_link_libraries( ll::freetype INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/libfreetype.a) endif() diff --git a/indra/cmake/GLIB.cmake b/indra/cmake/GLIB.cmake new file mode 100644 index 0000000000..f52cbb7f87 --- /dev/null +++ b/indra/cmake/GLIB.cmake @@ -0,0 +1,22 @@ +include_guard() + +include(Prebuilt) + +add_library( ll::glib INTERFACE IMPORTED ) +add_library( ll::glib_headers INTERFACE IMPORTED ) +add_library( ll::gio INTERFACE IMPORTED ) + +if( LINUX ) + find_package(PkgConfig REQUIRED) + pkg_search_module(GLIB REQUIRED glib-2.0) + pkg_search_module(GIO REQUIRED gio-2.0) + + target_include_directories( ll::glib SYSTEM INTERFACE ${GLIB_INCLUDE_DIRS} ) + target_link_libraries( ll::glib INTERFACE ${GLIB_LDFLAGS} ) + target_compile_definitions( ll::glib INTERFACE -DLL_GLIB=1) + + target_include_directories( ll::glib_headers SYSTEM INTERFACE ${GLIB_INCLUDE_DIRS} ) + target_compile_definitions( ll::glib_headers INTERFACE -DLL_GLIB=1) + + target_link_libraries( ll::gio INTERFACE ${GIO_LDFLAGS} ) +endif() diff --git a/indra/cmake/GStreamer10Plugin.cmake b/indra/cmake/GStreamer10Plugin.cmake new file mode 100644 index 0000000000..da2e33d04d --- /dev/null +++ b/indra/cmake/GStreamer10Plugin.cmake @@ -0,0 +1,27 @@ +# -*- cmake -*- + +include_guard() + +include(Prebuilt) +include(GLIB) + +add_library( ll::gstreamer10 INTERFACE IMPORTED ) + +if (LINUX) + include(FindPkgConfig) + + pkg_check_modules(GSTREAMER10 REQUIRED gstreamer-1.0) + pkg_check_modules(GSTREAMER10_PLUGINS_BASE REQUIRED gstreamer-plugins-base-1.0) + + target_include_directories( ll::gstreamer10 SYSTEM INTERFACE ${GSTREAMER10_INCLUDE_DIRS}) + target_link_libraries( ll::gstreamer10 INTERFACE ll::glib_headers) + +endif () + +if (GSTREAMER10_FOUND AND GSTREAMER10_PLUGINS_BASE_FOUND) + set(GSTREAMER10 ON CACHE BOOL "Build with GStreamer-1.0 streaming media support.") +endif (GSTREAMER10_FOUND AND GSTREAMER10_PLUGINS_BASE_FOUND) + +if (GSTREAMER10) + add_definitions(-DLL_GSTREAMER10_ENABLED=1) +endif (GSTREAMER10) diff --git a/indra/cmake/LLAddBuildTest.cmake b/indra/cmake/LLAddBuildTest.cmake index 6408f1200c..5d96a4398f 100644 --- a/indra/cmake/LLAddBuildTest.cmake +++ b/indra/cmake/LLAddBuildTest.cmake @@ -1,4 +1,11 @@ # -*- cmake -*- + +include_guard() + +if( NOT LL_TESTS ) + return() +endif() + include(00-Common) include(LLTestCommand) include(bugsplat) diff --git a/indra/cmake/LLWindow.cmake b/indra/cmake/LLWindow.cmake index 2e1b601b79..23f4115aeb 100644 --- a/indra/cmake/LLWindow.cmake +++ b/indra/cmake/LLWindow.cmake @@ -10,13 +10,11 @@ add_library( ll::SDL INTERFACE IMPORTED ) if (LINUX) #Must come first as use_system_binary can exit this file early - target_compile_definitions( ll::SDL INTERFACE LL_SDL=1) + target_compile_definitions( ll::SDL INTERFACE LL_SDL_VERSION=2 LL_SDL) - use_system_binary(SDL) - use_prebuilt_binary(SDL) + #find_package(SDL2 REQUIRED) + #target_link_libraries( ll::SDL INTERFACE SDL2::SDL2 SDL2::SDL2main X11) - target_include_directories( ll::SDL SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/include) - target_link_libraries( ll::SDL INTERFACE SDL directfb fusion direct X11) + use_prebuilt_binary(SDL2) + target_link_libraries( ll::SDL INTERFACE SDL2 X11) endif (LINUX) - - diff --git a/indra/cmake/Linker.cmake b/indra/cmake/Linker.cmake new file mode 100644 index 0000000000..8016842192 --- /dev/null +++ b/indra/cmake/Linker.cmake @@ -0,0 +1,11 @@ +include_guard(GLOBAL) + +if( LINK_WITH_MOLD ) + find_program(MOLD_BIN mold) + if(MOLD_BIN) + message(STATUS "Mold linker found: ${MOLD_BIN}. Enabling mold as active linker.") + add_link_options("-fuse-ld=${MOLD_BIN}") + else() + message(STATUS "Mold linker not found. Using default linker.") + endif() +endif() diff --git a/indra/cmake/Linking.cmake b/indra/cmake/Linking.cmake index 1d757abeff..2bdf0a8aa1 100644 --- a/indra/cmake/Linking.cmake +++ b/indra/cmake/Linking.cmake @@ -7,6 +7,7 @@ set(ARCH_PREBUILT_DIRS ${AUTOBUILD_INSTALL_DIR}/lib) set(ARCH_PREBUILT_DIRS_PLUGINS ${AUTOBUILD_INSTALL_DIR}/plugins) set(ARCH_PREBUILT_DIRS_RELEASE ${AUTOBUILD_INSTALL_DIR}/lib/release) set(ARCH_PREBUILT_DIRS_DEBUG ${AUTOBUILD_INSTALL_DIR}/lib/debug) + if (WINDOWS OR DARWIN ) # Kludge for older cmake versions, 3.20+ is needed to use a genex in add_custom_command( OUTPUT <var> ... ) # Using this will work okay-ish, as Debug is not supported anyway. But for property multi config and also diff --git a/indra/cmake/Lualibs.cmake b/indra/cmake/Lualibs.cmake new file mode 100644 index 0000000000..d66305a8e5 --- /dev/null +++ b/indra/cmake/Lualibs.cmake @@ -0,0 +1,29 @@ +# -*- cmake -*- + +include_guard() + +include(Prebuilt) + +add_library( ll::lualibs INTERFACE IMPORTED ) + +use_system_binary( lualibs ) + +use_prebuilt_binary(luau) + +target_include_directories( ll::lualibs SYSTEM INTERFACE + ${LIBS_PREBUILT_DIR}/include +) + +if (WINDOWS) + target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/Luau.Ast.lib) + target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/Luau.CodeGen.lib) + target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/Luau.Compiler.lib) + target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/Luau.Config.lib) + target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/Luau.VM.lib) +else () + target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/libLuau.CodeGen.a) + target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/libLuau.Compiler.a) + target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/libLuau.Config.a) + target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/libLuau.VM.a) + target_link_libraries(ll::lualibs INTERFACE ${ARCH_PREBUILT_DIRS_RELEASE}/libLuau.Ast.a) +endif () diff --git a/indra/cmake/Meshoptimizer.cmake b/indra/cmake/Meshoptimizer.cmake index fd144d2b97..6983a5895a 100644 --- a/indra/cmake/Meshoptimizer.cmake +++ b/indra/cmake/Meshoptimizer.cmake @@ -12,7 +12,7 @@ use_prebuilt_binary(meshoptimizer) if (WINDOWS) target_link_libraries( ll::meshoptimizer INTERFACE meshoptimizer.lib) elseif (LINUX) - target_link_libraries( ll::meshoptimizer INTERFACE meshoptimizer.o) + target_link_libraries( ll::meshoptimizer INTERFACE libmeshoptimizer.a) elseif (DARWIN) target_link_libraries( ll::meshoptimizer INTERFACE libmeshoptimizer.a) endif (WINDOWS) diff --git a/indra/cmake/NDOF.cmake b/indra/cmake/NDOF.cmake index b88fbccf2a..db9c8b1780 100644 --- a/indra/cmake/NDOF.cmake +++ b/indra/cmake/NDOF.cmake @@ -19,6 +19,6 @@ if (NDOF) target_link_libraries( ll::ndof INTERFACE ndofdev) endif (WINDOWS) target_compile_definitions( ll::ndof INTERFACE LIB_NDOF=1) +else() + add_compile_options(-ULIB_NDOF) endif (NDOF) - - diff --git a/indra/cmake/Prebuilt.cmake b/indra/cmake/Prebuilt.cmake index 634cc15c21..a8c702bfef 100644 --- a/indra/cmake/Prebuilt.cmake +++ b/indra/cmake/Prebuilt.cmake @@ -40,6 +40,7 @@ macro (use_prebuilt_binary _binary) --install-dir=${AUTOBUILD_INSTALL_DIR} ${_binary} ") endif(DEBUG_PREBUILT) + message(STATUS "Installing ${_binary}...") execute_process(COMMAND "${AUTOBUILD_EXECUTABLE}" install --install-dir=${AUTOBUILD_INSTALL_DIR} diff --git a/indra/cmake/Tracy.cmake b/indra/cmake/Tracy.cmake index d54a32fdc2..9eee9eb57f 100644 --- a/indra/cmake/Tracy.cmake +++ b/indra/cmake/Tracy.cmake @@ -6,12 +6,17 @@ add_library( ll::tracy INTERFACE IMPORTED ) # default Tracy profiling on for test builds, but off for all others string(TOLOWER ${VIEWER_CHANNEL} channel_lower) -if(WINDOWS AND channel_lower MATCHES "^second life test") +if(channel_lower MATCHES "^second life test") option(USE_TRACY "Use Tracy profiler." ON) else() option(USE_TRACY "Use Tracy profiler." OFF) endif() +if (LINUX) + # tracy currently conflicts with llcommon/stdtypes.h on linux. keep it disabled until we fix it + set(USE_TRACY OFF) +endif(LINUX) + if (USE_TRACY) option(USE_TRACY_ON_DEMAND "Use on-demand Tracy profiling." ON) option(USE_TRACY_LOCAL_ONLY "Disallow remote Tracy profiling." OFF) @@ -31,6 +36,11 @@ if (USE_TRACY) target_compile_definitions(ll::tracy INTERFACE -DTRACY_NO_BROADCAST=1 -DTRACY_ONLY_LOCALHOST=1) endif () + # GHA runners don't always provide invariant TSC support, but always build with LL_TESTS enabled + if (DARWIN AND LL_TESTS) + target_compile_definitions(ll::tracy INTERFACE -DTRACY_TIMER_FALLBACK=1) + endif () + # See: indra/llcommon/llprofiler.h add_compile_definitions(LL_PROFILER_CONFIGURATION=3) endif (USE_TRACY) diff --git a/indra/cmake/UI.cmake b/indra/cmake/UI.cmake index 8f135676d6..0df62808e7 100644 --- a/indra/cmake/UI.cmake +++ b/indra/cmake/UI.cmake @@ -1,35 +1,33 @@ # -*- cmake -*- include(Prebuilt) include(FreeType) +include(GLIB) add_library( ll::uilibraries INTERFACE IMPORTED ) if (LINUX) - target_compile_definitions(ll::uilibraries INTERFACE LL_GTK=1 LL_X11=1 ) + use_prebuilt_binary(fltk) + target_compile_definitions(ll::uilibraries INTERFACE LL_FLTK=1 LL_X11=1 ) if( USE_CONAN ) - target_link_libraries( ll::uilibraries INTERFACE CONAN_PKG::gtk ) return() endif() - use_prebuilt_binary(gtk-atk-pango-glib) target_link_libraries( ll::uilibraries INTERFACE - atk-1.0 - gdk-x11-2.0 - gdk_pixbuf-2.0 - Xinerama - glib-2.0 - gmodule-2.0 - gobject-2.0 - gthread-2.0 - gtk-x11-2.0 - pango-1.0 - pangoft2-1.0 - pangox-1.0 - pangoxft-1.0 + fltk + Xrender + Xcursor + Xfixes + Xext + Xft Xinerama + ll::fontconfig ll::freetype - ) + ll::SDL + ll::glib + ll::gio + ) + endif (LINUX) if( WINDOWS ) target_link_libraries( ll::uilibraries INTERFACE @@ -51,4 +49,3 @@ endif() target_include_directories( ll::uilibraries SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/include ) - diff --git a/indra/cmake/Variables.cmake b/indra/cmake/Variables.cmake index 5b3aeb8b7f..5dfad25577 100644 --- a/indra/cmake/Variables.cmake +++ b/indra/cmake/Variables.cmake @@ -127,8 +127,6 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") set(CMAKE_SYSTEM_LIBRARY_PATH /usr/lib/${DPKG_ARCH} /usr/local/lib/${DPKG_ARCH} ${CMAKE_SYSTEM_LIBRARY_PATH}) endif (DPKG_RESULT EQUAL 0) - include(ConfigurePkgConfig) - if (INSTALL_PROPRIETARY) # Only turn on headless if we can find osmesa libraries. include(FindPkgConfig) diff --git a/indra/cmake/ViewerMiscLibs.cmake b/indra/cmake/ViewerMiscLibs.cmake index cae68fbc11..2cb11994fc 100644 --- a/indra/cmake/ViewerMiscLibs.cmake +++ b/indra/cmake/ViewerMiscLibs.cmake @@ -2,14 +2,10 @@ include(Prebuilt) if (LINUX) - #use_prebuilt_binary(libuuid) add_library( ll::fontconfig INTERFACE IMPORTED ) - if( NOT USE_CONAN ) - use_prebuilt_binary(fontconfig) - else() - target_link_libraries( ll::fontconfig INTERFACE CONAN_PKG::fontconfig ) - endif() + find_package(Fontconfig REQUIRED) + target_link_libraries( ll::fontconfig INTERFACE Fontconfig::Fontconfig ) endif (LINUX) if( NOT USE_CONAN ) diff --git a/indra/cmake/run_build_test.py b/indra/cmake/run_build_test.py index 940a130a50..312d791d67 100755 --- a/indra/cmake/run_build_test.py +++ b/indra/cmake/run_build_test.py @@ -122,19 +122,17 @@ def main(command, arguments=[], libpath=[], vars={}): # Make sure we see all relevant output *before* child-process output. sys.stdout.flush() try: - return subprocess.call(command_list) - except OSError as err: + return subprocess.run(command_list).returncode + except FileNotFoundError as err: # If the caller is trying to execute a test program that doesn't # exist, we want to produce a reasonable error message rather than a # traceback. This happens when the build is halted by errors, but # CMake tries to proceed with testing anyway <eyeroll/>. However, do # NOT attempt to handle any error but "doesn't exist." - if err.errno != errno.ENOENT: - raise # In practice, the pathnames into CMake's build tree are so long as to # obscure the name of the test program. Just log its basename. - log.warn("No such program %s; check for preceding build errors" % \ - os.path.basename(command[0])) + log.warning("No such program %s; check for preceding build errors" % + os.path.basename(command[0])) # What rc should we simulate for missing executable? Windows produces # 9009. return 9009 diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py index 38de9c7cf1..b5fdccc9ba 100755 --- a/indra/lib/python/indra/util/llmanifest.py +++ b/indra/lib/python/indra/util/llmanifest.py @@ -309,7 +309,7 @@ def main(extra=[]): class LLManifestRegistry(type): def __init__(cls, name, bases, dct): super(LLManifestRegistry, cls).__init__(name, bases, dct) - match = re.match("(\w+)Manifest", name) + match = re.match(r"(\w+)Manifest", name) if match: cls.manifests[match.group(1).lower()] = cls diff --git a/indra/linux_crash_logger/llcrashloggerlinux.cpp b/indra/linux_crash_logger/llcrashloggerlinux.cpp index 9b40de741e..7992f59b36 100644 --- a/indra/linux_crash_logger/llcrashloggerlinux.cpp +++ b/indra/linux_crash_logger/llcrashloggerlinux.cpp @@ -38,9 +38,6 @@ #include "lldir.h" #include "llsdserialize.h" -#if LL_GTK -# include "gtk/gtk.h" -#endif // LL_GTK #define MAX_LOADSTRING 100 @@ -54,52 +51,9 @@ static const char dialog_text[] = static const char dialog_title[] = "Second Life Crash Logger"; -#if LL_GTK -static void response_callback (GtkDialog *dialog, - gint arg1, - gpointer user_data) -{ - gint *response = (gint*)user_data; - *response = arg1; - gtk_widget_destroy(GTK_WIDGET(dialog)); - gtk_main_quit(); -} -#endif // LL_GTK - static BOOL do_ask_dialog(void) { -#if LL_GTK - gtk_disable_setlocale(); - if (!gtk_init_check(NULL, NULL)) { - LL_INFOS() << "Could not initialize GTK for 'ask to send crash report' dialog; not sending report." << LL_ENDL; - return FALSE; - } - - GtkWidget *win = NULL; - GtkDialogFlags flags = GTK_DIALOG_MODAL; - GtkMessageType messagetype = GTK_MESSAGE_QUESTION; - GtkButtonsType buttons = GTK_BUTTONS_YES_NO; - gint response = GTK_RESPONSE_NONE; - - win = gtk_message_dialog_new(NULL, - flags, messagetype, buttons, - "%s", dialog_text); - gtk_window_set_type_hint(GTK_WINDOW(win), - GDK_WINDOW_TYPE_HINT_DIALOG); - gtk_window_set_title(GTK_WINDOW(win), dialog_title); - g_signal_connect (win, - "response", - G_CALLBACK (response_callback), - &response); - gtk_widget_show_all (win); - gtk_main(); - - return (GTK_RESPONSE_OK == response || - GTK_RESPONSE_YES == response || - GTK_RESPONSE_APPLY == response); -#else - return FALSE; -#endif // LL_GTK + // Ask to send crash report. Yes/No dialog. } LLCrashLoggerLinux::LLCrashLoggerLinux(void) diff --git a/indra/llappearance/llpolymesh.cpp b/indra/llappearance/llpolymesh.cpp index 97f9ca68b6..719381b4fc 100644 --- a/indra/llappearance/llpolymesh.cpp +++ b/indra/llappearance/llpolymesh.cpp @@ -981,7 +981,7 @@ void LLPolyMesh::initializeForMorph() LLVector4a::memcpyNonAliased16((F32*) mScaledNormals, (F32*) mSharedData->mBaseNormals, sizeof(LLVector4a) * mSharedData->mNumVertices); LLVector4a::memcpyNonAliased16((F32*) mBinormals, (F32*) mSharedData->mBaseNormals, sizeof(LLVector4a) * mSharedData->mNumVertices); LLVector4a::memcpyNonAliased16((F32*) mScaledBinormals, (F32*) mSharedData->mBaseNormals, sizeof(LLVector4a) * mSharedData->mNumVertices); - LLVector4a::memcpyNonAliased16((F32*) mTexCoords, (F32*) mSharedData->mTexCoords, sizeof(LLVector2) * (mSharedData->mNumVertices + mSharedData->mNumVertices%2)); + memcpy((F32*) mTexCoords, (F32*) mSharedData->mTexCoords, sizeof(LLVector2) * (mSharedData->mNumVertices)); // allocated in LLPolyMeshSharedData::allocateVertexData for (S32 i = 0; i < mSharedData->mNumVertices; ++i) { diff --git a/indra/llappearance/llwearable.cpp b/indra/llappearance/llwearable.cpp index a7e5292fed..ae038e09cc 100644 --- a/indra/llappearance/llwearable.cpp +++ b/indra/llappearance/llwearable.cpp @@ -645,9 +645,10 @@ void LLWearable::addVisualParam(LLVisualParam *param) void LLWearable::setVisualParamWeight(S32 param_index, F32 value) { - if( is_in_map(mVisualParamIndexMap, param_index ) ) + visual_param_index_map_t::iterator found = mVisualParamIndexMap.find(param_index); + if (found != mVisualParamIndexMap.end()) { - LLVisualParam *wearable_param = mVisualParamIndexMap[param_index]; + LLVisualParam *wearable_param = found->second; wearable_param->setWeight(value); } else @@ -658,9 +659,10 @@ void LLWearable::setVisualParamWeight(S32 param_index, F32 value) F32 LLWearable::getVisualParamWeight(S32 param_index) const { - if( is_in_map(mVisualParamIndexMap, param_index ) ) + visual_param_index_map_t::const_iterator found = mVisualParamIndexMap.find(param_index); + if(found != mVisualParamIndexMap.end()) { - const LLVisualParam *wearable_param = mVisualParamIndexMap.find(param_index)->second; + const LLVisualParam *wearable_param = found->second; return wearable_param->getWeight(); } else @@ -733,9 +735,9 @@ void LLWearable::writeToAvatar(LLAvatarAppearance* avatarp) if( (((LLViewerVisualParam*)param)->getWearableType() == mType) && (!((LLViewerVisualParam*)param)->getCrossWearable()) ) { S32 param_id = param->getID(); + // get weight from wearable and write back into character F32 weight = getVisualParamWeight(param_id); - - avatarp->setVisualParamWeight( param_id, weight); + param->setWeight(weight); } } } diff --git a/indra/llappearance/llwearabledata.cpp b/indra/llappearance/llwearabledata.cpp index 7598ed67f3..f3b76da224 100644 --- a/indra/llappearance/llwearabledata.cpp +++ b/indra/llappearance/llwearabledata.cpp @@ -286,43 +286,45 @@ const LLWearable* LLWearableData::getWearable(const LLWearableType::EType type, LLWearable* LLWearableData::getTopWearable(const LLWearableType::EType type) { - U32 count = getWearableCount(type); - if ( count == 0) + wearableentry_map_t::const_iterator wearable_iter = mWearableDatas.find(type); + if (wearable_iter == mWearableDatas.end()) { return NULL; } + const wearableentry_vec_t& wearable_vec = wearable_iter->second; - return getWearable(type, count-1); + size_t size = wearable_vec.size(); + if (size == 0) + { + return NULL; + } + return wearable_vec[size - 1]; } const LLWearable* LLWearableData::getTopWearable(const LLWearableType::EType type) const { - U32 count = getWearableCount(type); - if ( count == 0) + wearableentry_map_t::const_iterator wearable_iter = mWearableDatas.find(type); + if (wearable_iter == mWearableDatas.end()) { return NULL; } + const wearableentry_vec_t& wearable_vec = wearable_iter->second; - return getWearable(type, count-1); -} - -LLWearable* LLWearableData::getBottomWearable(const LLWearableType::EType type) -{ - if (getWearableCount(type) == 0) + size_t size = wearable_vec.size(); + if (size == 0) { return NULL; } + return wearable_vec[size - 1]; +} +LLWearable* LLWearableData::getBottomWearable(const LLWearableType::EType type) +{ return getWearable(type, 0); } const LLWearable* LLWearableData::getBottomWearable(const LLWearableType::EType type) const { - if (getWearableCount(type) == 0) - { - return NULL; - } - return getWearable(type, 0); } diff --git a/indra/llaudio/llaudioengine_openal.cpp b/indra/llaudio/llaudioengine_openal.cpp index 18d682b554..755547bfaa 100644 --- a/indra/llaudio/llaudioengine_openal.cpp +++ b/indra/llaudio/llaudioengine_openal.cpp @@ -79,7 +79,7 @@ bool LLAudioEngine_OpenAL::init(void* userdata, const std::string &app_title) ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext()); alcGetIntegerv(device, ALC_MAJOR_VERSION, 1, &major); - alcGetIntegerv(device, ALC_MAJOR_VERSION, 1, &minor); + alcGetIntegerv(device, ALC_MINOR_VERSION, 1, &minor); LL_INFOS() << "ALC version: " << major << "." << minor << LL_ENDL; LL_INFOS() << "ALC default device: " @@ -190,6 +190,8 @@ LLAudioChannelOpenAL::~LLAudioChannelOpenAL() void LLAudioChannelOpenAL::cleanup() { alSourceStop(mALSource); + alSourcei(mALSource, AL_BUFFER, AL_NONE); + mCurrentBufferp = NULL; } diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 60549d9d11..78bfaade55 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -16,6 +16,8 @@ include(Tracy) set(llcommon_SOURCE_FILES apply.cpp commoncontrol.cpp + coro_scheduler.cpp + hbxxh.cpp indra_constants.cpp lazyeventapi.cpp llapp.cpp @@ -53,8 +55,8 @@ set(llcommon_SOURCE_FILES llframetimer.cpp llheartbeat.cpp llheteromap.cpp - llinitparam.cpp llinitdestroyclass.cpp + llinitparam.cpp llinstancetracker.cpp llkeybind.cpp llleap.cpp @@ -64,15 +66,15 @@ set(llcommon_SOURCE_FILES llmd5.cpp llmemory.cpp llmemorystream.cpp - llmetrics.cpp llmetricperformancetester.cpp + llmetrics.cpp llmortician.cpp llmutex.cpp - llptrto.cpp llpredicate.cpp llprocess.cpp llprocessor.cpp llprocinfo.cpp + llptrto.cpp llqueuedthread.cpp llrand.cpp llrefcount.cpp @@ -102,9 +104,14 @@ set(llcommon_SOURCE_FILES lluriparser.cpp lluuid.cpp llworkerthread.cpp - hbxxh.cpp - u64.cpp + lockstatic.cpp + lua_function.cpp + lualistener.cpp + resultset.cpp + scriptcommand.cpp threadpool.cpp + throttle.cpp + u64.cpp workqueue.cpp StackWalker.cpp ) @@ -117,9 +124,12 @@ set(llcommon_HEADER_FILES chrono.h classic_callback.h commoncontrol.h + coro_scheduler.h ctype_workaround.h fix_macros.h + fsyspath.h function_types.h + hbxxh.h indra_constants.h lazyeventapi.h linden_common.h @@ -138,6 +148,7 @@ set(llcommon_HEADER_FILES llcommonutils.h llcond.h llcoros.h + llcoromutex.h llcrc.h llcriticaldamp.h lldate.h @@ -154,9 +165,9 @@ set(llcommon_HEADER_FILES lleventapi.h lleventcoro.h lleventdispatcher.h + lleventemitter.h lleventfilter.h llevents.h - lleventemitter.h llexception.h llfasttimer.h llfile.h @@ -173,6 +184,7 @@ set(llcommon_HEADER_FILES llinitparam.h llinstancetracker.h llinstancetrackersubclass.h + llinttracker.h llkeybind.h llkeythrottle.h llleap.h @@ -183,14 +195,12 @@ set(llcommon_HEADER_FILES llmd5.h llmemory.h llmemorystream.h - llmetrics.h llmetricperformancetester.h + llmetrics.h llmortician.h llmutex.h llnametable.h llpointer.h - llprofiler.h - llprofilercategories.h llpounceable.h llpredicate.h llpreprocessor.h @@ -198,6 +208,8 @@ set(llcommon_HEADER_FILES llprocess.h llprocessor.h llprocinfo.h + llprofiler.h + llprofilercategories.h llptrto.h llqueuedthread.h llrand.h @@ -215,14 +227,14 @@ set(llcommon_HEADER_FILES llsimplehash.h llsingleton.h llstacktrace.h + llstaticstringtable.h + llstatsaccumulator.h llstl.h llstreamqueue.h llstreamtools.h llstrider.h llstring.h llstringtable.h - llstaticstringtable.h - llstatsaccumulator.h llsys.h lltempredirect.h llthread.h @@ -241,13 +253,18 @@ set(llcommon_HEADER_FILES lluuid.h llwin32headers.h llworkerthread.h - hbxxh.h lockstatic.h + lua_function.h + lualistener.h + resultset.h + scriptcommand.h stdtypes.h stringize.h + tempset.h threadpool.h threadpool_fwd.h threadsafeschedule.h + throttle.h timer.h tuple.h u64.h diff --git a/indra/llcommon/always_return.h b/indra/llcommon/always_return.h index a206471da5..a56a8b443e 100644 --- a/indra/llcommon/always_return.h +++ b/indra/llcommon/always_return.h @@ -14,6 +14,7 @@ #define LL_ALWAYS_RETURN_H #include <type_traits> // std::enable_if, std::is_convertible +#include <utility> // std::forward namespace LL { @@ -79,6 +80,22 @@ namespace LL DESIRED mDefault; }; + // specialize for AlwaysReturn<void> + template <> + struct AlwaysReturn<void> + { + public: + AlwaysReturn() {} + + // callable returns a type not convertible to DESIRED, return default + template <typename CALLABLE, typename... ARGS> + void operator()(CALLABLE&& callable, ARGS&&... args) + { + // discard whatever callable(args) returns + std::forward<CALLABLE>(callable)(std::forward<ARGS>(args)...); + } + }; + /** * always_return<T>(some_function, some_args...) calls * some_function(some_args...). It is guaranteed to return a value of type diff --git a/indra/llcommon/coro_scheduler.cpp b/indra/llcommon/coro_scheduler.cpp new file mode 100644 index 0000000000..02b9f11333 --- /dev/null +++ b/indra/llcommon/coro_scheduler.cpp @@ -0,0 +1,164 @@ +/** + * @file coro_scheduler.cpp + * @author Nat Goodspeed + * @date 2024-08-05 + * @brief Implementation for llcoro::scheduler. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "coro_scheduler.h" +// STL headers +// std headers +#include <iomanip> +// external library headers +#include <boost/fiber/operations.hpp> +// other Linden headers +#include "llcallbacklist.h" +#include "lldate.h" +#include "llerror.h" + +namespace llcoro +{ + +const F64 scheduler::DEFAULT_TIMESLICE{ LL::Timers::DEFAULT_TIMESLICE }; + +const std::string qname("General"); + +scheduler::scheduler(): + // Since use_scheduling_algorithm() must be called before any other + // Boost.Fibers operations, we can assume that the calling fiber is in + // fact the main fiber. + mMainID(boost::this_fiber::get_id()), + mStart(LLDate::now().secondsSinceEpoch()), + mQueue(LL::WorkQueue::getInstance(qname)) +{} + +void scheduler::awakened( boost::fibers::context* ctx) noexcept +{ + if (ctx->get_id() == mMainID) + { + // If the fiber that just came ready is the main fiber, record its + // pointer. + llassert(! mMainCtx); + mMainCtx = ctx; + } + // Delegate to round_robin::awakened() as usual, even for the main fiber. + // This way, as long as other fibers don't take too long, we can just let + // normal round_robin processing pass control to the main fiber. + super::awakened(ctx); +} + +boost::fibers::context* scheduler::pick_next() noexcept +{ + // count calls to pick_next() + ++mSwitches; + // pick_next() is called when the previous fiber has suspended, and we + // need to pick another. Did the previous pick_next() call pick the main + // fiber? If so, it's the main fiber that just suspended. + auto now = LLDate::now().secondsSinceEpoch(); + if (mMainRunning) + { + mMainRunning = false; + mMainLast = now; + } + + boost::fibers::context* next; + + // When the main fiber is ready, and it's been more than mTimeslice since + // the main fiber last ran, it's time to intervene. + F64 elapsed(now - mMainLast); + if (mMainCtx && elapsed > mTimeslice) + { + // We claim that the main fiber is not only stored in mMainCtx, but is + // also queued (somewhere) in our ready list. + llassert(mMainCtx->ready_is_linked()); + // The usefulness of a doubly-linked list is that, given only a + // pointer to an item, we can unlink it. + mMainCtx->ready_unlink(); + // Instead of delegating to round_robin::pick_next() to pop the head + // of the queue, override by returning mMainCtx. + next = mMainCtx; + + /*------------------------- logging stuff --------------------------*/ + // Unless this log tag is enabled, don't even bother posting. + LL_DEBUGS("LLCoros.scheduler"); + // This feature is inherently hard to verify. The logging in the + // lambda below seems useful, but also seems like a lot of overhead + // for a coroutine context switch. Try posting the logging lambda to a + // ThreadPool to offload that overhead. However, if this is still + // taking an unreasonable amount of context-switch time, this whole + // passage could be skipped. + + // Record this event for logging, but push it off to a thread pool to + // perform that work. Presumably std::weak_ptr::lock() is cheaper than + // WorkQueue::getInstance(). + LL::WorkQueue::ptr_t queue{ mQueue.lock() }; + // We probably started before the relevant WorkQueue was created. + if (! queue) + { + // Try again to locate the specified WorkQueue. + queue = LL::WorkQueue::getInstance(qname); + mQueue = queue; + } + // Both the lock() call and the getInstance() call might have failed. + if (queue) + { + // Bind values. Do NOT bind 'this' to avoid cross-thread access! + // It would be interesting to know from what queue position we + // unlinked the main fiber, out of how many in the ready list. + // Unfortunately round_robin::rqueue_ is private, not protected, + // so we have no access. + queue->post( + [switches=mSwitches, start=mStart, elapsed, now] + () + { + U32 runtime(U32(now) - U32(start)); + U32 minutes(runtime / 60u); + U32 seconds(runtime % 60u); + // use stringize to avoid lasting side effects to the + // logging ostream + LL_DEBUGS("LLCoros.scheduler") + << "At time " + << stringize(minutes, ":", std::setw(2), std::setfill('0'), seconds) + << " (" << switches << " switches), coroutines took " + << stringize(std::setprecision(4), elapsed) + << " sec, main coroutine jumped queue" + << LL_ENDL; + }); + } + LL_ENDL; + /*----------------------- end logging stuff ------------------------*/ + } + else + { + // Either the main fiber isn't yet ready, or it hasn't yet been + // mTimeslice seconds since the last time the main fiber ran. Business + // as usual. + next = super::pick_next(); + } + + // super::pick_next() could also have returned the main fiber, which is + // why this is a separate test instead of being folded into the override + // case above. + if (next && next->get_id() == mMainID) + { + // we're about to resume the main fiber: it's no longer "ready" + mMainCtx = nullptr; + // instead, it's "running" + mMainRunning = true; + } + return next; +} + +void scheduler::use() +{ + boost::fibers::use_scheduling_algorithm<scheduler>(); +} + +} // namespace llcoro diff --git a/indra/llcommon/coro_scheduler.h b/indra/llcommon/coro_scheduler.h new file mode 100644 index 0000000000..eee2d746b5 --- /dev/null +++ b/indra/llcommon/coro_scheduler.h @@ -0,0 +1,73 @@ +/** + * @file coro_scheduler.h + * @author Nat Goodspeed + * @date 2024-08-05 + * @brief Custom scheduler for viewer's Boost.Fibers (aka coroutines) + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_CORO_SCHEDULER_H) +#define LL_CORO_SCHEDULER_H + +#include "workqueue.h" +#include <boost/fiber/fiber.hpp> +#include <boost/fiber/algo/round_robin.hpp> + +/** + * llcoro::scheduler is specifically intended for the viewer's main thread. + * Its role is to ensure that the main coroutine, responsible for UI + * operations and coordinating everything else, doesn't get starved by + * secondary coroutines -- however many of those there might be. + * + * The simple boost::fibers::algo::round_robin scheduler could result in + * arbitrary time lag between resumptions of the main coroutine. Of course + * every well-behaved viewer coroutine must be coded to yield before too much + * real time has elapsed, but sheer volume of secondary coroutines could still + * consume unreasonable real time before cycling back to the main coroutine. + */ + +namespace llcoro +{ + +class scheduler: public boost::fibers::algo::round_robin +{ + using super = boost::fibers::algo::round_robin; +public: + // If the main fiber is ready, and it's been at least this long since the + // main fiber last ran, jump the main fiber to the head of the queue. + static const F64 DEFAULT_TIMESLICE; + + scheduler(); + void awakened( boost::fibers::context*) noexcept override; + boost::fibers::context* pick_next() noexcept override; + + static void use(); + +private: + // This is the fiber::id of the main fiber. We use this to discover + // whether the fiber passed to awakened() is in fact the main fiber. + boost::fibers::fiber::id mMainID; + // This context* is nullptr until awakened() notices that the main fiber + // has become ready, at which point it contains the main fiber's context*. + boost::fibers::context* mMainCtx{}; + // Set when pick_next() returns the main fiber. + bool mMainRunning{ false }; + // If it's been at least this long since the last time the main fiber got + // control, jump it to the head of the queue. + F64 mTimeslice{ DEFAULT_TIMESLICE }; + // Timestamp as of the last time we suspended the main fiber. + F64 mMainLast{ 0 }; + // Timestamp of start time + F64 mStart{ 0 }; + // count context switches + U64 mSwitches{ 0 }; + // WorkQueue for deferred logging + LL::WorkQueue::weak_t mQueue; +}; + +} // namespace llcoro + +#endif /* ! defined(LL_CORO_SCHEDULER_H) */ diff --git a/indra/llcommon/fsyspath.h b/indra/llcommon/fsyspath.h new file mode 100644 index 0000000000..1b4aec09b4 --- /dev/null +++ b/indra/llcommon/fsyspath.h @@ -0,0 +1,79 @@ +/** + * @file fsyspath.h + * @author Nat Goodspeed + * @date 2024-04-03 + * @brief Adapt our UTF-8 std::strings for std::filesystem::path + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_FSYSPATH_H) +#define LL_FSYSPATH_H + +#include <filesystem> + +// While std::filesystem::path can be directly constructed from std::string on +// both Posix and Windows, that's not what we want on Windows. Per +// https://en.cppreference.com/w/cpp/filesystem/path/path: + +// ... the method of conversion to the native character set depends on the +// character type used by source. +// +// * If the source character type is char, the encoding of the source is +// assumed to be the native narrow encoding (so no conversion takes place on +// POSIX systems). +// * If the source character type is char8_t, conversion from UTF-8 to native +// filesystem encoding is used. (since C++20) +// * If the source character type is wchar_t, the input is assumed to be the +// native wide encoding (so no conversion takes places on Windows). + +// The trouble is that on Windows, from std::string ("source character type is +// char"), the "native narrow encoding" isn't UTF-8, so file paths containing +// non-ASCII characters get mangled. +// +// Once we're building with C++20, we could pass a UTF-8 std::string through a +// vector<char8_t> to engage std::filesystem::path's own UTF-8 conversion. But +// sigh, as of 2024-04-03 we're not yet there. +// +// Anyway, encapsulating the important UTF-8 conversions in our own subclass +// allows us to migrate forward to C++20 conventions without changing +// referencing code. + +class fsyspath: public std::filesystem::path +{ + using super = std::filesystem::path; + +public: + // default + fsyspath() {} + // construct from UTF-8 encoded std::string + fsyspath(const std::string& path): super(std::filesystem::u8path(path)) {} + // construct from UTF-8 encoded const char* + fsyspath(const char* path): super(std::filesystem::u8path(path)) {} + // construct from existing path + fsyspath(const super& path): super(path) {} + + fsyspath& operator=(const super& p) { super::operator=(p); return *this; } + fsyspath& operator=(const std::string& p) + { + super::operator=(std::filesystem::u8path(p)); + return *this; + } + fsyspath& operator=(const char* p) + { + super::operator=(std::filesystem::u8path(p)); + return *this; + } + + // shadow base-class string() method with UTF-8 aware method + std::string string() const { return super::u8string(); } + // On Posix systems, where value_type is already char, this operator + // std::string() method shadows the base class operator string_type() + // method. But on Windows, where value_type is wchar_t, the base class + // doesn't have operator std::string(). Provide it. + operator std::string() const { return string(); } +}; + +#endif /* ! defined(LL_FSYSPATH_H) */ diff --git a/indra/test/hexdump.h b/indra/llcommon/hexdump.h index 95f1e297c3..4b734426a3 100644..100755 --- a/indra/test/hexdump.h +++ b/indra/llcommon/hexdump.h @@ -1,8 +1,8 @@ /** * @file hexdump.h * @author Nat Goodspeed - * @date 2023-09-08 - * @brief Provide hexdump() and hexmix() ostream formatters + * @date 2023-10-03 + * @brief iostream manipulators to stream hex, or string with nonprinting chars * * $LicenseInfo:firstyear=2023&license=viewerlgpl$ * Copyright (c) 2023, Linden Research, Inc. @@ -17,12 +17,15 @@ #include <iostream> #include <string_view> +namespace LL +{ + // Format a given byte string as 2-digit hex values, no separators // Usage: std::cout << hexdump(somestring) << ... class hexdump { public: - hexdump(const std::string_view& data): + hexdump(std::string_view data): hexdump(data.data(), data.length()) {} @@ -30,6 +33,10 @@ public: hexdump(reinterpret_cast<const unsigned char*>(data), len) {} + hexdump(const std::vector<unsigned char>& data): + hexdump(data.data(), data.size()) + {} + hexdump(const unsigned char* data, size_t len): mData(data, data + len) {} @@ -59,7 +66,7 @@ private: class hexmix { public: - hexmix(const std::string_view& data): + hexmix(std::string_view data): mData(data) {} @@ -94,4 +101,6 @@ private: std::string mData; }; +} // namespace LL + #endif /* ! defined(LL_HEXDUMP_H) */ diff --git a/indra/llcommon/lazyeventapi.cpp b/indra/llcommon/lazyeventapi.cpp index 91db0ee4a6..eebed374c3 100644 --- a/indra/llcommon/lazyeventapi.cpp +++ b/indra/llcommon/lazyeventapi.cpp @@ -47,7 +47,9 @@ LL::LazyEventAPIBase::~LazyEventAPIBase() // case, do NOT unregister their name out from under them! // If this is a static instance being destroyed at process shutdown, // LLEventPumps will probably have been cleaned up already. - if (mRegistered && ! LLEventPumps::wasDeleted()) + // That said, in a test program, LLEventPumps might never have been + // constructed to start with. + if (mRegistered && LLEventPumps::instanceExists()) { // unregister the callback to this doomed instance LLEventPumps::instance().unregisterPumpFactory(mParams.name); diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index 3db03aec7d..c1dce97632 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -88,10 +88,6 @@ LLApp* LLApp::sApplication = NULL; // and disables crashlogger bool LLApp::sDisableCrashlogger = false; -// Local flag for whether or not to do logging in signal handlers. -//static -bool LLApp::sLogInSignal = true; - // static // Keeps track of application status LLScalarCond<LLApp::EAppStatus> LLApp::sStatus{LLApp::APP_STATUS_STOPPED}; @@ -314,7 +310,7 @@ void LLApp::stepFrame() { LLFrameTimer::updateFrameTime(); LLFrameTimer::updateFrameCount(); - LLEventTimer::updateClass(); + LLCallbackList::instance().callFunctions(); mRunner.run(); } @@ -596,6 +592,10 @@ void default_unix_signal_handler(int signum, siginfo_t *info, void *) // We do the somewhat sketchy operation of blocking in here until the error handler // has gracefully stopped the app. + // FIXME(brad) - we are using this handler for asynchronous signals as well, so sLogInSignal is currently + // disabled for safety. we need to find a way to selectively reenable it when it is safe. + // see issue secondlife/viewer#2566 + if (LLApp::sLogInSignal) { LL_INFOS() << "Signal handler - Got signal " << signum << " - " << apr_signal_description_get(signum) << LL_ENDL; diff --git a/indra/llcommon/llapp.h b/indra/llcommon/llapp.h index d90ecdf661..3d18864b80 100644 --- a/indra/llcommon/llapp.h +++ b/indra/llcommon/llapp.h @@ -339,8 +339,12 @@ private: friend void default_unix_signal_handler(int signum, siginfo_t *info, void *); #endif -public: - static bool sLogInSignal; +private: +#ifdef LL_RELEASE_FOR_DOWNLOAD + static constexpr bool sLogInSignal = false; +#else + static constexpr bool sLogInSignal = true; +#endif }; #endif // LL_LLAPP_H diff --git a/indra/llcommon/llapr.h b/indra/llcommon/llapr.h index 693cd7c01f..ff5c8c6a33 100644 --- a/indra/llcommon/llapr.h +++ b/indra/llcommon/llapr.h @@ -33,6 +33,7 @@ #include <sys/param.h> // Need PATH_MAX in APR headers... #endif +#include <memory> #include <boost/noncopyable.hpp> #include "llwin32headers.h" #include "apr_thread_proc.h" diff --git a/indra/llcommon/llassettype.cpp b/indra/llcommon/llassettype.cpp index c09cf7abd2..b0dbc84186 100644 --- a/indra/llcommon/llassettype.cpp +++ b/indra/llcommon/llassettype.cpp @@ -30,6 +30,7 @@ #include "lldictionary.h" #include "llmemory.h" #include "llsingleton.h" +#include "llsd.h" ///---------------------------------------------------------------------------- /// Class LLAssetType @@ -246,3 +247,19 @@ bool LLAssetType::lookupIsAssetIDKnowable(EType asset_type) } return false; } + +LLSD LLAssetType::getTypeNames() +{ + LLSD type_names; + const LLAssetDictionary *dict = LLAssetDictionary::getInstance(); + for (S32 type = 0; type < AT_COUNT; ++type) + { + const AssetEntry *entry = dict->lookup(LLAssetType::EType(type)); + // skip llassettype_bad_lookup + if (entry) + { + type_names.append(entry->mTypeName); + } + } + return type_names; +} diff --git a/indra/llcommon/llassettype.h b/indra/llcommon/llassettype.h index 547c3f4329..17177d81c3 100644 --- a/indra/llcommon/llassettype.h +++ b/indra/llcommon/llassettype.h @@ -165,6 +165,8 @@ public: static bool lookupIsAssetFetchByIDAllowed(EType asset_type); // the asset allows direct download static bool lookupIsAssetIDKnowable(EType asset_type); // asset data can be known by the viewer + static LLSD getTypeNames(); + static const std::string BADLOOKUP; protected: diff --git a/indra/llcommon/llcallbacklist.cpp b/indra/llcommon/llcallbacklist.cpp index 3d5d30bd90..7b05c25c21 100644 --- a/indra/llcommon/llcallbacklist.cpp +++ b/indra/llcommon/llcallbacklist.cpp @@ -24,18 +24,23 @@ * $/LicenseInfo$ */ +#include "lazyeventapi.h" #include "llcallbacklist.h" -#include "lleventtimer.h" -#include "llerrorlegacy.h" - -// Globals -// -LLCallbackList gIdleCallbacks; +#include "llerror.h" +#include "llexception.h" +#include "llsdutil.h" +#include "tempset.h" +#include <boost/container_hash/hash.hpp> +#include <iomanip> +#include <vector> // // Member functions // +/***************************************************************************** +* LLCallbackList +*****************************************************************************/ LLCallbackList::LLCallbackList() { // nothing @@ -45,46 +50,42 @@ LLCallbackList::~LLCallbackList() { } - -void LLCallbackList::addFunction( callback_t func, void *data) +LLCallbackList::handle_t LLCallbackList::addFunction( callback_t func, void *data) { if (!func) { - return; + return {}; } // only add one callback per func/data pair // if (containsFunction(func, data)) { - return; + return {}; } - callback_pair_t t(func, data); - mCallbackList.push_back(t); + auto handle = addFunction([func, data]{ func(data); }); + mLookup.emplace(callback_pair_t(func, data), handle); + return handle; } -bool LLCallbackList::containsFunction( callback_t func, void *data) +LLCallbackList::handle_t LLCallbackList::addFunction( const callable_t& func ) { - callback_pair_t t(func, data); - callback_list_t::iterator iter = find(func,data); - if (iter != mCallbackList.end()) - { - return true; - } - else - { - return false; - } + return mCallbackList.connect(func); } +bool LLCallbackList::containsFunction( callback_t func, void *data) +{ + return mLookup.find(callback_pair_t(func, data)) != mLookup.end(); +} bool LLCallbackList::deleteFunction( callback_t func, void *data) { - callback_list_t::iterator iter = find(func,data); - if (iter != mCallbackList.end()) + auto found = mLookup.find(callback_pair_t(func, data)); + if (found != mLookup.end()) { - mCallbackList.erase(iter); + deleteFunction(found->second); + mLookup.erase(found); return true; } else @@ -93,138 +94,449 @@ bool LLCallbackList::deleteFunction( callback_t func, void *data) } } -inline -LLCallbackList::callback_list_t::iterator -LLCallbackList::find(callback_t func, void *data) +void LLCallbackList::deleteFunction( const handle_t& handle ) { - callback_pair_t t(func, data); - return std::find(mCallbackList.begin(), mCallbackList.end(), t); + handle.disconnect(); } void LLCallbackList::deleteAllFunctions() { - mCallbackList.clear(); + mCallbackList = {}; + mLookup.clear(); } - void LLCallbackList::callFunctions() { - for (callback_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); ) + mCallbackList(); +} + +LLCallbackList::handle_t LLCallbackList::doOnIdleOneTime( const callable_t& func ) +{ + // connect_extended() passes the connection to the callback + return mCallbackList.connect_extended( + [func](const handle_t& handle) + { + handle.disconnect(); + func(); + }); +} + +LLCallbackList::handle_t LLCallbackList::doOnIdleRepeating( const bool_func_t& func ) +{ + return mCallbackList.connect_extended( + [func](const handle_t& handle) + { + if (func()) + { + handle.disconnect(); + } + }); +} + +/***************************************************************************** +* LL::Timers +*****************************************************************************/ +namespace LL +{ + +Timers::Timers() {} + +// Call a given callable once at specified timestamp. +Timers::handle_t Timers::scheduleAt(nullary_func_t callable, LLDate::timestamp time) +{ + // tick() assumes you want to run periodically until you return true. + // Schedule a task that returns true after a single call. + return scheduleAtEvery(once(callable), time, 0); +} + +// Call a given callable once after specified interval. +Timers::handle_t Timers::scheduleAfter(nullary_func_t callable, F32 seconds) +{ + return scheduleEvery(once(callable), seconds); +} + +// Call a given callable every specified number of seconds, until it returns true. +Timers::handle_t Timers::scheduleEvery(bool_func_t callable, F32 seconds) +{ + return scheduleAtEvery(callable, now() + seconds, seconds); +} + +Timers::handle_t Timers::scheduleAtEvery(bool_func_t callable, + LLDate::timestamp time, F32 interval) +{ + // Pick token FIRST to store a self-reference in mQueue's managed node as + // well as in mMeta. Pre-increment to distinguish 0 from any live + // handle_t. + token_t token{ ++mToken }; + // For the moment, store a default-constructed mQueue handle -- + // we'll fill in later. + auto [iter, inserted] = mMeta.emplace(token, + Metadata{ queue_t::handle_type(), time, interval }); + // It's important that our token is unique. + llassert(inserted); + + // Remember whether this is the first entry in mQueue + bool first{ mQueue.empty() }; + auto handle{ mQueue.emplace(callable, token, time) }; + // Now that we have an mQueue handle_type, store it in mMeta entry. + iter->second.mHandle = handle; + if (first && ! mLive.connected()) { - callback_list_t::iterator curiter = iter++; - curiter->first(curiter->second); + // If this is our first entry, register for regular callbacks. + mLive = LLCallbackList::instance().doOnIdleRepeating([this]{ return tick(); }); } + // Make an Timers::handle_t from token. + return { token }; } -// Shim class to allow arbitrary boost::bind -// expressions to be run as one-time idle callbacks. -class OnIdleCallbackOneTime +bool Timers::isRunning(handle_t timer) const { -public: - OnIdleCallbackOneTime(nullary_func_t callable): - mCallable(callable) + // A default-constructed timer isn't running. + // A timer we don't find in mMeta has fired or been canceled. + return timer && mMeta.find(timer.token) != mMeta.end(); +} + +F32 Timers::timeUntilCall(handle_t timer) const +{ + MetaMap::const_iterator found; + if ((! timer) || (found = mMeta.find(timer.token)) == mMeta.end()) { + return 0.f; } - static void onIdle(void *data) + else { - gIdleCallbacks.deleteFunction(onIdle, data); - OnIdleCallbackOneTime* self = reinterpret_cast<OnIdleCallbackOneTime*>(data); - self->call(); - delete self; + return narrow(found->second.mTime - now()); } - void call() +} + +// Cancel a future timer set by scheduleAt(), scheduleAfter(), scheduleEvery() +bool Timers::cancel(handle_t& timer) +{ + // For exception safety, capture and clear timer before canceling. + // Once we've canceled this handle, don't retain the live handle. + const handle_t ctimer{ timer }; + timer = handle_t(); + return cancel(ctimer); +} + +bool Timers::cancel(const handle_t& timer) +{ + if (! timer) { - mCallable(); + return false; } -private: - nullary_func_t mCallable; -}; -void doOnIdleOneTime(nullary_func_t callable) -{ - OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable); - gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor); + // fibonacci_heap documentation does not address the question of what + // happens if you call erase() twice with the same handle. Is it a no-op? + // Does it invalidate the heap? Is it UB? + + // Nor do we find any documented way to ask whether a given handle still + // tracks a valid heap node. That's why we capture all returned handles in + // mMeta and validate against that collection. What about the pop() + // call in tick()? How to map from the top() value back to the + // corresponding handle_t? That's why we store func_at::mToken. + + // fibonacci_heap provides a pair of begin()/end() methods to iterate over + // all nodes (NOT in heap order), plus a function to convert from such + // iterators to handles. Without mMeta, that would be our only chance + // to validate. + auto found{ mMeta.find(timer.token) }; + if (found == mMeta.end()) + { + // we don't recognize this handle -- maybe the timer has already + // fired, maybe it was previously canceled. + return false; + } + + // Funny case: what if the callback directly or indirectly reaches a + // cancel() call for its own handle? + if (found->second.mRunning) + { + // tick() has special logic to defer the actual deletion until the + // callback has returned + found->second.mCancel = true; + // this handle does in fact reference a live timer, + // which we're going to cancel when we get a chance + return true; + } + + // Erase from mQueue the handle_type referenced by timer.token. + mQueue.erase(found->second.mHandle); + // before erasing the mMeta entry + mMeta.erase(found); + if (mQueue.empty()) + { + // If that was the last active timer, unregister for callbacks. + //LLCallbackList::instance().deleteFunction(mLive); + // Since we're in the source file that knows the true identity of an + // LLCallbackList::handle_t, we don't even need to call instance(). + mLive.disconnect(); + } + return true; } -// Shim class to allow generic boost functions to be run as -// recurring idle callbacks. Callable should return true when done, -// false to continue getting called. -class OnIdleCallbackRepeating +void Timers::setTimeslice(F32 timeslice) { -public: - OnIdleCallbackRepeating(bool_func_t callable): - mCallable(callable) + if (timeslice < MINIMUM_TIMESLICE) { + // use stringize() so setprecision() affects only the temporary + // ostream, not the common logging ostream + LL_WARNS("Timers") << "LL::Timers::setTimeslice(" + << stringize(std::setprecision(4), timeslice) + << ") less than " + << stringize(std::setprecision(4), MINIMUM_TIMESLICE) + << ", ignoring" << LL_ENDL; } - // Will keep getting called until the callable returns true. - static void onIdle(void *data) + else + { + mTimeslice = timeslice; + } +} + +bool Timers::tick() +{ + // Fetch current time only on entry, even though running some mQueue task + // may take long enough that the next one after would become ready. We're + // sharing this thread with everything else, and there's a risk we might + // starve it if we have a sequence of tasks that take nontrivial time. + auto now{ LLDate::now().secondsSinceEpoch() }; + auto cutoff{ now + mTimeslice }; + + // Capture tasks we've processed but that want to be rescheduled. + // Defer rescheduling them immediately to avoid getting stuck looping over + // a recurring task with a nonpositive interval. + std::vector<std::pair<MetaMap::iterator, func_at>> deferred; + + while (! mQueue.empty()) { - OnIdleCallbackRepeating* self = reinterpret_cast<OnIdleCallbackRepeating*>(data); - bool done = self->call(); - if (done) + auto& top{ mQueue.top() }; + if (top.mTime > now) { - gIdleCallbacks.deleteFunction(onIdle, data); - delete self; + // we've hit an entry that's still in the future: + // done with this tick() + break; } + if (LLDate::now().secondsSinceEpoch() > cutoff) + { + // we still have ready tasks, but we've already eaten too much + // time this tick() -- defer until next tick() + break; + } + + // Found a ready task. Look up its corresponding mMeta entry. + auto meta{ mMeta.find(top.mToken) }; + llassert(meta != mMeta.end()); + bool done; + { + // Mark our mMeta entry so we don't cancel this timer while its + // callback is running, but unmark it even in case of exception. + TempSet running(meta->second.mRunning, true); + // run the callback and capture its desire to end repetition + try + { + done = top.mFunc(); + } + catch (...) + { + // Don't crash if a timer callable throws. + // But don't continue calling that callable, either. + done = true; + LOG_UNHANDLED_EXCEPTION("LL::Timers"); + } + } // clear mRunning + + // If mFunc() returned true (all done, stop calling me) or + // meta->mCancel (somebody tried to cancel this timer during the + // callback call), then we're done: clean up both entries. + if (done || meta->second.mCancel) + { + // remove the mMeta entry referencing this task + mMeta.erase(meta); + } + else + { + // mFunc returned false, and nobody asked to cancel: + // continue calling this task at a future time. + meta->second.mTime += meta->second.mInterval; + // capture this task to reschedule once we break loop + deferred.push_back({meta, top}); + // update func_at's mTime to match meta's + deferred.back().second.mTime = meta->second.mTime; + } + // Remove the mQueue entry regardless, or we risk stalling the + // queue right here if we have a nonpositive interval. + mQueue.pop(); } - bool call() + + // Now reschedule any tasks that need to be rescheduled. + for (const auto& [meta, task] : deferred) { - return mCallable(); + auto handle{ mQueue.push(task) }; + // track this new mQueue handle_type + meta->second.mHandle = handle; } -private: - bool_func_t mCallable; -}; -void doOnIdleRepeating(bool_func_t callable) -{ - OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable); - gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor); + // If, after all the twiddling above, our queue ended up empty, + // stop calling every tick. + return mQueue.empty(); } -class NullaryFuncEventTimer: public LLEventTimer +/***************************************************************************** +* TimersListener +*****************************************************************************/ + +class TimersListener: public LLEventAPI { public: - NullaryFuncEventTimer(nullary_func_t callable, F32 seconds): - LLEventTimer(seconds), - mCallable(callable) + TimersListener(const LazyEventAPIParams& params): LLEventAPI(params) {} + + // Forbid a script from requesting callbacks too quickly. + static constexpr LLSD::Real MINTIMER{ 0.010 }; + + void scheduleAfter(const LLSD& params); + void scheduleEvery(const LLSD& params); + LLSD cancel(const LLSD& params); + LLSD isRunning(const LLSD& params); + LLSD timeUntilCall(const LLSD& params); + +private: + // We use the incoming reqid to distinguish different timers -- but reqid + // by itself is not unique! Each reqid is local to a calling script. + // Distinguish scripts by reply-pump name, then reqid within script. + // "Additional specializations for std::pair and the standard container + // types, as well as utility functions to compose hashes are available in + // boost::hash." + // https://en.cppreference.com/w/cpp/utility/hash + using HandleKey = std::pair<LLSD::String, LLSD::Integer>; + using HandleMap = std::unordered_map<HandleKey, Timers::temp_handle_t, + boost::hash<HandleKey>>; + HandleMap mHandles; +}; + +void TimersListener::scheduleAfter(const LLSD& params) +{ + // Timer creation functions respond immediately with the reqid of the + // created timer, as well as later when the timer fires. That lets the + // requester invoke cancel, isRunning or timeUntilCall. + Response response(LLSD(), params); + LLSD::Real after{ params["after"] }; + if (after < MINTIMER) { + return response.error(stringize("after must be at least ", MINTIMER)); } -private: - bool tick() + HandleKey key{ params["reply"], params["reqid"] }; + mHandles.emplace( + key, + Timers::instance().scheduleAfter( + [this, params, key] + { + // we don't need any content save for the "reqid" + sendReply({}, params); + // ditch mHandles entry + mHandles.erase(key); + }, + narrow(after))); +} + +void TimersListener::scheduleEvery(const LLSD& params) +{ + // Timer creation functions respond immediately with the reqid of the + // created timer, as well as later when the timer fires. That lets the + // requester invoke cancel, isRunning or timeUntilCall. + Response response(LLSD(), params); + LLSD::Real every{ params["every"] }; + if (every < MINTIMER) { - mCallable(); - return true; + return response.error(stringize("every must be at least ", MINTIMER)); } - nullary_func_t mCallable; -}; + mHandles.emplace( + HandleKey{ params["reply"], params["reqid"] }, + Timers::instance().scheduleEvery( + [params, i=0]() mutable + { + // we don't need any content save for the "reqid" + sendReply(llsd::map("i", i++), params); + // we can't use a handshake -- always keep the ball rolling + return false; + }, + narrow(every))); +} -// Call a given callable once after specified interval. -void doAfterInterval(nullary_func_t callable, F32 seconds) +LLSD TimersListener::cancel(const LLSD& params) { - new NullaryFuncEventTimer(callable, seconds); + auto found{ mHandles.find({params["reply"], params["id"]}) }; + bool ok = false; + if (found != mHandles.end()) + { + ok = true; + Timers::instance().cancel(found->second); + mHandles.erase(found); + } + return llsd::map("ok", ok); } -class BoolFuncEventTimer: public LLEventTimer +LLSD TimersListener::isRunning(const LLSD& params) { -public: - BoolFuncEventTimer(bool_func_t callable, F32 seconds): - LLEventTimer(seconds), - mCallable(callable) + auto found{ mHandles.find({params["reply"], params["id"]}) }; + bool running = false; + if (found != mHandles.end()) { + running = Timers::instance().isRunning(found->second); } -private: - bool tick() + return llsd::map("running", running); +} + +LLSD TimersListener::timeUntilCall(const LLSD& params) +{ + auto found{ mHandles.find({params["reply"], params["id"]}) }; + bool ok = false; + LLSD::Real remaining = 0; + if (found != mHandles.end()) { - return mCallable(); + ok = true; + remaining = Timers::instance().timeUntilCall(found->second); } + return llsd::map("ok", ok, "remaining", remaining); +} - bool_func_t mCallable; +class TimersRegistrar: public LazyEventAPI<TimersListener> +{ + using super = LazyEventAPI<TimersListener>; + using super::listener; + +public: + TimersRegistrar(): + super("Timers", "Provide access to viewer timer functionality.") + { + add("scheduleAfter", +R"-(Create a timer with ID "reqid". Post response after "after" seconds.)-", + &listener::scheduleAfter, + llsd::map("reqid", LLSD::Integer(), "after", LLSD::Real())); + add("scheduleEvery", +R"-(Create a timer with ID "reqid". Post response every "every" seconds +until cancel().)-", + &listener::scheduleEvery, + llsd::map("reqid", LLSD::Integer(), "every", LLSD::Real())); + add("cancel", +R"-(Cancel the timer with ID "id". Respond "ok"=true if "id" identifies +a live timer.)-", + &listener::cancel, + llsd::map("reqid", LLSD::Integer(), "id", LLSD::Integer())); + add("isRunning", +R"-(Query the timer with ID "id": respond "running"=true if "id" identifies +a live timer.)-", + &listener::isRunning, + llsd::map("reqid", LLSD::Integer(), "id", LLSD::Integer())); + add("timeUntilCall", +R"-(Query the timer with ID "id": if "id" identifies a live timer, respond +"ok"=true, "remaining"=seconds with the time left before timer expiry; +otherwise "ok"=false, "remaining"=0.)-", + &listener::timeUntilCall, + llsd::map("reqid", LLSD::Integer())); + } }; +static TimersRegistrar registrar; -// Call a given callable every specified number of seconds, until it returns true. -void doPeriodically(bool_func_t callable, F32 seconds) -{ - new BoolFuncEventTimer(callable, seconds); -} +} // namespace LL diff --git a/indra/llcommon/llcallbacklist.h b/indra/llcommon/llcallbacklist.h index d6c415f7c5..fb4696188a 100644 --- a/indra/llcommon/llcallbacklist.h +++ b/indra/llcommon/llcallbacklist.h @@ -27,53 +27,282 @@ #ifndef LL_LLCALLBACKLIST_H #define LL_LLCALLBACKLIST_H +#include "lldate.h" +#include "llsingleton.h" #include "llstl.h" -#include <boost/function.hpp> -#include <list> +#include <boost/container_hash/hash.hpp> +#include <boost/heap/fibonacci_heap.hpp> +#include <boost/signals2.hpp> +#include <functional> +#include <unordered_map> -class LLCallbackList +/***************************************************************************** +* LLCallbackList: callbacks every idle tick (every callFunctions() call) +*****************************************************************************/ +class LLCallbackList: public LLSingleton<LLCallbackList> { + LLSINGLETON(LLCallbackList); public: typedef void (*callback_t)(void*); - typedef std::pair< callback_t,void* > callback_pair_t; - // NOTE: It is confirmed that we DEPEND on the order provided by using a list :( - // - typedef std::list< callback_pair_t > callback_list_t; + typedef boost::signals2::signal<void()> callback_list_t; + typedef callback_list_t::slot_type callable_t; + typedef boost::signals2::connection handle_t; + typedef boost::signals2::scoped_connection temp_handle_t; + typedef std::function<bool ()> bool_func_t; - LLCallbackList(); ~LLCallbackList(); - void addFunction( callback_t func, void *data = NULL ); // register a callback, which will be called as func(data) + handle_t addFunction( callback_t func, void *data = NULL ); // register a callback, which will be called as func(data) + handle_t addFunction( const callable_t& func ); bool containsFunction( callback_t func, void *data = NULL ); // true if list already contains the function/data pair bool deleteFunction( callback_t func, void *data = NULL ); // removes the first instance of this function/data pair from the list, false if not found - void callFunctions(); // calls all functions + void deleteFunction( const handle_t& handle ); + void callFunctions(); // calls all functions void deleteAllFunctions(); + handle_t doOnIdleOneTime( const callable_t& func ); + handle_t doOnIdleRepeating( const bool_func_t& func ); + bool isRunning(const handle_t& handle) const { return handle.connected(); }; + static void test(); protected: - - inline callback_list_t::iterator find(callback_t func, void *data); - callback_list_t mCallbackList; + + // "Additional specializations for std::pair and the standard container + // types, as well as utility functions to compose hashes are available in + // boost::hash." + // https://en.cppreference.com/w/cpp/utility/hash + typedef std::pair< callback_t,void* > callback_pair_t; + typedef std::unordered_map<callback_pair_t, handle_t, + boost::hash<callback_pair_t>> lookup_table; + lookup_table mLookup; }; -typedef boost::function<void ()> nullary_func_t; -typedef boost::function<bool ()> bool_func_t; +/*-------------------- legacy names in global namespace --------------------*/ +#define gIdleCallbacks (LLCallbackList::instance()) + +using nullary_func_t = LLCallbackList::callable_t; +using bool_func_t = LLCallbackList::bool_func_t; // Call a given callable once in idle loop. -void doOnIdleOneTime(nullary_func_t callable); +inline +LLCallbackList::handle_t doOnIdleOneTime(nullary_func_t callable) +{ + return gIdleCallbacks.doOnIdleOneTime(callable); +} // Repeatedly call a callable in idle loop until it returns true. -void doOnIdleRepeating(bool_func_t callable); +inline +LLCallbackList::handle_t doOnIdleRepeating(bool_func_t callable) +{ + return gIdleCallbacks.doOnIdleRepeating(callable); +} + +/***************************************************************************** +* LL::Timers: callbacks at some future time +*****************************************************************************/ +namespace LL +{ + +class Timers: public LLSingleton<Timers> +{ + LLSINGLETON(Timers); + + using token_t = U32; + + // Define a struct for our priority queue entries, instead of using + // a tuple, because we need to define the comparison operator. + struct func_at + { + // callback to run when this timer fires + bool_func_t mFunc; + // key to look up metadata in mHandles + token_t mToken; + // time at which this timer is supposed to fire + LLDate::timestamp mTime; + + func_at(const bool_func_t& func, token_t token, LLDate::timestamp tm): + mFunc(func), + mToken(token), + mTime(tm) + {} + + friend bool operator<(const func_at& lhs, const func_at& rhs) + { + // use greater-than because we want fibonacci_heap to select the + // EARLIEST time as the top() + return lhs.mTime > rhs.mTime; + } + }; + + // Accept default stable<false>: when two funcs have the same timestamp, + // we don't care in what order they're called. + // Specify constant_time_size<false>: we don't need to optimize the size() + // method, iow we don't need to store and maintain a count of entries. + typedef boost::heap::fibonacci_heap<func_at, boost::heap::constant_time_size<false>> + queue_t; + +public: + // If tasks that come ready during a given tick() take longer than this, + // defer any subsequent ready tasks to a future tick() call. + static constexpr F32 DEFAULT_TIMESLICE{ 0.005f }; + // Setting timeslice to be less than MINIMUM_TIMESLICE could lock up + // Timers processing, causing it to believe it's exceeded the allowable + // time every tick before processing ANY queue items. + static constexpr F32 MINIMUM_TIMESLICE{ 0.001f }; + + class handle_t + { + private: + friend class Timers; + token_t token; + public: + handle_t(token_t token=0): token(token) {} + bool operator==(const handle_t& rhs) const { return this->token == rhs.token; } + explicit operator bool() const { return bool(token); } + bool operator!() const { return ! bool(*this); } + }; + // Call a given callable once at specified timestamp. + handle_t scheduleAt(nullary_func_t callable, LLDate::timestamp time); + + // Call a given callable once after specified interval. + handle_t scheduleAfter(nullary_func_t callable, F32 seconds); + + // Call a given callable every specified number of seconds, until it returns true. + handle_t scheduleEvery(bool_func_t callable, F32 seconds); + + // test whether specified handle is still live + bool isRunning(handle_t timer) const; + // check remaining time + F32 timeUntilCall(handle_t timer) const; + + // Cancel a future timer set by scheduleAt(), scheduleAfter(), scheduleEvery(). + // Return true if and only if the handle corresponds to a live timer. + bool cancel(const handle_t& timer); + // If we're canceling a non-const handle_t, also clear it so we need not + // cancel again. + bool cancel(handle_t& timer); + + F32 getTimeslice() const { return mTimeslice; } + void setTimeslice(F32 timeslice); + + // Store a handle_t returned by scheduleAt(), scheduleAfter() or + // scheduleEvery() in a temp_handle_t to cancel() automatically on + // destruction of the temp_handle_t. + class temp_handle_t + { + public: + temp_handle_t() = default; + temp_handle_t(const handle_t& hdl): mHandle(hdl) {} + temp_handle_t(const temp_handle_t&) = delete; + temp_handle_t(temp_handle_t&&) = default; + temp_handle_t& operator=(const handle_t& hdl) + { + // initializing a new temp_handle_t, then swapping it into *this, + // takes care of destroying any previous mHandle + temp_handle_t replacement(hdl); + swap(replacement); + return *this; + } + temp_handle_t& operator=(const temp_handle_t&) = delete; + temp_handle_t& operator=(temp_handle_t&&) = default; + ~temp_handle_t() + { + cancel(); + } + + // temp_handle_t should be usable wherever handle_t is + operator handle_t() const { return mHandle; } + // If we're dealing with a non-const temp_handle_t, pass a reference + // to our handle_t member (e.g. to Timers::cancel()). + operator handle_t&() { return mHandle; } + + // For those in the know, provide a cancel() method of our own that + // avoids Timers::instance() lookup when mHandle isn't live. + bool cancel() + { + if (! mHandle) + { + return false; + } + else + { + return Timers::instance().cancel(mHandle); + } + } + + void swap(temp_handle_t& other) noexcept + { + std::swap(this->mHandle, other.mHandle); + } + + private: + handle_t mHandle; + }; + +private: + handle_t scheduleAtEvery(bool_func_t callable, LLDate::timestamp time, F32 interval); + LLDate::timestamp now() const { return LLDate::now().secondsSinceEpoch(); } + // wrap a nullary_func_t with a bool_func_t that will only execute once + bool_func_t once(nullary_func_t callable) + { + return [callable] + { + callable(); + return true; + }; + } + bool tick(); + + // NOTE: We don't lock our data members because it doesn't make sense to + // register cross-thread callbacks. If we start wanting to use Timers on + // threads other than the main thread, it would make more sense to make + // our data members thread_local than to lock them. + + // the heap aka priority queue + queue_t mQueue; + + // metadata about a given task + struct Metadata + { + // handle to mQueue entry + queue_t::handle_type mHandle; + // time at which this timer is supposed to fire + LLDate::timestamp mTime; + // interval at which this timer is supposed to fire repeatedly + F32 mInterval{ 0 }; + // mFunc is currently running: don't delete this entry + bool mRunning{ false }; + // cancel() was called while mFunc was running: deferred cancel + bool mCancel{ false }; + }; + + using MetaMap = std::unordered_map<token_t, Metadata>; + MetaMap mMeta; + token_t mToken{ 0 }; + // While mQueue is non-empty, register for regular callbacks. + LLCallbackList::temp_handle_t mLive; + F32 mTimeslice{ DEFAULT_TIMESLICE }; +}; + +} // namespace LL + +/*-------------------- legacy names in global namespace --------------------*/ // Call a given callable once after specified interval. -void doAfterInterval(nullary_func_t callable, F32 seconds); +inline +LL::Timers::handle_t doAfterInterval(nullary_func_t callable, F32 seconds) +{ + return LL::Timers::instance().scheduleAfter(callable, seconds); +} // Call a given callable every specified number of seconds, until it returns true. -void doPeriodically(bool_func_t callable, F32 seconds); - -extern LLCallbackList gIdleCallbacks; +inline +LL::Timers::handle_t doPeriodically(bool_func_t callable, F32 seconds) +{ + return LL::Timers::instance().scheduleEvery(callable, seconds); +} #endif diff --git a/indra/llcommon/llcoromutex.h b/indra/llcommon/llcoromutex.h new file mode 100644 index 0000000000..c0ceac4b22 --- /dev/null +++ b/indra/llcommon/llcoromutex.h @@ -0,0 +1,64 @@ +/** + * @file llcoromutex.h + * @author Nat Goodspeed + * @date 2024-09-04 + * @brief Coroutine-aware synchronization primitives + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLCOROMUTEX_H) +#define LL_LLCOROMUTEX_H + +#include "mutex.h" +#include <boost/fiber/future/promise.hpp> +#include <boost/fiber/future/future.hpp> + +// e.g. #include LLCOROS_MUTEX_HEADER +#define LLCOROS_MUTEX_HEADER <boost/fiber/mutex.hpp> +#define LLCOROS_RMUTEX_HEADER <boost/fiber/recursive_mutex.hpp> +#define LLCOROS_CONDVAR_HEADER <boost/fiber/condition_variable.hpp> + +namespace boost { + namespace fibers { + class mutex; + class recursive_mutex; + enum class cv_status; + class condition_variable; + } +} + +namespace llcoro +{ + +/** + * Aliases for promise and future. An older underlying future implementation + * required us to wrap future; that's no longer needed. However -- if it's + * important to restore kill() functionality, we might need to provide a + * proxy, so continue using the aliases. + */ +template <typename T> +using Promise = boost::fibers::promise<T>; +template <typename T> +using Future = boost::fibers::future<T>; +template <typename T> +inline +static Future<T> getFuture(Promise<T>& promise) { return promise.get_future(); } + +// use mutex, lock, condition_variable suitable for coroutines +using Mutex = boost::fibers::mutex; +using RMutex = boost::fibers::recursive_mutex; +// With C++17, LockType is deprecated: at this point we can directly +// declare 'std::unique_lock lk(some_mutex)' without explicitly stating +// the mutex type. Sadly, making LockType an alias template for +// std::unique_lock doesn't work the same way: Class Template Argument +// Deduction only works for class templates, not alias templates. +using LockType = std::unique_lock<Mutex>; +using cv_status = boost::fibers::cv_status; +using ConditionVariable = boost::fibers::condition_variable; + +} // namespace llcoro + +#endif /* ! defined(LL_LLCOROMUTEX_H) */ diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp index 1539b48bd3..1ae5c87a00 100644 --- a/indra/llcommon/llcoros.cpp +++ b/indra/llcommon/llcoros.cpp @@ -51,11 +51,12 @@ #endif // other Linden headers #include "llapp.h" -#include "lltimer.h" -#include "llevents.h" #include "llerror.h" -#include "stringize.h" +#include "llevents.h" #include "llexception.h" +#include "llsdutil.h" +#include "lltimer.h" +#include "stringize.h" #if LL_WINDOWS #include <excpt.h> @@ -64,12 +65,8 @@ // static bool LLCoros::on_main_coro() { - if (!LLCoros::instanceExists() || LLCoros::getName().empty()) - { - return true; - } - - return false; + return (!LLCoros::instanceExists() || + LLCoros::getName().empty()); } // static @@ -79,7 +76,7 @@ bool LLCoros::on_main_thread_main_coro() } // static -LLCoros::CoroData& LLCoros::get_CoroData(const std::string& caller) +LLCoros::CoroData& LLCoros::get_CoroData(const std::string&) { CoroData* current{ nullptr }; // be careful about attempted accesses in the final throes of app shutdown @@ -111,7 +108,7 @@ LLCoros::coro::id LLCoros::get_self() //static void LLCoros::set_consuming(bool consuming) { - CoroData& data(get_CoroData("set_consuming()")); + auto& data(get_CoroData("set_consuming()")); // DO NOT call this on the main() coroutine. llassert_always(! data.mName.empty()); data.mConsuming = consuming; @@ -140,11 +137,20 @@ LLCoros::LLCoros(): // Previously we used // boost::context::guarded_stack_allocator::default_stacksize(); // empirically this is insufficient. - mStackSize(1024*1024), + mStackSize(512*1024), // mCurrent does NOT own the current CoroData instance -- it simply // points to it. So initialize it with a no-op deleter. mCurrent{ [](CoroData*){} } { + auto& llapp{ LLEventPumps::instance().obtain("LLApp") }; + if (llapp.getListener("LLCoros") == LLBoundListener()) + { + // chain our "LLCoros" pump onto "LLApp" pump: echo events posted to "LLApp" + mConn = llapp.listen( + "LLCoros", + [](const LLSD& event) + { return LLEventPumps::instance().obtain("LLCoros").post(event); }); + } } LLCoros::~LLCoros() @@ -172,7 +178,7 @@ void LLCoros::cleanupSingleton() // don't use llcoro::suspend() because that module depends // on this one // This will yield current(main) thread and will let active - // corutines run once + // coroutines run once boost::this_fiber::yield(); } printActiveCoroutines("after pumping"); @@ -194,26 +200,26 @@ std::string LLCoros::generateDistinctName(const std::string& prefix) const // Until we find an unused name, append a numeric suffix for uniqueness. while (CoroData::getInstance(name)) { - name = STRINGIZE(prefix << unique++); + name = stringize(prefix, unique++); } return name; } -/*==========================================================================*| -bool LLCoros::kill(const std::string& name) +bool LLCoros::killreq(const std::string& name) { - CoroMap::iterator found = mCoros.find(name); - if (found == mCoros.end()) + auto found = CoroData::getInstance(name); + if (! found) { return false; } - // Because this is a boost::ptr_map, erasing the map entry also destroys - // the referenced heap object, in this case the boost::coroutine object, - // which will terminate the coroutine. - mCoros.erase(found); + // Next time the subject coroutine calls checkStop(), make it terminate. + found->mKilledBy = getName(); + // But if it's waiting for something, notify anyone in a position to poke + // it. + LLEventPumps::instance().obtain("LLCoros").post( + llsd::map("status", "killreq", "coro", name)); return true; } -|*==========================================================================*/ //static std::string LLCoros::getName() @@ -224,7 +230,7 @@ std::string LLCoros::getName() //static std::string LLCoros::logname() { - LLCoros::CoroData& data(get_CoroData("logname()")); + auto& data(get_CoroData("logname()")); return data.mName.empty()? data.getKey() : data.mName; } @@ -277,7 +283,7 @@ std::string LLCoros::launch(const std::string& prefix, const callable_t& callabl // std::allocator_arg is a flag to indicate that the following argument is // a StackAllocator. // protected_fixedsize_stack sets a guard page past the end of the new - // stack so that stack underflow will result in an access violation + // stack so that stack overflow will result in an access violation // instead of weird, subtle, possibly undiagnosed memory stomps. try @@ -303,55 +309,6 @@ std::string LLCoros::launch(const std::string& prefix, const callable_t& callabl return name; } -namespace -{ - -#if LL_WINDOWS - -static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific - -U32 exception_filter(U32 code, struct _EXCEPTION_POINTERS *exception_infop) -{ - if (code == STATUS_MSC_EXCEPTION) - { - // C++ exception, go on - return EXCEPTION_CONTINUE_SEARCH; - } - else - { - // handle it - return EXCEPTION_EXECUTE_HANDLER; - } -} - -void sehandle(const LLCoros::callable_t& callable) -{ - __try - { - callable(); - } - __except (exception_filter(GetExceptionCode(), GetExceptionInformation())) - { - // convert to C++ styled exception - // Note: it might be better to use _se_set_translator - // if you want exception to inherit full callstack - char integer_string[512]; - sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode()); - throw std::exception(integer_string); - } -} - -#else // ! LL_WINDOWS - -inline void sehandle(const LLCoros::callable_t& callable) -{ - callable(); -} - -#endif // ! LL_WINDOWS - -} // anonymous namespace - // Top-level wrapper around caller's coroutine callable. // Normally we like to pass strings and such by const reference -- but in this // case, we WANT to copy both the name and the callable to our local stack! @@ -362,10 +319,11 @@ void LLCoros::toplevel(std::string name, callable_t callable) // set it as current mCurrent.reset(&corodata); + LL_DEBUGS("LLCoros") << "entering " << name << LL_ENDL; // run the code the caller actually wants in the coroutine try { - sehandle(callable); + LL::seh::catcher(callable); } catch (const Stop& exc) { @@ -377,7 +335,7 @@ void LLCoros::toplevel(std::string name, callable_t callable) // Any uncaught exception derived from LLContinueError will be caught // here and logged. This coroutine will terminate but the rest of the // viewer will carry on. - LOG_UNHANDLED_EXCEPTION(STRINGIZE("coroutine " << name)); + LOG_UNHANDLED_EXCEPTION("coroutine " + name); } catch (...) { @@ -390,15 +348,24 @@ void LLCoros::toplevel(std::string name, callable_t callable) } //static -void LLCoros::checkStop() +void LLCoros::checkStop(callable_t cleanup) { + // don't replicate this 'if' test throughout the code below + if (! cleanup) + { + cleanup = {[](){}}; // hey, look, I'm coding in Haskell! + } + if (wasDeleted()) { + cleanup(); LLTHROW(Shutdown("LLCoros was deleted")); } - // do this AFTER the check above, because getName() depends on - // get_CoroData(), which depends on the local_ptr in our instance(). - if (getName().empty()) + + // do this AFTER the check above, because get_CoroData() depends on the + // local_ptr in our instance(). + auto& data(get_CoroData("checkStop()")); + if (data.mName.empty()) { // Our Stop exception and its subclasses are intended to stop loitering // coroutines. Don't throw it from the main coroutine. @@ -406,19 +373,80 @@ void LLCoros::checkStop() } if (LLApp::isStopped()) { + cleanup(); LLTHROW(Stopped("viewer is stopped")); } if (! LLApp::isRunning()) { + cleanup(); LLTHROW(Stopping("viewer is stopping")); } + if (! data.mKilledBy.empty()) + { + // Someone wants to kill this coroutine + cleanup(); + LLTHROW(Killed(stringize("coroutine ", data.mName, " killed by ", data.mKilledBy))); + } +} + +LLBoundListener LLCoros::getStopListener(const std::string& caller, LLVoidListener cleanup) +{ + if (! cleanup) + return {}; + + // This overload only responds to viewer shutdown. + return LLEventPumps::instance().obtain("LLCoros") + .listen( + LLEventPump::inventName(caller), + [cleanup](const LLSD& event) + { + auto status{ event["status"].asString() }; + if (status != "running" && status != "killreq") + { + cleanup(event); + } + return false; + }); +} + +LLBoundListener LLCoros::getStopListener(const std::string& caller, + const std::string& cnsmr, + LLVoidListener cleanup) +{ + if (! cleanup) + return {}; + + std::string consumer{cnsmr}; + if (consumer.empty()) + { + consumer = getName(); + } + + // This overload responds to viewer shutdown and to killreq(consumer). + return LLEventPumps::instance().obtain("LLCoros") + .listen( + LLEventPump::inventName(caller), + [consumer, cleanup](const LLSD& event) + { + auto status{ event["status"].asString() }; + if (status == "killreq") + { + if (event["coro"].asString() == consumer) + { + cleanup(event); + } + } + else if (status != "running") + { + cleanup(event); + } + return false; + }); } LLCoros::CoroData::CoroData(const std::string& name): LLInstanceTracker<CoroData, std::string>(name), mName(name), - // don't consume events unless specifically directed - mConsuming(false), mCreationTime(LLTimer::getTotalSeconds()) { } @@ -431,7 +459,6 @@ LLCoros::CoroData::CoroData(int n): // empty string as its visible name because some consumers test for that. LLInstanceTracker<CoroData, std::string>("main" + stringize(n)), mName(), - mConsuming(false), mCreationTime(LLTimer::getTotalSeconds()) { } diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index c3820ae987..0291d7f1d9 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -29,30 +29,16 @@ #if ! defined(LL_LLCOROS_H) #define LL_LLCOROS_H +#include "llcoromutex.h" +#include "llevents.h" #include "llexception.h" -#include <boost/fiber/fss.hpp> -#include <boost/fiber/future/future.hpp> -#include <boost/fiber/future/promise.hpp> -#include <boost/fiber/recursive_mutex.hpp> -#include "mutex.h" -#include "llsingleton.h" #include "llinstancetracker.h" -#include <boost/function.hpp> -#include <string> +#include "llsingleton.h" +#include <boost/fiber/fss.hpp> #include <exception> +#include <functional> #include <queue> - -// e.g. #include LLCOROS_MUTEX_HEADER -#define LLCOROS_MUTEX_HEADER <boost/fiber/mutex.hpp> -#define LLCOROS_CONDVAR_HEADER <boost/fiber/condition_variable.hpp> - -namespace boost { - namespace fibers { - class mutex; - enum class cv_status; - class condition_variable; - } -} +#include <string> /** * Registry of named Boost.Coroutine instances @@ -112,7 +98,7 @@ public: /// stuck with the term "coroutine." typedef boost::fibers::fiber coro; /// Canonical callable type - typedef boost::function<void()> callable_t; + typedef std::function<void()> callable_t; /** * Create and start running a new coroutine with specified name. The name @@ -154,13 +140,13 @@ public: std::string launch(const std::string& prefix, const callable_t& callable); /** - * Abort a running coroutine by name. Normally, when a coroutine either + * Ask the named coroutine to abort. Normally, when a coroutine either * runs to completion or terminates with an exception, LLCoros quietly * cleans it up. This is for use only when you must explicitly interrupt * one prematurely. Returns @c true if the specified name was found and * still running at the time. */ -// bool kill(const std::string& name); + bool killreq(const std::string& name); /** * From within a coroutine, look up the (tweaked) name string by which @@ -262,15 +248,21 @@ public: /// thrown by checkStop() // It may sound ironic that Stop is derived from LLContinueError, but the // point is that LLContinueError is the category of exception that should - // not immediately crash the viewer. Stop and its subclasses are to notify - // coroutines that the viewer intends to shut down. The expected response - // is to terminate the coroutine, rather than abort the viewer. + // not immediately crash the viewer. Stop and its subclasses are to tell + // coroutines to terminate, e.g. because the viewer is shutting down. We + // do not want any such exception to crash the viewer. struct Stop: public LLContinueError { Stop(const std::string& what): LLContinueError(what) {} }; - /// early stages + /// someone wants to kill this specific coroutine + struct Killed: public Stop + { + Killed(const std::string& what): Stop(what) {} + }; + + /// early shutdown stages struct Stopping: public Stop { Stopping(const std::string& what): Stop(what) {} @@ -289,34 +281,50 @@ public: }; /// Call this intermittently if there's a chance your coroutine might - /// continue running into application shutdown. Throws Stop if LLCoros has - /// been cleaned up. - static void checkStop(); + /// still be running at application shutdown. Throws one of the Stop + /// subclasses if the caller needs to terminate. Pass a cleanup function + /// if you need to execute that cleanup before terminating. + /// Of course, if your cleanup function throws, that will be the exception + /// propagated by checkStop(). + static void checkStop(callable_t cleanup={}); + + /// Call getStopListener() at the source end of a queue, promise or other + /// resource on which coroutines will wait, so that shutdown can wake up + /// consuming coroutines. @a caller should distinguish who's calling. The + /// passed @a cleanup function must close the queue, break the promise or + /// otherwise cause waiting consumers to wake up in an abnormal way. It's + /// advisable to store the returned LLBoundListener in an + /// LLTempBoundListener, or otherwise arrange to disconnect it. + static LLBoundListener getStopListener(const std::string& caller, LLVoidListener cleanup); + + /// This getStopListener() overload is like the two-argument one, for use + /// when we know the name of the only coroutine that will wait on the + /// resource in question. Pass @a consumer as the empty string if the + /// consumer coroutine is the same as the calling coroutine. Unlike the + /// two-argument getStopListener(), this one also responds to + /// killreq(target). + static LLBoundListener getStopListener(const std::string& caller, + const std::string& consumer, + LLVoidListener cleanup); /** - * Aliases for promise and future. An older underlying future implementation - * required us to wrap future; that's no longer needed. However -- if it's - * important to restore kill() functionality, we might need to provide a - * proxy, so continue using the aliases. + * LLCoros aliases for promise and future, for backwards compatibility. + * These have been hoisted out to the llcoro namespace. */ template <typename T> - using Promise = boost::fibers::promise<T>; + using Promise = llcoro::Promise<T>; template <typename T> - using Future = boost::fibers::future<T>; + using Future = llcoro::Future<T>; template <typename T> static Future<T> getFuture(Promise<T>& promise) { return promise.get_future(); } // use mutex, lock, condition_variable suitable for coroutines - using Mutex = boost::fibers::mutex; - using RMutex = boost::fibers::recursive_mutex; - // With C++17, LockType is deprecated: at this point we can directly - // declare 'std::unique_lock lk(some_mutex)' without explicitly stating - // the mutex type. Sadly, making LockType an alias template for - // std::unique_lock doesn't work the same way: Class Template Argument - // Deduction only works for class templates, not alias templates. - using LockType = std::unique_lock<Mutex>; - using cv_status = boost::fibers::cv_status; - using ConditionVariable = boost::fibers::condition_variable; + using Mutex = llcoro::Mutex; + using RMutex = llcoro::RMutex; + // LockType is deprecated; see llcoromutex.h + using LockType = llcoro::LockType; + using cv_status = llcoro::cv_status; + using ConditionVariable = llcoro::ConditionVariable; /// for data local to each running coroutine template <typename T> @@ -329,6 +337,8 @@ private: static CoroData& get_CoroData(const std::string& caller); void saveException(const std::string& name, std::exception_ptr exc); + LLTempBoundListener mConn; + struct ExceptionData { ExceptionData(const std::string& nm, std::exception_ptr exc): @@ -352,8 +362,10 @@ private: // tweaked name of the current coroutine const std::string mName; - // set_consuming() state - bool mConsuming; + // set_consuming() state -- don't consume events unless specifically directed + bool mConsuming{ false }; + // killed by which coroutine + std::string mKilledBy; // setStatus() state std::string mStatus; F64 mCreationTime; // since epoch diff --git a/indra/llcommon/lldate.cpp b/indra/llcommon/lldate.cpp index b38864688d..5f51f40232 100644 --- a/indra/llcommon/lldate.cpp +++ b/indra/llcommon/lldate.cpp @@ -41,7 +41,9 @@ #include "llstring.h" #include "llfasttimer.h" -static const F64 LL_APR_USEC_PER_SEC = 1000000.0; +static const LLDate::timestamp DATE_EPOCH = 0.0; + +static const LLDate::timestamp LL_APR_USEC_PER_SEC = 1000000.0; // should be APR_USEC_PER_SEC, but that relies on INT64_C which // isn't defined in glib under our build set up for some reason @@ -224,13 +226,13 @@ bool LLDate::fromStream(std::istream& s) return false; } - F64 seconds_since_epoch = time / LL_APR_USEC_PER_SEC; + timestamp seconds_since_epoch = time / LL_APR_USEC_PER_SEC; // check for fractional c = s.peek(); if(c == '.') { - F64 fractional = 0.0; + timestamp fractional = 0.0; s >> fractional; seconds_since_epoch += fractional; } @@ -290,12 +292,12 @@ bool LLDate::fromYMDHMS(S32 year, S32 month, S32 day, S32 hour, S32 min, S32 sec return true; } -F64 LLDate::secondsSinceEpoch() const +LLDate::timestamp LLDate::secondsSinceEpoch() const { return mSecondsSinceEpoch; } -void LLDate::secondsSinceEpoch(F64 seconds) +void LLDate::secondsSinceEpoch(timestamp seconds) { mSecondsSinceEpoch = seconds; } diff --git a/indra/llcommon/lldate.h b/indra/llcommon/lldate.h index 1a69a04232..0afe0b0599 100644 --- a/indra/llcommon/lldate.h +++ b/indra/llcommon/lldate.h @@ -45,6 +45,8 @@ class LL_COMMON_API LLDate { static constexpr F64 DATE_EPOCH = 0.0; public: + using timestamp = F64; + /** * @brief Construct a date equal to epoch. */ @@ -100,14 +102,14 @@ public: * * @return The number of seconds since epoch UTC. */ - F64 secondsSinceEpoch() const; + timestamp secondsSinceEpoch() const; /** * @brief Set the date in seconds since epoch. * * @param seconds The number of seconds since epoch UTC. */ - void secondsSinceEpoch(F64 seconds); + void secondsSinceEpoch(timestamp seconds); /** * @brief Create an LLDate object set to the current time. @@ -144,7 +146,7 @@ public: private: - F64 mSecondsSinceEpoch; + timestamp mSecondsSinceEpoch; }; // Helper function to stream out a date diff --git a/indra/llcommon/lldefs.h b/indra/llcommon/lldefs.h index 2fbb26dc1a..d4b063f88c 100644 --- a/indra/llcommon/lldefs.h +++ b/indra/llcommon/lldefs.h @@ -28,6 +28,7 @@ #define LL_LLDEFS_H #include "stdtypes.h" +#include <cassert> #include <type_traits> // Often used array indices @@ -169,6 +170,38 @@ constexpr U32 MAXADDRSTR = 17; // 123.567.901.345 = 15 chars + \0 + // llclampb(a) // clamps a to [0 .. 255] // +// llless(d0, d1) safely compares d0 < d1 even if one is signed and the other +// is unsigned. A simple (d0 < d1) expression converts the signed operand to +// unsigned before comparing. If the signed operand is negative, that flips +// the negative value to a huge positive value, producing the wrong answer! +// llless() specifically addresses that case. +template <typename T0, typename T1> +inline bool llless(T0 d0, T1 d1) +{ + if constexpr (std::is_signed_v<T0> && ! std::is_signed_v<T1>) + { + // T0 signed, T1 unsigned: negative d0 is less than any unsigned d1 + if (d0 < 0) + return true; + // both are non-negative: explicitly cast to avoid C4018 + return std::make_unsigned_t<T0>(d0) < d1; + } + else if constexpr (! std::is_signed_v<T0> && std::is_signed_v<T1>) + { + // T0 unsigned, T1 signed: any unsigned d0 is greater than negative d1 + if (d1 < 0) + return false; + // both are non-negative: explicitly cast to avoid C4018 + return d0 < std::make_unsigned_t<T1>(d1); + } + else + { + // both T0 and T1 are signed, or both are unsigned: + // straightforward comparison works + return d0 < d1; + } +} + // recursion tail template <typename T> inline auto llmax(T data) @@ -180,7 +213,7 @@ template <typename T0, typename T1, typename... Ts> inline auto llmax(T0 d0, T1 d1, Ts... rest) { auto maxrest = llmax(d1, rest...); - return (d0 > maxrest)? d0 : maxrest; + return llless(maxrest, d0)? d0 : maxrest; } // recursion tail @@ -194,12 +227,28 @@ template <typename T0, typename T1, typename... Ts> inline auto llmin(T0 d0, T1 d1, Ts... rest) { auto minrest = llmin(d1, rest...); - return (d0 < minrest) ? d0 : minrest; + return llless(d0, minrest) ? d0 : minrest; } template <typename A, typename MIN, typename MAX> inline A llclamp(A a, MIN minval, MAX maxval) { + // The only troublesome case is if A is unsigned and either minval or + // maxval is both signed and negative. Casting a negative number to + // unsigned flips it to a huge positive number, making this llclamp() call + // ineffective. + if constexpr (! std::is_signed_v<A>) + { + if constexpr (std::is_signed_v<MIN>) + { + assert(minval >= 0); + } + if constexpr (std::is_signed_v<MAX>) + { + assert(maxval >= 0); + } + } + A aminval{ static_cast<A>(minval) }, amaxval{ static_cast<A>(maxval) }; if ( a < aminval ) { diff --git a/indra/llcommon/lldependencies.h b/indra/llcommon/lldependencies.h index 47b6fedc7d..d19cdd1f25 100644 --- a/indra/llcommon/lldependencies.h +++ b/indra/llcommon/lldependencies.h @@ -30,6 +30,8 @@ #if ! defined(LL_LLDEPENDENCIES_H) #define LL_LLDEPENDENCIES_H +#include "linden_common.h" +#include "llexception.h" #include <string> #include <vector> #include <set> @@ -40,7 +42,6 @@ #include <boost/range/iterator_range.hpp> #include <boost/function.hpp> #include <boost/bind.hpp> -#include "llexception.h" /***************************************************************************** * Utilities diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 90c6ba309b..90294acd55 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -64,6 +64,8 @@ #define BOOST_STACKTRACE_GNU_SOURCE_NOT_REQUIRED #include <boost/stacktrace.hpp> +#include LLCOROS_RMUTEX_HEADER + namespace { #if LL_WINDOWS void debugger_print(const std::string& s) @@ -1431,6 +1433,7 @@ namespace LLError if (site.mLevel == LEVEL_ERROR) { + writeToRecorders(site, stringize(boost::stacktrace::stacktrace())); g->mFatalMessage = message; if (s->mCrashFunction) { diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h index 8a143ff30a..b17b9ff21e 100644 --- a/indra/llcommon/llerror.h +++ b/indra/llcommon/llerror.h @@ -95,6 +95,11 @@ const int LL_ERR_NOERR = 0; #define LL_STATIC_ASSERT(func, msg) static_assert(func, msg) #define LL_BAD_TEMPLATE_INSTANTIATION(type, msg) static_assert(false, msg) #else +#if LL_LINUX +// We need access to raise and SIGSEGV +#include <signal.h> +#endif + #define LL_STATIC_ASSERT(func, msg) BOOST_STATIC_ASSERT(func) #define LL_BAD_TEMPLATE_INSTANTIATION(type, msg) BOOST_STATIC_ASSERT(sizeof(type) != 0 && false); #endif @@ -408,12 +413,18 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG; #define LL_NEWLINE '\n' // Use this only in LL_ERRS or in a place that LL_ERRS may not be used + +#ifndef LL_LINUX #define LLERROR_CRASH \ { \ int* make_me_crash = (int*)0xDEADBEEFDEADBEEFUL; \ *make_me_crash = 0; \ exit(*make_me_crash); \ } +#else +// For Linux we just call raise and be done with it. No fighting the compiler to create a crashing code snippet. +#define LLERROR_CRASH raise(SIGSEGV ); +#endif #define LL_ENDL \ LLError::End(); \ diff --git a/indra/llcommon/lleventapi.cpp b/indra/llcommon/lleventapi.cpp index 8b724256b8..4672371b4f 100644 --- a/indra/llcommon/lleventapi.cpp +++ b/indra/llcommon/lleventapi.cpp @@ -55,6 +55,26 @@ LLEventAPI::~LLEventAPI() { } +bool LLEventAPI::process(const LLSD& event) const +{ + // LLDispatchListener is documented to let DispatchError propagate if the + // incoming request has no "reply" key. That may be fine for internal-only + // use, but LLEventAPI opens the door for external requests. It should NOT + // be possible for any external requester to crash the viewer with an + // unhandled exception, especially not by something as simple as omitting + // the "reply" key. + try + { + return LLDispatchListener::process(event); + } + catch (const std::exception& err) + { + // log the exception, but otherwise ignore it + LL_WARNS("LLEventAPI") << LLError::Log::classname(err) << ": " << err.what() << LL_ENDL; + return false; + } +} + LLEventAPI::Response::Response(const LLSD& seed, const LLSD& request, const LLSD::String& replyKey): mResp(seed), mReq(request), diff --git a/indra/llcommon/lleventapi.h b/indra/llcommon/lleventapi.h index 3ae820db51..da7c58e6f0 100644 --- a/indra/llcommon/lleventapi.h +++ b/indra/llcommon/lleventapi.h @@ -157,6 +157,7 @@ protected: LLEventAPI(const LL::LazyEventAPIParams&); private: + bool process(const LLSD& event) const override; std::string mDesc; }; diff --git a/indra/llcommon/lleventcoro.cpp b/indra/llcommon/lleventcoro.cpp index e1fc4764f6..33db27a116 100644 --- a/indra/llcommon/lleventcoro.cpp +++ b/indra/llcommon/lleventcoro.cpp @@ -119,7 +119,7 @@ void llcoro::suspendUntilTimeout(float seconds) // We used to call boost::this_fiber::sleep_for(). But some coroutines // (e.g. LLExperienceCache::idleCoro()) sit in a suspendUntilTimeout() // loop, in which case a sleep_for() call risks sleeping through shutdown. - // So instead, listen for "LLApp" state-changing events -- which + // So instead, listen for LLApp state-changing events -- which // fortunately is handled for us by suspendUntilEventOnWithTimeout(). // Wait for an event on a bogus LLEventPump on which nobody ever posts // events. Don't make it static because that would force instantiation of @@ -132,8 +132,8 @@ void llcoro::suspendUntilTimeout(float seconds) // Timeout is the NORMAL case for this call! static LLSD timedout; // Deliver, but ignore, timedout when (as usual) we did not receive any - // "LLApp" event. The point is that suspendUntilEventOnWithTimeout() will - // itself throw Stopping when "LLApp" starts broadcasting shutdown events. + // LLApp event. The point is that suspendUntilEventOnWithTimeout() will + // itself throw Stopping when LLApp starts broadcasting shutdown events. suspendUntilEventOnWithTimeout(bogus, seconds, timedout); } @@ -167,33 +167,26 @@ postAndSuspendSetup(const std::string& callerName, // "LLApp" were an LLEventMailDrop. But if we ever go there, we'd want to // notice the pending LLApp status first. LLBoundListener stopper( - LLEventPumps::instance().obtain("LLApp").listen( + LLCoros::getStopListener( listenerName, + LLCoros::instance().getName(), [&promise, listenerName](const LLSD& status) { - // anything except "running" should wake up the waiting - // coroutine - auto& statsd = status["status"]; - if (statsd.asString() != "running") + LL_DEBUGS("lleventcoro") << listenerName + << " spotted status " << status + << ", throwing Stopping" << LL_ENDL; + try + { + promise.set_exception( + std::make_exception_ptr( + LLCoros::Stopping("status " + stringize(status)))); + } + catch (const boost::fibers::promise_already_satisfied&) { - LL_DEBUGS("lleventcoro") << listenerName - << " spotted status " << statsd - << ", throwing Stopping" << LL_ENDL; - try - { - promise.set_exception( - std::make_exception_ptr( - LLCoros::Stopping("status " + statsd.asString()))); - } - catch (const boost::fibers::promise_already_satisfied&) - { - LL_WARNS("lleventcoro") << listenerName - << " couldn't throw Stopping " - "because promise already set" << LL_ENDL; - } + LL_WARNS("lleventcoro") << listenerName + << " couldn't throw Stopping " + "because promise already set" << LL_ENDL; } - // do not consume -- every listener must see status - return false; })); LLBoundListener connection( replyPump.listen( diff --git a/indra/llcommon/lleventdispatcher.cpp b/indra/llcommon/lleventdispatcher.cpp index 9dd6864cff..12b966fadf 100644 --- a/indra/llcommon/lleventdispatcher.cpp +++ b/indra/llcommon/lleventdispatcher.cpp @@ -800,7 +800,7 @@ void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const { result = (*this)(event); } - catch (const DispatchError& err) + catch (const std::exception& err) { if (! event.has(mReplyKey)) { @@ -810,7 +810,10 @@ void LLDispatchListener::call_one(const LLSD& name, const LLSD& event) const // Here there was an error and the incoming event has mReplyKey. Reply // with a map containing an "error" key explaining the problem. - return reply(llsd::map("error", err.what()), event); + return reply(llsd::map("error", + stringize(LLError::Log::classname(err), + ": ", err.what())), + event); } // We seem to have gotten a valid result. But we don't know whether the @@ -846,7 +849,7 @@ void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const { // in case of errors, tell user the dispatch key, the fact that // we're processing a request map and the current key in that map - SetState(this, '[', key, '[', name, "]]"); + SetState transient(this, '[', key, '[', name, "]]"); // With this form, capture return value even if undefined: // presence of the key in the response map can be used to detect // which request keys succeeded. @@ -869,7 +872,7 @@ void LLDispatchListener::call_map(const LLSD& reqmap, const LLSD& event) const if (! event.has(mReplyKey)) { // can't send reply, throw - sCallFail<DispatchError>(error); + callFail<DispatchError>(error); } else { @@ -923,7 +926,7 @@ void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) con // in case of errors, tell user the dispatch key, the fact that // we're processing a request array, the current entry in that // array and the corresponding callable name - SetState(this, '[', key, '[', i, "]=", name, ']'); + SetState transient(this, '[', key, '[', i, "]=", name, ']'); // With this form, capture return value even if undefined results.append((*this)(name, args)); } @@ -944,7 +947,7 @@ void LLDispatchListener::call_array(const LLSD& reqarray, const LLSD& event) con if (! event.has(mReplyKey)) { // can't send reply, throw - sCallFail<DispatchError>(error); + callFail<DispatchError>(error); } else { diff --git a/indra/llcommon/lleventdispatcher.h b/indra/llcommon/lleventdispatcher.h index 4c3c0f3414..6b5524e1eb 100644 --- a/indra/llcommon/lleventdispatcher.h +++ b/indra/llcommon/lleventdispatcher.h @@ -35,7 +35,6 @@ #include <boost/fiber/fss.hpp> #include <boost/function_types/is_member_function_pointer.hpp> #include <boost/function_types/is_nonmember_callable_builtin.hpp> -#include <boost/hof/is_invocable.hpp> // until C++17, when we get std::is_invocable #include <boost/iterator/transform_iterator.hpp> #include <functional> // std::function #include <memory> // std::unique_ptr @@ -48,6 +47,7 @@ #include "llevents.h" #include "llptrto.h" #include "llsdutil.h" +#include "stringize.h" class LLSD; @@ -99,7 +99,7 @@ public: template <typename CALLABLE, typename=typename std::enable_if< - boost::hof::is_invocable<CALLABLE, LLSD>::value + std::is_invocable<CALLABLE, LLSD>::value >::type> void add(const std::string& name, const std::string& desc, @@ -296,7 +296,7 @@ public: */ template <typename CALLABLE, typename=typename std::enable_if< - ! boost::hof::is_invocable<CALLABLE, LLSD>() + ! std::is_invocable<CALLABLE, LLSD>() >::type> void add(const std::string& name, const std::string& desc, @@ -338,7 +338,7 @@ public: template<typename Function, typename = typename std::enable_if< boost::function_types::is_nonmember_callable_builtin<Function>::value && - ! boost::hof::is_invocable<Function, LLSD>::value + ! std::is_invocable<Function, LLSD>::value >::type> void add(const std::string& name, const std::string& desc, Function f, const LLSD& params, const LLSD& defaults=LLSD()); @@ -851,8 +851,12 @@ public: ARGS&&... args); virtual ~LLDispatchListener() {} + std::string getPumpName() const { return getName(); } + +protected: + virtual bool process(const LLSD& event) const; + private: - bool process(const LLSD& event) const; void call_one(const LLSD& name, const LLSD& event) const; void call_map(const LLSD& reqmap, const LLSD& event) const; void call_array(const LLSD& reqarray, const LLSD& event) const; diff --git a/indra/llcommon/lleventfilter.cpp b/indra/llcommon/lleventfilter.cpp index 604ee8a42d..2b5401e9f7 100644 --- a/indra/llcommon/lleventfilter.cpp +++ b/indra/llcommon/lleventfilter.cpp @@ -33,20 +33,19 @@ // STL headers // std headers // external library headers -#include <boost/bind.hpp> // other Linden headers +#include "lldate.h" #include "llerror.h" // LL_ERRS +#include "lleventtimer.h" #include "llsdutil.h" // llsd_matches() #include "stringize.h" -#include "lleventtimer.h" -#include "lldate.h" /***************************************************************************** * LLEventFilter *****************************************************************************/ LLEventFilter::LLEventFilter(LLEventPump& source, const std::string& name, bool tweak): LLEventStream(name, tweak), - mSource(source.listen(getName(), boost::bind(&LLEventFilter::post, this, _1))) + mSource(source.listen(getName(), [this](const LLSD& event){ return post(event); })) { } @@ -74,136 +73,52 @@ bool LLEventMatching::post(const LLSD& event) } /***************************************************************************** -* LLEventTimeoutBase +* LLEventTimeout *****************************************************************************/ -LLEventTimeoutBase::LLEventTimeoutBase(): +LLEventTimeout::LLEventTimeout(): LLEventFilter("timeout") { } -LLEventTimeoutBase::LLEventTimeoutBase(LLEventPump& source): +LLEventTimeout::LLEventTimeout(LLEventPump& source): LLEventFilter(source, "timeout") { } -void LLEventTimeoutBase::actionAfter(F32 seconds, const Action& action) +void LLEventTimeout::actionAfter(F32 seconds, const Action& action) { - setCountdown(seconds); - mAction = action; - if (! mMainloop.connected()) - { - LLEventPump& mainloop(LLEventPumps::instance().obtain("mainloop")); - mMainloop = mainloop.listen(getName(), boost::bind(&LLEventTimeoutBase::tick, this, _1)); - } + mTimer = LL::Timers::instance().scheduleAfter(action, seconds); } -class ErrorAfter +void LLEventTimeout::errorAfter(F32 seconds, const std::string& message) { -public: - ErrorAfter(const std::string& message): mMessage(message) {} - - void operator()() - { - LL_ERRS("LLEventTimeout") << mMessage << LL_ENDL; - } - -private: - std::string mMessage; -}; - -void LLEventTimeoutBase::errorAfter(F32 seconds, const std::string& message) -{ - actionAfter(seconds, ErrorAfter(message)); + actionAfter( + seconds, + [message=message] + { + LL_ERRS("LLEventTimeout") << message << LL_ENDL; + }); } -class EventAfter +void LLEventTimeout::eventAfter(F32 seconds, const LLSD& event) { -public: - EventAfter(LLEventPump& pump, const LLSD& event): - mPump(pump), - mEvent(event) - {} - - void operator()() - { - mPump.post(mEvent); - } - -private: - LLEventPump& mPump; - LLSD mEvent; -}; - -void LLEventTimeoutBase::eventAfter(F32 seconds, const LLSD& event) -{ - actionAfter(seconds, EventAfter(*this, event)); + actionAfter(seconds, [this, event]{ post(event); }); } -bool LLEventTimeoutBase::post(const LLSD& event) +bool LLEventTimeout::post(const LLSD& event) { cancel(); return LLEventStream::post(event); } -void LLEventTimeoutBase::cancel() +void LLEventTimeout::cancel() { - mMainloop.disconnect(); + mTimer.cancel(); } -bool LLEventTimeoutBase::tick(const LLSD&) +bool LLEventTimeout::running() const { - if (countdownElapsed()) - { - cancel(); - mAction(); - } - return false; // show event to other listeners -} - -bool LLEventTimeoutBase::running() const -{ - return mMainloop.connected(); -} - -/***************************************************************************** -* LLEventTimeout -*****************************************************************************/ -LLEventTimeout::LLEventTimeout() {} - -LLEventTimeout::LLEventTimeout(LLEventPump& source): - LLEventTimeoutBase(source) -{ -} - -void LLEventTimeout::setCountdown(F32 seconds) -{ - mTimer.setTimerExpirySec(seconds); -} - -bool LLEventTimeout::countdownElapsed() const -{ - return mTimer.hasExpired(); -} - -LLEventTimer* LLEventTimeout::post_every(F32 period, const std::string& pump, const LLSD& data) -{ - return LLEventTimer::run_every( - period, - [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); }); -} - -LLEventTimer* LLEventTimeout::post_at(const LLDate& time, const std::string& pump, const LLSD& data) -{ - return LLEventTimer::run_at( - time, - [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); }); -} - -LLEventTimer* LLEventTimeout::post_after(F32 interval, const std::string& pump, const LLSD& data) -{ - return LLEventTimer::run_after( - interval, - [pump, data](){ LLEventPumps::instance().obtain(pump).post(data); }); + return LL::Timers::instance().isRunning(mTimer); } /***************************************************************************** @@ -246,21 +161,21 @@ void LLEventBatch::setSize(std::size_t size) } /***************************************************************************** -* LLEventThrottleBase +* LLEventThrottle *****************************************************************************/ -LLEventThrottleBase::LLEventThrottleBase(F32 interval): +LLEventThrottle::LLEventThrottle(F32 interval): LLEventFilter("throttle"), mInterval(interval), mPosts(0) {} -LLEventThrottleBase::LLEventThrottleBase(LLEventPump& source, F32 interval): +LLEventThrottle::LLEventThrottle(LLEventPump& source, F32 interval): LLEventFilter(source, "throttle"), mInterval(interval), mPosts(0) {} -void LLEventThrottleBase::flush() +void LLEventThrottle::flush() { // flush() is a no-op unless there's something pending. // Don't test mPending because there's no requirement that the consumer @@ -281,12 +196,12 @@ void LLEventThrottleBase::flush() } } -LLSD LLEventThrottleBase::pending() const +LLSD LLEventThrottle::pending() const { return mPending; } -bool LLEventThrottleBase::post(const LLSD& event) +bool LLEventThrottle::post(const LLSD& event) { // Always capture most recent post() event data. If caller wants to // aggregate multiple events, let them retrieve pending() and modify @@ -311,13 +226,13 @@ bool LLEventThrottleBase::post(const LLSD& event) // timeRemaining tells us how much longer it will be until // mInterval seconds since the last flush() call. At that time, // flush() deferred events. - alarmActionAfter(timeRemaining, boost::bind(&LLEventThrottleBase::flush, this)); + alarmActionAfter(timeRemaining, [this]{ flush(); }); } } return false; } -void LLEventThrottleBase::setInterval(F32 interval) +void LLEventThrottle::setInterval(F32 interval) { F32 oldInterval = mInterval; mInterval = interval; @@ -349,41 +264,30 @@ void LLEventThrottleBase::setInterval(F32 interval) // and if mAlarm is running, reset that too if (alarmRunning()) { - alarmActionAfter(timeRemaining, boost::bind(&LLEventThrottleBase::flush, this)); + alarmActionAfter(timeRemaining, [this](){ flush(); }); } } } } -F32 LLEventThrottleBase::getDelay() const +F32 LLEventThrottle::getDelay() const { return timerGetRemaining(); } -/***************************************************************************** -* LLEventThrottle implementation -*****************************************************************************/ -LLEventThrottle::LLEventThrottle(F32 interval): - LLEventThrottleBase(interval) -{} - -LLEventThrottle::LLEventThrottle(LLEventPump& source, F32 interval): - LLEventThrottleBase(source, interval) -{} - -void LLEventThrottle::alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) +void LLEventThrottle::alarmActionAfter(F32 interval, const LLEventTimeout::Action& action) { - mAlarm.actionAfter(interval, action); + mAlarm = LL::Timers::instance().scheduleAfter(action, interval); } bool LLEventThrottle::alarmRunning() const { - return mAlarm.running(); + return LL::Timers::instance().isRunning(mAlarm); } void LLEventThrottle::alarmCancel() { - return mAlarm.cancel(); + LL::Timers::instance().cancel(mAlarm); } void LLEventThrottle::timerSet(F32 interval) @@ -461,21 +365,22 @@ bool LLEventLogProxy::post(const LLSD& event) /* override */ } LLBoundListener LLEventLogProxy::listen_impl(const std::string& name, - const LLEventListener& target, + const LLAwareListener& target, const NameList& after, const NameList& before) { LL_DEBUGS("LogProxy") << "LLEventLogProxy('" << getName() << "').listen('" << name << "')" << LL_ENDL; return mPump.listen(name, - [this, name, target](const LLSD& event)->bool - { return listener(name, target, event); }, + [this, name, target](const LLBoundListener& conn, const LLSD& event) + { return listener(conn, name, target, event); }, after, before); } -bool LLEventLogProxy::listener(const std::string& name, - const LLEventListener& target, +bool LLEventLogProxy::listener(const LLBoundListener& conn, + const std::string& name, + const LLAwareListener& target, const LLSD& event) const { auto eventminus = event; @@ -487,7 +392,7 @@ bool LLEventLogProxy::listener(const std::string& name, } std::string hdr{STRINGIZE(getName() << " to " << name << " " << counter)}; LL_INFOS("LogProxy") << hdr << ": " << eventminus << LL_ENDL; - bool result = target(eventminus); + bool result = target(conn, eventminus); LL_INFOS("LogProxy") << hdr << " => " << result << LL_ENDL; return result; } diff --git a/indra/llcommon/lleventfilter.h b/indra/llcommon/lleventfilter.h index d8c7e15a27..09ef81a6f5 100644 --- a/indra/llcommon/lleventfilter.h +++ b/indra/llcommon/lleventfilter.h @@ -29,13 +29,13 @@ #if ! defined(LL_LLEVENTFILTER_H) #define LL_LLEVENTFILTER_H +#include "llcallbacklist.h" #include "llevents.h" -#include "stdtypes.h" -#include "lltimer.h" #include "llsdutil.h" -#include <boost/function.hpp> +#include "lltimer.h" +#include "stdtypes.h" +#include <functional> -class LLEventTimer; class LLDate; /** @@ -78,22 +78,27 @@ private: /** * Wait for an event to be posted. If no such event arrives within a specified - * time, take a specified action. See LLEventTimeout for production - * implementation. + * time, take a specified action. * - * @NOTE This is an abstract base class so that, for testing, we can use an - * alternate "timer" that doesn't actually consume real time. + * @NOTE: Caution should be taken when using the LLEventTimeout(LLEventPump &) + * constructor to ensure that the upstream event pump is not an LLEventMaildrop + * or any other kind of store and forward pump which may have events outstanding. + * Using this constructor will cause the upstream event pump to fire any pending + * events and could result in the invocation of a virtual method before the timeout + * has been fully constructed. The timeout should instead be constructed separately + * from the event pump and attached using the listen method. + * See llcoro::suspendUntilEventOnWithTimeout() for an example. */ -class LL_COMMON_API LLEventTimeoutBase: public LLEventFilter +class LL_COMMON_API LLEventTimeout: public LLEventFilter { public: /// construct standalone - LLEventTimeoutBase(); + LLEventTimeout(); /// construct and connect - LLEventTimeoutBase(LLEventPump& source); + LLEventTimeout(LLEventPump& source); /// Callable, can be constructed with boost::bind() - typedef boost::function<void()> Action; + typedef std::function<void()> Action; /** * Start countdown timer for the specified number of @a seconds. Forward @@ -120,8 +125,8 @@ public: * @endcode * * @NOTE - * The implementation relies on frequent events on the LLEventPump named - * "mainloop". + * The implementation relies on frequent calls to + * gIdleCallbacks.callFunctions(). */ void actionAfter(F32 seconds, const Action& action); @@ -134,7 +139,7 @@ public: * Instantiate an LLEventTimeout listening to that API and call * errorAfter() on each async request with a timeout comfortably longer * than the API's time guarantee (much longer than the anticipated - * "mainloop" granularity). + * gIdleCallbacks.callFunctions() granularity). * * Then if the async API breaks its promise, the program terminates with * the specified LL_ERRS @a message. The client of the async API can @@ -184,55 +189,9 @@ public: /// Is this timer currently running? bool running() const; -protected: - virtual void setCountdown(F32 seconds) = 0; - virtual bool countdownElapsed() const = 0; - private: - bool tick(const LLSD&); - - LLTempBoundListener mMainloop; - Action mAction; -}; - -/** - * Production implementation of LLEventTimoutBase. - * - * @NOTE: Caution should be taken when using the LLEventTimeout(LLEventPump &) - * constructor to ensure that the upstream event pump is not an LLEventMaildrop - * or any other kind of store and forward pump which may have events outstanding. - * Using this constructor will cause the upstream event pump to fire any pending - * events and could result in the invocation of a virtual method before the timeout - * has been fully constructed. The timeout should instead be connected upstream - * from the event pump and attached using the listen method. - * See llcoro::suspendUntilEventOnWithTimeout() for an example. - */ - -class LL_COMMON_API LLEventTimeout: public LLEventTimeoutBase -{ -public: - LLEventTimeout(); - LLEventTimeout(LLEventPump& source); - - /// using LLEventTimeout as namespace for free functions - /// Post event to specified LLEventPump every period seconds. Delete - /// returned LLEventTimer* to cancel. - static LLEventTimer* post_every(F32 period, const std::string& pump, const LLSD& data); - /// Post event to specified LLEventPump at specified future time. Call - /// LLEventTimer::getInstance(returned pointer) to check whether it's still - /// pending; if so, delete the pointer to cancel. - static LLEventTimer* post_at(const LLDate& time, const std::string& pump, const LLSD& data); - /// Post event to specified LLEventPump after specified interval. Call - /// LLEventTimer::getInstance(returned pointer) to check whether it's still - /// pending; if so, delete the pointer to cancel. - static LLEventTimer* post_after(F32 interval, const std::string& pump, const LLSD& data); - -protected: - virtual void setCountdown(F32 seconds); - virtual bool countdownElapsed() const; - -private: - LLTimer mTimer; + // Use a temp_handle_t so it's canceled on destruction. + LL::Timers::temp_handle_t mTimer; }; /** @@ -264,7 +223,7 @@ private: }; /** - * LLEventThrottleBase: construct with a time interval. Regardless of how + * LLEventThrottle: construct with a time interval. Regardless of how * frequently you call post(), LLEventThrottle will pass on an event to * its listeners no more often than once per specified interval. * @@ -297,13 +256,13 @@ private: * alternate "timer" that doesn't actually consume real time. See * LLEventThrottle. */ -class LL_COMMON_API LLEventThrottleBase: public LLEventFilter +class LL_COMMON_API LLEventThrottle: public LLEventFilter { public: // pass time interval - LLEventThrottleBase(F32 interval); + LLEventThrottle(F32 interval); // construct and connect - LLEventThrottleBase(LLEventPump& source, F32 interval); + LLEventThrottle(LLEventPump& source, F32 interval); // force out any deferred events void flush(); @@ -324,45 +283,24 @@ public: // time until next event would be passed through, 0.0 if now F32 getDelay() const; -protected: - // Implement these time-related methods for a valid LLEventThrottleBase - // subclass (see LLEventThrottle). For testing, we use a subclass that - // doesn't involve actual elapsed time. - virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) = 0; - virtual bool alarmRunning() const = 0; - virtual void alarmCancel() = 0; - virtual void timerSet(F32 interval) = 0; - virtual F32 timerGetRemaining() const = 0; - private: - // remember throttle interval - F32 mInterval; - // count post() calls since last flush() - std::size_t mPosts; + void alarmActionAfter(F32 interval, const LLEventTimeout::Action& action); + bool alarmRunning() const; + void alarmCancel(); + void timerSet(F32 interval); + F32 timerGetRemaining() const; + // pending event data from most recent deferred event LLSD mPending; -}; - -/** - * Production implementation of LLEventThrottle. - */ -class LLEventThrottle: public LLEventThrottleBase -{ -public: - LLEventThrottle(F32 interval); - LLEventThrottle(LLEventPump& source, F32 interval); - -private: - virtual void alarmActionAfter(F32 interval, const LLEventTimeoutBase::Action& action) /*override*/; - virtual bool alarmRunning() const /*override*/; - virtual void alarmCancel() /*override*/; - virtual void timerSet(F32 interval) /*override*/; - virtual F32 timerGetRemaining() const /*override*/; - - // use this to arrange a deferred flush() call - LLEventTimeout mAlarm; // use this to track whether we're within mInterval of last flush() LLTimer mTimer; + // count post() calls since last flush() + std::size_t mPosts; + // remember throttle interval + F32 mInterval; + + // use this to arrange a deferred flush() call + LL::Timers::handle_t mAlarm; }; /** @@ -521,7 +459,7 @@ public: LLEventLogProxy(LLEventPump& source, const std::string& name, bool tweak=false); /// register a new listener - LLBoundListener listen_impl(const std::string& name, const LLEventListener& target, + LLBoundListener listen_impl(const std::string& name, const LLAwareListener& target, const NameList& after, const NameList& before); /// Post an event to all listeners @@ -531,8 +469,9 @@ private: /// This method intercepts each call to any target listener. We pass it /// the listener name and the caller's intended target listener plus the /// posted LLSD event. - bool listener(const std::string& name, - const LLEventListener& target, + bool listener(const LLBoundListener& conn, + const std::string& name, + const LLAwareListener& target, const LLSD& event) const; LLEventPump& mPump; diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index 3c6743eac9..6531b951b9 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -43,6 +43,7 @@ #include <typeinfo> #include <cmath> #include <cctype> +#include <iomanip> // std::quoted // external library headers #include <boost/range/iterator_range.hpp> #if LL_WINDOWS @@ -54,10 +55,11 @@ #pragma warning (pop) #endif // other Linden headers -#include "stringize.h" #include "llerror.h" -#include "llsdutil.h" +#include "lleventfilter.h" #include "llexception.h" +#include "llsdutil.h" +#include "stringize.h" #if LL_MSVC #pragma warning (disable : 4702) #endif @@ -71,7 +73,9 @@ LLEventPumps::LLEventPumps(): { "LLEventStream", [](const std::string& name, bool tweak, const std::string& /*type*/) { return new LLEventStream(name, tweak); } }, { "LLEventMailDrop", [](const std::string& name, bool tweak, const std::string& /*type*/) - { return new LLEventMailDrop(name, tweak); } } + { return new LLEventMailDrop(name, tweak); } }, + { "LLEventLogProxy", [](const std::string& name, bool tweak, const std::string& /*type*/) + { return new LLEventLogProxyFor<LLEventStream>(name, tweak); } } }, mTypes { @@ -186,8 +190,13 @@ bool LLEventPumps::post(const std::string&name, const LLSD&message) PumpMap::iterator found = mPumpMap.find(name); if (found == mPumpMap.end()) + { + LL_DEBUGS("LLEventPumps") << "LLEventPump(" << std::quoted(name) << ") not found" + << LL_ENDL; return false; + } +// LL_DEBUGS("LLEventPumps") << "posting to " << name << ": " << message << LL_ENDL; return (*found).second->post(message); } @@ -350,8 +359,7 @@ const std::string LLEventPump::ANONYMOUS = std::string(); LLEventPump::LLEventPump(const std::string& name, bool tweak): // Register every new instance with LLEventPumps - mRegistry(LLEventPumps::instance().getHandle()), - mName(mRegistry.get()->registerNew(*this, name, tweak)), + mName(LLEventPumps::instance().registerNew(*this, name, tweak)), mSignal(std::make_shared<LLStandardSignal>()), mEnabled(true) {} @@ -364,10 +372,9 @@ LLEventPump::~LLEventPump() { // Unregister this doomed instance from LLEventPumps -- but only if // LLEventPumps is still around! - LLEventPumps* registry = mRegistry.get(); - if (registry) + if (LLEventPumps::instanceExists()) { - registry->unregister(*this); + LLEventPumps::instance().unregister(*this); } } @@ -382,7 +389,7 @@ std::string LLEventPump::inventName(const std::string& pfx) void LLEventPump::clear() { - LLCoros::LockType lock(mConnectionListMutex); + llcoro::LockType lock(mConnectionListMutex); // Destroy the original LLStandardSignal instance, replacing it with a // whole new one. mSignal = std::make_shared<LLStandardSignal>(); @@ -394,7 +401,7 @@ void LLEventPump::reset() { // Resetting mSignal is supposed to disconnect everything on its own // But due to crash on 'reset' added explicit cleanup to get more data - LLCoros::LockType lock(mConnectionListMutex); + llcoro::LockType lock(mConnectionListMutex); ConnectionMap::const_iterator iter = mConnections.begin(); ConnectionMap::const_iterator end = mConnections.end(); while (iter!=end) @@ -408,18 +415,18 @@ void LLEventPump::reset() //mDeps.clear(); } -LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventListener& listener, +LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLAwareListener& listener, const NameList& after, const NameList& before) { if (!mSignal) { - LL_WARNS() << "Can't connect listener" << LL_ENDL; + LL_WARNS("LLEventPump") << "Can't connect listener" << LL_ENDL; // connect will fail, return dummy return LLBoundListener(); } - LLCoros::LockType lock(mConnectionListMutex); + llcoro::LockType lock(mConnectionListMutex); float nodePosition = 1.0; @@ -568,7 +575,7 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL } // Now that newNode has a value that places it appropriately in mSignal, // connect it. - LLBoundListener bound = mSignal->connect(nodePosition, listener); + LLBoundListener bound = mSignal->connect_extended(nodePosition, listener); if (!name.empty()) { // note that we are not tracking anonymous listeners here either. @@ -582,7 +589,7 @@ LLBoundListener LLEventPump::listen_impl(const std::string& name, const LLEventL LLBoundListener LLEventPump::getListener(const std::string& name) { - LLCoros::LockType lock(mConnectionListMutex); + llcoro::LockType lock(mConnectionListMutex); ConnectionMap::const_iterator found = mConnections.find(name); if (found != mConnections.end()) { @@ -594,7 +601,7 @@ LLBoundListener LLEventPump::getListener(const std::string& name) void LLEventPump::stopListening(const std::string& name) { - LLCoros::LockType lock(mConnectionListMutex); + llcoro::LockType lock(mConnectionListMutex); ConnectionMap::iterator found = mConnections.find(name); if (found != mConnections.end()) { @@ -652,7 +659,7 @@ bool LLEventMailDrop::post(const LLSD& event) } LLBoundListener LLEventMailDrop::listen_impl(const std::string& name, - const LLEventListener& listener, + const LLAwareListener& listener, const NameList& after, const NameList& before) { @@ -661,7 +668,10 @@ LLBoundListener LLEventMailDrop::listen_impl(const std::string& name, // Remove any that this listener consumes -- Effective STL, Item 9. for (auto hi(mEventHistory.begin()), hend(mEventHistory.end()); hi != hend; ) { - if (listener(*hi)) + // We don't actually have an LLBoundListener in hand, and we won't + // until the base-class listen_impl() call below. Pass an empty + // instance. + if (listener({}, *hi)) { // new listener consumed this event, erase it hi = mEventHistory.erase(hi); @@ -733,7 +743,7 @@ void LLReqID::stamp(LLSD& response) const response["reqid"] = mReqid; } -bool sendReply(const LLSD& reply, const LLSD& request, const std::string& replyKey) +bool sendReply(LLSD reply, const LLSD& request, const std::string& replyKey) { // If the original request has no value for replyKey, it's pointless to // construct or send a reply event: on which LLEventPump should we send @@ -746,13 +756,13 @@ bool sendReply(const LLSD& reply, const LLSD& request, const std::string& replyK // Here the request definitely contains replyKey; reasonable to proceed. - // Copy 'reply' to modify it. - LLSD newreply(reply); // Get the ["reqid"] element from request LLReqID reqID(request); - // and copy it to 'newreply'. - reqID.stamp(newreply); - // Send reply on LLEventPump named in request[replyKey]. Don't forget to - // send the modified 'newreply' instead of the original 'reply'. - return LLEventPumps::instance().obtain(request[replyKey]).post(newreply); + // and copy it to 'reply'. + reqID.stamp(reply); + // Send reply on LLEventPump named in request[replyKey] -- if that + // LLEventPump exists. If it does not, don't create it. + // This addresses the case in which a requester goes away before a + // particular LLEventAPI responds. + return LLEventPumps::instance().post(request[replyKey], reply); } diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 4bf1fa07a2..d339a09117 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -32,35 +32,21 @@ #if ! defined(LL_LLEVENTS_H) #define LL_LLEVENTS_H -#include <string> +#include <deque> +#include <functional> #include <map> #include <set> +#include <string> +#include <type_traits> #include <vector> -#include <deque> -#include <functional> #include <boost/signals2.hpp> -#include <boost/bind.hpp> -#include <boost/utility.hpp> // noncopyable #include <boost/optional/optional.hpp> -#include <boost/visit_each.hpp> -#include <boost/ref.hpp> // reference_wrapper -#include <boost/type_traits/is_pointer.hpp> -#include <boost/static_assert.hpp> -#include "llsd.h" -#include "llsingleton.h" +#include "llcoromutex.h" #include "lldependencies.h" -#include "llstl.h" #include "llexception.h" -#include "llhandle.h" -#include "llcoros.h" - -/*==========================================================================*| -// override this to allow binding free functions with more parameters -#ifndef LLEVENTS_LISTENER_ARITY -#define LLEVENTS_LISTENER_ARITY 10 -#endif -|*==========================================================================*/ +#include "llsd.h" +#include "llsingleton.h" // hack for testing #ifndef testable @@ -144,6 +130,12 @@ typedef boost::signals2::signal<bool(const LLSD&), LLStopWhenHandled, float> LL /// Methods that forward listeners (e.g. constructed with /// <tt>boost::bind()</tt>) should accept (const LLEventListener&) typedef LLStandardSignal::slot_type LLEventListener; +/// Support a listener accepting (const LLBoundListener&, const LLSD&). +/// Note that LLBoundListener::disconnect() is a const method: this feature is +/// specifically intended to allow a listener to disconnect itself when done. +typedef LLStandardSignal::extended_slot_type LLAwareListener; +/// Accept a void listener too +typedef std::function<void(const LLSD&)> LLVoidListener; /// Result of registering a listener, supports <tt>connected()</tt>, /// <tt>disconnect()</tt> and <tt>blocked()</tt> typedef boost::signals2::connection LLBoundListener; @@ -218,15 +210,7 @@ class LLEventPump; * LLEventPumps is a Singleton manager through which one typically accesses * this subsystem. */ -// LLEventPumps isa LLHandleProvider only for (hopefully rare) long-lived -// class objects that must refer to this class late in their lifespan, say in -// the destructor. Specifically, the case that matters is a possible reference -// after LLEventPumps::deleteSingleton(). (Lingering LLEventPump instances are -// capable of this.) In that case, instead of calling LLEventPumps::instance() -// again -- resurrecting the deleted LLSingleton -- store an -// LLHandle<LLEventPumps> and test it before use. -class LL_COMMON_API LLEventPumps: public LLSingleton<LLEventPumps>, - public LLHandleProvider<LLEventPumps> +class LL_COMMON_API LLEventPumps: public LLSingleton<LLEventPumps> { LLSINGLETON(LLEventPumps); public: @@ -372,56 +356,13 @@ testable: }; /***************************************************************************** -* LLEventTrackable -*****************************************************************************/ -/** - * LLEventTrackable wraps boost::signals2::trackable, which resembles - * boost::trackable. Derive your listener class from LLEventTrackable instead, - * and use something like - * <tt>LLEventPump::listen(boost::bind(&YourTrackableSubclass::method, - * instance, _1))</tt>. This will implicitly disconnect when the object - * referenced by @c instance is destroyed. - * - * @note - * LLEventTrackable doesn't address a couple of cases: - * * Object destroyed during call - * - You enter a slot call in thread A. - * - Thread B destroys the object, which of course disconnects it from any - * future slot calls. - * - Thread A's call uses 'this', which now refers to a defunct object. - * Undefined behavior results. - * * Call during destruction - * - @c MySubclass is derived from LLEventTrackable. - * - @c MySubclass registers one of its own methods using - * <tt>LLEventPump::listen()</tt>. - * - The @c MySubclass object begins destruction. <tt>~MySubclass()</tt> - * runs, destroying state specific to the subclass. (For instance, a - * <tt>Foo*</tt> data member is <tt>delete</tt>d but not zeroed.) - * - The listening method will not be disconnected until - * <tt>~LLEventTrackable()</tt> runs. - * - Before we get there, another thread posts data to the @c LLEventPump - * instance, calling the @c MySubclass method. - * - The method in question relies on valid @c MySubclass state. (For - * instance, it attempts to dereference the <tt>Foo*</tt> pointer that was - * <tt>delete</tt>d but not zeroed.) - * - Undefined behavior results. - */ -typedef boost::signals2::trackable LLEventTrackable; - -/***************************************************************************** * LLEventPump *****************************************************************************/ /** * LLEventPump is the base class interface through which we access the * concrete subclasses such as LLEventStream. - * - * @NOTE - * LLEventPump derives from LLEventTrackable so that when you "chain" - * LLEventPump instances together, they will automatically disconnect on - * destruction. Please see LLEventTrackable documentation for situations in - * which this may be perilous across threads. */ -class LL_COMMON_API LLEventPump: public LLEventTrackable +class LL_COMMON_API LLEventPump { public: static const std::string ANONYMOUS; // constant for anonymous listeners. @@ -535,18 +476,37 @@ public: * call, allows us to optimize away the second and subsequent dependency * sorts. * - * If name is set to LLEventPump::ANONYMOUS listen will bypass the entire + * If name is set to LLEventPump::ANONYMOUS, listen() will bypass the entire * dependency and ordering calculation. In this case, it is critical that * the result be assigned to a LLTempBoundListener or the listener is - * manually disconnected when no longer needed since there will be no + * manually disconnected when no longer needed, since there will be no * way to later find and disconnect this listener manually. */ + template <typename LISTENER> LLBoundListener listen(const std::string& name, - const LLEventListener& listener, + LISTENER&& listener, const NameList& after=NameList(), const NameList& before=NameList()) { - return listen_impl(name, listener, after, before); + if constexpr (std::is_invocable_v<LISTENER, const LLSD&>) + { + // wrap classic LLEventListener in LLAwareListener lambda + return listenb( + name, + [listener=std::move(listener)] + (const LLBoundListener&, const LLSD& event) + { + return listener(event); + }, + after, + before); + } + else + { + static_assert(std::is_invocable_v<LISTENER, LLBoundListener, const LLSD&>, + "LLEventPump::listen() listener has bad parameter signature"); + return listenb(name, std::forward<LISTENER>(listener), after, before); + } } /// Get the LLBoundListener associated with the passed name (dummy @@ -587,17 +547,40 @@ private: virtual void clear(); virtual void reset(); - - private: - // must precede mName; see LLEventPump::LLEventPump() - LLHandle<LLEventPumps> mRegistry; - std::string mName; - LLCoros::Mutex mConnectionListMutex; + llcoro::Mutex mConnectionListMutex; protected: - virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, + template <typename LISTENER> + LLBoundListener listenb(const std::string& name, + LISTENER&& listener, + const NameList& after=NameList(), + const NameList& before=NameList()) + { + using result_t = std::decay_t<decltype(listener(LLBoundListener(), LLSD()))>; + if constexpr (std::is_same_v<bool, result_t>) + { + return listen_impl(name, std::forward<LISTENER>(listener), after, before); + } + else + { + static_assert(std::is_same_v<void, result_t>, + "LLEventPump::listen() listener has bad return type"); + // wrap void listener in one that returns bool + return listen_impl( + name, + [listener=std::move(listener)] + (const LLBoundListener& conn, const LLSD& event) + { + listener(conn, event); + return false; + }, + after, + before); + } + } + virtual LLBoundListener listen_impl(const std::string& name, const LLAwareListener&, const NameList& after, const NameList& before); @@ -672,7 +655,7 @@ public: void discard(); protected: - virtual LLBoundListener listen_impl(const std::string& name, const LLEventListener&, + virtual LLBoundListener listen_impl(const std::string& name, const LLAwareListener&, const NameList& after, const NameList& before) override; @@ -682,6 +665,30 @@ private: }; /***************************************************************************** +* LLNamedListener +*****************************************************************************/ +/** + * LLNamedListener bundles a concrete LLEventPump subclass with a specific + * listener function, with an LLTempBoundListener to ensure that it's + * disconnected before destruction. + */ +template <class PUMP=LLEventStream> +class LL_COMMON_API LLNamedListener: PUMP +{ + using pump_t = PUMP; +public: + template <typename LISTENER> + LLNamedListener(const std::string& name, LISTENER&& listener): + pump_t(name, false), // don't tweak the name + mConn(pump_t::listen("func", std::forward<LISTENER>(listener))) + {} + +private: + LLTempBoundListener mConn; +}; +using LLStreamListener = LLNamedListener<>; + +/***************************************************************************** * LLReqID *****************************************************************************/ /** @@ -773,7 +780,7 @@ private: * Before sending the reply event, sendReply() copies the ["reqid"] item from * the request to the reply. */ -LL_COMMON_API bool sendReply(const LLSD& reply, const LLSD& request, +LL_COMMON_API bool sendReply(LLSD reply, const LLSD& request, const std::string& replyKey="reply"); #endif /* ! defined(LL_LLEVENTS_H) */ diff --git a/indra/llcommon/lleventtimer.cpp b/indra/llcommon/lleventtimer.cpp index 33fafffefd..cc227193cd 100644 --- a/indra/llcommon/lleventtimer.cpp +++ b/indra/llcommon/lleventtimer.cpp @@ -25,49 +25,44 @@ */ #include "linden_common.h" - #include "lleventtimer.h" -#include "u64.h" - - ////////////////////////////////////////////////////////////////////////////// // // LLEventTimer Implementation // ////////////////////////////////////////////////////////////////////////////// -LLEventTimer::LLEventTimer(F32 period) -: mEventTimer() +LLEventTimer::LLEventTimer(F32 period): + mPeriod(period) { - mPeriod = period; + start(); } -LLEventTimer::LLEventTimer(const LLDate& time) -: mEventTimer() +LLEventTimer::LLEventTimer(const LLDate& time): + LLEventTimer(F32(time.secondsSinceEpoch() - LLDate::now().secondsSinceEpoch())) +{} + +LLEventTimer::~LLEventTimer() { - mPeriod = (F32)(time.secondsSinceEpoch() - LLDate::now().secondsSinceEpoch()); } - -LLEventTimer::~LLEventTimer() +void LLEventTimer::start() { + mTimer = LL::Timers::instance().scheduleEvery([this]{ return tick(); }, mPeriod); } -//static -void LLEventTimer::updateClass() +void LLEventTimer::stop() { - for (auto& timer : instance_snapshot()) - { - F32 et = timer.mEventTimer.getElapsedTimeF32(); - if (timer.mEventTimer.getStarted() && et > timer.mPeriod) { - timer.mEventTimer.reset(); - if ( timer.tick() ) - { - delete &timer; - } - } - } + LL::Timers::instance().cancel(mTimer); } +bool LLEventTimer::isRunning() +{ + return LL::Timers::instance().isRunning(mTimer); +} +F32 LLEventTimer::getRemaining() +{ + return LL::Timers::instance().timeUntilCall(mTimer); +} diff --git a/indra/llcommon/lleventtimer.h b/indra/llcommon/lleventtimer.h index e0c2381807..b37d682d29 100644 --- a/indra/llcommon/lleventtimer.h +++ b/indra/llcommon/lleventtimer.h @@ -27,13 +27,12 @@ #ifndef LL_EVENTTIMER_H #define LL_EVENTTIMER_H -#include "stdtypes.h" +#include "llcallbacklist.h" #include "lldate.h" -#include "llinstancetracker.h" -#include "lltimer.h" +#include "stdtypes.h" // class for scheduling a function to be called at a given frequency (approximate, inprecise) -class LL_COMMON_API LLEventTimer : public LLInstanceTracker<LLEventTimer> +class LL_COMMON_API LLEventTimer { public: @@ -41,82 +40,18 @@ public: LLEventTimer(const LLDate& time); virtual ~LLEventTimer(); + void start(); + void stop(); + bool isRunning(); + F32 getRemaining(); + //function to be called at the supplied frequency - // Normally return FALSE; TRUE will delete the timer after the function returns. + // Normally return false; true will delete the timer after the function returns. virtual bool tick() = 0; - static void updateClass(); - - /// Schedule recurring calls to generic callable every period seconds. - /// Returns a pointer; if you delete it, cancels the recurring calls. - template <typename CALLABLE> - static LLEventTimer* run_every(F32 period, const CALLABLE& callable); - - /// Schedule a future call to generic callable. Returns a pointer. - /// CAUTION: The object referenced by that pointer WILL BE DELETED once - /// the callback has been called! LLEventTimer::getInstance(pointer) (NOT - /// pointer->getInstance(pointer)!) can be used to test whether the - /// pointer is still valid. If it is, deleting it will cancel the - /// callback. - template <typename CALLABLE> - static LLEventTimer* run_at(const LLDate& time, const CALLABLE& callable); - - /// Like run_at(), but after a time delta rather than at a timestamp. - /// Same CAUTION. - template <typename CALLABLE> - static LLEventTimer* run_after(F32 interval, const CALLABLE& callable); - protected: - LLTimer mEventTimer; + LL::Timers::temp_handle_t mTimer; F32 mPeriod; - -private: - template <typename CALLABLE> - class Generic; }; -template <typename CALLABLE> -class LLEventTimer::Generic: public LLEventTimer -{ -public: - // making TIME generic allows engaging either LLEventTimer constructor - template <typename TIME> - Generic(const TIME& time, bool once, const CALLABLE& callable): - LLEventTimer(time), - mOnce(once), - mCallable(callable) - {} - bool tick() override - { - mCallable(); - // true tells updateClass() to delete this instance - return mOnce; - } - -private: - bool mOnce; - CALLABLE mCallable; -}; - -template <typename CALLABLE> -LLEventTimer* LLEventTimer::run_every(F32 period, const CALLABLE& callable) -{ - // return false to schedule recurring calls - return new Generic<CALLABLE>(period, false, callable); -} - -template <typename CALLABLE> -LLEventTimer* LLEventTimer::run_at(const LLDate& time, const CALLABLE& callable) -{ - // return true for one-shot callback - return new Generic<CALLABLE>(time, true, callable); -} - -template <typename CALLABLE> -LLEventTimer* LLEventTimer::run_after(F32 interval, const CALLABLE& callable) -{ - // one-shot callback after specified interval - return new Generic<CALLABLE>(interval, true, callable); -} - #endif //LL_EVENTTIMER_H diff --git a/indra/llcommon/llexception.cpp b/indra/llcommon/llexception.cpp index c0154a569f..0a2f2ab607 100644 --- a/indra/llcommon/llexception.cpp +++ b/indra/llcommon/llexception.cpp @@ -15,6 +15,8 @@ #include "llexception.h" // STL headers // std headers +#include <iomanip> +#include <sstream> #include <typeinfo> // external library headers #include <boost/exception/diagnostic_information.hpp> @@ -29,7 +31,6 @@ // On Windows, header-only implementation causes macro collisions -- use // prebuilt library #define BOOST_STACKTRACE_LINK -#include <excpt.h> #endif // LL_WINDOWS #include <boost/stacktrace.hpp> @@ -94,25 +95,47 @@ void annotate_exception_(boost::exception& exc) // For windows SEH exception handling we sometimes need a filter that will // separate C++ exceptions from C SEH exceptions -static const U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific +static constexpr U32 STATUS_MSC_EXCEPTION = 0xE06D7363; // compiler specific +static constexpr U32 STATUS_STACK_FULL = 0xC00000FD; -U32 msc_exception_filter(U32 code, struct _EXCEPTION_POINTERS *exception_infop) +void LL::seh::fill_stacktrace(std::string& stacktrace, U32 code) { - const auto stack = to_string(boost::stacktrace::stacktrace()); - LL_WARNS() << "SEH Exception handled (that probably shouldn't be): Code " << code - << "\n Stack trace: \n" - << stack << LL_ENDL; + // Sadly, despite its diagnostic importance, trying to capture a + // stacktrace when the stack is already blown only terminates us faster. + if (code == STATUS_STACK_FULL) + { + stacktrace = "(stack overflow, no traceback)"; + } + else + { + stacktrace = to_string(boost::stacktrace::stacktrace()); + } +} +U32 LL::seh::common_filter(U32 code, struct _EXCEPTION_POINTERS*) +{ if (code == STATUS_MSC_EXCEPTION) { - // C++ exception, go on + // C++ exception, don't stop at this handler return EXCEPTION_CONTINUE_SEARCH; } else { - // handle it + // This is a non-C++ exception, e.g. hardware check. + // Pass control into the handler block. return EXCEPTION_EXECUTE_HANDLER; } } +void LL::seh::rethrow(U32 code, const std::string& stacktrace) +{ + std::ostringstream out; + out << "Windows exception 0x" << std::hex << code; + if (! stacktrace.empty()) + { + out << '\n' << stacktrace; + } + LLTHROW(Windows_SEH_exception(out.str())); +} + #endif //LL_WINDOWS diff --git a/indra/llcommon/llexception.h b/indra/llcommon/llexception.h index 68e609444e..0c32a55409 100644 --- a/indra/llcommon/llexception.h +++ b/indra/llcommon/llexception.h @@ -12,10 +12,16 @@ #if ! defined(LL_LLEXCEPTION_H) #define LL_LLEXCEPTION_H +#include "always_return.h" +#include "stdtypes.h" #include <stdexcept> +#include <utility> // std::forward #include <boost/exception/exception.hpp> #include <boost/throw_exception.hpp> #include <boost/current_function.hpp> +#if LL_WINDOWS +#include <excpt.h> +#endif // LL_WINDOWS // "Found someone who can comfort me // But there are always exceptions..." @@ -102,14 +108,115 @@ void crash_on_unhandled_exception_(const char*, int, const char*, const std::str log_unhandled_exception_(__FILE__, __LINE__, BOOST_CURRENT_FUNCTION, CONTEXT) void log_unhandled_exception_(const char*, int, const char*, const std::string&); +/***************************************************************************** +* Structured Exception Handling +*****************************************************************************/ +// this is used in platform-generic code -- define outside #if LL_WINDOWS +struct Windows_SEH_exception: public LLException +{ + Windows_SEH_exception(const std::string& what): LLException(what) {} +}; -#if LL_WINDOWS +namespace LL +{ +namespace seh +{ + +#if LL_WINDOWS //------------------------------------------------------------- + +void fill_stacktrace(std::string& stacktrace, U32 code); + +// wrapper around caller's U32 filter(U32 code, struct _EXCEPTION_POINTERS*) +// filter function: capture a stacktrace, if possible, before forwarding the +// call to the caller's filter() function +template <typename FILTER> +U32 filter_(std::string& stacktrace, FILTER&& filter, + U32 code, struct _EXCEPTION_POINTERS* exptrs) +{ + // By the time the handler gets control, the stack has been unwound, + // so report the stack trace now at filter() time. + fill_stacktrace(stacktrace, code); + return std::forward<FILTER>(filter)(code, exptrs); +} + +template <typename TRYCODE, typename FILTER, typename HANDLER> +auto catcher_inner(std::string& stacktrace, + TRYCODE&& trycode, FILTER&& filter, HANDLER&& handler) +{ + __try + { + return std::forward<TRYCODE>(trycode)(); + } + __except (filter_(stacktrace, + std::forward<FILTER>(filter), + GetExceptionCode(), GetExceptionInformation())) + { + return always_return<decltype(trycode())>( + std::forward<HANDLER>(handler), GetExceptionCode(), stacktrace); + } +} + +// triadic variant specifies try(), filter(U32, struct _EXCEPTION_POINTERS*), +// handler(U32, const std::string& stacktrace) +// stacktrace may or may not be available +template <typename TRYCODE, typename FILTER, typename HANDLER> +auto catcher(TRYCODE&& trycode, FILTER&& filter, HANDLER&& handler) +{ + // Construct and destroy this stacktrace string in the outer function + // because we can't do either in the function with __try/__except. + std::string stacktrace; + return catcher_inner(stacktrace, + std::forward<TRYCODE>(trycode), + std::forward<FILTER>(filter), + std::forward<HANDLER>(handler)); +} + +// common_filter() handles the typical case in which we want our handler +// clause to handle only Structured Exceptions rather than explicitly-thrown +// C++ exceptions +U32 common_filter(U32 code, struct _EXCEPTION_POINTERS*); + +// dyadic variant specifies try(), handler(U32, stacktrace), assumes common_filter() +template <typename TRYCODE, typename HANDLER> +auto catcher(TRYCODE&& trycode, HANDLER&& handler) +{ + return catcher(std::forward<TRYCODE>(trycode), + common_filter, + std::forward<HANDLER>(handler)); +} + +[[noreturn]] void rethrow(U32 code, const std::string& stacktrace); + +// monadic variant specifies try(), assumes default filter and handler +template <typename TRYCODE> +auto catcher(TRYCODE&& trycode) +{ + return catcher(std::forward<TRYCODE>(trycode), rethrow); +} + +#else // not LL_WINDOWS ----------------------------------------------------- + +template <typename TRYCODE, typename FILTER, typename HANDLER> +auto catcher(TRYCODE&& trycode, FILTER&&, HANDLER&&) +{ + return std::forward<TRYCODE>(trycode)(); +} + +template <typename TRYCODE, typename HANDLER> +auto catcher(TRYCODE&& trycode, HANDLER&&) +{ + return std::forward<TRYCODE>(trycode)(); +} + +template <typename TRYCODE> +auto catcher(TRYCODE&& trycode) +{ + return std::forward<TRYCODE>(trycode)(); +} -// SEH exception filtering for use in __try __except -// Separates C++ exceptions from C SEH exceptions -// Todo: might be good idea to do some kind of seh_to_msc_wrapper(function, ARGS&&); -U32 msc_exception_filter(U32 code, struct _EXCEPTION_POINTERS *exception_infop); +#endif // not LL_WINDOWS ----------------------------------------------------- -#endif //LL_WINDOWS +} // namespace LL::seh +} // namespace LL #endif /* ! defined(LL_LLEXCEPTION_H) */ diff --git a/indra/llcommon/llformat.h b/indra/llcommon/llformat.h index 4456a72696..0c840d6e8e 100644 --- a/indra/llcommon/llformat.h +++ b/indra/llcommon/llformat.h @@ -28,6 +28,8 @@ #ifndef LL_LLFORMAT_H #define LL_LLFORMAT_H +#include "llpreprocessor.h" + // Use as follows: // LL_INFOS() << llformat("Test:%d (%.2f %.2f)", idx, x, y) << LL_ENDL; // diff --git a/indra/llcommon/llinstancetracker.h b/indra/llcommon/llinstancetracker.h index 92b26354a1..b5c681e60a 100644 --- a/indra/llcommon/llinstancetracker.h +++ b/indra/llcommon/llinstancetracker.h @@ -41,6 +41,7 @@ #include <boost/iterator/indirect_iterator.hpp> #include <boost/iterator/filter_iterator.hpp> +#include "llprofiler.h" #include "lockstatic.h" #include "stringize.h" @@ -80,6 +81,8 @@ class LLInstanceTracker { InstanceMap mMap; }; + // Unfortunately there's no umbrella class that owns all LLInstanceTracker + // instances, so there's no good place to call LockStatic::cleanup(). typedef llthread::LockStatic<StaticData> LockStatic; public: @@ -170,23 +173,7 @@ public: } // lock static data during construction -#if ! LL_WINDOWS LockStatic mLock; -#else // LL_WINDOWS - // We want to be able to use (e.g.) our instance_snapshot subclass as: - // for (auto& inst : T::instance_snapshot()) ... - // But when this snapshot base class directly contains LockStatic, as - // above, Visual Studio 2017 requires us to code instead: - // for (auto& inst : std::move(T::instance_snapshot())) ... - // nat thinks this should be unnecessary, as an anonymous class - // instance is already a temporary. It shouldn't need to be cast to - // rvalue reference (the role of std::move()). clang evidently agrees, - // as the short form works fine with Xcode on Mac. - // To support the succinct usage, instead of directly storing - // LockStatic, store std::shared_ptr<LockStatic>, which is copyable. - std::shared_ptr<LockStatic> mLockp{std::make_shared<LockStatic>()}; - LockStatic& mLock{*mLockp}; -#endif // LL_WINDOWS VectorType mData; }; using snapshot = snapshot_of<T>; @@ -276,6 +263,35 @@ protected: public: virtual const KEY& getKey() const { return mInstanceKey; } + /// for use ONLY for an object we're sure resides on the heap! + static bool erase(const KEY& key) + { + return erase(getInstance(key)); + } + + /// for use ONLY for an object we're sure resides on the heap! + static bool erase(const weak_t& ptr) + { + return erase(ptr.lock()); + } + + /// for use ONLY for an object we're sure resides on the heap! + static bool erase(const ptr_t& ptr) + { + if (! ptr) + { + return false; + } + + // Because we store and return ptr_t instances with no-op deleters, + // merely resetting the last pointer doesn't destroy the referenced + // object. Don't even bother resetting 'ptr'. Just extract its raw + // pointer and delete that. + auto raw{ ptr.get() }; + delete raw; + return true; + } + private: LLInstanceTracker( const LLInstanceTracker& ) = delete; LLInstanceTracker& operator=( const LLInstanceTracker& ) = delete; @@ -356,6 +372,7 @@ class LLInstanceTracker<T, void, KEY_COLLISION_BEHAVIOR> { InstanceSet mSet; }; + // see LockStatic comment in the above specialization for non-void KEY typedef llthread::LockStatic<StaticData> LockStatic; public: @@ -434,23 +451,7 @@ public: } // lock static data during construction -#if ! LL_WINDOWS LockStatic mLock; -#else // LL_WINDOWS - // We want to be able to use our instance_snapshot subclass as: - // for (auto& inst : T::instance_snapshot()) ... - // But when this snapshot base class directly contains LockStatic, as - // above, Visual Studio 2017 requires us to code instead: - // for (auto& inst : std::move(T::instance_snapshot())) ... - // nat thinks this should be unnecessary, as an anonymous class - // instance is already a temporary. It shouldn't need to be cast to - // rvalue reference (the role of std::move()). clang evidently agrees, - // as the short form works fine with Xcode on Mac. - // To support the succinct usage, instead of directly storing - // LockStatic, store std::shared_ptr<LockStatic>, which is copyable. - std::shared_ptr<LockStatic> mLockp{std::make_shared<LockStatic>()}; - LockStatic& mLock{*mLockp}; -#endif // LL_WINDOWS VectorType mData; }; using snapshot = snapshot_of<T>; @@ -481,6 +482,29 @@ public: template <typename SUBCLASS> using key_snapshot_of = instance_snapshot_of<SUBCLASS>; + /// for use ONLY for an object we're sure resides on the heap! + static bool erase(const weak_t& ptr) + { + return erase(ptr.lock()); + } + + /// for use ONLY for an object we're sure resides on the heap! + static bool erase(const ptr_t& ptr) + { + if (! ptr) + { + return false; + } + + // Because we store and return ptr_t instances with no-op deleters, + // merely resetting the last pointer doesn't destroy the referenced + // object. Don't even bother resetting 'ptr'. Just extract its raw + // pointer and delete that. + auto raw{ ptr.get() }; + delete raw; + return true; + } + protected: LLInstanceTracker() { diff --git a/indra/llcommon/llinttracker.h b/indra/llcommon/llinttracker.h new file mode 100644 index 0000000000..fd6d24d0fd --- /dev/null +++ b/indra/llcommon/llinttracker.h @@ -0,0 +1,43 @@ +/** + * @file llinttracker.h + * @author Nat Goodspeed + * @date 2024-08-30 + * @brief LLIntTracker isa LLInstanceTracker with generated int keys. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLINTTRACKER_H) +#define LL_LLINTTRACKER_H + +#include "llinstancetracker.h" + +template <typename T> +class LLIntTracker: public LLInstanceTracker<T, int> +{ + using super = LLInstanceTracker<T, int>; +public: + LLIntTracker(): + super(getUniqueKey()) + {} + +private: + static int getUniqueKey() + { + // Find a random key that does NOT already correspond to an instance. + // Passing a duplicate key to LLInstanceTracker would do Bad Things. + int key; + do + { + key = std::rand(); + } while (super::getInstance(key)); + // This could be racy, if we were instantiating new LLIntTracker<T>s + // on multiple threads. If we need that, have to lock between checking + // getInstance() and constructing the new super. + return key; + } +}; + +#endif /* ! defined(LL_LLINTTRACKER_H) */ diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index 662a2511cd..ef43af6ceb 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -14,23 +14,25 @@ // associated header #include "llleap.h" // STL headers -#include <sstream> #include <algorithm> +#include <memory> +#include <sstream> // std headers // external library headers // other Linden headers #include "llerror.h" -#include "llstring.h" -#include "llprocess.h" +#include "llerrorcontrol.h" #include "llevents.h" -#include "stringize.h" -#include "llsdutil.h" +#include "llexception.h" +#include "llleaplistener.h" +#include "llprocess.h" #include "llsdserialize.h" -#include "llerrorcontrol.h" +#include "llsdutil.h" +#include "llstring.h" #include "lltimer.h" #include "lluuid.h" -#include "llleaplistener.h" -#include "llexception.h" +#include "scriptcommand.h" +#include "stringize.h" #if LL_MSVC #pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally @@ -50,20 +52,19 @@ public: // We expect multiple LLLeapImpl instances. Definitely tweak // mDonePump's name for uniqueness. mDonePump("LLLeap", true), - // Troubling thought: what if one plugin intentionally messes with - // another plugin? LLEventPump names are in a single global namespace. - // Try to make that more difficult by generating a UUID for the reply- - // pump name -- so it should NOT need tweaking for uniqueness. - mReplyPump(LLUUID::generateNewID().asString()), mExpect(0), // Instantiate a distinct LLLeapListener for this plugin. (Every // plugin will want its own collection of managed listeners, etc.) - // Pass it a callback to our connect() method, so it can send events + // Pass it our wstdin() method as its callback, so it can send events // from a particular LLEventPump to the plugin without having to know // this class or method name. - mListener(new LLLeapListener( - [this](LLEventPump& pump, const std::string& listener) - { return connect(pump, listener); })) + mListener( + new LLLeapListener( + "LLLeap", + // Serialize any reply event to our child's stdin, suitably + // enriched with the pump name on which it was received. + [this](const std::string& pump, const LLSD& data) + { return wstdin(pump, data); })) { // Rule out unpopulated Params block if (! cparams.executable.isProvided()) @@ -107,7 +108,7 @@ public: // If that didn't work, no point in keeping this LLLeap object. if (! mChild) { - LLTHROW(Error(STRINGIZE("failed to run " << mDesc))); + LLTHROW(Error(stringize("failed to run ", mDesc))); } // Okay, launch apparently worked. Change our mDonePump listener. @@ -122,9 +123,6 @@ public: childout.setLimit(20); childerr.setLimit(20); - // Serialize any event received on mReplyPump to our child's stdin. - mStdinConnection = connect(mReplyPump, "LLLeap"); - // Listening on stdout is stateful. In general, we're either waiting // for the length prefix or waiting for the specified length of data. mReadPrefix = true; @@ -146,7 +144,7 @@ public: // Send child a preliminary event reporting our own reply-pump name -- // which would otherwise be pretty tricky to guess! - wstdin(mReplyPump.getName(), + wstdin(mListener->getReplyPump().getName(), LLSDMap ("command", mListener->getName()) // Include LLLeap features -- this may be important for child to @@ -193,7 +191,7 @@ public: return false; } - // Listener for events on mReplyPump: send to child stdin + // Listener for reply events: send to child stdin bool wstdin(const std::string& pump, const LLSD& data) { LLSD packet(LLSDMap("pump", pump)("data", data)); @@ -344,7 +342,7 @@ public: { // The LLSD object we got from our stream contains the // keys we need. - LLEventPumps::instance().obtain(data["pump"]).post(data["data"]); + LLEventPumps::instance().post(data["pump"], data["data"]); } catch (const std::exception& err) { @@ -355,7 +353,7 @@ public: // request, send a reply. We happen to know who originated // this request, and the reply LLEventPump of interest. // Not our problem if the plugin ignores the reply event. - data["reply"] = mReplyPump.getName(); + data["reply"] = mListener->getReplyPump().getName(); sendReply(llsd::map("error", stringize(LLError::Log::classname(err), ": ", err.what())), data); @@ -423,7 +421,7 @@ public: LLSD event; event["type"] = "error"; event["error"] = error; - mReplyPump.post(event); + mListener->getReplyPump().post(event); // All the above really accomplished was to buffer the serialized // event in our WritePipe. Have to pump mainloop a couple times to @@ -440,24 +438,10 @@ public: } private: - /// We always want to listen on mReplyPump with wstdin(); under some - /// circumstances we'll also echo other LLEventPumps to the plugin. - LLBoundListener connect(LLEventPump& pump, const std::string& listener) - { - // Serialize any event received on the specified LLEventPump to our - // child's stdin, suitably enriched with the pump name on which it was - // received. - return pump.listen(listener, - [this, name=pump.getName()](const LLSD& data) - { return wstdin(name, data); }); - } - std::string mDesc; LLEventStream mDonePump; - LLEventStream mReplyPump; LLProcessPtr mChild; - LLTempBoundListener - mStdinConnection, mStdoutConnection, mStderrConnection; + LLTempBoundListener mStdoutConnection, mStderrConnection; LLProcess::ReadPipe::size_type mExpect; LLError::RecorderPtr mRecorder; std::unique_ptr<LLLeapListener> mListener; @@ -501,12 +485,24 @@ LLLeap* LLLeap::create(const std::string& desc, const std::vector<std::string>& LLLeap* LLLeap::create(const std::string& desc, const std::string& plugin, bool exc) { - // Use LLStringUtil::getTokens() to parse the command line - return create(desc, - LLStringUtil::getTokens(plugin, - " \t\r\n", // drop_delims - "", // no keep_delims - "\"'", // either kind of quotes - "\\"), // backslash escape - exc); + // Use ScriptCommand to parse the command line + ScriptCommand command(plugin); + auto error = command.error(); + if (! error.empty()) + { + if (exc) + { + LLTHROW(Error(error)); + } + return nullptr; + } + + LLProcess::Params params; + params.desc = desc; + params.executable = command.script; + for (const auto& arg : command.args) + { + params.args.add(arg); + } + return create(params, exc); } diff --git a/indra/llcommon/llleaplistener.cpp b/indra/llcommon/llleaplistener.cpp index 050d71c327..9742c9e9de 100644 --- a/indra/llcommon/llleaplistener.cpp +++ b/indra/llcommon/llleaplistener.cpp @@ -54,53 +54,62 @@ return features; } -LLLeapListener::LLLeapListener(const ConnectFunc& connect): +LLLeapListener::LLLeapListener(std::string_view caller, const Callback& callback): // Each LEAP plugin has an instance of this listener. Make the command // pump name difficult for other such plugins to guess. LLEventAPI(LLUUID::generateNewID().asString(), "Operations relating to the LLSD Event API Plugin (LEAP) protocol"), - mConnect(connect) + mCaller(caller), + mCallback(callback), + // Troubling thought: what if one plugin intentionally messes with + // another plugin? LLEventPump names are in a single global namespace. + // Try to make that more difficult by generating a UUID for the reply- + // pump name -- so it should NOT need tweaking for uniqueness. + mReplyPump(LLUUID::generateNewID().asString()), + mReplyConn(connect(mReplyPump, mCaller)) { LLSD need_name(LLSDMap("name", LLSD())); add("newpump", - "Instantiate a new LLEventPump named like [\"name\"] and listen to it.\n" - "[\"type\"] == \"LLEventStream\", \"LLEventMailDrop\" et al.\n" - "Events sent through new LLEventPump will be decorated with [\"pump\"]=name.\n" - "Returns actual name in [\"name\"] (may be different if collision).", +R"-(Instantiate a new LLEventPump named like ["name"] and listen to it. +["type"] == "LLEventStream", "LLEventMailDrop" et al. +Events sent through new LLEventPump will be decorated with ["pump"]=name. +Returns actual name in ["name"] (may be different if collision).)-", &LLLeapListener::newpump, need_name); LLSD need_source_listener(LLSDMap("source", LLSD())("listener", LLSD())); add("listen", - "Listen to an existing LLEventPump named [\"source\"], with listener name\n" - "[\"listener\"].\n" - "By default, send events on [\"source\"] to the plugin, decorated\n" - "with [\"pump\"]=[\"source\"].\n" - "If [\"dest\"] specified, send undecorated events on [\"source\"] to the\n" - "LLEventPump named [\"dest\"].\n" - "Returns [\"status\"] boolean indicating whether the connection was made.", +R"-(Listen to an existing LLEventPump named ["source"], with listener name +["listener"]. +If ["tweak"] is specified as true, tweak listener name for uniqueness. +By default, send events on ["source"] to the plugin, decorated +with ["pump"]=["source"]. +If ["dest"] specified, send undecorated events on ["source"] to the +LLEventPump named ["dest"]. +Returns ["status"] boolean indicating whether the connection was made, +plus ["listener"] reporting (possibly tweaked) listener name.)-", &LLLeapListener::listen, need_source_listener); add("stoplistening", - "Disconnect a connection previously established by \"listen\".\n" - "Pass same [\"source\"] and [\"listener\"] arguments.\n" - "Returns [\"status\"] boolean indicating whether such a listener existed.", +R"-(Disconnect a connection previously established by "listen". +Pass same ["source"] and ["listener"] arguments. +Returns ["status"] boolean indicating whether such a listener existed.)-", &LLLeapListener::stoplistening, need_source_listener); add("ping", - "No arguments, just a round-trip sanity check.", +"No arguments, just a round-trip sanity check.", &LLLeapListener::ping); add("getAPIs", - "Enumerate all LLEventAPI instances by name and description.", +"Enumerate all LLEventAPI instances by name and description.", &LLLeapListener::getAPIs); add("getAPI", - "Get name, description, dispatch key and operations for LLEventAPI [\"api\"].", +R"-(Get name, description, dispatch key and operations for LLEventAPI ["api"].)-", &LLLeapListener::getAPI, LLSD().with("api", LLSD())); add("getFeatures", - "Return an LLSD map of feature strings (deltas from baseline LEAP protocol)", +"Return an LLSD map of feature strings (deltas from baseline LEAP protocol)", static_cast<void (LLLeapListener::*)(const LLSD&) const>(&LLLeapListener::getFeatures)); add("getFeature", - "Return the feature value with key [\"feature\"]", +R"-(Return the feature value with key ["feature"])-", &LLLeapListener::getFeature, LLSD().with("feature", LLSD())); } @@ -112,6 +121,7 @@ LLLeapListener::~LLLeapListener() // value_type, and Bad Things would happen if you copied an // LLTempBoundListener. (Destruction of the original would disconnect the // listener, invalidating every stored connection.) + LL_DEBUGS("LLLeapListener") << "~LLLeapListener(\"" << mCaller << "\")" << LL_ENDL; for (ListenersMap::value_type& pair : mListeners) { pair.second.disconnect(); @@ -133,8 +143,7 @@ void LLLeapListener::newpump(const LLSD& request) reply["name"] = name; // Now listen on this new pump with our plugin listener - std::string myname("llleap"); - saveListener(name, myname, mConnect(new_pump, myname)); + saveListener(name, mCaller, connect(new_pump, mCaller)); } catch (const LLEventPumps::BadType& error) { @@ -149,6 +158,11 @@ void LLLeapListener::listen(const LLSD& request) std::string source_name = request["source"]; std::string dest_name = request["dest"]; std::string listener_name = request["listener"]; + if (request["tweak"].asBoolean()) + { + listener_name = LLEventPump::inventName(listener_name); + } + reply["listener"] = listener_name; LLEventPump & source = LLEventPumps::instance().obtain(source_name); @@ -170,7 +184,7 @@ void LLLeapListener::listen(const LLSD& request) { // "dest" unspecified means to direct events on "source" // to our plugin listener. - saveListener(source_name, listener_name, mConnect(source, listener_name)); + saveListener(source_name, listener_name, connect(source, listener_name)); } reply["status"] = true; } @@ -292,10 +306,27 @@ void LLLeapListener::getFeature(const LLSD& request) const } } +LLBoundListener LLLeapListener::connect(LLEventPump& pump, const std::string& listener) +{ + // Connect to source pump with an adapter that calls our callback with the + // pump name as well as the event data. + return pump.listen( + listener, + [callback=mCallback, pump=pump.getName()] + (const LLSD& data) + { return callback(pump, data); }); +} + void LLLeapListener::saveListener(const std::string& pump_name, const std::string& listener_name, const LLBoundListener& listener) { - mListeners.insert(ListenersMap::value_type(ListenersMap::key_type(pump_name, listener_name), - listener)); + // Don't use insert() or emplace() because, if this (pump_name, + // listener_name) pair is already in mListeners, we *want* to overwrite it. + auto& listener_entry{ mListeners[ListenersMap::key_type(pump_name, listener_name)] }; + // If we already stored a connection for this pump and listener name, + // disconnect it before overwriting it. But if this entry was newly + // created, disconnect() will be a no-op. + listener_entry.disconnect(); + listener_entry = listener; } diff --git a/indra/llcommon/llleaplistener.h b/indra/llcommon/llleaplistener.h index cad4543d02..d38a6f4ace 100644 --- a/indra/llcommon/llleaplistener.h +++ b/indra/llcommon/llleaplistener.h @@ -13,10 +13,9 @@ #define LL_LLLEAPLISTENER_H #include "lleventapi.h" +#include <functional> #include <map> #include <string> -#include <boost/function.hpp> -#include <boost/ptr_container/ptr_map.hpp> /// Listener class implementing LLLeap query/control operations. /// See https://jira.lindenlab.com/jira/browse/DEV-31978. @@ -24,18 +23,16 @@ class LLLeapListener: public LLEventAPI { public: /** - * Decouple LLLeap by dependency injection. Certain LLLeapListener - * operations must be able to cause LLLeap to listen on a specified - * LLEventPump with the LLLeap listener that wraps incoming events in an - * outer (pump=, data=) map and forwards them to the plugin. Very well, - * define the signature for a function that will perform that, and make - * our constructor accept such a function. + * Certain LLLeapListener operations listen on a specified LLEventPump. + * Accept a bool(pump, data) callback from our caller for when any such + * event is received. */ - typedef boost::function<LLBoundListener(LLEventPump&, const std::string& listener)> - ConnectFunc; - LLLeapListener(const ConnectFunc& connect); + using Callback = std::function<bool(const std::string& pump, const LLSD& data)>; + LLLeapListener(std::string_view caller, const Callback& callback); ~LLLeapListener(); + LLEventPump& getReplyPump() { return mReplyPump; } + static LLSD getFeatures(); private: @@ -48,10 +45,16 @@ private: void getFeatures(const LLSD&) const; void getFeature(const LLSD&) const; + LLBoundListener connect(LLEventPump& pump, const std::string& listener); void saveListener(const std::string& pump_name, const std::string& listener_name, const LLBoundListener& listener); - ConnectFunc mConnect; + // The relative order of these next declarations is important because the + // constructor will initialize in this order. + std::string mCaller; + Callback mCallback; + LLEventStream mReplyPump; + LLTempBoundListener mReplyConn; // In theory, listen() could simply call the relevant LLEventPump's // listen() method, stoplistening() likewise. Lifespan issues make us diff --git a/indra/llcommon/lllivefile.cpp b/indra/llcommon/lllivefile.cpp index 58de61a7e4..b20c91dbe4 100644 --- a/indra/llcommon/lllivefile.cpp +++ b/indra/llcommon/lllivefile.cpp @@ -170,7 +170,7 @@ namespace : LLEventTimer(refresh), mLiveFile(f) { } - bool tick() + bool tick() override { mLiveFile.checkAndReload(); return false; diff --git a/indra/llcommon/llmainthreadtask.h b/indra/llcommon/llmainthreadtask.h index c3ed7fef52..eccf11fcbe 100644 --- a/indra/llcommon/llmainthreadtask.h +++ b/indra/llcommon/llmainthreadtask.h @@ -13,11 +13,8 @@ #if ! defined(LL_LLMAINTHREADTASK_H) #define LL_LLMAINTHREADTASK_H -#include "lleventtimer.h" #include "llthread.h" -#include "llmake.h" -#include <future> -#include <type_traits> // std::result_of +#include "workqueue.h" /** * LLMainThreadTask provides a way to perform some task specifically on the @@ -28,18 +25,17 @@ * Instead of instantiating LLMainThreadTask, pass your invocable to its * static dispatch() method. dispatch() returns the result of calling your * task. (Or, if your task throws an exception, dispatch() throws that - * exception. See std::packaged_task.) + * exception.) * * When you call dispatch() on the main thread (as determined by * on_main_thread() in llthread.h), it simply calls your task and returns the * result. * - * When you call dispatch() on a secondary thread, it instantiates an - * LLEventTimer subclass scheduled immediately. Next time the main loop calls - * LLEventTimer::updateClass(), your task will be run, and LLMainThreadTask - * will fulfill a future with its result. Meanwhile the requesting thread - * blocks on that future. As soon as it is set, the requesting thread wakes up - * with the task result. + * When you call dispatch() on a secondary thread, it posts your task to + * gMainloopWork, the WorkQueue serviced by the main thread, using + * WorkQueue::waitForResult() to block the caller. Next time the main loop + * calls gMainloopWork.runFor(), your task will be run, and waitForResult() + * will return its result. */ class LLMainThreadTask { @@ -59,41 +55,15 @@ public: } else { - // It's essential to construct LLEventTimer subclass instances on - // the heap because, on completion, LLEventTimer deletes them. - // Once we enable C++17, we can use Class Template Argument - // Deduction. Until then, use llmake_heap(). - auto* task = llmake_heap<Task>(std::forward<CALLABLE>(callable)); - auto future = task->mTask.get_future(); - // Now simply block on the future. - return future.get(); + auto queue{ LL::WorkQueue::getInstance("mainloop") }; + // If this needs a null check and a message, please introduce a + // method in the .cpp file so consumers of this header don't drag + // in llerror.h. + // Use waitForResult_() so dispatch() can be used even from the + // calling thread's default coroutine. + return queue->waitForResult_(std::forward<CALLABLE>(callable)); } } - -private: - template <typename CALLABLE> - struct Task: public LLEventTimer - { - Task(CALLABLE&& callable): - // no wait time: call tick() next chance we get - LLEventTimer(0), - mTask(std::forward<CALLABLE>(callable)) - {} - bool tick() override - { - // run the task on the main thread, will populate the future - // obtained by get_future() - mTask(); - // tell LLEventTimer we're done (one shot) - return true; - } - // Given arbitrary CALLABLE, which might be a lambda, how are we - // supposed to obtain its signature for std::packaged_task? It seems - // redundant to have to add an argument list to engage invoke_result_t, then - // add the argument list again to complete the signature. At least we - // only support a nullary CALLABLE. - std::packaged_task<std::invoke_result_t<CALLABLE>()> mTask; - }; }; #endif /* ! defined(LL_LLMAINTHREADTASK_H) */ diff --git a/indra/llcommon/llmutex.h b/indra/llcommon/llmutex.h index 6e8cf9643b..62943845a5 100644 --- a/indra/llcommon/llmutex.h +++ b/indra/llcommon/llmutex.h @@ -194,6 +194,18 @@ public: mSharedMutex->unlock<SHARED>(); } + void lock() + { + if (mSharedMutex) + mSharedMutex->lock<SHARED>(); + } + + void unlock() + { + if (mSharedMutex) + mSharedMutex->unlock<SHARED>(); + } + private: LLSharedMutex* mSharedMutex; }; diff --git a/indra/llcommon/llprocessor.cpp b/indra/llcommon/llprocessor.cpp index 41ff369de2..51b1b967f1 100644 --- a/indra/llcommon/llprocessor.cpp +++ b/indra/llcommon/llprocessor.cpp @@ -793,19 +793,55 @@ private: }; #elif LL_LINUX + +// *NOTE:Mani - eww, macros! srry. +#define LLPI_SET_INFO_STRING(llpi_id, cpuinfo_id) \ + if (!cpuinfo[cpuinfo_id].empty()) \ + { setInfo(llpi_id, cpuinfo[cpuinfo_id]);} + +#define LLPI_SET_INFO_INT(llpi_id, cpuinfo_id) \ + {\ + S32 result; \ + if (!cpuinfo[cpuinfo_id].empty() \ + && LLStringUtil::convertToS32(cpuinfo[cpuinfo_id], result)) \ + { setInfo(llpi_id, result);} \ + } + const char CPUINFO_FILE[] = "/proc/cpuinfo"; -class LLProcessorInfoLinuxImpl : public LLProcessorInfoImpl -{ +class LLProcessorInfoLinuxImpl : public LLProcessorInfoImpl { public: - LLProcessorInfoLinuxImpl() - { + LLProcessorInfoLinuxImpl() { get_proc_cpuinfo(); } virtual ~LLProcessorInfoLinuxImpl() {} + private: + F64 getCPUMaxMHZ() + { + // Nicky: We just look into cpu0. In theory we could iterate over all cores + // "/sys/devices/system/cpu/cpu*/cpufreq/cpuinfo_max_freq" + // But those should not fluctuate that much? + std::ifstream fIn { "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq" }; + + if( !fIn.is_open() ) + return 0.0; + + std::string strLine; + fIn >> strLine; + if( strLine.empty() ) + return 0.0l; + + F64 mhz {}; + if( !LLStringUtil::convertToF64(strLine, mhz ) ) + return 0.0; + + mhz = mhz / 1000.0; + return mhz; + } + void get_proc_cpuinfo() { std::map< std::string, std::string > cpuinfo; @@ -834,30 +870,23 @@ private: std::string llinename(linename); LLStringUtil::toLower(llinename); std::string lineval( spacespot + 1, nlspot ); - cpuinfo[ llinename ] = lineval; + cpuinfo[ llinename ] = lineval; } fclose(cpuinfo_fp); } # if LL_X86 -// *NOTE:Mani - eww, macros! srry. -#define LLPI_SET_INFO_STRING(llpi_id, cpuinfo_id) \ - if (!cpuinfo[cpuinfo_id].empty()) \ - { setInfo(llpi_id, cpuinfo[cpuinfo_id]);} - -#define LLPI_SET_INFO_INT(llpi_id, cpuinfo_id) \ - {\ - S32 result; \ - if (!cpuinfo[cpuinfo_id].empty() \ - && LLStringUtil::convertToS32(cpuinfo[cpuinfo_id], result)) \ - { setInfo(llpi_id, result);} \ + F64 mhzFromSys = getCPUMaxMHZ(); + F64 mhzFromProc {}; + if( !LLStringUtil::convertToF64(cpuinfo["cpu mhz"], mhzFromProc ) ) + mhzFromProc = 0.0; + if (mhzFromSys > 1.0 && mhzFromSys > mhzFromProc ) + { + setInfo( eFrequency, mhzFromSys ); } - - F64 mhz; - if (LLStringUtil::convertToF64(cpuinfo["cpu mhz"], mhz) - && 200.0 < mhz && mhz < 10000.0) + else if ( 200.0 < mhzFromProc && mhzFromProc < 10000.0) { - setInfo(eFrequency,(F64)(mhz)); + setInfo(eFrequency,(F64)(mhzFromProc)); } LLPI_SET_INFO_STRING(eBrandName, "model name"); @@ -867,7 +896,7 @@ private: LLPI_SET_INFO_INT(eModel, "model"); - S32 family; + S32 family{}; if (!cpuinfo["cpu family"].empty() && LLStringUtil::convertToS32(cpuinfo["cpu family"], family)) { diff --git a/indra/llcommon/llrand.cpp b/indra/llcommon/llrand.cpp index 2c51e6f07f..513613f543 100644 --- a/indra/llcommon/llrand.cpp +++ b/indra/llcommon/llrand.cpp @@ -58,9 +58,26 @@ * to restore uniform distribution. */ -// gRandomGenerator is a stateful static object, which is therefore not +// pRandomGenerator is a stateful static object, which is therefore not // inherently thread-safe. -static thread_local LLRandLagFib2281 gRandomGenerator(LLUUID::getRandomSeed()); +//We use a pointer to not construct a huge object in the TLS space, sadly this is necessary +// due to libcef.so on Linux being compiled with TLS model initial-exec (resulting in +// FLAG STATIC_TLS, see readelf libcef.so). CEFs own TLS objects + LLRandLagFib2281 then will exhaust the +// available TLS space, causing media failure. + +static thread_local std::unique_ptr< LLRandLagFib2281 > pRandomGenerator = nullptr; + +namespace { + F64 ll_internal_get_rand() + { + if( !pRandomGenerator ) + { + pRandomGenerator.reset(new LLRandLagFib2281(LLUUID::getRandomSeed( ) )); + } + + return(*pRandomGenerator)(); + } +} // no default implementation, only specific F64 and F32 specializations template <typename REAL> @@ -73,7 +90,7 @@ inline F64 ll_internal_random<F64>() // CPUs (or at least multi-threaded processes) seem to // occasionally give an obviously incorrect random number -- like // 5^15 or something. Sooooo, clamp it as described above. - F64 rv{ gRandomGenerator() }; + F64 rv{ ll_internal_get_rand() }; if(!((rv >= 0.0) && (rv < 1.0))) return fmod(rv, 1.0); return rv; } @@ -85,7 +102,7 @@ inline F32 ll_internal_random<F32>() // Per Monty, it's important to clamp using the correct fmodf() rather // than expanding to F64 for fmod() and then truncating back to F32. Prior // to this change, we were getting sporadic ll_frand() == 1.0 results. - F32 rv{ narrow<F64>(gRandomGenerator()) }; + F32 rv{ narrow<F64>(ll_internal_get_rand()) }; if(!((rv >= 0.0f) && (rv < 1.0f))) return fmodf(rv, 1.0f); return rv; } diff --git a/indra/llcommon/llrefcount.h b/indra/llcommon/llrefcount.h index 3a253d8fa6..707d825e53 100644 --- a/indra/llcommon/llrefcount.h +++ b/indra/llcommon/llrefcount.h @@ -29,6 +29,7 @@ #include <boost/noncopyable.hpp> #include <boost/intrusive_ptr.hpp> #include "llatomic.h" +#include "llerror.h" class LLMutex; diff --git a/indra/llcommon/llrun.h b/indra/llcommon/llrun.h index 70767572ff..adf1f49001 100644 --- a/indra/llcommon/llrun.h +++ b/indra/llcommon/llrun.h @@ -33,6 +33,17 @@ class LLRunnable; +////////////////////////////////////////////////////////////////////////////// +// DEPRECATION WARNING +// LLRunner is one of several mostly redundant ways to schedule future +// callbacks on the main thread. It seems to be unused in the current viewer. +// addRunnable() is only called by LLPumpIO::sleepChain(). +// sleepChain() is only called by LLIOSleeper and LLIOSleep. +// LLIOSleeper is referenced only by tests. +// LLIOSleep is only called by LLDeferredChain. +// LLDeferredChain isn't referenced at all. +////////////////////////////////////////////////////////////////////////////// + /** * @class LLRunner * @brief This class manages a set of LLRunnable objects. diff --git a/indra/llcommon/llsdjson.cpp b/indra/llcommon/llsdjson.cpp index 5d38e55686..1df2a8f9eb 100644 --- a/indra/llcommon/llsdjson.cpp +++ b/indra/llcommon/llsdjson.cpp @@ -61,12 +61,20 @@ LLSD LlsdFromJson(const boost::json::value& val) result = LLSD(val.as_bool()); break; case boost::json::kind::array: + { result = LLSD::emptyArray(); - for (const auto &element : val.as_array()) + auto& array = val.as_array(); + // allocate elements 0 .. (size() - 1) to avoid incremental allocation + if (! array.empty()) + { + result[array.size() - 1] = LLSD(); + } + for (const auto &element : array) { result.append(LlsdFromJson(element)); } break; + } case boost::json::kind::object: result = LLSD::emptyMap(); for (const auto& element : val.as_object()) @@ -106,6 +114,7 @@ boost::json::value LlsdToJson(const LLSD &val) case LLSD::TypeMap: { boost::json::object& obj = result.emplace_object(); + obj.reserve(val.size()); for (const auto& llsd_dat : llsd::inMap(val)) { obj[llsd_dat.first] = LlsdToJson(llsd_dat.second); @@ -115,6 +124,7 @@ boost::json::value LlsdToJson(const LLSD &val) case LLSD::TypeArray: { boost::json::array& json_array = result.emplace_array(); + json_array.reserve(val.size()); for (const auto& llsd_dat : llsd::inArray(val)) { json_array.push_back(LlsdToJson(llsd_dat)); @@ -123,7 +133,8 @@ boost::json::value LlsdToJson(const LLSD &val) } case LLSD::TypeBinary: default: - LL_ERRS("LlsdToJson") << "Unsupported conversion to JSON from LLSD type (" << val.type() << ")." << LL_ENDL; + LL_ERRS("LlsdToJson") << "Unsupported conversion to JSON from LLSD type (" + << val.type() << ")." << LL_ENDL; break; } diff --git a/indra/llcommon/llsdutil.cpp b/indra/llcommon/llsdutil.cpp index dbd89118c9..fda47c7a87 100644 --- a/indra/llcommon/llsdutil.cpp +++ b/indra/llcommon/llsdutil.cpp @@ -160,7 +160,7 @@ LLSD ll_binary_from_string(const LLSD& sd) char* ll_print_sd(const LLSD& sd) { const U32 bufferSize = 10 * 1024; - static char buffer[bufferSize]; + static char buffer[bufferSize + 1]; std::ostringstream stream; //stream.rdbuf()->pubsetbuf(buffer, bufferSize); stream << LLSDOStreamer<LLSDXMLFormatter>(sd); @@ -182,7 +182,7 @@ char* ll_pretty_print_sd_ptr(const LLSD* sd) char* ll_pretty_print_sd(const LLSD& sd) { const U32 bufferSize = 100 * 1024; - static char buffer[bufferSize]; + static char buffer[bufferSize + 1]; std::ostringstream stream; //stream.rdbuf()->pubsetbuf(buffer, bufferSize); stream << LLSDOStreamer<LLSDXMLFormatter>(sd, LLSDFormatter::OPTIONS_PRETTY); diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h index 38bbe19ddd..3ce0b1726d 100644 --- a/indra/llcommon/llsdutil.h +++ b/indra/llcommon/llsdutil.h @@ -365,15 +365,14 @@ private: // subject function has returned, so we must ensure that any constructed // LLSDParam<T> lives just as long as this LLSDParam<LLSD> does. Putting // each LLSDParam<T> on the heap and capturing a smart pointer in a vector - // works. We would have liked to use std::unique_ptr, but vector entries - // must be copyable. + // works. // (Alternatively we could assume that every instance of LLSDParam<LLSD> // will be asked for at most ONE conversion. We could store a scalar // std::unique_ptr and, when constructing an new LLSDParam<T>, assert that // the unique_ptr is empty. But some future change in usage patterns, and // consequent failure of that assertion, would be very mysterious. Instead // of explaining how to fix it, just fix it now.) - mutable std::vector<std::shared_ptr<LLSDParamBase>> converters_; + mutable std::vector<std::unique_ptr<LLSDParamBase>> converters_; public: LLSDParam(const LLSD& value): value_(value) {} @@ -389,9 +388,9 @@ public: { // capture 'ptr' with the specific subclass type because converters_ // only stores LLSDParamBase pointers - auto ptr{ std::make_shared<LLSDParam<std::decay_t<T>>>(value_) }; + auto ptr{ new LLSDParam<std::decay_t<T>>(value_) }; // keep the new converter alive until we ourselves are destroyed - converters_.push_back(ptr); + converters_.emplace_back(ptr); return *ptr; } }; @@ -474,12 +473,12 @@ public: } }; -namespace llsd -{ - /***************************************************************************** * range-based for-loop helpers for LLSD *****************************************************************************/ +namespace llsd +{ + /// Usage: for (LLSD item : inArray(someLLSDarray)) { ... } class inArray { @@ -525,7 +524,70 @@ private: } // namespace llsd +/***************************************************************************** +* LLSDParam<std::vector<T>> +*****************************************************************************/ +// Given an LLSD array, return a const std::vector<T>&, where T is a type +// supported by LLSDParam. Bonus: if the LLSD value is actually a scalar, +// return a single-element vector containing the converted value. +template <typename T> +class LLSDParam<std::vector<T>>: public LLSDParamBase +{ +public: + LLSDParam(const LLSD& array) + { + // treat undefined "array" as empty vector + if (array.isDefined()) + { + // what if it's a scalar? + if (! array.isArray()) + { + v.push_back(LLSDParam<T>(array)); + } + else // really is an array + { + // reserve space for the array entries + v.reserve(array.size()); + for (const auto& item : llsd::inArray(array)) + { + v.push_back(LLSDParam<T>(item)); + } + } + } + } + + operator const std::vector<T>&() const { return v; } + +private: + std::vector<T> v; +}; + +/***************************************************************************** +* LLSDParam<std::map<std::string, T>> +*****************************************************************************/ +// Given an LLSD map, return a const std::map<std::string, T>&, where T is a +// type supported by LLSDParam. +template <typename T> +class LLSDParam<std::map<std::string, T>>: public LLSDParamBase +{ +public: + LLSDParam(const LLSD& map) + { + for (const auto& pair : llsd::inMap(map)) + { + m[pair.first] = LLSDParam<T>(pair.second); + } + } + + operator const std::map<std::string, T>&() const { return m; } + +private: + std::map<std::string, T> m; +}; +/***************************************************************************** +* deep and shallow clone +*****************************************************************************/ // Creates a deep clone of an LLSD object. Maps, Arrays and binary objects // are duplicated, atomic primitives (Boolean, Integer, Real, etc) simply // use a shared reference. @@ -553,6 +615,60 @@ LLSD shallow(LLSD value, LLSD filter=LLSD()) { return llsd_shallow(value, filter } // namespace llsd +/***************************************************************************** +* toArray(), toMap() +*****************************************************************************/ +namespace llsd +{ + +// For some T convertible to LLSD, given std::vector<T> myVec, +// toArray(myVec) returns an LLSD array whose entries correspond to the +// items in myVec. +// For some U convertible to LLSD, given function U xform(const T&), +// toArray(myVec, xform) returns an LLSD array whose every entry is +// xform(item) of the corresponding item in myVec. +// toArray() actually works with any container<C> usable with range +// 'for', not just std::vector. +// (Once we get C++20 we can use std::identity instead of this default lambda.) +template <typename C, typename FUNC> +LLSD toArray(const C& container, FUNC&& func=[](const auto& arg){ return arg; }) +{ + LLSD array; + for (const auto& item : container) + { + array.append(std::forward<FUNC>(func)(item)); + } + return array; +} + +// For some T convertible to LLSD, given std::map<std::string, T> myMap, +// toMap(myMap) returns an LLSD map whose entries correspond to the +// (key, value) pairs in myMap. +// For some U convertible to LLSD, given function +// std::pair<std::string, U> xform(const std::pair<std::string, T>&), +// toMap(myMap, xform) returns an LLSD map whose every entry is +// xform(pair) of the corresponding (key, value) pair in myMap. +// toMap() actually works with any container usable with range 'for', not +// just std::map. It need not even be an associative container, as long as +// you pass an xform function that returns std::pair<std::string, U>. +// (Once we get C++20 we can use std::identity instead of this default lambda.) +template <typename C, typename FUNC> +LLSD toMap(const C& container, FUNC&& func=[](const auto& arg){ return arg; }) +{ + LLSD map; + for (const auto& pair : container) + { + const auto& [key, value] = std::forward<FUNC>(func)(pair); + map[key] = value; + } + return map; +} + +} // namespace llsd + +/***************************************************************************** +* boost::hash<LLSD> +*****************************************************************************/ // Specialization for generating a hash value from an LLSD block. namespace boost { diff --git a/indra/llcommon/llsingleton.cpp b/indra/llcommon/llsingleton.cpp index 05dc3cde79..2a38d6d896 100644 --- a/indra/llcommon/llsingleton.cpp +++ b/indra/llcommon/llsingleton.cpp @@ -27,11 +27,12 @@ #include "linden_common.h" #include "llsingleton.h" +#include "llcoros.h" +#include "lldependencies.h" #include "llerror.h" #include "llerrorcontrol.h" -#include "lldependencies.h" #include "llexception.h" -#include "llcoros.h" +#include "llmainthreadtask.h" #include <algorithm> #include <iostream> // std::cerr in dire emergency #include <sstream> @@ -270,17 +271,29 @@ void LLSingletonBase::reset_initializing(list_t::size_type size) void LLSingletonBase::MasterList::LockedInitializing::log(const char* verb, const char* name) { - LL_DEBUGS("LLSingleton") << verb << ' ' << demangle(name) << ';'; - if (mList) + LL_DEBUGS("LLSingleton") << verb << ' ' << demangle(name) << ';'; + if (mList) + { + for (list_t::const_reverse_iterator ri(mList->rbegin()), rend(mList->rend()); + ri != rend; ++ri) { - for (list_t::const_reverse_iterator ri(mList->rbegin()), rend(mList->rend()); - ri != rend; ++ri) - { - LLSingletonBase* sb(*ri); - LL_CONT << ' ' << classname(sb); - } + LLSingletonBase* sb(*ri); + LL_CONT << ' ' << classname(sb); } - LL_ENDL; + } + LL_ENDL; +} + +void LLSingletonBase::capture_dependency(LLSingletonBase* sb) +{ + // If we're called very late during application shutdown, the Boost.Fibers + // library may have shut down, and MasterList::mInitializing.get() might + // blow up. But if we're called that late, there's really no point in + // trying to capture this dependency. + if (boost::fibers::context::active()) + { + sb->capture_dependency(); + } } void LLSingletonBase::capture_dependency() @@ -484,3 +497,29 @@ std::string LLSingletonBase::demangle(const char* mangled) { return LLError::Log::demangle(mangled); } + +LLSingletonBase* LLSingletonBase::getInstanceForSecondaryThread( + const std::string& name, + const std::string& method, + const std::function<LLSingletonBase*()>& getInstance) +{ + // Normally it would be the height of folly to reference-bind args into a + // lambda to be executed on some other thread! By the time that thread + // executed the lambda, the references would all be dangling, and Bad + // Things would result. But LLMainThreadTask::dispatch() promises to block + // the calling thread until the passed task has completed. So in this case + // we know the references will remain valid until the lambda has run, so + // we dare to bind references. + return LLMainThreadTask::dispatch( + [&name, &method, &getInstance](){ + // VERY IMPORTANT to call getInstance() on the main thread, + // rather than going straight to constructSingleton()! + // During the time window before mInitState is INITIALIZED, + // multiple requests might be queued. It's essential that, as + // the main thread processes them, only the FIRST such request + // actually constructs the instance -- every subsequent one + // simply returns the existing instance. + loginfos({name, "::", method, " on main thread"}); + return getInstance(); + }); +} diff --git a/indra/llcommon/llsingleton.h b/indra/llcommon/llsingleton.h index b5659e053c..d3417a233e 100644 --- a/indra/llcommon/llsingleton.h +++ b/indra/llcommon/llsingleton.h @@ -25,18 +25,19 @@ #ifndef LLSINGLETON_H #define LLSINGLETON_H +#include <boost/call_traits.hpp> #include <boost/noncopyable.hpp> #include <boost/unordered_set.hpp> +#include <functional> #include <initializer_list> #include <list> #include <typeinfo> #include <vector> #include "mutex.h" #include "lockstatic.h" -#include "llthread.h" // on_main_thread() -#include "llmainthreadtask.h" +#include "apply.h" #include "llprofiler.h" -#include "llerror.h" +#include "llthread.h" // on_main_thread() #ifdef LL_WINDOWS #pragma warning(push) @@ -117,6 +118,8 @@ protected: // If a given call to B::getInstance() happens during either A::A() or // A::initSingleton(), record that A directly depends on B. void capture_dependency(); + // trampoline to non-static member function + static void capture_dependency(LLSingletonBase*); // delegate logging calls to llsingleton.cpp public: @@ -141,6 +144,17 @@ protected: // internal wrapper around calls to cleanupSingleton() void cleanup_(); + // This method is where we dispatch to LLMainThreadTask to acquire the + // subclass LLSingleton instance when the first getInstance() call is from + // a secondary thread. We delegate to the .cpp file to untangle header + // circularity. It accepts a std::function referencing the subclass + // getInstance() method -- which can't be virtual because it's static; we + // don't yet have an instance! For messaging, it also accepts the name of + // the subclass and the subclass method. + static LLSingletonBase* getInstanceForSecondaryThread( + const std::string& name, const std::string& method, + const std::function<LLSingletonBase*()>& getInstance); + // deleteSingleton() isn't -- and shouldn't be -- a virtual method. It's a // class static. However, given only Foo*, deleteAll() does need to be // able to reach Foo::deleteSingleton(). Make LLSingleton (which declares @@ -196,7 +210,7 @@ struct LLSingleton_manage_master } void capture_dependency(LLSingletonBase* sb) { - sb->capture_dependency(); + LLSingletonBase::capture_dependency(sb); } }; @@ -428,6 +442,11 @@ protected: // Remove this instance from the master list. LLSingleton_manage_master<DERIVED_TYPE>().remove(this); + // We should no longer need our LockStatic -- but the fact that we can + // query or even resurrect a deleted LLSingleton means we basically + // have to shrug and leak our SingletonData. It's not large, and this + // only happens at shutdown anyway. +// lk.cleanup(); } public: @@ -563,19 +582,11 @@ public: // Per the comment block above, dispatch to the main thread. loginfos({classname<DERIVED_TYPE>(), "::getInstance() dispatching to main thread"}); - auto instance = LLMainThreadTask::dispatch( - [](){ - // VERY IMPORTANT to call getInstance() on the main thread, - // rather than going straight to constructSingleton()! - // During the time window before mInitState is INITIALIZED, - // multiple requests might be queued. It's essential that, as - // the main thread processes them, only the FIRST such request - // actually constructs the instance -- every subsequent one - // simply returns the existing instance. - loginfos({classname<DERIVED_TYPE>(), - "::getInstance() on main thread"}); - return getInstance(); - }); + auto instance = static_cast<DERIVED_TYPE*>( + getInstanceForSecondaryThread( + classname<DERIVED_TYPE>(), + "getInstance()", + getInstance)); // record the dependency chain tracked on THIS thread, not the main // thread (consider a getInstance() overload with a tag param that // suppresses dep tracking when dispatched to the main thread) @@ -640,8 +651,14 @@ private: // Passes arguments to DERIVED_TYPE's constructor and sets appropriate // states, returning a pointer to the new instance. + // If we just let initParamSingleton_() infer its argument types, the + // compiler has trouble passing int and string literals. Use + // boost::call_traits::param_type to smooth parameter passing. This + // construction requires, though, that each invocation of this method + // explicitly specify template arguments, instead of inferring them. template <typename... Args> - static DERIVED_TYPE* initParamSingleton_(Args&&... args) + static LLSingletonBase* initParamSingleton_( + typename boost::call_traits<Args>::param_type... args) { // In case racing threads both call initParamSingleton() at the same // time, serialize them. One should initialize; the other should see @@ -660,7 +677,7 @@ private: // on the main thread, simply construct instance while holding lock super::logdebugs({super::template classname<DERIVED_TYPE>(), "::initParamSingleton()"}); - super::constructSingleton(lk, std::forward<Args>(args)...); + super::constructSingleton(lk, args...); return lk->mInstance; } else @@ -673,20 +690,19 @@ private: lk.unlock(); super::loginfos({super::template classname<DERIVED_TYPE>(), "::initParamSingleton() dispatching to main thread"}); - // Normally it would be the height of folly to reference-bind - // 'args' into a lambda to be executed on some other thread! By - // the time that thread executed the lambda, the references would - // all be dangling, and Bad Things would result. But - // LLMainThreadTask::dispatch() promises to block until the passed - // task has completed. So in this case we know the references will - // remain valid until the lambda has run, so we dare to bind - // references. - auto instance = LLMainThreadTask::dispatch( - [&](){ - super::loginfos({super::template classname<DERIVED_TYPE>(), - "::initParamSingleton() on main thread"}); - return initParamSingleton_(std::forward<Args>(args)...); - }); + auto instance = static_cast<DERIVED_TYPE*>( + super::getInstanceForSecondaryThread( + super::template classname<DERIVED_TYPE>(), + "initParamSingleton()", + // This lambda does what std::bind() is supposed to do -- + // but when the actual parameter is (e.g.) a string + // literal, type inference makes it fail. Apply param_type + // to each incoming type to make it work. + [args=std::tuple<typename boost::call_traits<Args>::param_type...>(args...)] + () + { + return LL::apply(initParamSingleton_<Args...>, args); + })); super::loginfos({super::template classname<DERIVED_TYPE>(), "::initParamSingleton() returning on requesting thread"}); return instance; @@ -703,7 +719,7 @@ public: template <typename... Args> static DERIVED_TYPE& initParamSingleton(Args&&... args) { - return *initParamSingleton_(std::forward<Args>(args)...); + return *static_cast<DERIVED_TYPE*>(initParamSingleton_<Args...>(args...)); } static DERIVED_TYPE* getInstance() diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 07adf71d18..447fa2d9dd 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -31,6 +31,7 @@ #include "llfasttimer.h" #include "llsd.h" #include <vector> +#include <sstream> #if LL_WINDOWS #include "llwin32headers.h" @@ -140,10 +141,10 @@ std::string rawstr_to_utf8(const std::string& raw) return wstring_to_utf8str(wstr); } -std::ptrdiff_t wchar_to_utf8chars(llwchar in_char, char* outchars) +std::string wchar_to_utf8chars(llwchar in_char) { - U32 cur_char = (U32)in_char; - char* base = outchars; + U32 cur_char(in_char); + char buff[8], *outchars = buff; if (cur_char < 0x80) { *outchars++ = (U8)cur_char; @@ -188,7 +189,7 @@ std::ptrdiff_t wchar_to_utf8chars(llwchar in_char, char* outchars) LL_WARNS() << "Invalid Unicode character " << cur_char << "!" << LL_ENDL; *outchars++ = LL_UNKNOWN_CHAR; } - return outchars - base; + return { buff, std::string::size_type(outchars - buff) }; } auto utf16chars_to_wchar(const U16* inchars, llwchar* outchar) @@ -213,7 +214,8 @@ auto utf16chars_to_wchar(const U16* inchars, llwchar* outchar) llutf16string wstring_to_utf16str(const llwchar* utf32str, size_t len) { - llutf16string out; + // ostringstream for llutf16string + std::basic_ostringstream<U16> out; S32 i = 0; while (i < len) @@ -221,16 +223,16 @@ llutf16string wstring_to_utf16str(const llwchar* utf32str, size_t len) U32 cur_char = utf32str[i]; if (cur_char > 0xFFFF) { - out += (0xD7C0 + (cur_char >> 10)); - out += (0xDC00 | (cur_char & 0x3FF)); + out.put(U16(0xD7C0 + (cur_char >> 10))); + out.put(U16(0xDC00 | (cur_char & 0x3FF))); } else { - out += cur_char; + out.put(U16(cur_char)); } i++; } - return out; + return out.str(); } llutf16string utf8str_to_utf16str( const char* utf8str, size_t len ) @@ -241,8 +243,16 @@ llutf16string utf8str_to_utf16str( const char* utf8str, size_t len ) LLWString utf16str_to_wstring(const U16* utf16str, size_t len) { - LLWString wout; - if (len == 0) return wout; + if (len == 0) return {}; + + // MS doesn't support std::basic_ostringstream<llwchar>; have to work + // around it. + std::vector<llwchar> wout; + // We want to minimize allocations. We don't know how many llwchars we'll + // generate from this utf16str, but we do know the length should be at + // most len. So if we reserve 'len' llwchars, we shouldn't need to expand + // wout incrementally. + wout.reserve(len); S32 i = 0; const U16* chars16 = utf16str; @@ -250,9 +260,9 @@ LLWString utf16str_to_wstring(const U16* utf16str, size_t len) { llwchar cur_char; i += (S32)utf16chars_to_wchar(chars16+i, &cur_char); - wout += cur_char; + wout.push_back(cur_char); } - return wout; + return { wout.begin(), wout.end() }; } // Length in llwchar (UTF-32) of the first len units (16 bits) of the given UTF-16 string. @@ -366,13 +376,12 @@ std::string wchar_utf8_preview(const llwchar wc) std::ostringstream oss; oss << std::hex << std::uppercase << (U32)wc; - U8 out_bytes[8]; - U32 size = (U32)wchar_to_utf8chars(wc, (char*)out_bytes); + auto out_bytes = wchar_to_utf8chars(wc); - if (size > 1) + if (out_bytes.length() > 1) { oss << " ["; - for (U32 i = 0; i < size; ++i) + for (U32 i = 0; i < out_bytes.length(); ++i) { if (i) { @@ -398,7 +407,14 @@ S32 wstring_utf8_length(const LLWString& wstr) LLWString utf8str_to_wstring(const char* utf8str, size_t len) { - LLWString wout; + // MS doesn't support std::basic_ostringstream<llwchar>; have to work + // around it. + std::vector<llwchar> wout; + // We want to minimize allocations. We don't know how many llwchars we'll + // generate from this utf8str, but we do know the length should be at most + // len. So if we reserve 'len' llwchars, we shouldn't need to expand wout + // incrementally. + wout.reserve(len); S32 i = 0; while (i < len) @@ -441,7 +457,7 @@ LLWString utf8str_to_wstring(const char* utf8str, size_t len) } else { - wout += LL_UNKNOWN_CHAR; + wout.push_back(LL_UNKNOWN_CHAR); ++i; continue; } @@ -478,26 +494,21 @@ LLWString utf8str_to_wstring(const char* utf8str, size_t len) } } - wout += unichar; + wout.push_back(unichar); ++i; } - return wout; + return { wout.begin(), wout.end() }; } std::string wstring_to_utf8str(const llwchar* utf32str, size_t len) { - std::string out; + std::ostringstream out; - S32 i = 0; - while (i < len) + for (size_t i = 0; i < len; ++i) { - char tchars[8]; /* Flawfinder: ignore */ - auto n = wchar_to_utf8chars(utf32str[i], tchars); - tchars[n] = 0; - out += tchars; - i++; + out << wchar_to_utf8chars(utf32str[i]); } - return out; + return out.str(); } std::string utf16str_to_utf8str(const U16* utf16str, size_t len) @@ -685,7 +696,21 @@ llwchar utf8str_to_wchar(const std::string& utf8str, size_t offset, size_t lengt std::string utf8str_showBytesUTF8(const std::string& utf8str) { - std::string result; + std::ostringstream result; + char lastchar = '\0'; + auto append = [&result, &lastchar](char c) + { + lastchar = c; + result << c; + }; + auto appends = [&result, &lastchar](const std::string& s) + { + if (! s.empty()) + { + lastchar = s.back(); + result << s; + } + }; bool in_sequence = false; size_t sequence_size = 0; @@ -694,9 +719,9 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) auto open_sequence = [&]() { - if (!result.empty() && result.back() != '\n') - result += '\n'; // Use LF as a separator before new UTF-8 sequence - result += '['; + if (lastchar != '\0' && lastchar != '\n') + append('\n'); // Use LF as a separator before new UTF-8 sequence + append('['); in_sequence = true; }; @@ -705,9 +730,9 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) llwchar unicode = utf8str_to_wchar(utf8str, byte_index - sequence_size, sequence_size); if (unicode != LL_UNKNOWN_CHAR) { - result += llformat("+%04X", unicode); + appends(llformat("+%04X", unicode)); } - result += ']'; + append(']'); in_sequence = false; sequence_size = 0; }; @@ -728,9 +753,9 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) } else // Continue the same UTF-8 sequence { - result += '.'; + append('.'); } - result += llformat("%02X", byte); // The byte is represented in hexadecimal form + appends(llformat("%02X", byte)); // The byte is represented in hexadecimal form ++sequence_size; } else // ASCII symbol is represented as a character @@ -740,10 +765,10 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) close_sequence(); if (byte != '\n') { - result += '\n'; // Use LF as a separator between UTF-8 and ASCII + append('\n'); // Use LF as a separator between UTF-8 and ASCII } } - result += byte; + append(byte); } ++byte_index; } @@ -753,7 +778,7 @@ std::string utf8str_showBytesUTF8(const std::string& utf8str) close_sequence(); } - return result; + return result.str(); } // Search for any emoji symbol, return true if found @@ -1594,7 +1619,7 @@ S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) LL_PROFILE_ZONE_SCOPED_CATEGORY_STRING; S32 res = 0; - std::string output; + std::ostringstream output; std::vector<std::string> tokens; std::string::size_type start = 0; @@ -1602,7 +1627,7 @@ S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) std::string::size_type key_start = 0; while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) { - output += std::string(s, prev_start, key_start-prev_start); + output << std::string(s, prev_start, key_start-prev_start); prev_start = start; bool found_replacement = false; @@ -1643,20 +1668,20 @@ S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) if (found_replacement) { - output += replacement; + output << replacement; res++; } else { // we had no replacement, use the string as is // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" - output += std::string(s, key_start, start-key_start); + output << std::string(s, key_start, start-key_start); } tokens.clear(); } // send the remainder of the string (with no further matches for bracketed names) - output += std::string(s, start); - s = output; + output << std::string(s, start); + s = output.str(); return res; } @@ -1672,7 +1697,7 @@ S32 LLStringUtil::format(std::string& s, const LLSD& substitutions) return res; } - std::string output; + std::ostringstream output; std::vector<std::string> tokens; std::string::size_type start = 0; @@ -1680,7 +1705,7 @@ S32 LLStringUtil::format(std::string& s, const LLSD& substitutions) std::string::size_type key_start = 0; while ((key_start = getSubstitution(s, start, tokens)) != std::string::npos) { - output += std::string(s, prev_start, key_start-prev_start); + output << std::string(s, prev_start, key_start-prev_start); prev_start = start; bool found_replacement = false; @@ -1713,20 +1738,20 @@ S32 LLStringUtil::format(std::string& s, const LLSD& substitutions) if (found_replacement) { - output += replacement; + output << replacement; res++; } else { // we had no replacement, use the string as is // e.g. "hello [MISSING_REPLACEMENT]" or "-=[Stylized Name]=-" - output += std::string(s, key_start, start-key_start); + output << std::string(s, key_start, start-key_start); } tokens.clear(); } // send the remainder of the string (with no further matches for bracketed names) - output += std::string(s, start); - s = output; + output << std::string(s, start); + s = output.str(); return res; } diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index db716b1431..d65fb16f5b 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -38,7 +38,9 @@ #include <algorithm> #include <vector> #include <map> +#include <type_traits> #include "llformat.h" +#include "stdtypes.h" #if LL_LINUX #include <wctype.h> @@ -313,6 +315,14 @@ public: static void trim(string_type& string) { trimHead(string); trimTail(string); } static void truncate(string_type& string, size_type count); + // if string startsWith prefix, remove it and return true + static bool removePrefix(string_type& string, const string_type& prefix); + // if string startsWith prefix, return (string without prefix, true), else (string, false) + static std::pair<string_type, bool> withoutPrefix(const string_type& string, const string_type& prefix); + // like removePrefix() + static bool removeSuffix(string_type& string, const string_type& suffix); + static std::pair<string_type, bool> withoutSuffix(const string_type& string, const string_type& suffix); + static void toUpper(string_type& string); static void toLower(string_type& string); @@ -521,11 +531,52 @@ struct ll_convert_impl TO operator()(const FROM& in) const; }; -// Use a function template to get the nice ll_convert<TO>(from_value) API. -template<typename TO, typename FROM> -TO ll_convert(const FROM& in) +/** + * somefunction(ll_convert(data)) + * target = ll_convert(data) + * totype otherfunc(const fromtype& data) + * { + * // ... + * return ll_convert(data); + * } + * all infer both the FROM type and the TO type. + */ +template <typename FROM> +class ll_convert { - return ll_convert_impl<TO, FROM>()(in); +private: + const FROM& mRef; + +public: + ll_convert(const FROM& ref): mRef(ref) {} + + inline operator const FROM&() const + { + return mRef; + } + + template <typename TO, + std::enable_if_t<! std::is_same_v<std::decay_t<TO>, std::decay_t<FROM>>, bool> =true> + inline operator TO() const + { + return ll_convert_impl<TO, std::decay_t<const FROM>>()(mRef); + } +}; + +// When the TO type must be explicit, use a function template to get +// ll_convert_to<TO>(from_value) API. +template<typename SAME> +const SAME& ll_convert_to(const SAME& in) +{ + return in; +} + +template<typename TO, + typename FROM, + std::enable_if_t<! std::is_same_v<std::decay_t<TO>, std::decay_t<FROM>>, bool> =true> +TO ll_convert_to(const FROM& in) +{ + return ll_convert_impl<TO, std::decay_t<const FROM>>()(in); } // degenerate case @@ -579,8 +630,8 @@ inline size_t ll_convert_length<char> (const char* zstr) { return std::strl // and longname(const string&, len) so calls written pre-ll_convert() will // work. Most of these overloads will be unified once we turn on C++17 and can // use std::string_view. -// It also uses aliasmacro to ensure that both ll_convert<OUTSTR>(const char*) -// and ll_convert<OUTSTR>(const string&) will work. +// It also uses aliasmacro to ensure that both ll_convert(const char*) +// and ll_convert(const string&) will work. #define ll_convert_forms(aliasmacro, OUTSTR, INSTR, longname) \ LL_COMMON_API OUTSTR longname(const INSTR::value_type* in, size_t len); \ inline auto longname(const INSTR& in, size_t len) \ @@ -670,7 +721,11 @@ ll_convert_forms(ll_convert_alias, LLWString, std::string, utf8str_to_ // Same function, better name. JC inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); } -LL_COMMON_API std::ptrdiff_t wchar_to_utf8chars(llwchar inchar, char* outchars); +// return a UTF-8 string representation of a single llwchar, which we +// occasionally require: +// cheaper than ll_convert_to<std::string>(LLWString(1, inchar)) +LL_COMMON_API std::string wchar_to_utf8chars(llwchar inchar); +ll_convert_alias(std::string, llwchar, wchar_to_utf8chars(in)); ll_convert_forms(ll_convert_alias, std::string, LLWString, wstring_to_utf8str); ll_convert_forms(ll_convert_u16_alias, std::string, llutf16string, utf16str_to_utf8str); @@ -823,7 +878,7 @@ LL_COMMON_API std::string ll_convert_string_to_utf8_string(const std::string& in template<typename STRING> STRING windows_message(unsigned long error) { - return ll_convert<STRING>(windows_message<std::wstring>(error)); + return ll_convert(windows_message<std::wstring>(error)); } /// There's only one real implementation @@ -1467,6 +1522,60 @@ void LLStringUtilBase<T>::trimTail(string_type& string) } } +// if string startsWith prefix, remove it and return true +template<class T> +bool LLStringUtilBase<T>::removePrefix(string_type& string, const string_type& prefix) +{ + bool found{ startsWith(string, prefix) }; + if (found) + { + string.erase(0, prefix.length()); + } + return found; +} + +// if string startsWith prefix, return (string without prefix, true), else (string, false) +template<class T> +std::pair<typename LLStringUtilBase<T>::string_type, bool> +LLStringUtilBase<T>::withoutPrefix(const string_type& string, const string_type& prefix) +{ + bool found{ startsWith(string, prefix) }; + if (! found) + { + return { string, false }; + } + else + { + return { string.substr(prefix.length()), true }; + } +} + +// like removePrefix() +template<class T> +bool LLStringUtilBase<T>::removeSuffix(string_type& string, const string_type& suffix) +{ + bool found{ endsWith(string, suffix) }; + if (found) + { + string.erase(string.length() - suffix.length()); + } + return found; +} + +template<class T> +std::pair<typename LLStringUtilBase<T>::string_type, bool> +LLStringUtilBase<T>::withoutSuffix(const string_type& string, const string_type& suffix) +{ + bool found{ endsWith(string, suffix) }; + if (! found) + { + return { string, false }; + } + else + { + return { string.substr(0, string.length() - suffix.length()), true }; + } +} // Replace line feeds with carriage return-line feed pairs. //static @@ -1835,7 +1944,7 @@ auto LLStringUtilBase<T>::getoptenv(const std::string& key) -> std::optional<str if (found) { // return populated std::optional - return { ll_convert<string_type>(*found) }; + return { ll_convert_to<string_type>(*found) }; } else { diff --git a/indra/llcommon/llsys.cpp b/indra/llcommon/llsys.cpp index 3f33ad61c5..7e69ddf6fc 100644 --- a/indra/llcommon/llsys.cpp +++ b/indra/llcommon/llsys.cpp @@ -504,57 +504,46 @@ const S32 LLOSInfo::getOSBitness() const return mOSBitness; } -//static -U32 LLOSInfo::getProcessVirtualSizeKB() -{ - U32 virtual_size = 0; -#if LL_LINUX -# define STATUS_SIZE 2048 - LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); - if (status_filep) - { - S32 numRead = 0; - char buff[STATUS_SIZE]; /* Flawfinder: ignore */ +namespace { - size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); - buff[nbytes] = '\0'; + U32 readFromProcStat( std::string entryName ) + { + U32 val{}; +#if LL_LINUX + constexpr U32 STATUS_SIZE = 2048; - // All these guys return numbers in KB - char *memp = strstr(buff, "VmSize:"); - if (memp) + LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); + if (status_filep) { - numRead += sscanf(memp, "%*s %u", &virtual_size); + char buff[STATUS_SIZE]; /* Flawfinder: ignore */ + + size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); + buff[nbytes] = '\0'; + + // All these guys return numbers in KB + char *memp = strstr(buff, entryName.c_str()); + if (memp) + { + (void) sscanf(memp, "%*s %u", &val); + } + fclose(status_filep); } - fclose(status_filep); - } #endif - return virtual_size; + return val; + } + } //static -U32 LLOSInfo::getProcessResidentSizeKB() +U32 LLOSInfo::getProcessVirtualSizeKB() { - U32 resident_size = 0; -#if LL_LINUX - LLFILE* status_filep = LLFile::fopen("/proc/self/status", "rb"); - if (status_filep != NULL) - { - S32 numRead = 0; - char buff[STATUS_SIZE]; /* Flawfinder: ignore */ - - size_t nbytes = fread(buff, 1, STATUS_SIZE-1, status_filep); - buff[nbytes] = '\0'; + return readFromProcStat( "VmSize:" ); +} - // All these guys return numbers in KB - char *memp = strstr(buff, "VmRSS:"); - if (memp) - { - numRead += sscanf(memp, "%*s %u", &resident_size); - } - fclose(status_filep); - } -#endif - return resident_size; +//static +U32 LLOSInfo::getProcessResidentSizeKB() +{ + return readFromProcStat( "VmRSS:" ); } //static @@ -1118,6 +1107,14 @@ LLSD LLMemoryInfo::loadStatsMap() LLSD::String key(matched[1].first, matched[1].second); LLSD::String value_str(matched[2].first, matched[2].second); LLSD::Integer value(0); + + // Skip over VmallocTotal. It's just a fixed and huge number on (modern) systems. "34359738367 kB" + // https://unix.stackexchange.com/questions/700724/why-is-vmalloctotal-34359738367-kb + // If not skipped converting it to a LLSD::integer (32 bit) will fail and spam the logs (this function + // is called quite frequently). + if( key == "VmallocTotal") + continue; + try { value = boost::lexical_cast<LLSD::Integer>(value_str); diff --git a/indra/llcommon/llthread.h b/indra/llcommon/llthread.h index 4194e0014d..21264351e5 100644 --- a/indra/llcommon/llthread.h +++ b/indra/llcommon/llthread.h @@ -68,7 +68,7 @@ public: // Called from MAIN THREAD. void pause(); void unpause(); - bool isPaused() { return isStopped() || mPaused; } + bool isPaused() const { return isStopped() || mPaused; } // Cause the thread to wake up and check its condition void wake(); diff --git a/indra/llcommon/lockstatic.cpp b/indra/llcommon/lockstatic.cpp new file mode 100755 index 0000000000..f531ef331e --- /dev/null +++ b/indra/llcommon/lockstatic.cpp @@ -0,0 +1,26 @@ +/** + * @file lockstatic.cpp + * @author Nat Goodspeed + * @date 2024-05-23 + * @brief Implementation for lockstatic. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lockstatic.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llerror.h" +#include "stringize.h" + +void llthread::LockStaticBase::throwDead(const char* mangled) +{ + LLTHROW(Dead(stringize(LLError::Log::demangle(mangled), " called after cleanup()"))); +} diff --git a/indra/llcommon/lockstatic.h b/indra/llcommon/lockstatic.h index 7cc9b7eec0..e83957b1fd 100644 --- a/indra/llcommon/lockstatic.h +++ b/indra/llcommon/lockstatic.h @@ -14,21 +14,36 @@ #define LL_LOCKSTATIC_H #include "mutex.h" // std::unique_lock +#include "llexception.h" +#include <typeinfo> namespace llthread { +class LockStaticBase +{ +public: + // trying to lock Static after cleanup() has been called + struct Dead: public LLException + { + Dead(const std::string& what): LLException(what) {} + }; + +protected: + static void throwDead(const char* mangled); +}; + // Instantiate this template to obtain a pointer to the canonical static // instance of Static while holding a lock on that instance. Use of // Static::mMutex presumes that Static declares some suitable mMutex. template <typename Static> -class LockStatic +class LockStatic: public LockStaticBase { typedef std::unique_lock<decltype(Static::mMutex)> lock_t; public: LockStatic(): mData(getStatic()), - mLock(mData->mMutex) + mLock(getLock(mData)) {} Static* get() const { return mData; } operator Static*() const { return get(); } @@ -40,31 +55,69 @@ public: mData = nullptr; mLock.unlock(); } + // explicit destruction + // We used to store a static instance of Static in getStatic(). The + // trouble with that is that at some point during final termination + // cleanup, the compiler calls ~Static(), destroying the mutex. If some + // later static object's destructor tries to lock our Static, we blow up + // trying to lock a destroyed mutex object. This can happen, for instance, + // if some class's destructor tries to reference an LLSingleton. + // Since a plain dumb pointer has no destructor, the compiler leaves it + // alone, so the referenced heap Static instance can survive until we + // explicitly call this method. + void cleanup() + { + // certainly don't claim to lock after this point! + mData = nullptr; + Static*& ptrref{ getStatic() }; + Static* ptr{ ptrref }; + ptrref = nullptr; + delete ptr; + } protected: Static* mData; lock_t mLock; private: - Static* getStatic() + static lock_t getLock(Static* data) + { + // data can be false if cleanup() has already been called. If so, no + // code in the caller is valid that depends on this instance. We dare + // to throw an exception because trying to lock Static after it's been + // deleted is not part of normal processing. There are callers who + // want to handle this exception, but it should indeed be treated as + // exceptional. + if (! data) + { + throwDead(typeid(LockStatic<Static>).name()); + } + // Usual case: data isn't nullptr, carry on. + return lock_t(data->mMutex); + } + + Static*& getStatic() { - // Static::mMutex must be function-local static rather than class- - // static. Some of our consumers must function properly (therefore - // lock properly) even when the containing module's static variables - // have not yet been runtime-initialized. A mutex requires + // Our Static instance must be function-local static rather than + // class-static. Some of our consumers must function properly + // (therefore lock properly) even when the containing module's static + // variables have not yet been runtime-initialized. A mutex requires // construction. A static class member might not yet have been // constructed. // - // We could store a dumb mutex_t*, notice when it's NULL and allocate a - // heap mutex -- but that's vulnerable to race conditions. And we can't - // defend the dumb pointer with another mutex. + // We could store a dumb mutex_t* class member, notice when it's NULL + // and allocate a heap mutex -- but that's vulnerable to race + // conditions. And we can't defend the dumb pointer with another + // mutex. // // We could store a std::atomic<mutex_t*> -- but a default-constructed // std::atomic<T> does not contain a valid T, even a default-constructed // T! Which means std::atomic, too, requires runtime initialization. // // But a function-local static is guaranteed to be initialized exactly - // once: the first time control reaches that declaration. - static Static sData; - return &sData; + // once: the first time control reaches that declaration. Importantly, + // since a plain dumb pointer has no destructor, the compiler lets our + // heap Static instance survive until someone calls cleanup() (above). + static Static* sData{ new Static }; + return sData; } }; diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp new file mode 100644 index 0000000000..33666964f7 --- /dev/null +++ b/indra/llcommon/lua_function.cpp @@ -0,0 +1,1722 @@ +/** + * @file lua_function.cpp + * @author Nat Goodspeed + * @date 2024-02-05 + * @brief Implementation for lua_function. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lua_function.h" +// STL headers +// std headers +#include <algorithm> +#include <exception> +#include <iomanip> // std::quoted +#include <map> +#include <memory> // std::unique_ptr +#include <typeinfo> +#include <unordered_map> +// external library headers +// other Linden headers +#include "commoncontrol.h" +#include "fsyspath.h" +#include "hexdump.h" +#include "llcoros.h" +#include "lleventcoro.h" +#include "llsd.h" +#include "llsdutil.h" +#include "llstring.h" +#include "lualistener.h" +#include "stringize.h" + +using namespace std::literals; // e.g. std::string_view literals: "this"sv + +const S32 INTERRUPTS_MAX_LIMIT = 100000; +const S32 INTERRUPTS_SUSPEND_LIMIT = 100; + +#define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n))) +#define lua_rawlen lua_objlen + +int DistinctInt::mValues{0}; + +/***************************************************************************** +* lluau namespace +*****************************************************************************/ +namespace +{ + // can't specify free function free() as a unique_ptr deleter + struct freer + { + void operator()(void* ptr){ free(ptr); } + }; +} // anonymous namespace + +namespace lluau +{ + +int dostring(lua_State* L, const std::string& desc, const std::string& text, + const std::vector<std::string>& args) +{ + // debug.traceback() + compiled chunk + args table + args... + slop + lluau_checkstack(L, 1 + 1 + 1 + int(args.size()) + 2); + auto r = loadstring(L, desc, text); + if (r != LUA_OK) + return r; + + // Push debug.traceback() onto the stack as lua_pcall()'s error + // handler function. On error, lua_pcall() calls the specified error + // handler function with the original error message; the message + // returned by the error handler is then returned by lua_pcall(). + // Luau's debug.traceback() is called with a message to prepend to the + // returned traceback string. Almost as if they'd been designed to + // work together... + lua_getglobal(L, "debug"); + lua_getfield(L, -1, "traceback"); + // ditch "debug" + lua_remove(L, -2); + // stack: compiled chunk, debug.traceback() + lua_insert(L, -2); + // stack: debug.traceback(), compiled chunk + // capture absolute index of debug.traceback() + int traceback = lua_absindex(L, -2); + // remove it from stack on exit + LuaRemover cleanup(L, traceback); + + // Originally we just pushed 'args' to the Lua stack before entering the + // chunk. But that's awkward for the chunk: it must reference those + // arguments as '...', using any of a number of tactics to move them to + // named variables. This doesn't work as a Lua chunk expecting arguments: + // function(a, b, c) + // -- ... + // end + // because that code only *defines* a function: the function's body isn't + // entered by executing the chunk. + // + // Per https://www.lua.org/manual/5.1/manual.html#6 Lua Stand-alone, we + // now also create a global table called 'arg' whose [0] is the script + // name, ['n'] is the number of additional arguments and [1] through + // [['n']] are the additional arguments. We diverge from that spec in not + // creating any negative indices. + // + // Since the spec notes that the chunk can also reference args using + // '...', we also leave them on the stack. + + // stack: debug.traceback(), compiled chunk + // create arg table pre-sized to hold the args array, plus [0] and ['n'] + lua_createtable(L, narrow(args.size()), 2); + // stack: debug.traceback(), compiled chunk, arg table + int argi = lua_absindex(L, -1); + lua_Integer i = 0; + // store desc (e.g. script name) as arg[0] + lua_pushinteger(L, i); + lua_pushstdstring(L, desc); + lua_rawset(L, argi); // rawset() pops key and value + // store args.size() as arg.n + lua_pushinteger(L, narrow(args.size())); + lua_setfield(L, argi, "n"); // setfield() pops value + for (const auto& arg : args) + { + // push each arg in order + lua_pushstdstring(L, arg); + // push index + lua_pushinteger(L, ++i); + // duplicate arg[i] to store in arg table + lua_pushvalue(L, -2); + // stack: ..., arg[i], i, arg[i] + lua_rawset(L, argi); + // leave ..., arg[i] on stack + } + // stack: debug.traceback(), compiled chunk, arg, arg[1], arg[2], ... + // duplicate the arg table to store it + lua_pushvalue(L, argi); + lua_setglobal(L, "arg"); + lua_remove(L, argi); + // stack: debug.traceback(), compiled chunk, arg[1], arg[2], ... + + // It's important to pass LUA_MULTRET as the expected number of return + // values: if we pass any fixed number, we discard any returned values + // beyond that number. + return lua_pcall(L, int(args.size()), LUA_MULTRET, traceback); +} + +int loadstring(lua_State *L, const std::string &desc, const std::string &text) +{ + lluau_checkstack(L, 1); + size_t bytecodeSize = 0; + // The char* returned by luau_compile() must be freed by calling free(). + // Use unique_ptr so the memory will be freed even if luau_load() throws. + std::unique_ptr<char[], freer> bytecode{ + luau_compile(text.data(), text.length(), nullptr, &bytecodeSize)}; + return luau_load(L, desc.data(), bytecode.get(), bytecodeSize, 0); +} + +fsyspath source_path(lua_State* L) +{ + //Luau lua_Debug and lua_getinfo() are different compared to default Lua: + //see https://github.com/luau-lang/luau/blob/80928acb92d1e4b6db16bada6d21b1fb6fa66265/VM/include/lua.h + // In particular: + // passing level=1 gets you info about the deepest function call + // passing level=lua_stackdepth() gets you info about the topmost script + // Empirically, lua_getinfo(level > 1) behaves strangely (including + // crashing the program) unless you iterate from 1 to desired level. + lua_Debug ar{}; + for (int i(0), depth(lua_stackdepth(L)); i <= depth; ++i) + { + lua_getinfo(L, i, "s", &ar); + } + return ar.source; +} + +} // namespace lluau + +/***************************************************************************** +* lua_destroyuserdata(), lua_destroybounduserdata() (see lua_emplace<T>()) +*****************************************************************************/ +int lua_destroyuserdata(lua_State* L) +{ + // stack: lua_emplace() userdata to be destroyed + if (int tag; + lua_isuserdata(L, -1) && + (tag = lua_userdatatag(L, -1)) != 0) + { + auto dtor = lua_getuserdatadtor(L, tag); + // detach this userdata from the destructor with tag 'tag' + lua_setuserdatatag(L, -1, 0); + // now run the real destructor + dtor(L, lua_touserdata(L, -1)); + } + lua_settop(L, 0); + return 0; +} + +int lua_destroybounduserdata(lua_State *L) +{ + // called with no arguments -- push bound upvalue + lluau_checkstack(L, 1); + lua_pushvalue(L, lua_upvalueindex(1)); + return lua_destroyuserdata(L); +} + +/***************************************************************************** +* Lua <=> C++ conversions +*****************************************************************************/ +std::string lua_tostdstring(lua_State* L, int index) +{ + lua_checkdelta(L); + size_t len; + const char* strval{ lua_tolstring(L, index, &len) }; + return { strval, len }; +} + +void lua_pushstdstring(lua_State* L, const std::string& str) +{ + lua_checkdelta(L, 1); + lluau_checkstack(L, 1); + lua_pushlstring(L, str.c_str(), str.length()); +} + +// By analogy with existing lua_tomumble() functions, return an LLSD object +// corresponding to the Lua object at stack index 'index' in state L. +// This function assumes that a Lua caller is fully aware that they're trying +// to call a viewer function. In other words, the caller must specifically +// construct Lua data convertible to LLSD. +// +// For proper error handling, we REQUIRE that the Lua runtime be compiled as +// C++ so errors are raised as C++ exceptions rather than as longjmp() calls: +// http://www.lua.org/manual/5.4/manual.html#4.4 +// "Internally, Lua uses the C longjmp facility to handle errors. (Lua will +// use exceptions if you compile it as C++; search for LUAI_THROW in the +// source code for details.)" +// Some blocks within this function construct temporary C++ objects in the +// expectation that these objects will be properly destroyed even if code +// reached by that block raises a Lua error. +LLSD lua_tollsd(lua_State* L, int index) +{ + lua_checkdelta(L); + switch (lua_type(L, index)) + { + case LUA_TNONE: + // Should LUA_TNONE be an error instead of returning isUndefined()? + case LUA_TNIL: + return {}; + + case LUA_TBOOLEAN: + return bool(lua_toboolean(L, index)); + + case LUA_TNUMBER: + { + // Vanilla Lua supports lua_tointegerx(), which tells the caller + // whether the number at the specified stack index is or is not an + // integer. Apparently the function exists but does not work right in + // Luau: it reports even non-integer numbers as integers. + // Instead, check if integer truncation leaves the number intact. + lua_Number numval{ lua_tonumber(L, index) }; + lua_Integer intval{ narrow(numval) }; + if (lua_Number(intval) == numval) + { + return LLSD::Integer(intval); + } + else + { + return numval; + } + } + + case LUA_TSTRING: + return lua_tostdstring(L, index); + + case LUA_TUSERDATA: + { + LLSD::Binary binary(lua_rawlen(L, index)); + std::memcpy(binary.data(), lua_touserdata(L, index), binary.size()); + return binary; + } + + case LUA_TTABLE: + { + // A Lua table correctly constructed to convert to LLSD will have + // either consecutive integer keys starting at 1, which we represent + // as an LLSD array (with Lua key 1 at C++ index 0), or will have + // all string keys. + // + // In the belief that Lua table traversal skips "holes," that is, it + // doesn't report any key/value pair whose value is nil, we allow a + // table with integer keys >= 1 but with "holes." This produces an + // LLSD array with isUndefined() entries at unspecified keys. There + // would be no other way for a Lua caller to construct an + // isUndefined() LLSD array entry. However, to guard against crazy int + // keys, we forbid gaps larger than a certain size: crazy int keys + // could result in a crazy large contiguous LLSD array. + // + // Possible looseness could include: + // - A mix of integer and string keys could produce an LLSD map in + // which the integer keys are converted to string. (Key conversion + // must be performed in C++, not Lua, to avoid confusing + // lua_next().) + // - However, since in Lua t[0] and t["0"] are distinct table entries, + // do not consider converting numeric string keys to int to return + // an LLSD array. + // But until we get more experience with actual Lua scripts in + // practice, let's say that any deviation is a Lua coding error. + // An important property of the strict definition above is that most + // conforming data blobs can make a round trip across the language + // boundary and still compare equal. A non-conforming data blob would + // lose that property. + // Known exceptions to round trip identity: + // - Empty LLSD map and empty LLSD array convert to empty Lua table. + // But empty Lua table converts to isUndefined() LLSD object. + // - LLSD::Real with integer value returns as LLSD::Integer. + // - LLSD::UUID, LLSD::Date and LLSD::URI all convert to Lua string, + // and so return as LLSD::String. + // - Lua does not store any table key whose value is nil. An LLSD + // array with isUndefined() entries produces a Lua table with + // "holes" in the int key sequence; this converts back to an LLSD + // array containing corresponding isUndefined() entries -- except + // when one or more of the final entries isUndefined(). These are + // simply dropped, producing a shorter LLSD array than the original. + // - For the same reason, any keys in an LLSD map whose value + // isUndefined() are simply discarded in the converted Lua table. + // This converts back to an LLSD map lacking those keys. + // - If it's important to preserve the original length of an LLSD + // array whose final entries are undefined, or the full set of keys + // for an LLSD map some of whose values are undefined, store an + // LLSD::emptyArray() or emptyMap() instead. These will be + // represented in Lua as empty table, which should convert back to + // undefined LLSD. Naturally, though, those won't survive a second + // round trip. + + // This is the most important of the lluau_checkstack() calls because a + // deeply nested Lua structure will enter this case at each level, and + // we'll need another 2 stack slots to traverse each nested table. + lluau_checkstack(L, 2); + // BEFORE we push nil to initialize the lua_next() traversal, convert + // 'index' to absolute! Our caller might have passed a relative index; + // we do, below: lua_tollsd(L, -1). If 'index' is -1, then when we + // push nil, what we find at index -1 is nil, not the table! + index = lua_absindex(L, index); + lua_pushnil(L); // first key + if (! lua_next(L, index)) + { + // it's a table, but the table is empty -- no idea if it should be + // modeled as empty array or empty map -- return isUndefined(), + // which can be consumed as either + return {}; + } + // key is at stack index -2, value at index -1 + // from here until lua_next() returns 0, have to lua_pop(2) if we + // return early + LuaPopper popper(L, 2); + // Remember the type of the first key + auto firstkeytype{ lua_type(L, -2) }; + switch (firstkeytype) + { + case LUA_TNUMBER: + { + // First Lua key is a number: try to convert table to LLSD array. + // This is tricky because we don't know in advance the size of the + // array. The Lua reference manual says that lua_rawlen() is the + // same as the length operator '#'; but the length operator states + // that it might stop at any "hole" in the subject table. + // Moreover, the Lua next() function (and presumably lua_next()) + // traverses a table in unspecified order, even for numeric keys + // (emphasized in the doc). + // Make a preliminary pass over the whole table to validate and to + // collect keys. + std::vector<LLSD::Integer> keys; + // Try to determine the length of the table. If the length + // operator is truthful, avoid allocations while we grow the keys + // vector. Even if it's not, we can still grow the vector, albeit + // a little less efficiently. + keys.reserve(lua_objlen(L, index)); + do + { + auto arraykeytype{ lua_type(L, -2) }; + switch (arraykeytype) + { + case LUA_TNUMBER: + { + int isint; + lua_Integer intkey{ lua_tointegerx(L, -2, &isint) }; + if (! isint) + { + // key isn't an integer - this doesn't fit our LLSD + // array constraints + return lluau::error(L, "Expected integer array key, got %f instead", + lua_tonumber(L, -2)); + } + if (intkey < 1) + { + return lluau::error(L, "array key %d out of bounds", int(intkey)); + } + + keys.push_back(LLSD::Integer(intkey)); + break; + } + + case LUA_TSTRING: + // break out strings specially to report the value + return lluau::error(L, "Cannot convert string array key '%s' to LLSD", + lua_tostring(L, -2)); + + default: + return lluau::error(L, "Cannot convert %s array key to LLSD", + lua_typename(L, arraykeytype)); + } + + // remove value, keep key for next iteration + lua_pop(L, 1); + } while (lua_next(L, index) != 0); + popper.disarm(); + // Table keys are all integers: are they reasonable integers? + // Arbitrary max: may bite us, but more likely to protect us + const size_t array_max{ 10000 }; + if (keys.size() > array_max) + { + return lluau::error(L, "Conversion from Lua to LLSD array limited to %d entries", + int(array_max)); + } + // We know the smallest key is >= 1. Check the largest. We also + // know the vector is NOT empty, else we wouldn't have gotten here. + std::sort(keys.begin(), keys.end()); + LLSD::Integer highkey = *keys.rbegin(); + if ((highkey - LLSD::Integer(keys.size())) > 100) + { + // Looks like we've gone beyond intentional array gaps into + // crazy key territory. + return lluau::error(L, "Gaps in Lua table too large for conversion to LLSD array"); + } + // right away expand the result array to the size we'll need + LLSD result{ LLSD::emptyArray() }; + result[highkey - 1] = LLSD(); + // Traverse the table again, and this time populate result array. + lua_pushnil(L); // first key + while (lua_next(L, index)) + { + // key at stack index -2, value at index -1 + // We've already validated lua_tointegerx() for each key. + auto key{ lua_tointeger(L, -2) }; + // Don't forget to subtract 1 from Lua key for LLSD subscript! + result[LLSD::Integer(key) - 1] = lua_tollsd(L, -1); + // remove value, keep key for next iteration + lua_pop(L, 1); + } + return result; + } + + case LUA_TSTRING: + { + // First Lua key is a string: try to convert table to LLSD map + LLSD result{ LLSD::emptyMap() }; + do + { + auto mapkeytype{ lua_type(L, -2) }; + if (mapkeytype != LUA_TSTRING) + { + return lluau::error(L, "Cannot convert %s map key to LLSD", + lua_typename(L, mapkeytype)); + } + + auto key{ lua_tostdstring(L, -2) }; + result[key] = lua_tollsd(L, -1); + // remove value, keep key for next iteration + lua_pop(L, 1); + } while (lua_next(L, index) != 0); + popper.disarm(); + return result; + } + + default: + // First Lua key isn't number or string: sorry + return lluau::error(L, "Cannot convert %s table key to LLSD", + lua_typename(L, firstkeytype)); + } + } + + default: + // Other Lua entities (e.g. function, C function, light userdata, + // thread, userdata) are not convertible to LLSD, indicating a coding + // error in the caller. + return lluau::error(L, "Cannot convert type %s to LLSD", luaL_typename(L, index)); + } +} + +// By analogy with existing lua_pushmumble() functions, push onto state L's +// stack a Lua object corresponding to the passed LLSD object. +void lua_pushllsd(lua_State* L, const LLSD& data) +{ + lua_checkdelta(L, 1); + // might need 2 slots for array or map + lluau_checkstack(L, 2); + switch (data.type()) + { + case LLSD::TypeUndefined: + lua_pushnil(L); + break; + + case LLSD::TypeBoolean: + lua_pushboolean(L, data.asBoolean()); + break; + + case LLSD::TypeInteger: + lua_pushinteger(L, data.asInteger()); + break; + + case LLSD::TypeReal: + lua_pushnumber(L, data.asReal()); + break; + + case LLSD::TypeBinary: + { + auto binary{ data.asBinary() }; + std::memcpy(lua_newuserdata(L, binary.size()), + binary.data(), binary.size()); + break; + } + + case LLSD::TypeMap: + { + // push a new table with space for our non-array keys + lua_createtable(L, 0, narrow(data.size())); + for (const auto& pair: llsd::inMap(data)) + { + // push value -- so now table is at -2, value at -1 + lua_pushllsd(L, pair.second); + // pop value, assign to table[key] + lua_setfield(L, -2, pair.first.c_str()); + } + break; + } + + case LLSD::TypeArray: + { + // push a new table with space for array entries + lua_createtable(L, narrow(data.size()), 0); + lua_Integer key{ 0 }; + for (const auto& item: llsd::inArray(data)) + { + // push new array value: table at -2, value at -1 + lua_pushllsd(L, item); + // pop value, assign table[key] = value + lua_rawseti(L, -2, ++key); + } + break; + } + + case LLSD::TypeString: + case LLSD::TypeUUID: + case LLSD::TypeDate: + case LLSD::TypeURI: + default: + { + lua_pushstdstring(L, data.asString()); + break; + } + } +} + +/***************************************************************************** +* LuaState class +*****************************************************************************/ +namespace +{ + +// If we find we're running Lua scripts from more than one thread, sLuaStateMap +// should be thread_local. Until then, avoid the overhead. +using LuaStateMap = std::unordered_map<lua_State*, LuaState*>; +static LuaStateMap sLuaStateMap; + +// replace table-at-index[name] with passed func, +// binding the original table-at-index[name] as func's upvalue +void replace_entry(lua_State* L, int index, + const std::string& name, lua_CFunction func); + +// replacement next() function that understands setdtor() proxy args +int lua_proxydrill(lua_State* L); +// replacement pairs() function that supports __iter() metamethod +int lua_metapairs(lua_State* L); +// replacement ipairs() function that supports __index() metamethod +int lua_metaipairs(lua_State* L); +// helper for lua_metaipairs() (actual generator function) +int lua_metaipair(lua_State* L); + +} // anonymous namespace + +LuaState::LuaState() +{ + /*---------------------------- feature flag ----------------------------*/ + try + { + mFeature = LL::CommonControl::get("Global", "LuaFeature").asBoolean(); + } + catch (const LL::CommonControl::NoListener&) + { + // If this program doesn't have an LLViewerControlListener, + // it's probably a test program; go ahead. + mFeature = true; + } + catch (const LL::CommonControl::ParamError&) + { + // We found LLViewerControlListener, but its settings do not include + // "LuaFeature". Hmm, fishy: that feature flag was introduced at the + // same time as this code. + mFeature = false; + } + // None of the rest of this is necessary if we're not going to run anything. + if (! mFeature) + { + mError = "Lua feature disabled"; + return; + } + /*---------------------------- feature flag ----------------------------*/ + + mState = luaL_newstate(); + // Ensure that we can always find this LuaState instance, given the + // lua_State we just created or any of its coroutines. + sLuaStateMap.emplace(mState, this); + luaL_openlibs(mState); + // publish to this new lua_State all the LL entry points we defined using + // the lua_function() macro + LuaFunction::init(mState); + // Try to make print() write to our log. + lua_register(mState, "print", LuaFunction::get("print_info")); + // We don't want to have to prefix require(). + lua_register(mState, "require", LuaFunction::get("require")); + + // Replace certain key global functions so they understand our + // LL.setdtor() proxy objects. + // (We could also do this for selected library functions as well, + // e.g. the table, string, math libraries... let's see if needed.) + replace_entry(mState, LUA_GLOBALSINDEX, "next", lua_proxydrill); + // Replacing pairs() with lua_metapairs() makes global pairs() honor + // objects with __iter() metamethods. + replace_entry(mState, LUA_GLOBALSINDEX, "pairs", lua_metapairs); + // Replacing ipairs() with lua_metaipairs() makes global ipairs() honor + // objects with __index() metamethods -- as long as the object in question + // has no array entries (int keys) of its own. (If it does, object[i] will + // retrieve table[i] instead of calling __index(table, i).) + replace_entry(mState, LUA_GLOBALSINDEX, "ipairs", lua_metaipairs); +} + +namespace +{ + +void replace_entry(lua_State* L, int index, + const std::string& name, lua_CFunction func) +{ + index = lua_absindex(L, index); + lua_checkdelta(L); + // push the function's name string twice + lua_pushlstring(L, name.data(), name.length()); + lua_pushvalue(L, -1); + // stack: name, name + // look up the existing table entry + lua_rawget(L, index); + // stack: name, original function + // bind original function as the upvalue for func() + lua_pushcclosure(L, func, (name + "()").c_str(), 1); + // stack: name, func-with-bound-original + // table[name] = func-with-bound-original + lua_rawset(L, index); +} + +int lua_metapairs(lua_State* L) +{ +// LuaLog debug(L, "lua_metapairs()"); + // pairs(obj): object is at index 1 + // How many args were we passed? + int args = lua_gettop(L); + // stack: obj, ... + if (luaL_getmetafield(L, 1, "__iter")) + { + // stack: obj, ..., getmetatable(obj).__iter + } + else + { + // Push the original pairs() function, captured as our upvalue. + lua_pushvalue(L, lua_upvalueindex(1)); + // stack: obj, ..., original pairs() + } + lua_insert(L, 1); + // stack: (__iter() or pairs()), obj, ... + // call whichever function(obj, ...) (args args, up to 3 return values) + lua_call(L, args, LUA_MULTRET); + // return as many values as the selected function returned + return lua_gettop(L); +} + +int lua_metaipairs(lua_State* L) +{ +// LuaLog debug(L, "lua_metaipairs()"); + // ipairs(obj): object is at index 1 + // How many args were we passed? + int args = lua_gettop(L); + // stack: obj, ... + if (luaL_getmetafield(L, 1, "__index")) + { + // stack: obj, ..., getmetatable(obj).__index + // discard __index and everything but obj: + // we don't want to call __index(), just check its presence + lua_settop(L, 1); + // stack: obj + lua_pushcfunction(L, lua_metaipair, "lua_metaipair"); + // stack: obj, lua_metaipair + lua_insert(L, 1); + // stack: lua_metaipair, obj + // push explicit 0 so lua_metaipair need not special-case nil + lua_pushinteger(L, 0); + // stack: lua_metaipair, obj, 0 + return 3; + } + else // no __index() metamethod + { + // Although our lua_metaipair() function demonstrably works whether or + // not our object has an __index() metamethod, the code below assumes + // that the Lua engine may have a more efficient implementation for + // built-in ipairs() than our lua_metaipair(). + // Push the original ipairs() function, captured as our upvalue. + lua_pushvalue(L, lua_upvalueindex(1)); + // stack: obj, ..., original ipairs() + // Shift the stack so the original function is first. + lua_insert(L, 1); + // stack: original ipairs(), obj, ... + // Call original ipairs() with all original args, no error checking. + // Don't truncate however many values that function returns. + lua_call(L, args, LUA_MULTRET); + // Return as many values as the original function returned. + return lua_gettop(L); + } +} + +int lua_metaipair(lua_State* L) +{ +// LuaLog debug(L, "lua_metaipair()"); + // called with (obj, previous-index) + // increment previous-index for this call + lua_Integer i = luaL_optinteger(L, 2, 0) + 1; + lua_pop(L, 1); + // stack: obj + lua_pushinteger(L, i); + // stack: obj, i + lua_pushvalue(L, -1); + // stack: obj, i, i + lua_insert(L, 1); + // stack: i, obj, i + lua_gettable(L, -2); + // stack: i, obj, obj[i] (honoring __index()) + lua_remove(L, -2); + // stack: i, obj[i] + if (! lua_isnil(L, -1)) + { + // great, obj[i] isn't nil: return (i, obj[i]) + return 2; + } + // obj[i] is nil. ipairs() is documented to stop at the first hole, + // regardless of #obj. Clear the stack, i.e. return nil. + lua_settop(L, 0); + return 0; +} + +} // anonymous namespace + +LuaState::~LuaState() +{ + // If we're unwinding the stack due to an exception, don't bother trying + // to call any callbacks -- either Lua or C++. + if (std::uncaught_exceptions() != 0) + return; + + /*---------------------------- feature flag ----------------------------*/ + if (! mFeature) + return; + /*---------------------------- feature flag ----------------------------*/ + + // We're just about to destroy this lua_State mState. Did this Lua chunk + // register any atexit() functions? + lluau_checkstack(mState, 3); + // look up Registry.atexit + lua_getfield(mState, LUA_REGISTRYINDEX, "atexit"); + // stack contains Registry.atexit + if (lua_istable(mState, -1)) + { + // We happen to know that Registry.atexit is built by appending array + // entries using table.insert(). That's important because it means + // there are no holes, and therefore lua_objlen() should be correct. + // That's important because we walk the atexit table backwards, to + // destroy last the things we created (passed to LL.atexit()) first. + int len(lua_objlen(mState, -1)); + LL_DEBUGS("Lua") << LLCoros::getName() << ": Registry.atexit is a table with " + << len << " entries" << LL_ENDL; + + // Push debug.traceback() onto the stack as lua_pcall()'s error + // handler function. On error, lua_pcall() calls the specified error + // handler function with the original error message; the message + // returned by the error handler is then returned by lua_pcall(). + // Luau's debug.traceback() is called with a message to prepend to the + // returned traceback string. Almost as if they'd been designed to + // work together... + lua_getglobal(mState, "debug"); + lua_getfield(mState, -1, "traceback"); + // ditch "debug" + lua_remove(mState, -2); + // stack now contains atexit, debug.traceback() + + for (int i(len); i >= 1; --i) + { + lua_pushinteger(mState, i); + // stack contains Registry.atexit, debug.traceback(), i + lua_gettable(mState, -3); + // stack contains Registry.atexit, debug.traceback(), atexit[i] + // Call atexit[i](), no args, no return values. + // Use lua_pcall() because errors in any one atexit() function + // shouldn't cancel the rest of them. Pass debug.traceback() as + // the error handler function. + LL_DEBUGS("Lua") << LLCoros::getName() + << ": calling atexit(" << i << ")" << LL_ENDL; + if (lua_pcall(mState, 0, 0, -2) != LUA_OK) + { + auto error{ lua_tostdstring(mState, -1) }; + LL_WARNS("Lua") << LLCoros::getName() + << ": atexit(" << i << ") error: " << error << LL_ENDL; + // pop error message + lua_pop(mState, 1); + } + LL_DEBUGS("Lua") << LLCoros::getName() << ": atexit(" << i << ") done" << LL_ENDL; + // lua_pcall() has already popped atexit[i]: + // stack contains atexit, debug.traceback() + } + // pop debug.traceback() + lua_pop(mState, 1); + } + // pop Registry.atexit (either table or nil) + lua_pop(mState, 1); + + // with the demise of this LuaState, remove sLuaStateMap entry + sLuaStateMap.erase(mState); + + lua_close(mState); +} + +bool LuaState::checkLua(const std::string& desc, int r) +{ + if (r != LUA_OK) + { + mError = lua_tostring(mState, -1); + lua_pop(mState, 1); + + LL_WARNS("Lua") << desc << ": " << mError << LL_ENDL; + return false; + } + return true; +} + +std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string& text, + const std::vector<std::string>& args) +{ + /*---------------------------- feature flag ----------------------------*/ + if (! mFeature) + { + // fake an error + return { -1, stringize("Not running ", desc) }; + } + /*---------------------------- feature flag ----------------------------*/ + + set_interrupts_counter(0); + + lua_callbacks(mState)->interrupt = [](lua_State *L, int gc) + { + // skip if we're interrupting only for garbage collection + if (gc >= 0) + return; + + LLCoros::checkStop(); + LuaState::getParent(L).check_interrupts_counter(); + }; + + LL_INFOS("Lua") << desc << " run" << LL_ENDL; + if (! checkLua(desc, lluau::dostring(mState, desc, text, args))) + { + LL_WARNS("Lua") << desc << " error: " << mError << LL_ENDL; + return { -1, mError }; + } + + // here we believe there was no error -- did the Lua fragment leave + // anything on the stack? + std::pair<int, LLSD> result{ lua_gettop(mState), {} }; + LL_INFOS("Lua") << desc << " done, " << result.first << " results." << LL_ENDL; + if (result.first) + { + // aha, at least one entry on the stack! + if (result.first == 1) + { + // Don't forget that lua_tollsd() can throw Lua errors. + try + { + result.second = lua_tollsd(mState, 1); + } + catch (const std::exception& error) + { + LL_WARNS("Lua") << desc << " error converting result: " << error.what() << LL_ENDL; + // lua_tollsd() is designed to be called from a lua_function(), + // that is, from a C++ function called by Lua. In case of error, + // it throws a Lua error to be caught by the Lua runtime. expr() + // is a peculiar use case in which our C++ code is calling + // lua_tollsd() after return from the Lua runtime. We must catch + // the exception thrown for a Lua error, else it will propagate + // out to the main coroutine and terminate the viewer -- but since + // we instead of the Lua runtime catch it, our lua_State retains + // its internal error status. Any subsequent lua_pcall() calls + // with this lua_State will report error regardless of whether the + // chunk runs successfully. + return { -1, stringize(LLError::Log::classname(error), ": ", error.what()) }; + } + } + else + { + // multiple entries on the stack + int index; + try + { + for (index = 1; index <= result.first; ++index) + { + result.second.append(lua_tollsd(mState, index)); + } + } + catch (const std::exception& error) + { + LL_WARNS("Lua") << desc << " error converting result " << index << ": " + << error.what() << LL_ENDL; + // see above comments regarding lua_State's error status + return { -1, stringize(LLError::Log::classname(error), ": ", error.what()) }; + } + } + } + // pop everything + lua_settop(mState, 0); + return result; +} + +// We think we don't need mFeature tests in the rest of these LuaState methods +// because, if expr() isn't running code, nobody should be calling any of them. + +LuaListener& LuaState::obtainListener(lua_State* L) +{ + lluau_checkstack(L, 2); + lua_getfield(L, LUA_REGISTRYINDEX, "LuaListener"); + // compare lua_type() because lua_isuserdata() also accepts light userdata + if (lua_type(L, -1) != LUA_TUSERDATA) + { + llassert(lua_type(L, -1) == LUA_TNIL); + lua_pop(L, 1); + // push a userdata containing new LuaListener, binding L + lua_emplace<LuaListener>(L, L); + // duplicate the top stack entry so we can store one copy + lua_pushvalue(L, -1); + lua_setfield(L, LUA_REGISTRYINDEX, "LuaListener"); + } + // At this point, one way or the other, the stack top should be (a Lua + // userdata containing) our LuaListener. + LuaListener* listener{ lua_toclass<LuaListener>(L, -1) }; + // Since our LuaListener instance is stored in the Registry, it won't be + // garbage collected: it will be destroyed only when lua_close() clears + // out the Registry. That's why we dare pop the userdata value off the + // stack while still depending on a pointer into its data. + lua_pop(L, 1); + return *listener; +} + +LuaState& LuaState::getParent(lua_State* L) +{ + // Look up the LuaState instance associated with the *script*, not the + // specific Lua *coroutine*. In other words, first find this lua_State's + // main thread. + auto found{ sLuaStateMap.find(lua_mainthread(L)) }; + // Our constructor creates the map entry, our destructor deletes it. As + // long as the LuaState exists, we should be able to find it. And we + // SHOULD only be talking to a lua_State managed by a LuaState instance. + llassert(found != sLuaStateMap.end()); + return *found->second; +} + +void LuaState::set_interrupts_counter(S32 counter) +{ + mInterrupts = counter; +} + +void LuaState::check_interrupts_counter() +{ + // The official way to manage data associated with a lua_State is to store + // it *as* Lua data within the lua_State. But this method is called by the + // Lua engine via lua_callbacks(L)->interrupt, and empirically we've hit + // mysterious Lua data stack overflows trying to use stack-based Lua data + // access functions in that situation. It seems the Lua engine is capable + // of interrupting itself at a moment when re-entry is not valid. So only + // touch data in this LuaState. + ++mInterrupts; + if (mInterrupts > INTERRUPTS_MAX_LIMIT) + { + lluau::error(mState, "Possible infinite loop, terminated."); + } + else if (mInterrupts % INTERRUPTS_SUSPEND_LIMIT == 0) + { + LL_DEBUGS("Lua.suspend") << LLCoros::getName() << " suspending at " + << mInterrupts << " interrupts" << LL_ENDL; + llcoro::suspend(); + } +} + +/***************************************************************************** +* atexit() +*****************************************************************************/ +lua_function(atexit, "atexit(function): " + "register Lua function to be called at script termination") +{ + lua_checkdelta(L, -1); + lluau_checkstack(L, 4); + // look up the global name "table" + lua_getglobal(L, "table"); + // stack contains function, table + // look up table.insert + lua_getfield(L, -1, "insert"); + // stack contains function, table, table.insert + // ditch table + lua_replace(L, -2); + // stack contains function, table.insert + // find or create the "atexit" table in the Registry + luaL_newmetatable(L, "atexit"); + // stack contains function, table.insert, Registry.atexit + // we were called with a Lua function to append to that Registry.atexit + // table -- push function + lua_pushvalue(L, 1); // or -3 + // stack contains function, table.insert, Registry.atexit, function + // call table.insert(Registry.atexit, function) + // don't use pcall(): if there's an error, let it propagate + lua_call(L, 2, 0); + // stack contains function -- pop everything + lua_settop(L, 0); + return 0; +} + +/***************************************************************************** +* LuaPopper class +*****************************************************************************/ +LuaPopper::~LuaPopper() +{ + // If we're unwinding the C++ stack due to an exception, don't pop! + if (std::uncaught_exceptions() == 0 && mCount) + { + lua_pop(mState, mCount); + } +} + +/***************************************************************************** +* LuaFunction class +*****************************************************************************/ +LuaFunction::LuaFunction(std::string_view name, lua_CFunction function, + std::string_view helptext) +{ + const auto& [registry, lookup] = getState(); + registry.emplace(name, Registry::mapped_type{ function, helptext }); + lookup.emplace(function, name); +} + +void LuaFunction::init(lua_State* L) +{ + const auto& [registry, lookup] = getRState(); + lluau_checkstack(L, 2); + // create LL table -- + // it happens that we know exactly how many non-array members we want + lua_createtable(L, 0, int(narrow(lookup.size()))); + int idx = lua_gettop(L); + for (const auto& [name, pair]: registry) + { + const auto& [funcptr, helptext] = pair; + // store funcptr in LL table with saved name + lua_pushcfunction(L, funcptr, name.c_str()); + lua_setfield(L, idx, name.c_str()); + } + // store LL in new lua_State's globals + lua_setglobal(L, "LL"); +} + +lua_CFunction LuaFunction::get(const std::string& key) +{ + // use find() instead of subscripting to avoid creating an entry for + // unknown key + const auto& [registry, lookup] = getState(); + auto found{ registry.find(key) }; + return (found == registry.end())? nullptr : found->second.first; +} + +std::pair<LuaFunction::Registry&, LuaFunction::Lookup&> LuaFunction::getState() +{ + // use function-local statics to ensure they're initialized + static Registry registry; + static Lookup lookup; + return { registry, lookup }; +} + +/***************************************************************************** +* source_path() +*****************************************************************************/ +lua_function(source_path, "source_path(): return the source path of the running Lua script") +{ + lua_checkdelta(L, 1); + lluau_checkstack(L, 1); + lua_pushstdstring(L, lluau::source_path(L).u8string()); + return 1; +} + +/***************************************************************************** +* source_dir() +*****************************************************************************/ +lua_function(source_dir, "source_dir(): return the source directory of the running Lua script") +{ + lua_checkdelta(L, 1); + lluau_checkstack(L, 1); + lua_pushstdstring(L, lluau::source_path(L).parent_path().u8string()); + return 1; +} + +/***************************************************************************** +* abspath() +*****************************************************************************/ +lua_function(abspath, "abspath(path): " + "for given filesystem path relative to running script, return absolute path") +{ + lua_checkdelta(L); + auto path{ lua_tostdstring(L, 1) }; + lua_pop(L, 1); + lua_pushstdstring(L, (lluau::source_path(L).parent_path() / path).u8string()); + return 1; +} + +/***************************************************************************** +* check_stop() +*****************************************************************************/ +lua_function(check_stop, "check_stop(): ensure that a Lua script responds to viewer shutdown") +{ + lua_checkdelta(L); + LLCoros::checkStop(); + return 0; +} + +/***************************************************************************** +* help() +*****************************************************************************/ +lua_function(help, + "help(): list viewer's Lua functions\n" + "LL.help(function): show help string for specific function") +{ + auto& luapump{ LLEventPumps::instance().obtain("lua output") }; + const auto& [registry, lookup]{ LuaFunction::getRState() }; + if (! lua_gettop(L)) + { + // no arguments passed: list all lua_functions + for (const auto& [name, pair] : registry) + { + const auto& [fptr, helptext] = pair; + luapump.post("LL." + helptext); + } + } + else + { + // arguments passed: list each of the specified lua_functions + for (int idx = 1, top = lua_gettop(L); idx <= top; ++idx) + { + std::string arg{ stringize("<unknown ", lua_typename(L, lua_type(L, idx)), ">") }; + if (lua_type(L, idx) == LUA_TSTRING) + { + arg = lua_tostdstring(L, idx); + LLStringUtil::removePrefix(arg, "LL."); + } + else if (lua_type(L, idx) == LUA_TFUNCTION) + { + // Caller passed the actual function instead of its string + // name. A Lua function is an anonymous callable object; it + // has a name only by assigment. You can't ask Lua for a + // function's name, which is why our constructor maintains a + // reverse Lookup map. + auto function{ lua_tocfunction(L, idx) }; + if (auto found = lookup.find(function); found != lookup.end()) + { + // okay, pass found name to lookup below + arg = found->second; + } + } + + if (auto found = registry.find(arg); found != registry.end()) + { + luapump.post("LL." + found->second.second); + } + else + { + luapump.post(arg + ": NOT FOUND"); + } + } + // pop all arguments + lua_settop(L, 0); + } + return 0; // void return +} + +/***************************************************************************** +* leaphelp() +*****************************************************************************/ +lua_function( + leaphelp, + "leaphelp(): list viewer's LEAP APIs\n" + "LL.leaphelp(api): show help for specific api string name") +{ + LLSD request; + int top{ lua_gettop(L) }; + if (top) + { + request = llsd::map("op", "getAPI", "api", lua_tostdstring(L, 1)); + } + else + { + request = llsd::map("op", "getAPIs"); + } + // pop all args + lua_settop(L, 0); + + auto& outpump{ LLEventPumps::instance().obtain("lua output") }; + auto& listener{ LuaState::obtainListener(L) }; + LLEventStream replyPump("leaphelp", true); + // ask the LuaListener's LeapListener and suspend calling coroutine until reply + auto reply{ llcoro::postAndSuspend(request, listener.getCommandName(), replyPump, "reply") }; + reply.erase("reqid"); + + if (auto error = reply["error"]; error.isString()) + { + outpump.post(error.asString()); + return 0; + } + + if (top) + { + // caller wants a specific API + outpump.post(stringize(reply["name"].asString(), ":\n", reply["desc"].asString())); + for (const auto& opmap : llsd::inArray(reply["ops"])) + { + std::ostringstream reqstr; + auto req{ opmap["required"] }; + if (req.isArray()) + { + const char* sep = " (requires "; + for (const auto& [reqkey, reqval] : llsd::inMap(req)) + { + reqstr << sep << reqkey; + sep = ", "; + } + reqstr << ")"; + } + outpump.post(stringize("---- ", reply["key"].asString(), " == '", + opmap["name"].asString(), "'", reqstr.str(), ":\n", + opmap["desc"].asString())); + } + } + else + { + // caller wants a list of APIs + for (const auto& [name, data] : llsd::inMap(reply)) + { + outpump.post(stringize("==== ", name, ":\n", data["desc"].asString())); + } + } + return 0; // void return +} + +/***************************************************************************** +* setdtor +*****************************************************************************/ +namespace { + +// proxy userdata object returned by setdtor() +struct setdtor_refs +{ + lua_State* L; + std::string desc; + // You can't directly store a Lua object in a C++ object, but you can + // create a Lua "reference" by storing the object in the Lua Registry and + // capturing its Registry index. + int objref; + int dtorref; + + setdtor_refs(lua_State* L, const std::string& desc, int objref, int dtorref): + L(L), + desc(desc), + objref(objref), + dtorref(dtorref) + {} + setdtor_refs(const setdtor_refs&) = delete; + setdtor_refs& operator=(const setdtor_refs&) = delete; + ~setdtor_refs(); + + static void push_metatable(lua_State* L); + static std::string binop(const std::string& name, const std::string& op); + static int meta__index(lua_State* L); +}; + +} // anonymous namespace + +lua_function( + setdtor, + "setdtor(desc, obj, dtorfunc) => proxy object referencing obj and dtorfunc.\n" + "When the returned proxy object is garbage-collected, or when the script\n" + "ends, call dtorfunc(obj). String desc is logged in the error message, if any.\n" + "Use the returned proxy object (or proxy._target) like obj.\n" + "obj won't be destroyed as long as the proxy exists; it's the proxy object's\n" + "lifespan that determines when dtorfunc(obj) will be called.") +{ + if (lua_gettop(L) != 3) + { + return lluau::error(L, "setdtor(desc, obj, dtor) requires exactly 3 arguments"); + } + // called with (desc, obj, dtor), returns proxy object + lua_checkdelta(L, -2); +// lluau_checkstack(L, 0); // might get up to 3 stack entries + auto desc{ lua_tostdstring(L, 1) }; + // Get Lua "references" for each of the object and the dtor function. + int objref = lua_ref(L, 2); + int dtorref = lua_ref(L, 3); + // Having captured each of our parameters, discard them. + lua_settop(L, 0); + // Push our setdtor_refs userdata. Not only do we want to push it on L's + // stack, but setdtor_refs's constructor itself requires L. + lua_emplace<setdtor_refs>(L, L, desc, objref, dtorref); + // stack: proxy (i.e. setdtor_refs userdata) + // have to set its metatable + lua_getfield(L, LUA_REGISTRYINDEX, "setdtor_meta"); + // stack: proxy, setdtor_meta (which might be nil) + if (lua_isnil(L, -1)) + { + // discard nil + lua_pop(L, 1); + // compile and push our forwarding metatable + setdtor_refs::push_metatable(L); + // stack: proxy, metatable + // duplicate metatable to save it + lua_pushvalue(L, -1); + // stack: proxy, metatable, metable + // save metatable for future calls + lua_setfield(L, LUA_REGISTRYINDEX, "setdtor_meta"); + // stack: proxy, metatable + } + // stack: proxy, metatable + lua_setmetatable(L, -2); + // stack: proxy + // Because ~setdtor_refs() necessarily uses the Lua stack, the Registry et + // al., we can't let a setdtor_refs instance be destroyed by lua_close(): + // the Lua environment will already be partially shut down. To destroy + // this new setdtor_refs instance BEFORE lua_close(), bind it with + // lua_destroybounduserdata() and register it with LL.atexit(). + // push (the entry point for) LL.atexit() + lua_pushcfunction(L, atexit_luasub::call, "LL.atexit()"); + // stack: proxy, atexit() + lua_pushvalue(L, -2); + // stack: proxy, atexit(), proxy + int tag = lua_userdatatag(L, -1); + // We don't have a lookup table to get from an int Lua userdata tag to the + // corresponding C++ typeinfo name string. We'll introduce one if we need + // it for debugging. But for this particular call, we happen to know it's + // always a setdtor_refs object. + lua_pushcclosure(L, lua_destroybounduserdata, + stringize("lua_destroybounduserdata<", tag, ">()").c_str(), + 1); + // stack: proxy, atexit(), lua_destroybounduserdata + // call atexit(): one argument, no results, let error propagate + lua_call(L, 1, 0); + // stack: proxy + return 1; +} + +namespace { + +void setdtor_refs::push_metatable(lua_State* L) +{ + lua_checkdelta(L, 1); + lluau_checkstack(L, 1); + // Ideally we want a metatable that forwards every operation on our + // setdtor_refs userdata proxy object to the original object. But the + // published C API doesn't include (e.g.) arithmetic operations on Lua + // objects, so in fact it's easier to express the desired metatable in Lua + // than in C++. We could make setdtor() depend on an external Lua module, + // but it seems less fragile to embed the Lua source code right here. + static const std::string setdtor_meta = stringize(R"-( + -- This metatable literal doesn't define __index() because that's + -- implemented in C++. We cannot, in Lua, peek into the setdtor_refs + -- userdata object to obtain objref, nor can we fetch Registry[objref]. + -- So our C++ __index() metamethod recognizes access to '_target' as a + -- reference to Registry[objref]. + -- The rest are defined per https://www.lua.org/manual/5.1/manual.html#2.8. + -- Luau supports destructors instead of __gc metamethod -- we rely on that! + -- We don't set __mode because our proxy is not a table. Real references + -- are stored in the wrapped table, so ITS __mode is what counts. + -- Initial definition of meta omits binary metamethods so they can bind the + -- metatable itself, as explained for binop() below. + local meta = { + __unm = function(arg) + return -arg._target + end, + __len = function(arg) + return #arg._target + end, + -- Comparison metamethods __eq(), __lt() and __le() are only called + -- when both operands have the same metamethod. For our purposes, that + -- means both operands are setdtor_refs userdata objects. + __eq = function(lhs, rhs) + return (lhs._target == rhs._target) + end, + __lt = function(lhs, rhs) + return (lhs._target < rhs._target) + end, + __le = function(lhs, rhs) + return (lhs._target <= rhs._target) + end, + __newindex = function(t, key, value) + assert(key ~= '_target', + "Don't try to replace a setdtor() proxy's _target") + t._target[key] = value + end, + __call = function(func, ...) + return func._target(...) + end, + __tostring = function(arg) + -- don't fret about arg._target's __tostring metamethod, + -- if any, because built-in tostring() deals with that + return tostring(arg._target) + end, + __iter = function(arg) + local iter = (getmetatable(arg._target) or {}).__iter + if iter then + return iter(arg._target) + else + return next, arg._target + end + end + } +)-", + binop("add", "+"), + binop("sub", "-"), + binop("mul", "*"), + binop("div", "/"), + binop("idiv", "//"), + binop("mod", "%"), + binop("pow", "^"), + binop("concat", ".."), +R"-( + return meta +)-"); + // only needed for debugging binop() +// LL_DEBUGS("Lua") << setdtor_meta << LL_ENDL; + + if (lluau::dostring(L, LL_PRETTY_FUNCTION, setdtor_meta) != LUA_OK) + { + // stack: error message string + lua_error(L); + } + llassert(lua_gettop(L) > 0); + llassert(lua_type(L, -1) == LUA_TTABLE); + // stack: Lua metatable compiled from setdtor_meta source + // Inject our C++ __index metamethod. + lua_rawsetfield(L, -1, "__index"sv, &setdtor_refs::meta__index); +} + +// In the definition of setdtor_meta above, binary arithmethic and +// concatenation metamethods are a little funny in that we don't know a +// priori which operand is the userdata with our metatable: the metamethod +// can be invoked either way. So every such metamethod must check, which +// leads to lots of redundancy. Hence this helper function. Call it a Lua +// macro. +std::string setdtor_refs::binop(const std::string& name, const std::string& op) +{ + return stringize( + " meta.__", name, " = function(lhs, rhs)\n" + " if getmetatable(lhs) == meta then\n" + " return lhs._target ", op, " rhs\n" + " else\n" + " return lhs ", op, " rhs._target\n" + " end\n" + " end\n"); +} + +// setdtor_refs __index() metamethod +int setdtor_refs::meta__index(lua_State* L) +{ + // called with (setdtor_refs userdata, key), returns retrieved object + lua_checkdelta(L, -1); + lluau_checkstack(L, 2); + // stack: proxy, key + // get ptr to the C++ struct data + auto ptr = lua_toclass<setdtor_refs>(L, -2); + // meta__index() should NEVER be called with anything but setdtor_refs! + llassert(ptr); + // push the wrapped object + lua_getref(L, ptr->objref); + // stack: proxy, key, _target + // replace userdata with _target + lua_replace(L, -3); + // stack: _target, key + // Duplicate key because lua_tostring() converts number to string: + // if the key is (e.g.) 1, don't try to retrieve _target["1"]! + lua_pushvalue(L, -1); + // stack: _target, key, key + // recognize the special _target field + if (lua_tostdstring(L, -1) == "_target") + { + // okay, ditch both copies of "_target" string key + lua_pop(L, 2); + // stack: _target + } + else // any key but _target + { + // ditch stringized key + lua_pop(L, 1); + // stack: _target, key + // replace key with _target[key], invoking metamethod if any + lua_gettable(L, -2); + // stack: _target, _target[key] + // discard _target + lua_remove(L, -2); + // stack: _target[key] + } + return 1; +} + +// replacement for global next(): +// its lua_upvalueindex(1) is the original function it's replacing +int lua_proxydrill(lua_State* L) +{ + // Accept however many arguments the original function normally accepts. + // If our first arg is a userdata, check if it's a setdtor_refs proxy. + // Drill through as many levels of proxy wrapper as needed. + while (const setdtor_refs* ptr = lua_toclass<setdtor_refs>(L, 1)) + { + // push original object + lua_getref(L, ptr->objref); + // replace first argument with that + lua_replace(L, 1); + } + // We've reached a first argument that's not a setdtor() proxy. + // How many arguments were we passed, anyway? + int args = lua_gettop(L); + // Push the original function, captured as our upvalue. + lua_pushvalue(L, lua_upvalueindex(1)); + // Shift the stack so the original function is first. + lua_insert(L, 1); + // Call the original function with all original args, no error checking. + // Don't truncate however many values that function returns. + lua_call(L, args, LUA_MULTRET); + // Return as many values as the original function returned. + return lua_gettop(L); +} + +// When Lua destroys a setdtor_refs userdata object, either from garbage +// collection or from LL.atexit(lua_destroybounduserdata), it's time to keep +// its promise to call the specified Lua destructor function with the +// specified Lua object. Of course we must also delete the captured +// "references" to both objects. +setdtor_refs::~setdtor_refs() +{ + lua_checkdelta(L); + lluau_checkstack(L, 2); + // push Registry[dtorref] + lua_getref(L, dtorref); + // push Registry[objref] + lua_getref(L, objref); + // free Registry[dtorref] + lua_unref(L, dtorref); + // free Registry[objref] + lua_unref(L, objref); + // call dtor(obj): one arg, no result, no error function + int rc = lua_pcall(L, 1, 0, 0); + if (rc != LUA_OK) + { + // TODO: we don't really want to propagate the error here. + // If this setdtor_refs instance is being destroyed by + // LL.atexit(), we want to continue cleanup. If it's being + // garbage-collected, the call is completely unpredictable from + // the consuming script's point of view. But what to do about this + // error?? For now, just log it. + LL_WARNS("Lua") << LLCoros::getName() + << ": setdtor(" << std::quoted(desc) << ") error: " + << lua_tostring(L, -1) << LL_ENDL; + lua_pop(L, 1); + } +} + +} // anonymous namespace + +/***************************************************************************** +* lua_what +*****************************************************************************/ +std::ostream& operator<<(std::ostream& out, const lua_what& self) +{ + switch (lua_type(self.L, self.index)) + { + case LUA_TNONE: + // distinguish acceptable but non-valid index + out << "none"; + break; + + case LUA_TNIL: + out << "nil"; + break; + + case LUA_TBOOLEAN: + { + auto oldflags { out.flags() }; + out << std::boolalpha << lua_toboolean(self.L, self.index); + out.flags(oldflags); + break; + } + + case LUA_TNUMBER: + out << lua_tonumber(self.L, self.index); + break; + + case LUA_TSTRING: + out << std::quoted(lua_tostdstring(self.L, self.index)); + break; + + case LUA_TUSERDATA: + { + const S32 maxlen = 20; + S32 binlen{ lua_rawlen(self.L, self.index) }; + LLSD::Binary binary(std::min(maxlen, binlen)); + std::memcpy(binary.data(), lua_touserdata(self.L, self.index), binary.size()); + out << LL::hexdump(binary); + if (binlen > maxlen) + { + out << "...(" << (binlen - maxlen) << " more)"; + } + break; + } + + case LUA_TLIGHTUSERDATA: + out << lua_touserdata(self.L, self.index); + break; + + case LUA_TFUNCTION: + { + // Try for the function's name, at the cost of a few more stack + // entries. + lua_checkdelta(self.L); + lluau_checkstack(self.L, 3); + lua_getglobal(self.L, "debug"); + // stack: ..., debug + lua_getfield(self.L, -1, "info"); + // stack: ..., debug, debug.info + lua_remove(self.L, -2); + // stack: ..., debug.info + lua_pushvalue(self.L, self.index); + // stack: ..., debug.info, this function + lua_pushstring(self.L, "n"); + // stack: ..., debug.info, this function, "n" + // 2 arguments, 1 return value (or error message), no error handler + lua_pcall(self.L, 2, 1, 0); + // stack: ..., function name (or error) from debug.info() + out << "function " << lua_tostdstring(self.L, -1); + lua_pop(self.L, 1); + // stack: ... + break; + } + + default: + // anything else, don't bother trying to report value, just type + out << lua_typename(self.L, lua_type(self.L, self.index)); + break; + } + return out; +} + +/***************************************************************************** +* lua_stack +*****************************************************************************/ +std::ostream& operator<<(std::ostream& out, const lua_stack& self) +{ + out << "stack: ["; + const char* sep = ""; + for (int index = 1; index <= lua_gettop(self.L); ++index) + { + out << sep << lua_what(self.L, index); + sep = ", "; + } + out << ']'; + return out; +} + +/***************************************************************************** +* LuaStackDelta +*****************************************************************************/ +LuaStackDelta::LuaStackDelta(lua_State* L, const std::string& where, int delta): + L(L), + mWhere(where), + mDepth(lua_gettop(L)), + mDelta(delta) +{} + +LuaStackDelta::~LuaStackDelta() +{ + auto depth{ lua_gettop(L) }; + // If we're unwinding the stack due to an exception, then of course we + // can't expect the logic in the block containing this LuaStackDelta + // instance to keep its contract wrt the Lua data stack. + if (std::uncaught_exceptions() == 0 && mDepth + mDelta != depth) + { + LL_ERRS("Lua") << LLCoros::getName() << ": " << mWhere + << ": Lua stack went from " << mDepth << " to " << depth; + if (mDelta) + { + LL_CONT << ", rather than expected " << (mDepth + mDelta) << " (" << mDelta << ")"; + } + LL_ENDL; + } +} diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h new file mode 100644 index 0000000000..ae6e0bf7ba --- /dev/null +++ b/indra/llcommon/lua_function.h @@ -0,0 +1,626 @@ +/** + * @file lua_function.h + * @author Nat Goodspeed + * @date 2024-02-05 + * @brief Definitions useful for coding a new Luau entry point into C++ + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LUA_FUNCTION_H) +#define LL_LUA_FUNCTION_H + +#include "luau/luacode.h" +#include "luau/lua.h" +#include "luau/luaconf.h" +#include "luau/lualib.h" +#include "fsyspath.h" +#include "llerror.h" +#include "llsd.h" +#include "scriptcommand.h" +#include "stringize.h" +#include <exception> // std::uncaught_exceptions() +#include <memory> // std::shared_ptr +#include <typeindex> +#include <typeinfo> +#include <unordered_map> +#include <utility> // std::pair +#include <vector> + +class LuaListener; + +/***************************************************************************** +* lluau namespace utility functions +*****************************************************************************/ +namespace lluau +{ + // luau defines luaL_error() as void, but we want to use the Lua idiom of + // 'return error(...)'. Wrap luaL_error() in an int function. +#if __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wformat-security" +#endif // __clang__ + template<typename... Args> + int error(lua_State* L, const char* format, Args&&... args) + { + luaL_error(L, format, std::forward<Args>(args)...); +#ifndef LL_MSVC + return 0; +#endif + } +#if __clang__ +#pragma clang diagnostic pop +#endif // __clang__ + + // luau removed lua_dostring(), but since we perform the equivalent luau + // sequence in multiple places, encapsulate it. desc and text are strings + // rather than string_views because dostring() needs pointers to nul- + // terminated char arrays. Any args are pushed to the Lua stack before + // calling the Lua chunk in text. + int dostring(lua_State* L, const std::string& desc, const std::string& text, + const std::vector<std::string>& args={}); + int loadstring(lua_State* L, const std::string& desc, const std::string& text); + + fsyspath source_path(lua_State* L); +} // namespace lluau + +// must be a macro because LL_PRETTY_FUNCTION is context-sensitive +#define lluau_checkstack(L, n) luaL_checkstack((L), (n), LL_PRETTY_FUNCTION) + +std::string lua_tostdstring(lua_State* L, int index); +void lua_pushstdstring(lua_State* L, const std::string& str); +LLSD lua_tollsd(lua_State* L, int index); +void lua_pushllsd(lua_State* L, const LLSD& data); + +/***************************************************************************** +* LuaState +*****************************************************************************/ +/** + * RAII class to manage the lifespan of a lua_State + */ +class LuaState +{ +public: + LuaState(); + + LuaState(const LuaState&) = delete; + LuaState& operator=(const LuaState&) = delete; + + ~LuaState(); + + bool checkLua(const std::string& desc, int r); + + // expr() is for when we want to capture any results left on the stack + // by a Lua expression, possibly including multiple return values. + // Pass: + // desc = description used for logging et al. + // text = Lua chunk to execute, e.g. contents of a script file + // args = arguments, if any, to pass to script file + // Returns: + // int < 0 means error, and LLSD::asString() is the error message. + // int == 0 with LLSD::isUndefined() means the Lua expression returned no + // results. + // int == 1 means the Lua expression returned one result. + // int > 1 with LLSD::isArray() means the Lua expression returned + // multiple results, represented as the entries of the array. + std::pair<int, LLSD> expr(const std::string& desc, const std::string& text, + const std::vector<std::string>& args={}); + + operator lua_State*() const { return mState; } + + // Find or create LuaListener for this LuaState. + LuaListener& obtainListener() { return obtainListener(mState); } + // Find or create LuaListener for passed lua_State. + static LuaListener& obtainListener(lua_State* L); + + // Given lua_State* L, return the LuaState object managing (the main Lua + // thread for) L. + static LuaState& getParent(lua_State* L); + + void set_interrupts_counter(S32 counter); + void check_interrupts_counter(); + +private: + /*---------------------------- feature flag ----------------------------*/ + bool mFeature{ false }; + /*---------------------------- feature flag ----------------------------*/ + lua_State* mState{ nullptr }; + std::string mError; + S32 mInterrupts{ 0 }; +}; + +/***************************************************************************** +* LuaPopper +*****************************************************************************/ +/** + * LuaPopper is an RAII class whose role is to pop some number of entries + * from the Lua stack if the calling function exits early. + */ +class LuaPopper +{ +public: + LuaPopper(lua_State* L, int count): + mState(L), + mCount(count) + {} + + LuaPopper(const LuaPopper&) = delete; + LuaPopper& operator=(const LuaPopper&) = delete; + + ~LuaPopper(); + + void disarm() { set(0); } + void set(int count) { mCount = count; } + +private: + lua_State* mState; + int mCount; +}; + +/***************************************************************************** +* LuaRemover +*****************************************************************************/ +/** + * Remove a particular stack index on exit from enclosing scope. + * If you pass a negative index (meaning relative to the current stack top), + * converts to an absolute index. The point of LuaRemover is to remove the + * entry at the specified index regardless of subsequent pushes to the stack. + */ +class LuaRemover +{ +public: + LuaRemover(lua_State* L, int index): + mState(L), + mIndex(lua_absindex(L, index)) + {} + LuaRemover(const LuaRemover&) = delete; + LuaRemover& operator=(const LuaRemover&) = delete; + ~LuaRemover() + { + // If we're unwinding the C++ stack due to an exception, don't mess + // with the Lua stack! + if (std::uncaught_exceptions() == 0) + lua_remove(mState, mIndex); + } + +private: + lua_State* mState; + int mIndex; +}; + +/***************************************************************************** +* LuaStackDelta +*****************************************************************************/ +/** + * Instantiate LuaStackDelta in a block to compare the Lua data stack depth on + * entry (LuaStackDelta construction) and exit. Optionally, pass the expected + * depth increment. (But be aware that LuaStackDelta cannot observe the effect + * of a LuaPopper or LuaRemover declared previously in the same block.) + */ +class LuaStackDelta +{ +public: + LuaStackDelta(lua_State* L, const std::string& where, int delta=0); + LuaStackDelta(const LuaStackDelta&) = delete; + LuaStackDelta& operator=(const LuaStackDelta&) = delete; + + ~LuaStackDelta(); + +private: + lua_State* L; + std::string mWhere; + int mDepth, mDelta; +}; + +#define lua_checkdelta(L, ...) LuaStackDelta delta(L, LL_PRETTY_FUNCTION, ##__VA_ARGS__) + +/***************************************************************************** +* lua_push() wrappers for generic code +*****************************************************************************/ +inline +void lua_push(lua_State* L, bool b) +{ + lua_pushboolean(L, int(b)); +} + +inline +void lua_push(lua_State* L, lua_CFunction fn) +{ + lua_pushcfunction(L, fn, ""); +} + +inline +void lua_push(lua_State* L, lua_Integer n) +{ + lua_pushinteger(L, n); +} + +inline +void lua_push(lua_State* L, void* p) +{ + lua_pushlightuserdata(L, p); +} + +inline +void lua_push(lua_State* L, const LLSD& data) +{ + lua_pushllsd(L, data); +} + +inline +void lua_push(lua_State* L, const char* s, size_t len) +{ + lua_pushlstring(L, s, len); +} + +inline +void lua_push(lua_State* L) +{ + lua_pushnil(L); +} + +inline +void lua_push(lua_State* L, lua_Number n) +{ + lua_pushnumber(L, n); +} + +inline +void lua_push(lua_State* L, const std::string& s) +{ + lua_pushstdstring(L, s); +} + +inline +void lua_push(lua_State* L, const char* s) +{ + lua_pushstring(L, s); +} + +/***************************************************************************** +* lua_to() wrappers for generic code +*****************************************************************************/ +template <typename T> +auto lua_to(lua_State* L, int index); + +template <> +inline +auto lua_to<bool>(lua_State* L, int index) +{ + return lua_toboolean(L, index); +} + +template <> +inline +auto lua_to<lua_CFunction>(lua_State* L, int index) +{ + return lua_tocfunction(L, index); +} + +template <> +inline +auto lua_to<lua_Integer>(lua_State* L, int index) +{ + return lua_tointeger(L, index); +} + +template <> +inline +auto lua_to<LLSD>(lua_State* L, int index) +{ + return lua_tollsd(L, index); +} + +template <> +inline +auto lua_to<lua_Number>(lua_State* L, int index) +{ + return lua_tonumber(L, index); +} + +template <> +inline +auto lua_to<std::string>(lua_State* L, int index) +{ + return lua_tostdstring(L, index); +} + +template <> +inline +auto lua_to<void*>(lua_State* L, int index) +{ + return lua_touserdata(L, index); +} + +/***************************************************************************** +* field operations +*****************************************************************************/ +// return to C++, from table at index, the value of field k +template <typename T> +auto lua_getfieldv(lua_State* L, int index, const char* k) +{ + lua_checkdelta(L); + lluau_checkstack(L, 1); + lua_getfield(L, index, k); + LuaPopper pop(L, 1); + return lua_to<T>(L, -1); +} + +// set in table at index, as field k, the specified C++ value +template <typename T> +auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value) +{ + index = lua_absindex(L, index); + lua_checkdelta(L); + lluau_checkstack(L, 1); + lua_push(L, value); + lua_setfield(L, index, k); +} + +// return to C++, from table at index, the value of field k (without metamethods) +template <typename T> +auto lua_rawgetfield(lua_State* L, int index, std::string_view k) +{ + index = lua_absindex(L, index); + lua_checkdelta(L); + lluau_checkstack(L, 1); + lua_pushlstring(L, k.data(), k.length()); + lua_rawget(L, index); + LuaPopper pop(L, 1); + return lua_to<T>(L, -1); +} + +// set in table at index, as field k, the specified C++ value (without metamethods) +template <typename T> +void lua_rawsetfield(lua_State* L, int index, std::string_view k, const T& value) +{ + index = lua_absindex(L, index); + lua_checkdelta(L); + lluau_checkstack(L, 2); + lua_pushlstring(L, k.data(), k.length()); + lua_push(L, value); + lua_rawset(L, index); +} + +/***************************************************************************** +* lua_function (and helper class LuaFunction) +*****************************************************************************/ +/** + * LuaFunction is a base class containing a static registry of its static + * subclass call() methods. call() is NOT virtual: instead, each subclass + * constructor passes a pointer to its distinct call() method to the base- + * class constructor, along with a name by which to register that method. + * + * The init() method walks the registry and registers each such name with the + * passed lua_State. + */ +class LuaFunction +{ +public: + LuaFunction(std::string_view name, lua_CFunction function, + std::string_view helptext); + + static void init(lua_State* L); + + static lua_CFunction get(const std::string& key); + +protected: + using Registry = std::map<std::string, std::pair<lua_CFunction, std::string>>; + using Lookup = std::map<lua_CFunction, std::string>; + static std::pair<const Registry&, const Lookup&> getRState() { return getState(); } + +private: + static std::pair<Registry&, Lookup&> getState(); +}; + +/** + * lua_function(name, helptext) is a macro to facilitate defining C++ functions + * available to Lua. It defines a subclass of LuaFunction and declares a + * static instance of that subclass, thereby forcing the compiler to call its + * constructor at module initialization time. The constructor passes the + * stringized instance name to its LuaFunction base-class constructor, along + * with a pointer to the static subclass call() method. It then emits the + * call() method definition header, to be followed by a method body enclosed + * in curly braces as usual. + */ +#define lua_function(name, helptext) \ +static struct name##_luasub : public LuaFunction \ +{ \ + name##_luasub(): LuaFunction(#name, &call, helptext) {} \ + static int call(lua_State* L); \ +} name##_lua; \ +int name##_luasub::call(lua_State* L) +// { +// ... supply method body here, referencing 'L' ... +// } + +/***************************************************************************** +* lua_emplace<T>(), lua_toclass<T>() +*****************************************************************************/ +// Every instance of DistinctInt has a different int value, barring int +// wraparound. +class DistinctInt +{ +public: + DistinctInt(): mValue(++mValues) {} + int get() const { return mValue; } + operator int() const { return mValue; } +private: + static int mValues; + int mValue; +}; + +namespace { + +template <typename T> +struct TypeTag +{ + // For (std::is_same<T, U>), &TypeTag<T>::value == &TypeTag<U>::value. + // For (! std::is_same<T, U>), &TypeTag<T>::value != &TypeTag<U>::value. + // And every distinct instance of DistinctInt has a distinct value. + // Therefore, TypeTag<T>::value is an int uniquely associated with each + // distinct T. + static DistinctInt value; +}; + +template <typename T> +DistinctInt TypeTag<T>::value; + +} // anonymous namespace + +/** + * On the stack belonging to the passed lua_State, push a Lua userdata object + * containing a newly-constructed C++ object T(args...). The userdata has a + * Luau destructor guaranteeing that the new T instance is destroyed when the + * userdata is garbage-collected, no later than when the LuaState is + * destroyed. It may be destroyed explicitly by calling lua_destroyuserdata(). + * + * Usage: + * lua_emplace<T>(L, T constructor args...); + * // L's Lua stack top is now a userdata containing T + */ +template <class T, typename... ARGS> +void lua_emplace(lua_State* L, ARGS&&... args) +{ + lua_checkdelta(L, 1); + lluau_checkstack(L, 1); + int tag{ TypeTag<T>::value }; + if (! lua_getuserdatadtor(L, tag)) + { + // We haven't yet told THIS lua_State the destructor to use for this tag. + lua_setuserdatadtor( + L, tag, + [](lua_State*, void* ptr) + { + // destroy the contained T instance + static_cast<T*>(ptr)->~T(); + }); + } + auto ptr = lua_newuserdatatagged(L, sizeof(T), tag); + // stack is uninitialized userdata + // For now, assume (but verify) that lua_newuserdata() returns a + // conservatively-aligned ptr. If that turns out not to be the case, we + // might have to discard the new userdata, overallocate its successor and + // perform manual alignment -- but only if we must. + llassert((uintptr_t(ptr) % alignof(T)) == 0); + // Construct our T there using placement new + new (ptr) T(std::forward<ARGS>(args)...); + // stack is now initialized userdata containing our T instance -- return + // that +} + +/** + * If the value at the passed acceptable index is a full userdata created by + * lua_emplace<T>(), return a pointer to the contained T instance. Otherwise + * (index is not a full userdata; userdata is not of type T) return nullptr. + */ +template <class T> +T* lua_toclass(lua_State* L, int index) +{ + lua_checkdelta(L); + // get void* pointer to userdata (if that's what it is) + void* ptr{ lua_touserdatatagged(L, index, TypeTag<T>::value) }; + // Derive the T* from ptr. If in future lua_emplace() must manually + // align our T* within the Lua-provided void*, adjust accordingly. + return static_cast<T*>(ptr); +} + +/** + * Call lua_destroyuserdata() with the doomed userdata on the stack top. + * It must have been created by lua_emplace(). + */ +int lua_destroyuserdata(lua_State* L); + +/** + * Call lua_pushcclosure(L, lua_destroybounduserdata, 1) with the target + * userdata on the stack top. When the resulting C closure is called with no + * arguments, the bound userdata is destroyed by lua_destroyuserdata(). + */ +int lua_destroybounduserdata(lua_State *L); + +/***************************************************************************** +* lua_what() +*****************************************************************************/ +// Usage: std::cout << lua_what(L, stackindex) << ...; +// Reports on the Lua value found at the passed stackindex. +// If cast to std::string, returns the corresponding string value. +class lua_what +{ +public: + lua_what(lua_State* state, int idx): + L(state), + index(idx) + {} + + friend std::ostream& operator<<(std::ostream& out, const lua_what& self); + + operator std::string() const { return stringize(*this); } + +private: + lua_State* L; + int index; +}; + +/***************************************************************************** +* lua_stack() +*****************************************************************************/ +// Usage: std::cout << lua_stack(L) << ...; +// Reports on the contents of the Lua stack. +// If cast to std::string, returns the corresponding string value. +class lua_stack +{ +public: + lua_stack(lua_State* state): + L(state) + {} + + friend std::ostream& operator<<(std::ostream& out, const lua_stack& self); + + operator std::string() const { return stringize(*this); } + +private: + lua_State* L; +}; + +/***************************************************************************** +* LuaLog +*****************************************************************************/ +// adapted from indra/test/debug.h +// can't generalize Debug::operator() target because it's a variadic template +class LuaLog +{ +public: + template <typename... ARGS> + LuaLog(lua_State* L, ARGS&&... args): + L(L), + mBlock(stringize(std::forward<ARGS>(args)...)) + { + (*this)("entry ", lua_stack(L)); + } + + // non-copyable + LuaLog(const LuaLog&) = delete; + LuaLog& operator=(const LuaLog&) = delete; + + ~LuaLog() + { + auto exceptional{ std::uncaught_exceptions()? "exceptional " : "" }; + (*this)(exceptional, "exit ", lua_stack(L)); + } + + template <typename... ARGS> + void operator()(ARGS&&... args) + { + LL_DEBUGS("Lua") << mBlock << ' '; + stream_to(LL_CONT, std::forward<ARGS>(args)...); + LL_ENDL; + } + +private: + lua_State* L; + const std::string mBlock; +}; + +#endif /* ! defined(LL_LUA_FUNCTION_H) */ diff --git a/indra/llcommon/lualistener.cpp b/indra/llcommon/lualistener.cpp new file mode 100644 index 0000000000..94085c6798 --- /dev/null +++ b/indra/llcommon/lualistener.cpp @@ -0,0 +1,102 @@ +/** + * @file lualistener.cpp + * @author Nat Goodspeed + * @date 2024-02-06 + * @brief Implementation for lualistener. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "lualistener.h" +// STL headers +// std headers +#include <iomanip> // std::quoted() +// external library headers +#include "luau/lua.h" +// other Linden headers +#include "llerror.h" +#include "llleaplistener.h" +#include "lua_function.h" + +const int MAX_QSIZE = 1000; + +std::ostream& operator<<(std::ostream& out, const LuaListener& self) +{ + return out << "LuaListener(" << std::quoted(self.mCoroName) << ", " + << self.getReplyName() << ", " << self.getCommandName() << ")"; +} + +LuaListener::LuaListener(lua_State* L): + mCoroName(LLCoros::getName()), + mListener(new LLLeapListener( + "LuaListener", + [this](const std::string& pump, const LLSD& data) + { return queueEvent(pump, data); })), + // Listen for shutdown events. + mShutdownConnection( + LLCoros::getStopListener( + "LuaState", + mCoroName, + [this](const LLSD&) + { + // If a Lua script is still blocked in getNext() during + // viewer shutdown, close the queue to wake up getNext(). + mQueue.close(); + })) +{ + LL_DEBUGS("Lua") << "LuaListener(" << std::quoted(mCoroName) << ")" << LL_ENDL; +} + +LuaListener::~LuaListener() +{ + LL_DEBUGS("Lua") << "~LuaListener(" << std::quoted(mCoroName) << ")" << LL_ENDL; +} + +std::string LuaListener::getReplyName() const +{ + return mListener->getReplyPump().getName(); +} + +std::string LuaListener::getCommandName() const +{ + return mListener->getPumpName(); +} + +bool LuaListener::queueEvent(const std::string& pump, const LLSD& data) +{ + // Our Lua script might be stalled, or just fail to retrieve events. Don't + // grow this queue indefinitely. But don't set MAX_QSIZE as the queue + // capacity or we'd block the post() call trying to propagate this event! + if (auto size = mQueue.size(); size > MAX_QSIZE) + { + LL_WARNS("Lua") << "LuaListener queue for " << mCoroName + << " exceeds " << MAX_QSIZE << ": " << size + << " -- discarding event" << LL_ENDL; + } + else + { + mQueue.push(decltype(mQueue)::value_type(pump, data)); + } + return false; +} + +LuaListener::PumpData LuaListener::getNext() +{ + try + { + LLCoros::TempStatus status("get_event_next()"); + return mQueue.pop(); + } + catch (const LLThreadSafeQueueInterrupt&) + { + // mQueue has been closed. The only way that happens is when we detect + // viewer shutdown. Terminate the calling Lua coroutine. + LLCoros::checkStop(); + return {}; + } +} diff --git a/indra/llcommon/lualistener.h b/indra/llcommon/lualistener.h new file mode 100644 index 0000000000..4c0a2a5c87 --- /dev/null +++ b/indra/llcommon/lualistener.h @@ -0,0 +1,65 @@ +/** + * @file lualistener.h + * @author Nat Goodspeed + * @date 2024-02-06 + * @brief Define LuaListener class + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LUALISTENER_H) +#define LL_LUALISTENER_H + +#include "llevents.h" // LLTempBoundListener +#include "llsd.h" +#include "llthreadsafequeue.h" +#include <iosfwd> // std::ostream +#include <memory> // std::unique_ptr +#include <string> +#include <utility> // std::pair + +struct lua_State; +class LLLeapListener; + +/** + * LuaListener is based on LLLeap. It serves an analogous function. + * + * Like LLLeap, each LuaListener instance also has an associated + * LLLeapListener to respond to LLEventPump management commands. + */ +class LuaListener +{ +public: + LuaListener(lua_State* L); + + LuaListener(const LuaListener&) = delete; + LuaListener& operator=(const LuaListener&) = delete; + + ~LuaListener(); + + std::string getReplyName() const; + std::string getCommandName() const; + + /** + * LuaListener enqueues reply events from its LLLeapListener on mQueue. + * Call getNext() to retrieve the next such event. Blocks the calling + * coroutine if the queue is empty. + */ + using PumpData = std::pair<std::string, LLSD>; + PumpData getNext(); + + friend std::ostream& operator<<(std::ostream& out, const LuaListener& self); + +private: + bool queueEvent(const std::string& pump, const LLSD& data); + + LLThreadSafeQueue<PumpData> mQueue; + + std::string mCoroName; + std::unique_ptr<LLLeapListener> mListener; + LLTempBoundListener mShutdownConnection; +}; + +#endif /* ! defined(LL_LUALISTENER_H) */ diff --git a/indra/llcommon/resultset.cpp b/indra/llcommon/resultset.cpp new file mode 100644 index 0000000000..4d7b00eabd --- /dev/null +++ b/indra/llcommon/resultset.cpp @@ -0,0 +1,96 @@ +/** + * @file resultset.cpp + * @author Nat Goodspeed + * @date 2024-09-03 + * @brief Implementation for resultset. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "resultset.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "llerror.h" +#include "llsdutil.h" + +namespace LL +{ + +LLSD ResultSet::getKeyLength() const +{ + return llsd::array(getKey(), getLength()); +} + +std::pair<LLSD, int> ResultSet::getSliceStart(int index, int count) const +{ + // only call getLength() once + auto length = getLength(); + // Adjust bounds [start, end) to overlap the actual result set from + // [0, getLength()). Permit negative index; e.g. with a result set + // containing 5 entries, getSlice(-2, 5) will adjust start to 0 and + // end to 3. + int start = llclamp(index, 0, length); + int end = llclamp(index + count, 0, length); + LLSD result{ LLSD::emptyArray() }; + // beware of count <= 0, or an [index, count) range that doesn't even + // overlap [0, length) at all + if (end > start) + { + // Right away expand the result array to the size we'll need. + // (end - start) is that size; (end - start - 1) is the index of the + // last entry in result. + result[end - start - 1] = LLSD(); + for (int i = 0; (start + i) < end; ++i) + { + // For this to be a slice, set result[0] = getSingle(start), etc. + result[i] = getSingle(start + i); + } + } + return { result, start }; +} + +LLSD ResultSet::getSlice(int index, int count) const +{ + return getSliceStart(index, count).first; +} + +/*==========================================================================*| +LLSD ResultSet::getSingle(int index) const +{ + if (0 <= index && index < getLength()) + { + return getSingle_(index); + } + else + { + return {}; + } +} +|*==========================================================================*/ + +ResultSet::ResultSet(const std::string& name): + mName(name) +{ + LL_DEBUGS("Lua") << *this << LL_ENDL; +} + +ResultSet::~ResultSet() +{ + // We want to be able to observe that the consuming script eventually + // destroys each of these ResultSets. + LL_DEBUGS("Lua") << "~" << *this << LL_ENDL; +} + +} // namespace LL + +std::ostream& operator<<(std::ostream& out, const LL::ResultSet& self) +{ + return out << "ResultSet(" << self.mName << ", " << self.getKey() << ")"; +} diff --git a/indra/llcommon/resultset.h b/indra/llcommon/resultset.h new file mode 100644 index 0000000000..90d52b6fe4 --- /dev/null +++ b/indra/llcommon/resultset.h @@ -0,0 +1,61 @@ +/** + * @file resultset.h + * @author Nat Goodspeed + * @date 2024-09-03 + * @brief ResultSet is an abstract base class to allow scripted access to + * potentially large collections representable as LLSD arrays. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_RESULTSET_H) +#define LL_RESULTSET_H + +#include "llinttracker.h" +#include "llsd.h" +#include <iosfwd> // std::ostream +#include <utility> // std::pair + +namespace LL +{ + +// This abstract base class defines an interface by which a large collection +// of items representable as an LLSD array can be retrieved in slices. It isa +// LLIntTracker so we can pass its unique int key to a consuming script via +// LLSD. +struct ResultSet: public LLIntTracker<ResultSet> +{ + // Get the length of the result set. Indexes are 0-relative. + virtual int getLength() const = 0; + // Get conventional LLSD { key, length } pair. + LLSD getKeyLength() const; + // Retrieve LLSD corresponding to a single entry from the result set, + // once we're sure the index is valid. + virtual LLSD getSingle(int index) const = 0; + // Retrieve LLSD corresponding to a "slice" of the result set: a + // contiguous sub-array starting at index. The returned LLSD array might + // be shorter than count entries if count > MAX_ITEM_LIMIT, or if the + // specified slice contains the end of the result set. + LLSD getSlice(int index, int count) const; + // Like getSlice(), but also return adjusted start position. + std::pair<LLSD, int> getSliceStart(int index, int count) const; +/*==========================================================================*| + // Retrieve LLSD corresponding to a single entry from the result set, + // with index validation. + LLSD getSingle(int index) const; +|*==========================================================================*/ + + /*---------------- the rest is solely for debug logging ----------------*/ + std::string mName; + + ResultSet(const std::string& name); + virtual ~ResultSet(); +}; + +} // namespace LL + +std::ostream& operator<<(std::ostream& out, const LL::ResultSet& self); + +#endif /* ! defined(LL_RESULTSET_H) */ diff --git a/indra/llcommon/scope_exit.h b/indra/llcommon/scope_exit.h new file mode 100644 index 0000000000..eb4df4f74d --- /dev/null +++ b/indra/llcommon/scope_exit.h @@ -0,0 +1,34 @@ +/** + * @file scope_exit.h + * @author Nat Goodspeed + * @date 2024-08-15 + * @brief Cheap imitation of std::experimental::scope_exit + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_SCOPE_EXIT_H) +#define LL_SCOPE_EXIT_H + +#include <functional> + +namespace LL +{ + +class scope_exit +{ +public: + scope_exit(const std::function<void()>& func): mFunc(func) {} + scope_exit(const scope_exit&) = delete; + scope_exit& operator=(const scope_exit&) = delete; + ~scope_exit() { mFunc(); } + +private: + std::function<void()> mFunc; +}; + +} // namespace LL + +#endif /* ! defined(LL_SCOPE_EXIT_H) */ diff --git a/indra/llcommon/scriptcommand.cpp b/indra/llcommon/scriptcommand.cpp new file mode 100644 index 0000000000..79afbc2063 --- /dev/null +++ b/indra/llcommon/scriptcommand.cpp @@ -0,0 +1,117 @@ +/** + * @file scriptcommand.cpp + * @author Nat Goodspeed + * @date 2024-09-16 + * @brief Implementation for scriptcommand. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "scriptcommand.h" +// STL headers +// std headers +#include <sstream> +// external library headers +// other Linden headers +#include "fsyspath.h" +#include "llerror.h" +#include "llsdutil.h" +#include "llstring.h" +#include "stringize.h" + +ScriptCommand::ScriptCommand(const std::string& command, const LLSD& path, + const std::string& base) +{ + fsyspath basepath(base); + // Use LLStringUtil::getTokens() to parse the script command line + args = LLStringUtil::getTokens(command, + " \t\r\n", // drop_delims + "", // no keep_delims + "\"'", // either kind of quotes + "\\"); // backslash escape + // search for args[0] on paths + if (search(args[0], path, basepath)) + { + // The first token is in fact the script filename. Now that we've + // found the script file, we've consumed that token. The rest are + // command-line arguments. + args.erase(args.begin()); + return; + } + + // Parsing the command line produced a script file path we can't find. + // Maybe that's because there are spaces in the original pathname that + // were neither quoted nor escaped? See if we can find the whole original + // command line string. + if (search(command, path, basepath)) + { + // Here we found script, using the whole input command line as its + // pathname. Discard any parts of it we mistook for command-line + // arguments. + args.clear(); + return; + } + + // Couldn't find the script either way. Is it because we can't even check + // existence? + if (! mError.empty()) + { + return; + } + + // No, existence check works, we just can't find the script. + std::ostringstream msgstream; + msgstream << "Can't find script file " << std::quoted(args[0]); + if (command != args[0]) + { + msgstream << " or " << std::quoted(command); + } + if (path.size() > 0) + { + msgstream << " on " << path; + } + if (! base.empty()) + { + msgstream << " relative to " << base; + } + mError = msgstream.str(); + LL_WARNS("Lua") << mError << LL_ENDL; +} + +bool ScriptCommand::search(const fsyspath& script, const LLSD& paths, const fsyspath& base) +{ + for (const auto& path : llsd::inArray(paths)) + { + // If a path is already absolute, (otherpath / path) preserves it. + // Explicitly instantiate fsyspath for every string conversion to + // properly convert UTF-8 filename strings on Windows. + fsyspath absscript{ base / fsyspath(path.asString()) / script }; + bool exists; + try + { + exists = std::filesystem::exists(absscript); + } + catch (const std::filesystem::filesystem_error& exc) + { + mError = stringize("Can't check existence: ", exc.what()); + LL_WARNS("Lua") << mError << LL_ENDL; + return false; + } + if (exists) + { + this->script = absscript.string(); + return true; + } + } + return false; +} + +std::string ScriptCommand::error() const +{ + return mError; +} diff --git a/indra/llcommon/scriptcommand.h b/indra/llcommon/scriptcommand.h new file mode 100644 index 0000000000..dafed552bb --- /dev/null +++ b/indra/llcommon/scriptcommand.h @@ -0,0 +1,64 @@ +/** + * @file scriptcommand.h + * @author Nat Goodspeed + * @date 2024-09-16 + * @brief ScriptCommand parses a string into a script filepath plus arguments. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_SCRIPTCOMMAND_H) +#define LL_SCRIPTCOMMAND_H + +#include "llsd.h" + +#include <string> +#include <vector> + +class fsyspath; + +class ScriptCommand +{ +public: + /** + * ScriptCommand accepts a command-line string to invoke an existing + * script, which may or may not include arguments to pass to that script. + * The constructor parses the command line into tokens using quoting and + * escaping rules similar to bash. The first token is assumed to be the + * script name; ScriptCommand tries to find that script on the filesystem, + * trying each directory listed in the LLSD array of strings 'path'. When + * it finds the script file, it stores its full pathname in 'script' and + * the remaining tokens in 'args'. + * + * But if the constructor can't find the first token on 'path', it + * considers the possibility that the whole command-line string is + * actually a single pathname containing unescaped spaces. If that script + * file is found on 'path', it stores its full pathname in 'script' and + * leaves 'args' empty. + * + * We accept 'path' as an LLSD array rather than (e.g.) + * std::vector<std::string> because the primary use case involves + * retrieving 'path' from settings. + * + * If you also pass 'base', any directory on 'path' may be specified + * relative to 'base'. Otherwise, every directory on 'path' must be + * absolute. + */ + ScriptCommand(const std::string& command, const LLSD& path=LLSD::emptyArray(), + const std::string& base={}); + + std::string script; + std::vector<std::string> args; + + // returns empty string if no error + std::string error() const; + +private: + bool search(const fsyspath& script, const LLSD& path, const fsyspath& base); + + std::string mError; +}; + +#endif /* ! defined(LL_SCRIPTCOMMAND_H) */ diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h index 536a18abc1..2730728637 100644 --- a/indra/llcommon/stringize.h +++ b/indra/llcommon/stringize.h @@ -88,13 +88,13 @@ struct gstringize_impl }; // partially specialize for a single STRING argument - -// note that ll_convert<T>(T) already handles the trivial case +// note that ll_convert_to<T>(T) already handles the trivial case template <typename OUTCHAR, typename INCHAR> struct gstringize_impl<OUTCHAR, std::basic_string<INCHAR>> { auto operator()(const std::basic_string<INCHAR>& arg) { - return ll_convert<std::basic_string<OUTCHAR>>(arg); + return ll_convert_to<std::basic_string<OUTCHAR>>(arg); } }; @@ -105,7 +105,7 @@ struct gstringize_impl<OUTCHAR, INCHAR*> { auto operator()(const INCHAR* arg) { - return ll_convert<std::basic_string<OUTCHAR>>(arg); + return ll_convert_to<std::basic_string<OUTCHAR>>(arg); } }; @@ -190,16 +190,4 @@ void destringize_f(std::basic_string<CHARTYPE> const & str, Functor const & f) f(in); } -/** - * DESTRINGIZE(str, item1 >> item2 >> item3 ...) effectively expands to the - * following: - * @code - * std::istringstream in(str); - * in >> item1 >> item2 >> item3 ... ; - * @endcode - */ -#define DESTRINGIZE(STR, EXPRESSION) (destringize_f((STR), [&](auto& in){in >> EXPRESSION;})) -// legacy name, just use DESTRINGIZE() going forward -#define DEWSTRINGIZE(STR, EXPRESSION) DESTRINGIZE(STR, EXPRESSION) - #endif /* ! defined(LL_STRINGIZE_H) */ diff --git a/indra/llcommon/tempset.h b/indra/llcommon/tempset.h new file mode 100755 index 0000000000..773f19f756 --- /dev/null +++ b/indra/llcommon/tempset.h @@ -0,0 +1,41 @@ +/** + * @file tempset.h + * @author Nat Goodspeed + * @date 2024-06-12 + * @brief Temporarily override a variable for scope duration, then restore + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_TEMPSET_H) +#define LL_TEMPSET_H + +// RAII class to set specified variable to specified value +// only for the duration of containing scope +template <typename VAR, typename VALUE> +class TempSet +{ +public: + TempSet(VAR& var, const VALUE& value): + mVar(var), + mOldValue(mVar) + { + mVar = value; + } + + TempSet(const TempSet&) = delete; + TempSet& operator=(const TempSet&) = delete; + + ~TempSet() + { + mVar = mOldValue; + } + +private: + VAR& mVar; + VAR mOldValue; +}; + +#endif /* ! defined(LL_TEMPSET_H) */ diff --git a/indra/llcommon/tests/StringVec.h b/indra/llcommon/tests/StringVec.h index 4311cba992..3f8665affb 100644 --- a/indra/llcommon/tests/StringVec.h +++ b/indra/llcommon/tests/StringVec.h @@ -18,6 +18,16 @@ typedef std::vector<std::string> StringVec; +#if defined(LL_LLTUT_H) +// Modern compilers require us to define operator<<(std::ostream&, StringVec) +// before the definition of the ensure() template that engages it. The error +// stating that the compiler can't find a viable operator<<() is so perplexing +// that even though I've obviously hit it a couple times before, a new +// instance still caused much head-scratching. This warning is intended to +// demystify any inadvertent future recurrence. +#warning "StringVec.h must be #included BEFORE lltut.h for ensure() to work" +#endif + std::ostream& operator<<(std::ostream& out, const StringVec& strings) { out << '('; diff --git a/indra/llcommon/tests/llcond_test.cpp b/indra/llcommon/tests/llcond_test.cpp index f2a302ed13..7d1ac6edb9 100644 --- a/indra/llcommon/tests/llcond_test.cpp +++ b/indra/llcommon/tests/llcond_test.cpp @@ -19,6 +19,7 @@ // other Linden headers #include "../test/lltut.h" #include "llcoros.h" +#include "lldefs.h" // llless() /***************************************************************************** * TUT @@ -64,4 +65,78 @@ namespace tut cond.set_all(2); cond.wait_equal(3); } + + template <typename T0, typename T1> + struct compare + { + const char* desc; + T0 lhs; + T1 rhs; + bool expect; + + void test() const + { + // fails +// ensure_equals(desc, (lhs < rhs), expect); + ensure_equals(desc, llless(lhs, rhs), expect); + } + }; + + template<> template<> + void object::test<3>() + { + set_test_name("comparison"); + // Try to construct signed and unsigned variables such that the + // compiler can't optimize away the code to compare at runtime. + std::istringstream input("-1 10 20 10 20"); + int minus1, s10, s20; + input >> minus1 >> s10 >> s20; + unsigned u10, u20; + input >> u10 >> u20; + ensure_equals("minus1 wrong", minus1, -1); + ensure_equals("s10 wrong", s10, 10); + ensure_equals("s20 wrong", s20, 20); + ensure_equals("u10 wrong", u10, 10); + ensure_equals("u20 wrong", u20, 20); + // signed < signed should always work! + compare<int, int> ss[] = + { {"minus1 < s10", minus1, s10, true}, + {"s10 < s10", s10, s10, false}, + {"s20 < s10", s20, s20, false} + }; + for (const auto& cmp : ss) + { + cmp.test(); + } + // unsigned < unsigned should always work! + compare<unsigned, unsigned> uu[] = + { {"u10 < u20", u10, u20, true}, + {"u20 < u20", u20, u20, false}, + {"u20 < u10", u20, u10, false} + }; + for (const auto& cmp : uu) + { + cmp.test(); + } + // signed < unsigned ?? + compare<int, unsigned> su[] = + { {"minus1 < u10", minus1, u10, true}, + {"s10 < u10", s10, u10, false}, + {"s20 < u10", s20, u10, false} + }; + for (const auto& cmp : su) + { + cmp.test(); + } + // unsigned < signed ?? + compare<unsigned, int> us[] = + { {"u10 < minus1", u10, minus1, false}, + {"u10 < s10", u10, s10, false}, + {"u10 < s20", u10, s20, true} + }; + for (const auto& cmp : us) + { + cmp.test(); + } + } } // namespace tut diff --git a/indra/llcommon/tests/llerror_test.cpp b/indra/llcommon/tests/llerror_test.cpp index 3ec429530c..d597e90ba0 100644 --- a/indra/llcommon/tests/llerror_test.cpp +++ b/indra/llcommon/tests/llerror_test.cpp @@ -112,10 +112,10 @@ namespace tut mMessages.push_back(message); } - int countMessages() { return (int) mMessages.size(); } + int countMessages() const { return (int) mMessages.size(); } void clearMessages() { mMessages.clear(); } - std::string message(int n) + std::string message(int n) const { std::ostringstream test_name; test_name << "testing message " << n << ", not enough messages"; @@ -124,6 +124,16 @@ namespace tut return mMessages[n]; } + void reportMessages() const + { + std::cerr << '\n'; + int n = 0; + for (const auto& msg : mMessages) + { + std::cerr << std::setw(2) << n++ << ": " << msg.substr(0, 100) << '\n'; + } + } + private: typedef std::vector<std::string> MessageVector; MessageVector mMessages; @@ -134,6 +144,8 @@ namespace tut LLError::RecorderPtr mRecorder; LLError::SettingsStoragePtr mPriorErrorSettings; + auto recorder() { return std::dynamic_pointer_cast<TestRecorder>(mRecorder); } + ErrorTestData(): mRecorder(new TestRecorder()) { @@ -153,27 +165,32 @@ namespace tut int countMessages() { - return std::dynamic_pointer_cast<TestRecorder>(mRecorder)->countMessages(); + return recorder()->countMessages(); } void clearMessages() { - std::dynamic_pointer_cast<TestRecorder>(mRecorder)->clearMessages(); + recorder()->clearMessages(); } void setWantsTime(bool t) - { - std::dynamic_pointer_cast<TestRecorder>(mRecorder)->showTime(t); - } + { + recorder()->showTime(t); + } void setWantsMultiline(bool t) - { - std::dynamic_pointer_cast<TestRecorder>(mRecorder)->showMultiline(t); - } + { + recorder()->showMultiline(t); + } std::string message(int n) { - return std::dynamic_pointer_cast<TestRecorder>(mRecorder)->message(n); + return recorder()->message(n); + } + + void reportMessages() + { + recorder()->reportMessages(); } void ensure_message_count(int expectedCount) @@ -204,7 +221,7 @@ namespace tut on_field++; } // except function, which may have embedded spaces so ends with " : " - else if ( ( on_field == FUNCTION_FIELD ) + else if (( on_field == FUNCTION_FIELD ) && ( ':' == msg[scan+1] && ' ' == msg[scan+2] ) ) { @@ -233,19 +250,35 @@ namespace tut } void ensure_message_field_equals(int msgnum, LogFieldIndex fieldnum, const std::string& expectedText) - { - std::ostringstream test_name; - test_name << "testing message " << msgnum << " field " << FieldName[fieldnum] << "\n message: \"" << message(msgnum) << "\"\n "; + { + std::ostringstream test_name; + test_name << "testing message " << msgnum << " field " << FieldName[fieldnum] << "\n message: \"" << message(msgnum).substr(0, 100) << "\"\n "; - ensure_equals(test_name.str(), message_field(msgnum, fieldnum), expectedText); - } + try + { + ensure_equals(test_name.str(), message_field(msgnum, fieldnum), expectedText); + } + catch (const failure&) + { + reportMessages(); + throw; + } + } void ensure_message_does_not_contain(int n, const std::string& expectedText) { std::ostringstream test_name; test_name << "testing message " << n; - ensure_does_not_contain(test_name.str(), message(n), expectedText); + try + { + ensure_does_not_contain(test_name.str(), message(n), expectedText); + } + catch (const failure&) + { + reportMessages(); + throw; + } } }; @@ -297,29 +330,33 @@ namespace tut ensure_message_field_equals(3, MSG_FIELD, "four"); ensure_message_field_equals(3, LEVEL_FIELD, "ERROR"); ensure_message_field_equals(3, TAGS_FIELD, "#WriteTag#"); - ensure_message_count(4); + // LL_ERRS() produces 2 recordMessage() calls + ensure_message_count(5); LLError::setDefaultLevel(LLError::LEVEL_INFO); writeSome(); - ensure_message_field_equals(4, MSG_FIELD, "two"); - ensure_message_field_equals(5, MSG_FIELD, "three"); - ensure_message_field_equals(6, MSG_FIELD, "four"); - ensure_message_count(7); + ensure_message_field_equals(5, MSG_FIELD, "two"); + ensure_message_field_equals(6, MSG_FIELD, "three"); + ensure_message_field_equals(7, MSG_FIELD, "four"); + // LL_ERRS() produces 2 recordMessage() calls + ensure_message_count(9); LLError::setDefaultLevel(LLError::LEVEL_WARN); writeSome(); - ensure_message_field_equals(7, MSG_FIELD, "three"); - ensure_message_field_equals(8, MSG_FIELD, "four"); - ensure_message_count(9); + ensure_message_field_equals(9, MSG_FIELD, "three"); + ensure_message_field_equals(10, MSG_FIELD, "four"); + // LL_ERRS() produces 2 recordMessage() calls + ensure_message_count(12); LLError::setDefaultLevel(LLError::LEVEL_ERROR); writeSome(); - ensure_message_field_equals(9, MSG_FIELD, "four"); - ensure_message_count(10); + ensure_message_field_equals(12, MSG_FIELD, "four"); + // LL_ERRS() produces 2 recordMessage() calls + ensure_message_count(14); LLError::setDefaultLevel(LLError::LEVEL_NONE); writeSome(); - ensure_message_count(10); + ensure_message_count(14); } template<> template<> @@ -331,7 +368,8 @@ namespace tut ensure_message_field_equals(1, LEVEL_FIELD, "INFO"); ensure_message_field_equals(2, LEVEL_FIELD, "WARNING"); ensure_message_field_equals(3, LEVEL_FIELD, "ERROR"); - ensure_message_count(4); + // LL_ERRS() produces 2 recordMessage() calls + ensure_message_count(5); } template<> template<> @@ -627,7 +665,8 @@ namespace tut ensure_message_field_equals(0, LOCATION_FIELD, location); ensure_message_field_equals(0, MSG_FIELD, "die"); - ensure_message_count(1); + // LL_ERRS() produces 2 recordMessage() calls + ensure_message_count(2); ensure("fatal callback called", fatalWasCalled); } @@ -751,10 +790,12 @@ namespace tut ensure_message_field_equals(0, MSG_FIELD, "aim west"); ensure_message_field_equals(1, MSG_FIELD, "ate eels"); - ensure_message_field_equals(2, MSG_FIELD, "buy iron"); - ensure_message_field_equals(3, MSG_FIELD, "bad word"); - ensure_message_field_equals(4, MSG_FIELD, "big easy"); - ensure_message_count(5); + // LL_ERRS() produces 2 recordMessage() calls + ensure_message_field_equals(3, MSG_FIELD, "buy iron"); + ensure_message_field_equals(4, MSG_FIELD, "bad word"); + ensure_message_field_equals(5, MSG_FIELD, "big easy"); + // LL_ERRS() produces 2 recordMessage() calls + ensure_message_count(7); } template<> template<> @@ -868,9 +909,11 @@ namespace tut TestBeta::doAll(); ensure_message_field_equals(3, MSG_FIELD, "aim west"); ensure_message_field_equals(4, MSG_FIELD, "ate eels"); - ensure_message_field_equals(5, MSG_FIELD, "bad word"); - ensure_message_field_equals(6, MSG_FIELD, "big easy"); - ensure_message_count(7); + // LL_ERRS() produces 2 recordMessage() calls + ensure_message_field_equals(6, MSG_FIELD, "bad word"); + ensure_message_field_equals(7, MSG_FIELD, "big easy"); + // LL_ERRS() produces 2 recordMessage() calls + ensure_message_count(9); } } diff --git a/indra/llcommon/tests/lleventcoro_test.cpp b/indra/llcommon/tests/lleventcoro_test.cpp index ab174a8bde..0e352bce0f 100644 --- a/indra/llcommon/tests/lleventcoro_test.cpp +++ b/indra/llcommon/tests/lleventcoro_test.cpp @@ -111,14 +111,13 @@ namespace tut void test_data::explicit_wait(std::shared_ptr<LLCoros::Promise<std::string>>& cbp) { - BEGIN - { - mSync.bump(); - // The point of this test is to verify / illustrate suspending a - // coroutine for something other than an LLEventPump. In other - // words, this shows how to adapt to any async operation that - // provides a callback-style notification (and prove that it - // works). + DEBUG; + mSync.bump(); + // The point of this test is to verify / illustrate suspending a + // coroutine for something other than an LLEventPump. In other + // words, this shows how to adapt to any async operation that + // provides a callback-style notification (and prove that it + // works). // Perhaps we would send a request to a remote server and arrange // for cbp->set_value() to be called on response. @@ -128,13 +127,11 @@ namespace tut cbp = std::make_shared<LLCoros::Promise<std::string>>(); LLCoros::Future<std::string> future = LLCoros::getFuture(*cbp); - // calling get() on the future causes us to suspend - debug("about to suspend"); - stringdata = future.get(); - mSync.bump(); - ensure_equals("Got it", stringdata, "received"); - } - END + // calling get() on the future causes us to suspend + debug("about to suspend"); + stringdata = future.get(); + mSync.bump(); + ensure_equals("Got it", stringdata, "received"); } template<> template<> @@ -161,13 +158,9 @@ namespace tut void test_data::waitForEventOn1() { - BEGIN - { - mSync.bump(); - result = suspendUntilEventOn("source"); - mSync.bump(); - } - END + mSync.bump(); + result = suspendUntilEventOn("source"); + mSync.bump(); } template<> template<> @@ -187,15 +180,11 @@ namespace tut void test_data::coroPump() { - BEGIN - { - mSync.bump(); - LLCoroEventPump waiter; - replyName = waiter.getName(); - result = waiter.suspend(); - mSync.bump(); - } - END + mSync.bump(); + LLCoroEventPump waiter; + replyName = waiter.getName(); + result = waiter.suspend(); + mSync.bump(); } template<> template<> @@ -215,16 +204,12 @@ namespace tut void test_data::postAndWait1() { - BEGIN - { - mSync.bump(); - result = postAndSuspend(LLSDMap("value", 17), // request event - immediateAPI.getPump(), // requestPump - "reply1", // replyPump - "reply"); // request["reply"] = name - mSync.bump(); - } - END + mSync.bump(); + result = postAndSuspend(LLSDMap("value", 17), // request event + immediateAPI.getPump(), // requestPump + "reply1", // replyPump + "reply"); // request["reply"] = name + mSync.bump(); } template<> template<> @@ -238,15 +223,11 @@ namespace tut void test_data::coroPumpPost() { - BEGIN - { - mSync.bump(); - LLCoroEventPump waiter; - result = waiter.postAndSuspend(LLSDMap("value", 17), - immediateAPI.getPump(), "reply"); - mSync.bump(); - } - END + mSync.bump(); + LLCoroEventPump waiter; + result = waiter.postAndSuspend(LLSDMap("value", 17), + immediateAPI.getPump(), "reply"); + mSync.bump(); } template<> template<> diff --git a/indra/llcommon/tests/lleventdispatcher_test.cpp b/indra/llcommon/tests/lleventdispatcher_test.cpp index 44f772e322..be05ca4e85 100644 --- a/indra/llcommon/tests/lleventdispatcher_test.cpp +++ b/indra/llcommon/tests/lleventdispatcher_test.cpp @@ -17,13 +17,13 @@ // std headers // external library headers // other Linden headers +#include "StringVec.h" #include "../test/lltut.h" #include "lleventfilter.h" #include "llsd.h" #include "llsdutil.h" #include "llevents.h" #include "stringize.h" -#include "StringVec.h" #include "tests/wrapllerrs.h" #include "../test/catch_and_store_what_in.h" #include "../test/debug.h" diff --git a/indra/llcommon/tests/lleventfilter_test.cpp b/indra/llcommon/tests/lleventfilter_test.cpp index d7b80e2545..9ca1ca4e2e 100644 --- a/indra/llcommon/tests/lleventfilter_test.cpp +++ b/indra/llcommon/tests/lleventfilter_test.cpp @@ -34,10 +34,10 @@ // std headers // external library headers // other Linden headers +#include "listener.h" #include "../test/lltut.h" #include "stringize.h" #include "llsdutil.h" -#include "listener.h" #include "tests/wrapllerrs.h" #include <typeinfo> @@ -51,6 +51,7 @@ // as we've carefully put all functionality except actual LLTimer calls into // LLEventTimeoutBase, that should suffice. We're not not not trying to test // LLTimer here. +#if 0 // time testing needs reworking class TestEventTimeout: public LLEventTimeoutBase { public: @@ -151,6 +152,7 @@ public: F32 mAlarmRemaining, mTimerRemaining; LLEventTimeoutBase::Action mAlarmAction; }; +#endif // time testing needs reworking /***************************************************************************** * TUT @@ -220,6 +222,8 @@ namespace tut void filter_object::test<2>() { set_test_name("LLEventTimeout::actionAfter()"); + skip("time testing needs reworking"); +#if 0 // time testing needs reworking LLEventPump& driver(pumps.obtain("driver")); TestEventTimeout filter(driver); listener0.reset(0); @@ -285,12 +289,15 @@ namespace tut filter.forceTimeout(); mainloop.post(17); check_listener("no timeout 6", listener1, LLSD(0)); +#endif // time testing needs reworking } template<> template<> void filter_object::test<3>() { set_test_name("LLEventTimeout::eventAfter()"); + skip("time testing needs reworking"); +#if 0 // time testing needs reworking LLEventPump& driver(pumps.obtain("driver")); TestEventTimeout filter(driver); listener0.reset(0); @@ -322,12 +329,15 @@ namespace tut filter.forceTimeout(); mainloop.post(17); check_listener("no timeout 3", listener0, LLSD(0)); +#endif // time testing needs reworking } template<> template<> void filter_object::test<4>() { set_test_name("LLEventTimeout::errorAfter()"); + skip("time testing needs reworking"); +#if 0 // time testing needs reworking WrapLLErrs capture; LLEventPump& driver(pumps.obtain("driver")); TestEventTimeout filter(driver); @@ -362,12 +372,15 @@ namespace tut filter.forceTimeout(); mainloop.post(17); check_listener("no timeout 3", listener0, LLSD(0)); +#endif // time testing needs reworking } template<> template<> void filter_object::test<5>() { set_test_name("LLEventThrottle"); + skip("time testing needs reworking"); +#if 0 // time testing needs reworking TestEventThrottle throttle(3); Concat cat; throttle.listen("concat", boost::ref(cat)); @@ -403,6 +416,7 @@ namespace tut throttle.advance(5); throttle.post(";17"); ensure_equals("17", cat.result, "136;12;17"); // "17" delivered +#endif // time testing needs reworking } template<class PUMP> diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index fa48bcdefd..773b373e72 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -18,6 +18,7 @@ #include <functional> // external library headers // other Linden headers +#include "StringVec.h" #include "../test/lltut.h" #include "../test/namedtempfile.h" #include "../test/catch_and_store_what_in.h" @@ -26,7 +27,6 @@ #include "llprocess.h" #include "llstring.h" #include "stringize.h" -#include "StringVec.h" #if defined(LL_WINDOWS) #define sleep(secs) _sleep((secs) * 1000) @@ -35,7 +35,7 @@ // causes Windows abdominal pain such that it later fails code-signing in some // mysterious way. Entirely suppressing these LLLeap tests pushes the failure // rate MUCH lower. Can we re-enable them with a smaller data size on Windows? -const size_t BUFFERED_LENGTH = 100*1024; +const size_t BUFFERED_LENGTH = 1023*1024; #else // not Windows const size_t BUFFERED_LENGTH = 1023*1024; // try wrangling just under a megabyte of data @@ -300,8 +300,8 @@ namespace tut std::string threw = catch_what<LLLeap::Error>([&BADPYTHON](){ LLLeap::create("bad exe", BADPYTHON); }); - ensure_contains("LLLeap::create() didn't throw", threw, "failed"); - log.messageWith("failed"); + ensure_contains("LLLeap::create() didn't throw", threw, "Can't find"); + log.messageWith("Can't find"); log.messageWith(BADPYTHON); // try the suppress-exception variant ensure("bad launch returned non-NULL", ! LLLeap::create("bad exe", BADPYTHON, false)); @@ -385,8 +385,7 @@ namespace tut "result = '' if resp == dict(pump=replypump(), data='ack')\\\n" " else 'bad: ' + str(resp)\n" "send(pump='" << result.getName() << "', data=result)\n";}); - waitfor(LLLeap::create(get_test_name(), - StringVec{PYTHON, script.getName()})); + waitfor(LLLeap::create(get_test_name(), StringVec{PYTHON, script.getName()})); result.ensure(); } diff --git a/indra/llcommon/tests/llmainthreadtask_test.cpp b/indra/llcommon/tests/llmainthreadtask_test.cpp index 9ccf391327..ea4232ad78 100644 --- a/indra/llcommon/tests/llmainthreadtask_test.cpp +++ b/indra/llcommon/tests/llmainthreadtask_test.cpp @@ -20,8 +20,8 @@ // other Linden headers #include "../test/lltut.h" #include "../test/sync.h" +#include "llcallbacklist.h" #include "llthread.h" // on_main_thread() -#include "lleventtimer.h" #include "lockstatic.h" /***************************************************************************** @@ -108,7 +108,7 @@ namespace tut lk.unlock(); // run the task -- should unblock thread, which will immediately block // on mSync - LLEventTimer::updateClass(); + LLCallbackList::instance().callFunctions(); // 'lk', having unlocked, can no longer be used to access; relock with // a new LockStatic instance ensure("should now have run", LockStatic()->ran); diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index 6e8422ca0c..9b3b345217 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -259,6 +259,7 @@ public: } std::string getName() const { return mPath.string(); } + std::string getNormalName() const { return mPath.lexically_normal().make_preferred().string(); } private: boost::filesystem::path mPath; @@ -590,7 +591,7 @@ namespace tut " f.write(os.path.normcase(os.path.normpath(os.getcwd())))\n"); // Before running, call setWorkingDirectory() py.mParams.cwd = tempdir.getName(); - std::string expected{ tempdir.getName() }; + std::string expected{ tempdir.getNormalName() }; #if LL_WINDOWS // SIGH, don't get tripped up by "C:" != "c:" -- // but on the Mac, using tolower() fails because "/users" != "/Users"! diff --git a/indra/llcommon/tests/llrand_test.cpp b/indra/llcommon/tests/llrand_test.cpp index a0dd4ef576..85dd53ce96 100644 --- a/indra/llcommon/tests/llrand_test.cpp +++ b/indra/llcommon/tests/llrand_test.cpp @@ -38,7 +38,7 @@ // testing extent < 0, negate the return value and the extent before passing // into ensure_in_range(). template <typename NUMBER> -void ensure_in_range(const std::string_view& name, +void ensure_in_range(std::string_view name, NUMBER value, NUMBER low, NUMBER high) { auto failmsg{ stringize(name, " >= ", low, " (", value, ')') }; diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index fae9f7023f..ff0607f503 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -44,19 +44,17 @@ typedef U32 uint32_t; #include "llstring.h" #endif -#include "boost/range.hpp" - #include "llsd.h" #include "llsdserialize.h" #include "llsdutil.h" #include "llformat.h" #include "llmemorystream.h" -#include "../test/hexdump.h" +#include "hexdump.h" +#include "StringVec.h" #include "../test/lltut.h" #include "../test/namedtempfile.h" #include "stringize.h" -#include "StringVec.h" #include <functional> typedef std::function<void(const LLSD& data, std::ostream& str)> FormatterFunction; @@ -1921,12 +1919,12 @@ namespace tut int bufflen{ static_cast<int>(buffstr.length()) }; out.write(reinterpret_cast<const char*>(&bufflen), sizeof(bufflen)); LL_DEBUGS() << "Wrote length: " - << hexdump(reinterpret_cast<const char*>(&bufflen), - sizeof(bufflen)) + << LL::hexdump(reinterpret_cast<const char*>(&bufflen), + sizeof(bufflen)) << LL_ENDL; out.write(buffstr.c_str(), buffstr.length()); LL_DEBUGS() << "Wrote data: " - << hexmix(buffstr.c_str(), buffstr.length()) + << LL::hexmix(buffstr.c_str(), buffstr.length()) << LL_ENDL; } } @@ -2097,7 +2095,8 @@ namespace tut NamedTempFile file("llsd", ""); python("Python " + pyformatter, - [&](std::ostream& out){ out << + [pyformatter, &file](std::ostream& out) { + out << import_llsd << "import struct\n" "lenformat = struct.Struct('i')\n" diff --git a/indra/llcommon/tests/llstring_test.cpp b/indra/llcommon/tests/llstring_test.cpp index b18712b8e9..b68c63a15f 100644 --- a/indra/llcommon/tests/llstring_test.cpp +++ b/indra/llcommon/tests/llstring_test.cpp @@ -377,7 +377,7 @@ namespace tut { F32 value; std::string str_val("2147483647"); //0x7FFFFFFF - ensure("1: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 2147483647); + ensure("1: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 2147483647.f); str_val = "0"; ensure("2: convertToF32 failed", LLStringUtil::convertToF32(str_val, value) && value == 0); @@ -399,7 +399,7 @@ namespace tut { F64 value; std::string str_val("9223372036854775807"); //0x7FFFFFFFFFFFFFFF - ensure("1: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 9223372036854775807LL); + ensure("1: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 9223372036854775807.); str_val = "0"; ensure("2: convertToF64 failed", LLStringUtil::convertToF64(str_val, value) && value == 0.0F); diff --git a/indra/llcommon/threadpool.cpp b/indra/llcommon/threadpool.cpp index 451e60c083..c94b5eecce 100644 --- a/indra/llcommon/threadpool.cpp +++ b/indra/llcommon/threadpool.cpp @@ -18,6 +18,7 @@ // external library headers // other Linden headers #include "commoncontrol.h" +#include "llcoros.h" #include "llerror.h" #include "llevents.h" #include "llsd.h" @@ -90,20 +91,14 @@ void LL::ThreadPoolBase::start() return; } - // Listen on "LLApp", and when the app is shutting down, close the queue - // and join the workers. - LLEventPumps::instance().obtain("LLApp").listen( + // When the app is shutting down, close the queue and join the workers. + mStopListener = LLCoros::getStopListener( mName, - [this](const LLSD& stat) + [this](const LLSD& status) { - std::string status(stat["status"]); - if (status != "running") - { - // viewer is starting shutdown -- proclaim the end is nigh! - LL_DEBUGS("ThreadPool") << mName << " saw " << status << LL_ENDL; - close(); - } - return false; + // viewer is starting shutdown -- proclaim the end is nigh! + LL_DEBUGS("ThreadPool") << mName << " saw " << status << LL_ENDL; + close(); }); } @@ -118,20 +113,19 @@ LL::ThreadPoolBase::~ThreadPoolBase() void LL::ThreadPoolBase::close() { - if (! mQueue->isClosed()) + // mQueue might have been closed already, but in any case we must join or + // detach each of our threads before destroying the mThreads vector. + LL_DEBUGS("ThreadPool") << mName << " closing queue and joining threads" << LL_ENDL; + mQueue->close(); + for (auto& pair: mThreads) { - LL_DEBUGS("ThreadPool") << mName << " closing queue and joining threads" << LL_ENDL; - mQueue->close(); - for (auto& pair: mThreads) + if (pair.second.joinable()) { - if (pair.second.joinable()) - { - LL_DEBUGS("ThreadPool") << mName << " waiting on thread " << pair.first << LL_ENDL; - pair.second.join(); - } + LL_DEBUGS("ThreadPool") << mName << " waiting on thread " << pair.first << LL_ENDL; + pair.second.join(); } - LL_DEBUGS("ThreadPool") << mName << " shutdown complete" << LL_ENDL; } + LL_DEBUGS("ThreadPool") << mName << " shutdown complete" << LL_ENDL; } void LL::ThreadPoolBase::run(const std::string& name) diff --git a/indra/llcommon/threadpool.h b/indra/llcommon/threadpool.h index 0eb1891754..2748d7b073 100644 --- a/indra/llcommon/threadpool.h +++ b/indra/llcommon/threadpool.h @@ -13,6 +13,7 @@ #if ! defined(LL_THREADPOOL_H) #define LL_THREADPOOL_H +#include "llcoros.h" #include "threadpool_fwd.h" #include "workqueue.h" #include <memory> // std::unique_ptr @@ -52,8 +53,8 @@ namespace LL void start(); /** - * ThreadPool listens for application shutdown messages on the "LLApp" - * LLEventPump. Call close() to shut down this ThreadPool early. + * ThreadPool listens for application shutdown events. Call close() to + * shut down this ThreadPool early. */ virtual void close(); @@ -95,6 +96,7 @@ namespace LL std::string mName; size_t mThreadCount; + LLTempBoundListener mStopListener; }; /** diff --git a/indra/llcommon/throttle.cpp b/indra/llcommon/throttle.cpp new file mode 100644 index 0000000000..deb0a26fb0 --- /dev/null +++ b/indra/llcommon/throttle.cpp @@ -0,0 +1,34 @@ +/** + * @file throttle.cpp + * @author Nat Goodspeed + * @date 2024-08-12 + * @brief Implementation for ThrottleBase. + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +#include "linden_common.h" +// associated header +#include "throttle.h" +// STL headers +// std headers +// external library headers +// other Linden headers +#include "lltimer.h" + +bool ThrottleBase::too_fast() +{ + F64 now = LLTimer::getElapsedSeconds(); + if (now < mNext) + { + return true; + } + else + { + mNext = now + mInterval; + return false; + } +} diff --git a/indra/llcommon/throttle.h b/indra/llcommon/throttle.h new file mode 100644 index 0000000000..7f5389f464 --- /dev/null +++ b/indra/llcommon/throttle.h @@ -0,0 +1,138 @@ +/** + * @file throttle.h + * @author Nat Goodspeed + * @date 2024-08-12 + * @brief Throttle class + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_THROTTLE_H) +#define LL_THROTTLE_H + +#include "apply.h" // LL::bind_front() +#include "llerror.h" +#include <functional> +#include <iomanip> // std::quoted() + +class ThrottleBase +{ +public: + ThrottleBase(F64 interval): + mInterval(interval) + {} + +protected: + bool too_fast(); // not const: we update mNext + F64 mInterval, mNext{ 0. }; +}; + +/** + * An instance of Throttle mediates calls to some other specified function, + * ensuring that it's called no more often than the specified time interval. + * Throttle is an abstract base class that delegates the behavior when the + * specified interval is exceeded. + */ +template <typename SIGNATURE> +class Throttle: public ThrottleBase +{ +public: + Throttle(const std::string& desc, + const std::function<SIGNATURE>& func, + F64 interval): + ThrottleBase(interval), + mDesc(desc), + mFunc(func) + {} + // Constructing Throttle with a member function pointer but without an + // instance pointer requires you to pass the instance pointer/reference as + // the first argument to operator()(). + template <typename R, class C> + Throttle(const std::string& desc, R C::* method, F64 interval): + Throttle(desc, std::mem_fn(method), interval) + {} + template <typename R, class C> + Throttle(const std::string& desc, R C::* method, C* instance, F64 interval): + Throttle(desc, LL::bind_front(method, instance), interval) + {} + template <typename R, class C> + Throttle(const std::string& desc, R C::* method, const C* instance, F64 interval): + Throttle(desc, LL::bind_front(method, instance), interval) + {} + virtual ~Throttle() {} + + template <typename... ARGS> + auto operator()(ARGS... args) + { + if (too_fast()) + { + suppress(); + using rtype = decltype(mFunc(std::forward<ARGS>(args)...)); + if constexpr (! std::is_same_v<rtype, void>) + { + return rtype{}; + } + } + else + { + return mFunc(std::forward<ARGS>(args)...); + } + } + +protected: + // override with desired behavior when calls come too often + virtual void suppress() = 0; + const std::string mDesc; + +private: + std::function<SIGNATURE> mFunc; +}; + +/** + * An instance of LogThrottle mediates calls to some other specified function, + * ensuring that it's called no more often than the specified time interval. + * When that interval is exceeded, it logs a message at the specified log + * level. It uses LL_MUMBLES_ONCE() logic to prevent spamming, since a too- + * frequent call may well be spammy. + */ +template <LLError::ELevel LOGLEVEL, typename SIGNATURE> +class LogThrottle: public Throttle<SIGNATURE> +{ + using super = Throttle<SIGNATURE>; +public: + LogThrottle(const std::string& desc, + const std::function<SIGNATURE>& func, + F64 interval): + super(desc, func, interval) + {} + template <typename R, class C> + LogThrottle(const std::string& desc, R C::* method, F64 interval): + super(desc, method, interval) + {} + template <typename R, class C> + LogThrottle(const std::string& desc, R C::* method, C* instance, F64 interval): + super(desc, method, instance, interval) + {} + template <typename R, class C> + LogThrottle(const std::string& desc, R C::* method, const C* instance, F64 interval): + super(desc, method, instance, interval) + {} + +private: + void suppress() override + { + // Using lllog(), the macro underlying LL_WARNS() et al., allows + // specifying compile-time LOGLEVEL. It does NOT support a variable + // LOGLEVEL, which is why LOGLEVEL is a non-type template parameter. + // See llvlog() for variable support, which is a bit more expensive. + // true = only print the log message once + lllog(LOGLEVEL, true, "LogThrottle") << std::quoted(super::mDesc) + << " called more than once per " + << super::mInterval + << LL_ENDL; + } +}; + +#endif /* ! defined(LL_THROTTLE_H) */ diff --git a/indra/llcommon/workqueue.cpp b/indra/llcommon/workqueue.cpp index 6066e74fb5..9138c862f9 100644 --- a/indra/llcommon/workqueue.cpp +++ b/indra/llcommon/workqueue.cpp @@ -32,8 +32,11 @@ using Lock = LLCoros::LockType; LL::WorkQueueBase::WorkQueueBase(const std::string& name): super(makeName(name)) { - // TODO: register for "LLApp" events so we can implicitly close() on - // viewer shutdown. + // Register for status change events so we'll implicitly close() on viewer + // shutdown. + mStopListener = LLCoros::getStopListener( + "WorkQueue:" + getKey(), + [this](const LLSD&){ close(); }); } void LL::WorkQueueBase::runUntilClose() diff --git a/indra/llcommon/workqueue.h b/indra/llcommon/workqueue.h index 9d7bbfbf7a..eb6923df0b 100644 --- a/indra/llcommon/workqueue.h +++ b/indra/llcommon/workqueue.h @@ -13,6 +13,7 @@ #define LL_WORKQUEUE_H #include "llcoros.h" +#include "llevents.h" #include "llexception.h" #include "llinstancetracker.h" #include "llinstancetrackersubclass.h" @@ -116,16 +117,29 @@ namespace LL ARGS&&... args); /** - * Post work to another WorkQueue, blocking the calling coroutine - * until then, returning the result to caller on completion. Optional - * final argument is TimePoint for WorkSchedule. + * Post work, blocking the calling coroutine, returning the result to + * caller on completion. Optional final argument is TimePoint for + * WorkSchedule. * * In general, we assume that each thread's default coroutine is busy * servicing its WorkQueue or whatever. To try to prevent mistakes, we * forbid calling waitForResult() from a thread's default coroutine. */ template <typename CALLABLE, typename... ARGS> - auto waitForResult(CALLABLE&& callable, ARGS&&... args); + auto waitForResult(CALLABLE&& callable, ARGS&&... args) + { + checkCoroutine("waitForResult()"); + return waitForResult_(std::forward<CALLABLE>(callable), + std::forward<ARGS>(args)...); + } + + /** + * Post work, blocking the calling coroutine, returning the result to + * caller on completion. Optional final argument is TimePoint for + * WorkSchedule. + */ + template <typename CALLABLE, typename... ARGS> + auto waitForResult_(CALLABLE&& callable, ARGS&&... args); /*--------------------------- worker API ---------------------------*/ @@ -194,6 +208,8 @@ namespace LL static std::string makeName(const std::string& name); void callWork(const Work& work); + LLTempBoundListener mStopListener; + private: virtual Work pop_() = 0; virtual bool tryPop_(Work&) = 0; @@ -359,6 +375,10 @@ namespace LL * CALLABLE that returns bool, a TimePoint and an interval at which to * relaunch it. As long as the callable continues returning true, BackJack * keeps resubmitting it to the target WorkQueue. + * + * "You go back, Jack, and do it again -- wheel turnin' round and round..." + * --Steely Dan, from "Can't Buy a Thrill" (1972) + * https://www.youtube.com/watch?v=yCgHTmv4YU8 */ // Why is BackJack a class and not a lambda? Because, unlike a lambda, a // class method gets its own 'this' pointer -- which we need to resubmit @@ -525,7 +545,7 @@ namespace LL reply, // Bind the current exception to transport back to the // originating WorkQueue. Once there, rethrow it. - [exc = std::current_exception()](){ std::rethrow_exception(exc); }); + [exc = std::current_exception()]{ std::rethrow_exception(exc); }); } }, // if caller passed a TimePoint, pass it along to post() @@ -618,9 +638,8 @@ namespace LL }; template <typename CALLABLE, typename... ARGS> - auto WorkQueueBase::waitForResult(CALLABLE&& callable, ARGS&&... args) + auto WorkQueueBase::waitForResult_(CALLABLE&& callable, ARGS&&... args) { - checkCoroutine("waitForResult()"); // derive callable's return type so we can specialize for void return WaitForResult<CALLABLE, decltype(std::forward<CALLABLE>(callable)())>() (this, std::forward<CALLABLE>(callable), std::forward<ARGS>(args)...); diff --git a/indra/llcorehttp/CMakeLists.txt b/indra/llcorehttp/CMakeLists.txt index 05b788a433..b141171516 100644 --- a/indra/llcorehttp/CMakeLists.txt +++ b/indra/llcorehttp/CMakeLists.txt @@ -10,7 +10,6 @@ include(ZLIBNG) include(LLCoreHttp) include(LLAddBuildTest) include(LLCommon) -include(Tut) include(bugsplat) set(llcorehttp_SOURCE_FILES diff --git a/indra/llcorehttp/tests/llcorehttp_test.cpp b/indra/llcorehttp/tests/llcorehttp_test.cpp index c0cc2c8030..8a788e5a93 100755 --- a/indra/llcorehttp/tests/llcorehttp_test.cpp +++ b/indra/llcorehttp/tests/llcorehttp_test.cpp @@ -43,9 +43,12 @@ #include "test_httpoperation.hpp" // As of 2019-06-28, test_httprequest.hpp consistently crashes on Mac Release // builds for reasons not yet diagnosed. -#if ! (LL_DARWIN && LL_RELEASE) +// On Linux too, this is likely to badly handling (p)thread creation and not waiting +// for threads to properly shutdown +#if LL_WINDOWS #include "test_httprequest.hpp" #endif + #include "test_httpheaders.hpp" #include "test_httprequestqueue.hpp" #include "_httpservice.h" diff --git a/indra/llfilesystem/lldir.cpp b/indra/llfilesystem/lldir.cpp index 99d4850610..2818aaf86c 100644 --- a/indra/llfilesystem/lldir.cpp +++ b/indra/llfilesystem/lldir.cpp @@ -468,6 +468,7 @@ static std::string ELLPathToString(ELLPath location) ENT(LL_PATH_DEFAULT_SKIN) ENT(LL_PATH_FONTS) ENT(LL_PATH_LAST) + ENT(LL_PATH_SCRIPTS) ; #undef ENT @@ -588,6 +589,10 @@ std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subd prefix = add(getAppRODataDir(), "fonts"); break; + case LL_PATH_SCRIPTS: + prefix = add(getAppRODataDir(), "scripts"); + break; + default: llassert(0); } diff --git a/indra/llfilesystem/lldir.h b/indra/llfilesystem/lldir.h index b0d2b6aada..241f151d47 100644 --- a/indra/llfilesystem/lldir.h +++ b/indra/llfilesystem/lldir.h @@ -49,6 +49,7 @@ typedef enum ELLPath LL_PATH_DEFAULT_SKIN = 17, LL_PATH_FONTS = 18, LL_PATH_DUMP = 19, + LL_PATH_SCRIPTS = 20, LL_PATH_LAST } ELLPath; diff --git a/indra/llfilesystem/tests/lldir_test.cpp b/indra/llfilesystem/tests/lldir_test.cpp index 6e191ad096..4fa5769f65 100644 --- a/indra/llfilesystem/tests/lldir_test.cpp +++ b/indra/llfilesystem/tests/lldir_test.cpp @@ -27,12 +27,14 @@ #include "linden_common.h" +#include <filesystem> #include "llstring.h" #include "tests/StringVec.h" #include "../lldir.h" #include "../lldiriterator.h" #include "../test/lltut.h" +#include "../test/namedtempfile.h" #include "stringize.h" #include <boost/assign/list_of.hpp> @@ -425,23 +427,19 @@ namespace tut return path; } - std::string makeTestDir( const std::string& dirbase ) + std::string makeTestDir( ) { - int counter; - std::string uniqueDir; - bool foundUnused; - std::string delim = gDirUtilp->getDirDelimiter(); + auto p = NamedTempFile::temp_path(); + std::filesystem::create_directories(p.native()); - for (counter=0, foundUnused=false; !foundUnused; counter++ ) - { - char counterStr[3]; - sprintf(counterStr, "%02d", counter); - uniqueDir = dirbase + counterStr; - foundUnused = ! ( LLFile::isdir(uniqueDir) || LLFile::isfile(uniqueDir) ); - } - ensure("test directory '" + uniqueDir + "' creation failed", !LLFile::mkdir(uniqueDir)); + std::string ret = p.string(); - return uniqueDir + delim; // HACK - apparently, the trailing delimiter is needed... + // There's an implicit assumtion all over this code that the returned path ends with "/" (or "\") + + if(ret.size() >= 1 && ret[ ret.size()-1 ] != std::filesystem::path::preferred_separator ) + ret += std::filesystem::path::preferred_separator; + + return ret; } static const char* DirScanFilename[5] = { "file1.abc", "file2.abc", "file1.xyz", "file2.xyz", "file1.mno" }; @@ -497,8 +495,9 @@ namespace tut // Create the same 5 file names of the two directories - std::string dir1 = makeTestDir(dirTemp + "LLDirIterator"); - std::string dir2 = makeTestDir(dirTemp + "LLDirIterator"); + std::string dir1 = makeTestDir(); + std::string dir2 = makeTestDir(); + std::string dir1files[5]; std::string dir2files[5]; for (int i=0; i<5; i++) diff --git a/indra/llimage/llimage.h b/indra/llimage/llimage.h index 8b966b8ea3..6b14b68c78 100644 --- a/indra/llimage/llimage.h +++ b/indra/llimage/llimage.h @@ -179,7 +179,7 @@ private: public: template<bool SHARED> - class DataLock : LLSharedMutexLockTemplate<SHARED> + class DataLock : public LLSharedMutexLockTemplate<SHARED> { public: DataLock(const LLImageBase* image) diff --git a/indra/llimage/llimagedimensionsinfo.cpp b/indra/llimage/llimagedimensionsinfo.cpp index d4efbcfad2..c896d60c85 100644 --- a/indra/llimage/llimagedimensionsinfo.cpp +++ b/indra/llimage/llimagedimensionsinfo.cpp @@ -75,7 +75,7 @@ bool LLImageDimensionsInfo::load(const std::string& src_filename,U32 codec) bool LLImageDimensionsInfo::getImageDimensionsBmp() { // Make sure the file is long enough. - const S32 DATA_LEN = 26; // BMP header (14) + DIB header size (4) + width (4) + height (4) + constexpr S32 DATA_LEN = 26; // BMP header (14) + DIB header size (4) + width (4) + height (4) if (!checkFileLength(DATA_LEN)) { LL_WARNS() << "Premature end of file" << LL_ENDL; @@ -105,7 +105,7 @@ bool LLImageDimensionsInfo::getImageDimensionsBmp() bool LLImageDimensionsInfo::getImageDimensionsTga() { - const S32 TGA_FILE_HEADER_SIZE = 12; + constexpr S32 TGA_FILE_HEADER_SIZE = 12; // Make sure the file is long enough. if (!checkFileLength(TGA_FILE_HEADER_SIZE + 1 /* width */ + 1 /* height */)) @@ -124,7 +124,7 @@ bool LLImageDimensionsInfo::getImageDimensionsTga() bool LLImageDimensionsInfo::getImageDimensionsPng() { - const S32 PNG_MAGIC_SIZE = 8; + constexpr S32 PNG_MAGIC_SIZE = 8; // Make sure the file is long enough. if (!checkFileLength(PNG_MAGIC_SIZE + 8 + sizeof(S32) * 2 /* width, height */)) @@ -134,7 +134,7 @@ bool LLImageDimensionsInfo::getImageDimensionsPng() } // Read PNG signature. - const U8 png_magic[PNG_MAGIC_SIZE] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; + constexpr U8 png_magic[PNG_MAGIC_SIZE] = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}; U8 signature[PNG_MAGIC_SIZE]; mInfile.read((void*)signature, PNG_MAGIC_SIZE); @@ -166,34 +166,36 @@ bool LLImageDimensionsInfo::getImageDimensionsJpeg() { sJpegErrorEncountered = false; clean(); - FILE *fp = LLFile::fopen(mSrcFilename, "rb"); - if (fp == NULL) + FILE* fp = LLFile::fopen(mSrcFilename, "rb"); + if (!fp) { setLastError("Unable to open file for reading", mSrcFilename); return false; } /* Make sure this is a JPEG file. */ - const size_t JPEG_MAGIC_SIZE = 2; - const U8 jpeg_magic[JPEG_MAGIC_SIZE] = {0xFF, 0xD8}; + constexpr size_t JPEG_MAGIC_SIZE = 2; + constexpr U8 jpeg_magic[JPEG_MAGIC_SIZE] = {0xFF, 0xD8}; U8 signature[JPEG_MAGIC_SIZE]; if (fread(signature, sizeof(signature), 1, fp) != 1) { LL_WARNS() << "Premature end of file" << LL_ENDL; + fclose(fp); return false; } if (memcmp(signature, jpeg_magic, JPEG_MAGIC_SIZE) != 0) { LL_WARNS() << "Not a JPEG" << LL_ENDL; mWarning = "texture_load_format_error"; + fclose(fp); return false; } fseek(fp, 0, SEEK_SET); // go back to start of the file /* Init jpeg */ jpeg_error_mgr jerr; - jpeg_decompress_struct cinfo; + jpeg_decompress_struct cinfo{}; cinfo.err = jpeg_std_error(&jerr); // Call our function instead of exit() if Libjpeg encounters an error. // This is done to avoid crash in this case (STORM-472). diff --git a/indra/llimage/llimagedimensionsinfo.h b/indra/llimage/llimagedimensionsinfo.h index 681d66ae4e..4870f2e815 100644 --- a/indra/llimage/llimagedimensionsinfo.h +++ b/indra/llimage/llimagedimensionsinfo.h @@ -38,7 +38,7 @@ class LLImageDimensionsInfo { public: LLImageDimensionsInfo(): - mData(NULL) + mData(nullptr) ,mHeight(0) ,mWidth(0) {} @@ -67,7 +67,7 @@ protected: { mInfile.close(); delete[] mData; - mData = NULL; + mData = nullptr; mWidth = 0; mHeight = 0; } diff --git a/indra/llimage/llimagefilter.cpp b/indra/llimage/llimagefilter.cpp index bfcb1f76de..3b12aa39e4 100644 --- a/indra/llimage/llimagefilter.cpp +++ b/indra/llimage/llimagefilter.cpp @@ -389,6 +389,11 @@ void LLImageFilter::convolve(const LLMatrix3 &kernel, bool normalize, bool abs_v S32 buffer_size = width * components; llassert_always(buffer_size > 0); + + // ND: GCC womtimes is unable to figure out llassert_always (aka LLERROR_CRASH) will never return. + // This return here is just a dummy and will not be reached. + if( buffer_size == 0 ){return; } + std::vector<U8> even_buffer(buffer_size); std::vector<U8> odd_buffer(buffer_size); diff --git a/indra/llimagej2coj/llimagej2coj.cpp b/indra/llimagej2coj/llimagej2coj.cpp index f4bcb97a5d..6037517103 100644 --- a/indra/llimagej2coj/llimagej2coj.cpp +++ b/indra/llimagej2coj/llimagej2coj.cpp @@ -617,7 +617,6 @@ public: for (S32 c = 0; c < numcomps; c++) { cmptparm[c].prec = 8; - cmptparm[c].bpp = 8; cmptparm[c].sgnd = 0; cmptparm[c].dx = parameters.subsampling_dx; cmptparm[c].dy = parameters.subsampling_dy; diff --git a/indra/llinventory/llfoldertype.cpp b/indra/llinventory/llfoldertype.cpp index 7e1be17ecc..74eb801cfd 100644 --- a/indra/llinventory/llfoldertype.cpp +++ b/indra/llinventory/llfoldertype.cpp @@ -29,6 +29,7 @@ #include "llfoldertype.h" #include "lldictionary.h" #include "llmemory.h" +#include "llsd.h" #include "llsingleton.h" ///---------------------------------------------------------------------------- @@ -220,3 +221,21 @@ const std::string &LLFolderType::badLookup() static const std::string sBadLookup = "llfoldertype_bad_lookup"; return sBadLookup; } + +LLSD LLFolderType::getTypeNames() +{ + LLSD type_names; + const LLFolderDictionary *dict = LLFolderDictionary::getInstance(); + for (S32 type = 0; type < FT_COUNT; ++type) + { + if (lookupIsEnsembleType(LLFolderType::EType(type))) continue; + + const FolderEntry *entry = dict->lookup(LLFolderType::EType(type)); + //skip llfoldertype_bad_lookup + if (entry) + { + type_names.append(entry->mName); + } + } + return type_names; +} diff --git a/indra/llinventory/llfoldertype.h b/indra/llinventory/llfoldertype.h index 46a1b92a96..dd12693f66 100644 --- a/indra/llinventory/llfoldertype.h +++ b/indra/llinventory/llfoldertype.h @@ -115,6 +115,8 @@ public: static const std::string& badLookup(); // error string when a lookup fails + static LLSD getTypeNames(); + protected: LLFolderType() {} ~LLFolderType() {} diff --git a/indra/llinventory/llparcel.h b/indra/llinventory/llparcel.h index 67d713db1f..5f116872b2 100644 --- a/indra/llinventory/llparcel.h +++ b/indra/llinventory/llparcel.h @@ -37,98 +37,98 @@ #include "llsettingsdaycycle.h" // Grid out of which parcels taken is stepped every 4 meters. -const F32 PARCEL_GRID_STEP_METERS = 4.f; +constexpr F32 PARCEL_GRID_STEP_METERS = 4.f; // Area of one "square" of parcel -const S32 PARCEL_UNIT_AREA = 16; +constexpr S32 PARCEL_UNIT_AREA = 16; // Height _above_ground_ that parcel boundary ends -const F32 PARCEL_HEIGHT = 50.f; +constexpr F32 PARCEL_HEIGHT = 50.f; //Height above ground which parcel boundries exist for explicitly banned avatars -const F32 BAN_HEIGHT = 5000.f; +constexpr F32 BAN_HEIGHT = 5000.f; // Maximum number of entries in an access list -const S32 PARCEL_MAX_ACCESS_LIST = 300; +constexpr S32 PARCEL_MAX_ACCESS_LIST = 300; //Maximum number of entires in an update packet //for access/ban lists. -const F32 PARCEL_MAX_ENTRIES_PER_PACKET = 48.f; +constexpr F32 PARCEL_MAX_ENTRIES_PER_PACKET = 48.f; // Maximum number of experiences -const S32 PARCEL_MAX_EXPERIENCE_LIST = 24; +constexpr S32 PARCEL_MAX_EXPERIENCE_LIST = 24; // Weekly charge for listing a parcel in the directory -const S32 PARCEL_DIRECTORY_FEE = 30; +constexpr S32 PARCEL_DIRECTORY_FEE = 30; -const S32 PARCEL_PASS_PRICE_DEFAULT = 10; -const F32 PARCEL_PASS_HOURS_DEFAULT = 1.f; +constexpr S32 PARCEL_PASS_PRICE_DEFAULT = 10; +constexpr F32 PARCEL_PASS_HOURS_DEFAULT = 1.f; // Number of "chunks" in which parcel overlay data is sent // Chunk 0 = southern rows, entire width -const S32 PARCEL_OVERLAY_CHUNKS = 4; +constexpr S32 PARCEL_OVERLAY_CHUNKS = 4; // Bottom three bits are a color index for the land overlay -const U8 PARCEL_COLOR_MASK = 0x07; -const U8 PARCEL_PUBLIC = 0x00; -const U8 PARCEL_OWNED = 0x01; -const U8 PARCEL_GROUP = 0x02; -const U8 PARCEL_SELF = 0x03; -const U8 PARCEL_FOR_SALE = 0x04; -const U8 PARCEL_AUCTION = 0x05; +constexpr U8 PARCEL_COLOR_MASK = 0x07; +constexpr U8 PARCEL_PUBLIC = 0x00; +constexpr U8 PARCEL_OWNED = 0x01; +constexpr U8 PARCEL_GROUP = 0x02; +constexpr U8 PARCEL_SELF = 0x03; +constexpr U8 PARCEL_FOR_SALE = 0x04; +constexpr U8 PARCEL_AUCTION = 0x05; // unused 0x06 // unused 0x07 // flag, unused 0x08 -const U8 PARCEL_HIDDENAVS = 0x10; // avatars not visible outside of parcel. Used for 'see avs' feature, but must be off for compatibility -const U8 PARCEL_SOUND_LOCAL = 0x20; -const U8 PARCEL_WEST_LINE = 0x40; // flag, property line on west edge -const U8 PARCEL_SOUTH_LINE = 0x80; // flag, property line on south edge +constexpr U8 PARCEL_HIDDENAVS = 0x10; // avatars not visible outside of parcel. Used for 'see avs' feature, but must be off for compatibility +constexpr U8 PARCEL_SOUND_LOCAL = 0x20; +constexpr U8 PARCEL_WEST_LINE = 0x40; // flag, property line on west edge +constexpr U8 PARCEL_SOUTH_LINE = 0x80; // flag, property line on south edge // Transmission results for parcel properties -const S32 PARCEL_RESULT_NO_DATA = -1; -const S32 PARCEL_RESULT_SUCCESS = 0; // got exactly one parcel -const S32 PARCEL_RESULT_MULTIPLE = 1; // got multiple parcels - -const S32 SELECTED_PARCEL_SEQ_ID = -10000; -const S32 COLLISION_NOT_IN_GROUP_PARCEL_SEQ_ID = -20000; -const S32 COLLISION_BANNED_PARCEL_SEQ_ID = -30000; -const S32 COLLISION_NOT_ON_LIST_PARCEL_SEQ_ID = -40000; -const S32 HOVERED_PARCEL_SEQ_ID = -50000; - -const U32 RT_NONE = 0x1 << 0; -const U32 RT_OWNER = 0x1 << 1; -const U32 RT_GROUP = 0x1 << 2; -const U32 RT_OTHER = 0x1 << 3; -const U32 RT_LIST = 0x1 << 4; -const U32 RT_SELL = 0x1 << 5; - -const S32 INVALID_PARCEL_ID = -1; - -const S32 INVALID_PARCEL_ENVIRONMENT_VERSION = -2; +constexpr S32 PARCEL_RESULT_NO_DATA = -1; +constexpr S32 PARCEL_RESULT_SUCCESS = 0; // got exactly one parcel +constexpr S32 PARCEL_RESULT_MULTIPLE = 1; // got multiple parcels + +constexpr S32 SELECTED_PARCEL_SEQ_ID = -10000; +constexpr S32 COLLISION_NOT_IN_GROUP_PARCEL_SEQ_ID = -20000; +constexpr S32 COLLISION_BANNED_PARCEL_SEQ_ID = -30000; +constexpr S32 COLLISION_NOT_ON_LIST_PARCEL_SEQ_ID = -40000; +constexpr S32 HOVERED_PARCEL_SEQ_ID = -50000; + +constexpr U32 RT_NONE = 0x1 << 0; +constexpr U32 RT_OWNER = 0x1 << 1; +constexpr U32 RT_GROUP = 0x1 << 2; +constexpr U32 RT_OTHER = 0x1 << 3; +constexpr U32 RT_LIST = 0x1 << 4; +constexpr U32 RT_SELL = 0x1 << 5; + +constexpr S32 INVALID_PARCEL_ID = -1; + +constexpr S32 INVALID_PARCEL_ENVIRONMENT_VERSION = -2; // if Region settings are used, parcel env. version is -1 -const S32 UNSET_PARCEL_ENVIRONMENT_VERSION = -1; +constexpr S32 UNSET_PARCEL_ENVIRONMENT_VERSION = -1; // Timeouts for parcels // default is 21 days * 24h/d * 60m/h * 60s/m *1000000 usec/s = 1814400000000 -const U64 DEFAULT_USEC_CONVERSION_TIMEOUT = U64L(1814400000000); +constexpr U64 DEFAULT_USEC_CONVERSION_TIMEOUT = U64L(1814400000000); // ***** TESTING is 10 minutes //const U64 DEFAULT_USEC_CONVERSION_TIMEOUT = U64L(600000000); // group is 60 days * 24h/d * 60m/h * 60s/m *1000000 usec/s = 5184000000000 -const U64 GROUP_USEC_CONVERSION_TIMEOUT = U64L(5184000000000); +constexpr U64 GROUP_USEC_CONVERSION_TIMEOUT = U64L(5184000000000); // ***** TESTING is 10 minutes //const U64 GROUP_USEC_CONVERSION_TIMEOUT = U64L(600000000); // default sale timeout is 2 days -> 172800000000 -const U64 DEFAULT_USEC_SALE_TIMEOUT = U64L(172800000000); +constexpr U64 DEFAULT_USEC_SALE_TIMEOUT = U64L(172800000000); // ***** TESTING is 10 minutes //const U64 DEFAULT_USEC_SALE_TIMEOUT = U64L(600000000); // more grace period extensions. -const U64 SEVEN_DAYS_IN_USEC = U64L(604800000000); +constexpr U64 SEVEN_DAYS_IN_USEC = U64L(604800000000); // if more than 100,000s before sale revert, and no extra extension // has been given, go ahead and extend it more. That's about 1.2 days. -const S32 EXTEND_GRACE_IF_MORE_THAN_SEC = 100000; +constexpr S32 EXTEND_GRACE_IF_MORE_THAN_SEC = 100000; @@ -250,9 +250,9 @@ public: void setMediaURL(const std::string& url); void setMediaType(const std::string& type); void setMediaDesc(const std::string& desc); - void setMediaID(const LLUUID& id) { mMediaID = id; } - void setMediaAutoScale ( U8 flagIn ) { mMediaAutoScale = flagIn; } - void setMediaLoop (U8 loop) { mMediaLoop = loop; } + void setMediaID(const LLUUID& id) { mMediaID = id; } + void setMediaAutoScale ( U8 flagIn ) { mMediaAutoScale = flagIn; } + void setMediaLoop(U8 loop) { mMediaLoop = loop; } void setMediaWidth(S32 width); void setMediaHeight(S32 height); void setMediaCurrentURL(const std::string& url); diff --git a/indra/llinventory/llsettingsdaycycle.cpp b/indra/llinventory/llsettingsdaycycle.cpp index 2b189e7cca..a45ab5a09c 100644 --- a/indra/llinventory/llsettingsdaycycle.cpp +++ b/indra/llinventory/llsettingsdaycycle.cpp @@ -143,7 +143,7 @@ LLSettingsDay::LLSettingsDay() : //========================================================================= LLSD& LLSettingsDay::getSettings() { - if (!mDaySettings.emptyMap()) + if (mDaySettings.size() > 0) { return mDaySettings; } diff --git a/indra/llmath/llcalcparser.h b/indra/llmath/llcalcparser.h index ea71752ebc..ec7f6a2cb6 100644 --- a/indra/llmath/llcalcparser.h +++ b/indra/llmath/llcalcparser.h @@ -131,14 +131,14 @@ struct LLCalcParser : grammar<LLCalcParser> power = unary_expr[power.value = arg1] >> - *('^' >> assert_syntax(unary_expr[power.value = phoenix::bind(&powf)(power.value, arg1)])) + *('^' >> assert_syntax(unary_expr[power.value = phoenix::bind(&LLCalcParser::_pow)(self, power.value, arg1)])) ; term = power[term.value = arg1] >> *(('*' >> assert_syntax(power[term.value *= arg1])) | ('/' >> assert_syntax(power[term.value /= arg1])) | - ('%' >> assert_syntax(power[term.value = phoenix::bind(&fmodf)(term.value, arg1)])) + ('%' >> assert_syntax(power[term.value = phoenix::bind(&LLCalcParser::_fmod)(self, term.value, arg1)])) ) ; @@ -177,6 +177,8 @@ private: F32 _floor(const F32& a) const { return (F32)llfloor(a); } F32 _ceil(const F32& a) const { return (F32)llceil(a); } F32 _atan2(const F32& a,const F32& b) const { return atan2(a,b); } + F32 _pow(const F32& a, const F32& b) const { return powf(a, b); } + F32 _fmod(const F32&a, const F32& b) const { return fmodf(a, b); } LLCalc::calc_map_t* mConstants; LLCalc::calc_map_t* mVariables; diff --git a/indra/llmath/tests/mathmisc_test.cpp b/indra/llmath/tests/mathmisc_test.cpp index ff0899e975..2b23987616 100644 --- a/indra/llmath/tests/mathmisc_test.cpp +++ b/indra/llmath/tests/mathmisc_test.cpp @@ -709,14 +709,34 @@ namespace tut first_plane, second_plane); - ensure("plane intersection should succeed", success); + try + { + ensure("plane intersection should succeed", success); - F32 dot = fabs(known_intersection.getDirection() * measured_intersection.getDirection()); - ensure("measured intersection should be parallel to known intersection", - dot > ALMOST_PARALLEL); + F32 dot = fabs(known_intersection.getDirection() * measured_intersection.getDirection()); + ensure("measured intersection should be parallel to known intersection", + dot > ALMOST_PARALLEL); - ensure("measured intersection should pass near known point", - measured_intersection.intersects(some_point, LARGE_RADIUS * allowable_relative_error)); + ensure("measured intersection should pass near known point", + measured_intersection.intersects(some_point, LARGE_RADIUS * allowable_relative_error)); + } + catch (const failure&) + { + // If any of these assertions fail, since the values involved + // are randomly generated, unless we report them, we have no + // hope of diagnosing the problem. + LL_INFOS() << "some_point = " << some_point << '\n' + << "another_point = " << another_point << '\n' + << "known_intersection = " << known_intersection << '\n' + << "point_on_plane = " << point_on_plane << '\n' + << "plane_normal = " << plane_normal << '\n' + << "first_plane = " << first_plane << '\n' + << "point_on_different_plane = " << point_on_different_plane << '\n' + << "different_plane_normal = " << different_plane_normal << '\n' + << "second_plane = " << second_plane << '\n' + << "measured_intersection = " << measured_intersection << LL_ENDL; + throw; + } } } } diff --git a/indra/llmath/v3math.h b/indra/llmath/v3math.h index d063b15c74..4fc7585a46 100644 --- a/indra/llmath/v3math.h +++ b/indra/llmath/v3math.h @@ -459,10 +459,17 @@ inline const LLVector3& operator*=(LLVector3 &a, const LLVector3 &b) inline const LLVector3& operator/=(LLVector3 &a, F32 k) { - F32 t = 1.f / k; - a.mV[0] *= t; - a.mV[1] *= t; - a.mV[2] *= t; + a.mV[0] /= k; + a.mV[1] /= k; + a.mV[2] /= k; + return a; +} + +inline const LLVector3& operator/=(LLVector3& a, const LLVector3& b) +{ + a.mV[0] /= b.mV[0]; + a.mV[1] /= b.mV[1]; + a.mV[2] /= b.mV[2]; return a; } diff --git a/indra/llmeshoptimizer/llmeshoptimizer.cpp b/indra/llmeshoptimizer/llmeshoptimizer.cpp index 7339454367..9d62a72188 100644 --- a/indra/llmeshoptimizer/llmeshoptimizer.cpp +++ b/indra/llmeshoptimizer/llmeshoptimizer.cpp @@ -57,7 +57,7 @@ void LLMeshOptimizer::generateShadowIndexBufferU32(U32 *destination, S32 index = 0; if (vertex_positions) { - streams[index].data = (const float*)vertex_positions; + streams[index].data = vertex_positions->getF32ptr(); // Despite being LLVector4a, only x, y and z are in use streams[index].size = sizeof(F32) * 3; streams[index].stride = sizeof(F32) * 4; @@ -65,14 +65,14 @@ void LLMeshOptimizer::generateShadowIndexBufferU32(U32 *destination, } if (normals) { - streams[index].data = (const float*)normals; + streams[index].data = normals->getF32ptr(); streams[index].size = sizeof(F32) * 3; streams[index].stride = sizeof(F32) * 4; index++; } if (text_coords) { - streams[index].data = (const float*)text_coords; + streams[index].data = text_coords->mV; streams[index].size = sizeof(F32) * 2; streams[index].stride = sizeof(F32) * 2; index++; @@ -108,21 +108,21 @@ void LLMeshOptimizer::generateShadowIndexBufferU16(U16 *destination, S32 index = 0; if (vertex_positions) { - streams[index].data = (const float*)vertex_positions; + streams[index].data = vertex_positions->getF32ptr(); streams[index].size = sizeof(F32) * 3; streams[index].stride = sizeof(F32) * 4; index++; } if (normals) { - streams[index].data = (const float*)normals; + streams[index].data = normals->getF32ptr(); streams[index].size = sizeof(F32) * 3; streams[index].stride = sizeof(F32) * 4; index++; } if (text_coords) { - streams[index].data = (const float*)text_coords; + streams[index].data = text_coords->mV; streams[index].size = sizeof(F32) * 2; streams[index].stride = sizeof(F32) * 2; index++; @@ -162,9 +162,9 @@ size_t LLMeshOptimizer::generateRemapMultiU32( U64 vertex_count) { meshopt_Stream streams[] = { - {(const float*)vertex_positions, sizeof(F32) * 3, sizeof(F32) * 4}, - {(const float*)normals, sizeof(F32) * 3, sizeof(F32) * 4}, - {(const float*)text_coords, sizeof(F32) * 2, sizeof(F32) * 2}, + {vertex_positions->getF32ptr(), sizeof(F32) * 3, sizeof(F32) * 4}, + {normals->getF32ptr(), sizeof(F32) * 3, sizeof(F32) * 4}, + {text_coords->mV, sizeof(F32) * 2, sizeof(F32) * 2}, }; // Remap can function without indices, @@ -236,7 +236,7 @@ void LLMeshOptimizer::remapPositionsBuffer(LLVector4a * destination_vertices, U64 vertex_count, const unsigned int* remap) { - meshopt_remapVertexBuffer((float*)destination_vertices, (const float*)vertex_positions, vertex_count, sizeof(LLVector4a), remap); + meshopt_remapVertexBuffer(destination_vertices->getF32ptr(), vertex_positions->getF32ptr(), vertex_count, sizeof(LLVector4a), remap); } void LLMeshOptimizer::remapNormalsBuffer(LLVector4a * destination_normalss, @@ -244,7 +244,7 @@ void LLMeshOptimizer::remapNormalsBuffer(LLVector4a * destination_normalss, U64 mormals_count, const unsigned int* remap) { - meshopt_remapVertexBuffer((float*)destination_normalss, (const float*)normals, mormals_count, sizeof(LLVector4a), remap); + meshopt_remapVertexBuffer(destination_normalss->getF32ptr(), normals->getF32ptr(), mormals_count, sizeof(LLVector4a), remap); } void LLMeshOptimizer::remapUVBuffer(LLVector2 * destination_uvs, @@ -252,7 +252,7 @@ void LLMeshOptimizer::remapUVBuffer(LLVector2 * destination_uvs, U64 uv_count, const unsigned int* remap) { - meshopt_remapVertexBuffer((float*)destination_uvs, (const float*)uv_positions, uv_count, sizeof(LLVector2), remap); + meshopt_remapVertexBuffer(destination_uvs->mV, uv_positions->mV, uv_count, sizeof(LLVector2), remap); } //static @@ -273,7 +273,7 @@ U64 LLMeshOptimizer::simplifyU32(U32 *destination, return meshopt_simplifySloppy<unsigned int>(destination, indices, index_count, - (const float*)vertex_positions, + vertex_positions->getF32ptr(), vertex_count, vertex_positions_stride, target_index_count, @@ -286,7 +286,7 @@ U64 LLMeshOptimizer::simplifyU32(U32 *destination, return meshopt_simplify<unsigned int>(destination, indices, index_count, - (const float*)vertex_positions, + vertex_positions->getF32ptr(), vertex_count, vertex_positions_stride, target_index_count, @@ -315,7 +315,7 @@ U64 LLMeshOptimizer::simplify(U16 *destination, return meshopt_simplifySloppy<unsigned short>(destination, indices, index_count, - (const float*)vertex_positions, + vertex_positions->getF32ptr(), vertex_count, vertex_positions_stride, target_index_count, @@ -328,7 +328,7 @@ U64 LLMeshOptimizer::simplify(U16 *destination, return meshopt_simplify<unsigned short>(destination, indices, index_count, - (const float*)vertex_positions, + vertex_positions->getF32ptr(), vertex_count, vertex_positions_stride, target_index_count, diff --git a/indra/llmessage/llcoproceduremanager.cpp b/indra/llmessage/llcoproceduremanager.cpp index 263670bdac..13972ad399 100644 --- a/indra/llmessage/llcoproceduremanager.cpp +++ b/indra/llmessage/llcoproceduremanager.cpp @@ -307,25 +307,20 @@ LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size): { try { - // store in our LLTempBoundListener so that when the LLCoprocedurePool is - // destroyed, we implicitly disconnect from this LLEventPump - // Monitores application status - mStatusListener = LLEventPumps::instance().obtain("LLApp").listen( + // Store in our LLTempBoundListener so that when the LLCoprocedurePool is + // destroyed, we implicitly disconnect from this LLEventPump. + // Monitors application status. + mStatusListener = LLCoros::getStopListener( poolName + "_pool", // Make sure it won't repeat names from lleventcoro - [pendingCoprocs = mPendingCoprocs, poolName](const LLSD& status) - { - auto& statsd = status["status"]; - if (statsd.asString() != "running") + [pendingCoprocs = mPendingCoprocs, poolName](const LLSD& event) { LL_INFOS("CoProcMgr") << "Pool " << poolName - << " closing queue because status " << statsd + << " closing queue because status " << event << LL_ENDL; // This should ensure that all waiting coprocedures in this // pool will wake up and terminate. pendingCoprocs->close(); - } - return false; - }); + }); } catch (const LLEventPump::DupListenerName &) { @@ -334,7 +329,7 @@ LLCoprocedurePool::LLCoprocedurePool(const std::string &poolName, size_t size): // // If this somehow happens again it is better to crash later on shutdown due to pump // not stopping coroutine and see warning in logs than on startup or during login. - LL_WARNS("CoProcMgr") << "Attempted to register dupplicate listener name: " << poolName + LL_WARNS("CoProcMgr") << "Attempted to register duplicate listener name: " << poolName << "_pool. Failed to start listener." << LL_ENDL; llassert(0); // Fix Me! Ignoring missing listener! diff --git a/indra/llmessage/llnamevalue.cpp b/indra/llmessage/llnamevalue.cpp index 853ae7df82..05ea3f26a1 100644 --- a/indra/llmessage/llnamevalue.cpp +++ b/indra/llmessage/llnamevalue.cpp @@ -967,4 +967,3 @@ std::ostream& operator<<(std::ostream& s, const LLNameValue &a) } return s; } - diff --git a/indra/llmessage/llproxy.cpp b/indra/llmessage/llproxy.cpp index 864e68998c..d713cb20d9 100644 --- a/indra/llmessage/llproxy.cpp +++ b/indra/llmessage/llproxy.cpp @@ -465,7 +465,7 @@ void LLProxy::applyProxySettings(CURL* handle) /** * @brief Send one TCP packet and receive one in return. * - * This operation is done synchronously with a 1000ms timeout. Therefore, it should not be used when a blocking + * This operation is done synchronously with a 100ms timeout. Therefore, it should not be used when a blocking * operation would impact the operation of the viewer. * * @param handle_ptr Pointer to a connected LLSocket of type STREAM_TCP. @@ -482,7 +482,7 @@ static apr_status_t tcp_blocking_handshake(LLSocket::ptr_t handle, char * dataou apr_size_t expected_len = outlen; - handle->setBlocking(1000); + handle->setBlocking(100000); // 100ms, 100000us. Should be sufficient for localhost, nearby network rv = apr_socket_send(apr_socket, dataout, &outlen); if (APR_SUCCESS != rv) @@ -498,8 +498,6 @@ static apr_status_t tcp_blocking_handshake(LLSocket::ptr_t handle, char * dataou rv = -1; } - ms_sleep(1); - if (APR_SUCCESS == rv) { expected_len = maxinlen; diff --git a/indra/llplugin/CMakeLists.txt b/indra/llplugin/CMakeLists.txt index 14a69afe6e..005426acde 100644 --- a/indra/llplugin/CMakeLists.txt +++ b/indra/llplugin/CMakeLists.txt @@ -31,14 +31,6 @@ set(llplugin_HEADER_FILES llpluginsharedmemory.h ) -if(NOT ADDRESS_SIZE EQUAL 32) - if(WINDOWS) - ##add_definitions(/FIXED:NO) - else(WINDOWS) # not windows therefore gcc LINUX and DARWIN - add_definitions(-fPIC) - endif(WINDOWS) -endif(NOT ADDRESS_SIZE EQUAL 32) - list(APPEND llplugin_SOURCE_FILES ${llplugin_HEADER_FILES}) add_library (llplugin ${llplugin_SOURCE_FILES}) diff --git a/indra/llplugin/llpluginclassmedia.cpp b/indra/llplugin/llpluginclassmedia.cpp index 8a356da93a..0527c76a2a 100644 --- a/indra/llplugin/llpluginclassmedia.cpp +++ b/indra/llplugin/llpluginclassmedia.cpp @@ -981,6 +981,15 @@ void LLPluginClassMedia::enableMediaPluginDebugging( bool enable ) sendMessage( message ); } +#if LL_LINUX +void LLPluginClassMedia::enablePipeWireVolumeCatcher( bool enable ) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "enable_pipewire_volume_catcher"); + message.setValueBoolean( "enable", enable ); + sendMessage( message ); +} +#endif + void LLPluginClassMedia::setTarget(const std::string &target) { mTarget = target; diff --git a/indra/llplugin/llpluginclassmedia.h b/indra/llplugin/llpluginclassmedia.h index d74b790d8f..5d2f3bbb79 100644 --- a/indra/llplugin/llpluginclassmedia.h +++ b/indra/llplugin/llpluginclassmedia.h @@ -135,6 +135,10 @@ public: // Text may be unicode (utf8 encoded) bool textInput(const std::string &text, MASK modifiers, LLSD native_key_data); +#if LL_LINUX + void enablePipeWireVolumeCatcher( bool enable ); +#endif + static std::string sOIDcookieUrl; static std::string sOIDcookieName; static std::string sOIDcookieValue; diff --git a/indra/llplugin/llpluginprocessparent.cpp b/indra/llplugin/llpluginprocessparent.cpp index 908f3c8ff5..b838e05560 100644 --- a/indra/llplugin/llpluginprocessparent.cpp +++ b/indra/llplugin/llpluginprocessparent.cpp @@ -48,7 +48,7 @@ LLPluginProcessParentOwner::~LLPluginProcessParentOwner() bool LLPluginProcessParent::sUseReadThread = false; apr_pollset_t *LLPluginProcessParent::sPollSet = NULL; bool LLPluginProcessParent::sPollsetNeedsRebuild = false; -LLCoros::Mutex *LLPluginProcessParent::sInstancesMutex = nullptr; +llcoro::Mutex *LLPluginProcessParent::sInstancesMutex = nullptr; LLPluginProcessParent::mapInstances_t LLPluginProcessParent::sInstances; LLThread *LLPluginProcessParent::sReadThread = NULL; @@ -87,7 +87,7 @@ LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner): { if(!sInstancesMutex) { - sInstancesMutex = new LLCoros::Mutex(); + sInstancesMutex = new llcoro::Mutex(); } mOwner = owner; @@ -145,7 +145,7 @@ LLPluginProcessParent::ptr_t LLPluginProcessParent::create(LLPluginProcessParent // Don't add to the global list until fully constructed. { - LLCoros::LockType lock(*sInstancesMutex); + llcoro::LockType lock(*sInstancesMutex); sInstances.insert(mapInstances_t::value_type(that.get(), that)); } @@ -161,7 +161,7 @@ void LLPluginProcessParent::shutdown() return; } - LLCoros::LockType lock(*sInstancesMutex); + llcoro::LockType lock(*sInstancesMutex); mapInstances_t::iterator it; for (it = sInstances.begin(); it != sInstances.end(); ++it) @@ -219,7 +219,7 @@ bool LLPluginProcessParent::pollTick() { // this grabs a copy of the smart pointer to ourselves to ensure that we do not // get destroyed until after this method returns. - LLCoros::LockType lock(*sInstancesMutex); + llcoro::LockType lock(*sInstancesMutex); mapInstances_t::iterator it = sInstances.find(this); if (it != sInstances.end()) that = (*it).second; @@ -238,7 +238,7 @@ void LLPluginProcessParent::removeFromProcessing() // Remove from the global list before beginning destruction. { // Make sure to get the global mutex _first_ here, to avoid a possible deadlock against LLPluginProcessParent::poll() - LLCoros::LockType lock(*sInstancesMutex); + llcoro::LockType lock(*sInstancesMutex); { LLMutexLock lock2(&mIncomingQueueMutex); sInstances.erase(this); @@ -823,7 +823,7 @@ void LLPluginProcessParent::updatePollset() return; } - LLCoros::LockType lock(*sInstancesMutex); + llcoro::LockType lock(*sInstancesMutex); if(sPollSet) { @@ -946,7 +946,7 @@ void LLPluginProcessParent::poll(F64 timeout) mapInstances_t::iterator it; { - LLCoros::LockType lock(*sInstancesMutex); + llcoro::LockType lock(*sInstancesMutex); it = sInstances.find(thatId); if (it != sInstances.end()) that = (*it).second; diff --git a/indra/llplugin/llpluginprocessparent.h b/indra/llplugin/llpluginprocessparent.h index 334f1411af..6422fc21f3 100644 --- a/indra/llplugin/llpluginprocessparent.h +++ b/indra/llplugin/llpluginprocessparent.h @@ -33,6 +33,7 @@ #include <boost/enable_shared_from_this.hpp> #include "llapr.h" +#include "llcoromutex.h" #include "llprocess.h" #include "llpluginmessage.h" #include "llpluginmessagepipe.h" @@ -200,7 +201,7 @@ private: apr_pollfd_t mPollFD; static apr_pollset_t *sPollSet; static bool sPollsetNeedsRebuild; - static LLCoros::Mutex *sInstancesMutex; + static llcoro::Mutex *sInstancesMutex; static mapInstances_t sInstances; static void dirtyPollSet(); static void updatePollset(); diff --git a/indra/llprimitive/lldaeloader.cpp b/indra/llprimitive/lldaeloader.cpp index f286bff353..7fa4230237 100644 --- a/indra/llprimitive/lldaeloader.cpp +++ b/indra/llprimitive/lldaeloader.cpp @@ -1115,19 +1115,17 @@ bool LLDAELoader::OpenFile(const std::string& filename) if (skin) { - domGeometry* geom = daeSafeCast<domGeometry>(skin->getSource().getElement()); - - if (geom) + if (domGeometry* geom = daeSafeCast<domGeometry>(skin->getSource().getElement())) { - domMesh* mesh = geom->getMesh(); - if (mesh) + if (domMesh* mesh = geom->getMesh()) { - std::vector< LLPointer< LLModel > >::iterator i = mModelsMap[mesh].begin(); - while (i != mModelsMap[mesh].end()) + dae_model_map::const_iterator it = mModelsMap.find(mesh); + if (it != mModelsMap.end()) { - LLPointer<LLModel> mdl = *i; - LLDAELoader::processDomModel(mdl, &dae, root, mesh, skin); - i++; + for (const LLPointer<LLModel>& model : it->second) + { + LLDAELoader::processDomModel(model, &dae, root, mesh, skin); + } } } } @@ -1297,6 +1295,7 @@ void LLDAELoader::processDomModel(LLModel* model, DAE* dae, daeElement* root, do } } else + { //Has one or more skeletons for (std::vector<domInstance_controller::domSkeleton*>::iterator skel_it = skeletons.begin(); skel_it != skeletons.end(); ++skel_it) @@ -1381,6 +1380,7 @@ void LLDAELoader::processDomModel(LLModel* model, DAE* dae, daeElement* root, do } }//got skeleton? } + } domSkin::domJoints* joints = skin->getJoints(); @@ -1681,7 +1681,7 @@ void LLDAELoader::processDomModel(LLModel* model, DAE* dae, daeElement* root, do materials[model->mMaterialList[i]] = LLImportMaterial(); } mScene[transformation].push_back(LLModelInstance(model, model->mLabel, transformation, materials)); - stretch_extents(model, transformation, mExtents[0], mExtents[1], mFirstTransform); + stretch_extents(model, transformation); } } @@ -2074,21 +2074,14 @@ void LLDAELoader::processElement( daeElement* element, bool& badElement, DAE* da mTransform.condition(); } - domInstance_geometry* instance_geo = daeSafeCast<domInstance_geometry>(element); - if (instance_geo) + if (domInstance_geometry* instance_geo = daeSafeCast<domInstance_geometry>(element)) { - domGeometry* geo = daeSafeCast<domGeometry>(instance_geo->getUrl().getElement()); - if (geo) + if (domGeometry* geo = daeSafeCast<domGeometry>(instance_geo->getUrl().getElement())) { - domMesh* mesh = daeSafeCast<domMesh>(geo->getDescendant(daeElement::matchType(domMesh::ID()))); - if (mesh) + if (domMesh* mesh = daeSafeCast<domMesh>(geo->getDescendant(daeElement::matchType(domMesh::ID())))) { - - std::vector< LLPointer< LLModel > >::iterator i = mModelsMap[mesh].begin(); - while (i != mModelsMap[mesh].end()) + for (LLModel* model : mModelsMap.find(mesh)->second) { - LLModel* model = *i; - LLMatrix4 transformation = mTransform; if (mTransform.determinant() < 0) @@ -2159,8 +2152,7 @@ void LLDAELoader::processElement( daeElement* element, bool& badElement, DAE* da } mScene[transformation].push_back(LLModelInstance(model, label, transformation, materials)); - stretch_extents(model, transformation, mExtents[0], mExtents[1], mFirstTransform); - i++; + stretch_extents(model, transformation); } } } diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 9908a155f2..4e3e49ec9f 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -91,19 +91,15 @@ std::string LLModel::getStatusString(U32 status) } -void LLModel::offsetMesh( const LLVector3& pivotPoint ) +void LLModel::offsetMesh(const LLVector3& pivotPoint) { - LLVector4a pivot( pivotPoint[VX], pivotPoint[VY], pivotPoint[VZ] ); + LLVector4a pivot(pivotPoint[VX], pivotPoint[VY], pivotPoint[VZ]); - for (std::vector<LLVolumeFace>::iterator faceIt = mVolumeFaces.begin(); faceIt != mVolumeFaces.end(); ) + for (LLVolumeFace& face : mVolumeFaces) { - std::vector<LLVolumeFace>:: iterator currentFaceIt = faceIt++; - LLVolumeFace& face = *currentFaceIt; - LLVector4a *pos = (LLVector4a*) face.mPositions; - - for (S32 i=0; i<face.mNumVertices; ++i ) + for (S32 i = 0; i < face.mNumVertices; ++i) { - pos[i].add( pivot ); + face.mPositions[i].add(pivot); } } } @@ -338,7 +334,7 @@ void LLModel::normalizeVolumeFaces() } } -void LLModel::getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) +void LLModel::getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const { scale_out = mNormalizedScale; translation_out = mNormalizedTranslation; diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 96cfb7151e..c89037a2d2 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -205,8 +205,8 @@ public: void trimVolumeFacesToSize(U32 new_count = LL_SCULPT_MESH_MAX_FACES, LLVolume::face_list_t* remainder = NULL); void remapVolumeFaces(); void optimizeVolumeFaces(); - void offsetMesh( const LLVector3& pivotPoint ); - void getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out); + void offsetMesh(const LLVector3& pivotPoint); + void getNormalizedScaleTranslation(LLVector3& scale_out, LLVector3& translation_out) const; LLVector3 getTransformedCenter(const LLMatrix4& mat); //reorder face list based on mMaterialList in this and reference so diff --git a/indra/llprimitive/llmodelloader.cpp b/indra/llprimitive/llmodelloader.cpp index ae69c4f8ab..84adec4da5 100644 --- a/indra/llprimitive/llmodelloader.cpp +++ b/indra/llprimitive/llmodelloader.cpp @@ -36,7 +36,7 @@ std::list<LLModelLoader*> LLModelLoader::sActiveLoaderList; -void stretch_extents(LLModel* model, LLMatrix4a& mat, LLVector4a& min, LLVector4a& max, bool& first_transform) +static void stretch_extents(const LLModel* model, const LLMatrix4a& mat, LLVector4a& min, LLVector4a& max, bool& first_transform) { LLVector4a box[] = { @@ -58,7 +58,7 @@ void stretch_extents(LLModel* model, LLMatrix4a& mat, LLVector4a& min, LLVector4 center.setAdd(face.mExtents[0], face.mExtents[1]); center.mul(0.5f); LLVector4a size; - size.setSub(face.mExtents[1],face.mExtents[0]); + size.setSub(face.mExtents[1], face.mExtents[0]); size.mul(0.5f); for (U32 i = 0; i < 8; i++) @@ -84,19 +84,19 @@ void stretch_extents(LLModel* model, LLMatrix4a& mat, LLVector4a& min, LLVector4 } } -void stretch_extents(LLModel* model, LLMatrix4& mat, LLVector3& min, LLVector3& max, bool& first_transform) +void LLModelLoader::stretch_extents(const LLModel* model, const LLMatrix4& mat) { LLVector4a mina, maxa; LLMatrix4a mata; mata.loadu(mat); - mina.load3(min.mV); - maxa.load3(max.mV); + mina.load3(mExtents[0].mV); + maxa.load3(mExtents[1].mV); - stretch_extents(model, mata, mina, maxa, first_transform); + ::stretch_extents(model, mata, mina, maxa, mFirstTransform); - min.set(mina.getF32ptr()); - max.set(maxa.getF32ptr()); + mExtents[0].set(mina.getF32ptr()); + mExtents[1].set(maxa.getF32ptr()); } //----------------------------------------------------------------------------- @@ -291,14 +291,7 @@ bool LLModelLoader::loadFromSLM(const std::string& filename) { if (idx >= model[lod].size()) { - if (model[lod].size()) - { - instance_list[i].mLOD[lod] = model[lod][0]; - } - else - { - instance_list[i].mLOD[lod] = NULL; - } + instance_list[i].mLOD[lod] = model[lod].front(); continue; } @@ -341,7 +334,7 @@ bool LLModelLoader::loadFromSLM(const std::string& filename) { LLModelInstance& cur_instance = instance_list[i]; mScene[cur_instance.mTransform].push_back(cur_instance); - stretch_extents(cur_instance.mModel, cur_instance.mTransform, mExtents[0], mExtents[1], mFirstTransform); + stretch_extents(cur_instance.mModel, cur_instance.mTransform); } setLoadState( DONE ); diff --git a/indra/llprimitive/llmodelloader.h b/indra/llprimitive/llmodelloader.h index 83bd2f5bbd..637dabe08a 100644 --- a/indra/llprimitive/llmodelloader.h +++ b/indra/llprimitive/llmodelloader.h @@ -34,13 +34,13 @@ class LLJoint; -typedef std::map<std::string, LLMatrix4> JointTransformMap; -typedef std::map<std::string, LLMatrix4>::iterator JointTransformMapIt; -typedef std::map<std::string, std::string> JointMap; -typedef std::deque<std::string> JointNameSet; +typedef std::map<std::string, LLMatrix4> JointTransformMap; +typedef std::map<std::string, LLMatrix4>::iterator JointTransformMapIt; +typedef std::map<std::string, std::string> JointMap; +typedef std::deque<std::string> JointNameSet; const S32 SLM_SUPPORTED_VERSION = 3; -const S32 NUM_LOD = 4; +const S32 NUM_LOD = 4; const U32 LEGACY_RIG_OK = 0; const U32 LEGACY_RIG_FLAG_TOO_MANY_JOINTS = 1; @@ -50,32 +50,32 @@ class LLModelLoader : public LLThread { public: - typedef std::map<std::string, LLImportMaterial> material_map; - typedef std::vector<LLPointer<LLModel > > model_list; - typedef std::vector<LLModelInstance> model_instance_list; - typedef std::map<LLMatrix4, model_instance_list > scene; + typedef std::map<std::string, LLImportMaterial> material_map; + typedef std::vector<LLPointer<LLModel>> model_list; + typedef std::vector<LLModelInstance> model_instance_list; + typedef std::map<LLMatrix4, model_instance_list> scene; // Callback with loaded model data and loaded LoD // - typedef boost::function<void (scene&,model_list&,S32,void*) > load_callback_t; + typedef boost::function<void (scene&, model_list&, S32, void*)> load_callback_t; // Function to provide joint lookup by name // (within preview avi skeleton, for example) // - typedef boost::function<LLJoint* (const std::string&,void*) > joint_lookup_func_t; + typedef boost::function<LLJoint* (const std::string&, void*)> joint_lookup_func_t; // Func to load and associate material with all it's textures, // returned value is the number of textures loaded // intentionally non-const so func can modify material to // store platform-specific data // - typedef boost::function<U32 (LLImportMaterial&,void*) > texture_load_func_t; + typedef boost::function<U32 (LLImportMaterial&, void*)> texture_load_func_t; // Callback to inform client of state changes // during loading process (errors will be reported // as state changes here as well) // - typedef boost::function<void (U32,void*) > state_callback_t; + typedef boost::function<void (U32, void*)> state_callback_t; typedef enum { @@ -136,7 +136,7 @@ public: JointNameSet& jointsFromNodes, JointMap& legalJointNamesMap, U32 maxJointsPerMesh); - virtual ~LLModelLoader() ; + virtual ~LLModelLoader(); virtual void setNoNormalize() { mNoNormalize = true; } virtual void setNoOptimize() { mNoOptimize = true; } @@ -156,13 +156,13 @@ public: bool loadFromSLM(const std::string& filename); void loadModelCallback(); - void loadTextures() ; //called in the main thread. + void loadTextures(); // called in the main thread. void setLoadState(U32 state); + void stretch_extents(const LLModel* model, const LLMatrix4& mat); - - S32 mNumOfFetchingTextures ; //updated in the main thread - bool areTexturesReady() { return !mNumOfFetchingTextures; } //called in the main thread. + S32 mNumOfFetchingTextures; // updated in the main thread + bool areTexturesReady() { return !mNumOfFetchingTextures; } // called in the main thread. bool verifyCount( int expected, int result ); @@ -212,10 +212,7 @@ protected: LLSD mWarningsArray; // preview floater will pull logs from here static std::list<LLModelLoader*> sActiveLoaderList; - static bool isAlive(LLModelLoader* loader) ; + static bool isAlive(LLModelLoader* loader); }; -class LLMatrix4a; -void stretch_extents(LLModel* model, LLMatrix4a& mat, LLVector4a& min, LLVector4a& max, bool& first_transform); -void stretch_extents(LLModel* model, LLMatrix4& mat, LLVector3& min, LLVector3& max, bool& first_transform); #endif // LL_LLMODELLOADER_H diff --git a/indra/llprimitive/llprimtexturelist.cpp b/indra/llprimitive/llprimtexturelist.cpp index 68f3f5ffac..76322a9d29 100644 --- a/indra/llprimitive/llprimtexturelist.cpp +++ b/indra/llprimitive/llprimtexturelist.cpp @@ -137,14 +137,7 @@ S32 LLPrimTextureList::copyTexture(const U8 index, const LLTextureEntry& te) // we're changing an existing entry llassert(mEntryList[index]); delete (mEntryList[index]); - if (&te) - { - mEntryList[index] = te.newCopy(); - } - else - { - mEntryList[index] = LLPrimTextureList::newTextureEntry(); - } + mEntryList[index] = te.newCopy(); return TEM_CHANGE_TEXTURE; } diff --git a/indra/llprimitive/object_flags.h b/indra/llprimitive/object_flags.h index e2cdba072a..06e216ba49 100644 --- a/indra/llprimitive/object_flags.h +++ b/indra/llprimitive/object_flags.h @@ -57,16 +57,16 @@ const U32 FLAGS_CAMERA_SOURCE = (1U << 22); //const U32 FLAGS_UNUSED_001 = (1U << 23); // was FLAGS_CAST_SHADOWS -//const U32 FLAGS_UNUSED_002 = (1U << 24); -//const U32 FLAGS_UNUSED_003 = (1U << 25); -//const U32 FLAGS_UNUSED_004 = (1U << 26); -//const U32 FLAGS_UNUSED_005 = (1U << 27); +const U32 FLAGS_SERVER_AUTOPILOT = (1U << 24); // Update was for an agent AND that agent is being autopiloted from the server +//const U32 FLAGS_UNUSED_002 = (1U << 25); +//const U32 FLAGS_UNUSED_003 = (1U << 26); +//const U32 FLAGS_UNUSED_004 = (1U << 27); const U32 FLAGS_OBJECT_OWNER_MODIFY = (1U << 28); const U32 FLAGS_TEMPORARY_ON_REZ = (1U << 29); -//const U32 FLAGS_UNUSED_006 = (1U << 30); // was FLAGS_TEMPORARY -//const U32 FLAGS_UNUSED_007 = (1U << 31); // was FLAGS_ZLIB_COMPRESSED +//const U32 FLAGS_UNUSED_005 = (1U << 30); // was FLAGS_TEMPORARY +//const U32 FLAGS_UNUSED_006 = (1U << 31); // was FLAGS_ZLIB_COMPRESSED const U32 FLAGS_LOCAL = FLAGS_ANIM_SOURCE | FLAGS_CAMERA_SOURCE; const U32 FLAGS_WORLD = FLAGS_USE_PHYSICS | FLAGS_PHANTOM | FLAGS_TEMPORARY_ON_REZ; diff --git a/indra/llrender/CMakeLists.txt b/indra/llrender/CMakeLists.txt index ccff7c7a8c..26a8ff070f 100644 --- a/indra/llrender/CMakeLists.txt +++ b/indra/llrender/CMakeLists.txt @@ -23,7 +23,6 @@ set(llrender_SOURCE_FILES llglslshader.cpp llgltexture.cpp llimagegl.cpp - llpostprocess.cpp llrender.cpp llrender2dutils.cpp llrendernavprim.cpp @@ -56,7 +55,6 @@ set(llrender_HEADER_FILES llgltexture.h llgltypes.h llimagegl.h - llpostprocess.h llrender.h llrender2dutils.h llrendernavprim.h diff --git a/indra/llrender/llfontfreetype.cpp b/indra/llrender/llfontfreetype.cpp index 6128e03fa7..fa76669258 100644 --- a/indra/llrender/llfontfreetype.cpp +++ b/indra/llrender/llfontfreetype.cpp @@ -52,7 +52,9 @@ #include "llfontbitmapcache.h" #include "llgl.h" -#define ENABLE_OT_SVG_SUPPORT +#if !defined(LL_NO_OTSVG) + #define ENABLE_OT_SVG_SUPPORT +#endif FT_Render_Mode gFontRenderMode = FT_RENDER_MODE_NORMAL; @@ -87,7 +89,7 @@ LLFontManager::LLFontManager() FT_Done_FreeType(gFTLibrary); } -#ifdef ENABLE_OT_SVG_SUPPORT +#if defined(ENABLE_OT_SVG_SUPPORT) SVG_RendererHooks hooks = { LLFontFreeTypeSvgRenderer::OnInit, LLFontFreeTypeSvgRenderer::OnFree, @@ -483,8 +485,8 @@ LLFontGlyphInfo* LLFontFreetype::addGlyph(llwchar wch, EFontGlyphType glyph_type continue; } glyph_index = FT_Get_Char_Index(pair.first->mFTFace, wch); - if (glyph_index) - { + if (glyph_index) + { return addGlyphFromFont(pair.first, wch, glyph_index, glyph_type); } diff --git a/indra/llrender/llfontfreetypesvg.cpp b/indra/llrender/llfontfreetypesvg.cpp index 71f751329e..15251fe1b1 100644 --- a/indra/llrender/llfontfreetypesvg.cpp +++ b/indra/llrender/llfontfreetypesvg.cpp @@ -80,6 +80,7 @@ void LLFontFreeTypeSvgRenderer::OnDataFinalizer(void* objectp) //static FT_Error LLFontFreeTypeSvgRenderer::OnPresetGlypthSlot(FT_GlyphSlot glyph_slot, FT_Bool cache, FT_Pointer*) { +#ifndef LL_NO_OTSVG FT_SVG_Document document = static_cast<FT_SVG_Document>(glyph_slot->other); llassert(!glyph_slot->generic.data || !cache || glyph_slot->glyph_index == ((LLSvgRenderData*)glyph_slot->generic.data)->GlyphIndex); @@ -166,6 +167,9 @@ FT_Error LLFontFreeTypeSvgRenderer::OnPresetGlypthSlot(FT_GlyphSlot glyph_slot, } return FT_Err_Ok; +#else + return FT_Err_Unimplemented_Feature; +#endif } // static diff --git a/indra/llrender/llfontfreetypesvg.h b/indra/llrender/llfontfreetypesvg.h index 4170cab273..94b83d5fff 100644 --- a/indra/llrender/llfontfreetypesvg.h +++ b/indra/llrender/llfontfreetypesvg.h @@ -29,7 +29,11 @@ #include <ft2build.h> #include FT_TYPES_H #include FT_MODULE_H -#include FT_OTSVG_H +#ifdef FT_OTSVG_H + #include FT_OTSVG_H +#else + #define LL_NO_OTSVG +#endif // See https://freetype.org/freetype2/docs/reference/ft2-svg_fonts.html class LLFontFreeTypeSvgRenderer diff --git a/indra/llrender/llfontgl.cpp b/indra/llrender/llfontgl.cpp index 9721b020c7..a012d57b5a 100644 --- a/indra/llrender/llfontgl.cpp +++ b/indra/llrender/llfontgl.cpp @@ -270,10 +270,10 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons const LLFontGlyphInfo* next_glyph = NULL; - static constexpr S32 GLYPH_BATCH_SIZE = 30; - static thread_local LLVector3 vertices[GLYPH_BATCH_SIZE * 4]; - static thread_local LLVector2 uvs[GLYPH_BATCH_SIZE * 4]; - static thread_local LLColor4U colors[GLYPH_BATCH_SIZE * 4]; + static constexpr U32 GLYPH_BATCH_SIZE = 30; + static thread_local LLVector4a vertices[GLYPH_BATCH_SIZE * 6]; + static thread_local LLVector2 uvs[GLYPH_BATCH_SIZE * 6]; + static thread_local LLColor4U colors[GLYPH_BATCH_SIZE * 6]; LLColor4U text_color(color); // Preserve the transparency to render fading emojis in fading text (e.g. @@ -281,7 +281,7 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons LLColor4U emoji_color(255, 255, 255, text_color.mV[VALPHA]); std::pair<EFontGlyphType, S32> bitmap_entry = std::make_pair(EFontGlyphType::Grayscale, -1); - S32 glyph_count = 0; + U32 glyph_count = 0; for (i = begin_offset; i < begin_offset + length; i++) { llwchar wch = wstr[i]; @@ -305,9 +305,9 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons // otherwise the queued glyphs will be taken from wrong textures. if (glyph_count > 0) { - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { - gGL.vertexBatchPreTransformed(vertices, uvs, colors, glyph_count * 4); + gGL.vertexBatchPreTransformed(vertices, uvs, colors, glyph_count * 6); } gGL.end(); glyph_count = 0; @@ -338,9 +338,9 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons if (glyph_count >= GLYPH_BATCH_SIZE) { - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { - gGL.vertexBatchPreTransformed(vertices, uvs, colors, glyph_count * 4); + gGL.vertexBatchPreTransformed(vertices, uvs, colors, glyph_count * 6); } gGL.end(); @@ -376,9 +376,9 @@ S32 LLFontGL::render(const LLWString &wstr, S32 begin_offset, F32 x, F32 y, cons cur_render_y = cur_y; } - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { - gGL.vertexBatchPreTransformed(vertices, uvs, colors, glyph_count * 4); + gGL.vertexBatchPreTransformed(vertices, uvs, colors, glyph_count * 6); } gGL.end(); @@ -1227,31 +1227,42 @@ LLFontGL &LLFontGL::operator=(const LLFontGL &source) return *this; } -void LLFontGL::renderQuad(LLVector3* vertex_out, LLVector2* uv_out, LLColor4U* colors_out, const LLRectf& screen_rect, const LLRectf& uv_rect, const LLColor4U& color, F32 slant_amt) const +void LLFontGL::renderTriangle(LLVector4a* vertex_out, LLVector2* uv_out, LLColor4U* colors_out, const LLRectf& screen_rect, const LLRectf& uv_rect, const LLColor4U& color, F32 slant_amt) const { S32 index = 0; - vertex_out[index] = LLVector3(screen_rect.mRight, screen_rect.mTop, 0.f); - uv_out[index] = LLVector2(uv_rect.mRight, uv_rect.mTop); + vertex_out[index].set(screen_rect.mRight, screen_rect.mTop, 0.f); + uv_out[index].set(uv_rect.mRight, uv_rect.mTop); colors_out[index] = color; index++; - vertex_out[index] = LLVector3(screen_rect.mLeft, screen_rect.mTop, 0.f); - uv_out[index] = LLVector2(uv_rect.mLeft, uv_rect.mTop); + vertex_out[index].set(screen_rect.mLeft, screen_rect.mTop, 0.f); + uv_out[index].set(uv_rect.mLeft, uv_rect.mTop); colors_out[index] = color; index++; - vertex_out[index] = LLVector3(screen_rect.mLeft, screen_rect.mBottom, 0.f); - uv_out[index] = LLVector2(uv_rect.mLeft, uv_rect.mBottom); + vertex_out[index].set(screen_rect.mLeft, screen_rect.mBottom, 0.f); + uv_out[index].set(uv_rect.mLeft, uv_rect.mBottom); colors_out[index] = color; index++; - vertex_out[index] = LLVector3(screen_rect.mRight, screen_rect.mBottom, 0.f); - uv_out[index] = LLVector2(uv_rect.mRight, uv_rect.mBottom); + + vertex_out[index].set(screen_rect.mRight, screen_rect.mTop, 0.f); + uv_out[index].set(uv_rect.mRight, uv_rect.mTop); + colors_out[index] = color; + index++; + + vertex_out[index].set(screen_rect.mLeft, screen_rect.mBottom, 0.f); + uv_out[index].set(uv_rect.mLeft, uv_rect.mBottom); + colors_out[index] = color; + index++; + + vertex_out[index].set(screen_rect.mRight, screen_rect.mBottom, 0.f); + uv_out[index].set(uv_rect.mRight, uv_rect.mBottom); colors_out[index] = color; } -void LLFontGL::drawGlyph(S32& glyph_count, LLVector3* vertex_out, LLVector2* uv_out, LLColor4U* colors_out, const LLRectf& screen_rect, const LLRectf& uv_rect, const LLColor4U& color, U8 style, ShadowType shadow, F32 drop_shadow_strength) const +void LLFontGL::drawGlyph(U32& glyph_count, LLVector4a* vertex_out, LLVector2* uv_out, LLColor4U* colors_out, const LLRectf& screen_rect, const LLRectf& uv_rect, const LLColor4U& color, U8 style, ShadowType shadow, F32 drop_shadow_strength) const { F32 slant_offset; slant_offset = ((style & ITALIC) ? ( -mFontFreetype->getAscenderHeight() * 0.2f) : 0.f); @@ -1265,7 +1276,7 @@ void LLFontGL::drawGlyph(S32& glyph_count, LLVector3* vertex_out, LLVector2* uv_ LLRectf screen_rect_offset = screen_rect; screen_rect_offset.translate((F32)(pass * BOLD_OFFSET), 0.f); - renderQuad(&vertex_out[glyph_count * 4], &uv_out[glyph_count * 4], &colors_out[glyph_count * 4], screen_rect_offset, uv_rect, color, slant_offset); + renderTriangle(&vertex_out[glyph_count * 6], &uv_out[glyph_count * 6], &colors_out[glyph_count * 6], screen_rect_offset, uv_rect, color, slant_offset); glyph_count++; } } @@ -1296,10 +1307,10 @@ void LLFontGL::drawGlyph(S32& glyph_count, LLVector3* vertex_out, LLVector2* uv_ break; } - renderQuad(&vertex_out[glyph_count * 4], &uv_out[glyph_count * 4], &colors_out[glyph_count * 4], screen_rect_offset, uv_rect, shadow_color, slant_offset); + renderTriangle(&vertex_out[glyph_count * 6], &uv_out[glyph_count * 6], &colors_out[glyph_count * 6], screen_rect_offset, uv_rect, shadow_color, slant_offset); glyph_count++; } - renderQuad(&vertex_out[glyph_count * 4], &uv_out[glyph_count * 4], &colors_out[glyph_count * 4], screen_rect, uv_rect, color, slant_offset); + renderTriangle(&vertex_out[glyph_count * 6], &uv_out[glyph_count * 6], &colors_out[glyph_count * 6], screen_rect, uv_rect, color, slant_offset); glyph_count++; } else if (shadow == DROP_SHADOW) @@ -1308,14 +1319,14 @@ void LLFontGL::drawGlyph(S32& glyph_count, LLVector3* vertex_out, LLVector2* uv_ shadow_color.mV[VALPHA] = U8(color.mV[VALPHA] * drop_shadow_strength); LLRectf screen_rect_shadow = screen_rect; screen_rect_shadow.translate(1.f, -1.f); - renderQuad(&vertex_out[glyph_count * 4], &uv_out[glyph_count * 4], &colors_out[glyph_count * 4], screen_rect_shadow, uv_rect, shadow_color, slant_offset); + renderTriangle(&vertex_out[glyph_count * 6], &uv_out[glyph_count * 6], &colors_out[glyph_count * 6], screen_rect_shadow, uv_rect, shadow_color, slant_offset); glyph_count++; - renderQuad(&vertex_out[glyph_count * 4], &uv_out[glyph_count * 4], &colors_out[glyph_count * 4], screen_rect, uv_rect, color, slant_offset); + renderTriangle(&vertex_out[glyph_count * 6], &uv_out[glyph_count * 6], &colors_out[glyph_count * 6], screen_rect, uv_rect, color, slant_offset); glyph_count++; } else // normal rendering { - renderQuad(&vertex_out[glyph_count * 4], &uv_out[glyph_count * 4], &colors_out[glyph_count * 4], screen_rect, uv_rect, color, slant_offset); + renderTriangle(&vertex_out[glyph_count * 6], &uv_out[glyph_count * 6], &colors_out[glyph_count * 6], screen_rect, uv_rect, color, slant_offset); glyph_count++; } } diff --git a/indra/llrender/llfontgl.h b/indra/llrender/llfontgl.h index de7529a583..a257d94512 100644 --- a/indra/llrender/llfontgl.h +++ b/indra/llrender/llfontgl.h @@ -238,8 +238,8 @@ private: LLFontDescriptor mFontDescriptor; LLPointer<LLFontFreetype> mFontFreetype; - void renderQuad(LLVector3* vertex_out, LLVector2* uv_out, LLColor4U* colors_out, const LLRectf& screen_rect, const LLRectf& uv_rect, const LLColor4U& color, F32 slant_amt) const; - void drawGlyph(S32& glyph_count, LLVector3* vertex_out, LLVector2* uv_out, LLColor4U* colors_out, const LLRectf& screen_rect, const LLRectf& uv_rect, const LLColor4U& color, U8 style, ShadowType shadow, F32 drop_shadow_fade) const; + void renderTriangle(LLVector4a* vertex_out, LLVector2* uv_out, LLColor4U* colors_out, const LLRectf& screen_rect, const LLRectf& uv_rect, const LLColor4U& color, F32 slant_amt) const; + void drawGlyph(U32& glyph_count, LLVector4a* vertex_out, LLVector2* uv_out, LLColor4U* colors_out, const LLRectf& screen_rect, const LLRectf& uv_rect, const LLColor4U& color, U8 style, ShadowType shadow, F32 drop_shadow_fade) const; // Registry holds all instantiated fonts. static LLFontRegistry* sFontRegistry; diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index c62cacdce6..15fb289de3 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -215,8 +215,6 @@ LLMatrix4 gGLObliqueProjectionInverse; std::list<LLGLUpdate*> LLGLUpdate::sGLQ; -#if (LL_WINDOWS || LL_LINUX) && !LL_MESA_HEADLESS - #if LL_WINDOWS // WGL_ARB_create_context PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = nullptr; @@ -236,8 +234,6 @@ PFNWGLBLITCONTEXTFRAMEBUFFERAMDPROC wglBlitContextFramebufferAMD = n PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = nullptr; PFNWGLGETSWAPINTERVALEXTPROC wglGetSwapIntervalEXT = nullptr; -#endif - // GL_VERSION_1_2 //PFNGLDRAWRANGEELEMENTSPROC glDrawRangeElements = nullptr; //PFNGLTEXIMAGE3DPROC glTexImage3D = nullptr; @@ -1170,6 +1166,11 @@ bool LLGLManager::initGL() mGLVendorShort = "INTEL"; mIsIntel = true; } + else if (mGLVendor.find("APPLE") != std::string::npos) + { + mGLVendorShort = "APPLE"; + mIsApple = true; + } else { mGLVendorShort = "MISC"; @@ -1402,6 +1403,9 @@ void LLGLManager::shutdownGL() void LLGLManager::initExtensions() { +#if LL_LINUX + glh_init_extensions(""); +#endif #if LL_DARWIN GLint num_extensions = 0; std::string all_extensions{""}; @@ -1432,10 +1436,9 @@ void LLGLManager::initExtensions() mInited = true; -#if (LL_WINDOWS || LL_LINUX) && !LL_MESA_HEADLESS +#if LL_WINDOWS LL_DEBUGS("RenderInit") << "GL Probe: Getting symbols" << LL_ENDL; -#if LL_WINDOWS // WGL_AMD_gpu_association wglGetGPUIDsAMD = (PFNWGLGETGPUIDSAMDPROC)GLH_EXT_GET_PROC_ADDRESS("wglGetGPUIDsAMD"); wglGetGPUInfoAMD = (PFNWGLGETGPUINFOAMDPROC)GLH_EXT_GET_PROC_ADDRESS("wglGetGPUInfoAMD"); @@ -1453,8 +1456,6 @@ void LLGLManager::initExtensions() // WGL_ARB_create_context wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)GLH_EXT_GET_PROC_ADDRESS("wglCreateContextAttribsARB"); -#endif - // Load entire OpenGL API through GetProcAddress, leaving sections beyond mGLVersion unloaded @@ -2582,6 +2583,7 @@ void parse_gl_version( S32* major, S32* minor, S32* release, std::string* vendor { return; } + LL_INFOS() << "GL: " << version << LL_ENDL; version_string->assign(version); @@ -2957,5 +2959,3 @@ extern "C" __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; } #endif - - diff --git a/indra/llrender/llgl.h b/indra/llrender/llgl.h index 17f825bd71..f5b1e8d786 100644 --- a/indra/llrender/llgl.h +++ b/indra/llrender/llgl.h @@ -102,6 +102,7 @@ public: bool mIsAMD; bool mIsNVIDIA; bool mIsIntel; + bool mIsApple = false; // hints to the render pipe U32 mDownScaleMethod = 0; // see settings.xml RenderDownScaleMethod diff --git a/indra/llrender/llglheaders.h b/indra/llrender/llglheaders.h index 3d4dc5e698..921adc0f8c 100644 --- a/indra/llrender/llglheaders.h +++ b/indra/llrender/llglheaders.h @@ -41,6 +41,22 @@ # include "GL/glh_extensions.h" # undef __APPLE__ +#elif LL_LINUX +#define GL_GLEXT_PROTOTYPES +#define GLX_GLEXT_PROTOTYPES + +#include "GL/gl.h" +#include "GL/glext.h" +#include "GL/glu.h" + +// The __APPLE__ kludge is to make glh_extensions.h not symbol-clash horribly +# define __APPLE__ +# include "GL/glh_extensions.h" +# undef __APPLE__ + +# include "GL/glx.h" +# include "GL/glxext.h" + #elif LL_WINDOWS //---------------------------------------------------------------------------- // LL_WINDOWS @@ -1029,6 +1045,25 @@ extern void glGetBufferPointervARB (GLenum, GLenum, GLvoid* *); #include <OpenGL/gl.h> +#elif LL_LINUX + +#define GL_GLEXT_PROTOTYPES +#define GLX_GLEXT_PROTOTYPES + +#include "GL/gl.h" +#include "GL/glu.h" +#include "GL/glext.h" +#include "GL/glx.h" + +// The __APPLE__ kludge is to make glh_extensions.h not symbol-clash horribly +# define __APPLE__ +# include "GL/glh_extensions.h" +# undef __APPLE__ + +// #include <X11/Xlib.h> +// #include <X11/Xutil.h> +#include "GL/glh_extensions.h" + #endif // LL_MESA / LL_WINDOWS / LL_DARWIN // Even when GL_ARB_depth_clamp is available in the driver, the (correct) diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index a157bfee21..6ba5463acd 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -63,6 +63,7 @@ U64 LLGLSLShader::sTotalTimeElapsed = 0; U32 LLGLSLShader::sTotalTrianglesDrawn = 0; U64 LLGLSLShader::sTotalSamplesDrawn = 0; U32 LLGLSLShader::sTotalBinds = 0; +boost::json::value LLGLSLShader::sDefaultStats; //UI shader -- declared here so llui_libtest will link properly LLGLSLShader gUIProgram; @@ -101,9 +102,9 @@ void LLGLSLShader::initProfile() sTotalSamplesDrawn = 0; sTotalBinds = 0; - for (std::set<LLGLSLShader*>::iterator iter = sInstances.begin(); iter != sInstances.end(); ++iter) + for (auto ptr : sInstances) { - (*iter)->clearStats(); + ptr->clearStats(); } } @@ -117,45 +118,57 @@ struct LLGLSLShaderCompareTimeElapsed }; //static -void LLGLSLShader::finishProfile(bool emit_report) +void LLGLSLShader::finishProfile(boost::json::value& statsv) { sProfileEnabled = false; - if (emit_report) + if (! statsv.is_null()) { - std::vector<LLGLSLShader*> sorted; - - for (std::set<LLGLSLShader*>::iterator iter = sInstances.begin(); iter != sInstances.end(); ++iter) - { - sorted.push_back(*iter); - } - + std::vector<LLGLSLShader*> sorted(sInstances.begin(), sInstances.end()); std::sort(sorted.begin(), sorted.end(), LLGLSLShaderCompareTimeElapsed()); + auto& stats = statsv.as_object(); + auto shadersit = stats.emplace("shaders", boost::json::array_kind).first; + auto& shaders = shadersit->value().as_array(); bool unbound = false; - for (std::vector<LLGLSLShader*>::iterator iter = sorted.begin(); iter != sorted.end(); ++iter) + for (auto ptr : sorted) { - (*iter)->dumpStats(); - if ((*iter)->mBinds == 0) + if (ptr->mBinds == 0) { unbound = true; } + else + { + auto& shaderit = shaders.emplace_back(boost::json::object_kind); + ptr->dumpStats(shaderit.as_object()); + } } + constexpr float mega = 1'000'000.f; + float totalTimeMs = sTotalTimeElapsed / mega; LL_INFOS() << "-----------------------------------" << LL_ENDL; - LL_INFOS() << "Total rendering time: " << llformat("%.4f ms", sTotalTimeElapsed / 1000000.f) << LL_ENDL; - LL_INFOS() << "Total samples drawn: " << llformat("%.4f million", sTotalSamplesDrawn / 1000000.f) << LL_ENDL; - LL_INFOS() << "Total triangles drawn: " << llformat("%.3f million", sTotalTrianglesDrawn / 1000000.f) << LL_ENDL; + LL_INFOS() << "Total rendering time: " << llformat("%.4f ms", totalTimeMs) << LL_ENDL; + LL_INFOS() << "Total samples drawn: " << llformat("%.4f million", sTotalSamplesDrawn / mega) << LL_ENDL; + LL_INFOS() << "Total triangles drawn: " << llformat("%.3f million", sTotalTrianglesDrawn / mega) << LL_ENDL; LL_INFOS() << "-----------------------------------" << LL_ENDL; - + auto totalsit = stats.emplace("totals", boost::json::object_kind).first; + auto& totals = totalsit->value().as_object(); + totals.emplace("time", totalTimeMs / 1000.0); + totals.emplace("binds", sTotalBinds); + totals.emplace("samples", sTotalSamplesDrawn); + totals.emplace("triangles", sTotalTrianglesDrawn); + + auto unusedit = stats.emplace("unused", boost::json::array_kind).first; + auto& unused = unusedit->value().as_array(); if (unbound) { LL_INFOS() << "The following shaders were unused: " << LL_ENDL; - for (std::vector<LLGLSLShader*>::iterator iter = sorted.begin(); iter != sorted.end(); ++iter) + for (auto ptr : sorted) { - if ((*iter)->mBinds == 0) + if (ptr->mBinds == 0) { - LL_INFOS() << (*iter)->mName << LL_ENDL; + LL_INFOS() << ptr->mName << LL_ENDL; + unused.emplace_back(ptr->mName); } } } @@ -170,36 +183,43 @@ void LLGLSLShader::clearStats() mBinds = 0; } -void LLGLSLShader::dumpStats() +void LLGLSLShader::dumpStats(boost::json::object& stats) { - if (mBinds > 0) + stats.emplace("name", mName); + auto filesit = stats.emplace("files", boost::json::array_kind).first; + auto& files = filesit->value().as_array(); + LL_INFOS() << "=============================================" << LL_ENDL; + LL_INFOS() << mName << LL_ENDL; + for (U32 i = 0; i < mShaderFiles.size(); ++i) { - LL_INFOS() << "=============================================" << LL_ENDL; - LL_INFOS() << mName << LL_ENDL; - for (U32 i = 0; i < mShaderFiles.size(); ++i) - { - LL_INFOS() << mShaderFiles[i].first << LL_ENDL; - } - LL_INFOS() << "=============================================" << LL_ENDL; + LL_INFOS() << mShaderFiles[i].first << LL_ENDL; + files.emplace_back(mShaderFiles[i].first); + } + LL_INFOS() << "=============================================" << LL_ENDL; - F32 ms = mTimeElapsed / 1000000.f; - F32 seconds = ms / 1000.f; + constexpr float mega = 1'000'000.f; + constexpr double giga = 1'000'000'000.0; + F32 ms = mTimeElapsed / mega; + F32 seconds = ms / 1000.f; - F32 pct_tris = (F32)mTrianglesDrawn / (F32)sTotalTrianglesDrawn * 100.f; - F32 tris_sec = (F32)(mTrianglesDrawn / 1000000.0); - tris_sec /= seconds; + F32 pct_tris = (F32)mTrianglesDrawn / (F32)sTotalTrianglesDrawn * 100.f; + F32 tris_sec = (F32)(mTrianglesDrawn / mega); + tris_sec /= seconds; - F32 pct_samples = (F32)((F64)mSamplesDrawn / (F64)sTotalSamplesDrawn) * 100.f; - F32 samples_sec = (F32)(mSamplesDrawn / 1000000000.0); - samples_sec /= seconds; + F32 pct_samples = (F32)((F64)mSamplesDrawn / (F64)sTotalSamplesDrawn) * 100.f; + F32 samples_sec = (F32)(mSamplesDrawn / giga); + samples_sec /= seconds; - F32 pct_binds = (F32)mBinds / (F32)sTotalBinds * 100.f; + F32 pct_binds = (F32)mBinds / (F32)sTotalBinds * 100.f; - LL_INFOS() << "Triangles Drawn: " << mTrianglesDrawn << " " << llformat("(%.2f pct of total, %.3f million/sec)", pct_tris, tris_sec) << LL_ENDL; - LL_INFOS() << "Binds: " << mBinds << " " << llformat("(%.2f pct of total)", pct_binds) << LL_ENDL; - LL_INFOS() << "SamplesDrawn: " << mSamplesDrawn << " " << llformat("(%.2f pct of total, %.3f billion/sec)", pct_samples, samples_sec) << LL_ENDL; - LL_INFOS() << "Time Elapsed: " << mTimeElapsed << " " << llformat("(%.2f pct of total, %.5f ms)\n", (F32)((F64)mTimeElapsed / (F64)sTotalTimeElapsed) * 100.f, ms) << LL_ENDL; - } + LL_INFOS() << "Triangles Drawn: " << mTrianglesDrawn << " " << llformat("(%.2f pct of total, %.3f million/sec)", pct_tris, tris_sec) << LL_ENDL; + LL_INFOS() << "Binds: " << mBinds << " " << llformat("(%.2f pct of total)", pct_binds) << LL_ENDL; + LL_INFOS() << "SamplesDrawn: " << mSamplesDrawn << " " << llformat("(%.2f pct of total, %.3f billion/sec)", pct_samples, samples_sec) << LL_ENDL; + LL_INFOS() << "Time Elapsed: " << mTimeElapsed << " " << llformat("(%.2f pct of total, %.5f ms)\n", (F32)((F64)mTimeElapsed / (F64)sTotalTimeElapsed) * 100.f, ms) << LL_ENDL; + stats.emplace("time", seconds); + stats.emplace("binds", mBinds); + stats.emplace("samples", mSamplesDrawn); + stats.emplace("triangles", mTrianglesDrawn); } //static diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h index 27c8f0b7d0..2d669c70a9 100644 --- a/indra/llrender/llglslshader.h +++ b/indra/llrender/llglslshader.h @@ -30,6 +30,7 @@ #include "llgl.h" #include "llrender.h" #include "llstaticstringtable.h" +#include <boost/json.hpp> #include <unordered_map> class LLShaderFeatures @@ -169,14 +170,14 @@ public: static U32 sMaxGLTFNodes; static void initProfile(); - static void finishProfile(bool emit_report = true); + static void finishProfile(boost::json::value& stats=sDefaultStats); static void startProfile(); static void stopProfile(); void unload(); void clearStats(); - void dumpStats(); + void dumpStats(boost::json::object& stats); // place query objects for profiling if profiling is enabled // if for_runtime is true, will place timer query only whether or not profiling is enabled @@ -363,6 +364,11 @@ public: private: void unloadInternal(); + // This must be static because finishProfile() is called at least once + // within a __try block. If we default its stats parameter to a temporary + // json::value, that temporary must be destroyed when the stack is + // unwound, which __try forbids. + static boost::json::value sDefaultStats; }; //UI shader (declared here so llui_libtest will link properly) diff --git a/indra/llrender/llpostprocess.cpp b/indra/llrender/llpostprocess.cpp deleted file mode 100644 index eef7193c92..0000000000 --- a/indra/llrender/llpostprocess.cpp +++ /dev/null @@ -1,454 +0,0 @@ -/** - * @file llpostprocess.cpp - * @brief LLPostProcess class implementation - * - * $LicenseInfo:firstyear=2007&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 "linden_common.h" - -#include "llpostprocess.h" -#include "llglslshader.h" -#include "llsdserialize.h" -#include "llrender.h" - -static LLStaticHashedString sRenderTexture("RenderTexture"); -static LLStaticHashedString sBrightness("brightness"); -static LLStaticHashedString sContrast("contrast"); -static LLStaticHashedString sContrastBase("contrastBase"); -static LLStaticHashedString sSaturation("saturation"); -static LLStaticHashedString sLumWeights("lumWeights"); -static LLStaticHashedString sNoiseTexture("NoiseTexture"); -static LLStaticHashedString sBrightMult("brightMult"); -static LLStaticHashedString sNoiseStrength("noiseStrength"); -static LLStaticHashedString sExtractLow("extractLow"); -static LLStaticHashedString sExtractHigh("extractHigh"); -static LLStaticHashedString sBloomStrength("bloomStrength"); -static LLStaticHashedString sTexelSize("texelSize"); -static LLStaticHashedString sBlurDirection("blurDirection"); -static LLStaticHashedString sBlurWidth("blurWidth"); - -LLPostProcess * gPostProcess = NULL; - -static const unsigned int NOISE_SIZE = 512; - -LLPostProcess::LLPostProcess(void) : - initialized(false), - mAllEffects(LLSD::emptyMap()), - screenW(1), screenH(1) -{ - mSceneRenderTexture = NULL ; - mNoiseTexture = NULL ; - mTempBloomTexture = NULL ; - - noiseTextureScale = 1.0f; - - /* Do nothing. Needs to be updated to use our current shader system, and to work with the move into llrender. - std::string pathName(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "windlight", XML_FILENAME)); - LL_DEBUGS("AppInit", "Shaders") << "Loading PostProcess Effects settings from " << pathName << LL_ENDL; - - llifstream effectsXML(pathName); - - if (effectsXML) - { - LLPointer<LLSDParser> parser = new LLSDXMLParser(); - - parser->parse(effectsXML, mAllEffects, LLSDSerialize::SIZE_UNLIMITED); - } - - if (!mAllEffects.has("default")) - { - LLSD & defaultEffect = (mAllEffects["default"] = LLSD::emptyMap()); - - defaultEffect["enable_night_vision"] = LLSD::Boolean(false); - defaultEffect["enable_bloom"] = LLSD::Boolean(false); - defaultEffect["enable_color_filter"] = LLSD::Boolean(false); - - /// NVG Defaults - defaultEffect["brightness_multiplier"] = 3.0; - defaultEffect["noise_size"] = 25.0; - defaultEffect["noise_strength"] = 0.4; - - // TODO BTest potentially add this to tweaks? - noiseTextureScale = 1.0f; - - /// Bloom Defaults - defaultEffect["extract_low"] = 0.95; - defaultEffect["extract_high"] = 1.0; - defaultEffect["bloom_width"] = 2.25; - defaultEffect["bloom_strength"] = 1.5; - - /// Color Filter Defaults - defaultEffect["brightness"] = 1.0; - defaultEffect["contrast"] = 1.0; - defaultEffect["saturation"] = 1.0; - - LLSD& contrastBase = (defaultEffect["contrast_base"] = LLSD::emptyArray()); - contrastBase.append(1.0); - contrastBase.append(1.0); - contrastBase.append(1.0); - contrastBase.append(0.5); - } - - setSelectedEffect("default"); - */ -} - -LLPostProcess::~LLPostProcess(void) -{ - invalidate() ; -} - -// static -void LLPostProcess::initClass(void) -{ - //this will cause system to crash at second time login - //if first time login fails due to network connection --- bao - //***llassert_always(gPostProcess == NULL); - //replaced by the following line: - if(gPostProcess) - return ; - - - gPostProcess = new LLPostProcess(); -} - -// static -void LLPostProcess::cleanupClass() -{ - delete gPostProcess; - gPostProcess = NULL; -} - -void LLPostProcess::setSelectedEffect(std::string const & effectName) -{ - mSelectedEffectName = effectName; - static_cast<LLSD &>(tweaks) = mAllEffects[effectName]; -} - -void LLPostProcess::saveEffect(std::string const & effectName) -{ - /* Do nothing. Needs to be updated to use our current shader system, and to work with the move into llrender. - mAllEffects[effectName] = tweaks; - - std::string pathName(gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "windlight", XML_FILENAME)); - //LL_INFOS() << "Saving PostProcess Effects settings to " << pathName << LL_ENDL; - - llofstream effectsXML(pathName); - - LLPointer<LLSDFormatter> formatter = new LLSDXMLFormatter(); - - formatter->format(mAllEffects, effectsXML); - */ -} -void LLPostProcess::invalidate() -{ - mSceneRenderTexture = NULL ; - mNoiseTexture = NULL ; - mTempBloomTexture = NULL ; - initialized = false ; -} - -void LLPostProcess::apply(unsigned int width, unsigned int height) -{ - if (!initialized || width != screenW || height != screenH){ - initialize(width, height); - } - if (shadersEnabled()){ - doEffects(); - } -} - -void LLPostProcess::initialize(unsigned int width, unsigned int height) -{ - screenW = width; - screenH = height; - createTexture(mSceneRenderTexture, screenW, screenH); - initialized = true; - - checkError(); - createNightVisionShader(); - createBloomShader(); - createColorFilterShader(); - checkError(); -} - -inline bool LLPostProcess::shadersEnabled(void) -{ - return (tweaks.useColorFilter().asBoolean() || - tweaks.useNightVisionShader().asBoolean() || - tweaks.useBloomShader().asBoolean() ); - -} - -void LLPostProcess::applyShaders(void) -{ - if (tweaks.useColorFilter()){ - applyColorFilterShader(); - checkError(); - } - if (tweaks.useNightVisionShader()){ - /// If any of the above shaders have been called update the frame buffer; - if (tweaks.useColorFilter()) - { - U32 tex = mSceneRenderTexture->getTexName() ; - copyFrameBuffer(tex, screenW, screenH); - } - applyNightVisionShader(); - checkError(); - } - if (tweaks.useBloomShader()){ - /// If any of the above shaders have been called update the frame buffer; - if (tweaks.useColorFilter().asBoolean() || tweaks.useNightVisionShader().asBoolean()) - { - U32 tex = mSceneRenderTexture->getTexName() ; - copyFrameBuffer(tex, screenW, screenH); - } - applyBloomShader(); - checkError(); - } -} - -void LLPostProcess::applyColorFilterShader(void) -{ - -} - -void LLPostProcess::createColorFilterShader(void) -{ - /// Define uniform names - colorFilterUniforms[sRenderTexture] = 0; - colorFilterUniforms[sBrightness] = 0; - colorFilterUniforms[sContrast] = 0; - colorFilterUniforms[sContrastBase] = 0; - colorFilterUniforms[sSaturation] = 0; - colorFilterUniforms[sLumWeights] = 0; -} - -void LLPostProcess::applyNightVisionShader(void) -{ - -} - -void LLPostProcess::createNightVisionShader(void) -{ - /// Define uniform names - nightVisionUniforms[sRenderTexture] = 0; - nightVisionUniforms[sNoiseTexture] = 0; - nightVisionUniforms[sBrightMult] = 0; - nightVisionUniforms[sNoiseStrength] = 0; - nightVisionUniforms[sLumWeights] = 0; - - createNoiseTexture(mNoiseTexture); -} - -void LLPostProcess::applyBloomShader(void) -{ - -} - -void LLPostProcess::createBloomShader(void) -{ - createTexture(mTempBloomTexture, unsigned(screenW * 0.5), unsigned(screenH * 0.5)); - - /// Create Bloom Extract Shader - bloomExtractUniforms[sRenderTexture] = 0; - bloomExtractUniforms[sExtractLow] = 0; - bloomExtractUniforms[sExtractHigh] = 0; - bloomExtractUniforms[sLumWeights] = 0; - - /// Create Bloom Blur Shader - bloomBlurUniforms[sRenderTexture] = 0; - bloomBlurUniforms[sBloomStrength] = 0; - bloomBlurUniforms[sTexelSize] = 0; - bloomBlurUniforms[sBlurDirection] = 0; - bloomBlurUniforms[sBlurWidth] = 0; -} - -void LLPostProcess::getShaderUniforms(glslUniforms & uniforms, GLuint & prog) -{ - /// Find uniform locations and insert into map - glslUniforms::iterator i; - for (i = uniforms.begin(); i != uniforms.end(); ++i){ - i->second = glGetUniformLocation(prog, i->first.String().c_str()); - } -} - -void LLPostProcess::doEffects(void) -{ - /// Save GL State - glPushAttrib(GL_ALL_ATTRIB_BITS); - glPushClientAttrib(GL_ALL_ATTRIB_BITS); - - /// Copy the screen buffer to the render texture - { - U32 tex = mSceneRenderTexture->getTexName() ; - copyFrameBuffer(tex, screenW, screenH); - } - - /// Clear the frame buffer. - glClearColor(0.0f, 0.0f, 0.0f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - - /// Change to an orthogonal view - viewOrthogonal(screenW, screenH); - - checkError(); - applyShaders(); - - LLGLSLShader::unbind(); - checkError(); - - /// Change to a perspective view - viewPerspective(); - - /// Reset GL State - glPopClientAttrib(); - glPopAttrib(); - checkError(); -} - -void LLPostProcess::copyFrameBuffer(U32 & texture, unsigned int width, unsigned int height) -{ - gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, texture); - glCopyTexImage2D(GL_TEXTURE_RECTANGLE, 0, GL_RGBA, 0, 0, width, height, 0); -} - -void LLPostProcess::drawOrthoQuad(unsigned int width, unsigned int height, QuadType type) -{ - -} - -void LLPostProcess::viewOrthogonal(unsigned int width, unsigned int height) -{ - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.pushMatrix(); - gGL.loadIdentity(); - gGL.ortho( 0.f, (GLfloat) width , (GLfloat) height , 0.f, -1.f, 1.f ); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.pushMatrix(); - gGL.loadIdentity(); -} - -void LLPostProcess::viewPerspective(void) -{ - gGL.matrixMode(LLRender::MM_PROJECTION); - gGL.popMatrix(); - gGL.matrixMode(LLRender::MM_MODELVIEW); - gGL.popMatrix(); -} - -void LLPostProcess::changeOrthogonal(unsigned int width, unsigned int height) -{ - viewPerspective(); - viewOrthogonal(width, height); -} - -void LLPostProcess::createTexture(LLPointer<LLImageGL>& texture, unsigned int width, unsigned int height) -{ - std::vector<GLubyte> data(width * height * 4, 0) ; - - texture = new LLImageGL(false) ; - if(texture->createGLTexture()) - { - gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, texture->getTexName()); - glTexImage2D(GL_TEXTURE_RECTANGLE, 0, 4, width, height, 0, - GL_RGBA, GL_UNSIGNED_BYTE, &data[0]); - gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR); - gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_CLAMP); - } -} - -void LLPostProcess::createNoiseTexture(LLPointer<LLImageGL>& texture) -{ - std::vector<GLubyte> buffer(NOISE_SIZE * NOISE_SIZE); - for (unsigned int i = 0; i < NOISE_SIZE; i++){ - for (unsigned int k = 0; k < NOISE_SIZE; k++){ - buffer[(i * NOISE_SIZE) + k] = (GLubyte)((double) rand() / ((double) RAND_MAX + 1.f) * 255.f); - } - } - - texture = new LLImageGL(false) ; - if(texture->createGLTexture()) - { - gGL.getTexUnit(0)->bindManual(LLTexUnit::TT_TEXTURE, texture->getTexName()); - LLImageGL::setManualImage(GL_TEXTURE_2D, 0, GL_LUMINANCE, NOISE_SIZE, NOISE_SIZE, GL_LUMINANCE, GL_UNSIGNED_BYTE, &buffer[0]); - gGL.getTexUnit(0)->setTextureFilteringOption(LLTexUnit::TFO_BILINEAR); - gGL.getTexUnit(0)->setTextureAddressMode(LLTexUnit::TAM_WRAP); - } -} - -bool LLPostProcess::checkError(void) -{ - GLenum glErr; - bool retCode = false; - - glErr = glGetError(); - while (glErr != GL_NO_ERROR) - { - // shaderErrorLog << (const char *) gluErrorString(glErr) << std::endl; - char const * err_str_raw = (const char *) gluErrorString(glErr); - - if(err_str_raw == NULL) - { - std::ostringstream err_builder; - err_builder << "unknown error number " << glErr; - mShaderErrorString = err_builder.str(); - } - else - { - mShaderErrorString = err_str_raw; - } - - retCode = true; - glErr = glGetError(); - } - return retCode; -} - -void LLPostProcess::checkShaderError(GLuint shader) -{ - GLint infologLength = 0; - GLint charsWritten = 0; - GLchar *infoLog; - - checkError(); // Check for OpenGL errors - - glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infologLength); - - checkError(); // Check for OpenGL errors - - if (infologLength > 0) - { - infoLog = (GLchar *)malloc(infologLength); - if (infoLog == NULL) - { - /// Could not allocate infolog buffer - return; - } - glGetProgramInfoLog(shader, infologLength, &charsWritten, infoLog); - // shaderErrorLog << (char *) infoLog << std::endl; - mShaderErrorString = (char *) infoLog; - free(infoLog); - } - checkError(); // Check for OpenGL errors -} diff --git a/indra/llrender/llpostprocess.h b/indra/llrender/llpostprocess.h deleted file mode 100644 index b5b7549efb..0000000000 --- a/indra/llrender/llpostprocess.h +++ /dev/null @@ -1,267 +0,0 @@ -/** - * @file llpostprocess.h - * @brief LLPostProcess class definition - * - * $LicenseInfo:firstyear=2007&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_POSTPROCESS_H -#define LL_POSTPROCESS_H - -#include <map> -#include <fstream> -#include "llgl.h" -#include "llglheaders.h" -#include "llstaticstringtable.h" - -class LLPostProcess -{ -public: - - typedef enum _QuadType { - QUAD_NORMAL, - QUAD_NOISE, - QUAD_BLOOM_EXTRACT, - QUAD_BLOOM_COMBINE - } QuadType; - - /// GLSL Shader Encapsulation Struct - typedef LLStaticStringTable<GLuint> glslUniforms; - - struct PostProcessTweaks : public LLSD { - inline PostProcessTweaks() : LLSD(LLSD::emptyMap()) - { - } - - inline LLSD & brightMult() { - return (*this)["brightness_multiplier"]; - } - - inline LLSD & noiseStrength() { - return (*this)["noise_strength"]; - } - - inline LLSD & noiseSize() { - return (*this)["noise_size"]; - } - - inline LLSD & extractLow() { - return (*this)["extract_low"]; - } - - inline LLSD & extractHigh() { - return (*this)["extract_high"]; - } - - inline LLSD & bloomWidth() { - return (*this)["bloom_width"]; - } - - inline LLSD & bloomStrength() { - return (*this)["bloom_strength"]; - } - - inline LLSD & brightness() { - return (*this)["brightness"]; - } - - inline LLSD & contrast() { - return (*this)["contrast"]; - } - - inline LLSD & contrastBaseR() { - return (*this)["contrast_base"][0]; - } - - inline LLSD & contrastBaseG() { - return (*this)["contrast_base"][1]; - } - - inline LLSD & contrastBaseB() { - return (*this)["contrast_base"][2]; - } - - inline LLSD & contrastBaseIntensity() { - return (*this)["contrast_base"][3]; - } - - inline LLSD & saturation() { - return (*this)["saturation"]; - } - - inline LLSD & useNightVisionShader() { - return (*this)["enable_night_vision"]; - } - - inline LLSD & useBloomShader() { - return (*this)["enable_bloom"]; - } - - inline LLSD & useColorFilter() { - return (*this)["enable_color_filter"]; - } - - - inline F32 getBrightMult() const { - return F32((*this)["brightness_multiplier"].asReal()); - } - - inline F32 getNoiseStrength() const { - return F32((*this)["noise_strength"].asReal()); - } - - inline F32 getNoiseSize() const { - return F32((*this)["noise_size"].asReal()); - } - - inline F32 getExtractLow() const { - return F32((*this)["extract_low"].asReal()); - } - - inline F32 getExtractHigh() const { - return F32((*this)["extract_high"].asReal()); - } - - inline F32 getBloomWidth() const { - return F32((*this)["bloom_width"].asReal()); - } - - inline F32 getBloomStrength() const { - return F32((*this)["bloom_strength"].asReal()); - } - - inline F32 getBrightness() const { - return F32((*this)["brightness"].asReal()); - } - - inline F32 getContrast() const { - return F32((*this)["contrast"].asReal()); - } - - inline F32 getContrastBaseR() const { - return F32((*this)["contrast_base"][0].asReal()); - } - - inline F32 getContrastBaseG() const { - return F32((*this)["contrast_base"][1].asReal()); - } - - inline F32 getContrastBaseB() const { - return F32((*this)["contrast_base"][2].asReal()); - } - - inline F32 getContrastBaseIntensity() const { - return F32((*this)["contrast_base"][3].asReal()); - } - - inline F32 getSaturation() const { - return F32((*this)["saturation"].asReal()); - } - - }; - - bool initialized; - PostProcessTweaks tweaks; - - // the map of all availible effects - LLSD mAllEffects; - -private: - LLPointer<LLImageGL> mSceneRenderTexture ; - LLPointer<LLImageGL> mNoiseTexture ; - LLPointer<LLImageGL> mTempBloomTexture ; - -public: - LLPostProcess(void); - - ~LLPostProcess(void); - - void apply(unsigned int width, unsigned int height); - void invalidate() ; - - /// Perform global initialization for this class. - static void initClass(void); - - // Cleanup of global data that's only inited once per class. - static void cleanupClass(); - - void setSelectedEffect(std::string const & effectName); - - inline std::string const & getSelectedEffect(void) const { - return mSelectedEffectName; - } - - void saveEffect(std::string const & effectName); - -private: - /// read in from file - std::string mShaderErrorString; - unsigned int screenW; - unsigned int screenH; - - float noiseTextureScale; - - /// Shader Uniforms - glslUniforms nightVisionUniforms; - glslUniforms bloomExtractUniforms; - glslUniforms bloomBlurUniforms; - glslUniforms colorFilterUniforms; - - // the name of currently selected effect in mAllEffects - // invariant: tweaks == mAllEffects[mSelectedEffectName] - std::string mSelectedEffectName; - - /// General functions - void initialize(unsigned int width, unsigned int height); - void doEffects(void); - void applyShaders(void); - bool shadersEnabled(void); - - /// Night Vision Functions - void createNightVisionShader(void); - void applyNightVisionShader(void); - - /// Bloom Functions - void createBloomShader(void); - void applyBloomShader(void); - - /// Color Filter Functions - void createColorFilterShader(void); - void applyColorFilterShader(void); - - /// OpenGL Helper Functions - void getShaderUniforms(glslUniforms & uniforms, GLuint & prog); - void createTexture(LLPointer<LLImageGL>& texture, unsigned int width, unsigned int height); - void copyFrameBuffer(U32 & texture, unsigned int width, unsigned int height); - void createNoiseTexture(LLPointer<LLImageGL>& texture); - bool checkError(void); - void checkShaderError(GLuint shader); - void drawOrthoQuad(unsigned int width, unsigned int height, QuadType type); - void viewOrthogonal(unsigned int width, unsigned int height); - void changeOrthogonal(unsigned int width, unsigned int height); - void viewPerspective(void); -}; - -extern LLPostProcess * gPostProcess; - - -#endif // LL_POSTPROCESS_H diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp index 16a8309a9a..565071ff0d 100644 --- a/indra/llrender/llrender.cpp +++ b/indra/llrender/llrender.cpp @@ -151,7 +151,8 @@ void LLTexUnit::refreshState(void) void LLTexUnit::activate(void) { - if (mIndex < 0) return; + if (mIndex < 0) + return; if ((S32)gGL.mCurrTextureUnitIndex != mIndex || gGL.mDirty) { @@ -163,7 +164,8 @@ void LLTexUnit::activate(void) void LLTexUnit::enable(eTextureType type) { - if (mIndex < 0) return; + if (mIndex < 0) + return; if ( (mCurrTexType != type || gGL.mDirty) && (type != TT_NONE) ) { @@ -180,7 +182,8 @@ void LLTexUnit::enable(eTextureType type) void LLTexUnit::disable(void) { - if (mIndex < 0) return; + if (mIndex < 0) + return; if (mCurrTexType != TT_NONE) { @@ -216,7 +219,7 @@ bool LLTexUnit::bind(LLTexture* texture, bool for_rendering, bool forceBind) { gGL.flush(); - LLImageGL* gl_tex = NULL ; + LLImageGL* gl_tex = NULL; if (texture != NULL && (gl_tex = texture->getGLTexture())) { @@ -231,8 +234,8 @@ bool LLTexUnit::bind(LLTexture* texture, bool for_rendering, bool forceBind) glBindTexture(sGLTextureType[gl_tex->getTarget()], mCurrTexture); if(gl_tex->updateBindStats()) { - texture->setActive() ; - texture->updateBindStatsForTester() ; + texture->setActive(); + texture->updateBindStatsForTester(); } mHasMipMaps = gl_tex->mHasMipMaps; if (gl_tex->mTexOptionsDirty) @@ -246,9 +249,9 @@ bool LLTexUnit::bind(LLTexture* texture, bool for_rendering, bool forceBind) else { //if deleted, will re-generate it immediately - texture->forceImmediateUpdate() ; + texture->forceImmediateUpdate(); - gl_tex->forceUpdateBindStats() ; + gl_tex->forceUpdateBindStats(); return texture->bindDefaultImage(mIndex); } } @@ -276,24 +279,27 @@ bool LLTexUnit::bind(LLTexture* texture, bool for_rendering, bool forceBind) bool LLTexUnit::bind(LLImageGL* texture, bool for_rendering, bool forceBind, S32 usename) { stop_glerror(); - if (mIndex < 0) return false; + if (mIndex < 0) + { + return false; + } U32 texname = usename ? usename : texture->getTexName(); - if(!texture) + if (!texture) { LL_DEBUGS() << "NULL LLTexUnit::bind texture" << LL_ENDL; return false; } - if(!texname) + if (!texname) { if(LLImageGL::sDefaultGLTexture && LLImageGL::sDefaultGLTexture->getTexName()) { - return bind(LLImageGL::sDefaultGLTexture) ; + return bind(LLImageGL::sDefaultGLTexture); } stop_glerror(); - return false ; + return false; } if ((mCurrTexture != texname) || forceBind) @@ -326,7 +332,10 @@ bool LLTexUnit::bind(LLImageGL* texture, bool for_rendering, bool forceBind, S32 bool LLTexUnit::bind(LLCubeMap* cubeMap) { - if (mIndex < 0) return false; + if (mIndex < 0) + { + return false; + } gGL.flush(); @@ -366,7 +375,10 @@ bool LLTexUnit::bind(LLCubeMap* cubeMap) // LLRenderTarget is unavailible on the mapserver since it uses FBOs. bool LLTexUnit::bind(LLRenderTarget* renderTarget, bool bindDepth) { - if (mIndex < 0) return false; + if (mIndex < 0) + { + return false; + } gGL.flush(); @@ -391,7 +403,7 @@ bool LLTexUnit::bindManual(eTextureType type, U32 texture, bool hasMips) return false; } - if(mCurrTexture != texture) + if (mCurrTexture != texture) { gGL.flush(); @@ -401,6 +413,7 @@ bool LLTexUnit::bindManual(eTextureType type, U32 texture, bool hasMips) glBindTexture(sGLTextureType[type], texture); mHasMipMaps = hasMips; } + return true; } @@ -408,7 +421,8 @@ void LLTexUnit::unbind(eTextureType type) { stop_glerror(); - if (mIndex < 0) return; + if (mIndex < 0) + return; //always flush and activate for consistency // some code paths assume unbind always flushes and sets the active texture @@ -454,7 +468,8 @@ void LLTexUnit::unbindFast(eTextureType type) void LLTexUnit::setTextureAddressMode(eTextureAddressMode mode) { - if (mIndex < 0 || mCurrTexture == 0) return; + if (mIndex < 0 || mCurrTexture == 0) + return; gGL.flush(); @@ -470,7 +485,8 @@ void LLTexUnit::setTextureAddressMode(eTextureAddressMode mode) void LLTexUnit::setTextureFilteringOption(LLTexUnit::eTextureFilterOptions option) { - if (mIndex < 0 || mCurrTexture == 0 || mCurrTexType == LLTexUnit::TT_MULTISAMPLE_TEXTURE) return; + if (mIndex < 0 || mCurrTexture == 0 || mCurrTexType == LLTexUnit::TT_MULTISAMPLE_TEXTURE) + return; gGL.flush(); @@ -525,7 +541,7 @@ void LLTexUnit::setTextureFilteringOption(LLTexUnit::eTextureFilterOptions optio GLint LLTexUnit::getTextureSource(eTextureBlendSrc src) { - switch(src) + switch (src) { // All four cases should return the same value. case TBS_PREV_COLOR: @@ -563,14 +579,14 @@ GLint LLTexUnit::getTextureSource(eTextureBlendSrc src) GLint LLTexUnit::getTextureSourceType(eTextureBlendSrc src, bool isAlpha) { - switch(src) + switch (src) { // All four cases should return the same value. case TBS_PREV_COLOR: case TBS_TEX_COLOR: case TBS_VERT_COLOR: case TBS_CONST_COLOR: - return (isAlpha) ? GL_SRC_ALPHA: GL_SRC_COLOR; + return isAlpha ? GL_SRC_ALPHA : GL_SRC_COLOR; // All four cases should return the same value. case TBS_PREV_ALPHA: @@ -584,7 +600,7 @@ GLint LLTexUnit::getTextureSourceType(eTextureBlendSrc src, bool isAlpha) case TBS_ONE_MINUS_TEX_COLOR: case TBS_ONE_MINUS_VERT_COLOR: case TBS_ONE_MINUS_CONST_COLOR: - return (isAlpha) ? GL_ONE_MINUS_SRC_ALPHA : GL_ONE_MINUS_SRC_COLOR; + return isAlpha ? GL_ONE_MINUS_SRC_ALPHA : GL_ONE_MINUS_SRC_COLOR; // All four cases should return the same value. case TBS_ONE_MINUS_PREV_ALPHA: @@ -595,7 +611,7 @@ GLint LLTexUnit::getTextureSourceType(eTextureBlendSrc src, bool isAlpha) default: LL_WARNS() << "Unknown eTextureBlendSrc: " << src << ". Using Source Color or Alpha instead." << LL_ENDL; - return (isAlpha) ? GL_SRC_ALPHA: GL_SRC_COLOR; + return isAlpha ? GL_SRC_ALPHA : GL_SRC_COLOR; } } @@ -605,7 +621,7 @@ void LLTexUnit::setColorScale(S32 scale) { mCurrColorScale = scale; gGL.flush(); - glTexEnvi( GL_TEXTURE_ENV, GL_RGB_SCALE, scale ); + glTexEnvi(GL_TEXTURE_ENV, GL_RGB_SCALE, scale); } } @@ -615,7 +631,7 @@ void LLTexUnit::setAlphaScale(S32 scale) { mCurrAlphaScale = scale; gGL.flush(); - glTexEnvi( GL_TEXTURE_ENV, GL_ALPHA_SCALE, scale ); + glTexEnvi(GL_TEXTURE_ENV, GL_ALPHA_SCALE, scale); } } @@ -623,7 +639,8 @@ void LLTexUnit::setAlphaScale(S32 scale) // texture unit based on the currently set active texture in opengl. void LLTexUnit::debugTextureUnit(void) { - if (mIndex < 0) return; + if (mIndex < 0) + return; GLint activeTexture; glGetIntegerv(GL_ACTIVE_TEXTURE, &activeTexture); @@ -645,16 +662,16 @@ LLLightState::LLLightState(S32 index) { if (mIndex == 0) { - mDiffuse.set(1,1,1,1); - mDiffuseB.set(0,0,0,0); - mSpecular.set(1,1,1,1); + mDiffuse.set(1, 1, 1, 1); + mDiffuseB.set(0, 0, 0, 0); + mSpecular.set(1, 1, 1, 1); } mSunIsPrimary = true; - mAmbient.set(0,0,0,1); - mPosition.set(0,0,1,0); - mSpotDirection.set(0,0,-1); + mAmbient.set(0, 0, 0, 1); + mPosition.set(0, 0, 1, 0); + mSpotDirection.set(0, 0, -1); } void LLLightState::enable() @@ -803,7 +820,6 @@ void LLLightState::setSpotDirection(const LLVector3& direction) LLRender::LLRender() : mDirty(false), mCount(0), - mQuadCycle(0), mMode(LLRender::TRIANGLES), mCurrTextureUnitIndex(0) { @@ -887,6 +903,7 @@ bool LLRender::init(bool needs_vertex_buffer) { initVertexBuffer(); } + return true; } @@ -1120,7 +1137,6 @@ void LLRender::syncMatrices() } } - if (shader->mFeatures.hasLighting || shader->mFeatures.calculatesLighting || shader->mFeatures.calculatesAtmospherics) { //also sync light state syncLightState(); @@ -1133,91 +1149,78 @@ void LLRender::translatef(const GLfloat& x, const GLfloat& y, const GLfloat& z) { flush(); - { - mMatrix[mMatrixMode][mMatIdx[mMatrixMode]] = glm::translate(mMatrix[mMatrixMode][mMatIdx[mMatrixMode]], glm::vec3(x, y, z)); - mMatHash[mMatrixMode]++; - } + mMatrix[mMatrixMode][mMatIdx[mMatrixMode]] = glm::translate(mMatrix[mMatrixMode][mMatIdx[mMatrixMode]], glm::vec3(x, y, z)); + mMatHash[mMatrixMode]++; } void LLRender::scalef(const GLfloat& x, const GLfloat& y, const GLfloat& z) { flush(); - { - mMatrix[mMatrixMode][mMatIdx[mMatrixMode]] = glm::scale(mMatrix[mMatrixMode][mMatIdx[mMatrixMode]], glm::vec3(x, y, z)); - mMatHash[mMatrixMode]++; - } + mMatrix[mMatrixMode][mMatIdx[mMatrixMode]] = glm::scale(mMatrix[mMatrixMode][mMatIdx[mMatrixMode]], glm::vec3(x, y, z)); + mMatHash[mMatrixMode]++; } void LLRender::ortho(F32 left, F32 right, F32 bottom, F32 top, F32 zNear, F32 zFar) { flush(); - { - mMatrix[mMatrixMode][mMatIdx[mMatrixMode]] *= glm::ortho(left, right, bottom, top, zNear, zFar); - mMatHash[mMatrixMode]++; - } + mMatrix[mMatrixMode][mMatIdx[mMatrixMode]] *= glm::ortho(left, right, bottom, top, zNear, zFar); + mMatHash[mMatrixMode]++; } void LLRender::rotatef(const GLfloat& a, const GLfloat& x, const GLfloat& y, const GLfloat& z) { flush(); - { - mMatrix[mMatrixMode][mMatIdx[mMatrixMode]] = glm::rotate(mMatrix[mMatrixMode][mMatIdx[mMatrixMode]], glm::radians(a), glm::vec3(x,y,z)); - mMatHash[mMatrixMode]++; - } + mMatrix[mMatrixMode][mMatIdx[mMatrixMode]] = glm::rotate(mMatrix[mMatrixMode][mMatIdx[mMatrixMode]], glm::radians(a), glm::vec3(x,y,z)); + mMatHash[mMatrixMode]++; } void LLRender::pushMatrix() { flush(); + if (mMatIdx[mMatrixMode] < LL_MATRIX_STACK_DEPTH-1) { - if (mMatIdx[mMatrixMode] < LL_MATRIX_STACK_DEPTH-1) - { - mMatrix[mMatrixMode][mMatIdx[mMatrixMode]+1] = mMatrix[mMatrixMode][mMatIdx[mMatrixMode]]; - ++mMatIdx[mMatrixMode]; - } - else - { - LL_WARNS() << "Matrix stack overflow." << LL_ENDL; - } + mMatrix[mMatrixMode][mMatIdx[mMatrixMode]+1] = mMatrix[mMatrixMode][mMatIdx[mMatrixMode]]; + ++mMatIdx[mMatrixMode]; + } + else + { + LL_WARNS() << "Matrix stack overflow." << LL_ENDL; } } void LLRender::popMatrix() { flush(); + + if (mMatIdx[mMatrixMode] > 0) { - if (mMatIdx[mMatrixMode] > 0) - { - --mMatIdx[mMatrixMode]; - mMatHash[mMatrixMode]++; - } - else - { - LL_WARNS() << "Matrix stack underflow." << LL_ENDL; - } + --mMatIdx[mMatrixMode]; + mMatHash[mMatrixMode]++; + } + else + { + LL_WARNS() << "Matrix stack underflow." << LL_ENDL; } } void LLRender::loadMatrix(const GLfloat* m) { flush(); - { - mMatrix[mMatrixMode][mMatIdx[mMatrixMode]] = glm::make_mat4((GLfloat*) m); - mMatHash[mMatrixMode]++; - } + + mMatrix[mMatrixMode][mMatIdx[mMatrixMode]] = glm::make_mat4((GLfloat*) m); + mMatHash[mMatrixMode]++; } void LLRender::multMatrix(const GLfloat* m) { flush(); - { - mMatrix[mMatrixMode][mMatIdx[mMatrixMode]] *= glm::make_mat4(m); - mMatHash[mMatrixMode]++; - } + + mMatrix[mMatrixMode][mMatIdx[mMatrixMode]] *= glm::make_mat4(m); + mMatHash[mMatrixMode]++; } void LLRender::matrixMode(eMatrixMode mode) @@ -1255,12 +1258,10 @@ void LLRender::loadIdentity() { flush(); - { - llassert_always(mMatrixMode < NUM_MATRIX_MODES) ; + llassert_always(mMatrixMode < NUM_MATRIX_MODES); - mMatrix[mMatrixMode][mMatIdx[mMatrixMode]] = glm::identity<glm::mat4>(); - mMatHash[mMatrixMode]++; - } + mMatrix[mMatrixMode][mMatIdx[mMatrixMode]] = glm::identity<glm::mat4>(); + mMatHash[mMatrixMode]++; } const glm::mat4& LLRender::getModelviewMatrix() @@ -1280,9 +1281,7 @@ void LLRender::translateUI(F32 x, F32 y, F32 z) LL_ERRS() << "Need to push a UI translation frame before offsetting" << LL_ENDL; } - mUIOffset.back().mV[0] += x; - mUIOffset.back().mV[1] += y; - mUIOffset.back().mV[2] += z; + mUIOffset.back().add(LLVector4a(x, y, z)); } void LLRender::scaleUI(F32 x, F32 y, F32 z) @@ -1292,14 +1291,14 @@ void LLRender::scaleUI(F32 x, F32 y, F32 z) LL_ERRS() << "Need to push a UI transformation frame before scaling." << LL_ENDL; } - mUIScale.back().scaleVec(LLVector3(x,y,z)); + mUIScale.back().mul(LLVector4a(x, y, z)); } void LLRender::pushUIMatrix() { if (mUIOffset.empty()) { - mUIOffset.push_back(LLVector3(0,0,0)); + mUIOffset.emplace_back(0.f); } else { @@ -1308,7 +1307,7 @@ void LLRender::pushUIMatrix() if (mUIScale.empty()) { - mUIScale.push_back(LLVector3(1,1,1)); + mUIScale.emplace_back(1.f); } else { @@ -1330,18 +1329,20 @@ LLVector3 LLRender::getUITranslation() { if (mUIOffset.empty()) { - return LLVector3(0,0,0); + return LLVector3::zero; } - return mUIOffset.back(); + + return LLVector3(mUIOffset.back().getF32ptr()); } LLVector3 LLRender::getUIScale() { if (mUIScale.empty()) { - return LLVector3(1,1,1); + return LLVector3::all_one; } - return mUIScale.back(); + + return LLVector3(mUIScale.back().getF32ptr()); } @@ -1351,8 +1352,9 @@ void LLRender::loadUIIdentity() { LL_ERRS() << "Need to push UI translation frame before clearing offset." << LL_ENDL; } - mUIOffset.back().setVec(0,0,0); - mUIScale.back().setVec(1,1,1); + + mUIOffset.back().clear(); + mUIScale.back().splat(1); } void LLRender::setColorMask(bool writeColor, bool writeAlpha) @@ -1446,7 +1448,7 @@ void LLRender::blendFunc(eBlendFactor color_sfactor, eBlendFactor color_dfactor, flush(); glBlendFuncSeparate(sGLBlendFactor[color_sfactor], sGLBlendFactor[color_dfactor], - sGLBlendFactor[alpha_sfactor], sGLBlendFactor[alpha_dfactor]); + sGLBlendFactor[alpha_sfactor], sGLBlendFactor[alpha_dfactor]); } } @@ -1456,11 +1458,9 @@ LLTexUnit* LLRender::getTexUnit(U32 index) { return &mTexUnits[index]; } - else - { - LL_DEBUGS() << "Non-existing texture unit layer requested: " << index << LL_ENDL; - return &mDummyTexUnit; - } + + LL_DEBUGS() << "Non-existing texture unit layer requested: " << index << LL_ENDL; + return &mDummyTexUnit; } LLLightState* LLRender::getLight(U32 index) @@ -1489,11 +1489,9 @@ bool LLRender::verifyTexUnitActive(U32 unitToVerify) { return true; } - else - { - LL_WARNS() << "TexUnit currently active: " << mCurrTextureUnitIndex << " (expecting " << unitToVerify << ")" << LL_ENDL; - return false; - } + + LL_WARNS() << "TexUnit currently active: " << mCurrTextureUnitIndex << " (expecting " << unitToVerify << ")" << LL_ENDL; + return false; } void LLRender::clearErrors() @@ -1510,6 +1508,7 @@ void LLRender::beginList(std::list<LLVertexBufferData> *list) { LL_ERRS() << "beginList called while another list is open." << LL_ENDL; } + llassert(LLGLSLShader::sCurBoundShaderPtr == &gUIProgram); flush(); sBufferDataList = list; @@ -1532,13 +1531,7 @@ void LLRender::begin(const GLuint& mode) { if (mode != mMode) { - if (mode == LLRender::QUADS) - { - mQuadCycle = 1; - } - - if (mMode == LLRender::QUADS || - mMode == LLRender::LINES || + if (mMode == LLRender::LINES || mMode == LLRender::TRIANGLES || mMode == LLRender::POINTS) { @@ -1561,8 +1554,7 @@ void LLRender::end() //IMM_ERRS << "GL begin and end called with no vertices specified." << LL_ENDL; } - if ((mMode != LLRender::QUADS && - mMode != LLRender::LINES && + if ((mMode != LLRender::LINES && mMode != LLRender::TRIANGLES && mMode != LLRender::POINTS) || mCount > 2048) @@ -1587,28 +1579,19 @@ void LLRender::flush() //store mCount in a local variable to avoid re-entrance (drawArrays may call flush) U32 count = mCount; - if (mMode == LLRender::QUADS && !sGLCoreProfile) - { - if (mCount%4 != 0) - { - count -= (mCount % 4); - LL_WARNS() << "Incomplete quad requested." << LL_ENDL; - } - } - - if (mMode == LLRender::TRIANGLES) + if (mMode == LLRender::TRIANGLES) + { + if (mCount % 3 != 0) { - if (mCount%3 != 0) - { - count -= (mCount % 3); - LL_WARNS() << "Incomplete triangle requested." << LL_ENDL; - } + count -= (mCount % 3); + LL_WARNS() << "Incomplete triangle requested." << LL_ENDL; } + } - if (mMode == LLRender::LINES) + if (mMode == LLRender::LINES) + { + if (mCount % 2 != 0) { - if (mCount%2 != 0) - { count -= (mCount % 2); LL_WARNS() << "Incomplete line requested." << LL_ENDL; } @@ -1618,7 +1601,6 @@ void LLRender::flush() if (mBuffer) { - LLVertexBuffer *vb; U32 attribute_mask = LLGLSLShader::sCurBoundShaderPtr->mAttributeMask; @@ -1725,6 +1707,7 @@ LLVertexBuffer* LLRender::bufferfromCache(U32 attribute_mask, U32 count) } } } + return vb; } @@ -1735,7 +1718,7 @@ LLVertexBuffer* LLRender::genBuffer(U32 attribute_mask, S32 count) vb->setBuffer(); - vb->setPositionData((LLVector4a*)mVerticesp.get()); + vb->setPositionData(mVerticesp.get()); if (attribute_mask & LLVertexBuffer::MAP_TEXCOORD0) { @@ -1758,16 +1741,7 @@ LLVertexBuffer* LLRender::genBuffer(U32 attribute_mask, S32 count) void LLRender::drawBuffer(LLVertexBuffer* vb, U32 mode, S32 count) { vb->setBuffer(); - - if (mode == LLRender::QUADS && sGLCoreProfile) - { - vb->drawArrays(LLRender::TRIANGLES, 0, count); - mQuadCycle = 1; - } - else - { - vb->drawArrays(mode, 0, count); - } + vb->drawArrays(mode, 0, count); } void LLRender::resetStriders(S32 count) @@ -1781,15 +1755,26 @@ void LLRender::resetStriders(S32 count) void LLRender::vertex3f(const GLfloat& x, const GLfloat& y, const GLfloat& z) { - //the range of mVerticesp, mColorsp and mTexcoordsp is [0, 4095] + // the range of mVerticesp, mColorsp and mTexcoordsp is [0, 4095] if (mCount > 2048) - { //break when buffer gets reasonably full to keep GL command buffers happy and avoid overflow below + { // break when buffer gets reasonably full to keep GL command buffers happy and avoid overflow below switch (mMode) { - case LLRender::POINTS: flush(); break; - case LLRender::TRIANGLES: if (mCount%3==0) flush(); break; - case LLRender::QUADS: if(mCount%4 == 0) flush(); break; - case LLRender::LINES: if (mCount%2 == 0) flush(); break; + case LLRender::POINTS: + flush(); + break; + case LLRender::TRIANGLES: + if (mCount % 3 == 0) + { + flush(); + } + break; + case LLRender::LINES: + if (mCount % 2 == 0) + { + flush(); + } + break; } } @@ -1799,42 +1784,64 @@ void LLRender::vertex3f(const GLfloat& x, const GLfloat& y, const GLfloat& z) return; } - if (mUIOffset.empty()) + LLVector4a vert(x, y, z); + transform(vert); + mVerticesp[mCount] = vert; + + mCount++; + mVerticesp[mCount] = vert; + mColorsp[mCount] = mColorsp[mCount-1]; + mTexcoordsp[mCount] = mTexcoordsp[mCount-1]; +} + +void LLRender::transform(LLVector3& vert) +{ + if (!mUIOffset.empty()) { - mVerticesp[mCount] = LLVector3(x,y,z); + vert += LLVector3(mUIOffset.back().getF32ptr()); + vert *= LLVector3(mUIScale.back().getF32ptr()); } - else +} + +void LLRender::transform(LLVector4a& vert) +{ + if (!mUIOffset.empty()) { - LLVector3 vert = (LLVector3(x,y,z)+mUIOffset.back()).scaledVec(mUIScale.back()); - mVerticesp[mCount] = vert; + vert.add(mUIOffset.back()); + vert.mul(mUIScale.back()); } +} - if (mMode == LLRender::QUADS && LLRender::sGLCoreProfile) +void LLRender::untransform(LLVector3& vert) +{ + if (!mUIOffset.empty()) { - mQuadCycle++; - if (mQuadCycle == 4) - { //copy two vertices so fourth quad element will add a triangle - mQuadCycle = 0; + vert /= LLVector3(mUIScale.back().getF32ptr()); + vert -= LLVector3(mUIOffset.back().getF32ptr()); + } +} - mCount++; - mVerticesp[mCount] = mVerticesp[mCount-3]; - mColorsp[mCount] = mColorsp[mCount-3]; - mTexcoordsp[mCount] = mTexcoordsp[mCount-3]; +void LLRender::batchTransform(LLVector4a* verts, U32 vert_count) +{ + if (!mUIOffset.empty()) + { + const LLVector4a& offset = mUIOffset.back(); + const LLVector4a& scale = mUIScale.back(); - mCount++; - mVerticesp[mCount] = mVerticesp[mCount-2]; - mColorsp[mCount] = mColorsp[mCount-2]; - mTexcoordsp[mCount] = mTexcoordsp[mCount-2]; + for (U32 i = 0; i < vert_count; ++i) + { + verts[i].add(offset); + verts[i].mul(scale); } } +} - mCount++; - mVerticesp[mCount] = mVerticesp[mCount-1]; - mColorsp[mCount] = mColorsp[mCount-1]; - mTexcoordsp[mCount] = mTexcoordsp[mCount-1]; +void LLRender::vertexBatchPreTransformed(const std::vector<LLVector4a>& verts) +{ + vertexBatchPreTransformed(verts.data(), verts.size()); } -void LLRender::vertexBatchPreTransformed(LLVector3* verts, S32 vert_count) +void LLRender::vertexBatchPreTransformed(const LLVector4a* verts, std::size_t vert_count) { if (mCount + vert_count > 4094) { @@ -1842,57 +1849,22 @@ void LLRender::vertexBatchPreTransformed(LLVector3* verts, S32 vert_count) return; } - if (sGLCoreProfile && mMode == LLRender::QUADS) - { //quads are deprecated, convert to triangle list - S32 i = 0; - - while (i < vert_count) - { - //read first three - mVerticesp[mCount++] = verts[i++]; - mTexcoordsp[mCount] = mTexcoordsp[mCount-1]; - mColorsp[mCount] = mColorsp[mCount-1]; - - mVerticesp[mCount++] = verts[i++]; - mTexcoordsp[mCount] = mTexcoordsp[mCount-1]; - mColorsp[mCount] = mColorsp[mCount-1]; - - mVerticesp[mCount++] = verts[i++]; - mTexcoordsp[mCount] = mTexcoordsp[mCount-1]; - mColorsp[mCount] = mColorsp[mCount-1]; - - //copy two - mVerticesp[mCount++] = verts[i-3]; - mTexcoordsp[mCount] = mTexcoordsp[mCount-1]; - mColorsp[mCount] = mColorsp[mCount-1]; - - mVerticesp[mCount++] = verts[i-1]; - mTexcoordsp[mCount] = mTexcoordsp[mCount-1]; - mColorsp[mCount] = mColorsp[mCount-1]; - - //copy last one - mVerticesp[mCount++] = verts[i++]; - mTexcoordsp[mCount] = mTexcoordsp[mCount-1]; - mColorsp[mCount] = mColorsp[mCount-1]; - } - } - else + for (S32 i = 0; i < vert_count; i++) { - for (S32 i = 0; i < vert_count; i++) - { - mVerticesp[mCount] = verts[i]; + mVerticesp[mCount] = verts[i]; - mCount++; - mTexcoordsp[mCount] = mTexcoordsp[mCount-1]; - mColorsp[mCount] = mColorsp[mCount-1]; - } + mCount++; + mTexcoordsp[mCount] = mTexcoordsp[mCount - 1]; + mColorsp[mCount] = mColorsp[mCount - 1]; } - if( mCount > 0 ) // ND: Guard against crashes if mCount is zero, yes it can happen - mVerticesp[mCount] = mVerticesp[mCount-1]; + if (mCount > 0) // ND: Guard against crashes if mCount is zero, yes it can happen + { + mVerticesp[mCount] = mVerticesp[mCount - 1]; + } } -void LLRender::vertexBatchPreTransformed(LLVector3* verts, LLVector2* uvs, S32 vert_count) +void LLRender::vertexBatchPreTransformed(const LLVector4a* verts, const LLVector2* uvs, std::size_t vert_count) { if (mCount + vert_count > 4094) { @@ -1900,50 +1872,13 @@ void LLRender::vertexBatchPreTransformed(LLVector3* verts, LLVector2* uvs, S32 v return; } - if (sGLCoreProfile && mMode == LLRender::QUADS) - { //quads are deprecated, convert to triangle list - S32 i = 0; - - while (i < vert_count) - { - //read first three - mVerticesp[mCount] = verts[i]; - mTexcoordsp[mCount++] = uvs[i++]; - mColorsp[mCount] = mColorsp[mCount-1]; - - mVerticesp[mCount] = verts[i]; - mTexcoordsp[mCount++] = uvs[i++]; - mColorsp[mCount] = mColorsp[mCount-1]; - - mVerticesp[mCount] = verts[i]; - mTexcoordsp[mCount++] = uvs[i++]; - mColorsp[mCount] = mColorsp[mCount-1]; - - //copy last two - mVerticesp[mCount] = verts[i-3]; - mTexcoordsp[mCount++] = uvs[i-3]; - mColorsp[mCount] = mColorsp[mCount-1]; - - mVerticesp[mCount] = verts[i-1]; - mTexcoordsp[mCount++] = uvs[i-1]; - mColorsp[mCount] = mColorsp[mCount-1]; - - //copy last one - mVerticesp[mCount] = verts[i]; - mTexcoordsp[mCount++] = uvs[i++]; - mColorsp[mCount] = mColorsp[mCount-1]; - } - } - else + for (S32 i = 0; i < vert_count; i++) { - for (S32 i = 0; i < vert_count; i++) - { - mVerticesp[mCount] = verts[i]; - mTexcoordsp[mCount] = uvs[i]; + mVerticesp[mCount] = verts[i]; + mTexcoordsp[mCount] = uvs[i]; - mCount++; - mColorsp[mCount] = mColorsp[mCount-1]; - } + mCount++; + mColorsp[mCount] = mColorsp[mCount-1]; } if (mCount > 0) @@ -1953,7 +1888,7 @@ void LLRender::vertexBatchPreTransformed(LLVector3* verts, LLVector2* uvs, S32 v } } -void LLRender::vertexBatchPreTransformed(LLVector3* verts, LLVector2* uvs, LLColor4U* colors, S32 vert_count) +void LLRender::vertexBatchPreTransformed(const LLVector4a* verts, const LLVector2* uvs, const LLColor4U* colors, std::size_t vert_count) { if (mCount + vert_count > 4094) { @@ -1961,51 +1896,13 @@ void LLRender::vertexBatchPreTransformed(LLVector3* verts, LLVector2* uvs, LLCol return; } - - if (sGLCoreProfile && mMode == LLRender::QUADS) - { //quads are deprecated, convert to triangle list - S32 i = 0; - - while (i < vert_count) - { - //read first three - mVerticesp[mCount] = verts[i]; - mTexcoordsp[mCount] = uvs[i]; - mColorsp[mCount++] = colors[i++]; - - mVerticesp[mCount] = verts[i]; - mTexcoordsp[mCount] = uvs[i]; - mColorsp[mCount++] = colors[i++]; - - mVerticesp[mCount] = verts[i]; - mTexcoordsp[mCount] = uvs[i]; - mColorsp[mCount++] = colors[i++]; - - //copy last two - mVerticesp[mCount] = verts[i-3]; - mTexcoordsp[mCount] = uvs[i-3]; - mColorsp[mCount++] = colors[i-3]; - - mVerticesp[mCount] = verts[i-1]; - mTexcoordsp[mCount] = uvs[i-1]; - mColorsp[mCount++] = colors[i-1]; - - //copy last one - mVerticesp[mCount] = verts[i]; - mTexcoordsp[mCount] = uvs[i]; - mColorsp[mCount++] = colors[i++]; - } - } - else + for (S32 i = 0; i < vert_count; i++) { - for (S32 i = 0; i < vert_count; i++) - { - mVerticesp[mCount] = verts[i]; - mTexcoordsp[mCount] = uvs[i]; - mColorsp[mCount] = colors[i]; + mVerticesp[mCount] = verts[i]; + mTexcoordsp[mCount] = uvs[i]; + mColorsp[mCount] = colors[i]; - mCount++; - } + mCount++; } if (mCount > 0) @@ -2018,27 +1915,27 @@ void LLRender::vertexBatchPreTransformed(LLVector3* verts, LLVector2* uvs, LLCol void LLRender::vertex2i(const GLint& x, const GLint& y) { - vertex3f((GLfloat) x, (GLfloat) y, 0); + vertex3f((GLfloat)x, (GLfloat)y, 0); } void LLRender::vertex2f(const GLfloat& x, const GLfloat& y) { - vertex3f(x,y,0); + vertex3f(x, y, 0); } void LLRender::vertex2fv(const GLfloat* v) { - vertex3f(v[0], v[1], 0); + vertex3f(v[VX], v[VY], 0); } void LLRender::vertex3fv(const GLfloat* v) { - vertex3f(v[0], v[1], v[2]); + vertex3f(v[VX], v[VY], v[VZ]); } void LLRender::texCoord2f(const GLfloat& x, const GLfloat& y) { - mTexcoordsp[mCount] = LLVector2(x,y); + mTexcoordsp[mCount].set(x,y); } void LLRender::texCoord2i(const GLint& x, const GLint& y) @@ -2048,46 +1945,47 @@ void LLRender::texCoord2i(const GLint& x, const GLint& y) void LLRender::texCoord2fv(const GLfloat* tc) { - texCoord2f(tc[0], tc[1]); + texCoord2f(tc[VX], tc[VY]); } void LLRender::color4ub(const GLubyte& r, const GLubyte& g, const GLubyte& b, const GLubyte& a) { - if (!LLGLSLShader::sCurBoundShaderPtr || LLGLSLShader::sCurBoundShaderPtr->mAttributeMask & LLVertexBuffer::MAP_COLOR) + if (!LLGLSLShader::sCurBoundShaderPtr || + LLGLSLShader::sCurBoundShaderPtr->mAttributeMask & LLVertexBuffer::MAP_COLOR) { - mColorsp[mCount] = LLColor4U(r,g,b,a); + mColorsp[mCount].set(r, g, b, a); } else { //not using shaders or shader reads color from a uniform - diffuseColor4ub(r,g,b,a); + diffuseColor4ub(r, g, b, a); } } void LLRender::color4ubv(const GLubyte* c) { - color4ub(c[0], c[1], c[2], c[3]); + color4ub(c[VX], c[VY], c[VZ], c[VW]); } void LLRender::color4f(const GLfloat& r, const GLfloat& g, const GLfloat& b, const GLfloat& a) { - color4ub((GLubyte) (llclamp(r, 0.f, 1.f)*255), - (GLubyte) (llclamp(g, 0.f, 1.f)*255), - (GLubyte) (llclamp(b, 0.f, 1.f)*255), - (GLubyte) (llclamp(a, 0.f, 1.f)*255)); + color4ub((GLubyte) (llclamp(r, 0.f, 1.f) * 255), + (GLubyte) (llclamp(g, 0.f, 1.f) * 255), + (GLubyte) (llclamp(b, 0.f, 1.f) * 255), + (GLubyte) (llclamp(a, 0.f, 1.f) * 255)); } void LLRender::color4fv(const GLfloat* c) { - color4f(c[0],c[1],c[2],c[3]); + color4f(c[VX], c[VY], c[VZ], c[VW]); } void LLRender::color3f(const GLfloat& r, const GLfloat& g, const GLfloat& b) { - color4f(r,g,b,1); + color4f(r, g, b, 1); } void LLRender::color3fv(const GLfloat* c) { - color4f(c[0],c[1],c[2],1); + color4f(c[VX], c[VY], c[VZ], 1); } void LLRender::diffuseColor3f(F32 r, F32 g, F32 b) @@ -2097,7 +1995,7 @@ void LLRender::diffuseColor3f(F32 r, F32 g, F32 b) if (shader) { - shader->uniform4f(LLShaderMgr::DIFFUSE_COLOR, r,g,b,1.f); + shader->uniform4f(LLShaderMgr::DIFFUSE_COLOR, r, g, b, 1.f); } } @@ -2108,7 +2006,7 @@ void LLRender::diffuseColor3fv(const F32* c) if (shader) { - shader->uniform4f(LLShaderMgr::DIFFUSE_COLOR, c[0], c[1], c[2], 1.f); + shader->uniform4f(LLShaderMgr::DIFFUSE_COLOR, c[VX], c[VY], c[VZ], 1.f); } } @@ -2119,7 +2017,7 @@ void LLRender::diffuseColor4f(F32 r, F32 g, F32 b, F32 a) if (shader) { - shader->uniform4f(LLShaderMgr::DIFFUSE_COLOR, r,g,b,a); + shader->uniform4f(LLShaderMgr::DIFFUSE_COLOR, r, g, b, a); } } @@ -2141,7 +2039,7 @@ void LLRender::diffuseColor4ubv(const U8* c) if (shader) { - shader->uniform4f(LLShaderMgr::DIFFUSE_COLOR, c[0]/255.f, c[1]/255.f, c[2]/255.f, c[3]/255.f); + shader->uniform4f(LLShaderMgr::DIFFUSE_COLOR, c[0] / 255.f, c[1] / 255.f, c[2] / 255.f, c[3] / 255.f); } } @@ -2152,11 +2050,10 @@ void LLRender::diffuseColor4ub(U8 r, U8 g, U8 b, U8 a) if (shader) { - shader->uniform4f(LLShaderMgr::DIFFUSE_COLOR, r/255.f, g/255.f, b/255.f, a/255.f); + shader->uniform4f(LLShaderMgr::DIFFUSE_COLOR, r / 255.f, g / 255.f, b / 255.f, a / 255.f); } } - void LLRender::debugTexUnits(void) { LL_INFOS("TextureUnit") << "Active TexUnit: " << mCurrTextureUnitIndex << LL_ENDL; @@ -2167,7 +2064,7 @@ void LLRender::debugTexUnits(void) { if (i == mCurrTextureUnitIndex) active_enabled = "true"; LL_INFOS("TextureUnit") << "TexUnit: " << i << " Enabled" << LL_ENDL; - LL_INFOS("TextureUnit") << "Enabled As: " ; + LL_INFOS("TextureUnit") << "Enabled As: "; switch (getTexUnit(i)->mCurrTexType) { case LLTexUnit::TT_TEXTURE: @@ -2252,10 +2149,10 @@ glm::vec3 mul_mat4_vec3(const glm::mat4& mat, const glm::vec3& vec) y.splat(vec.y); z.splat(vec.z); - s.splat<3>(mat[0].data); - t.splat<3>(mat[1].data); - p.splat<3>(mat[2].data); - q.splat<3>(mat[3].data); + s.splat<3>(mat[VX].data); + t.splat<3>(mat[VY].data); + p.splat<3>(mat[VZ].data); + q.splat<3>(mat[VW].data); s.mul(x); t.mul(y); @@ -2264,12 +2161,12 @@ glm::vec3 mul_mat4_vec3(const glm::mat4& mat, const glm::vec3& vec) t.add(p); q.add(t); - x.mul(mat[0].data); - y.mul(mat[1].data); - z.mul(mat[2].data); + x.mul(mat[VX].data); + y.mul(mat[VY].data); + z.mul(mat[VZ].data); x.add(y); - z.add(mat[3].data); + z.add(mat[VW].data); LLVector4a res; res.load3(glm::value_ptr(vec)); res.setAdd(x, z); diff --git a/indra/llrender/llrender.h b/indra/llrender/llrender.h index 2645597b1a..8c7126420e 100644 --- a/indra/llrender/llrender.h +++ b/indra/llrender/llrender.h @@ -46,6 +46,7 @@ #include <array> #include <list> +#include <vector> class LLVertexBuffer; class LLCubeMap; @@ -319,7 +320,6 @@ public: POINTS, LINES, LINE_STRIP, - QUADS, LINE_LOOP, NUM_MODES }; @@ -450,9 +450,16 @@ public: void diffuseColor4ubv(const U8* c); void diffuseColor4ub(U8 r, U8 g, U8 b, U8 a); - void vertexBatchPreTransformed(LLVector3* verts, S32 vert_count); - void vertexBatchPreTransformed(LLVector3* verts, LLVector2* uvs, S32 vert_count); - void vertexBatchPreTransformed(LLVector3* verts, LLVector2* uvs, LLColor4U*, S32 vert_count); + void transform(LLVector3& vert); + void transform(LLVector4a& vert); + void untransform(LLVector3& vert); + + void batchTransform(LLVector4a* verts, U32 vert_count); + + void vertexBatchPreTransformed(const std::vector<LLVector4a>& verts); + void vertexBatchPreTransformed(const LLVector4a* verts, std::size_t vert_count); + void vertexBatchPreTransformed(const LLVector4a* verts, const LLVector2* uvs, std::size_t vert_count); + void vertexBatchPreTransformed(const LLVector4a* verts, const LLVector2* uvs, const LLColor4U*, std::size_t vert_count); void setColorMask(bool writeColor, bool writeAlpha); void setColorMask(bool writeColorR, bool writeColorG, bool writeColorB, bool writeAlpha); @@ -508,14 +515,13 @@ private: LLColor4 mAmbientLightColor; bool mDirty; - U32 mQuadCycle; U32 mCount; U32 mMode; U32 mCurrTextureUnitIndex; bool mCurrColorMask[4]; LLPointer<LLVertexBuffer> mBuffer; - LLStrider<LLVector3> mVerticesp; + LLStrider<LLVector4a> mVerticesp; LLStrider<LLVector2> mTexcoordsp; LLStrider<LLColor4U> mColorsp; std::array<LLTexUnit, LL_NUM_TEXTURE_LAYERS> mTexUnits; @@ -527,8 +533,8 @@ private: eBlendFactor mCurrBlendAlphaSFactor; eBlendFactor mCurrBlendAlphaDFactor; - std::vector<LLVector3> mUIOffset; - std::vector<LLVector3> mUIScale; + std::vector<LLVector4a> mUIOffset; + std::vector<LLVector4a> mUIScale; }; extern F32 gGLModelView[16]; diff --git a/indra/llrender/llrender2dutils.cpp b/indra/llrender/llrender2dutils.cpp index 428370057e..0cd0c5fe6e 100644 --- a/indra/llrender/llrender2dutils.cpp +++ b/indra/llrender/llrender2dutils.cpp @@ -122,10 +122,13 @@ void gl_rect_2d(S32 left, S32 top, S32 right, S32 bottom, bool filled ) // Counterclockwise quad will face the viewer if( filled ) { - gGL.begin( LLRender::QUADS ); + gGL.begin( LLRender::TRIANGLES ); gGL.vertex2i(left, top); gGL.vertex2i(left, bottom); gGL.vertex2i(right, bottom); + + gGL.vertex2i(left, top); + gGL.vertex2i(right, bottom); gGL.vertex2i(right, top); gGL.end(); } @@ -172,50 +175,70 @@ void gl_drop_shadow(S32 left, S32 top, S32 right, S32 bottom, const LLColor4 &st LLColor4 end_color = start_color; end_color.mV[VALPHA] = 0.f; - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); // Right edge, CCW faces screen gGL.color4fv(start_color.mV); - gGL.vertex2i(right, top-lines); - gGL.vertex2i(right, bottom); + gGL.vertex2i(right, top - lines); + gGL.vertex2i(right, bottom); + gGL.color4fv(end_color.mV); + gGL.vertex2i(right + lines, bottom); + gGL.color4fv(start_color.mV); + gGL.vertex2i(right, top - lines); gGL.color4fv(end_color.mV); - gGL.vertex2i(right+lines, bottom); - gGL.vertex2i(right+lines, top-lines); + gGL.vertex2i(right + lines, bottom); + gGL.vertex2i(right + lines, top - lines); // Bottom edge, CCW faces screen gGL.color4fv(start_color.mV); - gGL.vertex2i(right, bottom); - gGL.vertex2i(left+lines, bottom); + gGL.vertex2i(right, bottom); + gGL.vertex2i(left + lines, bottom); + gGL.color4fv(end_color.mV); + gGL.vertex2i(left + lines, bottom - lines); + gGL.color4fv(start_color.mV); + gGL.vertex2i(right, bottom); gGL.color4fv(end_color.mV); - gGL.vertex2i(left+lines, bottom-lines); - gGL.vertex2i(right, bottom-lines); + gGL.vertex2i(left + lines, bottom - lines); + gGL.vertex2i(right, bottom - lines); // bottom left Corner gGL.color4fv(start_color.mV); - gGL.vertex2i(left+lines, bottom); + gGL.vertex2i(left + lines, bottom); gGL.color4fv(end_color.mV); - gGL.vertex2i(left, bottom); + gGL.vertex2i(left, bottom); // make the bottom left corner not sharp - gGL.vertex2i(left+1, bottom-lines+1); - gGL.vertex2i(left+lines, bottom-lines); + gGL.vertex2i(left + 1, bottom - lines + 1); + gGL.color4fv(start_color.mV); + gGL.vertex2i(left + lines, bottom); + gGL.color4fv(end_color.mV); + gGL.vertex2i(left + 1, bottom - lines + 1); + gGL.vertex2i(left + lines, bottom - lines); // bottom right corner gGL.color4fv(start_color.mV); - gGL.vertex2i(right, bottom); + gGL.vertex2i(right, bottom); gGL.color4fv(end_color.mV); - gGL.vertex2i(right, bottom-lines); + gGL.vertex2i(right, bottom - lines); // make the rightmost corner not sharp - gGL.vertex2i(right+lines-1, bottom-lines+1); - gGL.vertex2i(right+lines, bottom); + gGL.vertex2i(right + lines - 1, bottom - lines + 1); + gGL.color4fv(start_color.mV); + gGL.vertex2i(right, bottom); + gGL.color4fv(end_color.mV); + gGL.vertex2i(right + lines - 1, bottom - lines + 1); + gGL.vertex2i(right + lines, bottom); // top right corner gGL.color4fv(start_color.mV); - gGL.vertex2i( right, top-lines ); + gGL.vertex2i(right, top - lines); gGL.color4fv(end_color.mV); - gGL.vertex2i( right+lines, top-lines ); + gGL.vertex2i(right + lines, top - lines); // make the corner not sharp - gGL.vertex2i( right+lines-1, top-1 ); - gGL.vertex2i( right, top ); + gGL.vertex2i(right + lines - 1, top - 1); + gGL.color4fv(start_color.mV); + gGL.vertex2i(right, top - lines); + gGL.color4fv(end_color.mV); + gGL.vertex2i(right + lines - 1, top - 1); + gGL.vertex2i(right, top); gGL.end(); stop_glerror(); @@ -423,171 +446,251 @@ void gl_draw_scaled_image_with_border(S32 x, S32 y, S32 width, S32 height, LLTex ui_translation.mV[VX] + width * ui_scale.mV[VX], ui_translation.mV[VY]); - LLGLSUIDefault gls_ui; - gGL.getTexUnit(0)->bind(image, true); gGL.color4fv(color.mV); - const S32 NUM_VERTICES = 9 * 4; // 9 quads - LLVector2 uv[NUM_VERTICES]; - LLVector3 pos[NUM_VERTICES]; + constexpr U32 NUM_VERTICES = 9 * 2 * 3; // 9 quads, 2 triangles per quad, 3 vertices per triangle + static thread_local LLVector2 uv[NUM_VERTICES]; + static thread_local LLVector4a pos[NUM_VERTICES]; S32 index = 0; - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { - // draw bottom left - uv[index] = LLVector2(uv_outer_rect.mLeft, uv_outer_rect.mBottom); - pos[index] = LLVector3(draw_outer_rect.mLeft, draw_outer_rect.mBottom, 0.f); + // draw bottom left triangles + // 1 + uv[index].set(uv_outer_rect.mLeft, uv_outer_rect.mBottom); + pos[index].set(draw_outer_rect.mLeft, draw_outer_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mLeft, uv_outer_rect.mBottom); - pos[index] = LLVector3(draw_center_rect.mLeft, draw_outer_rect.mBottom, 0.f); + uv[index].set(uv_center_rect.mLeft, uv_outer_rect.mBottom); + pos[index].set(draw_center_rect.mLeft, draw_outer_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mLeft, uv_center_rect.mBottom); - pos[index] = LLVector3(draw_center_rect.mLeft, draw_center_rect.mBottom, 0.f); + uv[index].set(uv_center_rect.mLeft, uv_center_rect.mBottom); + pos[index].set(draw_center_rect.mLeft, draw_center_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_outer_rect.mLeft, uv_center_rect.mBottom); - pos[index] = LLVector3(draw_outer_rect.mLeft, draw_center_rect.mBottom, 0.f); + // 2 + uv[index].set(uv_outer_rect.mLeft, uv_outer_rect.mBottom); + pos[index].set(draw_outer_rect.mLeft, draw_outer_rect.mBottom, 0.f); index++; - // draw bottom middle - uv[index] = LLVector2(uv_center_rect.mLeft, uv_outer_rect.mBottom); - pos[index] = LLVector3(draw_center_rect.mLeft, draw_outer_rect.mBottom, 0.f); + uv[index].set(uv_center_rect.mLeft, uv_center_rect.mBottom); + pos[index].set(draw_center_rect.mLeft, draw_center_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mRight, uv_outer_rect.mBottom); - pos[index] = LLVector3(draw_center_rect.mRight, draw_outer_rect.mBottom, 0.f); + uv[index].set(uv_outer_rect.mLeft, uv_center_rect.mBottom); + pos[index].set(draw_outer_rect.mLeft, draw_center_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mRight, uv_center_rect.mBottom); - pos[index] = LLVector3(draw_center_rect.mRight, draw_center_rect.mBottom, 0.f); + // draw bottom middle triangles + uv[index].set(uv_center_rect.mLeft, uv_outer_rect.mBottom); + pos[index].set(draw_center_rect.mLeft, draw_outer_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mLeft, uv_center_rect.mBottom); - pos[index] = LLVector3(draw_center_rect.mLeft, draw_center_rect.mBottom, 0.f); + uv[index].set(uv_center_rect.mRight, uv_outer_rect.mBottom); + pos[index].set(draw_center_rect.mRight, draw_outer_rect.mBottom, 0.f); index++; - // draw bottom right - uv[index] = LLVector2(uv_center_rect.mRight, uv_outer_rect.mBottom); - pos[index] = LLVector3(draw_center_rect.mRight, draw_outer_rect.mBottom, 0.f); + uv[index].set(uv_center_rect.mRight, uv_center_rect.mBottom); + pos[index].set(draw_center_rect.mRight, draw_center_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_outer_rect.mRight, uv_outer_rect.mBottom); - pos[index] = LLVector3(draw_outer_rect.mRight, draw_outer_rect.mBottom, 0.f); + // 2 + uv[index].set(uv_center_rect.mLeft, uv_outer_rect.mBottom); + pos[index].set(draw_center_rect.mLeft, draw_outer_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_outer_rect.mRight, uv_center_rect.mBottom); - pos[index] = LLVector3(draw_outer_rect.mRight, draw_center_rect.mBottom, 0.f); + uv[index].set(uv_center_rect.mRight, uv_center_rect.mBottom); + pos[index].set(draw_center_rect.mRight, draw_center_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mRight, uv_center_rect.mBottom); - pos[index] = LLVector3(draw_center_rect.mRight, draw_center_rect.mBottom, 0.f); + uv[index].set(uv_center_rect.mLeft, uv_center_rect.mBottom); + pos[index].set(draw_center_rect.mLeft, draw_center_rect.mBottom, 0.f); index++; - // draw left - uv[index] = LLVector2(uv_outer_rect.mLeft, uv_center_rect.mBottom); - pos[index] = LLVector3(draw_outer_rect.mLeft, draw_center_rect.mBottom, 0.f); + // draw bottom right triangles + uv[index].set(uv_center_rect.mRight, uv_outer_rect.mBottom); + pos[index].set(draw_center_rect.mRight, draw_outer_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mLeft, uv_center_rect.mBottom); - pos[index] = LLVector3(draw_center_rect.mLeft, draw_center_rect.mBottom, 0.f); + uv[index].set(uv_outer_rect.mRight, uv_outer_rect.mBottom); + pos[index].set(draw_outer_rect.mRight, draw_outer_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mLeft, uv_center_rect.mTop); - pos[index] = LLVector3(draw_center_rect.mLeft, draw_center_rect.mTop, 0.f); + uv[index].set(uv_outer_rect.mRight, uv_center_rect.mBottom); + pos[index].set(draw_outer_rect.mRight, draw_center_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_outer_rect.mLeft, uv_center_rect.mTop); - pos[index] = LLVector3(draw_outer_rect.mLeft, draw_center_rect.mTop, 0.f); + // 2 + uv[index].set(uv_center_rect.mRight, uv_outer_rect.mBottom); + pos[index].set(draw_center_rect.mRight, draw_outer_rect.mBottom, 0.f); index++; - // draw middle - uv[index] = LLVector2(uv_center_rect.mLeft, uv_center_rect.mBottom); - pos[index] = LLVector3(draw_center_rect.mLeft, draw_center_rect.mBottom, 0.f); + uv[index].set(uv_outer_rect.mRight, uv_center_rect.mBottom); + pos[index].set(draw_outer_rect.mRight, draw_center_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mRight, uv_center_rect.mBottom); - pos[index] = LLVector3(draw_center_rect.mRight, draw_center_rect.mBottom, 0.f); + uv[index].set(uv_center_rect.mRight, uv_center_rect.mBottom); + pos[index].set(draw_center_rect.mRight, draw_center_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mRight, uv_center_rect.mTop); - pos[index] = LLVector3(draw_center_rect.mRight, draw_center_rect.mTop, 0.f); + // draw left triangles + uv[index].set(uv_outer_rect.mLeft, uv_center_rect.mBottom); + pos[index].set(draw_outer_rect.mLeft, draw_center_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mLeft, uv_center_rect.mTop); - pos[index] = LLVector3(draw_center_rect.mLeft, draw_center_rect.mTop, 0.f); + uv[index].set(uv_center_rect.mLeft, uv_center_rect.mBottom); + pos[index].set(draw_center_rect.mLeft, draw_center_rect.mBottom, 0.f); index++; - // draw right - uv[index] = LLVector2(uv_center_rect.mRight, uv_center_rect.mBottom); - pos[index] = LLVector3(draw_center_rect.mRight, draw_center_rect.mBottom, 0.f); + uv[index].set(uv_center_rect.mLeft, uv_center_rect.mTop); + pos[index].set(draw_center_rect.mLeft, draw_center_rect.mTop, 0.f); index++; - uv[index] = LLVector2(uv_outer_rect.mRight, uv_center_rect.mBottom); - pos[index] = LLVector3(draw_outer_rect.mRight, draw_center_rect.mBottom, 0.f); + // 2 + uv[index].set(uv_outer_rect.mLeft, uv_center_rect.mBottom); + pos[index].set(draw_outer_rect.mLeft, draw_center_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_outer_rect.mRight, uv_center_rect.mTop); - pos[index] = LLVector3(draw_outer_rect.mRight, draw_center_rect.mTop, 0.f); + uv[index].set(uv_center_rect.mLeft, uv_center_rect.mTop); + pos[index].set(draw_center_rect.mLeft, draw_center_rect.mTop, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mRight, uv_center_rect.mTop); - pos[index] = LLVector3(draw_center_rect.mRight, draw_center_rect.mTop, 0.f); + uv[index].set(uv_outer_rect.mLeft, uv_center_rect.mTop); + pos[index].set(draw_outer_rect.mLeft, draw_center_rect.mTop, 0.f); index++; - // draw top left - uv[index] = LLVector2(uv_outer_rect.mLeft, uv_center_rect.mTop); - pos[index] = LLVector3(draw_outer_rect.mLeft, draw_center_rect.mTop, 0.f); + // draw middle triangles + uv[index].set(uv_center_rect.mLeft, uv_center_rect.mBottom); + pos[index].set(draw_center_rect.mLeft, draw_center_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mLeft, uv_center_rect.mTop); - pos[index] = LLVector3(draw_center_rect.mLeft, draw_center_rect.mTop, 0.f); + uv[index].set(uv_center_rect.mRight, uv_center_rect.mBottom); + pos[index].set(draw_center_rect.mRight, draw_center_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mLeft, uv_outer_rect.mTop); - pos[index] = LLVector3(draw_center_rect.mLeft, draw_outer_rect.mTop, 0.f); + uv[index].set(uv_center_rect.mRight, uv_center_rect.mTop); + pos[index].set(draw_center_rect.mRight, draw_center_rect.mTop, 0.f); index++; - uv[index] = LLVector2(uv_outer_rect.mLeft, uv_outer_rect.mTop); - pos[index] = LLVector3(draw_outer_rect.mLeft, draw_outer_rect.mTop, 0.f); + // 2 + uv[index].set(uv_center_rect.mLeft, uv_center_rect.mBottom); + pos[index].set(draw_center_rect.mLeft, draw_center_rect.mBottom, 0.f); index++; - // draw top middle - uv[index] = LLVector2(uv_center_rect.mLeft, uv_center_rect.mTop); - pos[index] = LLVector3(draw_center_rect.mLeft, draw_center_rect.mTop, 0.f); + uv[index].set(uv_center_rect.mRight, uv_center_rect.mTop); + pos[index].set(draw_center_rect.mRight, draw_center_rect.mTop, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mRight, uv_center_rect.mTop); - pos[index] = LLVector3(draw_center_rect.mRight, draw_center_rect.mTop, 0.f); + uv[index].set(uv_center_rect.mLeft, uv_center_rect.mTop); + pos[index].set(draw_center_rect.mLeft, draw_center_rect.mTop, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mRight, uv_outer_rect.mTop); - pos[index] = LLVector3(draw_center_rect.mRight, draw_outer_rect.mTop, 0.f); + // draw right triangles + uv[index].set(uv_center_rect.mRight, uv_center_rect.mBottom); + pos[index].set(draw_center_rect.mRight, draw_center_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mLeft, uv_outer_rect.mTop); - pos[index] = LLVector3(draw_center_rect.mLeft, draw_outer_rect.mTop, 0.f); + uv[index].set(uv_outer_rect.mRight, uv_center_rect.mBottom); + pos[index].set(draw_outer_rect.mRight, draw_center_rect.mBottom, 0.f); index++; - // draw top right - uv[index] = LLVector2(uv_center_rect.mRight, uv_center_rect.mTop); - pos[index] = LLVector3(draw_center_rect.mRight, draw_center_rect.mTop, 0.f); + uv[index].set(uv_outer_rect.mRight, uv_center_rect.mTop); + pos[index].set(draw_outer_rect.mRight, draw_center_rect.mTop, 0.f); index++; - uv[index] = LLVector2(uv_outer_rect.mRight, uv_center_rect.mTop); - pos[index] = LLVector3(draw_outer_rect.mRight, draw_center_rect.mTop, 0.f); + // 2 + uv[index].set(uv_center_rect.mRight, uv_center_rect.mBottom); + pos[index].set(draw_center_rect.mRight, draw_center_rect.mBottom, 0.f); index++; - uv[index] = LLVector2(uv_outer_rect.mRight, uv_outer_rect.mTop); - pos[index] = LLVector3(draw_outer_rect.mRight, draw_outer_rect.mTop, 0.f); + uv[index].set(uv_outer_rect.mRight, uv_center_rect.mTop); + pos[index].set(draw_outer_rect.mRight, draw_center_rect.mTop, 0.f); index++; - uv[index] = LLVector2(uv_center_rect.mRight, uv_outer_rect.mTop); - pos[index] = LLVector3(draw_center_rect.mRight, draw_outer_rect.mTop, 0.f); + uv[index].set(uv_center_rect.mRight, uv_center_rect.mTop); + pos[index].set(draw_center_rect.mRight, draw_center_rect.mTop, 0.f); + index++; + + // draw top left triangles + uv[index].set(uv_outer_rect.mLeft, uv_center_rect.mTop); + pos[index].set(draw_outer_rect.mLeft, draw_center_rect.mTop, 0.f); + index++; + + uv[index].set(uv_center_rect.mLeft, uv_center_rect.mTop); + pos[index].set(draw_center_rect.mLeft, draw_center_rect.mTop, 0.f); + index++; + + uv[index].set(uv_center_rect.mLeft, uv_outer_rect.mTop); + pos[index].set(draw_center_rect.mLeft, draw_outer_rect.mTop, 0.f); + index++; + + // 2 + uv[index].set(uv_outer_rect.mLeft, uv_center_rect.mTop); + pos[index].set(draw_outer_rect.mLeft, draw_center_rect.mTop, 0.f); + index++; + + uv[index].set(uv_center_rect.mLeft, uv_outer_rect.mTop); + pos[index].set(draw_center_rect.mLeft, draw_outer_rect.mTop, 0.f); + index++; + + uv[index].set(uv_outer_rect.mLeft, uv_outer_rect.mTop); + pos[index].set(draw_outer_rect.mLeft, draw_outer_rect.mTop, 0.f); + index++; + + // draw top middle triangles + uv[index].set(uv_center_rect.mLeft, uv_center_rect.mTop); + pos[index].set(draw_center_rect.mLeft, draw_center_rect.mTop, 0.f); + index++; + + uv[index].set(uv_center_rect.mRight, uv_center_rect.mTop); + pos[index].set(draw_center_rect.mRight, draw_center_rect.mTop, 0.f); + index++; + + uv[index].set(uv_center_rect.mRight, uv_outer_rect.mTop); + pos[index].set(draw_center_rect.mRight, draw_outer_rect.mTop, 0.f); + index++; + + // 2 + uv[index].set(uv_center_rect.mLeft, uv_center_rect.mTop); + pos[index].set(draw_center_rect.mLeft, draw_center_rect.mTop, 0.f); + index++; + + uv[index].set(uv_center_rect.mRight, uv_outer_rect.mTop); + pos[index].set(draw_center_rect.mRight, draw_outer_rect.mTop, 0.f); + index++; + + uv[index].set(uv_center_rect.mLeft, uv_outer_rect.mTop); + pos[index].set(draw_center_rect.mLeft, draw_outer_rect.mTop, 0.f); + index++; + + // draw top right triangles + uv[index].set(uv_center_rect.mRight, uv_center_rect.mTop); + pos[index].set(draw_center_rect.mRight, draw_center_rect.mTop, 0.f); + index++; + + uv[index].set(uv_outer_rect.mRight, uv_center_rect.mTop); + pos[index].set(draw_outer_rect.mRight, draw_center_rect.mTop, 0.f); + index++; + + uv[index].set(uv_outer_rect.mRight, uv_outer_rect.mTop); + pos[index].set(draw_outer_rect.mRight, draw_outer_rect.mTop, 0.f); + index++; + + // 2 + uv[index].set(uv_center_rect.mRight, uv_center_rect.mTop); + pos[index].set(draw_center_rect.mRight, draw_center_rect.mTop, 0.f); + index++; + + uv[index].set(uv_outer_rect.mRight, uv_outer_rect.mTop); + pos[index].set(draw_outer_rect.mRight, draw_outer_rect.mTop, 0.f); + index++; + + uv[index].set(uv_center_rect.mRight, uv_outer_rect.mTop); + pos[index].set(draw_center_rect.mRight, draw_outer_rect.mTop, 0.f); index++; gGL.vertexBatchPreTransformed(pos, uv, NUM_VERTICES); @@ -614,8 +717,6 @@ void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degre return; } - LLGLSUIDefault gls_ui; - if(image != NULL) { gGL.getTexUnit(0)->bind(image, true); @@ -629,11 +730,11 @@ void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degre if (degrees == 0.f) { - const S32 NUM_VERTICES = 4; // 9 quads - LLVector2 uv[NUM_VERTICES]; - LLVector3 pos[NUM_VERTICES]; + constexpr S32 NUM_VERTICES = 2 * 3; + static thread_local LLVector2 uv[NUM_VERTICES +1]; + static thread_local LLVector4a pos[NUM_VERTICES +1]; - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { LLVector3 ui_scale = gGL.getUIScale(); LLVector3 ui_translation = gGL.getUITranslation(); @@ -644,20 +745,28 @@ void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degre S32 scaled_width = ll_round(width * ui_scale.mV[VX]); S32 scaled_height = ll_round(height * ui_scale.mV[VY]); - uv[index] = LLVector2(uv_rect.mRight, uv_rect.mTop); - pos[index] = LLVector3(ui_translation.mV[VX] + scaled_width, ui_translation.mV[VY] + scaled_height, 0.f); + uv[index].set(uv_rect.mRight, uv_rect.mTop); + pos[index].set(ui_translation.mV[VX] + scaled_width, ui_translation.mV[VY] + scaled_height, 0.f); + index++; + + uv[index].set(uv_rect.mLeft, uv_rect.mTop); + pos[index].set(ui_translation.mV[VX], ui_translation.mV[VY] + scaled_height, 0.f); + index++; + + uv[index].set(uv_rect.mLeft, uv_rect.mBottom); + pos[index].set(ui_translation.mV[VX], ui_translation.mV[VY], 0.f); index++; - uv[index] = LLVector2(uv_rect.mLeft, uv_rect.mTop); - pos[index] = LLVector3(ui_translation.mV[VX], ui_translation.mV[VY] + scaled_height, 0.f); + uv[index].set(uv_rect.mRight, uv_rect.mTop); + pos[index].set(ui_translation.mV[VX] + scaled_width, ui_translation.mV[VY] + scaled_height, 0.f); index++; - uv[index] = LLVector2(uv_rect.mLeft, uv_rect.mBottom); - pos[index] = LLVector3(ui_translation.mV[VX], ui_translation.mV[VY], 0.f); + uv[index].set(uv_rect.mLeft, uv_rect.mBottom); + pos[index].set(ui_translation.mV[VX], ui_translation.mV[VY], 0.f); index++; - uv[index] = LLVector2(uv_rect.mRight, uv_rect.mBottom); - pos[index] = LLVector3(ui_translation.mV[VX] + scaled_width, ui_translation.mV[VY], 0.f); + uv[index].set(uv_rect.mRight, uv_rect.mBottom); + pos[index].set(ui_translation.mV[VX] + scaled_width, ui_translation.mV[VY], 0.f); index++; gGL.vertexBatchPreTransformed(pos, uv, NUM_VERTICES); @@ -687,7 +796,7 @@ void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degre gGL.color4fv(color.mV); - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { LLVector3 v; @@ -703,6 +812,14 @@ void gl_draw_scaled_rotated_image(S32 x, S32 y, S32 width, S32 height, F32 degre gGL.texCoord2f(uv_rect.mLeft, uv_rect.mBottom); gGL.vertex2f(v.mV[0], v.mV[1] ); + v = LLVector3(offset_x, offset_y, 0.f) * quat; + gGL.texCoord2f(uv_rect.mRight, uv_rect.mTop); + gGL.vertex2f(v.mV[0], v.mV[1]); + + v = LLVector3(-offset_x, -offset_y, 0.f) * quat; + gGL.texCoord2f(uv_rect.mLeft, uv_rect.mBottom); + gGL.vertex2f(v.mV[0], v.mV[1]); + v = LLVector3(offset_x, -offset_y, 0.f) * quat; gGL.texCoord2f(uv_rect.mRight, uv_rect.mBottom); gGL.vertex2f(v.mV[0], v.mV[1] ); @@ -948,7 +1065,7 @@ void gl_washer_segment_2d(F32 outer_radius, F32 inner_radius, F32 start_radians, void gl_rect_2d_simple_tex( S32 width, S32 height ) { - gGL.begin( LLRender::QUADS ); + gGL.begin( LLRender::TRIANGLES ); gGL.texCoord2f(1.f, 1.f); gGL.vertex2i(width, height); @@ -959,6 +1076,12 @@ void gl_rect_2d_simple_tex( S32 width, S32 height ) gGL.texCoord2f(0.f, 0.f); gGL.vertex2i(0, 0); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex2i(width, height); + + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2i(0, 0); + gGL.texCoord2f(1.f, 0.f); gGL.vertex2i(width, 0); @@ -967,10 +1090,13 @@ void gl_rect_2d_simple_tex( S32 width, S32 height ) void gl_rect_2d_simple( S32 width, S32 height ) { - gGL.begin( LLRender::QUADS ); + gGL.begin( LLRender::TRIANGLES ); gGL.vertex2i(width, height); gGL.vertex2i(0, height); gGL.vertex2i(0, 0); + + gGL.vertex2i(width, height); + gGL.vertex2i(0, 0); gGL.vertex2i(width, 0); gGL.end(); } @@ -1011,7 +1137,7 @@ void gl_segmented_rect_2d_tex(const S32 left, LLVector2 width_vec((F32)width, 0.f); LLVector2 height_vec(0.f, (F32)height); - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { // draw bottom left gGL.texCoord2f(0.f, 0.f); @@ -1023,6 +1149,12 @@ void gl_segmented_rect_2d_tex(const S32 left, gGL.texCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); gGL.vertex2fv((border_width_left + border_height_bottom).mV); + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2f(0.f, 0.f); + + gGL.texCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + gGL.vertex2fv((border_width_left + border_height_bottom).mV); + gGL.texCoord2f(0.f, border_uv_scale.mV[VY]); gGL.vertex2fv(border_height_bottom.mV); @@ -1036,6 +1168,12 @@ void gl_segmented_rect_2d_tex(const S32 left, gGL.texCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); gGL.vertex2fv((width_vec - border_width_right + border_height_bottom).mV); + gGL.texCoord2f(border_uv_scale.mV[VX], 0.f); + gGL.vertex2fv(border_width_left.mV); + + gGL.texCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + gGL.vertex2fv((width_vec - border_width_right + border_height_bottom).mV); + gGL.texCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); gGL.vertex2fv((border_width_left + border_height_bottom).mV); @@ -1049,6 +1187,12 @@ void gl_segmented_rect_2d_tex(const S32 left, gGL.texCoord2f(1.f, border_uv_scale.mV[VY]); gGL.vertex2fv((width_vec + border_height_bottom).mV); + gGL.texCoord2f(1.f - border_uv_scale.mV[VX], 0.f); + gGL.vertex2fv((width_vec - border_width_right).mV); + + gGL.texCoord2f(1.f, border_uv_scale.mV[VY]); + gGL.vertex2fv((width_vec + border_height_bottom).mV); + gGL.texCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); gGL.vertex2fv((width_vec - border_width_right + border_height_bottom).mV); @@ -1062,6 +1206,12 @@ void gl_segmented_rect_2d_tex(const S32 left, gGL.texCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); gGL.vertex2fv((border_width_left + height_vec - border_height_top).mV); + gGL.texCoord2f(0.f, border_uv_scale.mV[VY]); + gGL.vertex2fv(border_height_bottom.mV); + + gGL.texCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + gGL.vertex2fv((border_width_left + height_vec - border_height_top).mV); + gGL.texCoord2f(0.f, 1.f - border_uv_scale.mV[VY]); gGL.vertex2fv((height_vec - border_height_top).mV); @@ -1075,6 +1225,12 @@ void gl_segmented_rect_2d_tex(const S32 left, gGL.texCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); gGL.vertex2fv((width_vec - border_width_right + height_vec - border_height_top).mV); + gGL.texCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + gGL.vertex2fv((border_width_left + border_height_bottom).mV); + + gGL.texCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + gGL.vertex2fv((width_vec - border_width_right + height_vec - border_height_top).mV); + gGL.texCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); gGL.vertex2fv((border_width_left + height_vec - border_height_top).mV); @@ -1088,6 +1244,12 @@ void gl_segmented_rect_2d_tex(const S32 left, gGL.texCoord2f(1.f, 1.f - border_uv_scale.mV[VY]); gGL.vertex2fv((width_vec + height_vec - border_height_top).mV); + gGL.texCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + gGL.vertex2fv((width_vec - border_width_right + border_height_bottom).mV); + + gGL.texCoord2f(1.f, 1.f - border_uv_scale.mV[VY]); + gGL.vertex2fv((width_vec + height_vec - border_height_top).mV); + gGL.texCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); gGL.vertex2fv((width_vec - border_width_right + height_vec - border_height_top).mV); @@ -1101,6 +1263,12 @@ void gl_segmented_rect_2d_tex(const S32 left, gGL.texCoord2f(border_uv_scale.mV[VX], 1.f); gGL.vertex2fv((border_width_left + height_vec).mV); + gGL.texCoord2f(0.f, 1.f - border_uv_scale.mV[VY]); + gGL.vertex2fv((height_vec - border_height_top).mV); + + gGL.texCoord2f(border_uv_scale.mV[VX], 1.f); + gGL.vertex2fv((border_width_left + height_vec).mV); + gGL.texCoord2f(0.f, 1.f); gGL.vertex2fv((height_vec).mV); @@ -1114,6 +1282,12 @@ void gl_segmented_rect_2d_tex(const S32 left, gGL.texCoord2f(1.f - border_uv_scale.mV[VX], 1.f); gGL.vertex2fv((width_vec - border_width_right + height_vec).mV); + gGL.texCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + gGL.vertex2fv((border_width_left + height_vec - border_height_top).mV); + + gGL.texCoord2f(1.f - border_uv_scale.mV[VX], 1.f); + gGL.vertex2fv((width_vec - border_width_right + height_vec).mV); + gGL.texCoord2f(border_uv_scale.mV[VX], 1.f); gGL.vertex2fv((border_width_left + height_vec).mV); @@ -1127,6 +1301,12 @@ void gl_segmented_rect_2d_tex(const S32 left, gGL.texCoord2f(1.f, 1.f); gGL.vertex2fv((width_vec + height_vec).mV); + gGL.texCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + gGL.vertex2fv((width_vec - border_width_right + height_vec - border_height_top).mV); + + gGL.texCoord2f(1.f, 1.f); + gGL.vertex2fv((width_vec + height_vec).mV); + gGL.texCoord2f(1.f - border_uv_scale.mV[VX], 1.f); gGL.vertex2fv((width_vec - border_width_right + height_vec).mV); } @@ -1181,7 +1361,7 @@ void gl_segmented_rect_2d_fragment_tex(const LLRect& rect, LLVector2 x_min; LLVector2 x_max; - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { if (start_fragment < middle_start) { @@ -1200,6 +1380,12 @@ void gl_segmented_rect_2d_fragment_tex(const LLRect& rect, gGL.texCoord2f(u_max, border_uv_scale.mV[VY]); gGL.vertex2fv((x_max + border_height_bottom).mV); + gGL.texCoord2f(u_min, 0.f); + gGL.vertex2fv(x_min.mV); + + gGL.texCoord2f(u_max, border_uv_scale.mV[VY]); + gGL.vertex2fv((x_max + border_height_bottom).mV); + gGL.texCoord2f(u_min, border_uv_scale.mV[VY]); gGL.vertex2fv((x_min + border_height_bottom).mV); @@ -1213,6 +1399,12 @@ void gl_segmented_rect_2d_fragment_tex(const LLRect& rect, gGL.texCoord2f(u_max, 1.f - border_uv_scale.mV[VY]); gGL.vertex2fv((x_max + height_vec - border_height_top).mV); + gGL.texCoord2f(u_min, border_uv_scale.mV[VY]); + gGL.vertex2fv((x_min + border_height_bottom).mV); + + gGL.texCoord2f(u_max, 1.f - border_uv_scale.mV[VY]); + gGL.vertex2fv((x_max + height_vec - border_height_top).mV); + gGL.texCoord2f(u_min, 1.f - border_uv_scale.mV[VY]); gGL.vertex2fv((x_min + height_vec - border_height_top).mV); @@ -1226,6 +1418,12 @@ void gl_segmented_rect_2d_fragment_tex(const LLRect& rect, gGL.texCoord2f(u_max, 1.f); gGL.vertex2fv((x_max + height_vec).mV); + gGL.texCoord2f(u_min, 1.f - border_uv_scale.mV[VY]); + gGL.vertex2fv((x_min + height_vec - border_height_top).mV); + + gGL.texCoord2f(u_max, 1.f); + gGL.vertex2fv((x_max + height_vec).mV); + gGL.texCoord2f(u_min, 1.f); gGL.vertex2fv((x_min + height_vec).mV); } @@ -1245,6 +1443,12 @@ void gl_segmented_rect_2d_fragment_tex(const LLRect& rect, gGL.texCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); gGL.vertex2fv((x_max + border_height_bottom).mV); + gGL.texCoord2f(border_uv_scale.mV[VX], 0.f); + gGL.vertex2fv(x_min.mV); + + gGL.texCoord2f(1.f - border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + gGL.vertex2fv((x_max + border_height_bottom).mV); + gGL.texCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); gGL.vertex2fv((x_min + border_height_bottom).mV); @@ -1258,6 +1462,12 @@ void gl_segmented_rect_2d_fragment_tex(const LLRect& rect, gGL.texCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); gGL.vertex2fv((x_max + height_vec - border_height_top).mV); + gGL.texCoord2f(border_uv_scale.mV[VX], border_uv_scale.mV[VY]); + gGL.vertex2fv((x_min + border_height_bottom).mV); + + gGL.texCoord2f(1.f - border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + gGL.vertex2fv((x_max + height_vec - border_height_top).mV); + gGL.texCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); gGL.vertex2fv((x_min + height_vec - border_height_top).mV); @@ -1271,6 +1481,12 @@ void gl_segmented_rect_2d_fragment_tex(const LLRect& rect, gGL.texCoord2f(1.f - border_uv_scale.mV[VX], 1.f); gGL.vertex2fv((x_max + height_vec).mV); + gGL.texCoord2f(border_uv_scale.mV[VX], 1.f - border_uv_scale.mV[VY]); + gGL.vertex2fv((x_min + height_vec - border_height_top).mV); + + gGL.texCoord2f(1.f - border_uv_scale.mV[VX], 1.f); + gGL.vertex2fv((x_max + height_vec).mV); + gGL.texCoord2f(border_uv_scale.mV[VX], 1.f); gGL.vertex2fv((x_min + height_vec).mV); } @@ -1292,6 +1508,12 @@ void gl_segmented_rect_2d_fragment_tex(const LLRect& rect, gGL.texCoord2f(u_max, border_uv_scale.mV[VY]); gGL.vertex2fv((x_max + border_height_bottom).mV); + gGL.texCoord2f(u_min, 0.f); + gGL.vertex2fv((x_min).mV); + + gGL.texCoord2f(u_max, border_uv_scale.mV[VY]); + gGL.vertex2fv((x_max + border_height_bottom).mV); + gGL.texCoord2f(u_min, border_uv_scale.mV[VY]); gGL.vertex2fv((x_min + border_height_bottom).mV); @@ -1305,6 +1527,12 @@ void gl_segmented_rect_2d_fragment_tex(const LLRect& rect, gGL.texCoord2f(u_max, 1.f - border_uv_scale.mV[VY]); gGL.vertex2fv((x_max + height_vec - border_height_top).mV); + gGL.texCoord2f(u_min, border_uv_scale.mV[VY]); + gGL.vertex2fv((x_min + border_height_bottom).mV); + + gGL.texCoord2f(u_max, 1.f - border_uv_scale.mV[VY]); + gGL.vertex2fv((x_max + height_vec - border_height_top).mV); + gGL.texCoord2f(u_min, 1.f - border_uv_scale.mV[VY]); gGL.vertex2fv((x_min + height_vec - border_height_top).mV); @@ -1318,6 +1546,12 @@ void gl_segmented_rect_2d_fragment_tex(const LLRect& rect, gGL.texCoord2f(u_max, 1.f); gGL.vertex2fv((x_max + height_vec).mV); + gGL.texCoord2f(u_min, 1.f - border_uv_scale.mV[VY]); + gGL.vertex2fv((x_min + height_vec - border_height_top).mV); + + gGL.texCoord2f(u_max, 1.f); + gGL.vertex2fv((x_max + height_vec).mV); + gGL.texCoord2f(u_min, 1.f); gGL.vertex2fv((x_min + height_vec).mV); } @@ -1332,7 +1566,7 @@ void gl_segmented_rect_3d_tex(const LLRectf& clip_rect, const LLRectf& center_uv { LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { // draw bottom left gGL.texCoord2f(clip_rect.mLeft, clip_rect.mBottom); @@ -1344,6 +1578,12 @@ void gl_segmented_rect_3d_tex(const LLRectf& clip_rect, const LLRectf& center_uv gGL.texCoord2f(center_uv_rect.mLeft, center_uv_rect.mBottom); gGL.vertex3fv((center_draw_rect.mLeft * width_vec + center_draw_rect.mBottom * height_vec).mV); + gGL.texCoord2f(clip_rect.mLeft, clip_rect.mBottom); + gGL.vertex3f(0.f, 0.f, 0.f); + + gGL.texCoord2f(center_uv_rect.mLeft, center_uv_rect.mBottom); + gGL.vertex3fv((center_draw_rect.mLeft * width_vec + center_draw_rect.mBottom * height_vec).mV); + gGL.texCoord2f(clip_rect.mLeft, center_uv_rect.mBottom); gGL.vertex3fv((center_draw_rect.mBottom * height_vec).mV); @@ -1357,6 +1597,12 @@ void gl_segmented_rect_3d_tex(const LLRectf& clip_rect, const LLRectf& center_uv gGL.texCoord2f(center_uv_rect.mRight, center_uv_rect.mBottom); gGL.vertex3fv((center_draw_rect.mRight * width_vec + center_draw_rect.mBottom * height_vec).mV); + gGL.texCoord2f(center_uv_rect.mLeft, clip_rect.mBottom); + gGL.vertex3fv((center_draw_rect.mLeft * width_vec).mV); + + gGL.texCoord2f(center_uv_rect.mRight, center_uv_rect.mBottom); + gGL.vertex3fv((center_draw_rect.mRight * width_vec + center_draw_rect.mBottom * height_vec).mV); + gGL.texCoord2f(center_uv_rect.mLeft, center_uv_rect.mBottom); gGL.vertex3fv((center_draw_rect.mLeft * width_vec + center_draw_rect.mBottom * height_vec).mV); @@ -1370,6 +1616,12 @@ void gl_segmented_rect_3d_tex(const LLRectf& clip_rect, const LLRectf& center_uv gGL.texCoord2f(clip_rect.mRight, center_uv_rect.mBottom); gGL.vertex3fv((width_vec + center_draw_rect.mBottom * height_vec).mV); + gGL.texCoord2f(center_uv_rect.mRight, clip_rect.mBottom); + gGL.vertex3fv((center_draw_rect.mRight * width_vec).mV); + + gGL.texCoord2f(clip_rect.mRight, center_uv_rect.mBottom); + gGL.vertex3fv((width_vec + center_draw_rect.mBottom * height_vec).mV); + gGL.texCoord2f(center_uv_rect.mRight, center_uv_rect.mBottom); gGL.vertex3fv((center_draw_rect.mRight * width_vec + center_draw_rect.mBottom * height_vec).mV); @@ -1383,6 +1635,12 @@ void gl_segmented_rect_3d_tex(const LLRectf& clip_rect, const LLRectf& center_uv gGL.texCoord2f(center_uv_rect.mLeft, center_uv_rect.mTop); gGL.vertex3fv((center_draw_rect.mLeft * width_vec + center_draw_rect.mTop * height_vec).mV); + gGL.texCoord2f(clip_rect.mLeft, center_uv_rect.mBottom); + gGL.vertex3fv((center_draw_rect.mBottom * height_vec).mV); + + gGL.texCoord2f(center_uv_rect.mLeft, center_uv_rect.mTop); + gGL.vertex3fv((center_draw_rect.mLeft * width_vec + center_draw_rect.mTop * height_vec).mV); + gGL.texCoord2f(clip_rect.mLeft, center_uv_rect.mTop); gGL.vertex3fv((center_draw_rect.mTop * height_vec).mV); @@ -1396,6 +1654,12 @@ void gl_segmented_rect_3d_tex(const LLRectf& clip_rect, const LLRectf& center_uv gGL.texCoord2f(center_uv_rect.mRight, center_uv_rect.mTop); gGL.vertex3fv((center_draw_rect.mRight * width_vec + center_draw_rect.mTop * height_vec).mV); + gGL.texCoord2f(center_uv_rect.mLeft, center_uv_rect.mBottom); + gGL.vertex3fv((center_draw_rect.mLeft * width_vec + center_draw_rect.mBottom * height_vec).mV); + + gGL.texCoord2f(center_uv_rect.mRight, center_uv_rect.mTop); + gGL.vertex3fv((center_draw_rect.mRight * width_vec + center_draw_rect.mTop * height_vec).mV); + gGL.texCoord2f(center_uv_rect.mLeft, center_uv_rect.mTop); gGL.vertex3fv((center_draw_rect.mLeft * width_vec + center_draw_rect.mTop * height_vec).mV); @@ -1409,6 +1673,12 @@ void gl_segmented_rect_3d_tex(const LLRectf& clip_rect, const LLRectf& center_uv gGL.texCoord2f(clip_rect.mRight, center_uv_rect.mTop); gGL.vertex3fv((width_vec + center_draw_rect.mTop * height_vec).mV); + gGL.texCoord2f(center_uv_rect.mRight, center_uv_rect.mBottom); + gGL.vertex3fv((center_draw_rect.mRight* width_vec + center_draw_rect.mBottom * height_vec).mV); + + gGL.texCoord2f(clip_rect.mRight, center_uv_rect.mTop); + gGL.vertex3fv((width_vec + center_draw_rect.mTop * height_vec).mV); + gGL.texCoord2f(center_uv_rect.mRight, center_uv_rect.mTop); gGL.vertex3fv((center_draw_rect.mRight * width_vec + center_draw_rect.mTop * height_vec).mV); @@ -1422,6 +1692,12 @@ void gl_segmented_rect_3d_tex(const LLRectf& clip_rect, const LLRectf& center_uv gGL.texCoord2f(center_uv_rect.mLeft, clip_rect.mTop); gGL.vertex3fv((center_draw_rect.mLeft * width_vec + height_vec).mV); + gGL.texCoord2f(clip_rect.mLeft, center_uv_rect.mTop); + gGL.vertex3fv((center_draw_rect.mTop* height_vec).mV); + + gGL.texCoord2f(center_uv_rect.mLeft, center_uv_rect.mTop); + gGL.vertex3fv((center_draw_rect.mLeft* width_vec + center_draw_rect.mTop * height_vec).mV); + gGL.texCoord2f(clip_rect.mLeft, clip_rect.mTop); gGL.vertex3fv((height_vec).mV); @@ -1435,6 +1711,12 @@ void gl_segmented_rect_3d_tex(const LLRectf& clip_rect, const LLRectf& center_uv gGL.texCoord2f(center_uv_rect.mRight, clip_rect.mTop); gGL.vertex3fv((center_draw_rect.mRight * width_vec + height_vec).mV); + gGL.texCoord2f(center_uv_rect.mLeft, center_uv_rect.mTop); + gGL.vertex3fv((center_draw_rect.mLeft* width_vec + center_draw_rect.mTop * height_vec).mV); + + gGL.texCoord2f(center_uv_rect.mRight, center_uv_rect.mTop); + gGL.vertex3fv((center_draw_rect.mRight* width_vec + center_draw_rect.mTop * height_vec).mV); + gGL.texCoord2f(center_uv_rect.mLeft, clip_rect.mTop); gGL.vertex3fv((center_draw_rect.mLeft * width_vec + height_vec).mV); @@ -1448,6 +1730,12 @@ void gl_segmented_rect_3d_tex(const LLRectf& clip_rect, const LLRectf& center_uv gGL.texCoord2f(clip_rect.mRight, clip_rect.mTop); gGL.vertex3fv((width_vec + height_vec).mV); + gGL.texCoord2f(center_uv_rect.mRight, center_uv_rect.mTop); + gGL.vertex3fv((center_draw_rect.mRight* width_vec + center_draw_rect.mTop * height_vec).mV); + + gGL.texCoord2f(clip_rect.mRight, clip_rect.mTop); + gGL.vertex3fv((width_vec + height_vec).mV); + gGL.texCoord2f(center_uv_rect.mRight, clip_rect.mTop); gGL.vertex3fv((center_draw_rect.mRight * width_vec + height_vec).mV); } diff --git a/indra/llrender/llrendertarget.cpp b/indra/llrender/llrendertarget.cpp index 38bc5ff331..c72f8fa2ba 100644 --- a/indra/llrender/llrendertarget.cpp +++ b/indra/llrender/llrendertarget.cpp @@ -51,6 +51,7 @@ void check_framebuffer_status() } bool LLRenderTarget::sUseFBO = false; +bool LLRenderTarget::sClearOnInvalidate = false; U32 LLRenderTarget::sCurFBO = 0; @@ -473,6 +474,13 @@ void LLRenderTarget::clear(U32 mask_in) } } +void LLRenderTarget::invalidate(U32 mask_in) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY; + if (!sClearOnInvalidate) { return; } + clear(mask_in); +} + U32 LLRenderTarget::getTexture(U32 attachment) const { if (attachment >= mTex.size()) @@ -584,7 +592,6 @@ void LLRenderTarget::swapFBORefs(LLRenderTarget& other) llassert(!other.isBoundInStack()); // Must be same type - llassert(sUseFBO == other.sUseFBO); llassert(mResX == other.mResX); llassert(mResY == other.mResY); llassert(mInternalFormat == other.mInternalFormat); diff --git a/indra/llrender/llrendertarget.h b/indra/llrender/llrendertarget.h index cd3290cf66..f066534cf4 100644 --- a/indra/llrender/llrendertarget.h +++ b/indra/llrender/llrendertarget.h @@ -63,6 +63,7 @@ class LLRenderTarget public: // Whether or not to use FBO implementation static bool sUseFBO; + static bool sClearOnInvalidate; static U32 sBytesAllocated; static U32 sCurFBO; static U32 sCurResX; @@ -128,11 +129,17 @@ public: // Asserts that this target is not currently bound in the stack void bindTarget(); - //clear render targer, clears depth buffer if present, + //clear render target, clears depth buffer if present, //uses scissor rect if in copy-to-texture mode // asserts that this target is currently bound void clear(U32 mask = 0xFFFFFFFF); + //same as clear, except may be a no-op depending on configuration + //useful to indicate the buffer is about to be overwritten and we + //don't care about its previous contents + //depending on the GPU, one may be more expensive than the other + void invalidate(U32 mask = 0xFFFFFFFF); + //get applied viewport void getViewport(S32* viewport); diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp index e9fa369b0c..6f4828397a 100644 --- a/indra/llrender/llvertexbuffer.cpp +++ b/indra/llrender/llvertexbuffer.cpp @@ -289,22 +289,58 @@ static GLuint gen_buffer() return ret; } -#define ANALYZE_VBO_POOL 0 +static void delete_buffers(S32 count, GLuint* buffers) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VERTEX; + // wait a few frames before actually deleting the buffers to avoid + // synchronization issues with the GPU + static std::vector<GLuint> sFreeList[4]; + + if (gGLManager.mInited) + { + U32 idx = LLImageGL::sFrameCount % 4; -#if LL_DARWIN + for (S32 i = 0; i < count; ++i) + { + sFreeList[idx].push_back(buffers[i]); + } + + idx = (LLImageGL::sFrameCount + 3) % 4; -// experimental -- disable VBO pooling on OS X and use glMapBuffer + if (!sFreeList[idx].empty()) + { + glDeleteBuffers((GLsizei)sFreeList[idx].size(), sFreeList[idx].data()); + sFreeList[idx].resize(0); + } + } +} + + +#define ANALYZE_VBO_POOL 0 + +// VBO Pool interface class LLVBOPool { + public: + virtual ~LLVBOPool() = default; + virtual void allocate(GLenum type, U32 size, GLuint& name, U8*& data) = 0; + virtual void free(GLenum type, U32 size, GLuint name, U8* data) = 0; + virtual U64 getVramBytesUsed() = 0; +}; + +// VBO Pool for Apple GPUs (as in M1/M2 etc, not Intel macs) +// Effectively disables VBO pooling +class LLAppleVBOPool final: public LLVBOPool +{ public: U64 mAllocated = 0; - U64 getVramBytesUsed() + U64 getVramBytesUsed() override { return mAllocated; } - void allocate(GLenum type, U32 size, GLuint& name, U8*& data) + void allocate(GLenum type, U32 size, GLuint& name, U8*& data) override { LL_PROFILE_ZONE_SCOPED_CATEGORY_VERTEX; STOP_GLERROR; @@ -324,7 +360,7 @@ public: } } - void free(GLenum type, U32 size, GLuint name, U8* data) + void free(GLenum type, U32 size, GLuint name, U8* data) override { LL_PROFILE_ZONE_SCOPED_CATEGORY_VERTEX; llassert(type == GL_ARRAY_BUFFER || type == GL_ELEMENT_ARRAY_BUFFER); @@ -339,19 +375,17 @@ public: STOP_GLERROR; if (name) { - glDeleteBuffers(1, &name); + delete_buffers(1, &name); } STOP_GLERROR; } }; -#else - -class LLVBOPool +// VBO Pool for GPUs that benefit from VBO pooling +class LLDefaultVBOPool final : public LLVBOPool { public: typedef std::chrono::steady_clock::time_point Time; - struct Entry { U8* mData; @@ -359,7 +393,7 @@ public: Time mAge; }; - ~LLVBOPool() + ~LLDefaultVBOPool() override { clear(); } @@ -377,7 +411,7 @@ public: U32 mMisses = 0; U32 mHits = 0; - U64 getVramBytesUsed() + U64 getVramBytesUsed() override { return mAllocated + mReserved; } @@ -393,7 +427,7 @@ public: size += block_size - (size % block_size); } - void allocate(GLenum type, U32 size, GLuint& name, U8*& data) + void allocate(GLenum type, U32 size, GLuint& name, U8*& data) override { LL_PROFILE_ZONE_SCOPED_CATEGORY_VERTEX; llassert(type == GL_ARRAY_BUFFER || type == GL_ELEMENT_ARRAY_BUFFER); @@ -449,7 +483,7 @@ public: clean(); } - void free(GLenum type, U32 size, GLuint name, U8* data) + void free(GLenum type, U32 size, GLuint name, U8* data) override { LL_PROFILE_ZONE_SCOPED_CATEGORY_VERTEX; llassert(type == GL_ARRAY_BUFFER || type == GL_ELEMENT_ARRAY_BUFFER); @@ -512,7 +546,7 @@ public: LL_PROFILE_ZONE_NAMED_CATEGORY_VERTEX("vbo cache timeout"); auto& entry = entries.back(); ll_aligned_free_16(entry.mData); - glDeleteBuffers(1, &entry.mGLName); + delete_buffers(1, &entry.mGLName); llassert(mReserved >= iter->first); mReserved -= iter->first; entries.pop_back(); @@ -548,7 +582,7 @@ public: for (auto& entry : entries.second) { ll_aligned_free_16(entry.mData); - glDeleteBuffers(1, &entry.mGLName); + delete_buffers(1, &entry.mGLName); } } @@ -557,7 +591,7 @@ public: for (auto& entry : entries.second) { ll_aligned_free_16(entry.mData); - glDeleteBuffers(1, &entry.mGLName); + delete_buffers(1, &entry.mGLName); } } @@ -567,7 +601,6 @@ public: mVBOPool.clear(); } }; -#endif static LLVBOPool* sVBOPool = nullptr; @@ -600,15 +633,7 @@ void LLVertexBufferData::draw() gGL.loadMatrix(glm::value_ptr(mTexture0)); mVB->setBuffer(); - - if (mMode == LLRender::QUADS && LLRender::sGLCoreProfile) - { - mVB->drawArrays(LLRender::TRIANGLES, 0, mCount); - } - else - { - mVB->drawArrays(mMode, 0, mCount); - } + mVB->drawArrays(mMode, 0, mCount); gGL.popMatrix(); gGL.matrixMode(LLRender::MM_PROJECTION); @@ -681,7 +706,6 @@ const U32 LLVertexBuffer::sGLMode[LLRender::NUM_MODES] = GL_POINTS, GL_LINES, GL_LINE_STRIP, - GL_QUADS, GL_LINE_LOOP, }; @@ -896,7 +920,16 @@ void LLVertexBuffer::drawArrays(U32 mode, U32 first, U32 count) const void LLVertexBuffer::initClass(LLWindow* window) { llassert(sVBOPool == nullptr); - sVBOPool = new LLVBOPool(); + if (gGLManager.mIsApple) + { + LL_INFOS() << "VBO Pooling Disabled" << LL_ENDL; + sVBOPool = new LLAppleVBOPool(); + } + else + { + LL_INFOS() << "VBO Pooling Enabled" << LL_ENDL; + sVBOPool = new LLDefaultVBOPool(); + } #if ENABLE_GL_WORK_QUEUE sQueue = new GLWorkQueue(); @@ -964,7 +997,6 @@ void LLVertexBuffer::flushBuffers() { LL_PROFILE_ZONE_SCOPED_CATEGORY_VERTEX; // must only be called from main thread - llassert(LLCoros::on_main_thread_main_coro()); for (auto& buffer : sMappedBuffers) { buffer->_unmapBuffer(); @@ -1231,28 +1263,29 @@ U8* LLVertexBuffer::mapVertexBuffer(LLVertexBuffer::AttributeType type, U32 inde count = mNumVerts - index; } -#if !LL_DARWIN - U32 start = mOffsets[type] + sTypeSize[type] * index; - U32 end = start + sTypeSize[type] * count-1; - - bool flagged = false; - // flag region as mapped - for (U32 i = 0; i < mMappedVertexRegions.size(); ++i) + if (!gGLManager.mIsApple) { - MappedRegion& region = mMappedVertexRegions[i]; - if (expand_region(region, start, end)) + U32 start = mOffsets[type] + sTypeSize[type] * index; + U32 end = start + sTypeSize[type] * count-1; + + bool flagged = false; + // flag region as mapped + for (U32 i = 0; i < mMappedVertexRegions.size(); ++i) { - flagged = true; - break; + MappedRegion& region = mMappedVertexRegions[i]; + if (expand_region(region, start, end)) + { + flagged = true; + break; + } } - } - if (!flagged) - { - //didn't expand an existing region, make a new one - mMappedVertexRegions.push_back({ start, end }); + if (!flagged) + { + //didn't expand an existing region, make a new one + mMappedVertexRegions.push_back({ start, end }); + } } -#endif return mMappedData+mOffsets[type]+sTypeSize[type]*index; } @@ -1267,28 +1300,29 @@ U8* LLVertexBuffer::mapIndexBuffer(U32 index, S32 count) count = mNumIndices-index; } -#if !LL_DARWIN - U32 start = sizeof(U16) * index; - U32 end = start + sizeof(U16) * count-1; - - bool flagged = false; - // flag region as mapped - for (U32 i = 0; i < mMappedIndexRegions.size(); ++i) + if (!gGLManager.mIsApple) { - MappedRegion& region = mMappedIndexRegions[i]; - if (expand_region(region, start, end)) + U32 start = sizeof(U16) * index; + U32 end = start + sizeof(U16) * count-1; + + bool flagged = false; + // flag region as mapped + for (U32 i = 0; i < mMappedIndexRegions.size(); ++i) { - flagged = true; - break; + MappedRegion& region = mMappedIndexRegions[i]; + if (expand_region(region, start, end)) + { + flagged = true; + break; + } } - } - if (!flagged) - { - //didn't expand an existing region, make a new one - mMappedIndexRegions.push_back({ start, end }); + if (!flagged) + { + //didn't expand an existing region, make a new one + mMappedIndexRegions.push_back({ start, end }); + } } -#endif return mMappedIndexData + sizeof(U16)*index; } @@ -1301,37 +1335,40 @@ U8* LLVertexBuffer::mapIndexBuffer(U32 index, S32 count) // dst -- mMappedData or mMappedIndexData void LLVertexBuffer::flush_vbo(GLenum target, U32 start, U32 end, void* data, U8* dst) { -#if LL_DARWIN - // on OS X, flush_vbo doesn't actually write to the GL buffer, so be sure to call - // _mapBuffer to tag the buffer for flushing to GL - _mapBuffer(); - LL_PROFILE_ZONE_NAMED_CATEGORY_VERTEX("vb memcpy"); - STOP_GLERROR; - // copy into mapped buffer - memcpy(dst+start, data, end-start+1); -#else - llassert(target == GL_ARRAY_BUFFER ? sGLRenderBuffer == mGLBuffer : sGLRenderIndices == mGLIndices); - - // skip mapped data and stream to GPU via glBufferSubData - if (end != 0) + if (gGLManager.mIsApple) { - LL_PROFILE_ZONE_NAMED_CATEGORY_VERTEX("glBufferSubData"); - LL_PROFILE_ZONE_NUM(start); - LL_PROFILE_ZONE_NUM(end); - LL_PROFILE_ZONE_NUM(end-start); - - constexpr U32 block_size = 65536; + // on OS X, flush_vbo doesn't actually write to the GL buffer, so be sure to call + // _mapBuffer to tag the buffer for flushing to GL + _mapBuffer(); + LL_PROFILE_ZONE_NAMED_CATEGORY_VERTEX("vb memcpy"); + STOP_GLERROR; + // copy into mapped buffer + memcpy(dst+start, data, end-start+1); + } + else + { + llassert(target == GL_ARRAY_BUFFER ? sGLRenderBuffer == mGLBuffer : sGLRenderIndices == mGLIndices); - for (U32 i = start; i <= end; i += block_size) + // skip mapped data and stream to GPU via glBufferSubData + if (end != 0) { - //LL_PROFILE_ZONE_NAMED_CATEGORY_VERTEX("glBufferSubData block"); - //LL_PROFILE_GPU_ZONE("glBufferSubData"); - U32 tend = llmin(i + block_size, end); - U32 size = tend - i + 1; - glBufferSubData(target, i, size, (U8*) data + (i-start)); + LL_PROFILE_ZONE_NAMED_CATEGORY_VERTEX("glBufferSubData"); + LL_PROFILE_ZONE_NUM(start); + LL_PROFILE_ZONE_NUM(end); + LL_PROFILE_ZONE_NUM(end-start); + + constexpr U32 block_size = 65536; + + for (U32 i = start; i <= end; i += block_size) + { + //LL_PROFILE_ZONE_NAMED_CATEGORY_VERTEX("glBufferSubData block"); + //LL_PROFILE_GPU_ZONE("glBufferSubData"); + U32 tend = llmin(i + block_size, end); + U32 size = tend - i + 1; + glBufferSubData(target, i, size, (U8*) data + (i-start)); + } } } -#endif } void LLVertexBuffer::unmapBuffer() @@ -1364,114 +1401,116 @@ void LLVertexBuffer::_unmapBuffer() } }; -#if LL_DARWIN - STOP_GLERROR; - if (mMappedData) - { - if (mGLBuffer) - { - glDeleteBuffers(1, &mGLBuffer); - } - mGLBuffer = gen_buffer(); - glBindBuffer(GL_ARRAY_BUFFER, mGLBuffer); - sGLRenderBuffer = mGLBuffer; - glBufferData(GL_ARRAY_BUFFER, mSize, mMappedData, GL_STATIC_DRAW); - } - else if (mGLBuffer != sGLRenderBuffer) - { - glBindBuffer(GL_ARRAY_BUFFER, mGLBuffer); - sGLRenderBuffer = mGLBuffer; - } - STOP_GLERROR; - - if (mMappedIndexData) + if (gGLManager.mIsApple) { - if (mGLIndices) + STOP_GLERROR; + if (mMappedData) { - glDeleteBuffers(1, &mGLIndices); + if (mGLBuffer) + { + delete_buffers(1, &mGLBuffer); + } + mGLBuffer = gen_buffer(); + glBindBuffer(GL_ARRAY_BUFFER, mGLBuffer); + sGLRenderBuffer = mGLBuffer; + glBufferData(GL_ARRAY_BUFFER, mSize, mMappedData, GL_STATIC_DRAW); } - - mGLIndices = gen_buffer(); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mGLIndices); - sGLRenderIndices = mGLIndices; - - glBufferData(GL_ELEMENT_ARRAY_BUFFER, mIndicesSize, mMappedIndexData, GL_STATIC_DRAW); - } - else if (mGLIndices != sGLRenderIndices) - { - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mGLIndices); - sGLRenderIndices = mGLIndices; - } - STOP_GLERROR; -#else - - if (!mMappedVertexRegions.empty()) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_VERTEX("unmapBuffer - vertex"); - - if (sGLRenderBuffer != mGLBuffer) + else if (mGLBuffer != sGLRenderBuffer) { glBindBuffer(GL_ARRAY_BUFFER, mGLBuffer); sGLRenderBuffer = mGLBuffer; } + STOP_GLERROR; - U32 start = 0; - U32 end = 0; - - std::sort(mMappedVertexRegions.begin(), mMappedVertexRegions.end(), SortMappedRegion()); - - for (U32 i = 0; i < mMappedVertexRegions.size(); ++i) + if (mMappedIndexData) { - const MappedRegion& region = mMappedVertexRegions[i]; - if (region.mStart == end + 1) + if (mGLIndices) { - end = region.mEnd; + delete_buffers(1, &mGLIndices); } - else - { - flush_vbo(GL_ARRAY_BUFFER, start, end, (U8*)mMappedData + start, mMappedData); - start = region.mStart; - end = region.mEnd; - } - } - - flush_vbo(GL_ARRAY_BUFFER, start, end, (U8*)mMappedData + start, mMappedData); - mMappedVertexRegions.clear(); - } - if (!mMappedIndexRegions.empty()) - { - LL_PROFILE_ZONE_NAMED_CATEGORY_VERTEX("unmapBuffer - index"); + mGLIndices = gen_buffer(); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mGLIndices); + sGLRenderIndices = mGLIndices; - if (mGLIndices != sGLRenderIndices) + glBufferData(GL_ELEMENT_ARRAY_BUFFER, mIndicesSize, mMappedIndexData, GL_STATIC_DRAW); + } + else if (mGLIndices != sGLRenderIndices) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mGLIndices); sGLRenderIndices = mGLIndices; } - U32 start = 0; - U32 end = 0; + STOP_GLERROR; + } + else + { + if (!mMappedVertexRegions.empty()) + { + LL_PROFILE_ZONE_NAMED_CATEGORY_VERTEX("unmapBuffer - vertex"); - std::sort(mMappedIndexRegions.begin(), mMappedIndexRegions.end(), SortMappedRegion()); + if (sGLRenderBuffer != mGLBuffer) + { + glBindBuffer(GL_ARRAY_BUFFER, mGLBuffer); + sGLRenderBuffer = mGLBuffer; + } - for (U32 i = 0; i < mMappedIndexRegions.size(); ++i) + U32 start = 0; + U32 end = 0; + + std::sort(mMappedVertexRegions.begin(), mMappedVertexRegions.end(), SortMappedRegion()); + + for (U32 i = 0; i < mMappedVertexRegions.size(); ++i) + { + const MappedRegion& region = mMappedVertexRegions[i]; + if (region.mStart == end + 1) + { + end = region.mEnd; + } + else + { + flush_vbo(GL_ARRAY_BUFFER, start, end, (U8*)mMappedData + start, mMappedData); + start = region.mStart; + end = region.mEnd; + } + } + + flush_vbo(GL_ARRAY_BUFFER, start, end, (U8*)mMappedData + start, mMappedData); + mMappedVertexRegions.clear(); + } + + if (!mMappedIndexRegions.empty()) { - const MappedRegion& region = mMappedIndexRegions[i]; - if (region.mStart == end + 1) + LL_PROFILE_ZONE_NAMED_CATEGORY_VERTEX("unmapBuffer - index"); + + if (mGLIndices != sGLRenderIndices) { - end = region.mEnd; + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mGLIndices); + sGLRenderIndices = mGLIndices; } - else + U32 start = 0; + U32 end = 0; + + std::sort(mMappedIndexRegions.begin(), mMappedIndexRegions.end(), SortMappedRegion()); + + for (U32 i = 0; i < mMappedIndexRegions.size(); ++i) { - flush_vbo(GL_ELEMENT_ARRAY_BUFFER, start, end, (U8*)mMappedIndexData + start, mMappedIndexData); - start = region.mStart; - end = region.mEnd; + const MappedRegion& region = mMappedIndexRegions[i]; + if (region.mStart == end + 1) + { + end = region.mEnd; + } + else + { + flush_vbo(GL_ELEMENT_ARRAY_BUFFER, start, end, (U8*)mMappedIndexData + start, mMappedIndexData); + start = region.mStart; + end = region.mEnd; + } } - } - flush_vbo(GL_ELEMENT_ARRAY_BUFFER, start, end, (U8*)mMappedIndexData + start, mMappedIndexData); - mMappedIndexRegions.clear(); + flush_vbo(GL_ELEMENT_ARRAY_BUFFER, start, end, (U8*)mMappedIndexData + start, mMappedIndexData); + mMappedIndexRegions.clear(); + } } -#endif } //---------------------------------------------------------------------------- diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index a0314cb5f2..69e1b57245 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -49,6 +49,7 @@ set(llui_SOURCE_FILES lllineeditor.cpp llloadingindicator.cpp lllocalcliprect.cpp + llluafloater.cpp llmenubutton.cpp llmenugl.cpp llmodaldialog.cpp @@ -164,6 +165,7 @@ set(llui_HEADER_FILES lllineeditor.h llloadingindicator.h lllocalcliprect.h + llluafloater.h llmenubutton.h llmenugl.h llmodaldialog.h diff --git a/indra/llui/llbadge.cpp b/indra/llui/llbadge.cpp index c6654ee0aa..42b6f1f07b 100644 --- a/indra/llui/llbadge.cpp +++ b/indra/llui/llbadge.cpp @@ -204,13 +204,13 @@ void renderBadgeBackground(F32 centerX, F32 centerY, F32 width, F32 height, cons (F32)ll_round(x) + width, (F32)ll_round(y) + height); - LLVector3 vertices[4]; - vertices[0] = LLVector3(screen_rect.mRight, screen_rect.mTop, 1.0f); - vertices[1] = LLVector3(screen_rect.mLeft, screen_rect.mTop, 1.0f); - vertices[2] = LLVector3(screen_rect.mLeft, screen_rect.mBottom, 1.0f); - vertices[3] = LLVector3(screen_rect.mRight, screen_rect.mBottom, 1.0f); + LLVector4a vertices[4]; + vertices[0].set(screen_rect.mLeft, screen_rect.mTop, 1.0f); + vertices[1].set(screen_rect.mRight, screen_rect.mTop, 1.0f); + vertices[2].set(screen_rect.mLeft, screen_rect.mBottom, 1.0f); + vertices[3].set(screen_rect.mRight, screen_rect.mBottom, 1.0f); - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLE_STRIP); { gGL.vertexBatchPreTransformed(vertices, 4); } diff --git a/indra/llui/llchat.h b/indra/llui/llchat.h index 5f75ed2f8d..8adb3a87ef 100644 --- a/indra/llui/llchat.h +++ b/indra/llui/llchat.h @@ -89,7 +89,8 @@ public: mPosAgent(), mURL(), mChatStyle(CHAT_STYLE_NORMAL), - mSessionID() + mSessionID(), + mIsScript(false) { } std::string mText; // UTF-8 line of text @@ -107,6 +108,22 @@ public: std::string mURL; EChatStyle mChatStyle; LLUUID mSessionID; + + bool mIsScript; }; +static const std::string LUA_PREFIX("[LUA]"); + +inline +std::string without_LUA_PREFIX(const std::string& string, bool is_lua) +{ + if (is_lua) + { + return string.substr(LUA_PREFIX.size()); + } + else + { + return string; + } +} #endif diff --git a/indra/llui/llchatentry.cpp b/indra/llui/llchatentry.cpp index da5afd0386..e8d942b8af 100644 --- a/indra/llui/llchatentry.cpp +++ b/indra/llui/llchatentry.cpp @@ -45,7 +45,8 @@ LLChatEntry::LLChatEntry(const Params& p) mExpandLinesCount(p.expand_lines_count), mPrevLinesCount(0), mSingleLineMode(false), - mPrevExpandedLineCount(S32_MAX) + mPrevExpandedLineCount(S32_MAX), + mCurrentInput("") { // Initialize current history line iterator mCurrentHistoryLine = mLineHistory.begin(); @@ -189,6 +190,7 @@ bool LLChatEntry::handleSpecialKey(const KEY key, const MASK mask) { needsReflow(); } + mCurrentInput = ""; break; case KEY_UP: @@ -196,6 +198,11 @@ bool LLChatEntry::handleSpecialKey(const KEY key, const MASK mask) { if (!mLineHistory.empty() && mCurrentHistoryLine > mLineHistory.begin()) { + if (mCurrentHistoryLine == mLineHistory.end()) + { + mCurrentInput = getText(); + } + setText(*(--mCurrentHistoryLine)); endOfDoc(); } @@ -210,16 +217,15 @@ bool LLChatEntry::handleSpecialKey(const KEY key, const MASK mask) case KEY_DOWN: if (mHasHistory && MASK_CONTROL == mask) { - if (!mLineHistory.empty() && mCurrentHistoryLine < (mLineHistory.end() - 1) ) + if (!mLineHistory.empty() && mCurrentHistoryLine < (mLineHistory.end() - 1)) { setText(*(++mCurrentHistoryLine)); endOfDoc(); } - else if (!mLineHistory.empty() && mCurrentHistoryLine == (mLineHistory.end() - 1) ) + else if (!mLineHistory.empty() && mCurrentHistoryLine == (mLineHistory.end() - 1)) { mCurrentHistoryLine++; - std::string empty(""); - setText(empty); + setText(mCurrentInput); needsReflow(); endOfDoc(); } diff --git a/indra/llui/llchatentry.h b/indra/llui/llchatentry.h index 5621ede1e7..9a0e8ee91e 100644 --- a/indra/llui/llchatentry.h +++ b/indra/llui/llchatentry.h @@ -101,6 +101,8 @@ private: S32 mExpandLinesCount; S32 mPrevLinesCount; S32 mPrevExpandedLineCount; + + std::string mCurrentInput; }; #endif /* LLCHATENTRY_H_ */ diff --git a/indra/llui/llcommandmanager.cpp b/indra/llui/llcommandmanager.cpp index 03717da80b..812a360190 100644 --- a/indra/llui/llcommandmanager.cpp +++ b/indra/llui/llcommandmanager.cpp @@ -32,6 +32,7 @@ #include "llcommandmanager.h" #include "lldir.h" #include "llerror.h" +#include "llsdutil.h" #include "llxuiparser.h" @@ -189,3 +190,8 @@ bool LLCommandManager::load() return true; } + +LLSD LLCommandManager::getCommandNames() +{ + return llsd::toArray(mCommands, [](const auto &cmd) { return cmd->name(); }); + } diff --git a/indra/llui/llcommandmanager.h b/indra/llui/llcommandmanager.h index e6df0d3a4b..69d631a398 100644 --- a/indra/llui/llcommandmanager.h +++ b/indra/llui/llcommandmanager.h @@ -192,6 +192,8 @@ public: LLCommand * getCommand(const LLCommandId& commandId); LLCommand * getCommand(const std::string& name); + LLSD getCommandNames(); + static bool load(); protected: diff --git a/indra/llui/llflashtimer.cpp b/indra/llui/llflashtimer.cpp index c3db24c987..2711e8088d 100644 --- a/indra/llui/llflashtimer.cpp +++ b/indra/llui/llflashtimer.cpp @@ -35,7 +35,7 @@ LLFlashTimer::LLFlashTimer(callback_t cb, S32 count, F32 period) mIsCurrentlyHighlighted(false), mUnset(false) { - mEventTimer.stop(); + stop(); // By default use settings from settings.xml to be able change them via Debug settings. See EXT-5973. // Due to Timer is implemented as derived class from EventTimer it is impossible to change period @@ -74,12 +74,12 @@ void LLFlashTimer::startFlashing() { mIsFlashingInProgress = true; mIsCurrentlyHighlighted = true; - mEventTimer.start(); + start(); } void LLFlashTimer::stopFlashing() { - mEventTimer.stop(); + stop(); mIsFlashingInProgress = false; mIsCurrentlyHighlighted = false; mCurrentTickCount = 0; diff --git a/indra/llui/llflashtimer.h b/indra/llui/llflashtimer.h index b55ce53fc0..988b577ed2 100644 --- a/indra/llui/llflashtimer.h +++ b/indra/llui/llflashtimer.h @@ -46,7 +46,7 @@ public: LLFlashTimer(callback_t cb = NULL, S32 count = 0, F32 period = 0.0); ~LLFlashTimer() {}; - /*virtual*/ bool tick(); + bool tick() override; void startFlashing(); void stopFlashing(); diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 92fb4b75bf..4b904f09e0 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -2275,36 +2275,28 @@ void LLFloater::drawConeToOwner(F32 &context_cone_opacity, gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); LLGLEnable(GL_CULL_FACE); - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLE_STRIP); { gGL.color4f(0.f, 0.f, 0.f, contex_cone_in_alpha * context_cone_opacity); gGL.vertex2i(owner_rect.mLeft, owner_rect.mTop); - gGL.vertex2i(owner_rect.mRight, owner_rect.mTop); - gGL.color4f(0.f, 0.f, 0.f, contex_cone_out_alpha * context_cone_opacity); - gGL.vertex2i(local_rect.mRight, local_rect.mTop); - gGL.vertex2i(local_rect.mLeft, local_rect.mTop); - gGL.color4f(0.f, 0.f, 0.f, contex_cone_out_alpha * context_cone_opacity); gGL.vertex2i(local_rect.mLeft, local_rect.mTop); - gGL.vertex2i(local_rect.mLeft, local_rect.mBottom); gGL.color4f(0.f, 0.f, 0.f, contex_cone_in_alpha * context_cone_opacity); - gGL.vertex2i(owner_rect.mLeft, owner_rect.mBottom); - gGL.vertex2i(owner_rect.mLeft, owner_rect.mTop); - + gGL.vertex2i(owner_rect.mRight, owner_rect.mTop); gGL.color4f(0.f, 0.f, 0.f, contex_cone_out_alpha * context_cone_opacity); - gGL.vertex2i(local_rect.mRight, local_rect.mBottom); gGL.vertex2i(local_rect.mRight, local_rect.mTop); gGL.color4f(0.f, 0.f, 0.f, contex_cone_in_alpha * context_cone_opacity); - gGL.vertex2i(owner_rect.mRight, owner_rect.mTop); gGL.vertex2i(owner_rect.mRight, owner_rect.mBottom); - - gGL.color4f(0.f, 0.f, 0.f, contex_cone_out_alpha * context_cone_opacity); - gGL.vertex2i(local_rect.mLeft, local_rect.mBottom); gGL.vertex2i(local_rect.mRight, local_rect.mBottom); gGL.color4f(0.f, 0.f, 0.f, contex_cone_in_alpha * context_cone_opacity); - gGL.vertex2i(owner_rect.mRight, owner_rect.mBottom); gGL.vertex2i(owner_rect.mLeft, owner_rect.mBottom); + gGL.color4f(0.f, 0.f, 0.f, contex_cone_out_alpha * context_cone_opacity); + gGL.vertex2i(local_rect.mLeft, local_rect.mBottom); + gGL.color4f(0.f, 0.f, 0.f, contex_cone_in_alpha * context_cone_opacity); + gGL.vertex2i(owner_rect.mLeft, owner_rect.mTop); + gGL.color4f(0.f, 0.f, 0.f, contex_cone_out_alpha * context_cone_opacity); + gGL.vertex2i(local_rect.mLeft, local_rect.mTop); } gGL.end(); } diff --git a/indra/llui/llfloaterreg.cpp b/indra/llui/llfloaterreg.cpp index a818e72f59..c4e6061a12 100644 --- a/indra/llui/llfloaterreg.cpp +++ b/indra/llui/llfloaterreg.cpp @@ -624,3 +624,8 @@ U32 LLFloaterReg::getVisibleFloaterInstanceCount() return count; } + +LLSD LLFloaterReg::getFloaterNames() +{ + return llsd::toArray(sGroupMap, [](const auto &pair) { return pair.first; }); +} diff --git a/indra/llui/llfloaterreg.h b/indra/llui/llfloaterreg.h index 94a67c8d8b..2873080c99 100644 --- a/indra/llui/llfloaterreg.h +++ b/indra/llui/llfloaterreg.h @@ -153,6 +153,8 @@ public: static void blockShowFloaters(bool value) { sBlockShowFloaters = value;} static U32 getVisibleFloaterInstanceCount(); + + static LLSD getFloaterNames(); }; #endif diff --git a/indra/llui/llfloaterreglistener.cpp b/indra/llui/llfloaterreglistener.cpp index 17641b8375..6e5f048c27 100644 --- a/indra/llui/llfloaterreglistener.cpp +++ b/indra/llui/llfloaterreglistener.cpp @@ -37,6 +37,8 @@ #include "llfloaterreg.h" #include "llfloater.h" #include "llbutton.h" +#include "llluafloater.h" +#include "resultset.h" LLFloaterRegListener::LLFloaterRegListener(): LLEventAPI("LLFloaterReg", @@ -72,6 +74,18 @@ LLFloaterRegListener::LLFloaterRegListener(): "Simulate clicking the named [\"button\"] in the visible floater named in [\"name\"]", &LLFloaterRegListener::clickButton, requiredNameButton); + + add("showLuaFloater", + "Open the new floater using XML file specified in [\"xml_path\"] with ID in [\"reqid\"]", + &LLLuaFloater::showLuaFloater, {llsd::map("xml_path", LLSD(), "reqid", LLSD())}); + add("getFloaterEvents", + "Return the table of Lua Floater events which are send to the script", + &LLFloaterRegListener::getLuaFloaterEvents); + + add("getFloaterNames", + "Return result set key [\"floaters\"] for names of all registered floaters", + &LLFloaterRegListener::getFloaterNames, + llsd::map("reply", LLSD::String())); } void LLFloaterRegListener::getBuildMap(const LLSD& event) const @@ -113,6 +127,24 @@ void LLFloaterRegListener::instanceVisible(const LLSD& event) const event); } +struct NameResultSet: public LL::ResultSet +{ + NameResultSet(): + LL::ResultSet("floaters"), + mNames(LLFloaterReg::getFloaterNames()) + {} + LLSD mNames; + + int getLength() const override { return narrow(mNames.size()); } + LLSD getSingle(int index) const override { return mNames[index]; } +}; + +void LLFloaterRegListener::getFloaterNames(const LLSD &event) const +{ + auto nameresult = new NameResultSet; + sendReply(llsd::map("floaters", nameresult->getKeyLength()), event); +} + void LLFloaterRegListener::clickButton(const LLSD& event) const { // If the caller requests a reply, build the reply. @@ -154,3 +186,8 @@ void LLFloaterRegListener::clickButton(const LLSD& event) const LLEventPumps::instance().obtain(replyPump).post(reply); } } + +void LLFloaterRegListener::getLuaFloaterEvents(const LLSD &event) const +{ + Response response(llsd::map("events", LLLuaFloater::getEventsData()), event); +} diff --git a/indra/llui/llfloaterreglistener.h b/indra/llui/llfloaterreglistener.h index a36072892c..42e7178cbc 100644 --- a/indra/llui/llfloaterreglistener.h +++ b/indra/llui/llfloaterreglistener.h @@ -49,6 +49,9 @@ private: void toggleInstance(const LLSD& event) const; void instanceVisible(const LLSD& event) const; void clickButton(const LLSD& event) const; + void getFloaterNames(const LLSD &event) const; + + void getLuaFloaterEvents(const LLSD &event) const; }; #endif /* ! defined(LL_LLFLOATERREGLISTENER_H) */ diff --git a/indra/llui/llfolderview.cpp b/indra/llui/llfolderview.cpp index 388dc5b1ac..42a9e267d2 100644 --- a/indra/llui/llfolderview.cpp +++ b/indra/llui/llfolderview.cpp @@ -1649,7 +1649,7 @@ void LLFolderView::scrollToShowItem(LLFolderViewItem* item, const LLRect& constr { LLRect local_rect = item->getLocalRect(); S32 icon_height = mIcon.isNull() ? 0 : mIcon->getHeight(); - S32 label_height = getLabelFontForStyle(mLabelStyle)->getLineHeight(); + S32 label_height = getLabelFont()->getLineHeight(); // when navigating with keyboard, only move top of opened folder on screen, otherwise show whole folder S32 max_height_to_show = item->isOpen() && mScrollContainer->hasFocus() ? (llmax( icon_height, label_height ) + item->getIconPad()) : local_rect.getHeight(); diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index 73803786a6..18bde344a0 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -48,7 +48,6 @@ static LLDefaultChildRegistry::Register<LLFolderViewItem> r("folder_view_item"); // statics std::map<U8, LLFontGL*> LLFolderViewItem::sFonts; // map of styles to fonts -bool LLFolderViewItem::sColorSetInitialized = false; LLUIColor LLFolderViewItem::sFgColor; LLUIColor LLFolderViewItem::sHighlightBgColor; LLUIColor LLFolderViewItem::sFlashBgColor; @@ -58,6 +57,10 @@ LLUIColor LLFolderViewItem::sFilterBGColor; LLUIColor LLFolderViewItem::sFilterTextColor; LLUIColor LLFolderViewItem::sSuffixColor; LLUIColor LLFolderViewItem::sSearchStatusColor; +S32 LLFolderViewItem::sTopPad = 0; +LLUIImagePtr LLFolderViewItem::sFolderArrowImg; +LLUIImagePtr LLFolderViewItem::sSelectionImg; +LLFontGL* LLFolderViewItem::sSuffixFont = nullptr; // only integers can be initialized in header const F32 LLFolderViewItem::FOLDER_CLOSE_TIME_CONSTANT = 0.02f; @@ -83,15 +86,42 @@ LLFontGL* LLFolderViewItem::getLabelFontForStyle(U8 style) return rtn; } + +const LLFontGL* LLFolderViewItem::getLabelFont() +{ + if (!pLabelFont) + { + pLabelFont = getLabelFontForStyle(mLabelStyle); + } + return pLabelFont; +} //static void LLFolderViewItem::initClass() { + const Params& default_params = LLUICtrlFactory::getDefaultParams<LLFolderViewItem>(); + sTopPad = default_params.item_top_pad; + sFolderArrowImg = default_params.folder_arrow_image; + sSelectionImg = default_params.selection_image; + sSuffixFont = getLabelFontForStyle(LLFontGL::NORMAL); + + sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); + sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE); + sFlashBgColor = LLUIColorTable::instance().getColor("MenuItemFlashBgColor", DEFAULT_WHITE); + sFocusOutlineColor = LLUIColorTable::instance().getColor("InventoryFocusOutlineColor", DEFAULT_WHITE); + sMouseOverColor = LLUIColorTable::instance().getColor("InventoryMouseOverColor", DEFAULT_WHITE); + sFilterBGColor = LLUIColorTable::instance().getColor("FilterBackgroundColor", DEFAULT_WHITE); + sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE); + sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE); + sSearchStatusColor = LLUIColorTable::instance().getColor("InventorySearchStatusColor", DEFAULT_WHITE); } //static void LLFolderViewItem::cleanupClass() { sFonts.clear(); + sFolderArrowImg = nullptr; + sSelectionImg = nullptr; + sSuffixFont = nullptr; } @@ -134,6 +164,7 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) mIsItemCut(false), mCutGeneration(0), mLabelStyle( LLFontGL::NORMAL ), + pLabelFont(nullptr), mHasVisibleChildren(false), mLocalIndentation(p.folder_indentation), mIndentation(0), @@ -158,20 +189,6 @@ LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) mMaxFolderItemOverlap(p.max_folder_item_overlap), mDoubleClickOverride(p.double_click_override) { - if (!sColorSetInitialized) - { - sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); - sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE); - sFlashBgColor = LLUIColorTable::instance().getColor("MenuItemFlashBgColor", DEFAULT_WHITE); - sFocusOutlineColor = LLUIColorTable::instance().getColor("InventoryFocusOutlineColor", DEFAULT_WHITE); - sMouseOverColor = LLUIColorTable::instance().getColor("InventoryMouseOverColor", DEFAULT_WHITE); - sFilterBGColor = LLUIColorTable::instance().getColor("FilterBackgroundColor", DEFAULT_WHITE); - sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE); - sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemLinkColor", DEFAULT_WHITE); - sSearchStatusColor = LLUIColorTable::instance().getColor("InventorySearchStatusColor", DEFAULT_WHITE); - sColorSetInitialized = true; - } - if (mViewModelItem) { mViewModelItem->setFolderViewItem(this); @@ -320,6 +337,7 @@ void LLFolderViewItem::refresh() // Very Expensive! // Can do a number of expensive checks, like checking active motions, wearables or friend list mLabelStyle = vmi.getLabelStyle(); + pLabelFont = nullptr; // refresh can be called from a coro, don't use getLabelFontForStyle, coro trips font list tread safety mLabelSuffix = utf8str_to_wstring(vmi.getLabelSuffix()); mSuffixFontBuffer.reset(); } @@ -346,6 +364,7 @@ void LLFolderViewItem::refreshSuffix() // Very Expensive! // Can do a number of expensive checks, like checking active motions, wearables or friend list mLabelStyle = vmi->getLabelStyle(); + pLabelFont = nullptr; mLabelSuffix = utf8str_to_wstring(vmi->getLabelSuffix()); } @@ -738,19 +757,17 @@ bool LLFolderViewItem::handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, return handled; } -void LLFolderViewItem::drawOpenFolderArrow(const Params& default_params, const LLUIColor& fg_color) +void LLFolderViewItem::drawOpenFolderArrow() { //--------------------------------------------------------------------------------// // Draw open folder arrow // - const S32 TOP_PAD = default_params.item_top_pad; if (hasVisibleChildren() || !isFolderComplete()) { - LLUIImage* arrow_image = default_params.folder_arrow_image; gl_draw_scaled_rotated_image( - mIndentation, getRect().getHeight() - mArrowSize - mTextPad - TOP_PAD, - mArrowSize, mArrowSize, mControlLabelRotation, arrow_image->getImage(), fg_color); + mIndentation, getRect().getHeight() - mArrowSize - mTextPad - sTopPad, + mArrowSize, mArrowSize, mControlLabelRotation, sFolderArrowImg->getImage(), sFgColor); } } @@ -766,7 +783,7 @@ void LLFolderViewItem::drawOpenFolderArrow(const Params& default_params, const L /*virtual*/ bool LLFolderViewItem::isFadeItem() { - LLClipboard& clipboard = LLClipboard::instance(); + static const LLClipboard& clipboard = LLClipboard::instance(); // Make it a 'simpleton'? if (mCutGeneration != clipboard.getGeneration()) { mCutGeneration = clipboard.getGeneration(); @@ -902,16 +919,14 @@ void LLFolderViewItem::draw() const bool show_context = (getRoot() ? getRoot()->getShowSelectionContext() : false); const bool filled = show_context || (getRoot() ? getRoot()->getParentPanel()->hasFocus() : false); // If we have keyboard focus, draw selection filled - const Params& default_params = LLUICtrlFactory::getDefaultParams<LLFolderViewItem>(); - const S32 TOP_PAD = default_params.item_top_pad; - - const LLFontGL* font = getLabelFontForStyle(mLabelStyle); + const LLFontGL* font = getLabelFont(); + S32 line_height = font->getLineHeight(); getViewModelItem()->update(); if (!mSingleFolderMode) { - drawOpenFolderArrow(default_params, sFgColor); + drawOpenFolderArrow(); } drawHighlight(show_context, filled, sHighlightBgColor, sFlashBgColor, sFocusOutlineColor, sMouseOverColor); @@ -920,18 +935,19 @@ void LLFolderViewItem::draw() // Draw open icon // const S32 icon_x = mIndentation + mArrowSize + mTextPad; + const S32 rect_height = getRect().getHeight(); if (!mIconOpen.isNull() && (llabs(mControlLabelRotation) > 80)) // For open folders { - mIconOpen->draw(icon_x, getRect().getHeight() - mIconOpen->getHeight() - TOP_PAD + 1); + mIconOpen->draw(icon_x, rect_height - mIconOpen->getHeight() - sTopPad + 1); } else if (mIcon) { - mIcon->draw(icon_x, getRect().getHeight() - mIcon->getHeight() - TOP_PAD + 1); + mIcon->draw(icon_x, rect_height - mIcon->getHeight() - sTopPad + 1); } if (mIconOverlay && getRoot()->showItemLinkOverlays()) { - mIconOverlay->draw(icon_x, getRect().getHeight() - mIcon->getHeight() - TOP_PAD + 1); + mIconOverlay->draw(icon_x, rect_height - mIcon->getHeight() - sTopPad + 1); } //--------------------------------------------------------------------------------// @@ -944,24 +960,22 @@ void LLFolderViewItem::draw() S32 filter_string_length = mViewModelItem->hasFilterStringMatch() ? (S32)mViewModelItem->getFilterStringSize() : 0; F32 right_x = 0; - F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; + F32 y = (F32)rect_height - line_height - (F32)mTextPad - (F32)sTopPad; F32 text_left = (F32)getLabelXPos(); LLWString combined_string = mLabel + mLabelSuffix; - const LLFontGL* suffix_font = getLabelFontForStyle(LLFontGL::NORMAL); S32 filter_offset = static_cast<S32>(mViewModelItem->getFilterStringOffset()); if (filter_string_length > 0) { - S32 bottom = getRect().getHeight() - font->getLineHeight() - 3 - TOP_PAD; - S32 top = getRect().getHeight() - TOP_PAD; - if(mLabelSuffix.empty() || (font == suffix_font)) + S32 bottom = rect_height - line_height - 3 - sTopPad; + S32 top = rect_height - sTopPad; + if(mLabelSuffix.empty() || (font == sSuffixFont)) { - S32 left = ll_round(text_left) + font->getWidth(combined_string.c_str(), 0, static_cast<S32>(mViewModelItem->getFilterStringOffset())) - 2; - S32 right = left + font->getWidth(combined_string.c_str(), static_cast<S32>(mViewModelItem->getFilterStringOffset()), filter_string_length) + 2; + S32 left = ll_round(text_left) + font->getWidth(combined_string.c_str(), 0, filter_offset) - 2; + S32 right = left + font->getWidth(combined_string.c_str(), filter_offset, filter_string_length) + 2; - LLUIImage* box_image = default_params.selection_image; - LLRect box_rect(left, top, right, bottom); - box_image->draw(box_rect, sFilterBGColor); + LLRect box_rect(left, top, right, bottom); + sSelectionImg->draw(box_rect, sFilterBGColor); } else { @@ -970,19 +984,17 @@ void LLFolderViewItem::draw() { S32 left = (S32)(ll_round(text_left) + font->getWidthF32(mLabel.c_str(), 0, llmin(filter_offset, (S32)mLabel.size()))) - 2; S32 right = left + (S32)font->getWidthF32(mLabel.c_str(), filter_offset, label_filter_length) + 2; - LLUIImage* box_image = default_params.selection_image; LLRect box_rect(left, top, right, bottom); - box_image->draw(box_rect, sFilterBGColor); + sSelectionImg->draw(box_rect, sFilterBGColor); } S32 suffix_filter_length = label_filter_length > 0 ? filter_string_length - label_filter_length : filter_string_length; if(suffix_filter_length > 0) { S32 suffix_offset = llmax(0, filter_offset - (S32)mLabel.size()); - S32 left = (S32)(ll_round(text_left) + font->getWidthF32(mLabel.c_str(), 0, static_cast<S32>(mLabel.size())) + suffix_font->getWidthF32(mLabelSuffix.c_str(), 0, suffix_offset)) - 2; - S32 right = left + (S32)suffix_font->getWidthF32(mLabelSuffix.c_str(), suffix_offset, suffix_filter_length) + 2; - LLUIImage* box_image = default_params.selection_image; + S32 left = (S32)(ll_round(text_left) + font->getWidthF32(mLabel.c_str(), 0, static_cast<S32>(mLabel.size())) + sSuffixFont->getWidthF32(mLabelSuffix.c_str(), 0, suffix_offset)) - 2; + S32 right = left + (S32)sSuffixFont->getWidthF32(mLabelSuffix.c_str(), suffix_offset, suffix_filter_length) + 2; LLRect box_rect(left, top, right, bottom); - box_image->draw(box_rect, sFilterBGColor); + sSelectionImg->draw(box_rect, sFilterBGColor); } } } @@ -1001,7 +1013,7 @@ void LLFolderViewItem::draw() // if (!mLabelSuffix.empty()) { - mSuffixFontBuffer.render(suffix_font, mLabelSuffix, 0, right_x, y, isFadeItem() ? color : sSuffixColor.get(), + mSuffixFontBuffer.render(sSuffixFont, mLabelSuffix, 0, right_x, y, isFadeItem() ? color : sSuffixColor.get(), LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, &right_x); } @@ -1011,10 +1023,10 @@ void LLFolderViewItem::draw() // if (filter_string_length > 0) { - if(mLabelSuffix.empty() || (font == suffix_font)) + if(mLabelSuffix.empty() || (font == sSuffixFont)) { F32 match_string_left = text_left + font->getWidthF32(combined_string.c_str(), 0, filter_offset + filter_string_length) - font->getWidthF32(combined_string.c_str(), filter_offset, filter_string_length); - F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; + F32 yy = (F32)rect_height - line_height - (F32)mTextPad - (F32)sTopPad; font->render(combined_string, filter_offset, match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, filter_string_length, S32_MAX, &right_x); @@ -1025,7 +1037,7 @@ void LLFolderViewItem::draw() if(label_filter_length > 0) { F32 match_string_left = text_left + font->getWidthF32(mLabel.c_str(), 0, filter_offset + label_filter_length) - font->getWidthF32(mLabel.c_str(), filter_offset, label_filter_length); - F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; + F32 yy = (F32)rect_height - line_height - (F32)mTextPad - (F32)sTopPad; font->render(mLabel, filter_offset, match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, label_filter_length, S32_MAX, &right_x); @@ -1035,9 +1047,9 @@ void LLFolderViewItem::draw() if(suffix_filter_length > 0) { S32 suffix_offset = llmax(0, filter_offset - (S32)mLabel.size()); - F32 match_string_left = text_left + font->getWidthF32(mLabel.c_str(), 0, static_cast<S32>(mLabel.size())) + suffix_font->getWidthF32(mLabelSuffix.c_str(), 0, suffix_offset + suffix_filter_length) - suffix_font->getWidthF32(mLabelSuffix.c_str(), suffix_offset, suffix_filter_length); - F32 yy = (F32)getRect().getHeight() - suffix_font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; - suffix_font->render(mLabelSuffix, suffix_offset, match_string_left, yy, sFilterTextColor, + F32 match_string_left = text_left + font->getWidthF32(mLabel.c_str(), 0, static_cast<S32>(mLabel.size())) + sSuffixFont->getWidthF32(mLabelSuffix.c_str(), 0, suffix_offset + suffix_filter_length) - sSuffixFont->getWidthF32(mLabelSuffix.c_str(), suffix_offset, suffix_filter_length); + F32 yy = (F32)rect_height - sSuffixFont->getLineHeight() - (F32)mTextPad - (F32)sTopPad; + sSuffixFont->render(mLabelSuffix, suffix_offset, match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, suffix_filter_length, S32_MAX, &right_x); } diff --git a/indra/llui/llfolderviewitem.h b/indra/llui/llfolderviewitem.h index 7ac28b1a8e..cc8a7d934c 100644 --- a/indra/llui/llfolderviewitem.h +++ b/indra/llui/llfolderviewitem.h @@ -135,7 +135,6 @@ protected: LLUIColor mFontHighlightColor; // For now assuming all colors are the same in derived classes. - static bool sColorSetInitialized; static LLUIColor sFgColor; static LLUIColor sFgDisabledColor; static LLUIColor sHighlightBgColor; @@ -158,6 +157,7 @@ protected: virtual void setFlashState(bool) { } static LLFontGL* getLabelFontForStyle(U8 style); + const LLFontGL* getLabelFont(); bool mIsSelected; @@ -297,7 +297,7 @@ public: // virtual void handleDropped(); virtual void draw(); - void drawOpenFolderArrow(const Params& default_params, const LLUIColor& fg_color); + void drawOpenFolderArrow(); void drawHighlight(bool showContent, bool hasKeyboardFocus, const LLUIColor& selectColor, const LLUIColor& flashColor, const LLUIColor& outlineColor, const LLUIColor& mouseOverColor); void drawLabel(const LLFontGL* font, const F32 x, const F32 y, const LLColor4& color, F32 &right_x); virtual bool handleDragAndDrop(S32 x, S32 y, MASK mask, bool drop, @@ -308,9 +308,14 @@ public: private: static std::map<U8, LLFontGL*> sFonts; // map of styles to fonts + static S32 sTopPad; + static LLUIImagePtr sFolderArrowImg; + static LLUIImagePtr sSelectionImg; + static LLFontGL* sSuffixFont; LLFontVertexBuffer mLabelFontBuffer; LLFontVertexBuffer mSuffixFontBuffer; + LLFontGL* pLabelFont{nullptr}; }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/indra/llui/llluafloater.cpp b/indra/llui/llluafloater.cpp new file mode 100644 index 0000000000..ccdadc6ae0 --- /dev/null +++ b/indra/llui/llluafloater.cpp @@ -0,0 +1,324 @@ +/** + * @file llluafloater.cpp + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, 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 "llluafloater.h" + +#include "fsyspath.h" +#include "llevents.h" + +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llscrolllistctrl.h" +#include "lltexteditor.h" + +const std::string LISTENER_NAME("LLLuaFloater"); + +std::set<std::string> EVENT_LIST = { + "commit", + "double_click", + "mouse_enter", + "mouse_leave", + "mouse_down", + "mouse_up", + "right_mouse_down", + "right_mouse_up", + "post_build", + "floater_close", + "keystroke" +}; + +LLLuaFloater::LLLuaFloater(const LLSD &key) : + LLFloater(key), + mDispatchListener(LLUUID::generateNewID().asString(), "action"), + mReplyPumpName(key["reply"].asString()), + mReqID(key) +{ + auto ctrl_lookup = [this](const LLSD &event, std::function<LLSD(LLUICtrl*,const LLSD&)> cb) + { + LLUICtrl *ctrl = getChild<LLUICtrl>(event["ctrl_name"].asString()); + if (!ctrl) + { + LL_WARNS("LuaFloater") << "Control not found: " << event["ctrl_name"] << LL_ENDL; + return LLSD(); + } + return cb(ctrl, event); + }; + + LLSD requiredParams = llsd::map("ctrl_name", LLSD(), "value", LLSD()); + mDispatchListener.add("set_enabled", "", [ctrl_lookup](const LLSD &event) + { + return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { ctrl->setEnabled(event["value"].asBoolean()); return LLSD(); }); + }, requiredParams); + mDispatchListener.add("set_visible", "", [ctrl_lookup](const LLSD &event) + { + return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { ctrl->setVisible(event["value"].asBoolean()); return LLSD(); }); + }, requiredParams); + mDispatchListener.add("set_value", "", [ctrl_lookup](const LLSD &event) + { + return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { ctrl->setValue(event["value"]); return LLSD(); }); + }, requiredParams); + + mDispatchListener.add("add_list_element", "", [this](const LLSD &event) + { + LLScrollListCtrl *ctrl = getChild<LLScrollListCtrl>(event["ctrl_name"].asString()); + if(ctrl) + { + LLSD element_data = event["value"]; + if (element_data.isArray()) + { + for (const auto &row : llsd::inArray(element_data)) + { + ctrl->addElement(row); + } + } + else + { + ctrl->addElement(element_data); + } + } + }, requiredParams); + + mDispatchListener.add("clear_list", "", [this](const LLSD &event) + { + LLScrollListCtrl *ctrl = getChild<LLScrollListCtrl>(event["ctrl_name"].asString()); + if(ctrl) + { + ctrl->deleteAllItems(); + } + }, llsd::map("ctrl_name", LLSD())); + + mDispatchListener.add("add_text", "", [this](const LLSD &event) + { + LLTextEditor *editor = getChild<LLTextEditor>(event["ctrl_name"].asString()); + if (editor) + { + editor->pasteTextWithLinebreaks(stringize(event["value"])); + editor->addLineBreakChar(true); + } + }, requiredParams); + + mDispatchListener.add("set_label", "", [this](const LLSD &event) + { + LLButton *btn = getChild<LLButton>(event["ctrl_name"].asString()); + if (btn) + { + btn->setLabel((event["value"]).asString()); + } + }, requiredParams); + + mDispatchListener.add("set_title", "", [this](const LLSD &event) + { + setTitle(event["value"].asString()); + }, llsd::map("value", LLSD())); + + mDispatchListener.add("get_value", "", [ctrl_lookup](const LLSD &event) + { + return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { return llsd::map("value", ctrl->getValue()); }); + }, llsd::map("ctrl_name", LLSD(), "reqid", LLSD())); + + mDispatchListener.add("get_selected_id", "", [this](const LLSD &event) + { + LLScrollListCtrl *ctrl = getChild<LLScrollListCtrl>(event["ctrl_name"].asString()); + if (!ctrl) + { + LL_WARNS("LuaFloater") << "Control not found: " << event["ctrl_name"] << LL_ENDL; + return LLSD(); + } + return llsd::map("value", ctrl->getCurrentID()); + }, llsd::map("ctrl_name", LLSD(), "reqid", LLSD())); +} + +LLLuaFloater::~LLLuaFloater() +{ + //post empty LLSD() to indicate done, in case it wasn't handled by the script after CLOSE_EVENT + post(LLSD()); +} + +bool LLLuaFloater::postBuild() +{ + for (LLView *view : *getChildList()) + { + LLUICtrl *ctrl = dynamic_cast<LLUICtrl*>(view); + if (ctrl) + { + LLSD data; + data["ctrl_name"] = view->getName(); + + ctrl->setCommitCallback([this, data](LLUICtrl *ctrl, const LLSD ¶m) + { + LLSD event(data); + event["value"] = ctrl->getValue(); + postEvent(event, "commit"); + }); + } + } + + //optional field to send additional specified events to the script + if (mKey.has("extra_events")) + { + //the first value is ctrl name, the second contains array of events to send + for (const auto &[name, data] : llsd::inMap(mKey["extra_events"])) + { + for (const auto &event : llsd::inArray(data)) + { + registerCallback(name, event); + } + } + } + + //send pump name to the script after the floater is built + postEvent(llsd::map("command_name", mDispatchListener.getPumpName()), "post_build"); + + return true; +} + +void LLLuaFloater::onClose(bool app_quitting) +{ + postEvent(llsd::map("app_quitting", app_quitting), "floater_close"); +} + +bool event_is(const std::string &event_name, const std::string &list_event) +{ + llassert(EVENT_LIST.find(list_event) != EVENT_LIST.end()); + return (event_name == list_event); +} + +void LLLuaFloater::registerCallback(const std::string &ctrl_name, const std::string &event) +{ + LLUICtrl *ctrl = getChild<LLUICtrl>(ctrl_name); + if (!ctrl) return; + + LLSD data; + data["ctrl_name"] = ctrl_name; + data["event"] = event; + + auto mouse_event_cb = [this, data](LLUICtrl *ctrl, const LLSD ¶m) { post(data); }; + + auto mouse_event_coords_cb = [this, data](LLUICtrl *ctrl, S32 x, S32 y, MASK mask) + { + LLSD event(data); + post(event.with("x", x).with("y", y)); + }; + + auto post_with_value = [this, data](LLSD value) + { + LLSD event(data); + post(event.with("value", value)); + }; + + if (event_is(event, "mouse_enter")) + { + ctrl->setMouseEnterCallback(mouse_event_cb); + } + else if (event_is(event, "mouse_leave")) + { + ctrl->setMouseLeaveCallback(mouse_event_cb); + } + else if (event_is(event, "mouse_down")) + { + ctrl->setMouseDownCallback(mouse_event_coords_cb); + } + else if (event_is(event, "mouse_up")) + { + ctrl->setMouseUpCallback(mouse_event_coords_cb); + } + else if (event_is(event, "right_mouse_down")) + { + ctrl->setRightMouseDownCallback(mouse_event_coords_cb); + } + else if (event_is(event, "right_mouse_up")) + { + ctrl->setRightMouseUpCallback(mouse_event_coords_cb); + } + else if (event_is(event, "double_click")) + { + LLScrollListCtrl *list = dynamic_cast<LLScrollListCtrl *>(ctrl); + if (list) + { + list->setDoubleClickCallback( [post_with_value, list](){ post_with_value(LLSD(list->getCurrentID())); }); + } + else + { + ctrl->setDoubleClickCallback(mouse_event_coords_cb); + } + } + else if (event_is(event, "keystroke")) + { + LLTextEditor* text_editor = dynamic_cast<LLTextEditor*>(ctrl); + if (text_editor) + { + text_editor->setKeystrokeCallback([post_with_value](LLTextEditor *editor) { post_with_value(editor->getValue()); }); + } + LLLineEditor* line_editor = dynamic_cast<LLLineEditor*>(ctrl); + if (line_editor) + { + line_editor->setKeystrokeCallback([post_with_value](LLLineEditor *editor, void* userdata) { post_with_value(editor->getValue()); }, NULL); + } + } + else + { + LL_WARNS("LuaFloater") << "Can't register callback for unknown event: " << event << " , control: " << ctrl_name << LL_ENDL; + } +} + +void LLLuaFloater::post(const LLSD &data) +{ + // send event data to the script signed with ["reqid"] key + LLSD stamped_data(data); + mReqID.stamp(stamped_data); + LLEventPumps::instance().obtain(mReplyPumpName).post(stamped_data); +} + +void LLLuaFloater::postEvent(LLSD data, const std::string &event_name) +{ + llassert(EVENT_LIST.find(event_name) != EVENT_LIST.end()); + post(data.with("event", event_name)); +} + +void LLLuaFloater::showLuaFloater(const LLSD &data) +{ + fsyspath fs_path(data["xml_path"].asString()); + std::string path = fs_path.lexically_normal().u8string(); + if (!fs_path.is_absolute()) + { + std::string lib_path = gDirUtilp->getExpandedFilename(LL_PATH_SCRIPTS, "lua"); + path = (fsyspath(lib_path) / path).u8string(); + } + + LLLuaFloater *floater = new LLLuaFloater(data); + floater->buildFromFile(path); + floater->openFloater(floater->getKey()); +} + +LLSD LLLuaFloater::getEventsData() +{ + LLSD event_data; + for (auto &it : EVENT_LIST) + { + event_data.append(it); + } + return event_data; +} diff --git a/indra/llui/llluafloater.h b/indra/llui/llluafloater.h new file mode 100644 index 0000000000..41132f926d --- /dev/null +++ b/indra/llui/llluafloater.h @@ -0,0 +1,54 @@ +/** + * @file llluafloater.h + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, 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_LLLUAFLOATER_H +#define LL_LLLUAFLOATER_H + +#include "llfloater.h" +#include "lleventdispatcher.h" +#include "llevents.h" + +class LLLuaFloater : public LLFloater +{ +public: + LLLuaFloater(const LLSD &key); + bool postBuild(); + virtual ~LLLuaFloater(); + + void registerCallback(const std::string &ctrl_name, const std::string &event); + void onClose(bool app_quitting); + + void post(const LLSD &data); + void postEvent(LLSD data, const std::string &event); + static void showLuaFloater(const LLSD &data); + static LLSD getEventsData(); + +private: + LLReqID mReqID; + LLDispatchListener mDispatchListener; + + std::string mReplyPumpName; +}; +#endif diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 69ffa9a94f..cc770ca90a 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -2625,7 +2625,9 @@ void LLMenuGL::insert( S32 position, LLView * ctrl, bool arrange /*= true*/ ) { LLMenuItemGL * item = dynamic_cast<LLMenuItemGL *>(ctrl); - if (NULL == item || position < 0 || position >= mItems.size()) + // If position == size(), std::advance() will return end() -- which is + // okay, because insert(end()) is the same as append(). + if (NULL == item || position < 0 || position > mItems.size()) { return; } diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h index 66f84393fe..88608b20ab 100644 --- a/indra/llui/llmenugl.h +++ b/indra/llui/llmenugl.h @@ -562,9 +562,6 @@ public: // add a context menu branch bool appendContextSubMenu(LLMenuGL *menu); - const LLFontGL *getFont() const { return mFont; } - - protected: void createSpilloverBranch(); void cleanupSpilloverBranch(); // Add the menu item to this menu. @@ -810,9 +807,10 @@ public: void resetMenuTrigger() { mAltKeyTrigger = false; } -private: // add a menu - this will create a drop down menu. - virtual bool appendMenu( LLMenuGL* menu ); + virtual bool appendMenu(LLMenuGL *menu); + +private: // rearrange the child rects so they fit the shape of the menu // bar. virtual void arrange( void ); @@ -948,16 +946,18 @@ public: LLUICtrl::EnableCallbackRegistry::currentRegistrar().add(name, boost::bind(&view_listener_t::handleEvent, listener, _2)); } - static void addCommit(view_listener_t* listener, const std::string& name) + typedef LLUICtrl::CommitCallbackInfo cb_info; + static void addCommit(view_listener_t *listener, const std::string &name, cb_info::EUntrustedCall handle_untrusted = cb_info::UNTRUSTED_ALLOW) { - LLUICtrl::CommitCallbackRegistry::currentRegistrar().add(name, boost::bind(&view_listener_t::handleEvent, listener, _2)); + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add(name, + cb_info([listener](LLUICtrl*, const LLSD& param){ return listener->handleEvent(param); }, handle_untrusted)); } - static void addMenu(view_listener_t* listener, const std::string& name) + static void addMenu(view_listener_t *listener, const std::string &name, cb_info::EUntrustedCall handle_untrusted = cb_info::UNTRUSTED_ALLOW) { // For now, add to both click and enable registries addEnable(listener, name); - addCommit(listener, name); + addCommit(listener, name, handle_untrusted); } static void cleanup() diff --git a/indra/llui/llnotifications.cpp b/indra/llui/llnotifications.cpp index cd80e7f63f..501ac26f9f 100644 --- a/indra/llui/llnotifications.cpp +++ b/indra/llui/llnotifications.cpp @@ -1243,7 +1243,7 @@ LLNotifications::LLNotifications() mIgnoreAllNotifications(false) { mListener.reset(new LLNotificationsListener(*this)); - LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Notification.Show", boost::bind(&LLNotifications::addFromCallback, this, _2)); + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Notification.Show", { boost::bind(&LLNotifications::addFromCallback, this, _2) }); // touch the instance tracker for notification channels, so that it will still be around in our destructor LLInstanceTracker<LLNotificationChannel, std::string>::instanceCount(); diff --git a/indra/llui/llnotifications.h b/indra/llui/llnotifications.h index 46286457cf..9b83da13ad 100644 --- a/indra/llui/llnotifications.h +++ b/indra/llui/llnotifications.h @@ -735,7 +735,7 @@ typedef std::multimap<std::string, LLNotificationPtr> LLNotificationMap; // all of the built-in tests should attach to the "Visible" channel // class LLNotificationChannelBase : - public LLEventTrackable, + public boost::signals2::trackable, public LLRefCount { LOG_CLASS(LLNotificationChannelBase); diff --git a/indra/llui/llnotificationslistener.cpp b/indra/llui/llnotificationslistener.cpp index 9c1fc27c51..dc12834b3f 100644 --- a/indra/llui/llnotificationslistener.cpp +++ b/indra/llui/llnotificationslistener.cpp @@ -204,7 +204,7 @@ void LLNotificationsListener::ignore(const LLSD& params) const } } -class LLNotificationsListener::Forwarder: public LLEventTrackable +class LLNotificationsListener::Forwarder: public boost::signals2::trackable { LOG_CLASS(LLNotificationsListener::Forwarder); public: @@ -213,8 +213,10 @@ public: mRespond(false) { // Connect to the specified channel on construction. Because - // LLEventTrackable is a base, we should automatically disconnect when - // destroyed. + // boost::signals2::trackable is a base, because we use boost::bind() + // below, and because connectPassedFilter() directly calls + // boost::signals2::signal::connect(), we should automatically + // disconnect when destroyed. LLNotificationChannelPtr channelptr(llnotifications.getChannel(channel)); if (channelptr) { @@ -252,10 +254,10 @@ void LLNotificationsListener::forward(const LLSD& params) if (! forward) { // This is a request to stop forwarding notifications on the specified - // channel. The rest of the params don't matter. - // Because mForwarders contains scoped_ptrs, erasing the map entry - // DOES delete the heap Forwarder object. Because Forwarder derives - // from LLEventTrackable, destroying it disconnects it from the + // channel. The rest of the params don't matter. Because mForwarders + // contains scoped_ptrs, erasing the map entry DOES delete the heap + // Forwarder object. Because Forwarder derives from + // boost::signals2::trackable, destroying it disconnects it from the // channel. mForwarders.erase(channel); return; diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp index 445377d3a2..ea2caaa1c0 100644 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -1975,7 +1975,7 @@ bool LLScrollListCtrl::handleRightMouseDown(S32 x, S32 y, MASK mask) // set up the callbacks for all of the avatar/group menu items // (N.B. callbacks don't take const refs as id is local scope) bool is_group = (mContextMenuType == MENU_GROUP); - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + ScopedRegistrarHelper registrar; registrar.add("Url.ShowProfile", boost::bind(&LLScrollListCtrl::showProfile, id, is_group)); registrar.add("Url.SendIM", boost::bind(&LLScrollListCtrl::sendIM, id)); registrar.add("Url.AddFriend", boost::bind(&LLScrollListCtrl::addFriend, id)); diff --git a/indra/llui/llstatbar.cpp b/indra/llui/llstatbar.cpp index 2693243eb1..62c0401869 100644 --- a/indra/llui/llstatbar.cpp +++ b/indra/llui/llstatbar.cpp @@ -460,7 +460,7 @@ void LLStatBar::draw() max_value = 0.f; gGL.color4f(1.f, 0.f, 0.f, 1.f); - gGL.begin( LLRender::QUADS ); + gGL.begin(LLRender::TRIANGLES); const S32 max_frame = llmin(num_frames, num_values); U32 num_samples = 0; for (S32 i = 1; i <= max_frame; i++) @@ -498,6 +498,9 @@ void LLStatBar::draw() gGL.vertex2f((F32)bar_rect.mRight - offset, max); gGL.vertex2f((F32)bar_rect.mRight - offset, min); gGL.vertex2f((F32)bar_rect.mRight - offset - 1, min); + + gGL.vertex2f((F32)bar_rect.mRight - offset, max); + gGL.vertex2f((F32)bar_rect.mRight - offset - 1, min); gGL.vertex2f((F32)bar_rect.mRight - offset - 1, max); } else @@ -505,7 +508,10 @@ void LLStatBar::draw() gGL.vertex2f(min, (F32)bar_rect.mBottom + offset + 1); gGL.vertex2f(min, (F32)bar_rect.mBottom + offset); gGL.vertex2f(max, (F32)bar_rect.mBottom + offset); - gGL.vertex2f(max, (F32)bar_rect.mBottom + offset + 1 ); + + gGL.vertex2f(min, (F32)bar_rect.mBottom + offset + 1); + gGL.vertex2f(max, (F32)bar_rect.mBottom + offset); + gGL.vertex2f(max, (F32)bar_rect.mBottom + offset + 1); } } gGL.end(); diff --git a/indra/llui/llstyle.cpp b/indra/llui/llstyle.cpp index df4b0ef6a0..f1d57a2273 100644 --- a/indra/llui/llstyle.cpp +++ b/indra/llui/llstyle.cpp @@ -43,8 +43,8 @@ LLStyle::Params::Params() image("image"), link_href("href"), is_link("is_link") -{} - +{ +} LLStyle::LLStyle(const LLStyle::Params& p) : mVisible(p.visible), @@ -57,14 +57,31 @@ LLStyle::LLStyle(const LLStyle::Params& p) mDropShadow(p.drop_shadow), mImagep(p.image()), mAlpha(p.alpha) -{} +{ +} + +LLStyle* LLStyle::makeCopy() const +{ + LLStyle* copy = new LLStyle(); + copy->mDropShadow = mDropShadow; + copy->mFontName = mFontName; + copy->mLink = mLink; + copy->mColor = mColor; + copy->mReadOnlyColor = mReadOnlyColor; + copy->mSelectedColor = mSelectedColor; + copy->mFont = mFont; + copy->mImagep = mImagep; + copy->mAlpha = mAlpha; + copy->mVisible = mVisible; + copy->mIsLink = mIsLink; + return copy; +} void LLStyle::setFont(const LLFontGL* font) { mFont = font; } - const LLFontGL* LLStyle::getFont() const { return mFont; diff --git a/indra/llui/llstyle.h b/indra/llui/llstyle.h index e506895de5..d3a50f99a5 100644 --- a/indra/llui/llstyle.h +++ b/indra/llui/llstyle.h @@ -33,6 +33,10 @@ #include "lluiimage.h" class LLFontGL; +class LLStyle; + +typedef LLPointer<LLStyle> LLStyleSP; +typedef LLPointer<const LLStyle> LLStyleConstSP; class LLStyle : public LLRefCount { @@ -52,6 +56,9 @@ public: Params(); }; LLStyle(const Params& p = Params()); + LLStyleSP clone() const { return makeCopy(); } + LLStyleConstSP cloneConst() const { return makeCopy(); } + public: const LLUIColor& getColor() const { return mColor; } void setColor(const LLUIColor &color) { mColor = color; } @@ -104,6 +111,7 @@ public: protected: ~LLStyle() = default; + LLStyle* makeCopy() const; private: std::string mFontName; @@ -118,7 +126,4 @@ private: bool mIsLink; }; -typedef LLPointer<LLStyle> LLStyleSP; -typedef LLPointer<const LLStyle> LLStyleConstSP; - #endif // LL_LLSTYLE_H diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index e2d31085c4..2a8b71055d 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -886,7 +886,7 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s } // shift remaining segments to right - for(;seg_iter != mSegments.end(); ++seg_iter) + for (;seg_iter != mSegments.end(); ++seg_iter) { LLTextSegmentPtr segmentp = *seg_iter; segmentp->setStart(segmentp->getStart() + insert_len); @@ -913,22 +913,29 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s // Insert special segments where necessary (insertSegment takes care of splitting normal text segments around them for us) if (mUseEmoji) { - LLStyleSP emoji_style; LLEmojiDictionary* ed = LLEmojiDictionary::instanceExists() ? LLEmojiDictionary::getInstance() : NULL; - for (S32 text_kitty = 0, text_len = static_cast<S32>(wstr.size()); text_kitty < text_len; text_kitty++) + for (std::size_t i = 0; i < wstr.size(); ++i) { - llwchar code = wstr[text_kitty]; + llwchar code = wstr[i]; bool isEmoji = ed ? ed->isEmoji(code) : LLStringOps::isEmoji(code); if (isEmoji) { - if (!emoji_style) + S32 new_seg_start = pos + (S32)i; + segment_set_t::iterator cur_seg_iter = getSegIterContaining(new_seg_start); + LLStyleSP new_style; + if (cur_seg_iter != mSegments.end()) // Should be 100% { - emoji_style = new LLStyle(getStyleParams()); - emoji_style->setFont(LLFontGL::getFontEmojiLarge()); + // Use font EmojiLarge but preserve the target font style + new_style = (*cur_seg_iter)->getStyle()->clone(); + U8 font_style = new_style->getFont()->getFontDesc().getStyle(); + new_style->setFont(LLFontGL::getFont(LLFontDescriptor("Emoji", "Large", font_style))); } - - S32 new_seg_start = pos + text_kitty; - insertSegment(new LLEmojiTextSegment(emoji_style, new_seg_start, new_seg_start + 1, *this)); + else // Very unlikely + { + new_style = new LLStyle(getStyleParams()); + new_style->setFont(LLFontGL::getFontEmojiLarge()); + } + insertSegment(new LLEmojiTextSegment(new_style, new_seg_start, new_seg_start + 1, *this)); } } } @@ -1059,14 +1066,18 @@ void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert) S32 old_segment_end = cur_segmentp->getEnd(); // split old at start point for new segment cur_segmentp->setEnd(segment_to_insert->getStart()); - // advance to next segment - // insert remainder of old segment - LLStyleConstSP sp = cur_segmentp->getStyle(); - LLTextSegmentPtr remainder_segment = new LLNormalTextSegment( sp, segment_to_insert->getStart(), old_segment_end, *this); - mSegments.insert(cur_seg_iter, remainder_segment); - remainder_segment->linkToDocument(this); // insert new segment before remainder of old segment mSegments.insert(cur_seg_iter, segment_to_insert); + // advance to next segment + // insert remainder of old segment + if (segment_to_insert->getEnd() < old_segment_end) + { + LLTextSegmentPtr remainder_segment = cur_segmentp->clone(*this); + remainder_segment->setStart(segment_to_insert->getEnd()); + remainder_segment->setEnd(old_segment_end); + mSegments.insert(cur_seg_iter, remainder_segment); + remainder_segment->linkToDocument(this); + } segment_to_insert->linkToDocument(this); // at this point, there will be two overlapping segments owning the text @@ -1080,7 +1091,7 @@ void LLTextBase::insertSegment(LLTextSegmentPtr segment_to_insert) // now delete/truncate remaining segments as necessary // cur_seg_iter points to segment before incoming segment - while(cur_seg_iter != mSegments.end()) + while (cur_seg_iter != mSegments.end()) { cur_segmentp = *cur_seg_iter; if (cur_segmentp == segment_to_insert) @@ -1966,7 +1977,7 @@ void LLTextBase::updateSegments() createDefaultSegment(); } -void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp ) const +void LLTextBase::getSegmentAndOffset(S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp) const { *seg_iter = getSegIterContaining(startpos); if (*seg_iter == mSegments.end()) @@ -1979,7 +1990,7 @@ void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::const_iterato } } -void LLTextBase::getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp ) +void LLTextBase::getSegmentAndOffset(S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp) { *seg_iter = getSegIterContaining(startpos); if (*seg_iter == mSegments.end()) @@ -1997,7 +2008,8 @@ LLTextBase::segment_set_t::iterator LLTextBase::getEditableSegIterContaining(S32 segment_set_t::iterator it = getSegIterContaining(index); segment_set_t::iterator orig_it = it; - if (it == mSegments.end()) return it; + if (it == mSegments.end()) + return it; if (!(*it)->canEdit() && index == (*it)->getStart() @@ -2009,6 +2021,7 @@ LLTextBase::segment_set_t::iterator LLTextBase::getEditableSegIterContaining(S32 return it; } } + return orig_it; } @@ -2016,7 +2029,8 @@ LLTextBase::segment_set_t::const_iterator LLTextBase::getEditableSegIterContaini { segment_set_t::const_iterator it = getSegIterContaining(index); segment_set_t::const_iterator orig_it = it; - if (it == mSegments.end()) return it; + if (it == mSegments.end()) + return it; if (!(*it)->canEdit() && index == (*it)->getStart() @@ -2028,6 +2042,7 @@ LLTextBase::segment_set_t::const_iterator LLTextBase::getEditableSegIterContaini return it; } } + return orig_it; } @@ -2036,7 +2051,10 @@ LLTextBase::segment_set_t::iterator LLTextBase::getSegIterContaining(S32 index) static LLPointer<LLIndexSegment> index_segment = new LLIndexSegment(); // when there are no segments, we return the end iterator, which must be checked by caller - if (mSegments.size() <= 1) { return mSegments.begin(); } + if (mSegments.size() <= 1) + { + return mSegments.begin(); + } index_segment->setStart(index); index_segment->setEnd(index); @@ -2049,7 +2067,10 @@ LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 i static LLPointer<LLIndexSegment> index_segment = new LLIndexSegment(); // when there are no segments, we return the end iterator, which must be checked by caller - if (mSegments.size() <= 1) { return mSegments.begin(); } + if (mSegments.size() <= 1) + { + return mSegments.begin(); + } index_segment->setStart(index); index_segment->setEnd(index); @@ -2058,7 +2079,7 @@ LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 i } // Finds the text segment (if any) at the give local screen position -LLTextSegmentPtr LLTextBase::getSegmentAtLocalPos( S32 x, S32 y, bool hit_past_end_of_line) +LLTextSegmentPtr LLTextBase::getSegmentAtLocalPos(S32 x, S32 y, bool hit_past_end_of_line) { // Find the cursor position at the requested local screen position S32 offset = getDocIndexFromLocalCoord( x, y, false, hit_past_end_of_line); @@ -2067,10 +2088,8 @@ LLTextSegmentPtr LLTextBase::getSegmentAtLocalPos( S32 x, S32 y, bool hit_past_e { return *seg_iter; } - else - { - return LLTextSegmentPtr(); - } + + return LLTextSegmentPtr(); } void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) @@ -2078,7 +2097,7 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) // work out the XUI menu file to use for this url LLUrlMatch match; std::string url = in_url; - if (! LLUrlRegistry::instance().findUrl(url, match)) + if (!LLUrlRegistry::instance().findUrl(url, match)) { return; } @@ -2091,7 +2110,7 @@ void LLTextBase::createUrlContextMenu(S32 x, S32 y, const std::string &in_url) // set up the callbacks for all of the potential menu items, N.B. we // don't use const ref strings in callbacks in case url goes out of scope - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + CommitRegistrarHelper registrar(LLUICtrl::CommitCallbackRegistry::currentRegistrar()); registrar.add("Url.Open", boost::bind(&LLUrlAction::openURL, url)); registrar.add("Url.OpenInternal", boost::bind(&LLUrlAction::openURLInternal, url)); registrar.add("Url.OpenExternal", boost::bind(&LLUrlAction::openURLExternal, url)); @@ -2192,10 +2211,8 @@ static LLUIImagePtr image_from_icon_name(const std::string& icon_name) { return LLUI::getUIImageByID( LLUUID(icon_name) ); } - else - { - return LLUI::getUIImage(icon_name); - } + + return LLUI::getUIImage(icon_name); } @@ -2205,17 +2222,17 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para LLStyle::Params style_params(getStyleParams()); style_params.overwriteFrom(input_params); - S32 part = (S32)LLTextParser::WHOLE; + LLTextParser::EHighlightPosition part = LLTextParser::WHOLE; if (mParseHTML && !style_params.is_link) // Don't search for URLs inside a link segment (STORM-358). { - S32 start=0,end=0; + U32 next = 0; LLUrlMatch match; std::string text = new_text; while (LLUrlRegistry::instance().findUrl(text, match, boost::bind(&LLTextBase::replaceUrl, this, _1, _2, _3), isContentTrusted() || mAlwaysShowIcons)) { - start = match.getStart(); - end = match.getEnd()+1; + U32 start = match.getStart(); + next = match.getEnd() + 1; LLStyle::Params link_params(style_params); link_params.overwriteFrom(match.getStyle()); @@ -2223,16 +2240,16 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para // output the text before the Url if (start > 0) { - if (part == (S32)LLTextParser::WHOLE || - part == (S32)LLTextParser::START) + if (part == LLTextParser::WHOLE || + part == LLTextParser::START) { - part = (S32)LLTextParser::START; + part = LLTextParser::START; } else { - part = (S32)LLTextParser::MIDDLE; + part = LLTextParser::MIDDLE; } - std::string subtext=text.substr(0,start); + std::string subtext = text.substr(0, start); appendAndHighlightText(subtext, part, style_params); } @@ -2244,14 +2261,8 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para } // output the styled Url - appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.underlineOnHoverOnly()); - bool tooltip_required = !match.getTooltip().empty(); - - // set the tooltip for the Url label - if (tooltip_required) - { - setLastSegmentToolTip(match.getTooltip()); - } + appendAndHighlightTextImpl(match.getLabel(), part, link_params, match.underlineOnHoverOnly(), match.getTooltip()); + bool tooltip_required = !match.getTooltip().empty(); // show query part of url with gray color only for LLUrlEntryHTTP url entries std::string label = match.getQuery(); @@ -2259,31 +2270,27 @@ void LLTextBase::appendTextImpl(const std::string &new_text, const LLStyle::Para { link_params.color = LLColor4::grey; link_params.readonly_color = LLColor4::grey; - appendAndHighlightTextImpl(label, part, link_params, match.underlineOnHoverOnly()); - - // set the tooltip for the query part of url - if (tooltip_required) - { - setLastSegmentToolTip(match.getTooltip()); - } + appendAndHighlightTextImpl(label, part, link_params, match.underlineOnHoverOnly(), match.getTooltip()); } - // move on to the rest of the text after the Url - if (end < (S32)text.length()) - { - text = text.substr(end,text.length() - end); - end=0; - part=(S32)LLTextParser::END; - } - else - { + if (next >= text.length()) break; - } + + // move on to the rest of the text after the Url + text = text.substr(next, text.length() - next); + next = 0; + part = LLTextParser::END; } - if (part != (S32)LLTextParser::WHOLE) - part=(S32)LLTextParser::END; - if (end < (S32)text.length()) + + if (part != LLTextParser::WHOLE) + { + part = LLTextParser::END; + } + + if (next < text.length()) + { appendAndHighlightText(text, part, style_params); + } } else { @@ -2307,7 +2314,7 @@ void LLTextBase::appendText(const std::string &new_text, bool prepend_newline, c if (new_text.empty()) return; - if(prepend_newline) + if (prepend_newline) appendLineBreakSegment(input_params); appendTextImpl(new_text,input_params); } @@ -2365,9 +2372,38 @@ S32 LLTextBase::removeFirstLine() removeStringNoUndo(0, length); return length; } + return 0; } +// virtual +void LLTextBase::copyContents(const LLTextBase* source) +{ + llassert(source); + if (!source) + return; + + beforeValueChange(); + deselect(); + + mSegments.clear(); + for (const LLTextSegmentPtr& segp : source->mSegments) + { + mSegments.emplace(segp->clone(*this)); + } + + mLineInfoList.clear(); + for (const line_info& li : mLineInfoList) + { + mLineInfoList.push_back(line_info(li)); + } + + getViewModel()->setDisplay(source->getViewModel()->getDisplay()); + + onValueChange(0, getLength()); + needsReflow(); +} + void LLTextBase::appendLineBreakSegment(const LLStyle::Params& style_params) { segment_vec_t segments; @@ -2379,10 +2415,11 @@ void LLTextBase::appendLineBreakSegment(const LLStyle::Params& style_params) void LLTextBase::appendImageSegment(const LLStyle::Params& style_params) { - if(getPlainText()) + if (getPlainText()) { return; } + segment_vec_t segments; LLStyleConstSP sp(new LLStyle(style_params)); segments.push_back(new LLImageTextSegment(sp, getLength(),*this)); @@ -2399,7 +2436,8 @@ void LLTextBase::appendWidget(const LLInlineViewSegment::Params& params, const s insertStringNoUndo(getLength(), widget_wide_text, &segments); } -void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only) +void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, LLTextParser::EHighlightPosition highlight_part, + const LLStyle::Params& style_params, bool underline_on_hover_only, std::string tooltip) { // Save old state S32 selection_start = mSelectionStart; @@ -2417,7 +2455,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig { LLStyle::Params highlight_params(style_params); - auto pieces = LLTextParser::instance().parsePartialLineHighlights(new_text, highlight_params.color, (LLTextParser::EHighlightPosition)highlight_part); + auto pieces = LLTextParser::instance().parsePartialLineHighlights(new_text, highlight_params.color, highlight_part); for (S32 i = 0; i < pieces.size(); i++) { const auto& piece_pair = pieces[i]; @@ -2439,8 +2477,13 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig { segmentp = new LLNormalTextSegment(sp, cur_length, cur_length + static_cast<S32>(wide_text.size()), *this); } - segment_vec_t segments; - segments.push_back(segmentp); + + if (!tooltip.empty()) + { + segmentp->setToolTip(tooltip); + } + + segment_vec_t segments = { segmentp }; insertStringNoUndo(cur_length, wide_text, &segments); } } @@ -2449,22 +2492,28 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig LLWString wide_text; wide_text = utf8str_to_wstring(new_text); - segment_vec_t segments; S32 segment_start = old_length; S32 segment_end = old_length + static_cast<S32>(wide_text.size()); LLStyleConstSP sp(new LLStyle(style_params)); + LLTextSegmentPtr segmentp; if (underline_on_hover_only || mSkipLinkUnderline) { LLStyle::Params normal_style_params(style_params); normal_style_params.font.style("NORMAL"); LLStyleConstSP normal_sp(new LLStyle(normal_style_params)); - segments.push_back(new LLOnHoverChangeableTextSegment(sp, normal_sp, segment_start, segment_end, *this)); + segmentp = new LLOnHoverChangeableTextSegment(sp, normal_sp, segment_start, segment_end, *this); } else { - segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this)); + segmentp = new LLNormalTextSegment(sp, segment_start, segment_end, *this); } + if (!tooltip.empty()) + { + segmentp->setToolTip(tooltip); + } + + segment_vec_t segments = { segmentp }; insertStringNoUndo(getLength(), wide_text, &segments); } @@ -2487,7 +2536,9 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig } } -void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only) +void LLTextBase::appendAndHighlightText(const std::string &new_text, + LLTextParser::EHighlightPosition highlight_part, + const LLStyle::Params& style_params, bool underline_on_hover_only) { if (new_text.empty()) { @@ -2499,13 +2550,15 @@ void LLTextBase::appendAndHighlightText(const std::string &new_text, S32 highlig while (pos != std::string::npos) { - if (pos != start) + if (pos > start) { - std::string str = std::string(new_text,start,pos-start); + std::string str = std::string(new_text, start, pos - start); appendAndHighlightTextImpl(str, highlight_part, style_params, underline_on_hover_only); } appendLineBreakSegment(style_params); - start = pos+1; + start = pos + 1; + if (start >= new_text.length()) + return; pos = new_text.find("\n", start); } @@ -3231,7 +3284,8 @@ boost::signals2::connection LLTextBase::setIsObjectBlockedCallback(const is_bloc // LLTextSegment::~LLTextSegment() -{} +{ +} bool LLTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { width = 0; height = 0; return false; } bool LLTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const @@ -3568,12 +3622,27 @@ void LLNormalTextSegment::setToolTip(const std::string& tooltip) // we cannot replace a keyword tooltip that's loaded from a file if (mToken) { - LL_WARNS() << "LLTextSegment::setToolTip: cannot replace keyword tooltip." << LL_ENDL; + LL_WARNS() << "Cannot replace keyword tooltip." << LL_ENDL; return; } mTooltip = tooltip; } +LLStyleConstSP LLNormalTextSegment::cloneStyle(LLTextBase& target, const LLStyle* source) const +{ + return (&target == &mEditor) ? mStyle : mStyle->cloneConst(); +} + +// virtual +LLTextSegmentPtr LLNormalTextSegment::clone(LLTextBase& target) const +{ + LLStyleConstSP sp(cloneStyle(target, mStyle)); + LLNormalTextSegment* copy = new LLNormalTextSegment(sp, mStart, mEnd, target); + copy->mTooltip = mTooltip; + return copy; +} + +// virtual bool LLNormalTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { height = 0; @@ -3701,6 +3770,13 @@ LLLabelTextSegment::LLLabelTextSegment( const LLUIColor& color, S32 start, S32 e { } +// virtual +LLTextSegmentPtr LLLabelTextSegment::clone(LLTextBase& target) const +{ + LLStyleConstSP sp(cloneStyle(target, mStyle)); + return new LLLabelTextSegment(sp, mStart, mEnd, target); +} + /*virtual*/ const LLWString& LLLabelTextSegment::getWText() const { @@ -3725,6 +3801,13 @@ LLEmojiTextSegment::LLEmojiTextSegment(const LLUIColor& color, S32 start, S32 en { } +// virtual +LLTextSegmentPtr LLEmojiTextSegment::clone(LLTextBase& target) const +{ + LLStyleConstSP sp(cloneStyle(target, mStyle)); + return new LLEmojiTextSegment(sp, mStart, mEnd, target); +} + bool LLEmojiTextSegment::handleToolTip(S32 x, S32 y, MASK mask) { if (mTooltip.empty()) @@ -3748,6 +3831,14 @@ LLOnHoverChangeableTextSegment::LLOnHoverChangeableTextSegment( LLStyleConstSP s mHoveredStyle(style), mNormalStyle(normal_style){} +// virtual +LLTextSegmentPtr LLOnHoverChangeableTextSegment::clone(LLTextBase& target) const +{ + LLStyleConstSP hsp(cloneStyle(target, mHoveredStyle)); + LLStyleConstSP nsp(cloneStyle(target, mNormalStyle)); + return new LLOnHoverChangeableTextSegment(hsp, nsp, mStart, mEnd, target); +} + /*virtual*/ F32 LLOnHoverChangeableTextSegment::draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) { @@ -3787,6 +3878,13 @@ LLInlineViewSegment::~LLInlineViewSegment() mView->die(); } +// virtual +LLTextSegmentPtr LLInlineViewSegment::clone(LLTextBase& target) const +{ + llassert_always_msg(false, "NOT SUPPORTED"); + return nullptr; +} + bool LLInlineViewSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { if (first_char == 0 && num_chars == 0) @@ -3874,6 +3972,14 @@ LLLineBreakTextSegment::LLLineBreakTextSegment(LLStyleConstSP style,S32 pos):LLT LLLineBreakTextSegment::~LLLineBreakTextSegment() { } + +// virtual +LLTextSegmentPtr LLLineBreakTextSegment::clone(LLTextBase& target) const +{ + LLLineBreakTextSegment* copy = new LLLineBreakTextSegment(mStart); + copy->mFontHeight = mFontHeight; + return copy; +} bool LLLineBreakTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { width = 0; @@ -3901,8 +4007,16 @@ LLImageTextSegment::~LLImageTextSegment() { } +// virtual +LLTextSegmentPtr LLImageTextSegment::clone(LLTextBase& target) const +{ + LLStyleConstSP sp((&target == &mEditor) ? mStyle : mStyle->cloneConst()); + return new LLImageTextSegment(sp, mStart, target); +} + static const S32 IMAGE_HPAD = 3; +// virtual bool LLImageTextSegment::getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { width = 0; diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index eb4697da15..b3fde84f5f 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -35,6 +35,7 @@ #include "llstyle.h" #include "llkeywords.h" #include "llpanel.h" +#include "lltextparser.h" #include <string> #include <vector> @@ -45,6 +46,7 @@ class LLScrollContainer; class LLContextMenu; class LLUrlMatch; +class LLTextBase; /// /// A text segment is used to specify a subsection of a text string @@ -62,6 +64,8 @@ public: mEnd(end) {} virtual ~LLTextSegment(); + virtual LLTextSegmentPtr clone(LLTextBase& terget) const { return new LLTextSegment(mStart, mEnd); } + bool getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const; virtual bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; @@ -128,6 +132,8 @@ public: LLNormalTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ); LLNormalTextSegment( const LLUIColor& color, S32 start, S32 end, LLTextBase& editor, bool is_visible = true); virtual ~LLNormalTextSegment(); + LLStyleConstSP cloneStyle(LLTextBase& target, const LLStyle* source) const; + /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; /*virtual*/ S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const; @@ -180,6 +186,7 @@ class LLLabelTextSegment : public LLNormalTextSegment public: LLLabelTextSegment( LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor ); LLLabelTextSegment( const LLUIColor& color, S32 start, S32 end, LLTextBase& editor, bool is_visible = true); + /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; protected: @@ -194,6 +201,7 @@ class LLEmojiTextSegment : public LLNormalTextSegment public: LLEmojiTextSegment(LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor); LLEmojiTextSegment(const LLUIColor& color, S32 start, S32 end, LLTextBase& editor, bool is_visible = true); + /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const override; bool canEdit() const override { return false; } bool handleToolTip(S32 x, S32 y, MASK mask) override; @@ -204,6 +212,7 @@ class LLOnHoverChangeableTextSegment : public LLNormalTextSegment { public: LLOnHoverChangeableTextSegment( LLStyleConstSP style, LLStyleConstSP normal_style, S32 start, S32 end, LLTextBase& editor ); + /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); /*virtual*/ bool handleHover(S32 x, S32 y, MASK mask); protected: @@ -218,6 +227,7 @@ class LLIndexSegment : public LLTextSegment { public: LLIndexSegment() : LLTextSegment(0, 0) {} + /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const { return new LLIndexSegment(); } }; class LLInlineViewSegment : public LLTextSegment @@ -235,6 +245,8 @@ public: LLInlineViewSegment(const Params& p, S32 start, S32 end); ~LLInlineViewSegment(); + /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; /*virtual*/ void updateLayout(const class LLTextBase& editor); @@ -256,9 +268,10 @@ class LLLineBreakTextSegment : public LLTextSegment { public: - LLLineBreakTextSegment(LLStyleConstSP style,S32 pos); + LLLineBreakTextSegment(LLStyleConstSP style, S32 pos); LLLineBreakTextSegment(S32 pos); ~LLLineBreakTextSegment(); + /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const; F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); @@ -270,17 +283,19 @@ private: class LLImageTextSegment : public LLTextSegment { public: - LLImageTextSegment(LLStyleConstSP style,S32 pos,class LLTextBase& editor); + LLImageTextSegment(LLStyleConstSP style, S32 pos,class LLTextBase& editor); ~LLImageTextSegment(); - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; - S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 char_offset, S32 max_chars, S32 line_ind) const; - F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); + /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const; + + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const; + /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 char_offset, S32 max_chars, S32 line_ind) const; + /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect); /*virtual*/ bool handleToolTip(S32 x, S32 y, MASK mask); /*virtual*/ void setToolTip(const std::string& tooltip); private: - class LLTextBase& mEditor; + LLTextBase& mEditor; LLStyleConstSP mStyle; protected: @@ -510,6 +525,7 @@ public: const LLFontGL* getFont() const override { return mFont; } + virtual void copyContents(const LLTextBase* source); virtual void appendLineBreakSegment(const LLStyle::Params& style_params); virtual void appendImageSegment(const LLStyle::Params& style_params); virtual void appendWidget(const LLInlineViewSegment::Params& params, const std::string& text, bool allow_undo); @@ -607,16 +623,19 @@ protected: void drawText(); // modify contents - S32 insertStringNoUndo(S32 pos, const LLWString &wstr, segment_vec_t* segments = NULL); // returns num of chars actually inserted + S32 insertStringNoUndo(S32 pos, const LLWString &wstr, + segment_vec_t* segments = NULL); // returns num of chars actually inserted S32 removeStringNoUndo(S32 pos, S32 length); S32 overwriteCharNoUndo(S32 pos, llwchar wc); - void appendAndHighlightText(const std::string &new_text, S32 highlight_part, const LLStyle::Params& stylep, bool underline_on_hover_only = false); + void appendAndHighlightText(const std::string &new_text, + LLTextParser::EHighlightPosition highlight_part, + const LLStyle::Params& stylep, bool underline_on_hover_only = false); // manage segments - void getSegmentAndOffset( S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp ) const; - void getSegmentAndOffset( S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp ); - LLTextSegmentPtr getSegmentAtLocalPos( S32 x, S32 y, bool hit_past_end_of_line = true); + void getSegmentAndOffset(S32 startpos, segment_set_t::const_iterator* seg_iter, S32* offsetp) const; + void getSegmentAndOffset(S32 startpos, segment_set_t::iterator* seg_iter, S32* offsetp); + LLTextSegmentPtr getSegmentAtLocalPos(S32 x, S32 y, bool hit_past_end_of_line = true); segment_set_t::iterator getEditableSegIterContaining(S32 index); segment_set_t::const_iterator getEditableSegIterContaining(S32 index) const; segment_set_t::iterator getSegIterContaining(S32 index); @@ -658,8 +677,9 @@ protected: // avatar names are looked up. void replaceUrl(const std::string &url, const std::string &label, const std::string& icon); - void appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params = LLStyle::Params()); - void appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only = false); + void appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params = LLStyle::Params()); + void appendAndHighlightTextImpl(const std::string &new_text, LLTextParser::EHighlightPosition highlight_part, + const LLStyle::Params& style_params, bool underline_on_hover_only, std::string tooltip = LLStringUtil::null); S32 normalizeUri(std::string& uri); protected: diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index 3537c764b9..ecac800def 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -1131,7 +1131,7 @@ void LLTextEditor::removeChar() // Add a single character to the text S32 LLTextEditor::addChar(S32 pos, llwchar wc) { - if ((wstring_utf8_length(getWText()) + wchar_utf8_length(wc)) > mMaxTextByteLength) + if ( (wstring_utf8_length( getWText() ) + wchar_utf8_length( wc )) > mMaxTextByteLength) { LLUI::getInstance()->reportBadKeystroke(); return 0; @@ -1166,12 +1166,12 @@ S32 LLTextEditor::addChar(S32 pos, llwchar wc) void LLTextEditor::addChar(llwchar wc) { - if (!getEnabled()) + if( !getEnabled() ) { return; } - if (hasSelection()) + if( hasSelection() ) { deleteSelection(true); } @@ -1594,8 +1594,7 @@ void LLTextEditor::cleanStringForPaste(LLWString & clean_string) } } - -void LLTextEditor::pasteTextWithLinebreaks(LLWString & clean_string) +void LLTextEditor::pasteTextWithLinebreaksImpl(const LLWString & clean_string) { std::basic_string<llwchar>::size_type start = 0; std::basic_string<llwchar>::size_type pos = clean_string.find('\n',start); diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index e9e7070414..fab46a00e3 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -34,6 +34,7 @@ #include "llstyle.h" #include "lleditmenuhandler.h" #include "llviewborder.h" // for params +#include "llstring.h" #include "lltextbase.h" #include "lltextvalidate.h" @@ -249,7 +250,9 @@ protected: // Undoable operations void addChar(llwchar c); // at mCursorPos S32 addChar(S32 pos, llwchar wc); +public: void addLineBreakChar(bool group_together = false); +protected: S32 overwriteChar(S32 pos, llwchar wc); void removeChar(); S32 removeChar(S32 pos); @@ -305,8 +308,16 @@ private: // void pasteHelper(bool is_primary); void cleanStringForPaste(LLWString & clean_string); - void pasteTextWithLinebreaks(LLWString & clean_string); +public: + template <typename STRINGTYPE> + void pasteTextWithLinebreaks(const STRINGTYPE& clean_string) + { + pasteTextWithLinebreaksImpl(ll_convert(clean_string)); + } + void pasteTextWithLinebreaksImpl(const LLWString & clean_string); + +private: void onKeyStroke(); // Concrete TextCmd sub-classes used by the LLTextEditor base class diff --git a/indra/llui/lltextvalidate.cpp b/indra/llui/lltextvalidate.cpp index 9a087d8230..84f8b9daf0 100644 --- a/indra/llui/lltextvalidate.cpp +++ b/indra/llui/lltextvalidate.cpp @@ -31,6 +31,7 @@ #include "lltextvalidate.h" #include "llnotificationsutil.h" +#include "lltimer.h" #include "lltrans.h" #include "llresmgr.h" // for LLLocale diff --git a/indra/llui/lltoolbar.cpp b/indra/llui/lltoolbar.cpp index 5955a28fa3..6e6e332632 100644 --- a/indra/llui/lltoolbar.cpp +++ b/indra/llui/lltoolbar.cpp @@ -144,7 +144,7 @@ void LLToolBar::createContextMenu() { // Setup bindings specific to this instance for the context menu options - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar commit_reg; + CommitRegistrarHelper commit_reg(LLUICtrl::CommitCallbackRegistry::currentRegistrar()); commit_reg.add("Toolbars.EnableSetting", boost::bind(&LLToolBar::onSettingEnable, this, _2)); commit_reg.add("Toolbars.RemoveSelectedCommand", boost::bind(&LLToolBar::onRemoveSelectedCommand, this)); diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp index e36dae3955..b4299ae8e5 100644 --- a/indra/llui/llui.cpp +++ b/indra/llui/llui.cpp @@ -169,8 +169,7 @@ mHelpImpl(NULL) LLFontGL::sShadowColor = LLUIColorTable::instance().getColor("ColorDropShadow"); - LLUICtrl::CommitCallbackRegistry::Registrar& reg = LLUICtrl::CommitCallbackRegistry::defaultRegistrar(); - + LLUICtrl::CommitRegistrarHelper reg(LLUICtrl::CommitCallbackRegistry::defaultRegistrar()); // Callbacks for associating controls with floater visibility: reg.add("Floater.Toggle", [](LLUICtrl* ctrl, const LLSD& param) -> void { LLFloaterReg::toggleInstance(param.asStringRef()); }); reg.add("Floater.ToggleOrBringToFront", [](LLUICtrl* ctrl, const LLSD& param) -> void { LLFloaterReg::toggleInstanceOrBringToFront(param.asStringRef()); }); diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index cbabb5a933..e3e8130f51 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -220,10 +220,10 @@ void LLUICtrl::initFromParams(const Params& p) } else { - commit_callback_t* initfunc = (CommitCallbackRegistry::getValue(p.init_callback.function_name)); - if (initfunc) + LLUICtrl::CommitCallbackInfo *info = (CommitCallbackRegistry::getValue(p.init_callback.function_name)); + if (info && info->callback_func) { - (*initfunc)(this, p.init_callback.parameter); + (info->callback_func)(this, p.init_callback.parameter); } } } @@ -283,13 +283,13 @@ LLUICtrl::commit_signal_t::slot_type LLUICtrl::initCommitCallback(const CommitCa { std::string function_name = cb.function_name; setFunctionName(function_name); - commit_callback_t* func = (CommitCallbackRegistry::getValue(function_name)); - if (func) + LLUICtrl::CommitCallbackInfo *info = (CommitCallbackRegistry::getValue(function_name)); + if (info && info->callback_func) { if (cb.parameter.isProvided()) - return boost::bind((*func), _1, cb.parameter); + return boost::bind((info->callback_func), _1, cb.parameter); else - return commit_signal_t::slot_type(*func); + return commit_signal_t::slot_type(info->callback_func); } else if (!function_name.empty()) { diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h index 8cd9950917..2c73ff6b57 100644 --- a/indra/llui/lluictrl.h +++ b/indra/llui/lluictrl.h @@ -274,11 +274,56 @@ public: template <typename F, typename DERIVED> class CallbackRegistry : public LLRegistrySingleton<std::string, F, DERIVED > {}; + struct CommitCallbackInfo + { + enum EUntrustedCall + { + UNTRUSTED_ALLOW, + UNTRUSTED_BLOCK, + UNTRUSTED_THROTTLE + }; + + CommitCallbackInfo(commit_callback_t func = {}, EUntrustedCall handle_untrusted = UNTRUSTED_ALLOW) : + callback_func(func), + handle_untrusted(handle_untrusted) + { + } - class CommitCallbackRegistry : public CallbackRegistry<commit_callback_t, CommitCallbackRegistry> + public: + commit_callback_t callback_func; + EUntrustedCall handle_untrusted; + }; + typedef LLUICtrl::CommitCallbackInfo cb_info; + class CommitCallbackRegistry : public CallbackRegistry<CommitCallbackInfo, CommitCallbackRegistry> { LLSINGLETON_EMPTY_CTOR(CommitCallbackRegistry); }; + + class CommitRegistrarHelper + { + public: + CommitRegistrarHelper(LLUICtrl::CommitCallbackRegistry::Registrar ®istrar) : mRegistrar(registrar) {} + + template <typename... ARGS> void add(const std::string &name, ARGS &&...args) + { + mRegistrar.add(name, {std::forward<ARGS>(args)...}); + } + private: + LLUICtrl::CommitCallbackRegistry::Registrar &mRegistrar; + }; + + class ScopedRegistrarHelper + { + public: + template <typename... ARGS> void add(const std::string &name, ARGS &&...args) + { + mRegistrar.add(name, {std::forward<ARGS>(args)...}); + } + + private: + LLUICtrl::CommitCallbackRegistry::ScopedRegistrar mRegistrar; + }; + // the enable callback registry is also used for visiblity callbacks class EnableCallbackRegistry : public CallbackRegistry<enable_callback_t, EnableCallbackRegistry> { diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index 7d6c937b85..0206e46b57 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -1370,8 +1370,10 @@ void LLView::drawDebugRect() y = rect_height - LINE_HEIGHT * (depth % lines + 1); - std::string debug_text = llformat("%s (%d x %d)", getName().c_str(), - debug_rect.getWidth(), debug_rect.getHeight()); + std::string debug_text = llformat("%s [%d, %d] + (%d x %d) = [%d, %d]", getName().c_str(), + debug_rect.mLeft, mParentView->getRect().getHeight() - debug_rect.mTop, + debug_rect.getWidth(), debug_rect.getHeight(), + debug_rect.mRight, mParentView->getRect().getHeight() - debug_rect.mBottom); LLFontGL::getFontSansSerifSmall()->renderUTF8(debug_text, 0, (F32)x, (F32)y, border_color, LLFontGL::HCENTER, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW); } diff --git a/indra/llui/llviewereventrecorder.cpp b/indra/llui/llviewereventrecorder.cpp index 6d907d7e45..0a4fe5234b 100644 --- a/indra/llui/llviewereventrecorder.cpp +++ b/indra/llui/llviewereventrecorder.cpp @@ -24,9 +24,10 @@ */ -#include "llviewereventrecorder.h" -#include "llui.h" #include "llleap.h" +#include "llstring.h" +#include "llui.h" +#include "llviewereventrecorder.h" LLViewerEventRecorder::LLViewerEventRecorder() { @@ -247,11 +248,9 @@ void LLViewerEventRecorder::logKeyUnicodeEvent(llwchar uni_char) { // keycode...or // char - LL_DEBUGS() << "Wrapped in conversion to wstring " << wstring_to_utf8str(LLWString( 1, uni_char)) << "\n" << LL_ENDL; + LL_DEBUGS() << "Wrapped in conversion to wstring " << ll_convert_to<std::string>(uni_char) << "\n" << LL_ENDL; - event.insert("char", - LLSD( wstring_to_utf8str(LLWString( 1,uni_char)) ) - ); + event.insert("char", LLSD(ll_convert_to<std::string>(uni_char))); // path (optional) - for now we are not recording path for key events during record - should not be needed for full record and playback of recorded steps // as a vita script - it does become useful if you edit the resulting vita script and wish to remove some steps leading to a key event - that sort of edit might diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt index 2996f58fe0..075e17235a 100644 --- a/indra/llwindow/CMakeLists.txt +++ b/indra/llwindow/CMakeLists.txt @@ -59,12 +59,14 @@ set(llwindow_LINK_LIBRARIES ll::glext ll::uilibraries ll::SDL + ll::zlib-ng ) + # Libraries on which this library depends, needed for Linux builds # Sort by high-level to low-level if (LINUX) - list(APPEND viewer_SOURCE_FILES - llkeyboardsdl.cpp + list(APPEND viewer_SOURCE_FILES + llkeyboardsdl.cpp llwindowsdl.cpp ) list(APPEND viewer_HEADER_FILES @@ -84,7 +86,6 @@ if (LINUX) fontconfig # For FCInit and other FC* functions. ) endif (BUILD_HEADLESS) - endif (LINUX) if (DARWIN) @@ -180,7 +181,7 @@ endif (SDL_FOUND) target_link_libraries (llwindow ${llwindow_LINK_LIBRARIES}) target_include_directories(llwindow INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) - + if (DARWIN) include(CMakeFindFrameworks) find_library(CARBON_LIBRARY Carbon) diff --git a/indra/llwindow/llkeyboard.cpp b/indra/llwindow/llkeyboard.cpp index 33eebdadd1..a16c0a318a 100644 --- a/indra/llwindow/llkeyboard.cpp +++ b/indra/llwindow/llkeyboard.cpp @@ -41,7 +41,6 @@ std::map<KEY,std::string> LLKeyboard::sKeysToNames; std::map<std::string,KEY> LLKeyboard::sNamesToKeys; LLKeyStringTranslatorFunc* LLKeyboard::mStringTranslator = NULL; // Used for l10n + PC/Mac/Linux accelerator labeling - // // Class Implementation // @@ -195,12 +194,11 @@ void LLKeyboard::resetKeys() } -bool LLKeyboard::translateKey(const U16 os_key, KEY *out_key) +bool LLKeyboard::translateKey(const NATIVE_KEY_TYPE os_key, KEY *out_key) { - std::map<U16, KEY>::iterator iter; // Only translate keys in the map, ignore all other keys for now - iter = mTranslateKeyMap.find(os_key); + auto iter = mTranslateKeyMap.find(os_key); if (iter == mTranslateKeyMap.end()) { //LL_WARNS() << "Unknown virtual key " << os_key << LL_ENDL; @@ -214,11 +212,9 @@ bool LLKeyboard::translateKey(const U16 os_key, KEY *out_key) } } - -U16 LLKeyboard::inverseTranslateKey(const KEY translated_key) +LLKeyboard::NATIVE_KEY_TYPE LLKeyboard::inverseTranslateKey(const KEY translated_key) { - std::map<KEY, U16>::iterator iter; - iter = mInvTranslateKeyMap.find(translated_key); + auto iter = mInvTranslateKeyMap.find(translated_key); if (iter == mInvTranslateKeyMap.end()) { return 0; diff --git a/indra/llwindow/llkeyboard.h b/indra/llwindow/llkeyboard.h index 713eb7aec2..d3c35b1ed4 100644 --- a/indra/llwindow/llkeyboard.h +++ b/indra/llwindow/llkeyboard.h @@ -55,6 +55,11 @@ class LLWindowCallbacks; class LLKeyboard { public: +#ifndef LL_SDL + typedef U16 NATIVE_KEY_TYPE; +#else + typedef U32 NATIVE_KEY_TYPE; +#endif LLKeyboard(); virtual ~LLKeyboard(); @@ -67,14 +72,13 @@ public: bool getKeyDown(const KEY key) { return mKeyLevel[key]; } bool getKeyRepeated(const KEY key) { return mKeyRepeated[key]; } - bool translateKey(const U16 os_key, KEY *translated_key); - U16 inverseTranslateKey(const KEY translated_key); + bool translateKey(const NATIVE_KEY_TYPE os_key, KEY *translated_key); + NATIVE_KEY_TYPE inverseTranslateKey(const KEY translated_key); bool handleTranslatedKeyUp(KEY translated_key, U32 translated_mask); // Translated into "Linden" keycodes bool handleTranslatedKeyDown(KEY translated_key, U32 translated_mask); // Translated into "Linden" keycodes - - virtual bool handleKeyUp(const U16 key, MASK mask) = 0; - virtual bool handleKeyDown(const U16 key, MASK mask) = 0; + virtual bool handleKeyUp(const NATIVE_KEY_TYPE key, MASK mask) = 0; + virtual bool handleKeyDown(const NATIVE_KEY_TYPE key, MASK mask) = 0; #ifdef LL_DARWIN // We only actually use this for macOS. @@ -111,8 +115,8 @@ protected: void addKeyName(KEY key, const std::string& name); protected: - std::map<U16, KEY> mTranslateKeyMap; // Map of translations from OS keys to Linden KEYs - std::map<KEY, U16> mInvTranslateKeyMap; // Map of translations from Linden KEYs to OS keys + std::map<NATIVE_KEY_TYPE, KEY> mTranslateKeyMap; // Map of translations from OS keys to Linden KEYs + std::map<KEY, NATIVE_KEY_TYPE> mInvTranslateKeyMap; // Map of translations from Linden KEYs to OS keys LLWindowCallbacks *mCallbacks; LLTimer mKeyLevelTimer[KEY_COUNT]; // Time since level was set diff --git a/indra/llwindow/llkeyboardheadless.cpp b/indra/llwindow/llkeyboardheadless.cpp index 8669a5b41a..ad8e42a412 100644 --- a/indra/llwindow/llkeyboardheadless.cpp +++ b/indra/llwindow/llkeyboardheadless.cpp @@ -34,14 +34,6 @@ LLKeyboardHeadless::LLKeyboardHeadless() void LLKeyboardHeadless::resetMaskKeys() { } - -bool LLKeyboardHeadless::handleKeyDown(const U16 key, const U32 mask) -{ return false; } - - -bool LLKeyboardHeadless::handleKeyUp(const U16 key, const U32 mask) -{ return false; } - MASK LLKeyboardHeadless::currentMask(bool for_mouse_event) { return MASK_NONE; } diff --git a/indra/llwindow/llkeyboardheadless.h b/indra/llwindow/llkeyboardheadless.h index 2528f0e3f1..439abaf25b 100644 --- a/indra/llwindow/llkeyboardheadless.h +++ b/indra/llwindow/llkeyboardheadless.h @@ -35,8 +35,13 @@ public: LLKeyboardHeadless(); /*virtual*/ ~LLKeyboardHeadless() {}; - /*virtual*/ bool handleKeyUp(const U16 key, MASK mask); - /*virtual*/ bool handleKeyDown(const U16 key, MASK mask); +#ifndef LL_SDL + /*virtual*/ bool handleKeyUp(const U16 key, MASK mask) { return false; } + /*virtual*/ bool handleKeyDown(const U16 key, MASK mask) { return false; } +#else + /*virtual*/ bool handleKeyUp(const U32 key, MASK mask) { return false; } + /*virtual*/ bool handleKeyDown(const U32 key, MASK mask) { return false; } +#endif /*virtual*/ void resetMaskKeys(); /*virtual*/ MASK currentMask(bool for_mouse_event); /*virtual*/ void scanKeyboard(); diff --git a/indra/llwindow/llkeyboardsdl.cpp b/indra/llwindow/llkeyboardsdl.cpp index 97198f0cc0..543882fc8f 100644 --- a/indra/llwindow/llkeyboardsdl.cpp +++ b/indra/llwindow/llkeyboardsdl.cpp @@ -1,6 +1,5 @@ /** - * @file llkeyboardsdl.cpp - * @brief Handler for assignable key bindings + * @author This module has many fathers, and it shows. * * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code @@ -24,12 +23,11 @@ * $/LicenseInfo$ */ -#if LL_SDL - #include "linden_common.h" #include "llkeyboardsdl.h" #include "llwindowcallbacks.h" -#include "SDL/SDL.h" +#include "SDL2/SDL.h" +#include "SDL2/SDL_keycode.h" LLKeyboardSDL::LLKeyboardSDL() { @@ -40,6 +38,10 @@ LLKeyboardSDL::LLKeyboardSDL() // Virtual key mappings from SDL_keysym.h ... // SDL maps the letter keys to the ASCII you'd expect, but it's lowercase... + + // <FS:ND> Looks like we need to map those despite of SDL_TEXTINPUT handling most of this, but without + // the translation lower->upper here accelerators will not work. + U16 cur_char; for (cur_char = 'A'; cur_char <= 'Z'; cur_char++) { @@ -68,13 +70,12 @@ LLKeyboardSDL::LLKeyboardSDL() //mTranslateKeyMap[SDLK_KP3] = KEY_PAGE_DOWN; //mTranslateKeyMap[SDLK_KP0] = KEY_INSERT; - mTranslateKeyMap[SDLK_SPACE] = ' '; + mTranslateKeyMap[SDLK_SPACE] = ' '; // <FS:ND/> Those are handled by SDL2 via text input, do not map them mTranslateKeyMap[SDLK_RETURN] = KEY_RETURN; mTranslateKeyMap[SDLK_LEFT] = KEY_LEFT; mTranslateKeyMap[SDLK_RIGHT] = KEY_RIGHT; mTranslateKeyMap[SDLK_UP] = KEY_UP; mTranslateKeyMap[SDLK_DOWN] = KEY_DOWN; - mTranslateKeyMap[SDLK_ESCAPE] = KEY_ESCAPE; mTranslateKeyMap[SDLK_KP_ENTER] = KEY_RETURN; mTranslateKeyMap[SDLK_ESCAPE] = KEY_ESCAPE; mTranslateKeyMap[SDLK_BACKSPACE] = KEY_BACKSPACE; @@ -111,40 +112,39 @@ LLKeyboardSDL::LLKeyboardSDL() mTranslateKeyMap[SDLK_F10] = KEY_F10; mTranslateKeyMap[SDLK_F11] = KEY_F11; mTranslateKeyMap[SDLK_F12] = KEY_F12; - mTranslateKeyMap[SDLK_PLUS] = '='; - mTranslateKeyMap[SDLK_COMMA] = ','; - mTranslateKeyMap[SDLK_MINUS] = '-'; - mTranslateKeyMap[SDLK_PERIOD] = '.'; - mTranslateKeyMap[SDLK_BACKQUOTE] = '`'; - mTranslateKeyMap[SDLK_SLASH] = KEY_DIVIDE; - mTranslateKeyMap[SDLK_SEMICOLON] = ';'; - mTranslateKeyMap[SDLK_LEFTBRACKET] = '['; - mTranslateKeyMap[SDLK_BACKSLASH] = '\\'; - mTranslateKeyMap[SDLK_RIGHTBRACKET] = ']'; - mTranslateKeyMap[SDLK_QUOTE] = '\''; + mTranslateKeyMap[SDLK_PLUS] = '='; // <FS:ND/> Those are handled by SDL2 via text input, do not map them + mTranslateKeyMap[SDLK_COMMA] = ','; // <FS:ND/> Those are handled by SDL2 via text input, do not map them + mTranslateKeyMap[SDLK_MINUS] = '-'; // <FS:ND/> Those are handled by SDL2 via text input, do not map them + mTranslateKeyMap[SDLK_PERIOD] = '.'; // <FS:ND/> Those are handled by SDL2 via text input, do not map them + mTranslateKeyMap[SDLK_BACKQUOTE] = '`'; // <FS:ND/> Those are handled by SDL2 via text input, do not map them + mTranslateKeyMap[SDLK_SLASH] = KEY_DIVIDE; // <FS:ND/> Those are handled by SDL2 via text input, do not map them + mTranslateKeyMap[SDLK_SEMICOLON] = ';'; // <FS:ND/> Those are handled by SDL2 via text input, do not map them + mTranslateKeyMap[SDLK_LEFTBRACKET] = '['; // <FS:ND/> Those are handled by SDL2 via text input, do not map them + mTranslateKeyMap[SDLK_BACKSLASH] = '\\'; // <FS:ND/> Those are handled by SDL2 via text input, do not map them + mTranslateKeyMap[SDLK_RIGHTBRACKET] = ']'; // <FS:ND/> Those are handled by SDL2 via text input, do not map them + mTranslateKeyMap[SDLK_QUOTE] = '\''; // <FS:ND/> Those are handled by SDL2 via text input, do not map them // Build inverse map - std::map<U16, KEY>::iterator iter; - for (iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) + for (auto iter = mTranslateKeyMap.begin(); iter != mTranslateKeyMap.end(); iter++) { mInvTranslateKeyMap[iter->second] = iter->first; } // numpad map - mTranslateNumpadMap[SDLK_KP0] = KEY_PAD_INS; - mTranslateNumpadMap[SDLK_KP1] = KEY_PAD_END; - mTranslateNumpadMap[SDLK_KP2] = KEY_PAD_DOWN; - mTranslateNumpadMap[SDLK_KP3] = KEY_PAD_PGDN; - mTranslateNumpadMap[SDLK_KP4] = KEY_PAD_LEFT; - mTranslateNumpadMap[SDLK_KP5] = KEY_PAD_CENTER; - mTranslateNumpadMap[SDLK_KP6] = KEY_PAD_RIGHT; - mTranslateNumpadMap[SDLK_KP7] = KEY_PAD_HOME; - mTranslateNumpadMap[SDLK_KP8] = KEY_PAD_UP; - mTranslateNumpadMap[SDLK_KP9] = KEY_PAD_PGUP; + mTranslateNumpadMap[SDLK_KP_0] = KEY_PAD_INS; + mTranslateNumpadMap[SDLK_KP_1] = KEY_PAD_END; + mTranslateNumpadMap[SDLK_KP_2] = KEY_PAD_DOWN; + mTranslateNumpadMap[SDLK_KP_3] = KEY_PAD_PGDN; + mTranslateNumpadMap[SDLK_KP_4] = KEY_PAD_LEFT; + mTranslateNumpadMap[SDLK_KP_5] = KEY_PAD_CENTER; + mTranslateNumpadMap[SDLK_KP_6] = KEY_PAD_RIGHT; + mTranslateNumpadMap[SDLK_KP_7] = KEY_PAD_HOME; + mTranslateNumpadMap[SDLK_KP_8] = KEY_PAD_UP; + mTranslateNumpadMap[SDLK_KP_9] = KEY_PAD_PGUP; mTranslateNumpadMap[SDLK_KP_PERIOD] = KEY_PAD_DEL; // build inverse numpad map - for (iter = mTranslateNumpadMap.begin(); + for (auto iter = mTranslateNumpadMap.begin(); iter != mTranslateNumpadMap.end(); iter++) { @@ -154,7 +154,7 @@ LLKeyboardSDL::LLKeyboardSDL() void LLKeyboardSDL::resetMaskKeys() { - SDLMod mask = SDL_GetModState(); + SDL_Keymod mask = SDL_GetModState(); // MBW -- XXX -- This mirrors the operation of the Windows version of resetMaskKeys(). // It looks a bit suspicious, as it won't correct for keys that have been released. @@ -201,37 +201,37 @@ MASK LLKeyboardSDL::updateModifiers(const U32 mask) } -static U16 adjustNativekeyFromUnhandledMask(const U16 key, const U32 mask) +static U32 adjustNativekeyFromUnhandledMask(const U32 key, const U32 mask) { // SDL doesn't automatically adjust the keysym according to // whether NUMLOCK is engaged, so we massage the keysym manually. - U16 rtn = key; + U32 rtn = key; if (!(mask & KMOD_NUM)) { switch (key) { - case SDLK_KP_PERIOD: rtn = SDLK_DELETE; break; - case SDLK_KP0: rtn = SDLK_INSERT; break; - case SDLK_KP1: rtn = SDLK_END; break; - case SDLK_KP2: rtn = SDLK_DOWN; break; - case SDLK_KP3: rtn = SDLK_PAGEDOWN; break; - case SDLK_KP4: rtn = SDLK_LEFT; break; - case SDLK_KP6: rtn = SDLK_RIGHT; break; - case SDLK_KP7: rtn = SDLK_HOME; break; - case SDLK_KP8: rtn = SDLK_UP; break; - case SDLK_KP9: rtn = SDLK_PAGEUP; break; + case SDLK_KP_PERIOD: rtn = SDLK_DELETE; break; + case SDLK_KP_0: rtn = SDLK_INSERT; break; + case SDLK_KP_1: rtn = SDLK_END; break; + case SDLK_KP_2: rtn = SDLK_DOWN; break; + case SDLK_KP_3: rtn = SDLK_PAGEDOWN; break; + case SDLK_KP_4: rtn = SDLK_LEFT; break; + case SDLK_KP_6: rtn = SDLK_RIGHT; break; + case SDLK_KP_7: rtn = SDLK_HOME; break; + case SDLK_KP_8: rtn = SDLK_UP; break; + case SDLK_KP_9: rtn = SDLK_PAGEUP; break; } } return rtn; } -bool LLKeyboardSDL::handleKeyDown(const U16 key, const U32 mask) +bool LLKeyboardSDL::handleKeyDown(const U32 key, const U32 mask) { - U16 adjusted_nativekey; + U32 adjusted_nativekey; KEY translated_key = 0; U32 translated_mask = MASK_NONE; - bool handled = false; + bool handled = false; adjusted_nativekey = adjustNativekeyFromUnhandledMask(key, mask); @@ -246,12 +246,12 @@ bool LLKeyboardSDL::handleKeyDown(const U16 key, const U32 mask) } -bool LLKeyboardSDL::handleKeyUp(const U16 key, const U32 mask) +bool LLKeyboardSDL::handleKeyUp(const U32 key, const U32 mask) { - U16 adjusted_nativekey; + U32 adjusted_nativekey; KEY translated_key = 0; U32 translated_mask = MASK_NONE; - bool handled = false; + bool handled = false; adjusted_nativekey = adjustNativekeyFromUnhandledMask(key, mask); @@ -268,16 +268,20 @@ bool LLKeyboardSDL::handleKeyUp(const U16 key, const U32 mask) MASK LLKeyboardSDL::currentMask(bool for_mouse_event) { MASK result = MASK_NONE; - SDLMod mask = SDL_GetModState(); + SDL_Keymod mask = SDL_GetModState(); - if (mask & KMOD_SHIFT) result |= MASK_SHIFT; - if (mask & KMOD_CTRL) result |= MASK_CONTROL; - if (mask & KMOD_ALT) result |= MASK_ALT; + if (mask & KMOD_SHIFT) + result |= MASK_SHIFT; + if (mask & KMOD_CTRL) + result |= MASK_CONTROL; + if (mask & KMOD_ALT) + result |= MASK_ALT; // For keyboard events, consider Meta keys equivalent to Control if (!for_mouse_event) { - if (mask & KMOD_META) result |= MASK_CONTROL; + if (mask & KMOD_GUI) + result |= MASK_CONTROL; } return result; @@ -310,7 +314,7 @@ void LLKeyboardSDL::scanKeyboard() } -bool LLKeyboardSDL::translateNumpadKey( const U16 os_key, KEY *translated_key) +bool LLKeyboardSDL::translateNumpadKey( const U32 os_key, KEY *translated_key) { return translateKey(os_key, translated_key); } @@ -320,5 +324,338 @@ U16 LLKeyboardSDL::inverseTranslateNumpadKey(const KEY translated_key) return inverseTranslateKey(translated_key); } -#endif +enum class WindowsVK : U32 +{ + VK_UNKNOWN = 0, + VK_CANCEL = 0x03, + VK_BACK = 0x08, + VK_TAB = 0x09, + VK_CLEAR = 0x0C, + VK_RETURN = 0x0D, + VK_SHIFT = 0x10, + VK_CONTROL = 0x11, + VK_MENU = 0x12, + VK_PAUSE = 0x13, + VK_CAPITAL = 0x14, + VK_KANA = 0x15, + VK_HANGUL = 0x15, + VK_JUNJA = 0x17, + VK_FINAL = 0x18, + VK_HANJA = 0x19, + VK_KANJI = 0x19, + VK_ESCAPE = 0x1B, + VK_CONVERT = 0x1C, + VK_NONCONVERT = 0x1D, + VK_ACCEPT = 0x1E, + VK_MODECHANGE = 0x1F, + VK_SPACE = 0x20, + VK_PRIOR = 0x21, + VK_NEXT = 0x22, + VK_END = 0x23, + VK_HOME = 0x24, + VK_LEFT = 0x25, + VK_UP = 0x26, + VK_RIGHT = 0x27, + VK_DOWN = 0x28, + VK_SELECT = 0x29, + VK_PRINT = 0x2A, + VK_EXECUTE = 0x2B, + VK_SNAPSHOT = 0x2C, + VK_INSERT = 0x2D, + VK_DELETE = 0x2E, + VK_HELP = 0x2F, + VK_0 = 0x30, + VK_1 = 0x31, + VK_2 = 0x32, + VK_3 = 0x33, + VK_4 = 0x34, + VK_5 = 0x35, + VK_6 = 0x36, + VK_7 = 0x37, + VK_8 = 0x38, + VK_9 = 0x39, + VK_A = 0x41, + VK_B = 0x42, + VK_C = 0x43, + VK_D = 0x44, + VK_E = 0x45, + VK_F = 0x46, + VK_G = 0x47, + VK_H = 0x48, + VK_I = 0x49, + VK_J = 0x4A, + VK_K = 0x4B, + VK_L = 0x4C, + VK_M = 0x4D, + VK_N = 0x4E, + VK_O = 0x4F, + VK_P = 0x50, + VK_Q = 0x51, + VK_R = 0x52, + VK_S = 0x53, + VK_T = 0x54, + VK_U = 0x55, + VK_V = 0x56, + VK_W = 0x57, + VK_X = 0x58, + VK_Y = 0x59, + VK_Z = 0x5A, + VK_LWIN = 0x5B, + VK_RWIN = 0x5C, + VK_APPS = 0x5D, + VK_SLEEP = 0x5F, + VK_NUMPAD0 = 0x60, + VK_NUMPAD1 = 0x61, + VK_NUMPAD2 = 0x62, + VK_NUMPAD3 = 0x63, + VK_NUMPAD4 = 0x64, + VK_NUMPAD5 = 0x65, + VK_NUMPAD6 = 0x66, + VK_NUMPAD7 = 0x67, + VK_NUMPAD8 = 0x68, + VK_NUMPAD9 = 0x69, + VK_MULTIPLY = 0x6A, + VK_ADD = 0x6B, + VK_SEPARATOR = 0x6C, + VK_SUBTRACT = 0x6D, + VK_DECIMAL = 0x6E, + VK_DIVIDE = 0x6F, + VK_F1 = 0x70, + VK_F2 = 0x71, + VK_F3 = 0x72, + VK_F4 = 0x73, + VK_F5 = 0x74, + VK_F6 = 0x75, + VK_F7 = 0x76, + VK_F8 = 0x77, + VK_F9 = 0x78, + VK_F10 = 0x79, + VK_F11 = 0x7A, + VK_F12 = 0x7B, + VK_F13 = 0x7C, + VK_F14 = 0x7D, + VK_F15 = 0x7E, + VK_F16 = 0x7F, + VK_F17 = 0x80, + VK_F18 = 0x81, + VK_F19 = 0x82, + VK_F20 = 0x83, + VK_F21 = 0x84, + VK_F22 = 0x85, + VK_F23 = 0x86, + VK_F24 = 0x87, + VK_NUMLOCK = 0x90, + VK_SCROLL = 0x91, + VK_LSHIFT = 0xA0, + VK_RSHIFT = 0xA1, + VK_LCONTROL = 0xA2, + VK_RCONTROL = 0xA3, + VK_LMENU = 0xA4, + VK_RMENU = 0xA5, + VK_BROWSER_BACK = 0xA6, + VK_BROWSER_FORWARD = 0xA7, + VK_BROWSER_REFRESH = 0xA8, + VK_BROWSER_STOP = 0xA9, + VK_BROWSER_SEARCH = 0xAA, + VK_BROWSER_FAVORITES = 0xAB, + VK_BROWSER_HOME = 0xAC, + VK_VOLUME_MUTE = 0xAD, + VK_VOLUME_DOWN = 0xAE, + VK_VOLUME_UP = 0xAF, + VK_MEDIA_NEXT_TRACK = 0xB0, + VK_MEDIA_PREV_TRACK = 0xB1, + VK_MEDIA_STOP = 0xB2, + VK_MEDIA_PLAY_PAUSE = 0xB3, + VK_MEDIA_LAUNCH_MAIL = 0xB4, + VK_MEDIA_LAUNCH_MEDIA_SELECT = 0xB5, + VK_MEDIA_LAUNCH_APP1 = 0xB6, + VK_MEDIA_LAUNCH_APP2 = 0xB7, + VK_OEM_1 = 0xBA, + VK_OEM_PLUS = 0xBB, + VK_OEM_COMMA = 0xBC, + VK_OEM_MINUS = 0xBD, + VK_OEM_PERIOD = 0xBE, + VK_OEM_2 = 0xBF, + VK_OEM_3 = 0xC0, + VK_OEM_4 = 0xDB, + VK_OEM_5 = 0xDC, + VK_OEM_6 = 0xDD, + VK_OEM_7 = 0xDE, + VK_OEM_8 = 0xDF, + VK_OEM_102 = 0xE2, + VK_PROCESSKEY = 0xE5, + VK_PACKET = 0xE7, + VK_ATTN = 0xF6, + VK_CRSEL = 0xF7, + VK_EXSEL = 0xF8, + VK_EREOF = 0xF9, + VK_PLAY = 0xFA, + VK_ZOOM = 0xFB, + VK_NONAME = 0xFC, + VK_PA1 = 0xFD, + VK_OEM_CLEAR = 0xFE, +}; + +std::map< U32, U32 > mSDL2_to_Win; +std::set< U32 > mIgnoreSDL2Keys; + +U32 LLKeyboardSDL::mapSDL2toWin( U32 aSymbol ) +{ + // <FS:ND> Map SDLK_ virtual keys to Windows VK_ virtual keys. + // Text is handled via unicode input (SDL_TEXTINPUT event) and does not need to be translated into VK_ values as those match already. + if( mSDL2_to_Win.empty() ) + { + + mSDL2_to_Win[ SDLK_BACKSPACE ] = (U32)WindowsVK::VK_BACK; + mSDL2_to_Win[ SDLK_TAB ] = (U32)WindowsVK::VK_TAB; + mSDL2_to_Win[ 12 ] = (U32)WindowsVK::VK_CLEAR; + mSDL2_to_Win[ SDLK_RETURN ] = (U32)WindowsVK::VK_RETURN; + mSDL2_to_Win[ 19 ] = (U32)WindowsVK::VK_PAUSE; + mSDL2_to_Win[ SDLK_ESCAPE ] = (U32)WindowsVK::VK_ESCAPE; + mSDL2_to_Win[ SDLK_SPACE ] = (U32)WindowsVK::VK_SPACE; + mSDL2_to_Win[ SDLK_QUOTE ] = (U32)WindowsVK::VK_OEM_7; + mSDL2_to_Win[ SDLK_COMMA ] = (U32)WindowsVK::VK_OEM_COMMA; + mSDL2_to_Win[ SDLK_MINUS ] = (U32)WindowsVK::VK_OEM_MINUS; + mSDL2_to_Win[ SDLK_PERIOD ] = (U32)WindowsVK::VK_OEM_PERIOD; + mSDL2_to_Win[ SDLK_SLASH ] = (U32)WindowsVK::VK_OEM_2; + + mSDL2_to_Win[ SDLK_0 ] = (U32)WindowsVK::VK_0; + mSDL2_to_Win[ SDLK_1 ] = (U32)WindowsVK::VK_1; + mSDL2_to_Win[ SDLK_2 ] = (U32)WindowsVK::VK_2; + mSDL2_to_Win[ SDLK_3 ] = (U32)WindowsVK::VK_3; + mSDL2_to_Win[ SDLK_4 ] = (U32)WindowsVK::VK_4; + mSDL2_to_Win[ SDLK_5 ] = (U32)WindowsVK::VK_5; + mSDL2_to_Win[ SDLK_6 ] = (U32)WindowsVK::VK_6; + mSDL2_to_Win[ SDLK_7 ] = (U32)WindowsVK::VK_7; + mSDL2_to_Win[ SDLK_8 ] = (U32)WindowsVK::VK_8; + mSDL2_to_Win[ SDLK_9 ] = (U32)WindowsVK::VK_9; + + mSDL2_to_Win[ SDLK_SEMICOLON ] = (U32)WindowsVK::VK_OEM_1; + mSDL2_to_Win[ SDLK_LESS ] = (U32)WindowsVK::VK_OEM_102; + mSDL2_to_Win[ SDLK_EQUALS ] = (U32)WindowsVK::VK_OEM_PLUS; + mSDL2_to_Win[ SDLK_KP_EQUALS ] = (U32)WindowsVK::VK_OEM_PLUS; + + mSDL2_to_Win[ SDLK_LEFTBRACKET ] = (U32)WindowsVK::VK_OEM_4; + mSDL2_to_Win[ SDLK_BACKSLASH ] = (U32)WindowsVK::VK_OEM_5; + mSDL2_to_Win[ SDLK_RIGHTBRACKET ] = (U32)WindowsVK::VK_OEM_6; + mSDL2_to_Win[ SDLK_BACKQUOTE ] = (U32)WindowsVK::VK_OEM_8; + + mSDL2_to_Win[ SDLK_a ] = (U32)WindowsVK::VK_A; + mSDL2_to_Win[ SDLK_b ] = (U32)WindowsVK::VK_B; + mSDL2_to_Win[ SDLK_c ] = (U32)WindowsVK::VK_C; + mSDL2_to_Win[ SDLK_d ] = (U32)WindowsVK::VK_D; + mSDL2_to_Win[ SDLK_e ] = (U32)WindowsVK::VK_E; + mSDL2_to_Win[ SDLK_f ] = (U32)WindowsVK::VK_F; + mSDL2_to_Win[ SDLK_g ] = (U32)WindowsVK::VK_G; + mSDL2_to_Win[ SDLK_h ] = (U32)WindowsVK::VK_H; + mSDL2_to_Win[ SDLK_i ] = (U32)WindowsVK::VK_I; + mSDL2_to_Win[ SDLK_j ] = (U32)WindowsVK::VK_J; + mSDL2_to_Win[ SDLK_k ] = (U32)WindowsVK::VK_K; + mSDL2_to_Win[ SDLK_l ] = (U32)WindowsVK::VK_L; + mSDL2_to_Win[ SDLK_m ] = (U32)WindowsVK::VK_M; + mSDL2_to_Win[ SDLK_n ] = (U32)WindowsVK::VK_N; + mSDL2_to_Win[ SDLK_o ] = (U32)WindowsVK::VK_O; + mSDL2_to_Win[ SDLK_p ] = (U32)WindowsVK::VK_P; + mSDL2_to_Win[ SDLK_q ] = (U32)WindowsVK::VK_Q; + mSDL2_to_Win[ SDLK_r ] = (U32)WindowsVK::VK_R; + mSDL2_to_Win[ SDLK_s ] = (U32)WindowsVK::VK_S; + mSDL2_to_Win[ SDLK_t ] = (U32)WindowsVK::VK_T; + mSDL2_to_Win[ SDLK_u ] = (U32)WindowsVK::VK_U; + mSDL2_to_Win[ SDLK_v ] = (U32)WindowsVK::VK_V; + mSDL2_to_Win[ SDLK_w ] = (U32)WindowsVK::VK_W; + mSDL2_to_Win[ SDLK_x ] = (U32)WindowsVK::VK_X; + mSDL2_to_Win[ SDLK_y ] = (U32)WindowsVK::VK_Y; + mSDL2_to_Win[ SDLK_z ] = (U32)WindowsVK::VK_Z; + + mSDL2_to_Win[ SDLK_DELETE ] = (U32)WindowsVK::VK_DELETE; + + + mSDL2_to_Win[ SDLK_NUMLOCKCLEAR ] = (U32)WindowsVK::VK_NUMLOCK; + mSDL2_to_Win[ SDLK_SCROLLLOCK ] = (U32)WindowsVK::VK_SCROLL; + + mSDL2_to_Win[ SDLK_HELP ] = (U32)WindowsVK::VK_HELP; + mSDL2_to_Win[ SDLK_PRINTSCREEN ] = (U32)WindowsVK::VK_SNAPSHOT; + mSDL2_to_Win[ SDLK_CANCEL ] = (U32)WindowsVK::VK_CANCEL; + mSDL2_to_Win[ SDLK_APPLICATION ] = (U32)WindowsVK::VK_APPS; + + mSDL2_to_Win[ SDLK_UNKNOWN ] = (U32)WindowsVK::VK_UNKNOWN; + mSDL2_to_Win[ SDLK_BACKSPACE ] = (U32)WindowsVK::VK_BACK; + mSDL2_to_Win[ SDLK_TAB ] = (U32)WindowsVK::VK_TAB; + mSDL2_to_Win[ SDLK_CLEAR ] = (U32)WindowsVK::VK_CLEAR; + mSDL2_to_Win[ SDLK_RETURN ] = (U32)WindowsVK::VK_RETURN; + mSDL2_to_Win[ SDLK_PAUSE ] = (U32)WindowsVK::VK_PAUSE; + mSDL2_to_Win[ SDLK_ESCAPE ] = (U32)WindowsVK::VK_ESCAPE; + mSDL2_to_Win[ SDLK_DELETE ] = (U32)WindowsVK::VK_DELETE; + + mSDL2_to_Win[ SDLK_KP_PERIOD ] = (U32)WindowsVK::VK_OEM_PERIOD; // VK_DECIMAL? + mSDL2_to_Win[ SDLK_KP_DIVIDE ] = (U32)WindowsVK::VK_DIVIDE; + mSDL2_to_Win[ SDLK_KP_MULTIPLY] = (U32)WindowsVK::VK_MULTIPLY; + mSDL2_to_Win[ SDLK_KP_MINUS ] = (U32)WindowsVK::VK_OEM_MINUS; // VK_SUBSTRACT? + mSDL2_to_Win[ SDLK_KP_PLUS ] = (U32)WindowsVK::VK_OEM_PLUS; // VK_ADD? + mSDL2_to_Win[ SDLK_KP_ENTER ] = (U32)WindowsVK::VK_RETURN; + mSDL2_to_Win[ SDLK_KP_0 ] = (U32)WindowsVK::VK_NUMPAD0; + mSDL2_to_Win[ SDLK_KP_1 ] = (U32)WindowsVK::VK_NUMPAD1; + mSDL2_to_Win[ SDLK_KP_2 ] = (U32)WindowsVK::VK_NUMPAD2; + mSDL2_to_Win[ SDLK_KP_3 ] = (U32)WindowsVK::VK_NUMPAD3; + mSDL2_to_Win[ SDLK_KP_4 ] = (U32)WindowsVK::VK_NUMPAD4; + mSDL2_to_Win[ SDLK_KP_5 ] = (U32)WindowsVK::VK_NUMPAD5; + mSDL2_to_Win[ SDLK_KP_6 ] = (U32)WindowsVK::VK_NUMPAD6; + mSDL2_to_Win[ SDLK_KP_7 ] = (U32)WindowsVK::VK_NUMPAD7; + mSDL2_to_Win[ SDLK_KP_8 ] = (U32)WindowsVK::VK_NUMPAD8; + mSDL2_to_Win[ SDLK_KP_9 ] = (U32)WindowsVK::VK_NUMPAD9; + + // ? + + mSDL2_to_Win[ SDLK_UP ] = (U32)WindowsVK::VK_UP; + mSDL2_to_Win[ SDLK_DOWN ] = (U32)WindowsVK::VK_DOWN; + mSDL2_to_Win[ SDLK_RIGHT ] = (U32)WindowsVK::VK_RIGHT; + mSDL2_to_Win[ SDLK_LEFT ] = (U32)WindowsVK::VK_LEFT; + mSDL2_to_Win[ SDLK_INSERT ] = (U32)WindowsVK::VK_INSERT; + mSDL2_to_Win[ SDLK_HOME ] = (U32)WindowsVK::VK_HOME; + mSDL2_to_Win[ SDLK_END ] = (U32)WindowsVK::VK_END; + mSDL2_to_Win[ SDLK_PAGEUP ] = (U32)WindowsVK::VK_PRIOR; + mSDL2_to_Win[ SDLK_PAGEDOWN ] = (U32)WindowsVK::VK_NEXT; + mSDL2_to_Win[ SDLK_F1 ] = (U32)WindowsVK::VK_F1; + mSDL2_to_Win[ SDLK_F2 ] = (U32)WindowsVK::VK_F2; + mSDL2_to_Win[ SDLK_F3 ] = (U32)WindowsVK::VK_F3; + mSDL2_to_Win[ SDLK_F4 ] = (U32)WindowsVK::VK_F4; + mSDL2_to_Win[ SDLK_F5 ] = (U32)WindowsVK::VK_F5; + mSDL2_to_Win[ SDLK_F6 ] = (U32)WindowsVK::VK_F6; + mSDL2_to_Win[ SDLK_F7 ] = (U32)WindowsVK::VK_F7; + mSDL2_to_Win[ SDLK_F8 ] = (U32)WindowsVK::VK_F8; + mSDL2_to_Win[ SDLK_F9 ] = (U32)WindowsVK::VK_F9; + mSDL2_to_Win[ SDLK_F10 ] = (U32)WindowsVK::VK_F10; + mSDL2_to_Win[ SDLK_F11 ] = (U32)WindowsVK::VK_F11; + mSDL2_to_Win[ SDLK_F12 ] = (U32)WindowsVK::VK_F12; + mSDL2_to_Win[ SDLK_F13 ] = (U32)WindowsVK::VK_F13; + mSDL2_to_Win[ SDLK_F14 ] = (U32)WindowsVK::VK_F14; + mSDL2_to_Win[ SDLK_F15 ] = (U32)WindowsVK::VK_F15; + mSDL2_to_Win[ SDLK_CAPSLOCK ] = (U32)WindowsVK::VK_CAPITAL; + mSDL2_to_Win[ SDLK_RSHIFT ] = (U32)WindowsVK::VK_SHIFT; + mSDL2_to_Win[ SDLK_LSHIFT ] = (U32)WindowsVK::VK_SHIFT; + mSDL2_to_Win[ SDLK_RCTRL ] = (U32)WindowsVK::VK_CONTROL; + mSDL2_to_Win[ SDLK_LCTRL ] = (U32)WindowsVK::VK_CONTROL; + mSDL2_to_Win[ SDLK_RALT ] = (U32)WindowsVK::VK_MENU; + mSDL2_to_Win[ SDLK_LALT ] = (U32)WindowsVK::VK_MENU; + + mSDL2_to_Win[ SDLK_MENU ] = (U32)WindowsVK::VK_MENU; + + // VK_MODECHANGE ? + // mSDL2_to_Win[ SDLK_MODE ] = (U32)WindowsVK::VK_MODE; + + // ? + // mSDL2_to_Win[ SDLK_SYSREQ ] = (U32)WindowsVK::VK_SYSREQ; + // mSDL2_to_Win[ SDLK_POWER ] = (U32)WindowsVK::VK_POWER; + // mSDL2_to_Win[ SDLK_UNDO ] = (U32)WindowsVK::VK_UNDO; + // mSDL2_to_Win[ SDLK_KP_EQUALS ] = (U32)WindowsVK::VK_EQUALS; + // mSDL2_to_Win[ 311 ] = (U32)WindowsVK::VK_LWIN; + // mSDL2_to_Win[ 312 ] = (U32)WindowsVK::VK_RWIN; + // mSDL2_to_Win[ SDLK_COLON ] = ? + } + auto itr = mSDL2_to_Win.find( aSymbol ); + if( itr != mSDL2_to_Win.end() ) + return itr->second; + + return aSymbol; +} diff --git a/indra/llwindow/llkeyboardsdl.h b/indra/llwindow/llkeyboardsdl.h index fd348b28f2..9ebff66865 100644 --- a/indra/llwindow/llkeyboardsdl.h +++ b/indra/llwindow/llkeyboardsdl.h @@ -1,8 +1,7 @@ /** - * @file llkeyboardsdl.h - * @brief Handler for assignable key bindings + * @author This module has many fathers, and it shows. * - * $LicenseInfo:firstyear=2004&license=viewerlgpl$ + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * @@ -24,11 +23,11 @@ * $/LicenseInfo$ */ -#ifndef LL_LLKEYBOARDSDL_H -#define LL_LLKEYBOARDSDL_H +#ifndef LL_LLKEYBOARDSDL2_H +#define LL_LLKEYBOARDSDL2_H #include "llkeyboard.h" -#include "SDL/SDL.h" +#include "SDL2/SDL.h" class LLKeyboardSDL : public LLKeyboard { @@ -36,8 +35,8 @@ public: LLKeyboardSDL(); /*virtual*/ ~LLKeyboardSDL() {}; - /*virtual*/ bool handleKeyUp(const U16 key, MASK mask); - /*virtual*/ bool handleKeyDown(const U16 key, MASK mask); + /*virtual*/ bool handleKeyUp(const U32 key, MASK mask); + /*virtual*/ bool handleKeyDown(const U32 key, MASK mask); /*virtual*/ void resetMaskKeys(); /*virtual*/ MASK currentMask(bool for_mouse_event); /*virtual*/ void scanKeyboard(); @@ -45,11 +44,14 @@ public: protected: MASK updateModifiers(const U32 mask); void setModifierKeyLevel( KEY key, bool new_state ); - bool translateNumpadKey( const U16 os_key, KEY *translated_key ); + bool translateNumpadKey( const U32 os_key, KEY *translated_key ); U16 inverseTranslateNumpadKey(const KEY translated_key); private: - std::map<U16, KEY> mTranslateNumpadMap; // special map for translating OS keys to numpad keys - std::map<KEY, U16> mInvTranslateNumpadMap; // inverse of the above + std::map<U32, KEY> mTranslateNumpadMap; // special map for translating OS keys to numpad keys + std::map<KEY, U32> mInvTranslateNumpadMap; // inverse of the above + +public: + static U32 mapSDL2toWin( U32 ); }; #endif diff --git a/indra/llwindow/llwindow.h b/indra/llwindow/llwindow.h index 5e06e665f3..fcc4fd863a 100644 --- a/indra/llwindow/llwindow.h +++ b/indra/llwindow/llwindow.h @@ -184,6 +184,8 @@ public: virtual void interruptLanguageTextInput() {} virtual void spawnWebBrowser(const std::string& escaped_url, bool async) {}; + virtual void openFolder(const std::string &path) {}; + static std::vector<std::string> getDynamicFallbackFontList(); // Provide native key event data diff --git a/indra/llwindow/llwindowmacosx-objc.h b/indra/llwindow/llwindowmacosx-objc.h index d9d8bfce1f..620bbc8876 100644 --- a/indra/llwindow/llwindowmacosx-objc.h +++ b/indra/llwindow/llwindowmacosx-objc.h @@ -178,6 +178,8 @@ void setMarkedText(unsigned short *text, unsigned int *selectedRange, unsigned i void getPreeditLocation(float *location, unsigned int length); void allowDirectMarkedTextInput(bool allow, GLViewRef glView); +void openFolderWithFinder(const char *folder_path); + NSWindowRef getMainAppWindow(); GLViewRef getGLView(); diff --git a/indra/llwindow/llwindowmacosx-objc.mm b/indra/llwindow/llwindowmacosx-objc.mm index 2e75d309ea..01feac7885 100644 --- a/indra/llwindow/llwindowmacosx-objc.mm +++ b/indra/llwindow/llwindowmacosx-objc.mm @@ -463,6 +463,13 @@ long showAlert(std::string text, std::string title, int type) return ret; } +void openFolderWithFinder(const char *folder_path) +{ + @autoreleasepool { + NSString *folderPathString = [NSString stringWithUTF8String:folder_path]; + [[NSWorkspace sharedWorkspace] openFile:folderPathString withApplication:@"Finder"]; + } +} /* GLViewRef getGLView() { diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp index 80001b14ee..e95ad4d970 100644 --- a/indra/llwindow/llwindowmacosx.cpp +++ b/indra/llwindow/llwindowmacosx.cpp @@ -221,14 +221,14 @@ bool callKeyDown(NSKeyEventRef event, unsigned short key, unsigned int mask, wch { //if (mask!=MASK_NONE) { - if((key == gKeyboard->inverseTranslateKey('Z')) && (character == 'y')) - { - key = gKeyboard->inverseTranslateKey('Y'); - } - else if ((key == gKeyboard->inverseTranslateKey('Y')) && (character == 'z')) - { - key = gKeyboard->inverseTranslateKey('Z'); - } + if((key == gKeyboard->inverseTranslateKey('Z')) && (character == 'y')) + { + key = gKeyboard->inverseTranslateKey('Y'); + } + else if ((key == gKeyboard->inverseTranslateKey('Y')) && (character == 'z')) + { + key = gKeyboard->inverseTranslateKey('Z'); + } } mRawKeyEvent = event; @@ -2556,6 +2556,11 @@ F32 LLWindowMacOSX::getSystemUISize() return gHiDPISupport ? ::getDeviceUnitSize(mGLView) : LLWindow::getSystemUISize(); } +void LLWindowMacOSX::openFolder(const std::string &path) +{ + openFolderWithFinder(path.c_str()); +} + #if LL_OS_DRAGDROP_ENABLED /* S16 LLWindowMacOSX::dragTrackingHandler(DragTrackingMessage message, WindowRef theWindow, diff --git a/indra/llwindow/llwindowmacosx.h b/indra/llwindow/llwindowmacosx.h index f5b6441746..211ae872c6 100644 --- a/indra/llwindow/llwindowmacosx.h +++ b/indra/llwindow/llwindowmacosx.h @@ -113,6 +113,8 @@ public: void spawnWebBrowser(const std::string& escaped_url, bool async) override; F32 getSystemUISize() override; + void openFolder(const std::string &path) override; + bool getInputDevices(U32 device_type_filter, std::function<bool(std::string&, LLSD&, void*)> osx_callback, void* win_callback, diff --git a/indra/llwindow/llwindowsdl.cpp b/indra/llwindow/llwindowsdl.cpp index 7433ad6bd2..89f0d152c6 100644 --- a/indra/llwindow/llwindowsdl.cpp +++ b/indra/llwindow/llwindowsdl.cpp @@ -25,8 +25,6 @@ * $/LicenseInfo$ */ -#if LL_SDL - #include "linden_common.h" #include "llwindowsdl.h" @@ -40,12 +38,9 @@ #include "lldir.h" #include "llfindlocale.h" -#if LL_GTK -extern "C" { -# include "gtk/gtk.h" -} -#include <locale.h> -#endif // LL_GTK +#ifdef LL_GLIB +#include <glib.h> +#endif extern "C" { # include "fontconfig/fontconfig.h" @@ -57,6 +52,7 @@ extern "C" { # include <unistd.h> # include <sys/types.h> # include <sys/wait.h> +# include <stdio.h> #endif // LL_LINUX extern bool gDebugWindowProc; @@ -97,100 +93,277 @@ void maybe_unlock_display(void) } -#if LL_GTK -// Lazily initialize and check the runtime GTK version for goodness. +#if LL_X11 // static -bool LLWindowSDL::ll_try_gtk_init(void) +Window LLWindowSDL::get_SDL_XWindowID(void) { - static bool done_gtk_diag = false; - static bool gtk_is_good = false; - static bool done_setlocale = false; - static bool tried_gtk_init = false; + if (gWindowImplementation) { + return gWindowImplementation->mSDL_XWindowID; + } + return None; +} - if (!done_setlocale) +//static +Display* LLWindowSDL::get_SDL_Display(void) +{ + if (gWindowImplementation) { + return gWindowImplementation->mSDL_Display; + } + return NULL; +} +#endif // LL_X11 + +#if LL_X11 + +// Clipboard handing via native X11, base on the implementation in Cool VL by Henri Beauchamp + +namespace +{ + std::array<Atom, 3> gSupportedAtoms; + + Atom XA_CLIPBOARD; + Atom XA_TARGETS; + Atom PVT_PASTE_BUFFER; + long const MAX_PASTE_BUFFER_SIZE = 16383; + + void filterSelectionRequest( XEvent aEvent ) { - LL_INFOS() << "Starting GTK Initialization." << LL_ENDL; - maybe_lock_display(); - gtk_disable_setlocale(); - maybe_unlock_display(); - done_setlocale = true; + auto *display = LLWindowSDL::getSDLDisplay(); + auto &request = aEvent.xselectionrequest; + + XSelectionEvent reply { SelectionNotify, aEvent.xany.serial, aEvent.xany.send_event, display, + request.requestor, request.selection, request.target, + request.property,request.time }; + + if (request.target == XA_TARGETS) + { + XChangeProperty(display, request.requestor, request.property, + XA_ATOM, 32, PropModeReplace, + (unsigned char *) &gSupportedAtoms.front(), gSupportedAtoms.size()); + } + else if (std::find(gSupportedAtoms.begin(), gSupportedAtoms.end(), request.target) != + gSupportedAtoms.end()) + { + std::string utf8; + if (request.selection == XA_PRIMARY) + utf8 = wstring_to_utf8str(gWindowImplementation->getPrimaryText()); + else + utf8 = wstring_to_utf8str(gWindowImplementation->getSecondaryText()); + + XChangeProperty(display, request.requestor, request.property, + request.target, 8, PropModeReplace, + (unsigned char *) utf8.c_str(), utf8.length()); + } + else if (request.selection == XA_CLIPBOARD) + { + // Did not have what they wanted, so no property set + reply.property = None; + } + else + return; + + XSendEvent(request.display, request.requestor, False, NoEventMask, (XEvent *) &reply); + XSync(display, False); } - if (!tried_gtk_init) + void filterSelectionClearRequest( XEvent aEvent ) { - tried_gtk_init = true; - if (!g_thread_supported ()) g_thread_init (NULL); - maybe_lock_display(); - gtk_is_good = gtk_init_check(NULL, NULL); - maybe_unlock_display(); - if (!gtk_is_good) - LL_WARNS() << "GTK Initialization failed." << LL_ENDL; + auto &request = aEvent.xselectionrequest; + if (request.selection == XA_PRIMARY) + gWindowImplementation->clearPrimaryText(); + else if (request.selection == XA_CLIPBOARD) + gWindowImplementation->clearSecondaryText(); + } + + int x11_clipboard_filter(void*, SDL_Event *evt) + { + Display *display = LLWindowSDL::getSDLDisplay(); + if (!display) + return 1; + + if (evt->type != SDL_SYSWMEVENT) + return 1; + + auto xevent = evt->syswm.msg->msg.x11.event; + + if (xevent.type == SelectionRequest) + filterSelectionRequest( xevent ); + else if (xevent.type == SelectionClear) + filterSelectionClearRequest( xevent ); + return 1; } - if (gtk_is_good && !done_gtk_diag) + bool grab_property(Display* display, Window window, Atom selection, Atom target) { - LL_INFOS() << "GTK Initialized." << LL_ENDL; - LL_INFOS() << "- Compiled against GTK version " - << GTK_MAJOR_VERSION << "." - << GTK_MINOR_VERSION << "." - << GTK_MICRO_VERSION << LL_ENDL; - LL_INFOS() << "- Running against GTK version " - << gtk_major_version << "." - << gtk_minor_version << "." - << gtk_micro_version << LL_ENDL; + if( !display ) + return false; + maybe_lock_display(); - const gchar* gtk_warning = gtk_check_version( - GTK_MAJOR_VERSION, - GTK_MINOR_VERSION, - GTK_MICRO_VERSION); + + XDeleteProperty(display, window, PVT_PASTE_BUFFER); + XFlush(display); + + XConvertSelection(display, selection, target, PVT_PASTE_BUFFER, window, CurrentTime); + + // Unlock the connection so that the SDL event loop may function maybe_unlock_display(); - if (gtk_warning) + + const auto start{ SDL_GetTicks() }; + const auto end{ start + 1000 }; + + XEvent xevent {}; + bool response = false; + + do { - LL_WARNS() << "- GTK COMPATIBILITY WARNING: " << - gtk_warning << LL_ENDL; - gtk_is_good = false; - } else { - LL_INFOS() << "- GTK version is good." << LL_ENDL; - } + SDL_Event event {}; + + // Wait for an event + SDL_WaitEvent(&event); + + // If the event is a window manager event + if (event.type == SDL_SYSWMEVENT) + { + xevent = event.syswm.msg->msg.x11.event; + + if (xevent.type == SelectionNotify && xevent.xselection.requestor == window) + response = true; + } + } while (!response && SDL_GetTicks() < end ); - done_gtk_diag = true; + return response && xevent.xselection.property != None; } +} + +void LLWindowSDL::initialiseX11Clipboard() +{ + if (!mSDL_Display) + return; + + SDL_EventState(SDL_SYSWMEVENT, SDL_ENABLE); + SDL_SetEventFilter(x11_clipboard_filter, nullptr); - return gtk_is_good; + maybe_lock_display(); + + XA_CLIPBOARD = XInternAtom(mSDL_Display, "CLIPBOARD", False); + + gSupportedAtoms[0] = XInternAtom(mSDL_Display, "UTF8_STRING", False); + gSupportedAtoms[1] = XInternAtom(mSDL_Display, "COMPOUND_TEXT", False); + gSupportedAtoms[2] = XA_STRING; + + // TARGETS atom + XA_TARGETS = XInternAtom(mSDL_Display, "TARGETS", False); + + // SL_PASTE_BUFFER atom + PVT_PASTE_BUFFER = XInternAtom(mSDL_Display, "FS_PASTE_BUFFER", False); + + maybe_unlock_display(); } -#endif // LL_GTK +bool LLWindowSDL::getSelectionText( Atom aSelection, Atom aType, LLWString &text ) +{ + if( !mSDL_Display ) + return false; -#if LL_X11 -// static -Window LLWindowSDL::get_SDL_XWindowID(void) + if( !grab_property(mSDL_Display, mSDL_XWindowID, aSelection,aType ) ) + return false; + + maybe_lock_display(); + + Atom type; + int format{}; + unsigned long len{},remaining {}; + unsigned char* data = nullptr; + int res = XGetWindowProperty(mSDL_Display, mSDL_XWindowID, + PVT_PASTE_BUFFER, 0, MAX_PASTE_BUFFER_SIZE, False, + AnyPropertyType, &type, &format, &len, + &remaining, &data); + if (data && len) + { + text = LLWString( + utf8str_to_wstring(reinterpret_cast< char const *>( data ) ) + ); + XFree(data); + } + + maybe_unlock_display(); + return res == Success; +} + +bool LLWindowSDL::getSelectionText(Atom selection, LLWString& text) { - if (gWindowImplementation) { - return gWindowImplementation->mSDL_XWindowID; + if (!mSDL_Display) + return false; + + maybe_lock_display(); + + Window owner = XGetSelectionOwner(mSDL_Display, selection); + if (owner == None) + { + if (selection == XA_PRIMARY) + { + owner = DefaultRootWindow(mSDL_Display); + selection = XA_CUT_BUFFER0; + } + else + { + maybe_unlock_display(); + return false; + } } - return None; + + maybe_unlock_display(); + + for( Atom atom : gSupportedAtoms ) + { + if(getSelectionText(selection, atom, text ) ) + return true; + } + + return false; } -//static -Display* LLWindowSDL::get_SDL_Display(void) +bool LLWindowSDL::setSelectionText(Atom selection, const LLWString& text) { - if (gWindowImplementation) { - return gWindowImplementation->mSDL_Display; + maybe_lock_display(); + + if (selection == XA_PRIMARY) + { + std::string utf8 = wstring_to_utf8str(text); + XStoreBytes(mSDL_Display, utf8.c_str(), utf8.length() + 1); + mPrimaryClipboard = text; } - return NULL; + else + mSecondaryClipboard = text; + + XSetSelectionOwner(mSDL_Display, selection, mSDL_XWindowID, CurrentTime); + + auto owner = XGetSelectionOwner(mSDL_Display, selection); + + maybe_unlock_display(); + + return owner == mSDL_XWindowID; } -#endif // LL_X11 + +Display* LLWindowSDL::getSDLDisplay() +{ + if (gWindowImplementation) + return gWindowImplementation->mSDL_Display; + return nullptr; +} + +#endif LLWindowSDL::LLWindowSDL(LLWindowCallbacks* callbacks, - const std::string& title, S32 x, S32 y, S32 width, - S32 height, U32 flags, - bool fullscreen, bool clearBg, - bool disable_vsync, bool use_gl, - bool ignore_pixel_depth, U32 fsaa_samples) - : LLWindow(callbacks, fullscreen, flags), - Lock_Display(NULL), - Unlock_Display(NULL), mGamma(1.0f) + const std::string& title, S32 x, S32 y, S32 width, + S32 height, U32 flags, + bool fullscreen, bool clearBg, + bool enable_vsync, bool use_gl, + bool ignore_pixel_depth, U32 fsaa_samples) + : LLWindow(callbacks, fullscreen, flags), + Lock_Display(NULL), + Unlock_Display(NULL), mGamma(1.0f) { // Initialize the keyboard gKeyboard = new LLKeyboardSDL(); @@ -199,6 +372,7 @@ LLWindowSDL::LLWindowSDL(LLWindowCallbacks* callbacks, // Ignore use_gl for now, only used for drones on PC mWindow = NULL; + mContext = {}; mNeedsResize = false; mOverrideAspectRatio = 0.f; mGrabbyKeyFlags = 0; @@ -209,16 +383,9 @@ LLWindowSDL::LLWindowSDL(LLWindowCallbacks* callbacks, #if LL_X11 mSDL_XWindowID = None; - mSDL_Display = NULL; + mSDL_Display = nullptr; #endif // LL_X11 -#if LL_GTK - // We MUST be the first to initialize GTK so that GTK doesn't get badly - // initialized with a non-C locale and cause lots of serious random - // weirdness. - ll_try_gtk_init(); -#endif // LL_GTK - // Assume 4:3 aspect ratio until we know better mOriginalAspectRatio = 1024.0 / 768.0; @@ -228,7 +395,7 @@ LLWindowSDL::LLWindowSDL(LLWindowCallbacks* callbacks, mWindowTitle = title; // Create the GL context and set it up for windowed or fullscreen, as appropriate. - if(createContext(x, y, width, height, 32, fullscreen, disable_vsync)) + if(createContext(x, y, width, height, 32, fullscreen, enable_vsync)) { gGLManager.initGL(); @@ -244,9 +411,9 @@ LLWindowSDL::LLWindowSDL(LLWindowCallbacks* callbacks, #if LL_X11 mFlashing = false; + initialiseX11Clipboard(); #endif // LL_X11 - mKeyScanCode = 0; mKeyVirtualKey = 0; mKeyModifiers = KMOD_NONE; } @@ -258,10 +425,10 @@ static SDL_Surface *Load_BMP_Resource(const char *basename) // Figure out where our BMP is living on the disk snprintf(path_buffer, PATH_BUFFER_SIZE-1, "%s%sres-sdl%s%s", - gDirUtilp->getAppRODataDir().c_str(), - gDirUtilp->getDirDelimiter().c_str(), - gDirUtilp->getDirDelimiter().c_str(), - basename); + gDirUtilp->getAppRODataDir().c_str(), + gDirUtilp->getDirDelimiter().c_str(), + gDirUtilp->getDirDelimiter().c_str(), + basename); path_buffer[PATH_BUFFER_SIZE-1] = '\0'; return SDL_LoadBMP(path_buffer); @@ -348,7 +515,7 @@ static int x11_detect_VRAM_kb() if (fp) { LL_INFOS() << "Looking in " << fname - << " for VRAM info..." << LL_ENDL; + << " for VRAM info..." << LL_ENDL; rtn = x11_detect_VRAM_kb_fp(fp, ": VideoRAM: "); fclose(fp); if (0 == rtn) @@ -373,7 +540,7 @@ static int x11_detect_VRAM_kb() else { LL_INFOS() << "Could not open " << fname - << " - skipped." << LL_ENDL; + << " - skipped." << LL_ENDL; // Try old XFree86 log otherwise fname = x_log_location; fname += "XFree86."; @@ -383,7 +550,7 @@ static int x11_detect_VRAM_kb() if (fp) { LL_INFOS() << "Looking in " << fname - << " for VRAM info..." << LL_ENDL; + << " for VRAM info..." << LL_ENDL; rtn = x11_detect_VRAM_kb_fp(fp, ": VideoRAM: "); fclose(fp); if (0 == rtn) @@ -399,95 +566,160 @@ static int x11_detect_VRAM_kb() else { LL_INFOS() << "Could not open " << fname - << " - skipped." << LL_ENDL; + << " - skipped." << LL_ENDL; } } return rtn; } #endif // LL_X11 -bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, bool fullscreen, bool disable_vsync) +void LLWindowSDL::setTitle(const std::string title) +{ + SDL_SetWindowTitle( mWindow, title.c_str() ); +} + +void LLWindowSDL::tryFindFullscreenSize( int &width, int &height ) +{ + LL_INFOS() << "createContext: setting up fullscreen " << width << "x" << height << LL_ENDL; + + // If the requested width or height is 0, find the best default for the monitor. + if((width == 0) || (height == 0)) + { + // Scan through the list of modes, looking for one which has: + // height between 700 and 800 + // aspect ratio closest to the user's original mode + S32 resolutionCount = 0; + LLWindowResolution *resolutionList = getSupportedResolutions(resolutionCount); + + if(resolutionList != NULL) + { + F32 closestAspect = 0; + U32 closestHeight = 0; + U32 closestWidth = 0; + int i; + + LL_INFOS() << "createContext: searching for a display mode, original aspect is " << mOriginalAspectRatio << LL_ENDL; + + for(i=0; i < resolutionCount; i++) + { + F32 aspect = (F32)resolutionList[i].mWidth / (F32)resolutionList[i].mHeight; + + LL_INFOS() << "createContext: width " << resolutionList[i].mWidth << " height " << resolutionList[i].mHeight << " aspect " << aspect << LL_ENDL; + + if( (resolutionList[i].mHeight >= 700) && (resolutionList[i].mHeight <= 800) && + (fabs(aspect - mOriginalAspectRatio) < fabs(closestAspect - mOriginalAspectRatio))) + { + LL_INFOS() << " (new closest mode) " << LL_ENDL; + + // This is the closest mode we've seen yet. + closestWidth = resolutionList[i].mWidth; + closestHeight = resolutionList[i].mHeight; + closestAspect = aspect; + } + } + + width = closestWidth; + height = closestHeight; + } + } + + if((width == 0) || (height == 0)) + { + // Mode search failed for some reason. Use the old-school default. + width = 1024; + height = 768; + } +} + +bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, bool fullscreen, bool enable_vsync) { //bool glneedsinit = false; LL_INFOS() << "createContext, fullscreen=" << fullscreen << - " size=" << width << "x" << height << LL_ENDL; + " size=" << width << "x" << height << LL_ENDL; // captures don't survive contexts mGrabbyKeyFlags = 0; mReallyCapturedCount = 0; - if (SDL_Init(SDL_INIT_VIDEO) < 0) + std::initializer_list<std::tuple< char const*, char const * > > hintList = + { + {SDL_HINT_VIDEO_X11_NET_WM_BYPASS_COMPOSITOR,"0"}, + {SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH,"1"}, + {SDL_HINT_IME_INTERNAL_EDITING,"1"} + }; + + for( auto hint: hintList ) { - LL_INFOS() << "sdl_init() failed! " << SDL_GetError() << LL_ENDL; - setupFailure("sdl_init() failure, window creation error", "error", OSMB_OK); - return false; + SDL_SetHint( std::get<0>(hint), std::get<1>(hint)); + } + + std::initializer_list<std::tuple<uint32_t, char const*, bool>> initList= + { {SDL_INIT_VIDEO,"SDL_INIT_VIDEO", true}, + {SDL_INIT_AUDIO,"SDL_INIT_AUDIO", false}, + {SDL_INIT_GAMECONTROLLER,"SDL_INIT_GAMECONTROLLER", false}, + {SDL_INIT_SENSOR,"SDL_INIT_SENSOR", false} + }; + + for( auto subSystem : initList) + { + if( SDL_InitSubSystem( std::get<0>(subSystem) ) < 0 ) + { + LL_WARNS() << "SDL_InitSubSystem for " << std::get<1>(subSystem) << " failed " << SDL_GetError() << LL_ENDL; + + if( std::get<2>(subSystem)) + setupFailure("SDL_Init() failure", "error", OSMB_OK); + + } } SDL_version c_sdl_version; SDL_VERSION(&c_sdl_version); LL_INFOS() << "Compiled against SDL " - << int(c_sdl_version.major) << "." - << int(c_sdl_version.minor) << "." - << int(c_sdl_version.patch) << LL_ENDL; - const SDL_version *r_sdl_version; - r_sdl_version = SDL_Linked_Version(); + << int(c_sdl_version.major) << "." + << int(c_sdl_version.minor) << "." + << int(c_sdl_version.patch) << LL_ENDL; + SDL_version r_sdl_version; + SDL_GetVersion(&r_sdl_version); LL_INFOS() << " Running against SDL " - << int(r_sdl_version->major) << "." - << int(r_sdl_version->minor) << "." - << int(r_sdl_version->patch) << LL_ENDL; + << int(r_sdl_version.major) << "." + << int(r_sdl_version.minor) << "." + << int(r_sdl_version.patch) << LL_ENDL; - const SDL_VideoInfo *video_info = SDL_GetVideoInfo( ); - if (!video_info) - { - LL_INFOS() << "SDL_GetVideoInfo() failed! " << SDL_GetError() << LL_ENDL; - setupFailure("SDL_GetVideoInfo() failed, Window creation error", "Error", OSMB_OK); - return false; - } + if (width == 0) + width = 1024; + if (height == 0) + width = 768; - if (video_info->current_h > 0) - { - mOriginalAspectRatio = (float)video_info->current_w / (float)video_info->current_h; - LL_INFOS() << "Original aspect ratio was " << video_info->current_w << ":" << video_info->current_h << "=" << mOriginalAspectRatio << LL_ENDL; - } + mFullscreen = fullscreen; - SDL_EnableUNICODE(1); - SDL_WM_SetCaption(mWindowTitle.c_str(), mWindowTitle.c_str()); + int sdlflags = SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE; - // Set the application icon. - SDL_Surface *bmpsurface; - bmpsurface = Load_BMP_Resource("ll_icon.BMP"); - if (bmpsurface) + if( mFullscreen ) { - // This attempts to give a black-keyed mask to the icon. - SDL_SetColorKey(bmpsurface, - SDL_SRCCOLORKEY, - SDL_MapRGB(bmpsurface->format, 0,0,0) ); - SDL_WM_SetIcon(bmpsurface, NULL); - // The SDL examples cheerfully avoid freeing the icon - // surface, but I'm betting that's leaky. - SDL_FreeSurface(bmpsurface); - bmpsurface = NULL; + sdlflags |= SDL_WINDOW_FULLSCREEN; + tryFindFullscreenSize( width, height ); } - // note: these SetAttributes make Tom's 9600-on-AMD64 fail to - // get a visual, but it's broken anyway when it does, and without - // these SetAttributes we might easily get an avoidable substandard - // visual to work with on most other machines. - SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8); - SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, (bits <= 16) ? 16 : 24); - // We need stencil support for a few (minor) things. - if (!getenv("LL_GL_NO_STENCIL")) - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); - SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, (bits <= 16) ? 1 : 8); + mSDLFlags = sdlflags; - // *FIX: try to toggle vsync here? + GLint redBits{8}, greenBits{8}, blueBits{8}, alphaBits{8}; - mFullscreen = fullscreen; + GLint depthBits{(bits <= 16) ? 16 : 24}, stencilBits{8}; + + if (getenv("LL_GL_NO_STENCIL")) + stencilBits = 0; - int sdlflags = SDL_OPENGL | SDL_RESIZABLE | SDL_ANYFORMAT; + SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, alphaBits); + SDL_GL_SetAttribute(SDL_GL_RED_SIZE, redBits); + SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, greenBits); + SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, blueBits); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, depthBits ); + + // We need stencil support for a few (minor) things. + if (stencilBits) + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, stencilBits); + // *FIX: try to toggle vsync here? SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); @@ -497,80 +729,39 @@ bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, b SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, mFSAASamples); } - mSDLFlags = sdlflags; + SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); + mWindow = SDL_CreateWindow( mWindowTitle.c_str(), SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, mSDLFlags ); - if (mFullscreen) + if( mWindow ) { - LL_INFOS() << "createContext: setting up fullscreen " << width << "x" << height << LL_ENDL; - - // If the requested width or height is 0, find the best default for the monitor. - if((width == 0) || (height == 0)) - { - // Scan through the list of modes, looking for one which has: - // height between 700 and 800 - // aspect ratio closest to the user's original mode - S32 resolutionCount = 0; - LLWindowResolution *resolutionList = getSupportedResolutions(resolutionCount); - - if(resolutionList != NULL) - { - F32 closestAspect = 0; - U32 closestHeight = 0; - U32 closestWidth = 0; - int i; - - LL_INFOS() << "createContext: searching for a display mode, original aspect is " << mOriginalAspectRatio << LL_ENDL; - - for(i=0; i < resolutionCount; i++) - { - F32 aspect = (F32)resolutionList[i].mWidth / (F32)resolutionList[i].mHeight; - - LL_INFOS() << "createContext: width " << resolutionList[i].mWidth << " height " << resolutionList[i].mHeight << " aspect " << aspect << LL_ENDL; + mContext = SDL_GL_CreateContext( mWindow ); - if( (resolutionList[i].mHeight >= 700) && (resolutionList[i].mHeight <= 800) && - (fabs(aspect - mOriginalAspectRatio) < fabs(closestAspect - mOriginalAspectRatio))) - { - LL_INFOS() << " (new closest mode) " << LL_ENDL; - - // This is the closest mode we've seen yet. - closestWidth = resolutionList[i].mWidth; - closestHeight = resolutionList[i].mHeight; - closestAspect = aspect; - } - } - - width = closestWidth; - height = closestHeight; - } - } - - if((width == 0) || (height == 0)) + if( mContext == 0 ) { - // Mode search failed for some reason. Use the old-school default. - width = 1024; - height = 768; + LL_WARNS() << "Cannot create GL context " << SDL_GetError() << LL_ENDL; + setupFailure("GL Context creation error creation error", "Error", OSMB_OK); + return false; } + // SDL_GL_SetSwapInterval(1); + mSurface = SDL_GetWindowSurface( mWindow ); + } - mWindow = SDL_SetVideoMode(width, height, bits, sdlflags | SDL_FULLSCREEN); - if (!mWindow && bits > 16) - { - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); - mWindow = SDL_SetVideoMode(width, height, bits, sdlflags | SDL_FULLSCREEN); - } - if (mWindow) + if( mFullscreen ) + { + if (mSurface) { mFullscreen = true; - mFullscreenWidth = mWindow->w; - mFullscreenHeight = mWindow->h; - mFullscreenBits = mWindow->format->BitsPerPixel; + mFullscreenWidth = mSurface->w; + mFullscreenHeight = mSurface->h; + mFullscreenBits = mSurface->format->BitsPerPixel; mFullscreenRefresh = -1; LL_INFOS() << "Running at " << mFullscreenWidth - << "x" << mFullscreenHeight - << "x" << mFullscreenBits - << " @ " << mFullscreenRefresh - << LL_ENDL; + << "x" << mFullscreenHeight + << "x" << mFullscreenBits + << " @ " << mFullscreenRefresh + << LL_ENDL; } else { @@ -584,33 +775,27 @@ bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, b std::string error = llformat("Unable to run fullscreen at %d x %d.\nRunning in window.", width, height); OSMessageBox(error, "Error", OSMB_OK); + return false; } } - - if(!mFullscreen && (mWindow == NULL)) + else { - if (width == 0) - width = 1024; - if (height == 0) - width = 768; - - LL_INFOS() << "createContext: creating window " << width << "x" << height << "x" << bits << LL_ENDL; - mWindow = SDL_SetVideoMode(width, height, bits, sdlflags); - if (!mWindow && bits > 16) - { - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); - mWindow = SDL_SetVideoMode(width, height, bits, sdlflags); - } - if (!mWindow) { LL_WARNS() << "createContext: window creation failure. SDL: " << SDL_GetError() << LL_ENDL; setupFailure("Window creation error", "Error", OSMB_OK); return false; } - } else if (!mFullscreen && (mWindow != NULL)) + } + + // Set the application icon. + SDL_Surface *bmpsurface; + bmpsurface = Load_BMP_Resource("ll_icon.BMP"); + if (bmpsurface) { - LL_INFOS() << "createContext: SKIPPING - !fullscreen, but +mWindow " << width << "x" << height << "x" << bits << LL_ENDL; + SDL_SetWindowIcon(mWindow, bmpsurface); + SDL_FreeSurface(bmpsurface); + bmpsurface = NULL; } // Detect video memory size. @@ -625,7 +810,7 @@ bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, b // fallback to letting SDL detect VRAM. // note: I've not seen SDL's detection ever actually find // VRAM != 0, but if SDL *does* detect it then that's a bonus. - gGLManager.mVRAM = video_info->video_mem / 1024; + gGLManager.mVRAM = 0; if (gGLManager.mVRAM != 0) { LL_INFOS() << "SDL detected " << gGLManager.mVRAM << "MB VRAM." << LL_ENDL; @@ -637,22 +822,20 @@ bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, b // explicitly unsupported cards. //const char* RENDERER = (const char*) glGetString(GL_RENDERER); - GLint depthBits, stencilBits, redBits, greenBits, blueBits, alphaBits; - - glGetIntegerv(GL_RED_BITS, &redBits); - glGetIntegerv(GL_GREEN_BITS, &greenBits); - glGetIntegerv(GL_BLUE_BITS, &blueBits); - glGetIntegerv(GL_ALPHA_BITS, &alphaBits); - glGetIntegerv(GL_DEPTH_BITS, &depthBits); - glGetIntegerv(GL_STENCIL_BITS, &stencilBits); + SDL_GL_GetAttribute(SDL_GL_RED_SIZE, &redBits); + SDL_GL_GetAttribute(SDL_GL_GREEN_SIZE, &greenBits); + SDL_GL_GetAttribute(SDL_GL_BLUE_SIZE, &blueBits); + SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &alphaBits); + SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &depthBits); + SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &stencilBits); LL_INFOS() << "GL buffer:" << LL_ENDL; - LL_INFOS() << " Red Bits " << S32(redBits) << LL_ENDL; - LL_INFOS() << " Green Bits " << S32(greenBits) << LL_ENDL; - LL_INFOS() << " Blue Bits " << S32(blueBits) << LL_ENDL; - LL_INFOS() << " Alpha Bits " << S32(alphaBits) << LL_ENDL; - LL_INFOS() << " Depth Bits " << S32(depthBits) << LL_ENDL; - LL_INFOS() << " Stencil Bits " << S32(stencilBits) << LL_ENDL; + LL_INFOS() << " Red Bits " << S32(redBits) << LL_ENDL; + LL_INFOS() << " Green Bits " << S32(greenBits) << LL_ENDL; + LL_INFOS() << " Blue Bits " << S32(blueBits) << LL_ENDL; + LL_INFOS() << " Alpha Bits " << S32(alphaBits) << LL_ENDL; + LL_INFOS() << " Depth Bits " << S32(depthBits) << LL_ENDL; + LL_INFOS() << " Stencil Bits " << S32(stencilBits) << LL_ENDL; GLint colorBits = redBits + greenBits + blueBits + alphaBits; // fixme: actually, it's REALLY important for picking that we get at @@ -662,76 +845,53 @@ bool LLWindowSDL::createContext(int x, int y, int width, int height, int bits, b { close(); setupFailure( - "Second Life requires True Color (32-bit) to run in a window.\n" - "Please go to Control Panels -> Display -> Settings and\n" - "set the screen to 32-bit color.\n" - "Alternately, if you choose to run fullscreen, Second Life\n" - "will automatically adjust the screen each time it runs.", - "Error", - OSMB_OK); - return false; - } - -#if 0 // *FIX: we're going to brave it for now... - if (alphaBits < 8) - { - close(); - setupFailure( - "Second Life is unable to run because it can't get an 8 bit alpha\n" - "channel. Usually this is due to video card driver issues.\n" - "Please make sure you have the latest video card drivers installed.\n" - "Also be sure your monitor is set to True Color (32-bit) in\n" - "Control Panels -> Display -> Settings.\n" - "If you continue to receive this message, contact customer service.", - "Error", - OSMB_OK); + "Second Life requires True Color (32-bit) to run in a window.\n" + "Please go to Control Panels -> Display -> Settings and\n" + "set the screen to 32-bit color.\n" + "Alternately, if you choose to run fullscreen, Second Life\n" + "will automatically adjust the screen each time it runs.", + "Error", + OSMB_OK); return false; } -#endif #if LL_X11 /* Grab the window manager specific information */ SDL_SysWMinfo info; SDL_VERSION(&info.version); - if ( SDL_GetWMInfo(&info) ) + if ( SDL_GetWindowWMInfo(mWindow, &info) ) { /* Save the information for later use */ if ( info.subsystem == SDL_SYSWM_X11 ) { mSDL_Display = info.info.x11.display; - mSDL_XWindowID = info.info.x11.wmwindow; - Lock_Display = info.info.x11.lock_func; - Unlock_Display = info.info.x11.unlock_func; + mSDL_XWindowID = info.info.x11.window; } else { LL_WARNS() << "We're not running under X11? Wild." - << LL_ENDL; + << LL_ENDL; } } else { LL_WARNS() << "We're not running under any known WM. Wild." - << LL_ENDL; + << LL_ENDL; } #endif // LL_X11 + SDL_StartTextInput(); //make sure multisampling is disabled by default glDisable(GL_MULTISAMPLE_ARB); - // We need to do this here, once video is init'd - if (-1 == SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, - SDL_DEFAULT_REPEAT_INTERVAL)) - LL_WARNS() << "Couldn't enable key-repeat: " << SDL_GetError() <<LL_ENDL; - // Don't need to get the current gamma, since there's a call that restores it to the system defaults. return true; } // changing fullscreen resolution, or switching between windowed and fullscreen mode. -bool LLWindowSDL::switchContext(bool fullscreen, const LLCoordScreen &size, bool disable_vsync, const LLCoordScreen * const posp) +bool LLWindowSDL::switchContext(bool fullscreen, const LLCoordScreen &size, bool enable_vsync, const LLCoordScreen * const posp) { const bool needsRebuild = true; // Just nuke the context and start over. bool result = true; @@ -741,7 +901,7 @@ bool LLWindowSDL::switchContext(bool fullscreen, const LLCoordScreen &size, bool if(needsRebuild) { destroyContext(); - result = createContext(0, 0, size.mX, size.mY, 0, fullscreen, disable_vsync); + result = createContext(0, 0, size.mX, size.mY, 0, fullscreen, enable_vsync); if (result) { gGLManager.initGL(); @@ -761,6 +921,7 @@ void LLWindowSDL::destroyContext() { LL_INFOS() << "destroyContext begins" << LL_ENDL; + SDL_StopTextInput(); #if LL_X11 mSDL_Display = NULL; mSDL_XWindowID = None; @@ -894,11 +1055,11 @@ bool LLWindowSDL::getPosition(LLCoordScreen *position) bool LLWindowSDL::getSize(LLCoordScreen *size) { - if (mWindow) + if (mSurface) { - size->mX = mWindow->w; - size->mY = mWindow->h; - return (true); + size->mX = mSurface->w; + size->mY = mSurface->h; + return (true); } return (false); @@ -906,11 +1067,11 @@ bool LLWindowSDL::getSize(LLCoordScreen *size) bool LLWindowSDL::getSize(LLCoordWindow *size) { - if (mWindow) + if (mSurface) { - size->mX = mWindow->w; - size->mY = mWindow->h; - return (true); + size->mX = mSurface->w; + size->mY = mSurface->h; + return (true); } return (false); @@ -927,40 +1088,37 @@ bool LLWindowSDL::setPosition(const LLCoordScreen position) return true; } -bool LLWindowSDL::setSizeImpl(const LLCoordScreen size) +template< typename T > bool setSizeImpl( const T& newSize, SDL_Window *pWin ) { - if(mWindow) - { - // Push a resize event onto SDL's queue - we'll handle it - // when it comes out again. - SDL_Event event; - event.type = SDL_VIDEORESIZE; - event.resize.w = size.mX; - event.resize.h = size.mY; - SDL_PushEvent(&event); // copied into queue + if( !pWin ) + return false; - return true; - } + auto nFlags = SDL_GetWindowFlags( pWin ); - return false; + if( nFlags & SDL_WINDOW_MAXIMIZED ) + SDL_RestoreWindow( pWin ); + + + SDL_SetWindowSize( pWin, newSize.mX, newSize.mY ); + SDL_Event event; + event.type = SDL_WINDOWEVENT; + event.window.event = SDL_WINDOWEVENT_RESIZED; + event.window.windowID = SDL_GetWindowID( pWin ); + event.window.data1 = newSize.mX; + event.window.data2 = newSize.mY; + SDL_PushEvent( &event ); + + return true; } -bool LLWindowSDL::setSizeImpl(const LLCoordWindow size) +bool LLWindowSDL::setSizeImpl(const LLCoordScreen size) { - if(mWindow) - { - // Push a resize event onto SDL's queue - we'll handle it - // when it comes out again. - SDL_Event event; - event.type = SDL_VIDEORESIZE; - event.resize.w = size.mX; - event.resize.h = size.mY; - SDL_PushEvent(&event); // copied into queue - - return true; - } + return ::setSizeImpl( size, mWindow ); +} - return false; +bool LLWindowSDL::setSizeImpl(const LLCoordWindow size) +{ + return ::setSizeImpl( size, mWindow ); } @@ -968,7 +1126,7 @@ void LLWindowSDL::swapBuffers() { if (mWindow) { - SDL_GL_SwapBuffers(); + SDL_GL_SwapWindow( mWindow ); } } @@ -990,7 +1148,7 @@ F32 LLWindowSDL::getGamma() bool LLWindowSDL::restoreGamma() { //CGDisplayRestoreColorSyncSettings(); - SDL_SetGamma(1.0f, 1.0f, 1.0f); + // SDL_SetGamma(1.0f, 1.0f, 1.0f); return true; } @@ -999,7 +1157,7 @@ bool LLWindowSDL::setGamma(const F32 gamma) mGamma = gamma; if (mGamma == 0) mGamma = 0.1f; mGamma = 1/mGamma; - SDL_SetGamma(mGamma, mGamma, mGamma); + // SDL_SetGamma(mGamma, mGamma, mGamma); return true; } @@ -1048,7 +1206,7 @@ bool LLWindowSDL::setCursorPosition(const LLCoordWindow position) //LL_INFOS() << "setCursorPosition(" << screen_pos.mX << ", " << screen_pos.mY << ")" << LL_ENDL; // do the actual forced cursor move. - SDL_WarpMouse(screen_pos.mX, screen_pos.mY); + SDL_WarpMouseInWindow(mWindow, screen_pos.mX, screen_pos.mY); //LL_INFOS() << llformat("llcw %d,%d -> scr %d,%d", position.mX, position.mY, screen_pos.mX, screen_pos.mY) << LL_ENDL; @@ -1073,18 +1231,6 @@ bool LLWindowSDL::getCursorPosition(LLCoordWindow *position) F32 LLWindowSDL::getNativeAspectRatio() { -#if 0 - // RN: this hack presumes that the largest supported resolution is monitor-limited - // and that pixels in that mode are square, therefore defining the native aspect ratio - // of the monitor...this seems to work to a close approximation for most CRTs/LCDs - S32 num_resolutions; - LLWindowResolution* resolutions = getSupportedResolutions(num_resolutions); - - - return ((F32)resolutions[num_resolutions - 1].mWidth / (F32)resolutions[num_resolutions - 1].mHeight); - //rn: AC -#endif - // MBW -- there are a couple of bad assumptions here. One is that the display list won't include // ridiculous resolutions nobody would ever use. The other is that the list is in order. @@ -1146,7 +1292,7 @@ void LLWindowSDL::beforeDialog() // it only works in X11 if (running_x11 && mWindow) { - SDL_WM_ToggleFullScreen(mWindow); + SDL_SetWindowFullscreen( mWindow, 0 ); } } } @@ -1162,12 +1308,6 @@ void LLWindowSDL::beforeDialog() } #endif // LL_X11 -#if LL_GTK - // this is a good time to grab some GTK version information for - // diagnostics, if not already done. - ll_try_gtk_init(); -#endif // LL_GTK - maybe_lock_display(); } @@ -1188,7 +1328,7 @@ void LLWindowSDL::afterDialog() // in X11 if (running_x11 && mWindow) { - SDL_WM_ToggleFullScreen(mWindow); + SDL_SetWindowFullscreen( mWindow, 0 ); } } } @@ -1224,143 +1364,56 @@ void LLWindowSDL::x11_set_urgent(bool urgent) void LLWindowSDL::flashIcon(F32 seconds) { + if (getMinimized()) + { #if !LL_X11 - LL_INFOS() << "Stub LLWindowSDL::flashIcon(" << seconds << ")" << LL_ENDL; + LL_INFOS() << "Stub LLWindowSDL::flashIcon(" << seconds << ")" << LL_ENDL; #else - LL_INFOS() << "X11 LLWindowSDL::flashIcon(" << seconds << ")" << LL_ENDL; + LL_INFOS() << "X11 LLWindowSDL::flashIcon(" << seconds << ")" << LL_ENDL; - F32 remaining_time = mFlashTimer.getRemainingTimeF32(); - if (remaining_time < seconds) - remaining_time = seconds; - mFlashTimer.reset(); - mFlashTimer.setTimerExpirySec(remaining_time); + F32 remaining_time = mFlashTimer.getRemainingTimeF32(); + if (remaining_time < seconds) + remaining_time = seconds; + mFlashTimer.reset(); + mFlashTimer.setTimerExpirySec(remaining_time); - x11_set_urgent(true); - mFlashing = true; + x11_set_urgent(true); + mFlashing = true; #endif // LL_X11 -} - - -#if LL_GTK -bool LLWindowSDL::isClipboardTextAvailable() -{ - if (ll_try_gtk_init()) - { - GtkClipboard * const clipboard = - gtk_clipboard_get(GDK_NONE); - return gtk_clipboard_wait_is_text_available(clipboard) ? - true : false; - } - return false; // failure -} - -bool LLWindowSDL::pasteTextFromClipboard(LLWString &text) -{ - if (ll_try_gtk_init()) - { - GtkClipboard * const clipboard = - gtk_clipboard_get(GDK_NONE); - gchar * const data = gtk_clipboard_wait_for_text(clipboard); - if (data) - { - text = LLWString(utf8str_to_wstring(data)); - g_free(data); - return true; - } - } - return false; // failure -} - -bool LLWindowSDL::copyTextToClipboard(const LLWString &text) -{ - if (ll_try_gtk_init()) - { - const std::string utf8 = wstring_to_utf8str(text); - GtkClipboard * const clipboard = - gtk_clipboard_get(GDK_NONE); - gtk_clipboard_set_text(clipboard, utf8.c_str(), utf8.length()); - return true; - } - return false; // failure -} - - -bool LLWindowSDL::isPrimaryTextAvailable() -{ - if (ll_try_gtk_init()) - { - GtkClipboard * const clipboard = - gtk_clipboard_get(GDK_SELECTION_PRIMARY); - return gtk_clipboard_wait_is_text_available(clipboard) ? - true : false; - } - return false; // failure -} - -bool LLWindowSDL::pasteTextFromPrimary(LLWString &text) -{ - if (ll_try_gtk_init()) - { - GtkClipboard * const clipboard = - gtk_clipboard_get(GDK_SELECTION_PRIMARY); - gchar * const data = gtk_clipboard_wait_for_text(clipboard); - if (data) - { - text = LLWString(utf8str_to_wstring(data)); - g_free(data); - return true; - } - } - return false; // failure -} - -bool LLWindowSDL::copyTextToPrimary(const LLWString &text) -{ - if (ll_try_gtk_init()) - { - const std::string utf8 = wstring_to_utf8str(text); - GtkClipboard * const clipboard = - gtk_clipboard_get(GDK_SELECTION_PRIMARY); - gtk_clipboard_set_text(clipboard, utf8.c_str(), utf8.length()); - return true; } - return false; // failure } -#else - bool LLWindowSDL::isClipboardTextAvailable() { - return false; // unsupported + return mSDL_Display && XGetSelectionOwner(mSDL_Display, XA_CLIPBOARD) != None; } bool LLWindowSDL::pasteTextFromClipboard(LLWString &dst) { - return false; // unsupported + return getSelectionText(XA_CLIPBOARD, dst); } bool LLWindowSDL::copyTextToClipboard(const LLWString &s) { - return false; // unsupported + return setSelectionText(XA_CLIPBOARD, s); } bool LLWindowSDL::isPrimaryTextAvailable() { - return false; // unsupported + LLWString text; + return getSelectionText(XA_PRIMARY, text) && !text.empty(); } bool LLWindowSDL::pasteTextFromPrimary(LLWString &dst) { - return false; // unsupported + return getSelectionText(XA_PRIMARY, dst); } bool LLWindowSDL::copyTextToPrimary(const LLWString &s) { - return false; // unsupported + return setSelectionText(XA_PRIMARY, s); } -#endif // LL_GTK - LLWindow::LLWindowResolution* LLWindowSDL::getSupportedResolutions(S32 &num_resolutions) { if (!mSupportedResolutions) @@ -1368,33 +1421,30 @@ LLWindow::LLWindowResolution* LLWindowSDL::getSupportedResolutions(S32 &num_reso mSupportedResolutions = new LLWindowResolution[MAX_NUM_RESOLUTIONS]; mNumSupportedResolutions = 0; - SDL_Rect **modes = SDL_ListModes(NULL, SDL_OPENGL | SDL_FULLSCREEN); - if ( (modes != NULL) && (modes != ((SDL_Rect **) -1)) ) + // <FS:ND> Use display no from mWindow/mSurface here? + int max = SDL_GetNumDisplayModes(0); + max = llclamp( max, 0, MAX_NUM_RESOLUTIONS ); + + for( int i =0; i < max; ++i ) { - int count = 0; - while (*modes && count<MAX_NUM_RESOLUTIONS) // they're sorted biggest to smallest, so find end... + SDL_DisplayMode mode = { SDL_PIXELFORMAT_UNKNOWN, 0, 0, 0, 0 }; + if (SDL_GetDisplayMode( 0 , i, &mode) != 0) { - modes++; - count++; + continue; } - while (count--) + int w = mode.w; + int h = mode.h; + if ((w >= 800) && (h >= 600)) { - modes--; - SDL_Rect *r = *modes; - int w = r->w; - int h = r->h; - if ((w >= 800) && (h >= 600)) + // make sure we don't add the same resolution multiple times! + if ( (mNumSupportedResolutions == 0) || + ((mSupportedResolutions[mNumSupportedResolutions-1].mWidth != w) && + (mSupportedResolutions[mNumSupportedResolutions-1].mHeight != h)) ) { - // make sure we don't add the same resolution multiple times! - if ( (mNumSupportedResolutions == 0) || - ((mSupportedResolutions[mNumSupportedResolutions-1].mWidth != w) && - (mSupportedResolutions[mNumSupportedResolutions-1].mHeight != h)) ) - { - mSupportedResolutions[mNumSupportedResolutions].mWidth = w; - mSupportedResolutions[mNumSupportedResolutions].mHeight = h; - mNumSupportedResolutions++; - } + mSupportedResolutions[mNumSupportedResolutions].mWidth = w; + mSupportedResolutions[mNumSupportedResolutions].mHeight = h; + mNumSupportedResolutions++; } } } @@ -1410,7 +1460,7 @@ bool LLWindowSDL::convertCoords(LLCoordGL from, LLCoordWindow *to) return false; to->mX = from.mX; - to->mY = mWindow->h - from.mY - 1; + to->mY = mSurface->h - from.mY - 1; return true; } @@ -1421,7 +1471,7 @@ bool LLWindowSDL::convertCoords(LLCoordWindow from, LLCoordGL* to) return false; to->mX = from.mX; - to->mY = mWindow->h - from.mY - 1; + to->mY = mSurface->h - from.mY - 1; return true; } @@ -1482,13 +1532,13 @@ bool LLWindowSDL::SDLReallyCaptureInput(bool capture) else mReallyCapturedCount = 0; - SDL_GrabMode wantmode, newmode; + bool wantGrab; if (mReallyCapturedCount <= 0) // uncapture { - wantmode = SDL_GRAB_OFF; + wantGrab = false; } else // capture { - wantmode = SDL_GRAB_ON; + wantGrab = true; } if (mReallyCapturedCount < 0) // yuck, imbalance. @@ -1497,9 +1547,11 @@ bool LLWindowSDL::SDLReallyCaptureInput(bool capture) LL_WARNS() << "ReallyCapture count was < 0" << LL_ENDL; } + bool newGrab = wantGrab; + +#if LL_X11 if (!mFullscreen) /* only bother if we're windowed anyway */ { -#if LL_X11 if (mSDL_Display) { /* we dirtily mix raw X11 with SDL so that our pointer @@ -1512,49 +1564,37 @@ bool LLWindowSDL::SDLReallyCaptureInput(bool capture) *keyboard* input from the window manager, which was frustrating users. */ int result; - if (wantmode == SDL_GRAB_ON) + if (wantGrab == true) { - //LL_INFOS() << "X11 POINTER GRABBY" << LL_ENDL; - //newmode = SDL_WM_GrabInput(wantmode); maybe_lock_display(); result = XGrabPointer(mSDL_Display, mSDL_XWindowID, - True, 0, GrabModeAsync, - GrabModeAsync, - None, None, CurrentTime); + True, 0, GrabModeAsync, + GrabModeAsync, + None, None, CurrentTime); maybe_unlock_display(); if (GrabSuccess == result) - newmode = SDL_GRAB_ON; + newGrab = true; else - newmode = SDL_GRAB_OFF; - } else if (wantmode == SDL_GRAB_OFF) + newGrab = false; + } + else { - //LL_INFOS() << "X11 POINTER UNGRABBY" << LL_ENDL; - newmode = SDL_GRAB_OFF; - //newmode = SDL_WM_GrabInput(SDL_GRAB_OFF); + newGrab = false; maybe_lock_display(); XUngrabPointer(mSDL_Display, CurrentTime); // Make sure the ungrab happens RIGHT NOW. XSync(mSDL_Display, False); maybe_unlock_display(); - } else - { - newmode = SDL_GRAB_QUERY; // neutral } - } else // not actually running on X11, for some reason - newmode = wantmode; -#endif // LL_X11 - } else { - // pretend we got what we wanted, when really we don't care. - newmode = wantmode; + } } - +#endif // LL_X11 // return boolean success for whether we ended up in the desired state - return (capture && SDL_GRAB_ON==newmode) || - (!capture && SDL_GRAB_OFF==newmode); + return capture == newGrab; } -U32 LLWindowSDL::SDLCheckGrabbyKeys(SDLKey keysym, bool gain) +U32 LLWindowSDL::SDLCheckGrabbyKeys(U32 keysym, bool gain) { /* part of the fix for SL-13243: Some popular window managers like to totally eat alt-drag for the purposes of moving windows. We @@ -1572,16 +1612,16 @@ U32 LLWindowSDL::SDLCheckGrabbyKeys(SDLKey keysym, bool gain) U32 mask = 0; switch (keysym) { - case SDLK_LALT: - mask = 1U << 0; break; - case SDLK_RALT: - mask = 1U << 1; break; - case SDLK_LCTRL: - mask = 1U << 2; break; - case SDLK_RCTRL: - mask = 1U << 3; break; - default: - break; + case SDLK_LALT: + mask = 1U << 0; break; + case SDLK_RALT: + mask = 1U << 1; break; + case SDLK_LCTRL: + mask = 1U << 2; break; + case SDLK_RCTRL: + mask = 1U << 3; break; + default: + break; } if (gain) @@ -1679,7 +1719,7 @@ void check_vm_bloat() last_rss_size = this_rss_size; last_vm_size = this_vm_size; -finally: + finally: if (NULL != ptr) { free(ptr); @@ -1694,33 +1734,17 @@ finally: // virtual void LLWindowSDL::processMiscNativeEvents() { -#if LL_GTK - // Pump GTK events to avoid starvation for: - // * DBUS servicing - // * Anything else which quietly hooks into the default glib/GTK loop - if (ll_try_gtk_init()) - { - // Yuck, Mozilla's GTK callbacks play with the locale - push/pop - // the locale to protect it, as exotic/non-C locales - // causes our code lots of general critical weirdness - // and crashness. (SL-35450) - static std::string saved_locale; - saved_locale = ll_safe_string(setlocale(LC_ALL, NULL)); - - // Pump until we've nothing left to do or passed 1/15th of a - // second pumping for this frame. - static LLTimer pump_timer; - pump_timer.reset(); - pump_timer.setTimerExpirySec(1.0f / 15.0f); - do { - // Always do at least one non-blocking pump - gtk_main_iteration_do(false); - } while (gtk_events_pending() && - !pump_timer.hasExpired()); - - setlocale(LC_ALL, saved_locale.c_str() ); - } -#endif // LL_GTK +#if LL_GLIB + // Pump until we've nothing left to do or passed 1/15th of a + // second pumping for this frame. + static LLTimer pump_timer; + pump_timer.reset(); + pump_timer.setTimerExpirySec(1.0f / 15.0f); + do + { + g_main_context_iteration(g_main_context_default(), false); + } while( g_main_context_pending(g_main_context_default()) && !pump_timer.hasExpired()); +#endif // hack - doesn't belong here - but this is just for debugging if (getenv("LL_DEBUG_BLOAT")) @@ -1743,6 +1767,11 @@ void LLWindowSDL::gatherInput() { switch (event.type) { + case SDL_MOUSEWHEEL: + if( event.wheel.y != 0 ) + mCallbacks->handleScrollWheel(this, -event.wheel.y); + break; + case SDL_MOUSEMOTION: { LLCoordWindow winCoord(event.button.x, event.button.y); @@ -1753,33 +1782,68 @@ void LLWindowSDL::gatherInput() break; } + case SDL_TEXTINPUT: + { + auto string = utf8str_to_utf16str( event.text.text ); + mKeyModifiers = gKeyboard->currentMask( false ); + mInputType = "textinput"; + for( auto key: string ) + { + mKeyVirtualKey = key; + + if( (MASK_CONTROL|MASK_ALT)&mKeyModifiers ) + gKeyboard->handleKeyDown(mKeyVirtualKey, mKeyModifiers ); + else + handleUnicodeUTF16( key, mKeyModifiers ); + } + break; + } + case SDL_KEYDOWN: - mKeyScanCode = event.key.keysym.scancode; - mKeyVirtualKey = event.key.keysym.unicode; - mKeyModifiers = event.key.keysym.mod; + mKeyVirtualKey = event.key.keysym.sym; + mKeyModifiers = event.key.keysym.mod; + mInputType = "keydown"; - gKeyboard->handleKeyDown(event.key.keysym.sym, event.key.keysym.mod); - // part of the fix for SL-13243 - if (SDLCheckGrabbyKeys(event.key.keysym.sym, true) != 0) - SDLReallyCaptureInput(true); + // treat all possible Enter/Return keys the same + if (mKeyVirtualKey == SDLK_RETURN2 || mKeyVirtualKey == SDLK_KP_ENTER) + { + mKeyVirtualKey = SDLK_RETURN; + } + + gKeyboard->handleKeyDown(mKeyVirtualKey, mKeyModifiers ); + + // <FS:ND> Slightly hacky :| To make the viewer honor enter (eg to accept form input) we've to not only send handleKeyDown but also send a + // invoke handleUnicodeUTF16 in case the user hits return. + // Note that we cannot blindly use handleUnicodeUTF16 for each SDL_KEYDOWN. Doing so will create bogus keyboard input (like % for cursor left). + if( mKeyVirtualKey == SDLK_RETURN ) + { + // fix return key not working when capslock, scrolllock or numlock are enabled + mKeyModifiers &= (~(KMOD_NUM | KMOD_CAPS | KMOD_MODE | KMOD_SCROLL)); + handleUnicodeUTF16( mKeyVirtualKey, mKeyModifiers ); + } + + // part of the fix for SL-13243 + if (SDLCheckGrabbyKeys(event.key.keysym.sym, true) != 0) + SDLReallyCaptureInput(true); - if (event.key.keysym.unicode) - { - handleUnicodeUTF16(event.key.keysym.unicode, - gKeyboard->currentMask(false)); - } break; case SDL_KEYUP: - mKeyScanCode = event.key.keysym.scancode; - mKeyVirtualKey = event.key.keysym.unicode; - mKeyModifiers = event.key.keysym.mod; + mKeyVirtualKey = event.key.keysym.sym; + mKeyModifiers = event.key.keysym.mod; + mInputType = "keyup"; - if (SDLCheckGrabbyKeys(event.key.keysym.sym, false) == 0) - SDLReallyCaptureInput(false); // part of the fix for SL-13243 + // treat all possible Enter/Return keys the same + if (mKeyVirtualKey == SDLK_RETURN2 || mKeyVirtualKey == SDLK_KP_ENTER) + { + mKeyVirtualKey = SDLK_RETURN; + } - gKeyboard->handleKeyUp(event.key.keysym.sym, event.key.keysym.mod); - break; + if (SDLCheckGrabbyKeys(mKeyVirtualKey, false) == 0) + SDLReallyCaptureInput(false); // part of the fix for SL-13243 + + gKeyboard->handleKeyUp(mKeyVirtualKey,mKeyModifiers); + break; case SDL_MOUSEBUTTONDOWN: { @@ -1787,7 +1851,7 @@ void LLWindowSDL::gatherInput() LLCoordWindow winCoord(event.button.x, event.button.y); LLCoordGL openGlCoord; convertCoords(winCoord, &openGlCoord); - MASK mask = gKeyboard->currentMask(true); + MASK mask = gKeyboard->currentMask(true); if (event.button.button == SDL_BUTTON_LEFT) // SDL doesn't manage double clicking... { @@ -1799,7 +1863,7 @@ void LLWindowSDL::gatherInput() if (++leftClick >= 2) { leftClick = 0; - isDoubleClick = true; + isDoubleClick = true; } } lastLeftDown = now; @@ -1830,7 +1894,7 @@ void LLWindowSDL::gatherInput() else if (event.button.button == SDL_BUTTON_RIGHT) // right { - mCallbacks->handleRightMouseDown(this, openGlCoord, mask); + mCallbacks->handleRightMouseDown(this, openGlCoord, mask); } else if (event.button.button == SDL_BUTTON_MIDDLE) // middle @@ -1850,86 +1914,69 @@ void LLWindowSDL::gatherInput() LLCoordWindow winCoord(event.button.x, event.button.y); LLCoordGL openGlCoord; convertCoords(winCoord, &openGlCoord); - MASK mask = gKeyboard->currentMask(true); + MASK mask = gKeyboard->currentMask(true); if (event.button.button == SDL_BUTTON_LEFT) // left - mCallbacks->handleMouseUp(this, openGlCoord, mask); + mCallbacks->handleMouseUp(this, openGlCoord, mask); else if (event.button.button == SDL_BUTTON_RIGHT) // right - mCallbacks->handleRightMouseUp(this, openGlCoord, mask); + mCallbacks->handleRightMouseUp(this, openGlCoord, mask); else if (event.button.button == SDL_BUTTON_MIDDLE) // middle - mCallbacks->handleMiddleMouseUp(this, openGlCoord, mask); + mCallbacks->handleMiddleMouseUp(this, openGlCoord, mask); // don't handle mousewheel here... break; } - case SDL_VIDEOEXPOSE: // VIDEOEXPOSE doesn't specify the damage, but hey, it's OpenGL...repaint the whole thing! - mCallbacks->handlePaint(this, 0, 0, mWindow->w, mWindow->h); - break; - - case SDL_VIDEORESIZE: // *FIX: handle this? + case SDL_WINDOWEVENT: // *FIX: handle this? { - LL_INFOS() << "Handling a resize event: " << event.resize.w << - "x" << event.resize.h << LL_ENDL; + if( event.window.event == SDL_WINDOWEVENT_RESIZED + /* || event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED*/ ) // <FS:ND> SDL_WINDOWEVENT_SIZE_CHANGED is followed by SDL_WINDOWEVENT_RESIZED, so handling one shall be enough + { + LL_INFOS() << "Handling a resize event: " << event.window.data1 << "x" << event.window.data2 << LL_ENDL; - S32 width = llmax(event.resize.w, (S32)mMinWindowWidth); - S32 height = llmax(event.resize.h, (S32)mMinWindowHeight); + S32 width = llmax(event.window.data1, (S32)mMinWindowWidth); + S32 height = llmax(event.window.data2, (S32)mMinWindowHeight); + mSurface = SDL_GetWindowSurface( mWindow ); - // *FIX: I'm not sure this is necessary! - mWindow = SDL_SetVideoMode(width, height, 32, mSDLFlags); - if (!mWindow) - { - // *FIX: More informative dialog? - LL_INFOS() << "Could not recreate context after resize! Quitting..." << LL_ENDL; - if(mCallbacks->handleCloseRequest(this)) - { - // Get the app to initiate cleanup. - mCallbacks->handleQuit(this); - // The app is responsible for calling destroyWindow when done with GL - } - break; - } + // *FIX: I'm not sure this is necessary! + // <FS:ND> I think is is not + // SDL_SetWindowSize(mWindow, width, height); + // - mCallbacks->handleResize(this, width, height); - break; - } - case SDL_ACTIVEEVENT: - if (event.active.state & SDL_APPINPUTFOCUS) + mCallbacks->handleResize(this, width, height); + } + else if( event.window.event == SDL_WINDOWEVENT_FOCUS_GAINED ) // <FS:ND> What about SDL_WINDOWEVENT_ENTER (mouse focus) { - // Note that for SDL (particularly on X11), keyboard - // and mouse focus are independent things. Here we are - // tracking keyboard focus state changes. - - // We have to do our own state massaging because SDL - // can send us two unfocus events in a row for example, - // which confuses the focus code [SL-24071]. - if (event.active.gain != mHaveInputFocus) - { - mHaveInputFocus = !!event.active.gain; + // We have to do our own state massaging because SDL + // can send us two unfocus events in a row for example, + // which confuses the focus code [SL-24071]. + mHaveInputFocus = true; - if (mHaveInputFocus) mCallbacks->handleFocus(this); - else + } + else if( event.window.event == SDL_WINDOWEVENT_FOCUS_LOST ) // <FS:ND> What about SDL_WINDOWEVENT_LEAVE (mouse focus) + { + // We have to do our own state massaging because SDL + // can send us two unfocus events in a row for example, + // which confuses the focus code [SL-24071]. + mHaveInputFocus = false; + mCallbacks->handleFocusLost(this); - } } - if (event.active.state & SDL_APPACTIVE) + else if( event.window.event == SDL_WINDOWEVENT_MINIMIZED || + event.window.event == SDL_WINDOWEVENT_MAXIMIZED || + event.window.event == SDL_WINDOWEVENT_RESTORED || + event.window.event == SDL_WINDOWEVENT_EXPOSED || + event.window.event == SDL_WINDOWEVENT_SHOWN ) { - // Change in iconification/minimization state. - if ((!event.active.gain) != mIsMinimized) - { - mIsMinimized = (!event.active.gain); + mIsMinimized = (event.window.event == SDL_WINDOWEVENT_MINIMIZED); - mCallbacks->handleActivate(this, !mIsMinimized); - LL_INFOS() << "SDL deiconification state switched to " << bool(event.active.gain) << LL_ENDL; - } - else - { - LL_INFOS() << "Ignored bogus redundant SDL deiconification state switch to " << bool(event.active.gain) << LL_ENDL; - } + mCallbacks->handleActivate(this, !mIsMinimized); + LL_INFOS() << "SDL deiconification state switched to " << mIsMinimized << LL_ENDL; } - break; + break; + } case SDL_QUIT: if(mCallbacks->handleCloseRequest(this)) { @@ -1938,9 +1985,9 @@ void LLWindowSDL::gatherInput() // The app is responsible for calling destroyWindow when done with GL } break; - default: - //LL_INFOS() << "Unhandled SDL event type " << event.type << LL_ENDL; - break; + default: + //LL_INFOS() << "Unhandled SDL event type " << event.type << LL_ENDL; + break; } } @@ -1968,21 +2015,21 @@ static SDL_Cursor *makeSDLCursorFromBMP(const char *filename, int hotx, int hoty { SDL_Surface *cursurface; LL_DEBUGS() << "Loaded cursor file " << filename << " " - << bmpsurface->w << "x" << bmpsurface->h << LL_ENDL; + << bmpsurface->w << "x" << bmpsurface->h << LL_ENDL; cursurface = SDL_CreateRGBSurface (SDL_SWSURFACE, - bmpsurface->w, - bmpsurface->h, - 32, - SDL_SwapLE32(0xFFU), - SDL_SwapLE32(0xFF00U), - SDL_SwapLE32(0xFF0000U), - SDL_SwapLE32(0xFF000000U)); + bmpsurface->w, + bmpsurface->h, + 32, + SDL_SwapLE32(0xFFU), + SDL_SwapLE32(0xFF00U), + SDL_SwapLE32(0xFF0000U), + SDL_SwapLE32(0xFF000000U)); SDL_FillRect(cursurface, NULL, SDL_SwapLE32(0x00000000U)); // Blit the cursor pixel data onto a 32-bit RGBA surface so we // only have to cope with processing one type of pixel format. if (0 == SDL_BlitSurface(bmpsurface, NULL, - cursurface, NULL)) + cursurface, NULL)) { // n.b. we already checked that width is a multiple of 8. const int bitmap_bytes = (cursurface->w * cursurface->h) / 8; @@ -1997,26 +2044,26 @@ static SDL_Cursor *makeSDLCursorFromBMP(const char *filename, int hotx, int hoty for (i=0; i<cursurface->h; ++i) { for (j=0; j<cursurface->w; ++j) { U8 *pixelp = - ((U8*)cursurface->pixels) - + cursurface->pitch * i - + j*cursurface->format->BytesPerPixel; + ((U8*)cursurface->pixels) + + cursurface->pitch * i + + j*cursurface->format->BytesPerPixel; U8 srcred = pixelp[0]; U8 srcgreen = pixelp[1]; U8 srcblue = pixelp[2]; bool mask_bit = (srcred != 200) - || (srcgreen != 200) - || (srcblue != 200); + || (srcgreen != 200) + || (srcblue != 200); bool data_bit = mask_bit && (srcgreen <= 80);//not 0x80 unsigned char bit_offset = (cursurface->w/8) * i - + j/8; + + j/8; cursor_data[bit_offset] |= (data_bit) << (7 - (j&7)); cursor_mask[bit_offset] |= (mask_bit) << (7 - (j&7)); } } sdlcursor = SDL_CreateCursor((Uint8*)cursor_data, - (Uint8*)cursor_mask, - cursurface->w, cursurface->h, - hotx, hoty); + (Uint8*)cursor_mask, + cursurface->w, cursurface->h, + hotx, hoty); delete[] cursor_data; delete[] cursor_mask; } else { @@ -2207,8 +2254,6 @@ void LLWindowSDL::hideCursorUntilMouseMove() } } - - // // LLSplashScreenSDL - I don't think we'll bother to implement this; it's // fairly obsolete at this point. @@ -2233,133 +2278,51 @@ void LLSplashScreenSDL::hideImpl() { } - - -#if LL_GTK -static void response_callback (GtkDialog *dialog, - gint arg1, - gpointer user_data) -{ - gint *response = (gint*)user_data; - *response = arg1; - gtk_widget_destroy(GTK_WIDGET(dialog)); - gtk_main_quit(); -} - S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 type) { - S32 rtn = OSBTN_CANCEL; - - if(gWindowImplementation != NULL) - gWindowImplementation->beforeDialog(); + SDL_MessageBoxData oData = { SDL_MESSAGEBOX_INFORMATION, nullptr, caption.c_str(), text.c_str(), 0, nullptr, nullptr }; + SDL_MessageBoxButtonData btnOk[] = {{SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, OSBTN_OK, "OK" }}; + SDL_MessageBoxButtonData btnOkCancel [] = {{SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, OSBTN_OK, "OK" }, {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, OSBTN_CANCEL, "Cancel"} }; + SDL_MessageBoxButtonData btnYesNo[] = { {SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, OSBTN_YES, "Yes" }, {SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, OSBTN_NO, "No"} }; - if (LLWindowSDL::ll_try_gtk_init()) + switch (type) { - GtkWidget *win = NULL; - - LL_INFOS() << "Creating a dialog because we're in windowed mode and GTK is happy." << LL_ENDL; - - GtkDialogFlags flags = GTK_DIALOG_MODAL; - GtkMessageType messagetype; - GtkButtonsType buttons; - switch (type) - { default: case OSMB_OK: - messagetype = GTK_MESSAGE_WARNING; - buttons = GTK_BUTTONS_OK; + oData.flags = SDL_MESSAGEBOX_WARNING; + oData.buttons = btnOk; + oData.numbuttons = 1; break; case OSMB_OKCANCEL: - messagetype = GTK_MESSAGE_QUESTION; - buttons = GTK_BUTTONS_OK_CANCEL; + oData.flags = SDL_MESSAGEBOX_INFORMATION; + oData.buttons = btnOkCancel; + oData.numbuttons = 2; break; case OSMB_YESNO: - messagetype = GTK_MESSAGE_QUESTION; - buttons = GTK_BUTTONS_YES_NO; + oData.flags = SDL_MESSAGEBOX_INFORMATION; + oData.buttons = btnYesNo; + oData.numbuttons = 2; break; - } - win = gtk_message_dialog_new(NULL, flags, messagetype, buttons, "%s", - text.c_str()); - -# if LL_X11 - // Make GTK tell the window manager to associate this - // dialog with our non-GTK SDL window, which should try - // to keep it on top etc. - if (gWindowImplementation && - gWindowImplementation->mSDL_XWindowID != None) - { - gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin - GdkWindow *gdkwin = gdk_window_foreign_new(gWindowImplementation->mSDL_XWindowID); - gdk_window_set_transient_for(GTK_WIDGET(win)->window, - gdkwin); - } -# endif //LL_X11 - - gtk_window_set_position(GTK_WINDOW(win), - GTK_WIN_POS_CENTER_ON_PARENT); - - gtk_window_set_type_hint(GTK_WINDOW(win), - GDK_WINDOW_TYPE_HINT_DIALOG); - - if (!caption.empty()) - gtk_window_set_title(GTK_WINDOW(win), caption.c_str()); - - gint response = GTK_RESPONSE_NONE; - g_signal_connect (win, - "response", - G_CALLBACK (response_callback), - &response); - - // we should be able to use a gtk_dialog_run(), but it's - // apparently not written to exist in a world without a higher - // gtk_main(), so we manage its signal/destruction outselves. - gtk_widget_show_all (win); - gtk_main(); - - //LL_INFOS() << "response: " << response << LL_ENDL; - switch (response) - { - case GTK_RESPONSE_OK: rtn = OSBTN_OK; break; - case GTK_RESPONSE_YES: rtn = OSBTN_YES; break; - case GTK_RESPONSE_NO: rtn = OSBTN_NO; break; - case GTK_RESPONSE_APPLY: rtn = OSBTN_OK; break; - case GTK_RESPONSE_NONE: - case GTK_RESPONSE_CANCEL: - case GTK_RESPONSE_CLOSE: - case GTK_RESPONSE_DELETE_EVENT: - default: rtn = OSBTN_CANCEL; - } } - else - { - LL_INFOS() << "MSGBOX: " << caption << ": " << text << LL_ENDL; - LL_INFOS() << "Skipping dialog because we're in fullscreen mode or GTK is not happy." << LL_ENDL; - rtn = OSBTN_OK; - } - - if(gWindowImplementation != NULL) - gWindowImplementation->afterDialog(); - return rtn; + int btn{0}; + if( 0 == SDL_ShowMessageBox( &oData, &btn ) ) + return btn; + return OSBTN_CANCEL; } -static void color_changed_callback(GtkWidget *widget, - gpointer user_data) +bool LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) { - GtkColorSelection *colorsel = GTK_COLOR_SELECTION(widget); - GdkColor *colorp = (GdkColor*)user_data; - - gtk_color_selection_get_current_color(colorsel, colorp); + return (false); } - /* Make the raw keyboard data available - used to poke through to LLQtWebKit so that Qt/Webkit has access to the virtual keycodes etc. that it needs */ LLSD LLWindowSDL::getNativeKeyData() { - LLSD result = LLSD::emptyMap(); + LLSD result = LLSD::emptyMap(); U32 modifiers = 0; // pretend-native modifiers... oh what a tangled web we weave! @@ -2377,99 +2340,14 @@ LLSD LLWindowSDL::getNativeKeyData() // *todo: test ALTs - I don't have a case for testing these. Do you? // *todo: NUM? - I don't care enough right now (and it's not a GDK modifier). - result["scan_code"] = (S32)mKeyScanCode; - result["virtual_key"] = (S32)mKeyVirtualKey; + result["virtual_key"] = (S32)mKeyVirtualKey; + result["virtual_key_win"] = (S32)LLKeyboardSDL::mapSDL2toWin( mKeyVirtualKey ); result["modifiers"] = (S32)modifiers; - - return result; -} - - -bool LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) -{ - bool rtn = false; - - beforeDialog(); - - if (ll_try_gtk_init()) - { - GtkWidget *win = NULL; - - win = gtk_color_selection_dialog_new(NULL); - -# if LL_X11 - // Get GTK to tell the window manager to associate this - // dialog with our non-GTK SDL window, which should try - // to keep it on top etc. - if (mSDL_XWindowID != None) - { - gtk_widget_realize(GTK_WIDGET(win)); // so we can get its gdkwin - GdkWindow *gdkwin = gdk_window_foreign_new(mSDL_XWindowID); - gdk_window_set_transient_for(GTK_WIDGET(win)->window, - gdkwin); - } -# endif //LL_X11 - - GtkColorSelection *colorsel = GTK_COLOR_SELECTION (GTK_COLOR_SELECTION_DIALOG(win)->colorsel); - - GdkColor color, orig_color; - orig_color.pixel = 0; - orig_color.red = guint16(65535 * *r); - orig_color.green= guint16(65535 * *g); - orig_color.blue = guint16(65535 * *b); - color = orig_color; - - gtk_color_selection_set_previous_color (colorsel, &color); - gtk_color_selection_set_current_color (colorsel, &color); - gtk_color_selection_set_has_palette (colorsel, true); - gtk_color_selection_set_has_opacity_control(colorsel, false); - - gint response = GTK_RESPONSE_NONE; - g_signal_connect (win, - "response", - G_CALLBACK (response_callback), - &response); - - g_signal_connect (G_OBJECT (colorsel), "color_changed", - G_CALLBACK (color_changed_callback), - &color); - - gtk_window_set_modal(GTK_WINDOW(win), true); - gtk_widget_show_all(win); - // hide the help button - we don't service it. - gtk_widget_hide(GTK_COLOR_SELECTION_DIALOG(win)->help_button); - gtk_main(); - - if (response == GTK_RESPONSE_OK && - (orig_color.red != color.red - || orig_color.green != color.green - || orig_color.blue != color.blue) ) - { - *r = color.red / 65535.0f; - *g = color.green / 65535.0f; - *b = color.blue / 65535.0f; - rtn = true; - } - } - - afterDialog(); - - return rtn; -} -#else -S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 type) -{ - LL_INFOS() << "MSGBOX: " << caption << ": " << text << LL_ENDL; - return 0; -} - -bool LLWindowSDL::dialogColorPicker( F32 *r, F32 *g, F32 *b) -{ - return (false); + result["input_type"] = mInputType; + return result; } -#endif // LL_GTK -#if LL_LINUX +#if LL_LINUX || LL_SOLARIS // extracted from spawnWebBrowser for clarity and to eliminate // compiler confusion regarding close(int fd) vs. LLWindow::close() void exec_cmd(const std::string& cmd, const std::string& arg) @@ -2481,9 +2359,32 @@ void exec_cmd(const std::string& cmd, const std::string& arg) { // child // disconnect from stdin/stdout/stderr, or child will // keep our output pipe undesirably alive if it outlives us. - close(0); - close(1); - close(2); + // close(0); + // close(1); + // close(2); + // <FS:TS> Reopen stdin, stdout, and stderr to /dev/null. + // It's good practice to always have those file + // descriptors open to something, lest the exec'd + // program actually try to use them. + FILE *result; + result = freopen("/dev/null","r",stdin); + if (result == NULL) + { + LL_WARNS() << "Error reopening stdin for web browser: " + << strerror(errno) << LL_ENDL; + } + result = freopen("/dev/null","w",stdout); + if (result == NULL) + { + LL_WARNS() << "Error reopening stdout for web browser: " + << strerror(errno) << LL_ENDL; + } + result = freopen("/dev/null","w",stderr); + if (result == NULL) + { + LL_WARNS() << "Error reopening stderr for web browser: " + << strerror(errno) << LL_ENDL; + } // end ourself by running the command execv(cmd.c_str(), argv); /* Flawfinder: ignore */ // if execv returns at all, there was a problem. @@ -2549,31 +2450,13 @@ void LLWindowSDL::spawnWebBrowser(const std::string& escaped_url, bool async) LL_INFOS() << "spawn_web_browser returning." << LL_ENDL; } +void LLWindowSDL::openFile(const std::string& file_name) +{ + spawnWebBrowser("file://"+file_name,true); +} void *LLWindowSDL::getPlatformWindow() { -#if LL_GTK && LL_LLMOZLIB_ENABLED - if (LLWindowSDL::ll_try_gtk_init()) - { - maybe_lock_display(); - - GtkWidget *owin = gtk_window_new(GTK_WINDOW_POPUP); - // Why a layout widget? A MozContainer would be ideal, but - // it involves exposing Mozilla headers to mozlib-using apps. - // A layout widget with a GtkWindow parent has the desired - // properties of being plain GTK, having a window, and being - // derived from a GtkContainer. - GtkWidget *rtnw = gtk_layout_new(NULL, NULL); - gtk_container_add(GTK_CONTAINER(owin), rtnw); - gtk_widget_realize(rtnw); - GTK_WIDGET_UNSET_FLAGS(GTK_WIDGET(rtnw), GTK_NO_WINDOW); - - maybe_unlock_display(); - - return rtnw; - } -#endif // LL_GTK && LL_LLMOZLIB_ENABLED - // Unixoid mozilla really needs GTK. return NULL; } @@ -2634,10 +2517,10 @@ std::vector<std::string> LLWindowSDL::getDynamicFallbackFontList() LL_INFOS("AppInit") << "Variant " << locale->variant << LL_ENDL; LL_INFOS() << "Preferring fonts of language: " - << locale->lang - << LL_ENDL; + << locale->lang + << LL_ENDL; sort_order = "lang=" + std::string(locale->lang) + ":" - + sort_order; + + sort_order; } } FL_FreeLocale(&locale); @@ -2655,7 +2538,7 @@ std::vector<std::string> LLWindowSDL::getDynamicFallbackFontList() // Sort the list of system fonts from most-to-least-desirable. FcResult result; fs = FcFontSort(NULL, sortpat, elide_unicode_coverage, - NULL, &result); + NULL, &result); FcPatternDestroy(sortpat); } @@ -2669,8 +2552,8 @@ std::vector<std::string> LLWindowSDL::getDynamicFallbackFontList() { FcChar8 *filename; if (FcResultMatch == FcPatternGetString(fs->fonts[i], - FC_FILE, 0, - &filename) + FC_FILE, 0, + &filename) && filename) { rtns.push_back(std::string((const char*)filename)); @@ -2694,4 +2577,54 @@ std::vector<std::string> LLWindowSDL::getDynamicFallbackFontList() return rtns; } -#endif // LL_SDL + +void* LLWindowSDL::createSharedContext() +{ + auto *pContext = SDL_GL_CreateContext(mWindow); + if ( pContext) + { + SDL_GL_SetSwapInterval(0); + SDL_GL_MakeCurrent(mWindow, mContext); + + LLCoordScreen size; + if (getSize(&size)) + setSize(size); + + LL_DEBUGS() << "Creating shared OpenGL context successful!" << LL_ENDL; + + return (void*)pContext; + } + + LL_WARNS() << "Creating shared OpenGL context failed!" << LL_ENDL; + + return nullptr; +} + +void LLWindowSDL::makeContextCurrent(void* contextPtr) +{ + LL_PROFILER_GPU_CONTEXT; + SDL_GL_MakeCurrent( mWindow, contextPtr ); +} + +void LLWindowSDL::destroySharedContext(void* contextPtr) +{ + SDL_GL_DeleteContext( contextPtr ); +} + +void LLWindowSDL::toggleVSync(bool enable_vsync) +{ +} + +void LLWindowSDL::setLanguageTextInput(const LLCoordGL& position) +{ + LLCoordWindow win_pos; + convertCoords( position, &win_pos ); + + SDL_Rect r; + r.x = win_pos.mX; + r.y = win_pos.mY; + r.w = 500; + r.h = 16; + + SDL_SetTextInputRect(&r); +} diff --git a/indra/llwindow/llwindowsdl.h b/indra/llwindow/llwindowsdl.h index 196ad2986d..10769bb3ba 100644 --- a/indra/llwindow/llwindowsdl.h +++ b/indra/llwindow/llwindowsdl.h @@ -24,20 +24,20 @@ * $/LicenseInfo$ */ -#ifndef LL_LLWINDOWSDL_H -#define LL_LLWINDOWSDL_H +#ifndef LL_LLWINDOWSDL2_H +#define LL_LLWINDOWSDL2_H // Simple Directmedia Layer (http://libsdl.org/) implementation of LLWindow class #include "llwindow.h" #include "lltimer.h" -#include "SDL/SDL.h" -#include "SDL/SDL_endian.h" +#include "SDL2/SDL.h" +#include "SDL2/SDL_endian.h" #if LL_X11 // get X11-specific headers for use in low-level stuff like copy-and-paste support -#include "SDL/SDL_syswm.h" +#include "SDL2/SDL_syswm.h" #endif // AssertMacros.h does bad things. @@ -46,83 +46,139 @@ #undef require -class LLWindowSDL : public LLWindow -{ +class LLWindowSDL : public LLWindow { public: - /*virtual*/ void show(); - /*virtual*/ void hide(); - /*virtual*/ void close(); - /*virtual*/ bool getVisible(); - /*virtual*/ bool getMinimized(); - /*virtual*/ bool getMaximized(); - /*virtual*/ bool maximize(); - /*virtual*/ void minimize(); - /*virtual*/ void restore(); - /*virtual*/ bool getFullscreen(); - /*virtual*/ bool getPosition(LLCoordScreen *position); - /*virtual*/ bool getSize(LLCoordScreen *size); - /*virtual*/ bool getSize(LLCoordWindow *size); - /*virtual*/ bool setPosition(LLCoordScreen position); - /*virtual*/ bool setSizeImpl(LLCoordScreen size); - /*virtual*/ bool setSizeImpl(LLCoordWindow size); - /*virtual*/ bool switchContext(bool fullscreen, const LLCoordScreen &size, bool disable_vsync, const LLCoordScreen * const posp = NULL); - /*virtual*/ bool setCursorPosition(LLCoordWindow position); - /*virtual*/ bool getCursorPosition(LLCoordWindow *position); - /*virtual*/ void showCursor(); - /*virtual*/ void hideCursor(); - /*virtual*/ void showCursorFromMouseMove(); - /*virtual*/ void hideCursorUntilMouseMove(); - /*virtual*/ bool isCursorHidden(); - /*virtual*/ void updateCursor(); - /*virtual*/ void captureMouse(); - /*virtual*/ void releaseMouse(); - /*virtual*/ void setMouseClipping( bool b ); - /*virtual*/ void setMinSize(U32 min_width, U32 min_height, bool enforce_immediately = true); - - /*virtual*/ bool isClipboardTextAvailable(); - /*virtual*/ bool pasteTextFromClipboard(LLWString &dst); - /*virtual*/ bool copyTextToClipboard(const LLWString & src); - - /*virtual*/ bool isPrimaryTextAvailable(); - /*virtual*/ bool pasteTextFromPrimary(LLWString &dst); - /*virtual*/ bool copyTextToPrimary(const LLWString & src); - - /*virtual*/ void flashIcon(F32 seconds); - /*virtual*/ F32 getGamma(); - /*virtual*/ bool setGamma(const F32 gamma); // Set the gamma - /*virtual*/ U32 getFSAASamples(); - /*virtual*/ void setFSAASamples(const U32 samples); - /*virtual*/ bool restoreGamma(); // Restore original gamma table (before updating gamma) - /*virtual*/ ESwapMethod getSwapMethod() { return mSwapMethod; } - /*virtual*/ void processMiscNativeEvents(); - /*virtual*/ void gatherInput(); - /*virtual*/ void swapBuffers(); - /*virtual*/ void restoreGLContext() {}; - - /*virtual*/ void delayInputProcessing() { }; + void show() override; + + void hide() override; + + void close() override; + + bool getVisible() override; + + bool getMinimized() override; + + bool getMaximized() override; + + bool maximize() override; + + void minimize() override; + + void restore() override; + + bool getFullscreen(); + + bool getPosition(LLCoordScreen *position) override; + + bool getSize(LLCoordScreen *size) override; + + bool getSize(LLCoordWindow *size) override; + + bool setPosition(LLCoordScreen position) override; + + bool setSizeImpl(LLCoordScreen size) override; + + bool setSizeImpl(LLCoordWindow size) override; + + bool switchContext(bool fullscreen, const LLCoordScreen &size, bool enable_vsync, + const LLCoordScreen *const posp = NULL) override; + + bool setCursorPosition(LLCoordWindow position) override; + + bool getCursorPosition(LLCoordWindow *position) override; + + void showCursor() override; + + void hideCursor() override; + + void showCursorFromMouseMove() override; + + void hideCursorUntilMouseMove() override; + + bool isCursorHidden() override; + + void updateCursor() override; + + void captureMouse() override; + + void releaseMouse() override; + + void setMouseClipping(bool b) override; + + void setMinSize(U32 min_width, U32 min_height, bool enforce_immediately = true) override; + + bool isClipboardTextAvailable() override; + + bool pasteTextFromClipboard(LLWString &dst) override; + + bool copyTextToClipboard(const LLWString &src) override; + + bool isPrimaryTextAvailable() override; + + bool pasteTextFromPrimary(LLWString &dst) override; + + bool copyTextToPrimary(const LLWString &src) override; + + void flashIcon(F32 seconds) override; + + F32 getGamma() override; + + bool setGamma(const F32 gamma) override; // Set the gamma + U32 getFSAASamples() override; + + void setFSAASamples(const U32 samples) override; + + bool restoreGamma() override; // Restore original gamma table (before updating gamma) + ESwapMethod getSwapMethod() override { return mSwapMethod; } + + void processMiscNativeEvents() override; + + void gatherInput() override; + + void swapBuffers() override; + + void restoreGLContext() {}; + + void delayInputProcessing() override {}; // handy coordinate space conversion routines - /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordWindow *to); - /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordScreen *to); - /*virtual*/ bool convertCoords(LLCoordWindow from, LLCoordGL *to); - /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordWindow *to); - /*virtual*/ bool convertCoords(LLCoordScreen from, LLCoordGL *to); - /*virtual*/ bool convertCoords(LLCoordGL from, LLCoordScreen *to); + bool convertCoords(LLCoordScreen from, LLCoordWindow *to) override; + + bool convertCoords(LLCoordWindow from, LLCoordScreen *to) override; + + bool convertCoords(LLCoordWindow from, LLCoordGL *to) override; + + bool convertCoords(LLCoordGL from, LLCoordWindow *to) override; + + bool convertCoords(LLCoordScreen from, LLCoordGL *to) override; - /*virtual*/ LLWindowResolution* getSupportedResolutions(S32 &num_resolutions); - /*virtual*/ F32 getNativeAspectRatio(); - /*virtual*/ F32 getPixelAspectRatio(); - /*virtual*/ void setNativeAspectRatio(F32 ratio) { mOverrideAspectRatio = ratio; } + bool convertCoords(LLCoordGL from, LLCoordScreen *to) override; - /*virtual*/ void beforeDialog(); - /*virtual*/ void afterDialog(); + LLWindowResolution *getSupportedResolutions(S32 &num_resolutions) override; - /*virtual*/ bool dialogColorPicker(F32 *r, F32 *g, F32 *b); + F32 getNativeAspectRatio() override; - /*virtual*/ void *getPlatformWindow(); - /*virtual*/ void bringToFront(); + F32 getPixelAspectRatio() override; - /*virtual*/ void spawnWebBrowser(const std::string& escaped_url, bool async); + void setNativeAspectRatio(F32 ratio) override { mOverrideAspectRatio = ratio; } + + void beforeDialog() override; + + void afterDialog() override; + + bool dialogColorPicker(F32 *r, F32 *g, F32 *b) override; + + void *getPlatformWindow() override; + + void bringToFront() override; + + void setLanguageTextInput(const LLCoordGL& pos) override; + + void spawnWebBrowser(const std::string &escaped_url, bool async) override; + + void openFile(const std::string &file_name); + + void setTitle(const std::string title) override; static std::vector<std::string> getDynamicFallbackFontList(); @@ -132,40 +188,52 @@ public: Window mSDL_XWindowID; Display *mSDL_Display; #endif + void (*Lock_Display)(void); - void (*Unlock_Display)(void); -#if LL_GTK - // Lazily initialize and check the runtime GTK version for goodness. - static bool ll_try_gtk_init(void); -#endif // LL_GTK + void (*Unlock_Display)(void); #if LL_X11 + static Window get_SDL_XWindowID(void); - static Display* get_SDL_Display(void); + + static Display *get_SDL_Display(void); + #endif // LL_X11 + void *createSharedContext() override; + + void makeContextCurrent(void *context) override; + + void destroySharedContext(void *context) override; + + void toggleVSync(bool enable_vsync) override; + protected: - LLWindowSDL(LLWindowCallbacks* callbacks, - const std::string& title, int x, int y, int width, int height, U32 flags, - bool fullscreen, bool clearBg, bool disable_vsync, bool use_gl, - bool ignore_pixel_depth, U32 fsaa_samples); + LLWindowSDL(LLWindowCallbacks *callbacks, + const std::string &title, int x, int y, int width, int height, U32 flags, + bool fullscreen, bool clearBg, bool enable_vsync, bool use_gl, + bool ignore_pixel_depth, U32 fsaa_samples); + ~LLWindowSDL(); - /*virtual*/ bool isValid(); - /*virtual*/ LLSD getNativeKeyData(); + bool isValid() override; + + LLSD getNativeKeyData() override; + + void initCursors(); - void initCursors(); - void quitCursors(); - void moveWindow(const LLCoordScreen& position,const LLCoordScreen& size); + void quitCursors(); + + void moveWindow(const LLCoordScreen &position, const LLCoordScreen &size); // Changes display resolution. Returns true if successful - bool setDisplayResolution(S32 width, S32 height, S32 bits, S32 refresh); + bool setDisplayResolution(S32 width, S32 height, S32 bits, S32 refresh); // Go back to last fullscreen display resolution. - bool setFullscreenResolution(); + bool setFullscreenResolution(); - bool shouldPostQuit() { return mPostQuit; } + bool shouldPostQuit() { return mPostQuit; } protected: // @@ -173,47 +241,85 @@ protected: // // create or re-create the GL context/window. Called from the constructor and switchContext(). - bool createContext(int x, int y, int width, int height, int bits, bool fullscreen, bool disable_vsync); + bool createContext(int x, int y, int width, int height, int bits, bool fullscreen, bool enable_vsync); + void destroyContext(); - void setupFailure(const std::string& text, const std::string& caption, U32 type); + + void setupFailure(const std::string &text, const std::string &caption, U32 type); + void fixWindowSize(void); - U32 SDLCheckGrabbyKeys(SDLKey keysym, bool gain); + + U32 SDLCheckGrabbyKeys(U32 keysym, bool gain); + bool SDLReallyCaptureInput(bool capture); // // Platform specific variables // - U32 mGrabbyKeyFlags; - int mReallyCapturedCount; - SDL_Surface * mWindow; + U32 mGrabbyKeyFlags; + int mReallyCapturedCount; + + SDL_Window *mWindow; + SDL_Surface *mSurface; + SDL_GLContext mContext; + SDL_Cursor *mSDLCursors[UI_CURSOR_COUNT]; + std::string mWindowTitle; - double mOriginalAspectRatio; - bool mNeedsResize; // Constructor figured out the window is too big, it needs a resize. - LLCoordScreen mNeedsResizeSize; - F32 mOverrideAspectRatio; - F32 mGamma; - U32 mFSAASamples; + double mOriginalAspectRatio; + bool mNeedsResize; // Constructor figured out the window is too big, it needs a resize. + LLCoordScreen mNeedsResizeSize; + F32 mOverrideAspectRatio; + F32 mGamma; + U32 mFSAASamples; - int mSDLFlags; + int mSDLFlags; - SDL_Cursor* mSDLCursors[UI_CURSOR_COUNT]; - int mHaveInputFocus; /* 0=no, 1=yes, else unknown */ - int mIsMinimized; /* 0=no, 1=yes, else unknown */ + int mHaveInputFocus; /* 0=no, 1=yes, else unknown */ + int mIsMinimized; /* 0=no, 1=yes, else unknown */ friend class LLWindowManager; private: #if LL_X11 + void x11_set_urgent(bool urgent); + bool mFlashing; LLTimer mFlashTimer; #endif //LL_X11 - U32 mKeyScanCode; - U32 mKeyVirtualKey; - SDLMod mKeyModifiers; -}; + U32 mKeyVirtualKey; + U32 mKeyModifiers; + std::string mInputType; + +public: +#if LL_X11 + + static Display *getSDLDisplay(); + LLWString const &getPrimaryText() const { return mPrimaryClipboard; } + + LLWString const &getSecondaryText() const { return mSecondaryClipboard; } + + void clearPrimaryText() { mPrimaryClipboard.clear(); } + + void clearSecondaryText() { mSecondaryClipboard.clear(); } + +private: + void tryFindFullscreenSize(int &aWidth, int &aHeight); + + void initialiseX11Clipboard(); + + bool getSelectionText(Atom selection, LLWString &text); + + bool getSelectionText(Atom selection, Atom type, LLWString &text); + + bool setSelectionText(Atom selection, const LLWString &text); + +#endif + LLWString mPrimaryClipboard; + LLWString mSecondaryClipboard; +}; class LLSplashScreenSDL : public LLSplashScreen { @@ -221,9 +327,9 @@ public: LLSplashScreenSDL(); virtual ~LLSplashScreenSDL(); - /*virtual*/ void showImpl(); - /*virtual*/ void updateImpl(const std::string& mesg); - /*virtual*/ void hideImpl(); + void showImpl(); + void updateImpl(const std::string& mesg); + void hideImpl(); }; S32 OSMessageBoxSDL(const std::string& text, const std::string& caption, U32 type); diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index 0b6ee541c0..b19fa13b41 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -163,18 +163,7 @@ HGLRC SafeCreateContext(HDC &hdc) GLuint SafeChoosePixelFormat(HDC &hdc, const PIXELFORMATDESCRIPTOR *ppfd) { - __try - { - return ChoosePixelFormat(hdc, ppfd); - } - __except (EXCEPTION_EXECUTE_HANDLER) - { - // convert to C++ styled exception - // C exception don't allow classes, so it's a regular char array - char integer_string[32]; - sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode()); - throw std::exception(integer_string); - } + return LL::seh::catcher([hdc, ppfd]{ return ChoosePixelFormat(hdc, ppfd); }); } //static @@ -1630,9 +1619,11 @@ const S32 max_format = (S32)num_formats - 1; } else { - LLError::LLUserWarningMsg::show(mCallbacks->translateString("MBVideoDrvErr")); - // mWindowHandle is 0, going to crash either way - LL_ERRS("Window") << "No wgl_ARB_pixel_format extension!" << LL_ENDL; + LL_WARNS("Window") << "No wgl_ARB_pixel_format extension!" << LL_ENDL; + // cannot proceed without wgl_ARB_pixel_format extension, shutdown same as any other gGLManager.initGL() failure + OSMessageBox(mCallbacks->translateString("MBVideoDrvErr"), mCallbacks->translateString("MBError"), OSMB_OK); + close(); + return false; } // Verify what pixel format we actually received. @@ -3742,6 +3733,23 @@ S32 OSMessageBoxWin32(const std::string& text, const std::string& caption, U32 t return retval; } +void shell_open(const std::string &file, bool async) +{ + std::wstring url_utf16 = ll_convert(file); + + // let the OS decide what to use to open the URL + SHELLEXECUTEINFO sei = {sizeof(sei)}; + // NOTE: this assumes that SL will stick around long enough to complete the DDE message exchange + // necessary for ShellExecuteEx to complete + if (async) + { + sei.fMask = SEE_MASK_ASYNCOK; + } + sei.nShow = SW_SHOWNORMAL; + sei.lpVerb = L"open"; + sei.lpFile = url_utf16.c_str(); + ShellExecuteEx(&sei); +} void LLWindowWin32::spawnWebBrowser(const std::string& escaped_url, bool async) { @@ -3767,22 +3775,12 @@ void LLWindowWin32::spawnWebBrowser(const std::string& escaped_url, bool async) // replaced ShellExecute code with ShellExecuteEx since ShellExecute doesn't work // reliablly on Vista. - // this is madness.. no, this is.. - LLWString url_wstring = utf8str_to_wstring( escaped_url ); - llutf16string url_utf16 = wstring_to_utf16str( url_wstring ); + shell_open(escaped_url, async); +} - // let the OS decide what to use to open the URL - SHELLEXECUTEINFO sei = { sizeof( sei ) }; - // NOTE: this assumes that SL will stick around long enough to complete the DDE message exchange - // necessary for ShellExecuteEx to complete - if (async) - { - sei.fMask = SEE_MASK_ASYNCOK; - } - sei.nShow = SW_SHOWNORMAL; - sei.lpVerb = L"open"; - sei.lpFile = url_utf16.c_str(); - ShellExecuteEx( &sei ); +void LLWindowWin32::openFolder(const std::string &path) +{ + shell_open(path, false); } /* diff --git a/indra/llwindow/llwindowwin32.h b/indra/llwindow/llwindowwin32.h index 36e89e4586..e38cfe7ebc 100644 --- a/indra/llwindow/llwindowwin32.h +++ b/indra/llwindow/llwindowwin32.h @@ -120,6 +120,8 @@ public: /*virtual*/ void interruptLanguageTextInput(); /*virtual*/ void spawnWebBrowser(const std::string& escaped_url, bool async); + void openFolder(const std::string &path) override; + /*virtual*/ F32 getSystemUISize(); LLWindowCallbacks::DragNDropResult completeDragNDropRequest( const LLCoordGL gl_coord, const MASK mask, LLWindowCallbacks::DragNDropAction action, const std::string url ); diff --git a/indra/llxml/llcontrol.cpp b/indra/llxml/llcontrol.cpp index bb590ebd76..6d02cbaa34 100644 --- a/indra/llxml/llcontrol.cpp +++ b/indra/llxml/llcontrol.cpp @@ -728,7 +728,7 @@ void LLControlGroup::setLLSD(std::string_view name, const LLSD& val) set(name, val); } -void LLControlGroup::setUntypedValue(std::string_view name, const LLSD& val) +void LLControlGroup::setUntypedValue(std::string_view name, const LLSD& val, bool saved_value) { if (name.empty()) { @@ -739,7 +739,7 @@ void LLControlGroup::setUntypedValue(std::string_view name, const LLSD& val) if (control) { - control->setValue(val); + control->setValue(val, saved_value); } else { diff --git a/indra/llxml/llcontrol.h b/indra/llxml/llcontrol.h index 4f54a9d705..a614ff216b 100644 --- a/indra/llxml/llcontrol.h +++ b/indra/llxml/llcontrol.h @@ -119,7 +119,7 @@ public: LLSD getDefault() const { return mValues.front(); } LLSD getSaveValue() const; - void set(const LLSD& val) { setValue(val); } + void set(const LLSD& val, bool saved_value = true) { setValue(val, saved_value); } void setValue(const LLSD& value, bool saved_value = true); void setDefaultValue(const LLSD& value); void setPersist(ePersist); @@ -254,7 +254,7 @@ public: void setLLSD(std::string_view name, const LLSD& val); // type agnostic setter that takes LLSD - void setUntypedValue(std::string_view name, const LLSD& val); + void setUntypedValue(std::string_view name, const LLSD& val, bool saved_value = true); // generic setter template<typename T> void set(std::string_view name, const T& val) diff --git a/indra/llxml/llxmltree.cpp b/indra/llxml/llxmltree.cpp index 164b3156e1..d66544d0f8 100644 --- a/indra/llxml/llxmltree.cpp +++ b/indra/llxml/llxmltree.cpp @@ -108,12 +108,15 @@ LLXmlTreeNode::LLXmlTreeNode( const std::string& name, LLXmlTreeNode* parent, LL LLXmlTreeNode::~LLXmlTreeNode() { - attribute_map_t::iterator iter; - for (iter=mAttributes.begin(); iter != mAttributes.end(); iter++) - delete iter->second; - for(LLXmlTreeNode* node : mChildren) + for (auto& attr : mAttributes) + { + delete attr.second; + } + mAttributes.clear(); + + for (auto& child : mChildren) { - delete node; + delete child; } mChildren.clear(); } diff --git a/indra/llxml/tests/llcontrol_test.cpp b/indra/llxml/tests/llcontrol_test.cpp index 4192e029c5..52c202bceb 100644 --- a/indra/llxml/tests/llcontrol_test.cpp +++ b/indra/llxml/tests/llcontrol_test.cpp @@ -97,7 +97,7 @@ namespace tut template<> template<> void control_group_t::test<1>() { - int results = mCG->loadFromFile(mTestConfigFile.c_str()); + int results = mCG->loadFromFile(mTestConfigFile); ensure("number of settings", (results == 1)); ensure("value of setting", (mCG->getU32("TestSetting") == 12)); } @@ -106,14 +106,14 @@ namespace tut template<> template<> void control_group_t::test<2>() { - int results = mCG->loadFromFile(mTestConfigFile.c_str()); + int results = mCG->loadFromFile(mTestConfigFile); mCG->setU32("TestSetting", 13); ensure_equals("value of changed setting", mCG->getU32("TestSetting"), 13); LLControlGroup test_cg("foo2"); std::string temp_test_file = (mTestConfigDir + "setting_llsd_temp.xml"); mCleanups.push_back(temp_test_file); mCG->saveToFile(temp_test_file.c_str(), true); - results = test_cg.loadFromFile(temp_test_file.c_str()); + results = test_cg.loadFromFile(temp_test_file); ensure("number of changed settings loaded", (results == 1)); ensure("value of changed settings loaded", (test_cg.getU32("TestSetting") == 13)); } @@ -126,7 +126,7 @@ namespace tut // a default settings file that declares variables, rather than a user // settings file. When loadFromFile() encounters an unrecognized user // settings variable, it forcibly preserves it (CHOP-962). - int results = mCG->loadFromFile(mTestConfigFile.c_str(), true); + int results = mCG->loadFromFile(mTestConfigFile, true); LLControlVariable* control = mCG->getControl("TestSetting"); LLSD new_value = 13; control->setValue(new_value, false); @@ -135,7 +135,7 @@ namespace tut std::string temp_test_file = (mTestConfigDir + "setting_llsd_persist_temp.xml"); mCleanups.push_back(temp_test_file); mCG->saveToFile(temp_test_file.c_str(), true); - results = test_cg.loadFromFile(temp_test_file.c_str()); + results = test_cg.loadFromFile(temp_test_file); //If we haven't changed any settings, then we shouldn't have any settings to load ensure("number of non-persisted changed settings loaded", (results == 0)); } @@ -144,7 +144,7 @@ namespace tut template<> template<> void control_group_t::test<4>() { - int results = mCG->loadFromFile(mTestConfigFile.c_str()); + int results = mCG->loadFromFile(mTestConfigFile); ensure("number of settings", (results == 1)); mCG->getControl("TestSetting")->getSignal()->connect(boost::bind(&this->handleListenerTest)); mCG->setU32("TestSetting", 13); diff --git a/indra/media_plugins/CMakeLists.txt b/indra/media_plugins/CMakeLists.txt index 972bb7dd2d..86c46cb476 100644 --- a/indra/media_plugins/CMakeLists.txt +++ b/indra/media_plugins/CMakeLists.txt @@ -3,8 +3,9 @@ add_subdirectory(base) if (LINUX) - #add_subdirectory(gstreamer010) + add_subdirectory(cef) add_subdirectory(example) + add_subdirectory(gstreamer10) endif (LINUX) if (DARWIN) diff --git a/indra/media_plugins/base/CMakeLists.txt b/indra/media_plugins/base/CMakeLists.txt index 64b6a4228d..5e635c6ca3 100644 --- a/indra/media_plugins/base/CMakeLists.txt +++ b/indra/media_plugins/base/CMakeLists.txt @@ -12,13 +12,6 @@ include(PluginAPI) ### media_plugin_base -if(NOT ADDRESS_SIZE EQUAL 32) - if(WINDOWS) - ##add_definitions(/FIXED:NO) - else(WINDOWS) # not windows therefore gcc LINUX and DARWIN - add_definitions(-fPIC) - endif(WINDOWS) -endif(NOT ADDRESS_SIZE EQUAL 32) set(media_plugin_base_SOURCE_FILES media_plugin_base.cpp diff --git a/indra/media_plugins/base/media_plugin_base.cpp b/indra/media_plugins/base/media_plugin_base.cpp index 545eee25a9..9bb3bad035 100644 --- a/indra/media_plugins/base/media_plugin_base.cpp +++ b/indra/media_plugins/base/media_plugin_base.cpp @@ -167,6 +167,56 @@ void MediaPluginBase::sendStatus() sendMessage(message); } +#if LL_LINUX + +size_t SymbolGrabber::registerSymbol( SymbolToGrab aSymbol ) +{ + gSymbolsToGrab.emplace_back(aSymbol); + return gSymbolsToGrab.size(); +} + +bool SymbolGrabber::grabSymbols(std::vector< std::string > const &aDSONames) +{ + std::cerr << "SYMBOLS: " << gSymbolsToGrab.size() << std::endl; + + if (sSymsGrabbed) + return true; + + //attempt to load the shared libraries + apr_pool_create(&sSymDSOMemoryPool, nullptr); + + for( std::vector< std::string >::const_iterator itr = aDSONames.begin(); itr != aDSONames.end(); ++itr ) + { + apr_dso_handle_t *pDSO(NULL); + std::string strDSO{ *itr }; + if( APR_SUCCESS == apr_dso_load( &pDSO, strDSO.c_str(), sSymDSOMemoryPool )) + sLoadedLibraries.push_back( pDSO ); + + for( auto i = 0; i < gSymbolsToGrab.size(); ++i ) + { + if( !*gSymbolsToGrab[i].mPPFunc ) + apr_dso_sym( gSymbolsToGrab[i].mPPFunc, pDSO, gSymbolsToGrab[i].mName ); + } + } + + bool sym_error = false; + + for( auto i = 0; i < gSymbolsToGrab.size(); ++i ) + { + if( gSymbolsToGrab[ i ].mRequired && ! *gSymbolsToGrab[ i ].mPPFunc ) + sym_error = true; + } + + sSymsGrabbed = !sym_error; + return sSymsGrabbed; +} + +void SymbolGrabber::ungrabSymbols() +{ + +} +#endif + #if LL_WINDOWS # define LLSYMEXPORT __declspec(dllexport) @@ -204,3 +254,50 @@ int WINAPI DllEntryPoint( HINSTANCE hInstance, unsigned long reason, void* param return 1; } #endif + +#if LL_LINUX +pid_t getParentPid( pid_t aPid ) +{ + std::stringstream strm; + strm << "/proc/" << aPid << "/status"; + std::ifstream in{ strm.str() }; + + if( !in.is_open() ) + return 0; + + pid_t res {0}; + while( !in.eof() && res == 0 ) + { + std::string line; + line.resize( 1024, 0 ); + in.getline( &line[0], line.length() ); + + auto i = line.find( "PPid:" ); + + if( i == std::string::npos ) + continue; + + char const *pIn = line.c_str() + 5; // Skip over pid; + while( *pIn != 0 && isspace( *pIn ) ) + ++pIn; + + if( *pIn ) + res = atoll( pIn ); + } + return res; +} + +bool isPluginPid( pid_t aPid ) +{ + auto myPid = getpid(); + + do + { + if( aPid == myPid ) + return true; + aPid = getParentPid( aPid ); + } while( aPid > 1 ); + + return false; +} +#endif diff --git a/indra/media_plugins/base/media_plugin_base.h b/indra/media_plugins/base/media_plugin_base.h index f65c712a66..a084fc9834 100644 --- a/indra/media_plugins/base/media_plugin_base.h +++ b/indra/media_plugins/base/media_plugin_base.h @@ -32,6 +32,41 @@ #include "llpluginmessage.h" #include "llpluginmessageclasses.h" +#if LL_LINUX + +struct SymbolToGrab +{ + bool mRequired; + char const *mName; + apr_dso_handle_sym_t *mPPFunc; +}; + +class SymbolGrabber +{ +public: + size_t registerSymbol( SymbolToGrab aSymbol ); + bool grabSymbols(std::vector< std::string > const &aDSONames); + void ungrabSymbols(); + +private: + std::vector< SymbolToGrab > gSymbolsToGrab; + + bool sSymsGrabbed = false; + apr_pool_t *sSymDSOMemoryPool = nullptr; + std::vector<apr_dso_handle_t *> sLoadedLibraries; +}; + +extern SymbolGrabber gSymbolGrabber; + +// extern SymbolGrabber gSymbolGrabber; + +#define LL_GRAB_SYM(SYMBOL_GRABBER, REQUIRED, SYMBOL_NAME, RETURN, ...) \ + RETURN (*ll##SYMBOL_NAME)(__VA_ARGS__) = nullptr; \ + size_t gRegistered##SYMBOL_NAME = SYMBOL_GRABBER.registerSymbol( \ + { REQUIRED, #SYMBOL_NAME , (apr_dso_handle_sym_t*)&ll##SYMBOL_NAME} \ + ); + +#endif class MediaPluginBase { @@ -46,7 +81,6 @@ public: static void staticReceiveMessage(const char *message_string, void **user_data); protected: - /** Plugin status. */ typedef enum { @@ -126,4 +160,7 @@ int init_media_plugin( LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data); - +#if LL_LINUX +pid_t getParentPid(pid_t aPid); +bool isPluginPid(pid_t aPid); +#endif diff --git a/indra/media_plugins/cef/CMakeLists.txt b/indra/media_plugins/cef/CMakeLists.txt index 0d1a833936..28e5f64132 100644 --- a/indra/media_plugins/cef/CMakeLists.txt +++ b/indra/media_plugins/cef/CMakeLists.txt @@ -10,18 +10,10 @@ include(Linking) include(PluginAPI) include(CEFPlugin) - +include(GLIB) ### media_plugin_cef -if(NOT ADDRESS_SIZE EQUAL 32) - if(WINDOWS) - ##add_definitions(/FIXED:NO) - else(WINDOWS) # not windows therefore gcc LINUX and DARWIN - add_definitions(-fPIC) - endif(WINDOWS) -endif(NOT ADDRESS_SIZE EQUAL 32) - set(media_plugin_cef_SOURCE_FILES media_plugin_cef.cpp ) @@ -32,10 +24,36 @@ set(media_plugin_cef_HEADER_FILES # Select which VolumeCatcher implementation to use if (LINUX) - message(FATAL_ERROR "CEF plugin has been enabled for a Linux compile.\n" - " Please create a volume_catcher implementation for this platform.") + foreach( PULSE_FILE pulse/introspect.h pulse/context.h pulse/subscribe.h pulse/glib-mainloop.h ) + find_path( PULSE_FILE_${PULSE_FILE}_FOUND ${PULSE_FILE} NO_CACHE) + if( NOT PULSE_FILE_${PULSE_FILE}_FOUND ) + message( "Looking for ${PULSE_FILE} ... not found") + message( FATAL_ERROR "Pulse header not found" ) + else() + message( "Looking for ${PULSE_FILE} ... found") + endif() + endforeach() + + include(FindPipeWire) + include_directories(SYSTEM ${PIPEWIRE_INCLUDE_DIRS} ${SPA_INCLUDE_DIRS}) + + message( "Building with Linux volume catcher for PipeWire and PulseAudio" ) + + list(APPEND media_plugin_cef_HEADER_FILES + linux/volume_catcher_linux.h + ) + + set(LINUX_VOLUME_CATCHER + linux/volume_catcher_linux.cpp + linux/volume_catcher_pulseaudio.cpp + linux/volume_catcher_pipewire.cpp + ) + + list(APPEND media_plugin_cef_SOURCE_FILES ${LINUX_VOLUME_CATCHER}) + set(CMAKE_SHARED_LINKER_FLAGS "-Wl,--build-id -Wl,-rpath,'$ORIGIN:$ORIGIN/../../lib'") + list(APPEND media_plugin_cef_LINK_LIBRARIES llwindow ) elseif (DARWIN) - list(APPEND media_plugin_cef_SOURCE_FILES mac_volume_catcher_null.cpp) + list(APPEND media_plugin_cef_SOURCE_FILES volume_catcher_null.cpp) find_library(CORESERVICES_LIBRARY CoreServices) find_library(AUDIOUNIT_LIBRARY AudioUnit) set( media_plugin_cef_LINK_LIBRARIES @@ -60,6 +78,7 @@ add_library(media_plugin_cef target_link_libraries(media_plugin_cef media_plugin_base ll::cef + ll::glib_headers ) if (WINDOWS) diff --git a/indra/media_plugins/cef/linux/volume_catcher_linux.cpp b/indra/media_plugins/cef/linux/volume_catcher_linux.cpp new file mode 100644 index 0000000000..7d33242063 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.cpp @@ -0,0 +1,78 @@ +/** + * @file volume_catcher.cpp + * @brief Linux volume catcher which will pick an implementation to use + * + * @cond + * $LicenseInfo:firstyear=2010&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$ + * @endcond + */ + +#include "volume_catcher_linux.h" + +VolumeCatcher::VolumeCatcher() +{ +} + +void VolumeCatcher::onEnablePipeWireVolumeCatcher(bool enable) +{ + if (pimpl != nullptr) + return; + + if (enable) + { + LL_DEBUGS() << "volume catcher using pipewire" << LL_ENDL; + pimpl = new VolumeCatcherPipeWire(); + } + else + { + LL_DEBUGS() << "volume catcher using pulseaudio" << LL_ENDL; + pimpl = new VolumeCatcherPulseAudio(); + } +} + +VolumeCatcher::~VolumeCatcher() +{ + if (pimpl != nullptr) + { + delete pimpl; + pimpl = nullptr; + } +} + +void VolumeCatcher::setVolume(F32 volume) +{ + if (pimpl != nullptr) { + pimpl->setVolume(volume); + } +} + +void VolumeCatcher::setPan(F32 pan) +{ + if (pimpl != nullptr) + pimpl->setPan(pan); +} + +void VolumeCatcher::pump() +{ + if (pimpl != nullptr) + pimpl->pump(); +} diff --git a/indra/media_plugins/cef/linux/volume_catcher_linux.h b/indra/media_plugins/cef/linux/volume_catcher_linux.h new file mode 100644 index 0000000000..505f9ffb31 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_linux.h @@ -0,0 +1,149 @@ +/** + * @file volume_catcher_impl.h + * @brief + * + * @cond + * $LicenseInfo:firstyear=2010&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$ + * @endcond + */ + +#ifndef VOLUME_CATCHER_LINUX_H +#define VOLUME_CATCHER_LINUX_H + +#include "linden_common.h" + +#include "../volume_catcher.h" + +#include <unordered_set> +#include <mutex> + +extern "C" { +// There's no special reason why we want the *glib* PA mainloop, but the generic polling implementation seems broken. +#include <pulse/glib-mainloop.h> +#include <pulse/context.h> + +#include <pipewire/pipewire.h> + +#include "apr_pools.h" +#include "apr_dso.h" +} + +#include "media_plugin_base.h" + +class VolumeCatcherImpl +{ +public: + virtual ~VolumeCatcherImpl() = default; + + virtual void setVolume(F32 volume) = 0; // 0.0 - 1.0 + + // Set the left-right pan of audio sources + // where -1.0 = left, 0 = center, and 1.0 = right + virtual void setPan(F32 pan) = 0; + + virtual void pump() = 0; // call this at least a few times a second if you can - it affects how quickly we can 'catch' a new audio source and adjust its volume +}; + +class VolumeCatcherPulseAudio : public VolumeCatcherImpl +{ +public: + VolumeCatcherPulseAudio(); + ~VolumeCatcherPulseAudio(); + + void setVolume(F32 volume); + void setPan(F32 pan); + void pump(); + + // for internal use - can't be private because used from our C callbacks + + bool loadsyms(std::string pa_dso_name); + void init(); + void cleanup(); + + void update_all_volumes(F32 volume); + void update_index_volume(U32 index, F32 volume); + void connected_okay(); + + std::set<U32> mSinkInputIndices; + std::map<U32,U32> mSinkInputNumChannels; + F32 mDesiredVolume; + pa_glib_mainloop *mMainloop; + pa_context *mPAContext; + bool mConnected; + bool mGotSyms; +}; + +class VolumeCatcherPipeWire : public VolumeCatcherImpl +{ +public: + VolumeCatcherPipeWire(); + ~VolumeCatcherPipeWire(); + + bool loadsyms(std::string pw_dso_name); + void init(); + void cleanup(); + + // some of these should be private + + void lock(); + void unlock(); + + void setVolume(F32 volume); + void setPan(F32 pan); + void pump(); + + void handleRegistryEventGlobal( + uint32_t id, uint32_t permissions, const char* type, + uint32_t version, const struct spa_dict* props + ); + + class ChildNode + { + public: + bool mActive = false; + + pw_proxy* mProxy = nullptr; + spa_hook mNodeListener {}; + spa_hook mProxyListener {}; + VolumeCatcherPipeWire* mImpl = nullptr; + + void updateVolume(); + void destroy(); + }; + + bool mGotSyms = false; + + F32 mVolume = 1.0f; // max by default + // F32 mPan = 0.0f; // center + + pw_thread_loop* mThreadLoop = nullptr; + pw_context* mContext = nullptr; + pw_core* mCore = nullptr; + pw_registry* mRegistry = nullptr; + spa_hook mRegistryListener; + + std::unordered_set<ChildNode*> mChildNodes; + std::mutex mChildNodesMutex; + std::mutex mCleanupMutex; +}; + +#endif // VOLUME_CATCHER_LINUX_H diff --git a/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp new file mode 100755 index 0000000000..a8f1366d6f --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_pipewire.cpp @@ -0,0 +1,333 @@ +/** + * @file volume_catcher_pipewire.cpp + * @brief A Linux-specific, PipeWire-specific hack to detect and volume-adjust new audio sources + * + * @cond + * $LicenseInfo:firstyear=2010&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$ + * @endcond + */ + +/* + The high-level design is as follows: + 1) Connect to the PipeWire daemon + 2) Find all existing and new audio nodes + 3) Examine PID and parent PID's to see if it belongs to our process + 4) If so, tell PipeWire to adjust the volume of that node + 5) Keep a list of all audio nodes and adjust when we setVolume() + */ + +#include "linden_common.h" + +#include "volume_catcher_linux.h" + +extern "C" { +#include <spa/pod/builder.h> +#include <spa/param/props.h> +} + +SymbolGrabber pwSymbolGrabber; + +#include "volume_catcher_pipewire_syms.inc" + +//////////////////////////////////////////////////// + +VolumeCatcherPipeWire::VolumeCatcherPipeWire() +{ + init(); +} + +VolumeCatcherPipeWire::~VolumeCatcherPipeWire() +{ + cleanup(); +} + +static void registryEventGlobal( + void *data, uint32_t id, uint32_t permissions, const char *type, + uint32_t version, const struct spa_dict *props) +{ + static_cast<VolumeCatcherPipeWire*>(data)->handleRegistryEventGlobal( + id, permissions, type, version, props + ); +} + +static const struct pw_registry_events REGISTRY_EVENTS = { + .version = PW_VERSION_REGISTRY_EVENTS, + .global = registryEventGlobal, +}; + +bool VolumeCatcherPipeWire::loadsyms(std::string pw_dso_name) +{ + return pwSymbolGrabber.grabSymbols({ pw_dso_name }); +} + +void VolumeCatcherPipeWire::init() +{ + LL_DEBUGS() << "init" << LL_ENDL; + + mGotSyms = loadsyms("libpipewire-0.3.so.0"); + + if (!mGotSyms) + return; + + LL_DEBUGS() << "successfully got symbols" << LL_ENDL; + + llpw_init(nullptr, nullptr); + + mThreadLoop = llpw_thread_loop_new("SL Plugin Volume Adjuster", nullptr); + + if (!mThreadLoop) + return; + + // i dont think we need to lock this early + // std::lock_guard pwLock(*this); + + mContext = llpw_context_new( + llpw_thread_loop_get_loop(mThreadLoop), nullptr, 0 + ); + + if (!mContext) + return; + + mCore = llpw_context_connect(mContext, nullptr, 0); + + if (!mCore) + return; + + mRegistry = pw_core_get_registry(mCore, PW_VERSION_REGISTRY, 0); + + LL_DEBUGS() << "pw_core_get_registry: " << (mRegistry?"success":"nullptr") << LL_ENDL; + + spa_zero(mRegistryListener); + + pw_registry_add_listener( + mRegistry, &mRegistryListener, ®ISTRY_EVENTS, this + ); + + llpw_thread_loop_start(mThreadLoop); + + LL_DEBUGS() << "thread loop started" << LL_ENDL; +} + +void VolumeCatcherPipeWire::cleanup() +{ + { + std::unique_lock childNodesLock(mChildNodesMutex); + for (auto *childNode: mChildNodes) + childNode->destroy(); + + mChildNodes.clear(); + } + + { + std::unique_lock pwLock(mCleanupMutex); + if (mRegistry) + llpw_proxy_destroy((struct pw_proxy *) mRegistry); + + spa_zero(mRegistryListener); + + if (mCore) + llpw_core_disconnect(mCore); + if (mContext) + llpw_context_destroy(mContext); + } + + if (!mThreadLoop) + return; + + llpw_thread_loop_stop(mThreadLoop); + llpw_thread_loop_destroy(mThreadLoop); + + LL_DEBUGS() << "cleanup done" << LL_ENDL; +} + +void VolumeCatcherPipeWire::lock() +{ + if (!mThreadLoop) + return; + + llpw_thread_loop_lock(mThreadLoop); +} + +void VolumeCatcherPipeWire::unlock() +{ + if (!mThreadLoop) + return; + + llpw_thread_loop_unlock(mThreadLoop); +} + +const uint32_t channels = 1; +const float resetVolumes[channels] = { 1.0f }; + +void VolumeCatcherPipeWire::ChildNode::updateVolume() +{ + if (!mActive) + return; + + F32 volume = std::clamp(mImpl->mVolume, 0.0f, 1.0f); + + const float volumes[channels] = { volume }; + + uint8_t buffer[512]; + + spa_pod_builder builder; + spa_pod_builder_init(&builder, buffer, sizeof(buffer)); + + spa_pod_frame frame; + spa_pod_builder_push_object(&builder, &frame, SPA_TYPE_OBJECT_Props, SPA_PARAM_Props); + + // resets system-wide memorized volume for chromium (not google chrome) to 100% + spa_pod_builder_prop(&builder, SPA_PROP_channelVolumes, 0); + spa_pod_builder_array(&builder, sizeof(float), SPA_TYPE_Float, channels, resetVolumes); + + // sets temporary volume + spa_pod_builder_prop(&builder, SPA_PROP_softVolumes, 0); + spa_pod_builder_array(&builder, sizeof(float), SPA_TYPE_Float, channels, volumes); + + spa_pod* pod = static_cast<spa_pod*>(spa_pod_builder_pop(&builder, &frame)); + + { + std::lock_guard pwLock(*mImpl); + pw_node_set_param(mProxy, SPA_PARAM_Props, 0, pod); + } +} + +void VolumeCatcherPipeWire::ChildNode::destroy() +{ + if (!mActive) + return; + + mActive = false; + + { + std::unique_lock childNodesLock(mImpl->mChildNodesMutex); + mImpl->mChildNodes.erase(this); + } + + spa_hook_remove(&mNodeListener); + spa_hook_remove(&mProxyListener); + + { + std::lock_guard pwLock(*mImpl); + llpw_proxy_destroy(mProxy); + } +} + +static void nodeEventInfo(void* data, const struct pw_node_info* info) +{ + const char* processId = spa_dict_lookup(info->props, PW_KEY_APP_PROCESS_ID); + + if (processId == nullptr) + return; + + pid_t pid = atoll(processId); + + if (!isPluginPid(pid)) + return; + + const char* appName = spa_dict_lookup(info->props, PW_KEY_APP_NAME); + LL_DEBUGS() << "got app: " << appName << LL_ENDL; + + auto* const childNode = static_cast<VolumeCatcherPipeWire::ChildNode*>(data); + LL_DEBUGS() << "init volume: " << childNode->mImpl->mVolume << LL_ENDL; + + childNode->updateVolume(); + + { + std::lock_guard childNodesLock(childNode->mImpl->mChildNodesMutex); + childNode->mImpl->mChildNodes.insert(childNode); + } +} + +static const struct pw_node_events NODE_EVENTS = { + .version = PW_VERSION_CLIENT_EVENTS, + .info = nodeEventInfo, +}; + +static void proxyEventDestroy(void* data) +{ + auto* const childNode = static_cast<VolumeCatcherPipeWire::ChildNode*>(data); + childNode->destroy(); +} + +static void proxyEventRemoved(void* data) +{ + auto* const childNode = static_cast<VolumeCatcherPipeWire::ChildNode*>(data); + childNode->destroy(); +} + +static const struct pw_proxy_events PROXY_EVENTS = { + .version = PW_VERSION_PROXY_EVENTS, + .destroy = proxyEventDestroy, + .removed = proxyEventRemoved, +}; + +void VolumeCatcherPipeWire::handleRegistryEventGlobal( + uint32_t id, uint32_t permissions, const char *type, uint32_t version, + const struct spa_dict *props) +{ + if (props == nullptr || type == nullptr || strcmp(type, PW_TYPE_INTERFACE_Node) != 0) + return; + + const char* mediaClass = spa_dict_lookup(props, PW_KEY_MEDIA_CLASS); + + if (mediaClass == nullptr || strcmp(mediaClass, "Stream/Output/Audio") != 0) + return; + + pw_proxy* proxy = static_cast<pw_proxy*>( + pw_registry_bind(mRegistry, id, type, PW_VERSION_CLIENT, sizeof(ChildNode)) + ); + + auto* const childNode = static_cast<ChildNode*>(llpw_proxy_get_user_data(proxy)); + + childNode->mActive = true; + childNode->mProxy = proxy; + childNode->mImpl = this; + + pw_node_add_listener(proxy, &childNode->mNodeListener, &NODE_EVENTS, childNode); + llpw_proxy_add_listener(proxy, &childNode->mProxyListener, &PROXY_EVENTS, childNode); +} + +void VolumeCatcherPipeWire::setVolume(F32 volume) +{ + LL_DEBUGS() << "setting volume to: " << volume << LL_ENDL; + + mVolume = volume; + + { + std::unique_lock childNodeslock(mChildNodesMutex); + std::unordered_set<ChildNode *> copyOfChildNodes(mChildNodes); + + LL_DEBUGS() << "found " << copyOfChildNodes.size() << " child nodes" << LL_ENDL; + + for (auto* childNode : copyOfChildNodes) + childNode->updateVolume(); + } +} + +void VolumeCatcherPipeWire::setPan(F32 pan) +{ +} + +void VolumeCatcherPipeWire::pump() +{ +} diff --git a/indra/media_plugins/cef/linux/volume_catcher_pipewire_syms.inc b/indra/media_plugins/cef/linux/volume_catcher_pipewire_syms.inc new file mode 100644 index 0000000000..dbc0f5f169 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_pipewire_syms.inc @@ -0,0 +1,26 @@ +#define G pwSymbolGrabber + +// required symbols to grab +LL_GRAB_SYM(G, true, pw_init, void, int *argc, char **argv[]); +// LL_GRAB_SYM(G, true, pw_main_loop_new, struct pw_main_loop *, const struct spa_dict *props); +// LL_GRAB_SYM(G, true, pw_main_loop_get_loop, struct pw_loop *, struct pw_main_loop *loop); +// LL_GRAB_SYM(G, true, pw_main_loop_destroy, void, struct pw_main_loop *loop); +// LL_GRAB_SYM(G, true, pw_main_loop_run, void, struct pw_main_loop *loop); +LL_GRAB_SYM(G, true, pw_context_new, struct pw_context *, struct pw_loop *main_loop, struct pw_properties *props, size_t user_data_size); +LL_GRAB_SYM(G, true, pw_context_destroy, void, struct pw_context *context); +LL_GRAB_SYM(G, true, pw_context_connect, struct pw_core *, struct pw_context *context, struct pw_properties *properties, size_t user_data_size); +LL_GRAB_SYM(G, true, pw_thread_loop_new, struct pw_thread_loop *, const char *name, const struct spa_dict *props); +LL_GRAB_SYM(G, true, pw_thread_loop_destroy, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(G, true, pw_thread_loop_get_loop, struct pw_loop *, struct pw_thread_loop *loop); +LL_GRAB_SYM(G, true, pw_thread_loop_start, int, struct pw_thread_loop *loop); +LL_GRAB_SYM(G, true, pw_thread_loop_stop, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(G, true, pw_thread_loop_lock, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(G, true, pw_thread_loop_unlock, void, struct pw_thread_loop *loop); +LL_GRAB_SYM(G, true, pw_proxy_add_listener, void, struct pw_proxy *proxy, struct spa_hook *listener, const struct pw_proxy_events *events, void *data); +LL_GRAB_SYM(G, true, pw_proxy_destroy, void, struct pw_proxy *proxy); +LL_GRAB_SYM(G, true, pw_proxy_get_user_data, void *, struct pw_proxy *proxy); +LL_GRAB_SYM(G, true, pw_core_disconnect, int, struct pw_core *core); + +// optional symbols to grab + +#undef G diff --git a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp new file mode 100755 index 0000000000..f8a48a91fd --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio.cpp @@ -0,0 +1,322 @@ +/** + * @file volume_catcher_pulseaudio.cpp + * @brief A Linux-specific, PulseAudio-specific hack to detect and volume-adjust new audio sources + * + * @cond + * $LicenseInfo:firstyear=2010&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$ + * @endcond + */ + +/* + The high-level design is as follows: + 1) Connect to the PulseAudio daemon + 2) Watch for the creation of new audio players connecting to the daemon (this includes ALSA clients running on the PulseAudio emulation layer, such as Flash plugins) + 3) Examine any new audio player's PID to see if it belongs to our own process + 4) If so, tell PA to adjust the volume of that audio player ('sink input' in PA parlance) + 5) Keep a list of all living audio players that we care about, adjust the volumes of all of them when we get a new setVolume() call + */ + +#include "linden_common.h" + +#include "volume_catcher_linux.h" + +extern "C" { +#include <glib.h> +#include <glib-object.h> + +#include <pulse/introspect.h> + +#include <pulse/subscribe.h> +} + +SymbolGrabber paSymbolGrabber; + +#include "volume_catcher_pulseaudio_syms.inc" +#include "volume_catcher_pulseaudio_glib_syms.inc" + +//////////////////////////////////////////////////// + +// PulseAudio requires a chain of callbacks with C linkage +extern "C" { + void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *i, int eol, void *userdata); + void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata); + void callback_context_state(pa_context *context, void *userdata); +} + +VolumeCatcherPulseAudio::VolumeCatcherPulseAudio() + : mDesiredVolume(0.0f), + mMainloop(nullptr), + mPAContext(nullptr), + mConnected(false), + mGotSyms(false) +{ + init(); +} + +VolumeCatcherPulseAudio::~VolumeCatcherPulseAudio() +{ + cleanup(); +} + +bool VolumeCatcherPulseAudio::loadsyms(std::string pulse_dso_name) +{ + return paSymbolGrabber.grabSymbols({ pulse_dso_name }); +} + +void VolumeCatcherPulseAudio::init() +{ + // try to be as defensive as possible because PA's interface is a + // bit fragile and (for our purposes) we'd rather simply not function + // than crash + + // we cheat and rely upon libpulse-mainloop-glib.so.0 to pull-in + // libpulse.so.0 - this isn't a great assumption, and the two DSOs should + // probably be loaded separately. Our Linux DSO framework needs refactoring, + // we do this sort of thing a lot with practically identical logic... + mGotSyms = loadsyms("libpulse-mainloop-glib.so.0"); + + if (!mGotSyms) + mGotSyms = loadsyms("libpulse.so.0"); + + if (!mGotSyms) + return; + + mMainloop = llpa_glib_mainloop_new(g_main_context_default()); + + if (mMainloop) + { + pa_mainloop_api *api = llpa_glib_mainloop_get_api(mMainloop); + + if (api) + { + pa_proplist *proplist = llpa_proplist_new(); + + if (proplist) + { + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "multimedia-player"); + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "com.secondlife.viewer.mediaplugvoladjust"); + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "SL Plugin Volume Adjuster"); + llpa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "1"); + + // plain old pa_context_new() is broken! + mPAContext = llpa_context_new_with_proplist(api, nullptr, proplist); + + llpa_proplist_free(proplist); + } + } + } + + // Now we've set up a PA context and mainloop, try connecting the + // PA context to a PA daemon. + if (mPAContext) + { + llpa_context_set_state_callback(mPAContext, callback_context_state, this); + pa_context_flags_t cflags = (pa_context_flags)0; // maybe add PA_CONTEXT_NOAUTOSPAWN? + if (llpa_context_connect(mPAContext, nullptr, cflags, nullptr) >= 0) + { + // Okay! We haven't definitely connected, but we + // haven't definitely failed yet. + } + else + { + // Failed to connect to PA manager... we'll leave + // things like that. Perhaps we should try again later. + } + } +} + +void VolumeCatcherPulseAudio::cleanup() +{ + mConnected = false; + + if (mGotSyms && mPAContext) + { + llpa_context_disconnect(mPAContext); + llpa_context_unref(mPAContext); + } + + mPAContext = nullptr; + + if (mGotSyms && mMainloop) + llpa_glib_mainloop_free(mMainloop); + + mMainloop = nullptr; +} + +void VolumeCatcherPulseAudio::setVolume(F32 volume) +{ + mDesiredVolume = volume; + + if (!mGotSyms) + return; + + if (mConnected && mPAContext) + { + update_all_volumes(mDesiredVolume); + } + + pump(); +} + +void VolumeCatcherPulseAudio::setPan(F32 pan) +{ +} + +void VolumeCatcherPulseAudio::pump() +{ + gboolean may_block = FALSE; + g_main_context_iteration(g_main_context_default(), may_block); +} + +void VolumeCatcherPulseAudio::connected_okay() +{ + pa_operation *op; + + // fetch global list of existing sinkinputs + if ((op = llpa_context_get_sink_input_info_list(mPAContext, + callback_discovered_sinkinput, + this))) + { + llpa_operation_unref(op); + } + + // subscribe to future global sinkinput changes + llpa_context_set_subscribe_callback(mPAContext, + callback_subscription_alert, + this); + if ((op = llpa_context_subscribe(mPAContext, (pa_subscription_mask_t) + (PA_SUBSCRIPTION_MASK_SINK_INPUT), + nullptr, nullptr))) + { + llpa_operation_unref(op); + } +} + +void VolumeCatcherPulseAudio::update_all_volumes(F32 volume) +{ + for (std::set<U32>::iterator it = mSinkInputIndices.begin(); + it != mSinkInputIndices.end(); ++it) + { + update_index_volume(*it, volume); + } +} + +void VolumeCatcherPulseAudio::update_index_volume(U32 index, F32 volume) +{ + static pa_cvolume cvol; + llpa_cvolume_set(&cvol, mSinkInputNumChannels[index], + llpa_sw_volume_from_linear(volume)); + + pa_context *c = mPAContext; + uint32_t idx = index; + const pa_cvolume *cvolumep = &cvol; + pa_context_success_cb_t cb = nullptr; // okay as null + void *userdata = nullptr; // okay as null + + pa_operation *op; + if ((op = llpa_context_set_sink_input_volume(c, idx, cvolumep, cb, userdata))) + llpa_operation_unref(op); +} + +void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *sii, int eol, void *userdata) +{ + VolumeCatcherPulseAudio *impl = dynamic_cast<VolumeCatcherPulseAudio*>((VolumeCatcherPulseAudio*)userdata); + llassert(impl); + + if (0 == eol) + { + pa_proplist *proplist = sii->proplist; + pid_t sinkpid = atoll(llpa_proplist_gets(proplist, PA_PROP_APPLICATION_PROCESS_ID)); + + if (isPluginPid( sinkpid )) // does the discovered sinkinput belong to this process? + { + bool is_new = (impl->mSinkInputIndices.find(sii->index) == impl->mSinkInputIndices.end()); + + impl->mSinkInputIndices.insert(sii->index); + impl->mSinkInputNumChannels[sii->index] = sii->channel_map.channels; + + if (is_new) + { + // new! + impl->update_index_volume(sii->index, impl->mDesiredVolume); + } + else + { + // seen it already, do nothing. + } + } + } +} + +void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata) +{ + VolumeCatcherPulseAudio *impl = dynamic_cast<VolumeCatcherPulseAudio*>((VolumeCatcherPulseAudio*)userdata); + llassert(impl); + + switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) + { + case PA_SUBSCRIPTION_EVENT_SINK_INPUT: + if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) + { + // forget this sinkinput, if we were caring about it + impl->mSinkInputIndices.erase(index); + impl->mSinkInputNumChannels.erase(index); + } + else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) + { + // ask for more info about this new sinkinput + pa_operation *op; + if ((op = llpa_context_get_sink_input_info(impl->mPAContext, index, callback_discovered_sinkinput, impl))) + { + llpa_operation_unref(op); + } + } + else + { + // property change on this sinkinput - we don't care. + } + break; + + default:; + } +} + +void callback_context_state(pa_context *context, void *userdata) +{ + VolumeCatcherPulseAudio *impl = dynamic_cast<VolumeCatcherPulseAudio*>((VolumeCatcherPulseAudio*)userdata); + llassert(impl); + + switch (llpa_context_get_state(context)) + { + case PA_CONTEXT_READY: + impl->mConnected = true; + impl->connected_okay(); + break; + case PA_CONTEXT_TERMINATED: + impl->mConnected = false; + break; + case PA_CONTEXT_FAILED: + impl->mConnected = false; + break; + default:; + } +} diff --git a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_glib_syms.inc b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_glib_syms.inc new file mode 100755 index 0000000000..e9b7196e51 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_glib_syms.inc @@ -0,0 +1,10 @@ +#define G paSymbolGrabber + +// required symbols to grab +LL_GRAB_SYM(G, true, pa_glib_mainloop_free, void, pa_glib_mainloop* g) +LL_GRAB_SYM(G, true, pa_glib_mainloop_get_api, pa_mainloop_api*, pa_glib_mainloop* g) +LL_GRAB_SYM(G, true, pa_glib_mainloop_new, pa_glib_mainloop *, GMainContext *c) + +// optional symbols to grab + +#undef G diff --git a/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_syms.inc b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_syms.inc new file mode 100755 index 0000000000..4859a34405 --- /dev/null +++ b/indra/media_plugins/cef/linux/volume_catcher_pulseaudio_syms.inc @@ -0,0 +1,29 @@ +#define G paSymbolGrabber + +// required symbols to grab +LL_GRAB_SYM(G, true, pa_context_connect, int, pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api) +LL_GRAB_SYM(G, true, pa_context_disconnect, void, pa_context *c) +LL_GRAB_SYM(G, true, pa_context_get_sink_input_info, pa_operation*, pa_context *c, uint32_t idx, pa_sink_input_info_cb_t cb, void *userdata) +LL_GRAB_SYM(G, true, pa_context_get_sink_input_info_list, pa_operation*, pa_context *c, pa_sink_input_info_cb_t cb, void *userdata) +LL_GRAB_SYM(G, true, pa_context_get_state, pa_context_state_t, pa_context *c) +LL_GRAB_SYM(G, true, pa_context_new_with_proplist, pa_context*, pa_mainloop_api *mainloop, const char *name, pa_proplist *proplist) +LL_GRAB_SYM(G, true, pa_context_set_sink_input_volume, pa_operation*, pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata) +LL_GRAB_SYM(G, true, pa_context_set_state_callback, void, pa_context *c, pa_context_notify_cb_t cb, void *userdata) +LL_GRAB_SYM(G, true, pa_context_set_subscribe_callback, void, pa_context *c, pa_context_subscribe_cb_t cb, void *userdata) +LL_GRAB_SYM(G, true, pa_context_subscribe, pa_operation*, pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata) +LL_GRAB_SYM(G, true, pa_context_unref, void, pa_context *c) +LL_GRAB_SYM(G, true, pa_cvolume_set, pa_cvolume*, pa_cvolume *a, unsigned channels, pa_volume_t v) +LL_GRAB_SYM(G, true, pa_operation_unref, void, pa_operation *o) +LL_GRAB_SYM(G, true, pa_proplist_free, void, pa_proplist* p) +LL_GRAB_SYM(G, true, pa_proplist_gets, const char*, pa_proplist *p, const char *key) +LL_GRAB_SYM(G, true, pa_proplist_new, pa_proplist*, void) +LL_GRAB_SYM(G, true, pa_proplist_sets, int, pa_proplist *p, const char *key, const char *value) +LL_GRAB_SYM(G, true, pa_sw_volume_from_linear, pa_volume_t, double v) +// LL_GRAB_SYM(G, true, pa_mainloop_free, void, pa_mainloop *m) +// LL_GRAB_SYM(G, true, pa_mainloop_get_api, pa_mainloop_api *, pa_mainloop *m) +// LL_GRAB_SYM(G, true, pa_mainloop_iterate, int, pa_mainloop *m, int block, int *retval) +// LL_GRAB_SYM(G, true, pa_mainloop_new, pa_mainloop *, void) + +// optional symbols to grab + +#undef G diff --git a/indra/media_plugins/cef/media_plugin_cef.cpp b/indra/media_plugins/cef/media_plugin_cef.cpp index 64fc7e452b..9c205b558c 100644 --- a/indra/media_plugins/cef/media_plugin_cef.cpp +++ b/indra/media_plugins/cef/media_plugin_cef.cpp @@ -886,7 +886,7 @@ void MediaPluginCEF::receiveMessage(const char* message_string) keyEvent(key_event, native_key_data); -#elif LL_WINDOWS +#else std::string event = message_in.getValue("event"); LLSD native_key_data = message_in.getValueLLSD("native_key_data"); @@ -908,6 +908,13 @@ void MediaPluginCEF::receiveMessage(const char* message_string) { mEnableMediaPluginDebugging = message_in.getValueBoolean("enable"); } +#if LL_LINUX + else if (message_name == "enable_pipewire_volume_catcher") + { + bool enable = message_in.getValueBoolean("enable"); + mVolumeCatcher.onEnablePipeWireVolumeCatcher(enable); + } +#endif if (message_name == "pick_file_response") { LLSD file_list_llsd = message_in.getValueLLSD("file_list"); @@ -1050,6 +1057,28 @@ void MediaPluginCEF::keyEvent(dullahan::EKeyEvent key_event, LLSD native_key_dat mCEFLib->nativeKeyboardEventWin(msg, wparam, lparam); #endif + +#if LL_LINUX + + uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key"].asInteger()); // this is actually the SDL event.key.keysym.sym; + uint32_t native_virtual_key_win = (uint32_t)(native_key_data["virtual_key_win"].asInteger()); + uint32_t native_modifiers = (uint32_t)(native_key_data["modifiers"].asInteger()); + + // only for non-printable keysyms, the actual text input is done in unicodeInput() below + if (native_virtual_key <= 0x1b || native_virtual_key >= 0x7f) + { + // set keypad flag, not sure if this even does anything + bool keypad = false; + if (native_virtual_key_win >= 0x60 && native_virtual_key_win <= 0x6f) + { + keypad = true; + } + + // yes, we send native_virtual_key_win twice because native_virtual_key breaks it + mCEFLib->nativeKeyboardEventSDL2(key_event, native_virtual_key, native_modifiers, keypad); + } + +#endif // LL_LINUX }; void MediaPluginCEF::unicodeInput(std::string event, LLSD native_key_data = LLSD::emptyMap()) @@ -1080,6 +1109,16 @@ void MediaPluginCEF::unicodeInput(std::string event, LLSD native_key_data = LLSD U64 lparam = ll_U32_from_sd(native_key_data["l_param"]); mCEFLib->nativeKeyboardEventWin(msg, wparam, lparam); #endif + +#if LL_LINUX + + uint32_t native_scan_code = (uint32_t)(native_key_data["sdl_sym"].asInteger()); + uint32_t native_virtual_key = (uint32_t)(native_key_data["virtual_key"].asInteger()); + uint32_t native_modifiers = (uint32_t)(native_key_data["modifiers"].asInteger()); + + mCEFLib->nativeKeyboardEvent(dullahan::KE_KEY_DOWN, native_scan_code, native_virtual_key, native_modifiers); + +#endif // LL_LINUX }; //////////////////////////////////////////////////////////////////////////////// diff --git a/indra/media_plugins/cef/volume_catcher.h b/indra/media_plugins/cef/volume_catcher.h index ea97a24947..6933854e8e 100644 --- a/indra/media_plugins/cef/volume_catcher.h +++ b/indra/media_plugins/cef/volume_catcher.h @@ -35,19 +35,20 @@ class VolumeCatcherImpl; class VolumeCatcher { - public: +public: VolumeCatcher(); ~VolumeCatcher(); - void setVolume(F32 volume); // 0.0 - 1.0 - - // Set the left-right pan of audio sources - // where -1.0 = left, 0 = center, and 1.0 = right + void setVolume(F32 volume); void setPan(F32 pan); - void pump(); // call this at least a few times a second if you can - it affects how quickly we can 'catch' a new audio source and adjust its volume + void pump(); + +#if LL_LINUX + void onEnablePipeWireVolumeCatcher(bool enable); +#endif - private: +private: VolumeCatcherImpl *pimpl; }; diff --git a/indra/media_plugins/cef/mac_volume_catcher_null.cpp b/indra/media_plugins/cef/volume_catcher_null.cpp index c479e24a95..1f0f1e8e38 100644 --- a/indra/media_plugins/cef/mac_volume_catcher_null.cpp +++ b/indra/media_plugins/cef/volume_catcher_null.cpp @@ -1,5 +1,5 @@ /** - * @file windows_volume_catcher.cpp + * @file volume_catcher_null.cpp * @brief A null implementation of volume level control of all audio channels opened by a process. * We are using this for the macOS version for now until we can understand how to make the * exitising mac_volume_catcher.cpp work without the (now, non-existant) QuickTime dependency @@ -29,67 +29,25 @@ */ #include "volume_catcher.h" -#include "llsingleton.h" -class VolumeCatcherImpl : public LLSingleton<VolumeCatcherImpl> -{ - LLSINGLETON(VolumeCatcherImpl); - // This is a singleton class -- both callers and the component implementation should use getInstance() to find the instance. - ~VolumeCatcherImpl(); - -public: - - void setVolume(F32 volume); - void setPan(F32 pan); - -private: - F32 mVolume; - F32 mPan; - bool mSystemIsVistaOrHigher; -}; - -VolumeCatcherImpl::VolumeCatcherImpl() -: mVolume(1.0f), // default volume is max - mPan(0.f) // default pan is centered -{ -} - -VolumeCatcherImpl::~VolumeCatcherImpl() -{ -} - -void VolumeCatcherImpl::setVolume(F32 volume) -{ - mVolume = volume; -} - -void VolumeCatcherImpl::setPan(F32 pan) -{ // remember pan for calculating individual channel levels later - mPan = pan; -} ///////////////////////////////////////////////////// VolumeCatcher::VolumeCatcher() { - pimpl = VolumeCatcherImpl::getInstance(); } VolumeCatcher::~VolumeCatcher() { - // Let the instance persist until exit. } void VolumeCatcher::setVolume(F32 volume) { - pimpl->setVolume(volume); } void VolumeCatcher::setPan(F32 pan) { - pimpl->setPan(pan); } void VolumeCatcher::pump() { - // No periodic tasks are necessary for this implementation. } diff --git a/indra/media_plugins/cef/windows_volume_catcher.cpp b/indra/media_plugins/cef/windows_volume_catcher.cpp index e7daeb5f74..1e52fee9de 100644 --- a/indra/media_plugins/cef/windows_volume_catcher.cpp +++ b/indra/media_plugins/cef/windows_volume_catcher.cpp @@ -44,7 +44,6 @@ public: private: F32 mVolume; F32 mPan; - bool mSystemIsVistaOrHigher; }; VolumeCatcherImpl::VolumeCatcherImpl() diff --git a/indra/media_plugins/example/CMakeLists.txt b/indra/media_plugins/example/CMakeLists.txt index 41e2353f31..71343d5f85 100644 --- a/indra/media_plugins/example/CMakeLists.txt +++ b/indra/media_plugins/example/CMakeLists.txt @@ -13,14 +13,6 @@ include(ExamplePlugin) ### media_plugin_example -if(NOT ADDRESS_SIZE EQUAL 32) - if(WINDOWS) - ##add_definitions(/FIXED:NO) - else(WINDOWS) # not windows therefore gcc LINUX and DARWIN - add_definitions(-fPIC) - endif(WINDOWS) -endif(NOT ADDRESS_SIZE EQUAL 32) - set(media_plugin_example_SOURCE_FILES media_plugin_example.cpp ) diff --git a/indra/media_plugins/gstreamer010/CMakeLists.txt b/indra/media_plugins/gstreamer010/CMakeLists.txt deleted file mode 100644 index 38fc8201bf..0000000000 --- a/indra/media_plugins/gstreamer010/CMakeLists.txt +++ /dev/null @@ -1,46 +0,0 @@ -# -*- cmake -*- - -project(media_plugin_gstreamer010) - -include(00-Common) -include(LLCommon) -include(LLImage) -include(LLMath) -include(LLWindow) -include(Linking) -include(PluginAPI) -include(OpenGL) - -include(GStreamer010Plugin) - -### media_plugin_gstreamer010 - -if(NOT ADDRESS_SIZE EQUAL 32) - if(WINDOWS) - ##add_definitions(/FIXED:NO) - else(WINDOWS) # not windows therefore gcc LINUX and DARWIN - add_definitions(-fPIC) - endif(WINDOWS) -endif(NOT ADDRESS_SIZE EQUAL 32) - -set(media_plugin_gstreamer010_SOURCE_FILES - media_plugin_gstreamer010.cpp - llmediaimplgstreamer_syms.cpp - llmediaimplgstreamervidplug.cpp - ) - -set(media_plugin_gstreamer010_HEADER_FILES - llmediaimplgstreamervidplug.h - llmediaimplgstreamer_syms.h - llmediaimplgstreamertriviallogging.h - ) - -add_library(media_plugin_gstreamer010 - SHARED - ${media_plugin_gstreamer010_SOURCE_FILES} - ) - -target_link_libraries(media_plugin_gstreamer010 - media_plugin_base - ll::gstreamer - ) diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.cpp b/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.cpp deleted file mode 100644 index dcc04b37e4..0000000000 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.cpp +++ /dev/null @@ -1,167 +0,0 @@ -/** - * @file llmediaimplgstreamer_syms.cpp - * @brief dynamic GStreamer symbol-grabbing code - * - * @cond - * $LicenseInfo:firstyear=2007&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$ - * @endcond - */ - -#if LL_GSTREAMER010_ENABLED - -#include <string> - -extern "C" { -#include <gst/gst.h> - -#include "apr_pools.h" -#include "apr_dso.h" -} - -#include "llmediaimplgstreamertriviallogging.h" - -#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) RTN (*ll##GSTSYM)(__VA_ARGS__) = NULL -#include "llmediaimplgstreamer_syms_raw.inc" -#include "llmediaimplgstreamer_syms_rawv.inc" -#undef LL_GST_SYM - -// a couple of stubs for disgusting reasons -GstDebugCategory* -ll_gst_debug_category_new(gchar *name, guint color, gchar *description) -{ - static GstDebugCategory dummy; - return &dummy; -} -void ll_gst_debug_register_funcptr(GstDebugFuncPtr func, gchar* ptrname) -{ -} - -static bool sSymsGrabbed = false; -static apr_pool_t *sSymGSTDSOMemoryPool = NULL; -static apr_dso_handle_t *sSymGSTDSOHandleG = NULL; -static apr_dso_handle_t *sSymGSTDSOHandleV = NULL; - - -bool grab_gst_syms(std::string gst_dso_name, - std::string gst_dso_name_vid) -{ - if (sSymsGrabbed) - { - // already have grabbed good syms - return TRUE; - } - - bool sym_error = false; - bool rtn = false; - apr_status_t rv; - apr_dso_handle_t *sSymGSTDSOHandle = NULL; - -#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) do{rv = apr_dso_sym((apr_dso_handle_sym_t*)&ll##GSTSYM, sSymGSTDSOHandle, #GSTSYM); if (rv != APR_SUCCESS) {INFOMSG("Failed to grab symbol: %s", #GSTSYM); if (REQ) sym_error = true;} else DEBUGMSG("grabbed symbol: %s from %p", #GSTSYM, (void*)ll##GSTSYM);}while(0) - - //attempt to load the shared libraries - apr_pool_create(&sSymGSTDSOMemoryPool, NULL); - - if ( APR_SUCCESS == (rv = apr_dso_load(&sSymGSTDSOHandle, - gst_dso_name.c_str(), - sSymGSTDSOMemoryPool) )) - { - INFOMSG("Found DSO: %s", gst_dso_name.c_str()); -#include "llmediaimplgstreamer_syms_raw.inc" - - if ( sSymGSTDSOHandle ) - { - sSymGSTDSOHandleG = sSymGSTDSOHandle; - sSymGSTDSOHandle = NULL; - } - - if ( APR_SUCCESS == - (rv = apr_dso_load(&sSymGSTDSOHandle, - gst_dso_name_vid.c_str(), - sSymGSTDSOMemoryPool) )) - { - INFOMSG("Found DSO: %s", gst_dso_name_vid.c_str()); -#include "llmediaimplgstreamer_syms_rawv.inc" - rtn = !sym_error; - } - else - { - INFOMSG("Couldn't load DSO: %s", gst_dso_name_vid.c_str()); - rtn = false; // failure - } - } - else - { - INFOMSG("Couldn't load DSO: %s", gst_dso_name.c_str()); - rtn = false; // failure - } - - if (sym_error) - { - WARNMSG("Failed to find necessary symbols in GStreamer libraries."); - } - - if ( sSymGSTDSOHandle ) - { - sSymGSTDSOHandleV = sSymGSTDSOHandle; - sSymGSTDSOHandle = NULL; - } -#undef LL_GST_SYM - - sSymsGrabbed = !!rtn; - return rtn; -} - - -void ungrab_gst_syms() -{ - // should be safe to call regardless of whether we've - // actually grabbed syms. - - if ( sSymGSTDSOHandleG ) - { - apr_dso_unload(sSymGSTDSOHandleG); - sSymGSTDSOHandleG = NULL; - } - - if ( sSymGSTDSOHandleV ) - { - apr_dso_unload(sSymGSTDSOHandleV); - sSymGSTDSOHandleV = NULL; - } - - if ( sSymGSTDSOMemoryPool ) - { - apr_pool_destroy(sSymGSTDSOMemoryPool); - sSymGSTDSOMemoryPool = NULL; - } - - // NULL-out all of the symbols we'd grabbed -#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) do{ll##GSTSYM = NULL;}while(0) -#include "llmediaimplgstreamer_syms_raw.inc" -#include "llmediaimplgstreamer_syms_rawv.inc" -#undef LL_GST_SYM - - sSymsGrabbed = false; -} - - -#endif // LL_GSTREAMER010_ENABLED diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.h b/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.h deleted file mode 100644 index 57d446c7df..0000000000 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.h +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @file llmediaimplgstreamer_syms.h - * @brief dynamic GStreamer symbol-grabbing code - * - * @cond - * $LicenseInfo:firstyear=2007&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$ - * @endcond - */ - -#include "linden_common.h" - -#if LL_GSTREAMER010_ENABLED - -extern "C" { -#include <gst/gst.h> -} - -bool grab_gst_syms(std::string gst_dso_name, - std::string gst_dso_name_vid); -void ungrab_gst_syms(); - -#define LL_GST_SYM(REQ, GSTSYM, RTN, ...) extern RTN (*ll##GSTSYM)(__VA_ARGS__) -#include "llmediaimplgstreamer_syms_raw.inc" -#include "llmediaimplgstreamer_syms_rawv.inc" -#undef LL_GST_SYM - -// regrettable hacks to give us better runtime compatibility with older systems -#define llg_return_if_fail(COND) do{if (!(COND)) return;}while(0) -#define llg_return_val_if_fail(COND,V) do{if (!(COND)) return V;}while(0) - -// regrettable hacks because GStreamer was not designed for runtime loading -#undef GST_TYPE_MESSAGE -#define GST_TYPE_MESSAGE (llgst_message_get_type()) -#undef GST_TYPE_OBJECT -#define GST_TYPE_OBJECT (llgst_object_get_type()) -#undef GST_TYPE_PIPELINE -#define GST_TYPE_PIPELINE (llgst_pipeline_get_type()) -#undef GST_TYPE_ELEMENT -#define GST_TYPE_ELEMENT (llgst_element_get_type()) -#undef GST_TYPE_VIDEO_SINK -#define GST_TYPE_VIDEO_SINK (llgst_video_sink_get_type()) -// more regrettable hacks to stub-out these .h-exposed GStreamer internals -void ll_gst_debug_register_funcptr(GstDebugFuncPtr func, gchar* ptrname); -#undef _gst_debug_register_funcptr -#define _gst_debug_register_funcptr ll_gst_debug_register_funcptr -GstDebugCategory* ll_gst_debug_category_new(gchar *name, guint color, gchar *description); -#undef _gst_debug_category_new -#define _gst_debug_category_new ll_gst_debug_category_new -#undef __gst_debug_enabled -#define __gst_debug_enabled (0) - -// more hacks -#define LLGST_MESSAGE_TYPE_NAME(M) (llgst_message_type_get_name(GST_MESSAGE_TYPE(M))) - -#endif // LL_GSTREAMER010_ENABLED diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms_raw.inc b/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms_raw.inc deleted file mode 100644 index b33e59363d..0000000000 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms_raw.inc +++ /dev/null @@ -1,51 +0,0 @@ - -// required symbols to grab -LL_GST_SYM(true, gst_pad_peer_accept_caps, gboolean, GstPad *pad, GstCaps *caps); -LL_GST_SYM(true, gst_buffer_new, GstBuffer*, void); -LL_GST_SYM(true, gst_buffer_set_caps, void, GstBuffer*, GstCaps *); -LL_GST_SYM(true, gst_structure_set_value, void, GstStructure *, const gchar *, const GValue*); -LL_GST_SYM(true, gst_init_check, gboolean, int *argc, char **argv[], GError ** err); -LL_GST_SYM(true, gst_message_get_type, GType, void); -LL_GST_SYM(true, gst_message_type_get_name, const gchar*, GstMessageType type); -LL_GST_SYM(true, gst_message_parse_error, void, GstMessage *message, GError **gerror, gchar **debug); -LL_GST_SYM(true, gst_message_parse_warning, void, GstMessage *message, GError **gerror, gchar **debug); -LL_GST_SYM(true, gst_message_parse_state_changed, void, GstMessage *message, GstState *oldstate, GstState *newstate, GstState *pending); -LL_GST_SYM(true, gst_element_set_state, GstStateChangeReturn, GstElement *element, GstState state); -LL_GST_SYM(true, gst_object_unref, void, gpointer object); -LL_GST_SYM(true, gst_object_get_type, GType, void); -LL_GST_SYM(true, gst_pipeline_get_type, GType, void); -LL_GST_SYM(true, gst_pipeline_get_bus, GstBus*, GstPipeline *pipeline); -LL_GST_SYM(true, gst_bus_add_watch, guint, GstBus * bus, GstBusFunc func, gpointer user_data); -LL_GST_SYM(true, gst_element_factory_make, GstElement*, const gchar *factoryname, const gchar *name); -LL_GST_SYM(true, gst_element_get_type, GType, void); -LL_GST_SYM(true, gst_static_pad_template_get, GstPadTemplate*, GstStaticPadTemplate *pad_template); -LL_GST_SYM(true, gst_element_class_add_pad_template, void, GstElementClass *klass, GstPadTemplate *temp); -LL_GST_SYM(true, gst_element_class_set_details, void, GstElementClass *klass, const GstElementDetails *details); -LL_GST_SYM(true, gst_caps_unref, void, GstCaps* caps); -LL_GST_SYM(true, gst_caps_ref, GstCaps *, GstCaps* caps); -//LL_GST_SYM(true, gst_caps_is_empty, gboolean, const GstCaps *caps); -LL_GST_SYM(true, gst_caps_from_string, GstCaps *, const gchar *string); -LL_GST_SYM(true, gst_caps_replace, void, GstCaps **caps, GstCaps *newcaps); -LL_GST_SYM(true, gst_caps_get_structure, GstStructure *, const GstCaps *caps, guint index); -LL_GST_SYM(true, gst_caps_copy, GstCaps *, const GstCaps * caps); -//LL_GST_SYM(true, gst_caps_intersect, GstCaps *, const GstCaps *caps1, const GstCaps *caps2); -LL_GST_SYM(true, gst_element_register, gboolean, GstPlugin *plugin, const gchar *name, guint rank, GType type); -LL_GST_SYM(true, _gst_plugin_register_static, void, GstPluginDesc *desc); -LL_GST_SYM(true, gst_structure_get_int, gboolean, const GstStructure *structure, const gchar *fieldname, gint *value); -LL_GST_SYM(true, gst_structure_get_value, G_CONST_RETURN GValue *, const GstStructure *structure, const gchar *fieldname); -LL_GST_SYM(true, gst_value_get_fraction_numerator, gint, const GValue *value); -LL_GST_SYM(true, gst_value_get_fraction_denominator, gint, const GValue *value); -LL_GST_SYM(true, gst_structure_get_name, G_CONST_RETURN gchar *, const GstStructure *structure); -LL_GST_SYM(true, gst_element_seek, bool, GstElement *, gdouble, GstFormat, GstSeekFlags, GstSeekType, gint64, GstSeekType, gint64); - -// optional symbols to grab -LL_GST_SYM(false, gst_registry_fork_set_enabled, void, gboolean enabled); -LL_GST_SYM(false, gst_segtrap_set_enabled, void, gboolean enabled); -LL_GST_SYM(false, gst_message_parse_buffering, void, GstMessage *message, gint *percent); -LL_GST_SYM(false, gst_message_parse_info, void, GstMessage *message, GError **gerror, gchar **debug); -LL_GST_SYM(false, gst_element_query_position, gboolean, GstElement *element, GstFormat *format, gint64 *cur); -LL_GST_SYM(false, gst_version, void, guint *major, guint *minor, guint *micro, guint *nano); - -// GStreamer 'internal' symbols which may not be visible in some runtimes but are still used in expanded GStreamer header macros - yuck! We'll substitute our own stubs for these. -//LL_GST_SYM(true, _gst_debug_register_funcptr, void, GstDebugFuncPtr func, gchar* ptrname); -//LL_GST_SYM(true, _gst_debug_category_new, GstDebugCategory *, gchar *name, guint color, gchar *description); diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms_rawv.inc b/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms_rawv.inc deleted file mode 100644 index 14fbcb48b9..0000000000 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms_rawv.inc +++ /dev/null @@ -1,5 +0,0 @@ - -// required symbols to grab -LL_GST_SYM(true, gst_video_sink_get_type, GType, void); - -// optional symbols to grab diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamertriviallogging.h b/indra/media_plugins/gstreamer010/llmediaimplgstreamertriviallogging.h deleted file mode 100644 index 43ebad6744..0000000000 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamertriviallogging.h +++ /dev/null @@ -1,55 +0,0 @@ -/** - * @file llmediaimplgstreamertriviallogging.h - * @brief minimal logging utilities. - * - * @cond - * $LicenseInfo:firstyear=2009&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$ - * @endcond - */ - -#ifndef __LLMEDIAIMPLGSTREAMERTRIVIALLOGGING_H__ -#define __LLMEDIAIMPLGSTREAMERTRIVIALLOGGING_H__ - -#include <cstdio> - -extern "C" { -#include <sys/types.h> -#include <unistd.h> -} - -///////////////////////////////////////////////////////////////////////// -// Debug/Info/Warning macros. -#define MSGMODULEFOO "(media plugin)" -#define STDERRMSG(...) do{\ - fprintf(stderr, " pid:%d: ", (int)getpid());\ - fprintf(stderr, MSGMODULEFOO " %s:%d: ", __FUNCTION__, __LINE__);\ - fprintf(stderr, __VA_ARGS__);\ - fputc('\n',stderr);\ - }while(0) -#define NULLMSG(...) do{}while(0) - -#define DEBUGMSG NULLMSG -#define INFOMSG STDERRMSG -#define WARNMSG STDERRMSG -///////////////////////////////////////////////////////////////////////// - -#endif /* __LLMEDIAIMPLGSTREAMERTRIVIALLOGGING_H__ */ diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.cpp b/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.cpp deleted file mode 100644 index acec0f2399..0000000000 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.cpp +++ /dev/null @@ -1,526 +0,0 @@ -/** - * @file llmediaimplgstreamervidplug.h - * @brief Video-consuming static GStreamer plugin for gst-to-LLMediaImpl - * - * @cond - * $LicenseInfo:firstyear=2007&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$ - * @endcond - */ - -#if LL_GSTREAMER010_ENABLED - -#include "linden_common.h" - -#include <gst/gst.h> -#include <gst/video/video.h> -#include <gst/video/gstvideosink.h> - -#include "llmediaimplgstreamer_syms.h" -#include "llmediaimplgstreamertriviallogging.h" - -#include "llmediaimplgstreamervidplug.h" - - -GST_DEBUG_CATEGORY_STATIC (gst_slvideo_debug); -#define GST_CAT_DEFAULT gst_slvideo_debug - - -#define SLV_SIZECAPS ", width=(int)[1,2048], height=(int)[1,2048] " -#define SLV_ALLCAPS GST_VIDEO_CAPS_RGBx SLV_SIZECAPS - -static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ( - (gchar*)"sink", - GST_PAD_SINK, - GST_PAD_ALWAYS, - GST_STATIC_CAPS (SLV_ALLCAPS) - ); - -GST_BOILERPLATE (GstSLVideo, gst_slvideo, GstVideoSink, - GST_TYPE_VIDEO_SINK); - -static void gst_slvideo_set_property (GObject * object, guint prop_id, - const GValue * value, - GParamSpec * pspec); -static void gst_slvideo_get_property (GObject * object, guint prop_id, - GValue * value, GParamSpec * pspec); - -static void -gst_slvideo_base_init (gpointer gclass) -{ - static GstElementDetails element_details = { - (gchar*)"PluginTemplate", - (gchar*)"Generic/PluginTemplate", - (gchar*)"Generic Template Element", - (gchar*)"Linden Lab" - }; - GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); - - llgst_element_class_add_pad_template (element_class, - llgst_static_pad_template_get (&sink_factory)); - llgst_element_class_set_details (element_class, &element_details); -} - - -static void -gst_slvideo_finalize (GObject * object) -{ - GstSLVideo *slvideo; - slvideo = GST_SLVIDEO (object); - if (slvideo->caps) - { - llgst_caps_unref(slvideo->caps); - } - - G_OBJECT_CLASS(parent_class)->finalize (object); -} - - -static GstFlowReturn -gst_slvideo_show_frame (GstBaseSink * bsink, GstBuffer * buf) -{ - GstSLVideo *slvideo; - llg_return_val_if_fail (buf != NULL, GST_FLOW_ERROR); - - slvideo = GST_SLVIDEO(bsink); - - DEBUGMSG("transferring a frame of %dx%d <- %p (%d)", - slvideo->width, slvideo->height, GST_BUFFER_DATA(buf), - slvideo->format); - - if (GST_BUFFER_DATA(buf)) - { - // copy frame and frame info into neutral territory - GST_OBJECT_LOCK(slvideo); - slvideo->retained_frame_ready = TRUE; - slvideo->retained_frame_width = slvideo->width; - slvideo->retained_frame_height = slvideo->height; - slvideo->retained_frame_format = slvideo->format; - int rowbytes = - SLVPixelFormatBytes[slvideo->retained_frame_format] * - slvideo->retained_frame_width; - int needbytes = rowbytes * slvideo->retained_frame_width; - // resize retained frame hunk only if necessary - if (needbytes != slvideo->retained_frame_allocbytes) - { - delete[] slvideo->retained_frame_data; - slvideo->retained_frame_data = new unsigned char[needbytes]; - slvideo->retained_frame_allocbytes = needbytes; - - } - // copy the actual frame data to neutral territory - - // flipped, for GL reasons - for (int ypos=0; ypos<slvideo->height; ++ypos) - { - memcpy(&slvideo->retained_frame_data[(slvideo->height-1-ypos)*rowbytes], - &(((unsigned char*)GST_BUFFER_DATA(buf))[ypos*rowbytes]), - rowbytes); - } - // done with the shared data - GST_OBJECT_UNLOCK(slvideo); - } - - return GST_FLOW_OK; -} - - -static GstStateChangeReturn -gst_slvideo_change_state(GstElement * element, GstStateChange transition) -{ - GstSLVideo *slvideo; - GstStateChangeReturn ret = GST_STATE_CHANGE_SUCCESS; - - slvideo = GST_SLVIDEO (element); - - switch (transition) { - case GST_STATE_CHANGE_NULL_TO_READY: - break; - case GST_STATE_CHANGE_READY_TO_PAUSED: - break; - case GST_STATE_CHANGE_PAUSED_TO_PLAYING: - break; - default: - break; - } - - ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition); - if (ret == GST_STATE_CHANGE_FAILURE) - return ret; - - switch (transition) { - case GST_STATE_CHANGE_PLAYING_TO_PAUSED: - break; - case GST_STATE_CHANGE_PAUSED_TO_READY: - slvideo->fps_n = 0; - slvideo->fps_d = 1; - GST_VIDEO_SINK_WIDTH(slvideo) = 0; - GST_VIDEO_SINK_HEIGHT(slvideo) = 0; - break; - case GST_STATE_CHANGE_READY_TO_NULL: - break; - default: - break; - } - - return ret; -} - - -static GstCaps * -gst_slvideo_get_caps (GstBaseSink * bsink) -{ - GstSLVideo *slvideo; - slvideo = GST_SLVIDEO(bsink); - - return llgst_caps_ref (slvideo->caps); -} - - -/* this function handles the link with other elements */ -static gboolean -gst_slvideo_set_caps (GstBaseSink * bsink, GstCaps * caps) -{ - GstSLVideo *filter; - GstStructure *structure; - - GST_DEBUG ("set caps with %" GST_PTR_FORMAT, caps); - - filter = GST_SLVIDEO(bsink); - - int width, height; - gboolean ret; - const GValue *fps; - const GValue *par; - structure = llgst_caps_get_structure (caps, 0); - ret = llgst_structure_get_int (structure, "width", &width); - ret = ret && llgst_structure_get_int (structure, "height", &height); - fps = llgst_structure_get_value (structure, "framerate"); - ret = ret && (fps != NULL); - par = llgst_structure_get_value (structure, "pixel-aspect-ratio"); - if (!ret) - return FALSE; - - INFOMSG("** filter caps set with width=%d, height=%d", width, height); - - GST_OBJECT_LOCK(filter); - - filter->width = width; - filter->height = height; - - filter->fps_n = llgst_value_get_fraction_numerator(fps); - filter->fps_d = llgst_value_get_fraction_denominator(fps); - if (par) - { - filter->par_n = llgst_value_get_fraction_numerator(par); - filter->par_d = llgst_value_get_fraction_denominator(par); - } - else - { - filter->par_n = 1; - filter->par_d = 1; - } - GST_VIDEO_SINK_WIDTH(filter) = width; - GST_VIDEO_SINK_HEIGHT(filter) = height; - - // crufty lump - we *always* accept *only* RGBX now. - /* - filter->format = SLV_PF_UNKNOWN; - if (0 == strcmp(llgst_structure_get_name(structure), - "video/x-raw-rgb")) - { - int red_mask; - int green_mask; - int blue_mask; - llgst_structure_get_int(structure, "red_mask", &red_mask); - llgst_structure_get_int(structure, "green_mask", &green_mask); - llgst_structure_get_int(structure, "blue_mask", &blue_mask); - if ((unsigned int)red_mask == 0xFF000000 && - (unsigned int)green_mask == 0x00FF0000 && - (unsigned int)blue_mask == 0x0000FF00) - { - filter->format = SLV_PF_RGBX; - //fprintf(stderr, "\n\nPIXEL FORMAT RGB\n\n"); - } else if ((unsigned int)red_mask == 0x0000FF00 && - (unsigned int)green_mask == 0x00FF0000 && - (unsigned int)blue_mask == 0xFF000000) - { - filter->format = SLV_PF_BGRX; - //fprintf(stderr, "\n\nPIXEL FORMAT BGR\n\n"); - } - }*/ - - filter->format = SLV_PF_RGBX; - - GST_OBJECT_UNLOCK(filter); - - return TRUE; -} - - -static gboolean -gst_slvideo_start (GstBaseSink * bsink) -{ - gboolean ret = TRUE; - - GST_SLVIDEO(bsink); - - return ret; -} - -static gboolean -gst_slvideo_stop (GstBaseSink * bsink) -{ - GstSLVideo *slvideo; - slvideo = GST_SLVIDEO(bsink); - - // free-up retained frame buffer - GST_OBJECT_LOCK(slvideo); - slvideo->retained_frame_ready = FALSE; - delete[] slvideo->retained_frame_data; - slvideo->retained_frame_data = NULL; - slvideo->retained_frame_allocbytes = 0; - GST_OBJECT_UNLOCK(slvideo); - - return TRUE; -} - - -static GstFlowReturn -gst_slvideo_buffer_alloc (GstBaseSink * bsink, guint64 offset, guint size, - GstCaps * caps, GstBuffer ** buf) -{ - gint width, height; - GstStructure *structure = NULL; - GstSLVideo *slvideo; - slvideo = GST_SLVIDEO(bsink); - - // caps == requested caps - // we can ignore these and reverse-negotiate our preferred dimensions with - // the peer if we like - we need to do this to obey dynamic resize requests - // flowing in from the app. - structure = llgst_caps_get_structure (caps, 0); - if (!llgst_structure_get_int(structure, "width", &width) || - !llgst_structure_get_int(structure, "height", &height)) - { - GST_WARNING_OBJECT (slvideo, "no width/height in caps %" GST_PTR_FORMAT, caps); - return GST_FLOW_NOT_NEGOTIATED; - } - - GstBuffer *newbuf = llgst_buffer_new(); - bool made_bufferdata_ptr = false; -#define MAXDEPTHHACK 4 - - GST_OBJECT_LOCK(slvideo); - if (slvideo->resize_forced_always) // app is giving us a fixed size to work with - { - gint slwantwidth, slwantheight; - slwantwidth = slvideo->resize_try_width; - slwantheight = slvideo->resize_try_height; - - if (slwantwidth != width || - slwantheight != height) - { - // don't like requested caps, we will issue our own suggestion - copy - // the requested caps but substitute our own width and height and see - // if our peer is happy with that. - - GstCaps *desired_caps; - GstStructure *desired_struct; - desired_caps = llgst_caps_copy (caps); - desired_struct = llgst_caps_get_structure (desired_caps, 0); - - GValue value = {0}; - g_value_init(&value, G_TYPE_INT); - g_value_set_int(&value, slwantwidth); - llgst_structure_set_value (desired_struct, "width", &value); - g_value_unset(&value); - g_value_init(&value, G_TYPE_INT); - g_value_set_int(&value, slwantheight); - llgst_structure_set_value (desired_struct, "height", &value); - - if (llgst_pad_peer_accept_caps (GST_VIDEO_SINK_PAD (slvideo), - desired_caps)) - { - // todo: re-use buffers from a pool? - // todo: set MALLOCDATA to null, set DATA to point straight to shm? - - // peer likes our cap suggestion - DEBUGMSG("peer loves us :)"); - GST_BUFFER_SIZE(newbuf) = slwantwidth * slwantheight * MAXDEPTHHACK; - GST_BUFFER_MALLOCDATA(newbuf) = (guint8*)g_malloc(GST_BUFFER_SIZE(newbuf)); - GST_BUFFER_DATA(newbuf) = GST_BUFFER_MALLOCDATA(newbuf); - llgst_buffer_set_caps (GST_BUFFER_CAST(newbuf), desired_caps); - - made_bufferdata_ptr = true; - } else { - // peer hates our cap suggestion - INFOMSG("peer hates us :("); - llgst_caps_unref(desired_caps); - } - } - } - - GST_OBJECT_UNLOCK(slvideo); - - if (!made_bufferdata_ptr) // need to fallback to malloc at original size - { - GST_BUFFER_SIZE(newbuf) = width * height * MAXDEPTHHACK; - GST_BUFFER_MALLOCDATA(newbuf) = (guint8*)g_malloc(GST_BUFFER_SIZE(newbuf)); - GST_BUFFER_DATA(newbuf) = GST_BUFFER_MALLOCDATA(newbuf); - llgst_buffer_set_caps (GST_BUFFER_CAST(newbuf), caps); - } - - *buf = GST_BUFFER_CAST(newbuf); - - return GST_FLOW_OK; -} - - -/* initialize the plugin's class */ -static void -gst_slvideo_class_init (GstSLVideoClass * klass) -{ - GObjectClass *gobject_class; - GstElementClass *gstelement_class; - GstBaseSinkClass *gstbasesink_class; - - gobject_class = (GObjectClass *) klass; - gstelement_class = (GstElementClass *) klass; - gstbasesink_class = (GstBaseSinkClass *) klass; - - gobject_class->finalize = gst_slvideo_finalize; - gobject_class->set_property = gst_slvideo_set_property; - gobject_class->get_property = gst_slvideo_get_property; - - gstelement_class->change_state = gst_slvideo_change_state; - -#define LLGST_DEBUG_FUNCPTR(p) (p) - gstbasesink_class->get_caps = LLGST_DEBUG_FUNCPTR (gst_slvideo_get_caps); - gstbasesink_class->set_caps = LLGST_DEBUG_FUNCPTR( gst_slvideo_set_caps); - gstbasesink_class->buffer_alloc=LLGST_DEBUG_FUNCPTR(gst_slvideo_buffer_alloc); - //gstbasesink_class->get_times = LLGST_DEBUG_FUNCPTR (gst_slvideo_get_times); - gstbasesink_class->preroll = LLGST_DEBUG_FUNCPTR (gst_slvideo_show_frame); - gstbasesink_class->render = LLGST_DEBUG_FUNCPTR (gst_slvideo_show_frame); - - gstbasesink_class->start = LLGST_DEBUG_FUNCPTR (gst_slvideo_start); - gstbasesink_class->stop = LLGST_DEBUG_FUNCPTR (gst_slvideo_stop); - - // gstbasesink_class->unlock = LLGST_DEBUG_FUNCPTR (gst_slvideo_unlock); -#undef LLGST_DEBUG_FUNCPTR -} - - -/* initialize the new element - * instantiate pads and add them to element - * set functions - * initialize structure - */ -static void -gst_slvideo_init (GstSLVideo * filter, - GstSLVideoClass * gclass) -{ - filter->caps = NULL; - filter->width = -1; - filter->height = -1; - - // this is the info we share with the client app - GST_OBJECT_LOCK(filter); - filter->retained_frame_ready = FALSE; - filter->retained_frame_data = NULL; - filter->retained_frame_allocbytes = 0; - filter->retained_frame_width = filter->width; - filter->retained_frame_height = filter->height; - filter->retained_frame_format = SLV_PF_UNKNOWN; - GstCaps *caps = llgst_caps_from_string (SLV_ALLCAPS); - llgst_caps_replace (&filter->caps, caps); - filter->resize_forced_always = false; - filter->resize_try_width = -1; - filter->resize_try_height = -1; - GST_OBJECT_UNLOCK(filter); -} - -static void -gst_slvideo_set_property (GObject * object, guint prop_id, - const GValue * value, GParamSpec * pspec) -{ - llg_return_if_fail (GST_IS_SLVIDEO (object)); - - switch (prop_id) { - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - -static void -gst_slvideo_get_property (GObject * object, guint prop_id, - GValue * value, GParamSpec * pspec) -{ - llg_return_if_fail (GST_IS_SLVIDEO (object)); - - switch (prop_id) { - default: - G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); - break; - } -} - - -/* entry point to initialize the plug-in - * initialize the plug-in itself - * register the element factories and pad templates - * register the features - */ -static gboolean -plugin_init (GstPlugin * plugin) -{ - DEBUGMSG("PLUGIN INIT"); - - GST_DEBUG_CATEGORY_INIT (gst_slvideo_debug, (gchar*)"private-slvideo-plugin", - 0, (gchar*)"Second Life Video Sink"); - - return llgst_element_register (plugin, "private-slvideo", - GST_RANK_NONE, GST_TYPE_SLVIDEO); -} - -/* this is the structure that gstreamer looks for to register plugins - */ -/* NOTE: Can't rely upon GST_PLUGIN_DEFINE_STATIC to self-register, since - some g++ versions buggily avoid __attribute__((constructor)) functions - - so we provide an explicit plugin init function. - */ -#define PACKAGE (gchar*)"packagehack" -// this macro quietly refers to PACKAGE internally -GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, - GST_VERSION_MINOR, - (gchar*)"private-slvideoplugin", - (gchar*)"SL Video sink plugin", - plugin_init, (gchar*)"1.0", (gchar*)"LGPL", - (gchar*)"Second Life", - (gchar*)"http://www.secondlife.com/"); -#undef PACKAGE -void gst_slvideo_init_class (void) -{ - ll_gst_plugin_register_static (&gst_plugin_desc); - DEBUGMSG("CLASS INIT"); -} - -#endif // LL_GSTREAMER010_ENABLED diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.h b/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.h deleted file mode 100644 index d4e07daf4f..0000000000 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.h +++ /dev/null @@ -1,105 +0,0 @@ -/** - * @file llmediaimplgstreamervidplug.h - * @brief Video-consuming static GStreamer plugin for gst-to-LLMediaImpl - * - * @cond - * $LicenseInfo:firstyear=2007&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$ - * @endcond - */ - -#ifndef __GST_SLVIDEO_H__ -#define __GST_SLVIDEO_H__ - -#if LL_GSTREAMER010_ENABLED - -extern "C" { -#include <gst/gst.h> -#include <gst/video/video.h> -#include <gst/video/gstvideosink.h> -} - -G_BEGIN_DECLS - -/* #defines don't like whitespacey bits */ -#define GST_TYPE_SLVIDEO \ - (gst_slvideo_get_type()) -#define GST_SLVIDEO(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SLVIDEO,GstSLVideo)) -#define GST_SLVIDEO_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SLVIDEO,GstSLVideoClass)) -#define GST_IS_SLVIDEO(obj) \ - (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SLVIDEO)) -#define GST_IS_SLVIDEO_CLASS(klass) \ - (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SLVIDEO)) - -typedef struct _GstSLVideo GstSLVideo; -typedef struct _GstSLVideoClass GstSLVideoClass; - -typedef enum { - SLV_PF_UNKNOWN = 0, - SLV_PF_RGBX = 1, - SLV_PF_BGRX = 2, - SLV__END = 3 -} SLVPixelFormat; -const int SLVPixelFormatBytes[SLV__END] = {1, 4, 4}; - -struct _GstSLVideo -{ - GstVideoSink video_sink; - - GstCaps *caps; - - int fps_n, fps_d; - int par_n, par_d; - int height, width; - SLVPixelFormat format; - - // SHARED WITH APPLICATION: - // Access to the following should be protected by GST_OBJECT_LOCK() on - // the GstSLVideo object, and should be totally consistent upon UNLOCK - // (i.e. all written at once to reflect the current retained frame info - // when the retained frame is updated.) - bool retained_frame_ready; // new frame ready since flag last reset. (*TODO: could get the writer to wait on a semaphore instead of having the reader poll, potentially making dropped frames somewhat cheaper.) - unsigned char* retained_frame_data; - int retained_frame_allocbytes; - int retained_frame_width, retained_frame_height; - SLVPixelFormat retained_frame_format; - // sticky resize info - bool resize_forced_always; - int resize_try_width; - int resize_try_height; -}; - -struct _GstSLVideoClass -{ - GstVideoSinkClass parent_class; -}; - -GType gst_slvideo_get_type (void); - -void gst_slvideo_init_class (void); - -G_END_DECLS - -#endif // LL_GSTREAMER010_ENABLED - -#endif /* __GST_SLVIDEO_H__ */ diff --git a/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp b/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp deleted file mode 100644 index 97d1d7d7b5..0000000000 --- a/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp +++ /dev/null @@ -1,1266 +0,0 @@ -/** - * @file media_plugin_gstreamer010.cpp - * @brief GStreamer-0.10 plugin for LLMedia API plugin system - * - * @cond - * $LicenseInfo:firstyear=2007&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$ - * @endcond - */ - -#include "linden_common.h" - -#include "llgl.h" - -#include "llplugininstance.h" -#include "llpluginmessage.h" -#include "llpluginmessageclasses.h" -#include "media_plugin_base.h" - -#if LL_GSTREAMER010_ENABLED - -extern "C" { -#include <gst/gst.h> -} - -#include "llmediaimplgstreamer.h" -#include "llmediaimplgstreamertriviallogging.h" - -#include "llmediaimplgstreamervidplug.h" - -#include "llmediaimplgstreamer_syms.h" - -////////////////////////////////////////////////////////////////////////////// -// -class MediaPluginGStreamer010 : public MediaPluginBase -{ -public: - MediaPluginGStreamer010(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data); - ~MediaPluginGStreamer010(); - - /* virtual */ void receiveMessage(const char *message_string); - - static bool startup(); - static bool closedown(); - - gboolean processGSTEvents(GstBus *bus, - GstMessage *message); - -private: - std::string getVersion(); - bool navigateTo( const std::string urlIn ); - bool seek( double time_sec ); - bool setVolume( float volume ); - - // misc - bool pause(); - bool stop(); - bool play(double rate); - bool getTimePos(double &sec_out); - - static const double MIN_LOOP_SEC = 1.0F; - - bool mIsLooping; - - enum ECommand { - COMMAND_NONE, - COMMAND_STOP, - COMMAND_PLAY, - COMMAND_FAST_FORWARD, - COMMAND_FAST_REWIND, - COMMAND_PAUSE, - COMMAND_SEEK, - }; - ECommand mCommand; - -private: - bool unload(); - bool load(); - - bool update(int milliseconds); - void mouseDown( int x, int y ); - void mouseUp( int x, int y ); - void mouseMove( int x, int y ); - - void sizeChanged(); - - static bool mDoneInit; - - guint mBusWatchID; - - float mVolume; - - int mDepth; - - // media NATURAL size - int mNaturalWidth; - int mNaturalHeight; - // media current size - int mCurrentWidth; - int mCurrentHeight; - int mCurrentRowbytes; - // previous media size so we can detect changes - int mPreviousWidth; - int mPreviousHeight; - // desired render size from host - int mWidth; - int mHeight; - // padded texture size we need to write into - int mTextureWidth; - int mTextureHeight; - - int mTextureFormatPrimary; - int mTextureFormatType; - - bool mSeekWanted; - double mSeekDestination; - - // Very GStreamer-specific - GMainLoop *mPump; // event pump for this media - GstElement *mPlaybin; - GstElement *mVisualizer; - GstSLVideo *mVideoSink; -}; - -//static -bool MediaPluginGStreamer010::mDoneInit = false; - -MediaPluginGStreamer010::MediaPluginGStreamer010( - LLPluginInstance::sendMessageFunction host_send_func, - void *host_user_data ) : - MediaPluginBase(host_send_func, host_user_data), - mBusWatchID ( 0 ), - mCurrentRowbytes ( 4 ), - mTextureFormatPrimary ( GL_RGBA ), - mTextureFormatType ( GL_UNSIGNED_INT_8_8_8_8_REV ), - mSeekWanted(false), - mSeekDestination(0.0), - mPump ( NULL ), - mPlaybin ( NULL ), - mVisualizer ( NULL ), - mVideoSink ( NULL ), - mCommand ( COMMAND_NONE ) -{ - std::ostringstream str; - INFOMSG("MediaPluginGStreamer010 constructor - my PID=%u", U32(getpid())); -} - -/////////////////////////////////////////////////////////////////////////////// -// -//#define LL_GST_REPORT_STATE_CHANGES -#ifdef LL_GST_REPORT_STATE_CHANGES -static char* get_gst_state_name(GstState state) -{ - switch (state) { - case GST_STATE_VOID_PENDING: return "VOID_PENDING"; - case GST_STATE_NULL: return "NULL"; - case GST_STATE_READY: return "READY"; - case GST_STATE_PAUSED: return "PAUSED"; - case GST_STATE_PLAYING: return "PLAYING"; - } - return "(unknown)"; -} -#endif // LL_GST_REPORT_STATE_CHANGES - -gboolean -MediaPluginGStreamer010::processGSTEvents(GstBus *bus, - GstMessage *message) -{ - if (!message) - return TRUE; // shield against GStreamer bug - - if (GST_MESSAGE_TYPE(message) != GST_MESSAGE_STATE_CHANGED && - GST_MESSAGE_TYPE(message) != GST_MESSAGE_BUFFERING) - { - DEBUGMSG("Got GST message type: %s", - LLGST_MESSAGE_TYPE_NAME (message)); - } - else - { - // TODO: grok 'duration' message type - DEBUGMSG("Got GST message type: %s", - LLGST_MESSAGE_TYPE_NAME (message)); - } - - switch (GST_MESSAGE_TYPE (message)) { - case GST_MESSAGE_BUFFERING: { - // NEEDS GST 0.10.11+ - if (llgst_message_parse_buffering) - { - gint percent = 0; - llgst_message_parse_buffering(message, &percent); - DEBUGMSG("GST buffering: %d%%", percent); - } - break; - } - case GST_MESSAGE_STATE_CHANGED: { - GstState old_state; - GstState new_state; - GstState pending_state; - llgst_message_parse_state_changed(message, - &old_state, - &new_state, - &pending_state); -#ifdef LL_GST_REPORT_STATE_CHANGES - // not generally very useful, and rather spammy. - DEBUGMSG("state change (old,<new>,pending): %s,<%s>,%s", - get_gst_state_name(old_state), - get_gst_state_name(new_state), - get_gst_state_name(pending_state)); -#endif // LL_GST_REPORT_STATE_CHANGES - - switch (new_state) { - case GST_STATE_VOID_PENDING: - break; - case GST_STATE_NULL: - break; - case GST_STATE_READY: - setStatus(STATUS_LOADED); - break; - case GST_STATE_PAUSED: - setStatus(STATUS_PAUSED); - break; - case GST_STATE_PLAYING: - setStatus(STATUS_PLAYING); - break; - } - break; - } - case GST_MESSAGE_ERROR: { - GError *err = NULL; - gchar *debug = NULL; - - llgst_message_parse_error (message, &err, &debug); - WARNMSG("GST error: %s", err?err->message:"(unknown)"); - if (err) - g_error_free (err); - g_free (debug); - - mCommand = COMMAND_STOP; - - setStatus(STATUS_ERROR); - - break; - } - case GST_MESSAGE_INFO: { - if (llgst_message_parse_info) - { - GError *err = NULL; - gchar *debug = NULL; - - llgst_message_parse_info (message, &err, &debug); - INFOMSG("GST info: %s", err?err->message:"(unknown)"); - if (err) - g_error_free (err); - g_free (debug); - } - break; - } - case GST_MESSAGE_WARNING: { - GError *err = NULL; - gchar *debug = NULL; - - llgst_message_parse_warning (message, &err, &debug); - WARNMSG("GST warning: %s", err?err->message:"(unknown)"); - if (err) - g_error_free (err); - g_free (debug); - - break; - } - case GST_MESSAGE_EOS: - /* end-of-stream */ - DEBUGMSG("GST end-of-stream."); - if (mIsLooping) - { - DEBUGMSG("looping media..."); - double eos_pos_sec = 0.0F; - bool got_eos_position = getTimePos(eos_pos_sec); - - if (got_eos_position && eos_pos_sec < MIN_LOOP_SEC) - { - // if we know that the movie is really short, don't - // loop it else it can easily become a time-hog - // because of GStreamer spin-up overhead - DEBUGMSG("really short movie (%0.3fsec) - not gonna loop this, pausing instead.", eos_pos_sec); - // inject a COMMAND_PAUSE - mCommand = COMMAND_PAUSE; - } - else - { -#undef LLGST_LOOP_BY_SEEKING -// loop with a stop-start instead of a seek, because it actually seems rather -// faster than seeking on remote streams. -#ifdef LLGST_LOOP_BY_SEEKING - // first, try looping by an explicit rewind - bool seeksuccess = seek(0.0); - if (seeksuccess) - { - play(1.0); - } - else -#endif // LLGST_LOOP_BY_SEEKING - { // use clumsy stop-start to loop - DEBUGMSG("didn't loop by rewinding - stopping and starting instead..."); - stop(); - play(1.0); - } - } - } - else // not a looping media - { - // inject a COMMAND_STOP - mCommand = COMMAND_STOP; - } - break; - default: - /* unhandled message */ - break; - } - - /* we want to be notified again the next time there is a message - * on the bus, so return true (false means we want to stop watching - * for messages on the bus and our callback should not be called again) - */ - return TRUE; -} - -extern "C" { -gboolean -llmediaimplgstreamer_bus_callback (GstBus *bus, - GstMessage *message, - gpointer data) -{ - MediaPluginGStreamer010 *impl = (MediaPluginGStreamer010*)data; - return impl->processGSTEvents(bus, message); -} -} // extern "C" - - - -bool -MediaPluginGStreamer010::navigateTo ( const std::string urlIn ) -{ - if (!mDoneInit) - return false; // error - - setStatus(STATUS_LOADING); - - DEBUGMSG("Setting media URI: %s", urlIn.c_str()); - - mSeekWanted = false; - - if (NULL == mPump || - NULL == mPlaybin) - { - setStatus(STATUS_ERROR); - return false; // error - } - - // set URI - g_object_set (G_OBJECT (mPlaybin), "uri", urlIn.c_str(), NULL); - //g_object_set (G_OBJECT (mPlaybin), "uri", "file:///tmp/movie", NULL); - - // navigateTo implicitly plays, too. - play(1.0); - - return true; -} - - -bool -MediaPluginGStreamer010::update(int milliseconds) -{ - if (!mDoneInit) - return false; // error - - DEBUGMSG("updating media..."); - - // sanity check - if (NULL == mPump || - NULL == mPlaybin) - { - DEBUGMSG("dead media..."); - return false; - } - - // see if there's an outstanding seek wanted - if (mSeekWanted && - // bleh, GST has to be happy that the movie is really truly playing - // or it may quietly ignore the seek (with rtsp:// at least). - (GST_STATE(mPlaybin) == GST_STATE_PLAYING)) - { - seek(mSeekDestination); - mSeekWanted = false; - } - - // *TODO: time-limit - but there isn't a lot we can do here, most - // time is spent in gstreamer's own opaque worker-threads. maybe - // we can do something sneaky like only unlock the video object - // for 'milliseconds' and otherwise hold the lock. - while (g_main_context_pending(g_main_loop_get_context(mPump))) - { - g_main_context_iteration(g_main_loop_get_context(mPump), FALSE); - } - - // check for availability of a new frame - - if (mVideoSink) - { - GST_OBJECT_LOCK(mVideoSink); - if (mVideoSink->retained_frame_ready) - { - DEBUGMSG("NEW FRAME READY"); - - if (mVideoSink->retained_frame_width != mCurrentWidth || - mVideoSink->retained_frame_height != mCurrentHeight) - // *TODO: also check for change in format - { - // just resize container, don't consume frame - int neww = mVideoSink->retained_frame_width; - int newh = mVideoSink->retained_frame_height; - - int newd = 4; - mTextureFormatPrimary = GL_RGBA; - mTextureFormatType = GL_UNSIGNED_INT_8_8_8_8_REV; - - /* - int newd = SLVPixelFormatBytes[mVideoSink->retained_frame_format]; - if (SLV_PF_BGRX == mVideoSink->retained_frame_format) - { - mTextureFormatPrimary = GL_BGRA; - mTextureFormatType = GL_UNSIGNED_INT_8_8_8_8_REV; - } - else - { - mTextureFormatPrimary = GL_RGBA; - mTextureFormatType = GL_UNSIGNED_INT_8_8_8_8_REV; - } - */ - - GST_OBJECT_UNLOCK(mVideoSink); - - mCurrentRowbytes = neww * newd; - DEBUGMSG("video container resized to %dx%d", - neww, newh); - - mDepth = newd; - mCurrentWidth = neww; - mCurrentHeight = newh; - sizeChanged(); - return true; - } - - if (mPixels && - mCurrentHeight <= mHeight && - mCurrentWidth <= mWidth && - !mTextureSegmentName.empty()) - { - // we're gonna totally consume this frame - reset 'ready' flag - mVideoSink->retained_frame_ready = FALSE; - int destination_rowbytes = mWidth * mDepth; - for (int row=0; row<mCurrentHeight; ++row) - { - memcpy(&mPixels - [destination_rowbytes * row], - &mVideoSink->retained_frame_data - [mCurrentRowbytes * row], - mCurrentRowbytes); - } - - GST_OBJECT_UNLOCK(mVideoSink); - DEBUGMSG("NEW FRAME REALLY TRULY CONSUMED, TELLING HOST"); - - setDirty(0,0,mCurrentWidth,mCurrentHeight); - } - else - { - // new frame ready, but we're not ready to - // consume it. - - GST_OBJECT_UNLOCK(mVideoSink); - - DEBUGMSG("NEW FRAME not consumed, still waiting for a shm segment and/or shm resize"); - } - - return true; - } - else - { - // nothing to do yet. - GST_OBJECT_UNLOCK(mVideoSink); - return true; - } - } - - return true; -} - - -void -MediaPluginGStreamer010::mouseDown( int x, int y ) -{ - // do nothing -} - -void -MediaPluginGStreamer010::mouseUp( int x, int y ) -{ - // do nothing -} - -void -MediaPluginGStreamer010::mouseMove( int x, int y ) -{ - // do nothing -} - - -bool -MediaPluginGStreamer010::pause() -{ - DEBUGMSG("pausing media..."); - // todo: error-check this? - if (mDoneInit && mPlaybin) - { - llgst_element_set_state(mPlaybin, GST_STATE_PAUSED); - return true; - } - return false; -} - -bool -MediaPluginGStreamer010::stop() -{ - DEBUGMSG("stopping media..."); - // todo: error-check this? - if (mDoneInit && mPlaybin) - { - llgst_element_set_state(mPlaybin, GST_STATE_READY); - return true; - } - return false; -} - -bool -MediaPluginGStreamer010::play(double rate) -{ - // NOTE: we don't actually support non-natural rate. - - DEBUGMSG("playing media... rate=%f", rate); - // todo: error-check this? - if (mDoneInit && mPlaybin) - { - llgst_element_set_state(mPlaybin, GST_STATE_PLAYING); - return true; - } - return false; -} - -bool -MediaPluginGStreamer010::setVolume( float volume ) -{ - // we try to only update volume as conservatively as - // possible, as many gst-plugins-base versions up to at least - // November 2008 have critical race-conditions in setting volume - sigh - if (mVolume == volume) - return true; // nothing to do, everything's fine - - mVolume = volume; - if (mDoneInit && mPlaybin) - { - g_object_set(mPlaybin, "volume", mVolume, NULL); - return true; - } - - return false; -} - -bool -MediaPluginGStreamer010::seek(double time_sec) -{ - bool success = false; - if (mDoneInit && mPlaybin) - { - success = llgst_element_seek(mPlaybin, 1.0F, GST_FORMAT_TIME, - GstSeekFlags(GST_SEEK_FLAG_FLUSH | - GST_SEEK_FLAG_KEY_UNIT), - GST_SEEK_TYPE_SET, gint64(time_sec*GST_SECOND), - GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); - } - DEBUGMSG("MEDIA SEEK REQUEST to %fsec result was %d", - float(time_sec), int(success)); - return success; -} - -bool -MediaPluginGStreamer010::getTimePos(double &sec_out) -{ - bool got_position = false; - if (mDoneInit && mPlaybin) - { - gint64 pos; - GstFormat timefmt = GST_FORMAT_TIME; - got_position = - llgst_element_query_position && - llgst_element_query_position(mPlaybin, - &timefmt, - &pos); - got_position = got_position - && (timefmt == GST_FORMAT_TIME); - // GStreamer may have other ideas, but we consider the current position - // undefined if not PLAYING or PAUSED - got_position = got_position && - (GST_STATE(mPlaybin) == GST_STATE_PLAYING || - GST_STATE(mPlaybin) == GST_STATE_PAUSED); - if (got_position && !GST_CLOCK_TIME_IS_VALID(pos)) - { - if (GST_STATE(mPlaybin) == GST_STATE_PLAYING) - { - // if we're playing then we treat an invalid clock time - // as 0, for complicated reasons (insert reason here) - pos = 0; - } - else - { - got_position = false; - } - - } - // If all the preconditions succeeded... we can trust the result. - if (got_position) - { - sec_out = double(pos) / double(GST_SECOND); // gst to sec - } - } - return got_position; -} - -bool -MediaPluginGStreamer010::load() -{ - if (!mDoneInit) - return false; // error - - setStatus(STATUS_LOADING); - - DEBUGMSG("setting up media..."); - - mIsLooping = false; - mVolume = 0.1234567; // minor hack to force an initial volume update - - // Create a pumpable main-loop for this media - mPump = g_main_loop_new (NULL, FALSE); - if (!mPump) - { - setStatus(STATUS_ERROR); - return false; // error - } - - // instantiate a playbin element to do the hard work - mPlaybin = llgst_element_factory_make ("playbin", "play"); - if (!mPlaybin) - { - setStatus(STATUS_ERROR); - return false; // error - } - - // get playbin's bus - GstBus *bus = llgst_pipeline_get_bus (GST_PIPELINE (mPlaybin)); - if (!bus) - { - setStatus(STATUS_ERROR); - return false; // error - } - mBusWatchID = llgst_bus_add_watch (bus, - llmediaimplgstreamer_bus_callback, - this); - llgst_object_unref (bus); - -#if 0 // not quite stable/correct yet - // get a visualizer element (bonus feature!) - char* vis_name = getenv("LL_GST_VIS_NAME"); - if (!vis_name || - (vis_name && std::string(vis_name)!="none")) - { - if (vis_name) - { - mVisualizer = llgst_element_factory_make (vis_name, "vis"); - } - if (!mVisualizer) - { - mVisualizer = llgst_element_factory_make ("libvisual_jess", "vis"); - if (!mVisualizer) - { - mVisualizer = llgst_element_factory_make ("goom", "vis"); - if (!mVisualizer) - { - mVisualizer = llgst_element_factory_make ("libvisual_lv_scope", "vis"); - if (!mVisualizer) - { - // That's okay, we don't NEED this. - } - } - } - } - } -#endif - - if (NULL == getenv("LL_GSTREAMER_EXTERNAL")) { - // instantiate a custom video sink - mVideoSink = - GST_SLVIDEO(llgst_element_factory_make ("private-slvideo", "slvideo")); - if (!mVideoSink) - { - WARNMSG("Could not instantiate private-slvideo element."); - // todo: cleanup. - setStatus(STATUS_ERROR); - return false; // error - } - - // connect the pieces - g_object_set(mPlaybin, "video-sink", mVideoSink, NULL); - } - - if (mVisualizer) - { - g_object_set(mPlaybin, "vis-plugin", mVisualizer, NULL); - } - - return true; -} - -bool -MediaPluginGStreamer010::unload () -{ - if (!mDoneInit) - return false; // error - - DEBUGMSG("unloading media..."); - - // stop getting callbacks for this bus - g_source_remove(mBusWatchID); - mBusWatchID = 0; - - if (mPlaybin) - { - llgst_element_set_state (mPlaybin, GST_STATE_NULL); - llgst_object_unref (GST_OBJECT (mPlaybin)); - mPlaybin = NULL; - } - - if (mVisualizer) - { - llgst_object_unref (GST_OBJECT (mVisualizer)); - mVisualizer = NULL; - } - - if (mPump) - { - g_main_loop_quit(mPump); - mPump = NULL; - } - - mVideoSink = NULL; - - setStatus(STATUS_NONE); - - return true; -} - - -//static -bool -MediaPluginGStreamer010::startup() -{ - // first - check if GStreamer is explicitly disabled - if (NULL != getenv("LL_DISABLE_GSTREAMER")) - return false; - - // only do global GStreamer initialization once. - if (!mDoneInit) - { - g_thread_init(NULL); - - // Init the glib type system - we need it. - g_type_init(); - - // Get symbols! -#if LL_DARWIN - if (! grab_gst_syms("libgstreamer-0.10.dylib", - "libgstvideo-0.10.dylib") ) -#elseif LL_WINDOWS - if (! grab_gst_syms("libgstreamer-0.10.dll", - "libgstvideo-0.10.dll") ) -#else // linux or other ELFy unixoid - if (! grab_gst_syms("libgstreamer-0.10.so.0", - "libgstvideo-0.10.so.0") ) -#endif - { - WARNMSG("Couldn't find suitable GStreamer 0.10 support on this system - video playback disabled."); - return false; - } - - if (llgst_segtrap_set_enabled) - { - llgst_segtrap_set_enabled(FALSE); - } - else - { - WARNMSG("gst_segtrap_set_enabled() is not available; plugin crashes won't be caught."); - } - -#if LL_LINUX - // Gstreamer tries a fork during init, waitpid-ing on it, - // which conflicts with any installed SIGCHLD handler... - struct sigaction tmpact, oldact; - if (llgst_registry_fork_set_enabled) { - // if we can disable SIGCHLD-using forking behaviour, - // do it. - llgst_registry_fork_set_enabled(false); - } - else { - // else temporarily install default SIGCHLD handler - // while GStreamer initialises - tmpact.sa_handler = SIG_DFL; - sigemptyset( &tmpact.sa_mask ); - tmpact.sa_flags = SA_SIGINFO; - sigaction(SIGCHLD, &tmpact, &oldact); - } -#endif // LL_LINUX - - // Protect against GStreamer resetting the locale, yuck. - static std::string saved_locale; - saved_locale = setlocale(LC_ALL, NULL); - - // finally, try to initialize GStreamer! - GError *err = NULL; - gboolean init_gst_success = llgst_init_check(NULL, NULL, &err); - - // restore old locale - setlocale(LC_ALL, saved_locale.c_str() ); - -#if LL_LINUX - // restore old SIGCHLD handler - if (!llgst_registry_fork_set_enabled) - sigaction(SIGCHLD, &oldact, NULL); -#endif // LL_LINUX - - if (!init_gst_success) // fail - { - if (err) - { - WARNMSG("GST init failed: %s", err->message); - g_error_free(err); - } - else - { - WARNMSG("GST init failed for unspecified reason."); - } - return false; - } - - // Init our custom plugins - only really need do this once. - gst_slvideo_init_class(); - - mDoneInit = true; - } - - return true; -} - - -void -MediaPluginGStreamer010::sizeChanged() -{ - // the shared writing space has possibly changed size/location/whatever - - // Check to see whether the movie's NATURAL size has been set yet - if (1 == mNaturalWidth && - 1 == mNaturalHeight) - { - mNaturalWidth = mCurrentWidth; - mNaturalHeight = mCurrentHeight; - DEBUGMSG("Media NATURAL size better detected as %dx%d", - mNaturalWidth, mNaturalHeight); - } - - // if the size has changed then the shm has changed and the app needs telling - if (mCurrentWidth != mPreviousWidth || - mCurrentHeight != mPreviousHeight) - { - mPreviousWidth = mCurrentWidth; - mPreviousHeight = mCurrentHeight; - - LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_request"); - message.setValue("name", mTextureSegmentName); - message.setValueS32("width", mNaturalWidth); - message.setValueS32("height", mNaturalHeight); - DEBUGMSG("<--- Sending size change request to application with name: '%s' - natural size is %d x %d", mTextureSegmentName.c_str(), mNaturalWidth, mNaturalHeight); - sendMessage(message); - } -} - - - -//static -bool -MediaPluginGStreamer010::closedown() -{ - if (!mDoneInit) - return false; // error - - ungrab_gst_syms(); - - mDoneInit = false; - - return true; -} - -MediaPluginGStreamer010::~MediaPluginGStreamer010() -{ - DEBUGMSG("MediaPluginGStreamer010 destructor"); - - closedown(); - - DEBUGMSG("GStreamer010 closing down"); -} - - -std::string -MediaPluginGStreamer010::getVersion() -{ - std::string plugin_version = "GStreamer010 media plugin, GStreamer version "; - if (mDoneInit && - llgst_version) - { - guint major, minor, micro, nano; - llgst_version(&major, &minor, µ, &nano); - plugin_version += llformat("%u.%u.%u.%u (runtime), %u.%u.%u.%u (headers)", (unsigned int)major, (unsigned int)minor, (unsigned int)micro, (unsigned int)nano, (unsigned int)GST_VERSION_MAJOR, (unsigned int)GST_VERSION_MINOR, (unsigned int)GST_VERSION_MICRO, (unsigned int)GST_VERSION_NANO); - } - else - { - plugin_version += "(unknown)"; - } - return plugin_version; -} - -void MediaPluginGStreamer010::receiveMessage(const char *message_string) -{ - //std::cerr << "MediaPluginGStreamer010::receiveMessage: received message: \"" << message_string << "\"" << std::endl; - - LLPluginMessage message_in; - - if(message_in.parse(message_string) >= 0) - { - std::string message_class = message_in.getClass(); - std::string message_name = message_in.getName(); - if(message_class == LLPLUGIN_MESSAGE_CLASS_BASE) - { - if(message_name == "init") - { - LLPluginMessage message("base", "init_response"); - LLSD versions = LLSD::emptyMap(); - versions[LLPLUGIN_MESSAGE_CLASS_BASE] = LLPLUGIN_MESSAGE_CLASS_BASE_VERSION; - versions[LLPLUGIN_MESSAGE_CLASS_MEDIA] = LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION; - versions[LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME] = LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME_VERSION; - message.setValueLLSD("versions", versions); - - if ( load() ) - { - DEBUGMSG("GStreamer010 media instance set up"); - } - else - { - WARNMSG("GStreamer010 media instance failed to set up"); - } - - message.setValue("plugin_version", getVersion()); - sendMessage(message); - } - else if(message_name == "idle") - { - // no response is necessary here. - double time = message_in.getValueReal("time"); - - // Convert time to milliseconds for update() - update((int)(time * 1000.0f)); - } - else if(message_name == "cleanup") - { - unload(); - closedown(); - } - else if(message_name == "shm_added") - { - SharedSegmentInfo info; - info.mAddress = message_in.getValuePointer("address"); - info.mSize = (size_t)message_in.getValueS32("size"); - std::string name = message_in.getValue("name"); - - std::ostringstream str; - INFOMSG("MediaPluginGStreamer010::receiveMessage: shared memory added, name: %s, size: %d, address: %p", name.c_str(), int(info.mSize), info.mAddress); - - mSharedSegments.insert(SharedSegmentMap::value_type(name, info)); - } - else if(message_name == "shm_remove") - { - std::string name = message_in.getValue("name"); - - DEBUGMSG("MediaPluginGStreamer010::receiveMessage: shared memory remove, name = %s", name.c_str()); - - SharedSegmentMap::iterator iter = mSharedSegments.find(name); - if(iter != mSharedSegments.end()) - { - if(mPixels == iter->second.mAddress) - { - // This is the currently active pixel buffer. Make sure we stop drawing to it. - mPixels = NULL; - mTextureSegmentName.clear(); - - // Make sure the movie decoder is no longer pointed at the shared segment. - sizeChanged(); - } - mSharedSegments.erase(iter); - } - else - { - WARNMSG("MediaPluginGStreamer010::receiveMessage: unknown shared memory region!"); - } - - // Send the response so it can be cleaned up. - LLPluginMessage message("base", "shm_remove_response"); - message.setValue("name", name); - sendMessage(message); - } - else - { - std::ostringstream str; - INFOMSG("MediaPluginGStreamer010::receiveMessage: unknown base message: %s", message_name.c_str()); - } - } - else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA) - { - if(message_name == "init") - { - // Plugin gets to decide the texture parameters to use. - LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params"); - // lame to have to decide this now, it depends on the movie. Oh well. - mDepth = 4; - - mCurrentWidth = 1; - mCurrentHeight = 1; - mPreviousWidth = 1; - mPreviousHeight = 1; - mNaturalWidth = 1; - mNaturalHeight = 1; - mWidth = 1; - mHeight = 1; - mTextureWidth = 1; - mTextureHeight = 1; - - message.setValueU32("format", GL_RGBA); - message.setValueU32("type", GL_UNSIGNED_INT_8_8_8_8_REV); - - message.setValueS32("depth", mDepth); - message.setValueS32("default_width", mWidth); - message.setValueS32("default_height", mHeight); - message.setValueU32("internalformat", GL_RGBA8); - message.setValueBoolean("coords_opengl", true); // true == use OpenGL-style coordinates, false == (0,0) is upper left. - message.setValueBoolean("allow_downsample", true); // we respond with grace and performance if asked to downscale - sendMessage(message); - } - else if(message_name == "size_change") - { - std::string name = message_in.getValue("name"); - S32 width = message_in.getValueS32("width"); - S32 height = message_in.getValueS32("height"); - S32 texture_width = message_in.getValueS32("texture_width"); - S32 texture_height = message_in.getValueS32("texture_height"); - - std::ostringstream str; - INFOMSG("---->Got size change instruction from application with shm name: %s - size is %d x %d", name.c_str(), width, height); - - LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_response"); - message.setValue("name", name); - message.setValueS32("width", width); - message.setValueS32("height", height); - message.setValueS32("texture_width", texture_width); - message.setValueS32("texture_height", texture_height); - sendMessage(message); - - if(!name.empty()) - { - // Find the shared memory region with this name - SharedSegmentMap::iterator iter = mSharedSegments.find(name); - if(iter != mSharedSegments.end()) - { - INFOMSG("*** Got size change with matching shm, new size is %d x %d", width, height); - INFOMSG("*** Got size change with matching shm, texture size size is %d x %d", texture_width, texture_height); - - mPixels = (unsigned char*)iter->second.mAddress; - mTextureSegmentName = name; - mWidth = width; - mHeight = height; - - if (texture_width > 1 || - texture_height > 1) // not a dummy size from the app, a real explicit forced size - { - INFOMSG("**** = REAL RESIZE REQUEST FROM APP"); - - GST_OBJECT_LOCK(mVideoSink); - mVideoSink->resize_forced_always = true; - mVideoSink->resize_try_width = texture_width; - mVideoSink->resize_try_height = texture_height; - GST_OBJECT_UNLOCK(mVideoSink); - } - - mTextureWidth = texture_width; - mTextureHeight = texture_height; - } - } - } - else if(message_name == "load_uri") - { - std::string uri = message_in.getValue("uri"); - navigateTo( uri ); - sendStatus(); - } - else if(message_name == "mouse_event") - { - std::string event = message_in.getValue("event"); - S32 x = message_in.getValueS32("x"); - S32 y = message_in.getValueS32("y"); - - if(event == "down") - { - mouseDown(x, y); - } - else if(event == "up") - { - mouseUp(x, y); - } - else if(event == "move") - { - mouseMove(x, y); - }; - }; - } - else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME) - { - if(message_name == "stop") - { - stop(); - } - else if(message_name == "start") - { - double rate = 0.0; - if(message_in.hasValue("rate")) - { - rate = message_in.getValueReal("rate"); - } - // NOTE: we don't actually support rate. - play(rate); - } - else if(message_name == "pause") - { - pause(); - } - else if(message_name == "seek") - { - double time = message_in.getValueReal("time"); - // defer the actual seek in case we haven't - // really truly started yet in which case there - // is nothing to seek upon - mSeekWanted = true; - mSeekDestination = time; - } - else if(message_name == "set_loop") - { - bool loop = message_in.getValueBoolean("loop"); - mIsLooping = loop; - } - else if(message_name == "set_volume") - { - double volume = message_in.getValueReal("volume"); - setVolume(volume); - } - } - else - { - INFOMSG("MediaPluginGStreamer010::receiveMessage: unknown message class: %s", message_class.c_str()); - } - } -} - -int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data) -{ - if (MediaPluginGStreamer010::startup()) - { - MediaPluginGStreamer010 *self = new MediaPluginGStreamer010(host_send_func, host_user_data); - *plugin_send_func = MediaPluginGStreamer010::staticReceiveMessage; - *plugin_user_data = (void*)self; - - return 0; // okay - } - else - { - return -1; // failed to init - } -} - -#else // LL_GSTREAMER010_ENABLED - -// Stubbed-out class with constructor/destructor (necessary or windows linker -// will just think its dead code and optimize it all out) -class MediaPluginGStreamer010 : public MediaPluginBase -{ -public: - MediaPluginGStreamer010(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data); - ~MediaPluginGStreamer010(); - /* virtual */ void receiveMessage(const char *message_string); -}; - -MediaPluginGStreamer010::MediaPluginGStreamer010( - LLPluginInstance::sendMessageFunction host_send_func, - void *host_user_data ) : - MediaPluginBase(host_send_func, host_user_data) -{ - // no-op -} - -MediaPluginGStreamer010::~MediaPluginGStreamer010() -{ - // no-op -} - -void MediaPluginGStreamer010::receiveMessage(const char *message_string) -{ - // no-op -} - -// We're building without GStreamer enabled. Just refuse to initialize. -int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data) -{ - return -1; -} - -#endif // LL_GSTREAMER010_ENABLED diff --git a/indra/media_plugins/gstreamer10/CMakeLists.txt b/indra/media_plugins/gstreamer10/CMakeLists.txt new file mode 100644 index 0000000000..14ce5bfaa1 --- /dev/null +++ b/indra/media_plugins/gstreamer10/CMakeLists.txt @@ -0,0 +1,41 @@ +# -*- cmake -*- + +project(media_plugin_gstreamer10) + +include(00-Common) +include(LLCommon) +include(LLImage) +include(LLMath) +include(LLWindow) +include(Linking) +include(PluginAPI) +include(OpenGL) +include(GLIB) + +include(GStreamer10Plugin) + +### media_plugin_gstreamer10 + +set(media_plugin_gstreamer10_SOURCE_FILES + media_plugin_gstreamer10.cpp + ) + +set(media_plugin_gstreamer10_HEADER_FILES + llmediaimplgstreamer_syms.h + llmediaimplgstreamertriviallogging.h + ) + +add_library(media_plugin_gstreamer10 + SHARED + ${media_plugin_gstreamer10_SOURCE_FILES} +) + +target_link_libraries(media_plugin_gstreamer10 media_plugin_base ll::gstreamer10 ) + +if (WINDOWS) + set_target_properties( + media_plugin_gstreamer10 + PROPERTIES + LINK_FLAGS "/MANIFEST:NO /SAFESEH:NO /NODEFAULTLIB:LIBCMT" + ) +endif (WINDOWS) diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamer.h b/indra/media_plugins/gstreamer10/llmediaimplgstreamer.h index cae11a5cb3..cae11a5cb3 100644 --- a/indra/media_plugins/gstreamer010/llmediaimplgstreamer.h +++ b/indra/media_plugins/gstreamer10/llmediaimplgstreamer.h diff --git a/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc b/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc new file mode 100644 index 0000000000..6f5bb04bdf --- /dev/null +++ b/indra/media_plugins/gstreamer10/llmediaimplgstreamer_syms_raw.inc @@ -0,0 +1,71 @@ +#define G gstSymbolGrabber + +LL_GRAB_SYM(G, true, gst_buffer_new, GstBuffer*, void) +LL_GRAB_SYM(G, true, gst_structure_set_value, void, GstStructure *, const gchar *, const GValue*) +LL_GRAB_SYM(G, true, gst_init_check, gboolean, int *argc, char **argv[], GError ** err) +LL_GRAB_SYM(G, true, gst_message_get_type, GType, void) +LL_GRAB_SYM(G, true, gst_message_type_get_name, const gchar*, GstMessageType type) +LL_GRAB_SYM(G, true, gst_message_parse_error, void, GstMessage *message, GError **gerror, gchar **debug) +LL_GRAB_SYM(G, true, gst_message_parse_warning, void, GstMessage *message, GError **gerror, gchar **debug) +LL_GRAB_SYM(G, true, gst_message_parse_state_changed, void, GstMessage *message, GstState *oldstate, GstState *newstate, GstState *pending) +LL_GRAB_SYM(G, true, gst_element_set_state, GstStateChangeReturn, GstElement *element, GstState state) +LL_GRAB_SYM(G, true, gst_object_unref, void, gpointer object) +LL_GRAB_SYM(G, true, gst_object_get_type, GType, void) +LL_GRAB_SYM(G, true, gst_pipeline_get_type, GType, void) +LL_GRAB_SYM(G, true, gst_pipeline_get_bus, GstBus*, GstPipeline *pipeline) +LL_GRAB_SYM(G, true, gst_bus_add_watch, guint, GstBus * bus, GstBusFunc func, gpointer user_data) +LL_GRAB_SYM(G, true, gst_element_factory_make, GstElement*, const gchar *factoryname, const gchar *name) +LL_GRAB_SYM(G, true, gst_element_get_type, GType, void) +LL_GRAB_SYM(G, true, gst_static_pad_template_get, GstPadTemplate*, GstStaticPadTemplate *pad_template) +LL_GRAB_SYM(G, true, gst_element_class_add_pad_template, void, GstElementClass *klass, GstPadTemplate *temp) +LL_GRAB_SYM(G, true, gst_caps_from_string, GstCaps *, const gchar *string) +LL_GRAB_SYM(G, true, gst_caps_get_structure, GstStructure *, const GstCaps *caps, guint index) +LL_GRAB_SYM(G, true, gst_element_register, gboolean, GstPlugin *plugin, const gchar *name, guint rank, GType type) +LL_GRAB_SYM(G, true, gst_structure_get_int, gboolean, const GstStructure *structure, const gchar *fieldname, gint *value) +LL_GRAB_SYM(G, true, gst_structure_get_value, const GValue *, const GstStructure *structure, const gchar *fieldname) +LL_GRAB_SYM(G, true, gst_value_get_fraction_numerator, gint, const GValue *value) +LL_GRAB_SYM(G, true, gst_value_get_fraction_denominator, gint, const GValue *value) +LL_GRAB_SYM(G, true, gst_structure_get_name, const gchar *, const GstStructure *structure) +LL_GRAB_SYM(G, true, gst_element_seek, bool, GstElement *, gdouble, GstFormat, GstSeekFlags, GstSeekType, gint64, GstSeekType, gint64) + +LL_GRAB_SYM(G, false, gst_registry_fork_set_enabled, void, gboolean enabled) +LL_GRAB_SYM(G, false, gst_segtrap_set_enabled, void, gboolean enabled) +LL_GRAB_SYM(G, false, gst_message_parse_buffering, void, GstMessage *message, gint *percent) +LL_GRAB_SYM(G, false, gst_message_parse_info, void, GstMessage *message, GError **gerror, gchar **debug) +LL_GRAB_SYM(G, false, gst_element_query_position, gboolean, GstElement *element, GstFormat *format, gint64 *cur) +LL_GRAB_SYM(G, false, gst_version, void, guint *major, guint *minor, guint *micro, guint *nano) + +LL_GRAB_SYM(G, true, gst_message_parse_tag, void, GstMessage *, GstTagList **) +LL_GRAB_SYM(G, true, gst_tag_list_foreach, void, const GstTagList *, GstTagForeachFunc, gpointer) +LL_GRAB_SYM(G, true, gst_tag_list_get_tag_size, guint, const GstTagList *, const gchar *) +LL_GRAB_SYM(G, true, gst_tag_list_get_value_index, const GValue *, const GstTagList *, const gchar *, guint) + +LL_GRAB_SYM(G, true, gst_caps_new_simple, GstCaps*, const char *, const char*, ... ) + +LL_GRAB_SYM(G, true, gst_sample_get_caps, GstCaps*, GstSample* ) +LL_GRAB_SYM(G, true, gst_sample_get_buffer, GstBuffer*, GstSample* ) +LL_GRAB_SYM(G, true, gst_buffer_map, gboolean, GstBuffer*, GstMapInfo*, GstMapFlags ) +LL_GRAB_SYM(G, true, gst_buffer_unmap, void, GstBuffer*, GstMapInfo* ) + +LL_GRAB_SYM(G, true, gst_app_sink_set_caps, void, GstAppSink*, GstCaps const* ) +LL_GRAB_SYM(G, true, gst_app_sink_pull_sample, GstSample*, GstAppSink* ) + +LL_GRAB_SYM(G, true, g_free, void, gpointer ) +LL_GRAB_SYM(G, true, g_error_free, void, GError* ) + +LL_GRAB_SYM(G, true, g_main_context_pending, gboolean, GMainContext* ) +LL_GRAB_SYM(G, true, g_main_loop_get_context, GMainContext*, GMainLoop* ) +LL_GRAB_SYM(G, true, g_main_context_iteration, gboolean, GMainContext*, gboolean ) +LL_GRAB_SYM(G, true, g_main_loop_new, GMainLoop*, GMainContext*, gboolean ) +LL_GRAB_SYM(G, true, g_main_loop_quit, void, GMainLoop* ) +LL_GRAB_SYM(G, true, gst_mini_object_unref, void, GstMiniObject* ) +LL_GRAB_SYM(G, true, g_object_set, void, gpointer, gchar const*, ... ) +LL_GRAB_SYM(G, true, g_source_remove, gboolean, guint ) +LL_GRAB_SYM(G, true, g_value_get_string, gchar const*, GValue const* ) + +LL_GRAB_SYM(G, true, gst_debug_set_active, void, gboolean ) +LL_GRAB_SYM(G, true, gst_debug_add_log_function, void, GstLogFunction, gpointer, GDestroyNotify ) +LL_GRAB_SYM(G, true, gst_debug_set_default_threshold, void, GstDebugLevel ) +LL_GRAB_SYM(G, true, gst_debug_message_get , gchar const*, GstDebugMessage * ) + +#undef G diff --git a/indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp b/indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp new file mode 100644 index 0000000000..0f45c151a2 --- /dev/null +++ b/indra/media_plugins/gstreamer10/media_plugin_gstreamer10.cpp @@ -0,0 +1,958 @@ +/** + * @file media_plugin_gstreamer10.cpp + * @brief GStreamer-1.0 plugin for LLMedia API plugin system + * + * @cond + * $LicenseInfo:firstyear=2016&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2016, Linden Research, Inc. / Nicky Dasmijn + * + * 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$ + * @endcond + */ + +#define FLIP_Y + +#include "linden_common.h" + +#include "llgl.h" + +#include "llplugininstance.h" +#include "llpluginmessage.h" +#include "llpluginmessageclasses.h" +#include "media_plugin_base.h" + +#define G_DISABLE_CAST_CHECKS +extern "C" { +#include <gst/gst.h> +#include <gst/app/gstappsink.h> +} + +SymbolGrabber gstSymbolGrabber; + +#include "llmediaimplgstreamer_syms_raw.inc" + +static inline void llgst_caps_unref( GstCaps * caps ) +{ + llgst_mini_object_unref( GST_MINI_OBJECT_CAST( caps ) ); +} + +static inline void llgst_sample_unref( GstSample *aSample ) +{ + llgst_mini_object_unref( GST_MINI_OBJECT_CAST( aSample ) ); +} + +////////////////////////////////////////////////////////////////////////////// +// +class MediaPluginGStreamer10 : public MediaPluginBase +{ +public: + MediaPluginGStreamer10(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data); + ~MediaPluginGStreamer10(); + + /* virtual */ void receiveMessage(const char *message_string); + + static bool startup(); + static bool closedown(); + + gboolean processGSTEvents(GstBus *bus, GstMessage *message); + +private: + std::string getVersion(); + bool navigateTo( const std::string urlIn ); + bool seek( double time_sec ); + bool setVolume( float volume ); + + // misc + bool pause(); + bool stop(); + bool play(double rate); + bool getTimePos(double &sec_out); + + double MIN_LOOP_SEC = 1.0F; + U32 INTERNAL_TEXTURE_SIZE = 1024; + + bool mIsLooping; + + enum ECommand { + COMMAND_NONE, + COMMAND_STOP, + COMMAND_PLAY, + COMMAND_FAST_FORWARD, + COMMAND_FAST_REWIND, + COMMAND_PAUSE, + COMMAND_SEEK, + }; + ECommand mCommand; + +private: + bool unload(); + bool load(); + + bool update(int milliseconds); + void mouseDown( int x, int y ); + void mouseUp( int x, int y ); + void mouseMove( int x, int y ); + + static bool mDoneInit; + + guint mBusWatchID; + + float mVolume; + + int mDepth; + + // padded texture size we need to write into + int mTextureWidth; + int mTextureHeight; + + bool mSeekWanted; + double mSeekDestination; + + // Very GStreamer-specific + GMainLoop *mPump; // event pump for this media + GstElement *mPlaybin; + GstAppSink *mAppSink; +}; + +//static +bool MediaPluginGStreamer10::mDoneInit = false; + +MediaPluginGStreamer10::MediaPluginGStreamer10( LLPluginInstance::sendMessageFunction host_send_func, + void *host_user_data ) + : MediaPluginBase(host_send_func, host_user_data) + , mBusWatchID ( 0 ) + , mSeekWanted(false) + , mSeekDestination(0.0) + , mPump ( nullptr ) + , mPlaybin ( nullptr ) + , mAppSink ( nullptr ) + , mCommand ( COMMAND_NONE ) +{ +} + +gboolean MediaPluginGStreamer10::processGSTEvents(GstBus *bus, GstMessage *message) +{ + if (!message) + return TRUE; // shield against GStreamer bug + + switch (GST_MESSAGE_TYPE (message)) + { + case GST_MESSAGE_BUFFERING: + { + // NEEDS GST 0.10.11+ + if (llgst_message_parse_buffering) + { + gint percent = 0; + llgst_message_parse_buffering(message, &percent); + } + break; + } + case GST_MESSAGE_STATE_CHANGED: + { + GstState old_state; + GstState new_state; + GstState pending_state; + llgst_message_parse_state_changed(message, + &old_state, + &new_state, + &pending_state); + + switch (new_state) + { + case GST_STATE_VOID_PENDING: + break; + case GST_STATE_NULL: + break; + case GST_STATE_READY: + setStatus(STATUS_LOADED); + break; + case GST_STATE_PAUSED: + setStatus(STATUS_PAUSED); + break; + case GST_STATE_PLAYING: + setStatus(STATUS_PLAYING); + break; + } + break; + } + case GST_MESSAGE_ERROR: + { + GError *err = nullptr; + gchar *debug = nullptr; + + llgst_message_parse_error (message, &err, &debug); + if (err) + llg_error_free (err); + llg_free (debug); + + mCommand = COMMAND_STOP; + + setStatus(STATUS_ERROR); + + break; + } + case GST_MESSAGE_INFO: + { + if (llgst_message_parse_info) + { + GError *err = nullptr; + gchar *debug = nullptr; + + llgst_message_parse_info (message, &err, &debug); + if (err) + llg_error_free (err); + llg_free (debug); + } + break; + } + case GST_MESSAGE_WARNING: + { + GError *err = nullptr; + gchar *debug = nullptr; + + llgst_message_parse_warning (message, &err, &debug); + if (err) + llg_error_free (err); + llg_free (debug); + + break; + } + case GST_MESSAGE_EOS: + /* end-of-stream */ + if (mIsLooping) + { + double eos_pos_sec = 0.0F; + bool got_eos_position = getTimePos(eos_pos_sec); + + if (got_eos_position && eos_pos_sec < MIN_LOOP_SEC) + { + // if we know that the movie is really short, don't + // loop it else it can easily become a time-hog + // because of GStreamer spin-up overhead + // inject a COMMAND_PAUSE + mCommand = COMMAND_PAUSE; + } + else + { + stop(); + play(1.0); + } + } + else // not a looping media + { + // inject a COMMAND_STOP + mCommand = COMMAND_STOP; + } + break; + default: + /* unhandled message */ + break; + } + + /* we want to be notified again the next time there is a message + * on the bus, so return true (false means we want to stop watching + * for messages on the bus and our callback should not be called again) + */ + return TRUE; +} + +extern "C" { + gboolean llmediaimplgstreamer_bus_callback (GstBus *bus, + GstMessage *message, + gpointer data) + { + MediaPluginGStreamer10 *impl = (MediaPluginGStreamer10*)data; + return impl->processGSTEvents(bus, message); + } +} // extern "C" + + + +bool MediaPluginGStreamer10::navigateTo ( const std::string urlIn ) +{ + if (!mDoneInit) + return false; // error + + setStatus(STATUS_LOADING); + + mSeekWanted = false; + + if (nullptr == mPump || nullptr == mPlaybin) + { + setStatus(STATUS_ERROR); + return false; // error + } + + llg_object_set (G_OBJECT (mPlaybin), "uri", urlIn.c_str(), nullptr); + + // navigateTo implicitly plays, too. + play(1.0); + + return true; +} + + +class GstSampleUnref +{ + GstSample *mT; +public: + GstSampleUnref( GstSample *aT ) + : mT( aT ) + { llassert_always( mT ); } + + ~GstSampleUnref( ) + { llgst_sample_unref( mT ); } +}; + +bool MediaPluginGStreamer10::update(int milliseconds) +{ + if (!mDoneInit) + return false; // error + + // DEBUGMSG("updating media..."); + + // sanity check + if (nullptr == mPump || nullptr == mPlaybin) + { + return false; + } + + // see if there's an outstanding seek wanted + if (mSeekWanted && + // bleh, GST has to be happy that the movie is really truly playing + // or it may quietly ignore the seek (with rtsp:// at least). + (GST_STATE(mPlaybin) == GST_STATE_PLAYING)) + { + seek(mSeekDestination); + mSeekWanted = false; + } + + // *TODO: time-limit - but there isn't a lot we can do here, most + // time is spent in gstreamer's own opaque worker-threads. maybe + // we can do something sneaky like only unlock the video object + // for 'milliseconds' and otherwise hold the lock. + while (llg_main_context_pending(llg_main_loop_get_context(mPump))) + { + llg_main_context_iteration(llg_main_loop_get_context(mPump), FALSE); + } + + // check for availability of a new frame + + if( !mAppSink ) + return true; + + if( GST_STATE(mPlaybin) != GST_STATE_PLAYING) // Do not try to pull a sample if not in playing state + return true; + + GstSample *pSample = llgst_app_sink_pull_sample( mAppSink ); + if(!pSample) + return false; // Done playing + + GstSampleUnref oSampleUnref( pSample ); + GstCaps *pCaps = llgst_sample_get_caps ( pSample ); + if (!pCaps) + return false; + + gint width = 0, height = 0; + GstStructure *pStruct = llgst_caps_get_structure ( pCaps, 0); + + if(!llgst_structure_get_int ( pStruct, "width", &width) ) + width = 0; + if(!llgst_structure_get_int ( pStruct, "height", &height) ) + height = 0; + + if( !mPixels || width == 0 || height == 0) + return true; + + GstBuffer *pBuffer = llgst_sample_get_buffer ( pSample ); + GstMapInfo map; + llgst_buffer_map ( pBuffer, &map, GST_MAP_READ); + + // Our render buffer is always 1kx1k + + U32 rowSkip = INTERNAL_TEXTURE_SIZE / mTextureHeight; + U32 colSkip = INTERNAL_TEXTURE_SIZE / mTextureWidth; + + for (int row = 0; row < mTextureHeight; ++row) + { + U8 const *pTexelIn = map.data + (row*rowSkip * width *3); +#ifndef FLIP_Y + U8 *pTexelOut = mPixels + (row * mTextureWidth * mDepth ); +#else + U8 *pTexelOut = mPixels + ((mTextureHeight-row-1) * mTextureWidth * mDepth ); +#endif + for( int col = 0; col < mTextureWidth; ++col ) + { + pTexelOut[ 0 ] = pTexelIn[0]; + pTexelOut[ 1 ] = pTexelIn[1]; + pTexelOut[ 2 ] = pTexelIn[2]; + pTexelOut += mDepth; + pTexelIn += colSkip*3; + } + } + + llgst_buffer_unmap( pBuffer, &map ); + setDirty(0,0,mTextureWidth,mTextureHeight); + + return true; +} + +void MediaPluginGStreamer10::mouseDown( int x, int y ) +{ + // do nothing +} + +void MediaPluginGStreamer10::mouseUp( int x, int y ) +{ + // do nothing +} + +void MediaPluginGStreamer10::mouseMove( int x, int y ) +{ + // do nothing +} + + +bool MediaPluginGStreamer10::pause() +{ + // todo: error-check this? + if (mDoneInit && mPlaybin) + { + llgst_element_set_state(mPlaybin, GST_STATE_PAUSED); + return true; + } + return false; +} + +bool MediaPluginGStreamer10::stop() +{ + // todo: error-check this? + if (mDoneInit && mPlaybin) + { + llgst_element_set_state(mPlaybin, GST_STATE_READY); + return true; + } + return false; +} + +bool MediaPluginGStreamer10::play(double rate) +{ + // NOTE: we don't actually support non-natural rate. + + // todo: error-check this? + if (mDoneInit && mPlaybin) + { + llgst_element_set_state(mPlaybin, GST_STATE_PLAYING); + return true; + } + return false; +} + +bool MediaPluginGStreamer10::setVolume( float volume ) +{ + // we try to only update volume as conservatively as + // possible, as many gst-plugins-base versions up to at least + // November 2008 have critical race-conditions in setting volume - sigh + if (mVolume == volume) + return true; // nothing to do, everything's fine + + mVolume = volume; + if (mDoneInit && mPlaybin) + { + llg_object_set(mPlaybin, "volume", mVolume, nullptr); + return true; + } + + return false; +} + +bool MediaPluginGStreamer10::seek(double time_sec) +{ + bool success = false; + if (mDoneInit && mPlaybin) + { + success = llgst_element_seek(mPlaybin, 1.0F, GST_FORMAT_TIME, + GstSeekFlags(GST_SEEK_FLAG_FLUSH | + GST_SEEK_FLAG_KEY_UNIT), + GST_SEEK_TYPE_SET, gint64(time_sec*GST_SECOND), + GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE); + } + return success; +} + +bool MediaPluginGStreamer10::getTimePos(double &sec_out) +{ + bool got_position = false; + if (mDoneInit && mPlaybin) + { + gint64 pos(0); + GstFormat timefmt = GST_FORMAT_TIME; + got_position = + llgst_element_query_position && + llgst_element_query_position(mPlaybin, + &timefmt, + &pos); + got_position = got_position + && (timefmt == GST_FORMAT_TIME); + // GStreamer may have other ideas, but we consider the current position + // undefined if not PLAYING or PAUSED + got_position = got_position && + (GST_STATE(mPlaybin) == GST_STATE_PLAYING || + GST_STATE(mPlaybin) == GST_STATE_PAUSED); + if (got_position && !GST_CLOCK_TIME_IS_VALID(pos)) + { + if (GST_STATE(mPlaybin) == GST_STATE_PLAYING) + { + // if we're playing then we treat an invalid clock time + // as 0, for complicated reasons (insert reason here) + pos = 0; + } + else + { + got_position = false; + } + + } + // If all the preconditions succeeded... we can trust the result. + if (got_position) + { + sec_out = double(pos) / double(GST_SECOND); // gst to sec + } + } + return got_position; +} + +bool MediaPluginGStreamer10::load() +{ + if (!mDoneInit) + return false; // error + + setStatus(STATUS_LOADING); + + mIsLooping = false; + mVolume = 0.1234567f; // minor hack to force an initial volume update + + // Create a pumpable main-loop for this media + mPump = llg_main_loop_new (nullptr, FALSE); + if (!mPump) + { + setStatus(STATUS_ERROR); + return false; // error + } + + // instantiate a playbin element to do the hard work + mPlaybin = llgst_element_factory_make ("playbin", ""); + if (!mPlaybin) + { + setStatus(STATUS_ERROR); + return false; // error + } + + // get playbin's bus + GstBus *bus = llgst_pipeline_get_bus (GST_PIPELINE (mPlaybin)); + if (!bus) + { + setStatus(STATUS_ERROR); + return false; // error + } + mBusWatchID = llgst_bus_add_watch (bus, + llmediaimplgstreamer_bus_callback, + this); + llgst_object_unref (bus); + + mAppSink = (GstAppSink*)(llgst_element_factory_make ("appsink", "")); + + GstCaps* pCaps = llgst_caps_new_simple( "video/x-raw", + "format", G_TYPE_STRING, "RGB", + "width", G_TYPE_INT, INTERNAL_TEXTURE_SIZE, + "height", G_TYPE_INT, INTERNAL_TEXTURE_SIZE, + nullptr ); + + llgst_app_sink_set_caps( mAppSink, pCaps ); + llgst_caps_unref( pCaps ); + + if (!mAppSink) + { + setStatus(STATUS_ERROR); + return false; + } + + llg_object_set(mPlaybin, "video-sink", mAppSink, nullptr); + + return true; +} + +bool MediaPluginGStreamer10::unload () +{ + if (!mDoneInit) + return false; // error + + // stop getting callbacks for this bus + llg_source_remove(mBusWatchID); + mBusWatchID = 0; + + if (mPlaybin) + { + llgst_element_set_state (mPlaybin, GST_STATE_NULL); + llgst_object_unref (GST_OBJECT (mPlaybin)); + mPlaybin = nullptr; + } + + if (mPump) + { + llg_main_loop_quit(mPump); + mPump = nullptr; + } + + mAppSink = nullptr; + + setStatus(STATUS_NONE); + + return true; +} + +void LogFunction(GstDebugCategory *category, GstDebugLevel level, const gchar *file, const gchar *function, gint line, GObject *object, GstDebugMessage *message, gpointer user_data ) +{ + std::cerr << file << ":" << line << "(" << function << "): " << llgst_debug_message_get( message ) << std::endl; +} + +//static +bool MediaPluginGStreamer10::startup() +{ + // first - check if GStreamer is explicitly disabled + if (nullptr != getenv("LL_DISABLE_GSTREAMER")) + return false; + + // only do global GStreamer initialization once. + if (!mDoneInit) + { + ll_init_apr(); + + // Get symbols! + std::vector< std::string > vctDSONames; + vctDSONames.push_back( "libgstreamer-1.0.so.0" ); + vctDSONames.push_back( "libgstapp-1.0.so.0" ); + vctDSONames.push_back( "libglib-2.0.so.0" ); + vctDSONames.push_back( "libgobject-2.0.so" ); + if( !gstSymbolGrabber.grabSymbols( vctDSONames ) ) + return false; + + if (llgst_segtrap_set_enabled) + { + llgst_segtrap_set_enabled(FALSE); + } + + // Gstreamer tries a fork during init, waitpid-ing on it, + // which conflicts with any installed SIGCHLD handler... + struct sigaction tmpact, oldact; + if (llgst_registry_fork_set_enabled ) { + // if we can disable SIGCHLD-using forking behaviour, + // do it. + llgst_registry_fork_set_enabled(false); + } + else { + // else temporarily install default SIGCHLD handler + // while GStreamer initialises + tmpact.sa_handler = SIG_DFL; + sigemptyset( &tmpact.sa_mask ); + tmpact.sa_flags = SA_SIGINFO; + sigaction(SIGCHLD, &tmpact, &oldact); + } + // Protect against GStreamer resetting the locale, yuck. + static std::string saved_locale; + saved_locale = setlocale(LC_ALL, nullptr); + + llgst_debug_set_default_threshold( GST_LEVEL_WARNING ); + llgst_debug_add_log_function( LogFunction, nullptr, nullptr ); + llgst_debug_set_active( false ); + + // finally, try to initialize GStreamer! + GError *err = nullptr; + gboolean init_gst_success = llgst_init_check(nullptr, nullptr, &err); + + // restore old locale + setlocale(LC_ALL, saved_locale.c_str() ); + + // restore old SIGCHLD handler + if (!llgst_registry_fork_set_enabled) + sigaction(SIGCHLD, &oldact, nullptr); + + if (!init_gst_success) // fail + { + if (err) + llg_error_free(err); + return false; + } + + mDoneInit = true; + } + + return true; +} + +//static +bool MediaPluginGStreamer10::closedown() +{ + if (!mDoneInit) + return false; // error + + gstSymbolGrabber.ungrabSymbols(); + mDoneInit = false; + + return true; +} + +MediaPluginGStreamer10::~MediaPluginGStreamer10() +{ + closedown(); +} + +std::string MediaPluginGStreamer10::getVersion() +{ + std::string plugin_version = "GStreamer10 media plugin, GStreamer version "; + if (mDoneInit && + llgst_version) + { + guint major, minor, micro, nano; + llgst_version(&major, &minor, µ, &nano); + plugin_version += llformat("%u.%u.%u.%u (runtime), %u.%u.%u.%u (headers)", (unsigned int)major, (unsigned int)minor, + (unsigned int)micro, (unsigned int)nano, (unsigned int)GST_VERSION_MAJOR, (unsigned int)GST_VERSION_MINOR, + (unsigned int)GST_VERSION_MICRO, (unsigned int)GST_VERSION_NANO); + } + else + { + plugin_version += "(unknown)"; + } + return plugin_version; +} + +void MediaPluginGStreamer10::receiveMessage(const char *message_string) +{ + LLPluginMessage message_in; + + if(message_in.parse(message_string) >= 0) + { + std::string message_class = message_in.getClass(); + std::string message_name = message_in.getName(); + + if(message_class == LLPLUGIN_MESSAGE_CLASS_BASE) + { + if(message_name == "init") + { + LLPluginMessage message("base", "init_response"); + LLSD versions = LLSD::emptyMap(); + versions[LLPLUGIN_MESSAGE_CLASS_BASE] = LLPLUGIN_MESSAGE_CLASS_BASE_VERSION; + versions[LLPLUGIN_MESSAGE_CLASS_MEDIA] = LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION; + versions[LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME] = LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME_VERSION; + message.setValueLLSD("versions", versions); + + load(); + + message.setValue("plugin_version", getVersion()); + sendMessage(message); + } + else if(message_name == "idle") + { + // no response is necessary here. + double time = message_in.getValueReal("time"); + + // Convert time to milliseconds for update() + update((int)(time * 1000.0f)); + } + else if(message_name == "cleanup") + { + unload(); + closedown(); + } + else if(message_name == "shm_added") + { + SharedSegmentInfo info; + info.mAddress = message_in.getValuePointer("address"); + info.mSize = (size_t)message_in.getValueS32("size"); + std::string name = message_in.getValue("name"); + + mSharedSegments.insert(SharedSegmentMap::value_type(name, info)); + } + else if(message_name == "shm_remove") + { + std::string name = message_in.getValue("name"); + + SharedSegmentMap::iterator iter = mSharedSegments.find(name); + if(iter != mSharedSegments.end()) + { + if(mPixels == iter->second.mAddress) + { + // This is the currently active pixel buffer. Make sure we stop drawing to it. + mPixels = nullptr; + mTextureSegmentName.clear(); + } + mSharedSegments.erase(iter); + } + + // Send the response so it can be cleaned up. + LLPluginMessage message("base", "shm_remove_response"); + message.setValue("name", name); + sendMessage(message); + } + } + else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA) + { + if(message_name == "init") + { + // Plugin gets to decide the texture parameters to use. + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params"); + // lame to have to decide this now, it depends on the movie. Oh well. + mDepth = 4; + + mTextureWidth = 1; + mTextureHeight = 1; + + message.setValueU32("format", GL_RGBA); + message.setValueU32("type", GL_UNSIGNED_INT_8_8_8_8_REV); + + message.setValueS32("depth", mDepth); + message.setValueS32("default_width", INTERNAL_TEXTURE_SIZE ); + message.setValueS32("default_height", INTERNAL_TEXTURE_SIZE ); + message.setValueU32("internalformat", GL_RGBA8); + message.setValueBoolean("coords_opengl", true); // true == use OpenGL-style coordinates, false == (0,0) is upper left. + message.setValueBoolean("allow_downsample", true); // we respond with grace and performance if asked to downscale + sendMessage(message); + } + else if(message_name == "size_change") + { + std::string name = message_in.getValue("name"); + S32 width = message_in.getValueS32("width"); + S32 height = message_in.getValueS32("height"); + S32 texture_width = message_in.getValueS32("texture_width"); + S32 texture_height = message_in.getValueS32("texture_height"); + + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_response"); + message.setValue("name", name); + message.setValueS32("width", width); + message.setValueS32("height", height); + message.setValueS32("texture_width", texture_width); + message.setValueS32("texture_height", texture_height); + sendMessage(message); + + if(!name.empty()) + { + // Find the shared memory region with this name + SharedSegmentMap::iterator iter = mSharedSegments.find(name); + if(iter != mSharedSegments.end()) + { + mPixels = (unsigned char*)iter->second.mAddress; + mTextureSegmentName = name; + + mTextureWidth = texture_width; + mTextureHeight = texture_height; + memset( mPixels, 0, mTextureWidth*mTextureHeight*mDepth ); + } + + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_request"); + message.setValue("name", mTextureSegmentName); + message.setValueS32("width", INTERNAL_TEXTURE_SIZE ); + message.setValueS32("height", INTERNAL_TEXTURE_SIZE ); + sendMessage(message); + + } + } + else if(message_name == "load_uri") + { + std::string uri = message_in.getValue("uri"); + navigateTo( uri ); + sendStatus(); + } + else if(message_name == "mouse_event") + { + std::string event = message_in.getValue("event"); + S32 x = message_in.getValueS32("x"); + S32 y = message_in.getValueS32("y"); + + if(event == "down") + { + mouseDown(x, y); + } + else if(event == "up") + { + mouseUp(x, y); + } + else if(event == "move") + { + mouseMove(x, y); + }; + }; + } + else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME) + { + if(message_name == "stop") + { + stop(); + } + else if(message_name == "start") + { + double rate = 0.0; + if(message_in.hasValue("rate")) + { + rate = message_in.getValueReal("rate"); + } + // NOTE: we don't actually support rate. + play(rate); + } + else if(message_name == "pause") + { + pause(); + } + else if(message_name == "seek") + { + double time = message_in.getValueReal("time"); + // defer the actual seek in case we haven't + // really truly started yet in which case there + // is nothing to seek upon + mSeekWanted = true; + mSeekDestination = time; + } + else if(message_name == "set_loop") + { + bool loop = message_in.getValueBoolean("loop"); + mIsLooping = loop; + } + else if(message_name == "set_volume") + { + double volume = message_in.getValueReal("volume"); + setVolume(volume); + } + } + } +} + +int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data) +{ + if( MediaPluginGStreamer10::startup() ) + { + MediaPluginGStreamer10 *self = new MediaPluginGStreamer10(host_send_func, host_user_data); + *plugin_send_func = MediaPluginGStreamer10::staticReceiveMessage; + *plugin_user_data = (void*)self; + + return 0; // okay + } + else + { + return -1; // failed to init + } +} diff --git a/indra/media_plugins/libvlc/CMakeLists.txt b/indra/media_plugins/libvlc/CMakeLists.txt index 202cbed96e..863b4617e9 100644 --- a/indra/media_plugins/libvlc/CMakeLists.txt +++ b/indra/media_plugins/libvlc/CMakeLists.txt @@ -13,13 +13,6 @@ include(LibVLCPlugin) ### media_plugin_libvlc -if(NOT ADDRESS_SIZE EQUAL 32) - if(WINDOWS) - ##add_definitions(/FIXED:NO) - else(WINDOWS) # not windows therefore gcc LINUX and DARWIN - add_definitions(-fPIC) - endif(WINDOWS) -endif(NOT ADDRESS_SIZE EQUAL 32) set(media_plugin_libvlc_SOURCE_FILES media_plugin_libvlc.cpp diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index ec27b3d415..421c3fbed4 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -46,6 +46,7 @@ include(VisualLeakDetector) include(VulkanGltf) include(ZLIBNG) include(LLPrimitive) +include(Lualibs) if (NOT HAVOK_TPV) # When using HAVOK_TPV, the library is precompiled, so no need for this @@ -92,6 +93,7 @@ set(viewer_SOURCE_FILES llagentwearables.cpp llanimstatelabels.cpp llappcorehttp.cpp + llappearancelistener.cpp llappearancemgr.cpp llappviewer.cpp llappviewerlistener.cpp @@ -245,6 +247,8 @@ set(viewer_SOURCE_FILES llfloaterlandholdings.cpp llfloaterlinkreplace.cpp llfloaterloadprefpreset.cpp + llfloaterluadebug.cpp + llfloaterluascripts.cpp llfloatermarketplacelistings.cpp llfloatermap.cpp llfloatermediasettings.cpp @@ -267,7 +271,6 @@ set(viewer_SOURCE_FILES llfloaterpay.cpp llfloaterperformance.cpp llfloaterperms.cpp - llfloaterpostprocess.cpp llfloaterprofile.cpp llfloaterpreference.cpp llfloaterpreferencesgraphicsadvanced.cpp @@ -359,6 +362,7 @@ set(viewer_SOURCE_FILES llinventorygallerymenu.cpp llinventoryicon.cpp llinventoryitemslist.cpp + llinventorylistener.cpp llinventorylistitem.cpp llinventorymodel.cpp llinventorymodelbackgroundfetch.cpp @@ -378,6 +382,7 @@ set(viewer_SOURCE_FILES lllogchat.cpp llloginhandler.cpp lllogininstance.cpp + llluamanager.cpp llmachineid.cpp llmanip.cpp llmaniprotate.cpp @@ -764,6 +769,7 @@ set(viewer_HEADER_FILES llanimstatelabels.h llappcorehttp.h llappearance.h + llappearancelistener.h llappearancemgr.h llappviewer.h llappviewerlistener.h @@ -920,6 +926,8 @@ set(viewer_HEADER_FILES llfloaterlandholdings.h llfloaterlinkreplace.h llfloaterloadprefpreset.h + llfloaterluadebug.h + llfloaterluascripts.h llfloatermap.h llfloatermarketplacelistings.h llfloatermediasettings.h @@ -942,7 +950,6 @@ set(viewer_HEADER_FILES llfloaterpay.h llfloaterperformance.h llfloaterperms.h - llfloaterpostprocess.h llfloaterprofile.h llfloaterpreference.h llfloaterpreferencesgraphicsadvanced.h @@ -1032,6 +1039,7 @@ set(viewer_HEADER_FILES llinventorygallerymenu.h llinventoryicon.h llinventoryitemslist.h + llinventorylistener.h llinventorylistitem.h llinventorymodel.h llinventorymodelbackgroundfetch.h @@ -1051,6 +1059,7 @@ set(viewer_HEADER_FILES lllogchat.h llloginhandler.h lllogininstance.h + llluamanager.h llmachineid.h llmanip.h llmaniprotate.h @@ -1465,7 +1474,6 @@ if (LINUX) PROPERTIES COMPILE_DEFINITIONS "${VIEWER_CHANNEL_VERSION_DEFINES}" ) - LIST(APPEND viewer_SOURCE_FILES llappviewerlinux_api_dbus.cpp) SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") endif (LINUX) @@ -1627,6 +1635,13 @@ set_source_files_properties(${viewer_SHADER_FILES} PROPERTIES HEADER_FILE_ONLY TRUE) list(APPEND viewer_SOURCE_FILES ${viewer_SHADER_FILES}) +# Add lua script sources +file(GLOB_RECURSE viewer_LUA_SCRIPT_FILES LIST_DIRECTORIES FALSE + ${CMAKE_CURRENT_SOURCE_DIR}/scripts/lua/*.lua) +source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR}/scripts/lua PREFIX "LUA Scripts" FILES ${viewer_LUA_SCRIPT_FILES}) +set_source_files_properties(${viewer_LUA_SCRIPT_FILES} + PROPERTIES HEADER_FILE_ONLY TRUE) +list(APPEND viewer_SOURCE_FILES ${viewer_LUA_SCRIPT_FILES}) set(viewer_APPSETTINGS_FILES app_settings/anim.ini @@ -1827,20 +1842,6 @@ if (WINDOWS) if (PACKAGE) add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/event_host.tar.xz - COMMAND ${PYTHON_EXECUTABLE} - ARGS - ${CMAKE_CURRENT_SOURCE_DIR}/event_host_manifest.py - ${CMAKE_CURRENT_SOURCE_DIR}/.. - ${CMAKE_CURRENT_BINARY_DIR} - ${CMAKE_CFG_INTDIR} - DEPENDS - lleventhost - ${EVENT_HOST_SCRIPTS} - ${CMAKE_CURRENT_SOURCE_DIR}/event_host_manifest.py - ) - - add_custom_command( OUTPUT ${CMAKE_CFG_INTDIR}/touched.bat COMMAND ${PYTHON_EXECUTABLE} ARGS @@ -1869,9 +1870,6 @@ if (WINDOWS) add_custom_target(llpackage ALL DEPENDS ${CMAKE_CFG_INTDIR}/touched.bat ) - # temporarily disable packaging of event_host until hg subrepos get - # sorted out on the parabuild cluster... - #${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/event_host.tar.xz) endif (PACKAGE) elseif (DARWIN) @@ -1933,12 +1931,13 @@ target_link_libraries(${VIEWER_BINARY_NAME} llcommon llmeshoptimizer llwebrtc - ll::ndof lllogin llprimitive llappearance ${LLPHYSICSEXTENSIONS_LIBRARIES} ll::bugsplat + ll::lualibs + ll::ndof ll::tracy ll::openxr ) @@ -2315,6 +2314,11 @@ if (LL_TESTS) "${test_libs}" ) + LL_ADD_INTEGRATION_TEST(llluamanager + "llluamanager.cpp" + "${test_libs};ll::lualibs" + ) + LL_ADD_INTEGRATION_TEST(llsechandler_basic llsechandler_basic.cpp "${test_libs}" @@ -2369,4 +2373,3 @@ if (LL_TESTS) endif (LL_TESTS) check_message_template(${VIEWER_BINARY_NAME}) - diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index e0eaaa0bbc..0f9f025fe4 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -7.1.11 +7.1.12 diff --git a/indra/newview/app_settings/cmd_line.xml b/indra/newview/app_settings/cmd_line.xml index e16a5c7e76..e610775332 100644 --- a/indra/newview/app_settings/cmd_line.xml +++ b/indra/newview/app_settings/cmd_line.xml @@ -195,7 +195,33 @@ <string>LogPerformance</string> </map> - <key>multiple</key> + <key>lua</key> + <map> + <key>desc</key> + <string>Run specified Lua chunk</string> + <key>count</key> + <integer>1</integer> + <!-- you can specify multiple such chunks --> + <key>compose</key> + <boolean>true</boolean> + <key>map-to</key> + <string>LuaChunk</string> + </map> + + <key>luafile</key> + <map> + <key>desc</key> + <string>Run specified Lua script</string> + <key>count</key> + <integer>1</integer> + <!-- you can specify multiple such scripts --> + <key>compose</key> + <boolean>true</boolean> + <key>map-to</key> + <string>LuaScript</string> + </map> + + <key>multiple</key> <map> <key>desc</key> <string>Allow multiple viewers.</string> diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 5f672cc218..55ccfaa715 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -68,7 +68,7 @@ <key>Value</key> <integer>1</integer> </map> - <key>CrashHostUrl</key> + <key>CrashHostUrl</key> <map> <key>Comment</key> <string>A URL pointing to a crash report handler; overrides cluster negotiation to locate crash handler.</string> @@ -346,7 +346,7 @@ <key>Value</key> <real>0.5</real> </map> - <key>AudioStreamingMedia</key> + <key>AudioStreamingMedia</key> <map> <key>Comment</key> <string>Enable streaming</string> @@ -1314,7 +1314,7 @@ <key>Type</key> <string>Boolean</string> <key>Value</key> - <integer>0</integer> + <integer>0</integer> </map> <key>CameraPositionSmoothing</key> <map> @@ -1952,17 +1952,17 @@ <key>Value</key> <string /> </map> - <key>DebugAvatarRezTime</key> - <map> - <key>Comment</key> - <string>Display times for avatars to resolve.</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>0</integer> - </map> + <key>DebugAvatarRezTime</key> + <map> + <key>Comment</key> + <string>Display times for avatars to resolve.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>DebugAvatarLocalTexLoadedTime</key> <map> <key>Comment</key> @@ -2337,39 +2337,39 @@ <key>Value</key> <integer>0</integer> </map> - <key>DefaultFemaleAvatar</key> - <map> - <key>Comment</key> - <string>Default Female Avatar</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>String</string> - <key>Value</key> - <string>Female Shape & Outfit</string> - </map> - <key>DefaultLoginLocation</key> - <map> - <key>Comment</key> - <string>Startup destination default (if not specified on command line)</string> - <key>Persist</key> - <integer>0</integer> - <key>Type</key> - <string>String</string> - <key>Value</key> - <string/> - </map> - <key>DefaultMaleAvatar</key> - <map> - <key>Comment</key> - <string>Default Male Avatar</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>String</string> - <key>Value</key> - <string>Male Shape & Outfit</string> - </map> + <key>DefaultFemaleAvatar</key> + <map> + <key>Comment</key> + <string>Default Female Avatar</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string>Female Shape & Outfit</string> + </map> + <key>DefaultLoginLocation</key> + <map> + <key>Comment</key> + <string>Startup destination default (if not specified on command line)</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string/> + </map> + <key>DefaultMaleAvatar</key> + <map> + <key>Comment</key> + <string>Default Male Avatar</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string>Male Shape & Outfit</string> + </map> <key>DestinationGuideURL</key> <map> <key>Comment</key> @@ -2744,7 +2744,7 @@ <key>Value</key> <integer>0</integer> </map> - <key>FirstSelectedEnabledPopups</key> + <key>FirstSelectedEnabledPopups</key> <map> <key>Comment</key> <string>Return false if there is not enable popup selected in the list of floater preferences popups</string> @@ -3566,39 +3566,39 @@ <key>Value</key> <integer>0</integer> </map> - <key>InventoryLinking</key> - <map> - <key>Comment</key> - <string>Enable ability to create links to folders and items via "Paste as link".</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>1</integer> - </map> - <key>InventoryOutboxLogging</key> - <map> - <key>Comment</key> - <string>Enable debug output associated with the Merchant Outbox.</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>0</integer> - </map> - <key>InventoryOutboxMakeVisible</key> - <map> - <key>Comment</key> - <string>Enable making the Merchant Outbox and Inbox visible in the inventory for debug purposes.</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>0</integer> - </map> + <key>InventoryLinking</key> + <map> + <key>Comment</key> + <string>Enable ability to create links to folders and items via "Paste as link".</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> + <key>InventoryOutboxLogging</key> + <map> + <key>Comment</key> + <string>Enable debug output associated with the Merchant Outbox.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>InventoryOutboxMakeVisible</key> + <map> + <key>Comment</key> + <string>Enable making the Merchant Outbox and Inbox visible in the inventory for debug purposes.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>InventoryOutboxMaxFolderCount</key> <map> <key>Comment</key> @@ -3852,8 +3852,8 @@ <key>Value</key> <real>0.25</real> </map> - <key>Jpeg2000AdvancedCompression</key> - <map> + <key>Jpeg2000AdvancedCompression</key> + <map> <key>Comment</key> <string>Use advanced Jpeg2000 compression options (precincts, blocks, ordering, markers)</string> <key>Persist</key> @@ -3862,9 +3862,9 @@ <string>Boolean</string> <key>Value</key> <integer>0</integer> - </map> - <key>Jpeg2000PrecinctsSize</key> - <map> + </map> + <key>Jpeg2000PrecinctsSize</key> + <map> <key>Comment</key> <string>Size of image precincts. Assumed square and same for all levels. Must be power of 2.</string> <key>Persist</key> @@ -3873,9 +3873,9 @@ <string>S32</string> <key>Value</key> <integer>256</integer> - </map> - <key>Jpeg2000BlocksSize</key> - <map> + </map> + <key>Jpeg2000BlocksSize</key> + <map> <key>Comment</key> <string>Size of encoding blocks. Assumed square and same for all levels. Must be power of 2. Max 64, Min 4.</string> <key>Persist</key> @@ -3884,7 +3884,7 @@ <string>S32</string> <key>Value</key> <integer>64</integer> - </map> + </map> <key>KeepAspectForSnapshot</key> <map> <key>Comment</key> @@ -3962,6 +3962,78 @@ <key>Value</key> <string>Monospace</string> </map> + <key>LuaAutorunPath</key> + <map> + <key>Comment</key> + <string>Directories containing scripts to autorun at viewer startup</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>LLSD</string> + <key>Value</key> + <array> + <string>scripts/lua/auto</string> + </array> + </map> + <key>LuaChunk</key> + <map> + <key>Comment</key> + <string>Zero or more Lua chunks to run from command line</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>LLSD</string> + <key>Value</key> + <array /> + </map> + <key>LuaCommandPath</key> + <map> + <key>Comment</key> + <string>Directories containing scripts recognized as chat slash commands</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>LLSD</string> + <key>Value</key> + <array> + <string>scripts/lua</string> + </array> + </map> + <key>LuaFeature</key> + <map> + <key>Comment</key> + <string>Enable viewer's Lua script engine.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>LuaRequirePath</key> + <map> + <key>Comment</key> + <string>Directories containing Lua modules loadable by require()</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>LLSD</string> + <key>Value</key> + <array> + <string>scripts/lua/require</string> + </array> + </map> + <key>LuaScript</key> + <map> + <key>Comment</key> + <string>Zero or more Lua script files to run from command line</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>LLSD</string> + <key>Value</key> + <array /> + </map> <key>GridStatusRSS</key> <map> <key>Comment</key> @@ -4492,17 +4564,17 @@ <key>Value</key> <string /> </map> - <key>MarketplaceListingsLogging</key> - <map> - <key>Comment</key> - <string>Enable debug output associated with the Marketplace Listings (SLM) API.</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>0</integer> - </map> + <key>MarketplaceListingsLogging</key> + <map> + <key>Comment</key> + <string>Enable debug output associated with the Marketplace Listings (SLM) API.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>MarketplaceURL</key> <map> <key>Comment</key> @@ -4965,6 +5037,17 @@ <key>Value</key> <integer>0</integer> </map> + <key>MediaPluginPipeWireVolumeCatcher</key> + <map> + <key>Comment</key> + <string>Use PipeWire instead of PulseAudio for controlling web media volume.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>MediaControlFadeTime</key> <map> <key>Comment</key> @@ -5388,28 +5471,28 @@ <key>Value</key> <integer>1000</integer> </map> - <key>FakeInitialOutfitName</key> - <map> - <key>Comment</key> - <string>Pretend that this is first time login and specified name was chosen</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> + <key>FakeInitialOutfitName</key> + <map> + <key>Comment</key> + <string>Pretend that this is first time login and specified name was chosen</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> <string>String</string> <key>Value</key> <string /> - </map> - <key>MyOutfitsAutofill</key> - <map> - <key>Comment</key> - <string>Always autofill My Outfits from library when empty (else happens just once).</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>0</integer> - </map> + </map> + <key>MyOutfitsAutofill</key> + <map> + <key>Comment</key> + <string>Always autofill My Outfits from library when empty (else happens just once).</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>NearMeRange</key> <map> <key>Comment</key> @@ -5518,7 +5601,7 @@ <key>Type</key> <string>Boolean</string> <key>Value</key> - <integer>0</integer> + <integer>0</integer> </map> <key>NonvisibleObjectsInMemoryTime</key> <map> @@ -5529,7 +5612,7 @@ <key>Type</key> <string>U32</string> <key>Value</key> - <integer>64</integer> + <integer>64</integer> </map> <key>NoPreload</key> <map> @@ -6441,7 +6524,7 @@ <key>Value</key> <real>6.0</real> </map> - <key>PreferredMaturity</key> + <key>PreferredMaturity</key> <map> <key>Comment</key> <string>Setting for the user's preferred maturity level (consts in indra_constants.h)</string> @@ -6450,7 +6533,7 @@ <key>Type</key> <string>U32</string> <key>Value</key> - <integer>13</integer> + <integer>13</integer> </map> <key>PreviewAmbientColor</key> <map> @@ -6620,8 +6703,8 @@ </map> <key>PrimMediaMasterEnabled</key> - <map> - <key>Comment</key> + <map> + <key>Comment</key> <string>Whether or not Media on a Prim is enabled.</string> <key>Persist</key> <integer>1</integer> @@ -6630,9 +6713,9 @@ <key>Value</key> <integer>1</integer> </map> - <key>PrimMediaControlsUseHoverControlSet</key> - <map> - <key>Comment</key> + <key>PrimMediaControlsUseHoverControlSet</key> + <map> + <key>Comment</key> <string>Whether or not hovering over prim media uses minimal "hover" controls or the authored control set.</string> <key>Persist</key> <integer>1</integer> @@ -6641,17 +6724,17 @@ <key>Value</key> <integer>0</integer> </map> - <key>PrimMediaDragNDrop</key> - <map> - <key>Comment</key> - <string>Enable drag and drop of URLs onto prim faces</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>1</integer> - </map> + <key>PrimMediaDragNDrop</key> + <map> + <key>Comment</key> + <string>Enable drag and drop of URLs onto prim faces</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> <key>PrimMediaMaxRetries</key> <map> <key>Comment</key> @@ -6685,7 +6768,7 @@ <key>Value</key> <real>5.0</real> </map> - <key>PrimMediaMaxSortedQueueSize</key> + <key>PrimMediaMaxSortedQueueSize</key> <map> <key>Comment</key> <string>Maximum number of objects the viewer will load media for initially</string> @@ -6696,7 +6779,7 @@ <key>Value</key> <integer>100000</integer> </map> - <key>PrimMediaMaxRoundRobinQueueSize</key> + <key>PrimMediaMaxRoundRobinQueueSize</key> <map> <key>Comment</key> <string>Maximum number of objects the viewer will continuously update media for</string> @@ -7330,6 +7413,17 @@ <key>Value</key> <integer>-1</integer> </map> + <key>RenderBufferClearOnInvalidate</key> + <map> + <key>Comment</key> + <string>Whether to call glClear on render buffers that will be fully overwritten with new contents</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>RenderCompressTextures</key> <map> <key>Comment</key> @@ -8855,17 +8949,17 @@ <key>Value</key> <integer>1024</integer> </map> - <key>RenderHeroProbeDistance</key> - <map> - <key>Comment</key> - <string>Distance in meters for hero probes to render out to.</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>F32</string> - <key>Value</key> - <real>8</real> - </map> + <key>RenderHeroProbeDistance</key> + <map> + <key>Comment</key> + <string>Distance in meters for hero probes to render out to.</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>8</real> + </map> <key>RenderHeroProbeUpdateRate</key> <map> <key>Comment</key> @@ -9430,17 +9524,17 @@ <key>Value</key> <integer>1</integer> </map> - <key>RenderTransparentWater</key> - <map> - <key>Comment</key> - <string>Render water as transparent. Setting to false renders water as opaque with a simple texture applied.</string> + <key>RenderTransparentWater</key> + <map> + <key>Comment</key> + <string>Render water as transparent. Setting to false renders water as opaque with a simple texture applied.</string> <key>Persist</key> <integer>1</integer> <key>Type</key> <string>Boolean</string> <key>Value</key> <integer>1</integer> - </map> + </map> <key>RenderTreeLODFactor</key> <map> <key>Comment</key> @@ -9733,18 +9827,18 @@ <key>Value</key> <integer>1</integer> </map> - <key>RenderPreferStreamDraw</key> - <map> - <key>Comment</key> - <string>Use GL_STREAM_DRAW in place of GL_DYNAMIC_DRAW</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>0</integer> - </map> - <key>RenderVolumeLODFactor</key> + <key>RenderPreferStreamDraw</key> + <map> + <key>Comment</key> + <string>Use GL_STREAM_DRAW in place of GL_DYNAMIC_DRAW</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>RenderVolumeLODFactor</key> <map> <key>Comment</key> <string>Controls level of detail of primitives (multiplier for current screen area when calculated level of detail)</string> @@ -9876,17 +9970,17 @@ <key>Value</key> <integer>0</integer> </map> - <key>ReportBugURL</key> - <map> - <key>Comment</key> - <string>URL used for filing bugs from viewer</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>String</string> - <key>Value</key> - <string>https://feedback.secondlife.com/</string> - </map> + <key>ReportBugURL</key> + <map> + <key>Comment</key> + <string>URL used for filing bugs from viewer</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>String</string> + <key>Value</key> + <string>https://feedback.secondlife.com/</string> + </map> <key>RestrainedLove</key> <map> <key>Comment</key> @@ -9964,7 +10058,7 @@ <key>Value</key> <boolean>1</boolean> </map> - <key>RevokePermsOnStopAnimation</key> + <key>RevokePermsOnStopAnimation</key> <map> <key>Comment</key> <string>Clear animation permssions when choosing "Stop Animating Me"</string> @@ -10217,39 +10311,39 @@ <key>Value</key> <real>400.0</real> </map> - <key>SceneLoadingMonitorEnabled</key> - <map> - <key>Comment</key> - <string>Enabled scene loading monitor if set</string> - <key>Persist</key> - <integer>0</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>0</integer> - </map> - <key>SceneLoadingMonitorSampleTime</key> - <map> - <key>Comment</key> - <string>Time between screen samples when monitor scene load (seconds)</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>F32</string> - <key>Value</key> - <real>0.25</real> - </map> - <key>SceneLoadingMonitorPixelDiffThreshold</key> - <map> - <key>Comment</key> - <string>Amount of pixels changed required to consider the scene as still loading (square root of fraction of pixels on screen)</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>F32</string> - <key>Value</key> - <real>0.02</real> - </map> + <key>SceneLoadingMonitorEnabled</key> + <map> + <key>Comment</key> + <string>Enabled scene loading monitor if set</string> + <key>Persist</key> + <integer>0</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>SceneLoadingMonitorSampleTime</key> + <map> + <key>Comment</key> + <string>Time between screen samples when monitor scene load (seconds)</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>0.25</real> + </map> + <key>SceneLoadingMonitorPixelDiffThreshold</key> + <map> + <key>Comment</key> + <string>Amount of pixels changed required to consider the scene as still loading (square root of fraction of pixels on screen)</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>0.02</real> + </map> <key>ScriptHelpFollowsCursor</key> <map> <key>Comment</key> @@ -10426,7 +10520,7 @@ <key>Value</key> <integer>0</integer> </map> - <key>AvatarNameTagMode</key> + <key>AvatarNameTagMode</key> <map> <key>Comment</key> <string>Select Avatar Name Tag Mode</string> @@ -10635,7 +10729,7 @@ <key>Value</key> <integer>1</integer> </map> - <key>ShowScriptErrors</key> + <key>ShowScriptErrors</key> <map> <key>Comment</key> <string>Show script errors</string> @@ -10646,7 +10740,7 @@ <key>Value</key> <integer>1</integer> </map> - <key>ShowScriptErrorsLocation</key> + <key>ShowScriptErrorsLocation</key> <map> <key>Comment</key> <string>Show script error in chat (0) or window (1).</string> @@ -10905,8 +10999,8 @@ <string>Display results of find events that are flagged as moderate</string> <key>Persist</key> <integer>1</integer> - <key>HideFromEditor</key> - <integer>1</integer> + <key>HideFromEditor</key> + <integer>1</integer> <key>Type</key> <string>Boolean</string> <key>Value</key> @@ -10918,8 +11012,8 @@ <string>Display results of find events that are flagged as adult</string> <key>Persist</key> <integer>1</integer> - <key>HideFromEditor</key> - <integer>1</integer> + <key>HideFromEditor</key> + <integer>1</integer> <key>Type</key> <string>Boolean</string> <key>Value</key> @@ -10931,8 +11025,8 @@ <string>Display results of find land sales that are flagged as general</string> <key>Persist</key> <integer>1</integer> - <key>HideFromEditor</key> - <integer>1</integer> + <key>HideFromEditor</key> + <integer>1</integer> <key>Type</key> <string>Boolean</string> <key>Value</key> @@ -10944,8 +11038,8 @@ <string>Display results of find land sales that are flagged as moderate</string> <key>Persist</key> <integer>1</integer> - <key>HideFromEditor</key> - <integer>1</integer> + <key>HideFromEditor</key> + <integer>1</integer> <key>Type</key> <string>Boolean</string> <key>Value</key> @@ -10957,8 +11051,8 @@ <string>Display results of find land sales that are flagged as adult</string> <key>Persist</key> <integer>1</integer> - <key>HideFromEditor</key> - <integer>1</integer> + <key>HideFromEditor</key> + <integer>1</integer> <key>Type</key> <string>Boolean</string> <key>Value</key> @@ -10970,8 +11064,8 @@ <string>Display results of find places or find popular that are in general sims</string> <key>Persist</key> <integer>1</integer> - <key>HideFromEditor</key> - <integer>1</integer> + <key>HideFromEditor</key> + <integer>1</integer> <key>Type</key> <string>Boolean</string> <key>Value</key> @@ -10983,8 +11077,8 @@ <string>Display results of find places or find popular that are in moderate sims</string> <key>Persist</key> <integer>1</integer> - <key>HideFromEditor</key> - <integer>1</integer> + <key>HideFromEditor</key> + <integer>1</integer> <key>Type</key> <string>Boolean</string> <key>Value</key> @@ -10996,8 +11090,8 @@ <string>Display results of find places or find popular that are in adult sims</string> <key>Persist</key> <integer>1</integer> - <key>HideFromEditor</key> - <integer>1</integer> + <key>HideFromEditor</key> + <integer>1</integer> <key>Type</key> <string>Boolean</string> <key>Value</key> @@ -11135,17 +11229,17 @@ <key>Value</key> <integer>0</integer> </map> - <key>ShowTutorial</key> - <map> - <key>Comment</key> - <string>Show tutorial window on login</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>0</integer> - </map> + <key>ShowTutorial</key> + <map> + <key>Comment</key> + <string>Show tutorial window on login</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>ShowVoiceVisualizersInCalls</key> <map> <key>Comment</key> @@ -13796,17 +13890,17 @@ <key>Value</key> <real>0.40000000596</real> </map> - <key>moapbeacon</key> - <map> - <key>Comment</key> - <string>Beacon / Highlight media on a prim sources</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>0</integer> - </map> + <key>moapbeacon</key> + <map> + <key>Comment</key> + <string>Beacon / Highlight media on a prim sources</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>particlesbeacon</key> <map> <key>Comment</key> @@ -13906,17 +14000,17 @@ <key>Value</key> <integer>0</integer> </map> - <key>SLURLDragNDrop</key> - <map> - <key>Comment</key> - <string>Enable drag and drop of SLURLs onto the viewer</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>Boolean</string> - <key>Value</key> - <integer>1</integer> - </map> + <key>SLURLDragNDrop</key> + <map> + <key>Comment</key> + <string>Enable drag and drop of SLURLs onto the viewer</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>1</integer> + </map> <key>SLURLPassToOtherInstance</key> <map> <key>Comment</key> @@ -14104,10 +14198,10 @@ <string>LLSD</string> <key>Value</key> <array> - <string>snapshot</string> - <string>postcard</string> - <string>mini_map</string> - <string>beacons</string> + <string>snapshot</string> + <string>postcard</string> + <string>mini_map</string> + <string>beacons</string> </array> </map> <key>LandmarksSortedByDate</key> @@ -14868,7 +14962,7 @@ <key>Value</key> <integer>0</integer> </map> - <key>LocalTerrainAsset1</key> + <key>LocalTerrainAsset1</key> <map> <key>Comment</key> <string>If set to a non-null UUID, overrides the terrain asset locally for all regions with material assets. Local terrain assets are not visible to others. Please keep in mind that this debug setting may be temporary. Do not rely on this setting existing in future viewer builds.</string> @@ -14879,7 +14973,7 @@ <key>Value</key> <string>00000000-0000-0000-0000-000000000000</string> </map> - <key>LocalTerrainAsset2</key> + <key>LocalTerrainAsset2</key> <map> <key>Comment</key> <string>If set to a non-null UUID, overrides the terrain asset locally for all regions with material assets. Local terrain assets are not visible to others. Please keep in mind that this debug setting may be temporary. Do not rely on this setting existing in future viewer builds.</string> @@ -14890,7 +14984,7 @@ <key>Value</key> <string>00000000-0000-0000-0000-000000000000</string> </map> - <key>LocalTerrainAsset3</key> + <key>LocalTerrainAsset3</key> <map> <key>Comment</key> <string>If set to a non-null UUID, overrides the terrain asset locally for all regions with material assets. Local terrain assets are not visible to others. Please keep in mind that this debug setting may be temporary. Do not rely on this setting existing in future viewer builds.</string> @@ -14901,7 +14995,7 @@ <key>Value</key> <string>00000000-0000-0000-0000-000000000000</string> </map> - <key>LocalTerrainAsset4</key> + <key>LocalTerrainAsset4</key> <map> <key>Comment</key> <string>If set to a non-null UUID, overrides the terrain asset locally for all regions with material assets. Local terrain assets are not visible to others. Please keep in mind that this debug setting may be temporary. Do not rely on this setting existing in future viewer builds.</string> @@ -15165,7 +15259,7 @@ <key>Value</key> <integer>2048</integer> </map> - <key>PathfindingRetrieveNeighboringRegion</key> + <key>PathfindingRetrieveNeighboringRegion</key> <map> <key>Comment</key> <string>Download a neighboring region when visualizing a pathfinding navmesh (default val 99 means do not download neighbors).</string> @@ -15174,9 +15268,9 @@ <key>Type</key> <string>U32</string> <key>Value</key> - <integer>99</integer> + <integer>99</integer> </map> - <key>PathfindingNavMeshClear</key> + <key>PathfindingNavMeshClear</key> <map> <key>Comment</key> <string>Background color when displaying pathfinding navmesh.</string> @@ -15336,7 +15430,7 @@ <real>1.0</real> </array> </map> - <key>PathfindingTestPathValidEndColor</key> + <key>PathfindingTestPathValidEndColor</key> <map> <key>Comment</key> <string>Color of the pathfinding test-pathing tool end-point when the path is valid.</string> @@ -15368,7 +15462,7 @@ <real>1.0</real> </array> </map> - <key>PathfindingTestPathColor</key> + <key>PathfindingTestPathColor</key> <map> <key>Comment</key> <string>Color of the pathfinding test-path when the path is valid.</string> @@ -16016,17 +16110,17 @@ <key>Value</key> <real>300</real> </map> - <key>StatsReportFileInterval</key> - <map> - <key>Comment</key> - <string>Interval to save viewer stats file data</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>F32</string> - <key>Value</key> - <real>0.2</real> - </map> + <key>StatsReportFileInterval</key> + <map> + <key>Comment</key> + <string>Interval to save viewer stats file data</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>0.2</real> + </map> <key>StatsReportSkipZeroDataSaves</key> <map> <key>Comment</key> diff --git a/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl b/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl index 210ecce8db..e9f849a8c0 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/alphaF.glsl @@ -79,7 +79,7 @@ float getAmbientClamp(); void mirrorClip(vec3 pos); -void sampleReflectionProbesLegacy(inout vec3 ambenv, inout vec3 glossenv, inout vec3 legacyenv, +void sampleReflectionProbesLegacy(out vec3 ambenv, out vec3 glossenv, out vec3 legacyenv, vec2 tc, vec3 pos, vec3 norm, float glossiness, float envIntensity, bool transparent, vec3 amblit_linear); vec3 calcPointLightOrSpotLight(vec3 light_col, vec3 diffuse, vec3 v, vec3 n, vec4 lp, vec3 ln, float la, float fa, float is_pointlight, float ambiance) diff --git a/indra/newview/app_settings/shaders/class2/deferred/reflectionProbeF.glsl b/indra/newview/app_settings/shaders/class2/deferred/reflectionProbeF.glsl index d178bf22b6..4fb24ca0b1 100644 --- a/indra/newview/app_settings/shaders/class2/deferred/reflectionProbeF.glsl +++ b/indra/newview/app_settings/shaders/class2/deferred/reflectionProbeF.glsl @@ -55,7 +55,7 @@ vec4 sampleReflectionProbesDebug(vec3 pos) return vec4(0, 0, 0, 0); } -void sampleReflectionProbesLegacy(inout vec3 ambenv, inout vec3 glossenv, inout vec3 legacyenv, +void sampleReflectionProbesLegacy(out vec3 ambenv, out vec3 glossenv, out vec3 legacyenv, vec2 tc, vec3 pos, vec3 norm, float glossiness, float envIntensity, bool transparent, vec3 amblit_linear) { ambenv = vec3(reflection_probe_ambiance * 0.25); diff --git a/indra/newview/app_settings/shaders/class3/deferred/fullbrightShinyF.glsl b/indra/newview/app_settings/shaders/class3/deferred/fullbrightShinyF.glsl index 03dc3d7113..5e38864d38 100644 --- a/indra/newview/app_settings/shaders/class3/deferred/fullbrightShinyF.glsl +++ b/indra/newview/app_settings/shaders/class3/deferred/fullbrightShinyF.glsl @@ -48,7 +48,7 @@ vec3 linear_to_srgb(vec3 c); vec3 srgb_to_linear(vec3 c); // reflection probe interface -void sampleReflectionProbesLegacy(inout vec3 ambenv, inout vec3 glossenv, inout vec3 legacyenv, +void sampleReflectionProbesLegacy(out vec3 ambenv, out vec3 glossenv, out vec3 legacyenv, vec2 tc, vec3 pos, vec3 norm, float glossiness, float envIntensity, bool transparent, vec3 amblit_linear); void applyLegacyEnv(inout vec3 color, vec3 legacyenv, vec4 spec, vec3 pos, vec3 norm, float envIntensity); diff --git a/indra/newview/app_settings/shaders/class3/deferred/materialF.glsl b/indra/newview/app_settings/shaders/class3/deferred/materialF.glsl index 26ab0406f6..5ee9aea09d 100644 --- a/indra/newview/app_settings/shaders/class3/deferred/materialF.glsl +++ b/indra/newview/app_settings/shaders/class3/deferred/materialF.glsl @@ -60,7 +60,7 @@ out vec4 frag_color; float sampleDirectionalShadow(vec3 pos, vec3 norm, vec2 pos_screen); #endif -void sampleReflectionProbesLegacy(inout vec3 ambenv, inout vec3 glossenv, inout vec3 legacyenv, +void sampleReflectionProbesLegacy(out vec3 ambenv, out vec3 glossenv, out vec3 legacyenv, vec2 tc, vec3 pos, vec3 norm, float glossiness, float envIntensity, bool transparent, vec3 amblit_linear); void applyGlossEnv(inout vec3 color, vec3 glossenv, vec4 spec, vec3 pos, vec3 norm); void applyLegacyEnv(inout vec3 color, vec3 legacyenv, vec4 spec, vec3 pos, vec3 norm, float envIntensity); @@ -280,10 +280,10 @@ float getShadow(vec3 pos, vec3 norm) #if (DIFFUSE_ALPHA_MODE == DIFFUSE_ALPHA_MODE_BLEND) return sampleDirectionalShadow(pos, norm, vary_texcoord0.xy); #else - return 1; + return 1.; #endif #else - return 1; + return 1.; #endif } diff --git a/indra/newview/app_settings/shaders/class3/deferred/reflectionProbeF.glsl b/indra/newview/app_settings/shaders/class3/deferred/reflectionProbeF.glsl index 5dfa196cf6..4bae7b6deb 100644 --- a/indra/newview/app_settings/shaders/class3/deferred/reflectionProbeF.glsl +++ b/indra/newview/app_settings/shaders/class3/deferred/reflectionProbeF.glsl @@ -837,7 +837,7 @@ vec4 sampleReflectionProbesDebug(vec3 pos) return col; } -void sampleReflectionProbesLegacy(inout vec3 ambenv, inout vec3 glossenv, inout vec3 legacyenv, +void sampleReflectionProbesLegacy(out vec3 ambenv, out vec3 glossenv, out vec3 legacyenv, vec2 tc, vec3 pos, vec3 norm, float glossiness, float envIntensity, bool transparent, vec3 amblit) { float reflection_lods = max_probe_lod; diff --git a/indra/newview/app_settings/shaders/class3/deferred/softenLightF.glsl b/indra/newview/app_settings/shaders/class3/deferred/softenLightF.glsl index ca88fe7482..4231d8580e 100644 --- a/indra/newview/app_settings/shaders/class3/deferred/softenLightF.glsl +++ b/indra/newview/app_settings/shaders/class3/deferred/softenLightF.glsl @@ -69,7 +69,7 @@ vec3 scaleSoftClipFragLinear(vec3 l); // reflection probe interface void sampleReflectionProbes(inout vec3 ambenv, inout vec3 glossenv, vec2 tc, vec3 pos, vec3 norm, float glossiness, bool transparent, vec3 amblit_linear); -void sampleReflectionProbesLegacy(inout vec3 ambenv, inout vec3 glossenv, inout vec3 legacyenv, +void sampleReflectionProbesLegacy(out vec3 ambenv, out vec3 glossenv, out vec3 legacyenv, vec2 tc, vec3 pos, vec3 norm, float glossiness, float envIntensity, bool transparent, vec3 amblit_linear); void applyGlossEnv(inout vec3 color, vec3 glossenv, vec4 spec, vec3 pos, vec3 norm); void applyLegacyEnv(inout vec3 color, vec3 legacyenv, vec4 spec, vec3 pos, vec3 norm, float envIntensity); diff --git a/indra/newview/featuretable_mac.txt b/indra/newview/featuretable_mac.txt index 2e220d2b1c..06ad730a40 100644 --- a/indra/newview/featuretable_mac.txt +++ b/indra/newview/featuretable_mac.txt @@ -1,4 +1,4 @@ -version 60 +version 61 // The version number above should be incremented IF AND ONLY IF some // change has been made that is sufficiently important to justify // resetting the graphics preferences of all users to the recommended @@ -68,7 +68,8 @@ RenderFSAASamples 1 3 RenderMaxTextureIndex 1 16 RenderGLContextCoreProfile 1 1 RenderGLMultiThreadedTextures 1 0 -RenderGLMultiThreadedMedia 1 0 +RenderGLMultiThreadedMedia 1 1 +RenderAppleUseMultGL 1 1 RenderReflectionsEnabled 1 1 RenderReflectionProbeDetail 1 2 RenderScreenSpaceReflections 1 1 @@ -381,6 +382,15 @@ list Intel RenderAnisotropic 1 0 RenderFSAASamples 1 0 +// AppleGPU and NonAppleGPU can be thought of as Apple silicon vs Intel Mac +list AppleGPU +RenderGLMultiThreadedMedia 1 1 +RenderAppleUseMultGL 1 1 + +list NonAppleGPU +RenderGLMultiThreadedMedia 1 0 +RenderAppleUseMultGL 1 0 + list GL3 RenderFSAASamples 0 0 RenderReflectionProbeDetail 0 0 diff --git a/indra/newview/gltfscenemanager.cpp b/indra/newview/gltfscenemanager.cpp index ce54fa4c12..ed66753267 100644 --- a/indra/newview/gltfscenemanager.cpp +++ b/indra/newview/gltfscenemanager.cpp @@ -371,43 +371,46 @@ void GLTFSceneManager::addGLTFObject(LLViewerObject* obj, LLUUID gltf_id) //static void GLTFSceneManager::onGLTFBinLoadComplete(const LLUUID& id, LLAssetType::EType asset_type, void* user_data, S32 status, LLExtStat ext_status) { - LLViewerObject* obj = (LLViewerObject*)user_data; - llassert(asset_type == LLAssetType::AT_GLTF_BIN); - - if (status == LL_ERR_NOERR) - { - if (obj) + LLAppViewer::instance()->postToMainCoro([=]() { - // find the Buffer with the given id in the asset - if (obj->mGLTFAsset) - { - obj->mGLTFAsset->mPendingBuffers--; - + LLViewerObject* obj = (LLViewerObject*)user_data; + llassert(asset_type == LLAssetType::AT_GLTF_BIN); - if (obj->mGLTFAsset->mPendingBuffers == 0) + if (status == LL_ERR_NOERR) + { + if (obj) { - if (obj->mGLTFAsset->prep()) + // find the Buffer with the given id in the asset + if (obj->mGLTFAsset) { - GLTFSceneManager& mgr = GLTFSceneManager::instance(); - if (std::find(mgr.mObjects.begin(), mgr.mObjects.end(), obj) == mgr.mObjects.end()) + obj->mGLTFAsset->mPendingBuffers--; + + + if (obj->mGLTFAsset->mPendingBuffers == 0) { - GLTFSceneManager::instance().mObjects.push_back(obj); + if (obj->mGLTFAsset->prep()) + { + GLTFSceneManager& mgr = GLTFSceneManager::instance(); + if (std::find(mgr.mObjects.begin(), mgr.mObjects.end(), obj) == mgr.mObjects.end()) + { + GLTFSceneManager::instance().mObjects.push_back(obj); + } + } + else + { + LL_WARNS("GLTF") << "Failed to prepare GLTF asset: " << id << LL_ENDL; + obj->mGLTFAsset = nullptr; + } } } - else - { - LL_WARNS("GLTF") << "Failed to prepare GLTF asset: " << id << LL_ENDL; - obj->mGLTFAsset = nullptr; - } } } - } - } - else - { - LL_WARNS("GLTF") << "Failed to load GLTF asset: " << id << LL_ENDL; - obj->unref(); - } + else + { + LL_WARNS("GLTF") << "Failed to load GLTF asset: " << id << LL_ENDL; + obj->unref(); + } + }); } //static diff --git a/indra/newview/groupchatlistener.cpp b/indra/newview/groupchatlistener.cpp index 43507f13e9..e48cbe824a 100644 --- a/indra/newview/groupchatlistener.cpp +++ b/indra/newview/groupchatlistener.cpp @@ -2,11 +2,11 @@ * @file groupchatlistener.cpp * @author Nat Goodspeed * @date 2011-04-11 - * @brief Implementation for groupchatlistener. + * @brief Implementation for LLGroupChatListener. * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. + * Copyright (C) 2024, 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 @@ -34,43 +34,82 @@ // std headers // external library headers // other Linden headers +#include "llchat.h" #include "llgroupactions.h" #include "llimview.h" +static const F64 GROUP_CHAT_THROTTLE_PERIOD = 1.f; -namespace { - void startIm_wrapper(LLSD const & event) +LLGroupChatListener::LLGroupChatListener(): + LLEventAPI("GroupChat", + "API to enter, leave, send and intercept group chat messages"), + mIMThrottle("sendGroupIM", &LLGroupChatListener::sendGroupIM_, this, + GROUP_CHAT_THROTTLE_PERIOD) +{ + add("startGroupChat", + "Enter a group chat in group with UUID [\"group_id\"]\n" + "Assumes the logged-in agent is already a member of this group.", + &LLGroupChatListener::startGroupChat, + llsd::map("group_id", LLSD())); + add("leaveGroupChat", + "Leave a group chat in group with UUID [\"group_id\"]\n" + "Assumes a prior successful startIM request.", + &LLGroupChatListener::leaveGroupChat, + llsd::map("group_id", LLSD())); + add("sendGroupIM", + "send a [\"message\"] to group with UUID [\"group_id\"]", + &LLGroupChatListener::sendGroupIM, + llsd::map("message", LLSD(), "group_id", LLSD())); +} + +bool is_in_group(LLEventAPI::Response &response, const LLSD &data) +{ + if (!LLGroupActions::isInGroup(data["group_id"])) { - LLUUID session_id = LLGroupActions::startIM(event["id"].asUUID()); - sendReply(LLSDMap("session_id", LLSD(session_id)), event); + response.error(stringize("You are not the member of the group:", std::quoted(data["group_id"].asString()))); + return false; } + return true; +} - void send_message_wrapper(const std::string& text, const LLUUID& session_id, const LLUUID& group_id) +void LLGroupChatListener::startGroupChat(LLSD const &data) +{ + Response response(LLSD(), data); + if (!is_in_group(response, data)) + { + return; + } + if (LLGroupActions::startIM(data["group_id"]).isNull()) { - LLIMModel::sendMessage(text, session_id, group_id, IM_SESSION_GROUP_START); + return response.error(stringize("Failed to start group chat session ", std::quoted(data["group_id"].asString()))); } } +void LLGroupChatListener::leaveGroupChat(LLSD const &data) +{ + Response response(LLSD(), data); + if (is_in_group(response, data)) + { + LLGroupActions::endIM(data["group_id"].asUUID()); + } +} -GroupChatListener::GroupChatListener(): - LLEventAPI("GroupChat", - "API to enter, leave, send and intercept group chat messages") +void LLGroupChatListener::sendGroupIM(LLSD const &data) { - add("startIM", - "Enter a group chat in group with UUID [\"id\"]\n" - "Assumes the logged-in agent is already a member of this group.", - &startIm_wrapper); - add("endIM", - "Leave a group chat in group with UUID [\"id\"]\n" - "Assumes a prior successful startIM request.", - &LLGroupActions::endIM, - llsd::array("id")); - add("sendIM", - "send a groupchat IM", - &send_message_wrapper, - llsd::array("text", "session_id", "group_id")); + Response response(LLSD(), data); + if (!is_in_group(response, data)) + { + return; + } + + mIMThrottle(data["group_id"], data["message"]); +} + +void LLGroupChatListener::sendGroupIM_(const LLUUID& group_id, const std::string& message) +{ + LLIMModel::sendMessage(LUA_PREFIX + message, + gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id), + group_id, + IM_SESSION_SEND); } -/* - static void sendMessage(const std::string& utf8_text, const LLUUID& im_session_id, - const LLUUID& other_participant_id, EInstantMessage dialog); -*/ + diff --git a/indra/newview/groupchatlistener.h b/indra/newview/groupchatlistener.h index 3819ac59b7..a75fecb254 100644 --- a/indra/newview/groupchatlistener.h +++ b/indra/newview/groupchatlistener.h @@ -4,9 +4,9 @@ * @date 2011-04-11 * @brief * - * $LicenseInfo:firstyear=2011&license=viewerlgpl$ + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2011, Linden Research, Inc. + * Copyright (C) 2024, 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 @@ -26,15 +26,24 @@ * $/LicenseInfo$ */ -#if ! defined(LL_GROUPCHATLISTENER_H) -#define LL_GROUPCHATLISTENER_H +#if ! defined(LL_LLGROUPCHATLISTENER_H) +#define LL_LLGROUPCHATLISTENER_H #include "lleventapi.h" +#include "throttle.h" -class GroupChatListener: public LLEventAPI +class LLGroupChatListener: public LLEventAPI { public: - GroupChatListener(); + LLGroupChatListener(); + +private: + void startGroupChat(LLSD const &data); + void leaveGroupChat(LLSD const &data); + void sendGroupIM(LLSD const &data); + void sendGroupIM_(const LLUUID& group_id, const std::string& message); + + LogThrottle<LLError::LEVEL_DEBUG, void(const LLUUID&, const std::string&)> mIMThrottle; }; -#endif /* ! defined(LL_GROUPCHATLISTENER_H) */ +#endif /* ! defined(LL_LLGROUPCHATLISTENER_H) */ diff --git a/indra/newview/linux_tools/wrapper.sh b/indra/newview/linux_tools/wrapper.sh index eb3ead433b..a027aaf6d1 100755 --- a/indra/newview/linux_tools/wrapper.sh +++ b/indra/newview/linux_tools/wrapper.sh @@ -58,21 +58,14 @@ fi ## - Avoids an often-buggy X feature that doesn't really benefit us anyway. export SDL_VIDEO_X11_DGAMOUSE=0 -## - Works around a problem with misconfigured 64-bit systems not finding GL -I386_MULTIARCH="$(dpkg-architecture -ai386 -qDEB_HOST_MULTIARCH 2>/dev/null)" -MULTIARCH_ERR=$? -if [ $MULTIARCH_ERR -eq 0 ]; then - echo 'Multi-arch support detected.' - MULTIARCH_GL_DRIVERS="/usr/lib/${I386_MULTIARCH}/dri" - export LIBGL_DRIVERS_PATH="${LIBGL_DRIVERS_PATH}:${MULTIARCH_GL_DRIVERS}:/usr/lib64/dri:/usr/lib32/dri:/usr/lib/dri" -else - export LIBGL_DRIVERS_PATH="${LIBGL_DRIVERS_PATH}:/usr/lib64/dri:/usr/lib32/dri:/usr/lib/dri" -fi - ## - The 'scim' GTK IM module widely crashes the viewer. Avoid it. if [ "$GTK_IM_MODULE" = "scim" ]; then export GTK_IM_MODULE=xim fi +if [ "$XMODIFIERS" = "" ]; then + ## IME is valid only for fcitx, not when using ibus + export XMODIFIERS="@im=fcitx" +fi ## - Automatically work around the ATI mouse cursor crash bug: ## (this workaround is disabled as most fglrx users do not see the bug) @@ -98,25 +91,6 @@ cd "${RUN_PATH}" ## Before we mess with LD_LIBRARY_PATH, save the old one to restore for ## subprocesses that care. export SAVED_LD_LIBRARY_PATH="${LD_LIBRARY_PATH}" - -# if [ -n "$LL_TCMALLOC" ]; then -# tcmalloc_libs='/usr/lib/libtcmalloc.so.0 /usr/lib/libstacktrace.so.0 /lib/libpthread.so.0' -# all=1 -# for f in $tcmalloc_libs; do -# if [ ! -f $f ]; then -# all=0 -# fi -# done -# if [ $all != 1 ]; then -# echo 'Cannot use tcmalloc libraries: components missing' 1>&2 -# else -# export LD_PRELOAD=$(echo $tcmalloc_libs | tr ' ' :) -# if [ -z "$HEAPCHECK" -a -z "$HEAPPROFILE" ]; then -# export HEAPCHECK=${HEAPCHECK:-normal} -# fi -# fi -#fi - export LD_LIBRARY_PATH="$PWD/lib:${LD_LIBRARY_PATH}" # Copy "$@" to ARGS array specifically to delete the --skip-gridargs switch. @@ -140,18 +114,6 @@ LL_RUN_ERR=$? if [ $LL_RUN_ERR -ne 0 ]; then # generic error running the binary echo '*** Bad shutdown ($LL_RUN_ERR). ***' - if [ "$(uname -m)" = "x86_64" ]; then - echo - cat << EOFMARKER -You are running the Second Life Viewer on a x86_64 platform. The -most common problems when launching the Viewer (particularly -'bin/do-not-directly-run-secondlife-bin: not found' and 'error while -loading shared libraries') may be solved by installing your Linux -distribution's 32-bit compatibility packages. -For example, on Ubuntu and other Debian-based Linuxes you might run: -$ sudo apt-get install ia32-libs ia32-libs-gtk ia32-libs-kde ia32-libs-sdl -EOFMARKER - fi fi echo diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 81e79a2ed9..d4767e18af 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -1988,16 +1988,6 @@ LLVector3d LLAgentCamera::calcCameraPositionTargetGlobal(bool *hit_limit) isConstrained = true; } } - -// JC - Could constrain camera based on parcel stuff here. -// LLViewerRegion *regionp = LLWorld::getInstance()->getRegionFromPosGlobal(camera_position_global); -// -// if (regionp && !regionp->mParcelOverlay->isBuildCameraAllowed(regionp->getPosRegionFromGlobal(camera_position_global))) -// { -// camera_position_global = last_position_global; -// -// isConstrained = true; -// } } // Don't let camera go underground diff --git a/indra/newview/llagentlistener.cpp b/indra/newview/llagentlistener.cpp index 0c120ae01d..14e443ec4e 100644 --- a/indra/newview/llagentlistener.cpp +++ b/indra/newview/llagentlistener.cpp @@ -31,23 +31,30 @@ #include "llagentlistener.h" #include "llagent.h" +#include "llagentcamera.h" #include "llvoavatar.h" #include "llcommandhandler.h" +#include "llinventorymodel.h" #include "llslurl.h" #include "llurldispatcher.h" #include "llviewernetwork.h" #include "llviewerobject.h" #include "llviewerobjectlist.h" #include "llviewerregion.h" +#include "llvoavatarself.h" #include "llsdutil.h" #include "llsdutil_math.h" #include "lltoolgrab.h" #include "llhudeffectlookat.h" #include "llagentcamera.h" +#include <functional> + +static const F64 PLAY_ANIM_THROTTLE_PERIOD = 1.f; LLAgentListener::LLAgentListener(LLAgent &agent) : LLEventAPI("LLAgent", "LLAgent listener to (e.g.) teleport, sit, stand, etc."), + mPlayAnimThrottle("playAnimation", &LLAgentListener::playAnimation_, this, PLAY_ANIM_THROTTLE_PERIOD), mAgent(agent) { add("requestTeleport", @@ -69,13 +76,6 @@ LLAgentListener::LLAgentListener(LLAgent &agent) add("resetAxes", "Set the agent to a fixed orientation (optionally specify [\"lookat\"] = array of [x, y, z])", &LLAgentListener::resetAxes); - add("getAxes", - "Obsolete - use getPosition instead\n" - "Send information about the agent's orientation on [\"reply\"]:\n" - "[\"euler\"]: map of {roll, pitch, yaw}\n" - "[\"quat\"]: array of [x, y, z, w] quaternion values", - &LLAgentListener::getAxes, - LLSDMap("reply", LLSD())); add("getPosition", "Send information about the agent's position and orientation on [\"reply\"]:\n" "[\"region\"]: array of region {x, y, z} position\n" @@ -87,33 +87,34 @@ LLAgentListener::LLAgentListener(LLAgent &agent) add("startAutoPilot", "Start the autopilot system using the following parameters:\n" "[\"target_global\"]: array of target global {x, y, z} position\n" - "[\"stop_distance\"]: target maxiumum distance from target [default: autopilot guess]\n" + "[\"stop_distance\"]: maximum stop distance from target [default: autopilot guess]\n" "[\"target_rotation\"]: array of [x, y, z, w] quaternion values [default: no target]\n" "[\"rotation_threshold\"]: target maximum angle from target facing rotation [default: 0.03 radians]\n" - "[\"behavior_name\"]: name of the autopilot behavior [default: \"\"]" - "[\"allow_flying\"]: allow flying during autopilot [default: True]", - //"[\"callback_pump\"]: pump to send success/failure and callback data to [default: none]\n" - //"[\"callback_data\"]: data to send back during a callback [default: none]", - &LLAgentListener::startAutoPilot); + "[\"behavior_name\"]: name of the autopilot behavior [default: \"\"]\n" + "[\"allow_flying\"]: allow flying during autopilot [default: True]\n" + "event with [\"success\"] flag is sent to 'LLAutopilot' event pump, when auto pilot is terminated", + &LLAgentListener::startAutoPilot, + llsd::map("target_global", LLSD())); add("getAutoPilot", "Send information about current state of the autopilot system to [\"reply\"]:\n" "[\"enabled\"]: boolean indicating whether or not autopilot is enabled\n" "[\"target_global\"]: array of target global {x, y, z} position\n" "[\"leader_id\"]: uuid of target autopilot is following\n" - "[\"stop_distance\"]: target maximum distance from target\n" + "[\"stop_distance\"]: maximum stop distance from target\n" "[\"target_distance\"]: last known distance from target\n" "[\"use_rotation\"]: boolean indicating if autopilot has a target facing rotation\n" "[\"target_facing\"]: array of {x, y} target direction to face\n" "[\"rotation_threshold\"]: target maximum angle from target facing rotation\n" "[\"behavior_name\"]: name of the autopilot behavior", &LLAgentListener::getAutoPilot, - LLSDMap("reply", LLSD())); + llsd::map("reply", LLSD())); add("startFollowPilot", "[\"leader_id\"]: uuid of target to follow using the autopilot system (optional with avatar_name)\n" "[\"avatar_name\"]: avatar name to follow using the autopilot system (optional with leader_id)\n" "[\"allow_flying\"]: allow flying during autopilot [default: True]\n" - "[\"stop_distance\"]: target maxiumum distance from target [default: autopilot guess]", - &LLAgentListener::startFollowPilot); + "[\"stop_distance\"]: maximum stop distance from target [default: autopilot guess]", + &LLAgentListener::startFollowPilot, + llsd::map("reply", LLSD())); add("setAutoPilotTarget", "Update target for currently running autopilot:\n" "[\"target_global\"]: array of target global {x, y, z} position", @@ -138,6 +139,43 @@ LLAgentListener::LLAgentListener(LLAgent &agent) "[\"contrib\"]: user's land contribution to this group\n", &LLAgentListener::getGroups, LLSDMap("reply", LLSD())); + //camera params are similar to LSL, see https://wiki.secondlife.com/wiki/LlSetCameraParams + add("setCameraParams", + "Set Follow camera params, and then activate it:\n" + "[\"camera_pos\"]: vector3, camera position in region coordinates\n" + "[\"focus_pos\"]: vector3, what the camera is aimed at (in region coordinates)\n" + "[\"focus_offset\"]: vector3, adjusts the camera focus position relative to the target, default is (1, 0, 0)\n" + "[\"distance\"]: float (meters), distance the camera wants to be from its target, default is 3\n" + "[\"focus_threshold\"]: float (meters), sets the radius of a sphere around the camera's target position within which its focus is not affected by target motion, default is 1\n" + "[\"camera_threshold\"]: float (meters), sets the radius of a sphere around the camera's ideal position within which it is not affected by target motion, default is 1\n" + "[\"focus_lag\"]: float (seconds), how much the camera lags as it tries to aim towards the target, default is 0.1\n" + "[\"camera_lag\"]: float (seconds), how much the camera lags as it tries to move towards its 'ideal' position, default is 0.1\n" + "[\"camera_pitch\"]: float (degrees), adjusts the angular amount that the camera aims straight ahead vs. straight down, maintaining the same distance, default is 0\n" + "[\"behindness_angle\"]: float (degrees), sets the angle in degrees within which the camera is not constrained by changes in target rotation, default is 10\n" + "[\"behindness_lag\"]: float (seconds), sets how strongly the camera is forced to stay behind the target if outside of behindness angle, default is 0\n" + "[\"camera_locked\"]: bool, locks the camera position so it will not move\n" + "[\"focus_locked\"]: bool, locks the camera focus so it will not move", + &LLAgentListener::setFollowCamParams); + add("setFollowCamActive", + "Turns on or off scripted control of the camera using boolean [\"active\"]", + &LLAgentListener::setFollowCamActive, + llsd::map("active", LLSD())); + add("removeCameraParams", + "Reset Follow camera params", + &LLAgentListener::removeFollowCamParams); + + add("playAnimation", + "Play [\"item_id\"] animation locally (by default) or [\"inworld\"] (when set to true)", + &LLAgentListener::playAnimation, + llsd::map("item_id", LLSD(), "reply", LLSD())); + add("stopAnimation", + "Stop playing [\"item_id\"] animation", + &LLAgentListener::stopAnimation, + llsd::map("item_id", LLSD(), "reply", LLSD())); + add("getAnimationInfo", + "Return information about [\"item_id\"] animation", + &LLAgentListener::getAnimationInfo, + llsd::map("item_id", LLSD(), "reply", LLSD())); } void LLAgentListener::requestTeleport(LLSD const & event_data) const @@ -168,7 +206,7 @@ void LLAgentListener::requestSit(LLSD const & event_data) const //mAgent.getAvatarObject()->sitOnObject(); // shamelessly ripped from llviewermenu.cpp:handle_sit_or_stand() // *TODO - find a permanent place to share this code properly. - + Response response(LLSD(), event_data); LLViewerObject *object = NULL; if (event_data.has("obj_uuid")) { @@ -177,7 +215,13 @@ void LLAgentListener::requestSit(LLSD const & event_data) const else if (event_data.has("position")) { LLVector3 target_position = ll_vector3_from_sd(event_data["position"]); - object = findObjectClosestTo(target_position); + object = findObjectClosestTo(target_position, true); + } + else + { + //just sit on the ground + mAgent.setControlFlags(AGENT_CONTROL_SIT_ON_GROUND); + return; } if (object && object->getPCode() == LL_PCODE_VOLUME) @@ -194,8 +238,7 @@ void LLAgentListener::requestSit(LLSD const & event_data) const } else { - LL_WARNS() << "LLAgent requestSit could not find the sit target: " - << event_data << LL_ENDL; + response.error("requestSit could not find the sit target"); } } @@ -205,7 +248,7 @@ void LLAgentListener::requestStand(LLSD const & event_data) const } -LLViewerObject * LLAgentListener::findObjectClosestTo( const LLVector3 & position ) const +LLViewerObject * LLAgentListener::findObjectClosestTo(const LLVector3 & position, bool sit_target) const { LLViewerObject *object = NULL; @@ -216,8 +259,13 @@ LLViewerObject * LLAgentListener::findObjectClosestTo( const LLVector3 & positio while (cur_index < num_objects) { LLViewerObject * cur_object = gObjectList.getObject(cur_index++); - if (cur_object) - { // Calculate distance from the target position + if (cur_object && !cur_object->isAttachment()) + { + if(sit_target && (cur_object->getPCode() != LL_PCODE_VOLUME)) + { + continue; + } + // Calculate distance from the target position LLVector3 target_diff = cur_object->getPositionRegion() - position; F32 distance_to_target = target_diff.length(); if (distance_to_target < min_distance) @@ -296,22 +344,6 @@ void LLAgentListener::resetAxes(const LLSD& event_data) const } } -void LLAgentListener::getAxes(const LLSD& event_data) const -{ - LLQuaternion quat(mAgent.getQuat()); - F32 roll, pitch, yaw; - quat.getEulerAngles(&roll, &pitch, &yaw); - // The official query API for LLQuaternion's [x, y, z, w] values is its - // public member mQ... - LLSD reply = LLSD::emptyMap(); - reply["quat"] = llsd_copy_array(boost::begin(quat.mQ), boost::end(quat.mQ)); - reply["euler"] = LLSD::emptyMap(); - reply["euler"]["roll"] = roll; - reply["euler"]["pitch"] = pitch; - reply["euler"]["yaw"] = yaw; - sendReply(reply, event_data); -} - void LLAgentListener::getPosition(const LLSD& event_data) const { F32 roll, pitch, yaw; @@ -333,14 +365,13 @@ void LLAgentListener::getPosition(const LLSD& event_data) const void LLAgentListener::startAutoPilot(LLSD const & event_data) { - LLQuaternion target_rotation_value; LLQuaternion* target_rotation = NULL; if (event_data.has("target_rotation")) { - target_rotation_value = ll_quaternion_from_sd(event_data["target_rotation"]); + LLQuaternion target_rotation_value = ll_quaternion_from_sd(event_data["target_rotation"]); target_rotation = &target_rotation_value; } - // *TODO: Use callback_pump and callback_data + F32 rotation_threshold = 0.03f; if (event_data.has("rotation_threshold")) { @@ -360,13 +391,24 @@ void LLAgentListener::startAutoPilot(LLSD const & event_data) stop_distance = (F32)event_data["stop_distance"].asReal(); } + std::string behavior_name = LLCoros::getName(); + if (event_data.has("behavior_name")) + { + behavior_name = event_data["behavior_name"].asString(); + } + // Clear follow target, this is doing a path mFollowTarget.setNull(); + auto finish_cb = [](bool success, void*) + { + LLEventPumps::instance().obtain("LLAutopilot").post(llsd::map("success", success)); + }; + mAgent.startAutoPilotGlobal(ll_vector3d_from_sd(event_data["target_global"]), - event_data["behavior_name"], + behavior_name, target_rotation, - NULL, NULL, + finish_cb, NULL, stop_distance, rotation_threshold, allow_flying); @@ -374,7 +416,7 @@ void LLAgentListener::startAutoPilot(LLSD const & event_data) void LLAgentListener::getAutoPilot(const LLSD& event_data) const { - LLSD reply = LLSD::emptyMap(); + Response reply(LLSD(), event_data); LLSD::Boolean enabled = mAgent.getAutoPilot(); reply["enabled"] = enabled; @@ -403,12 +445,11 @@ void LLAgentListener::getAutoPilot(const LLSD& event_data) const reply["rotation_threshold"] = mAgent.getAutoPilotRotationThreshold(); reply["behavior_name"] = mAgent.getAutoPilotBehaviorName(); reply["fly"] = (LLSD::Boolean) mAgent.getFlying(); - - sendReply(reply, event_data); } void LLAgentListener::startFollowPilot(LLSD const & event_data) { + Response response(LLSD(), event_data); LLUUID target_id; bool allow_flying = true; @@ -442,6 +483,10 @@ void LLAgentListener::startFollowPilot(LLSD const & event_data) } } } + else + { + return response.error("'leader_id' or 'avatar_name' should be specified"); + } F32 stop_distance = 0.f; if (event_data.has("stop_distance")) @@ -449,13 +494,16 @@ void LLAgentListener::startFollowPilot(LLSD const & event_data) stop_distance = (F32)event_data["stop_distance"].asReal(); } - if (target_id.notNull()) + if (!gObjectList.findObject(target_id)) { - mAgent.setFlying(allow_flying); - mFollowTarget = target_id; // Save follow target so we can report distance later - - mAgent.startFollowPilot(target_id, allow_flying, stop_distance); + std::string target_info = event_data.has("leader_id") ? event_data["leader_id"] : event_data["avatar_name"]; + return response.error(stringize("Target ", std::quoted(target_info), " was not found")); } + + mAgent.setFlying(allow_flying); + mFollowTarget = target_id; // Save follow target so we can report distance later + + mAgent.startFollowPilot(target_id, allow_flying, stop_distance); } void LLAgentListener::setAutoPilotTarget(LLSD const & event_data) const @@ -519,3 +567,129 @@ void LLAgentListener::getGroups(const LLSD& event) const } sendReply(LLSDMap("groups", reply), event); } + +/*----------------------------- camera control -----------------------------*/ +// specialize LLSDParam to support (const LLVector3&) arguments -- this +// wouldn't even be necessary except that the relevant LLVector3 constructor +// is explicitly explicit +template <> +class LLSDParam<const LLVector3&>: public LLSDParamBase +{ +public: + LLSDParam(const LLSD& value): value(LLVector3(value)) {} + + operator const LLVector3&() const { return value; } + +private: + LLVector3 value; +}; + +// accept any of a number of similar LLFollowCamMgr methods with different +// argument types, and return a wrapper lambda that accepts LLSD and converts +// to the target argument type +template <typename T> +auto wrap(void (LLFollowCamMgr::*method)(const LLUUID& source, T arg)) +{ + return [method](LLFollowCamMgr& followcam, const LLUUID& source, const LLSD& arg) + { (followcam.*method)(source, LLSDParam<T>(arg)); }; +} + +// table of supported LLFollowCamMgr methods, +// with the corresponding setFollowCamParams() argument keys +static std::pair<std::string, std::function<void(LLFollowCamMgr&, const LLUUID&, const LLSD&)>> +cam_params[] = +{ + { "camera_pos", wrap(&LLFollowCamMgr::setPosition) }, + { "focus_pos", wrap(&LLFollowCamMgr::setFocus) }, + { "focus_offset", wrap(&LLFollowCamMgr::setFocusOffset) }, + { "camera_locked", wrap(&LLFollowCamMgr::setPositionLocked) }, + { "focus_locked", wrap(&LLFollowCamMgr::setFocusLocked) }, + { "distance", wrap(&LLFollowCamMgr::setDistance) }, + { "focus_threshold", wrap(&LLFollowCamMgr::setFocusThreshold) }, + { "camera_threshold", wrap(&LLFollowCamMgr::setPositionThreshold) }, + { "focus_lag", wrap(&LLFollowCamMgr::setFocusLag) }, + { "camera_lag", wrap(&LLFollowCamMgr::setPositionLag) }, + { "camera_pitch", wrap(&LLFollowCamMgr::setPitch) }, + { "behindness_lag", wrap(&LLFollowCamMgr::setBehindnessLag) }, + { "behindness_angle", wrap(&LLFollowCamMgr::setBehindnessAngle) }, +}; + +void LLAgentListener::setFollowCamParams(const LLSD& event) const +{ + auto& followcam{ LLFollowCamMgr::instance() }; + for (const auto& pair : cam_params) + { + if (event.has(pair.first)) + { + pair.second(followcam, gAgentID, event[pair.first]); + } + } + followcam.setCameraActive(gAgentID, true); +} + +void LLAgentListener::setFollowCamActive(LLSD const & event) const +{ + LLFollowCamMgr::getInstance()->setCameraActive(gAgentID, event["active"]); +} + +void LLAgentListener::removeFollowCamParams(LLSD const & event) const +{ + LLFollowCamMgr::getInstance()->removeFollowCamParams(gAgentID); +} + +LLViewerInventoryItem* get_anim_item(LLEventAPI::Response &response, const LLSD &event_data) +{ + LLViewerInventoryItem* item = gInventory.getItem(event_data["item_id"].asUUID()); + if (!item || (item->getInventoryType() != LLInventoryType::IT_ANIMATION)) + { + response.error(stringize("Animation item ", std::quoted(event_data["item_id"].asString()), " was not found")); + return NULL; + } + return item; +} + +void LLAgentListener::playAnimation(LLSD const &event_data) +{ + Response response(LLSD(), event_data); + if (LLViewerInventoryItem* item = get_anim_item(response, event_data)) + { + mPlayAnimThrottle(item->getAssetUUID(), event_data["inworld"].asBoolean()); + } +} + +void LLAgentListener::playAnimation_(const LLUUID& asset_id, const bool inworld) +{ + if (inworld) + { + mAgent.sendAnimationRequest(asset_id, ANIM_REQUEST_START); + } + else + { + gAgentAvatarp->startMotion(asset_id); + } +} + +void LLAgentListener::stopAnimation(LLSD const &event_data) +{ + Response response(LLSD(), event_data); + if (LLViewerInventoryItem* item = get_anim_item(response, event_data)) + { + gAgentAvatarp->stopMotion(item->getAssetUUID()); + mAgent.sendAnimationRequest(item->getAssetUUID(), ANIM_REQUEST_STOP); + } +} + +void LLAgentListener::getAnimationInfo(LLSD const &event_data) +{ + Response response(LLSD(), event_data); + if (LLViewerInventoryItem* item = get_anim_item(response, event_data)) + { + // if motion exists, will return existing one + LLMotion* motion = gAgentAvatarp->createMotion(item->getAssetUUID()); + response["anim_info"] = llsd::map("duration", motion->getDuration(), + "is_loop", motion->getLoop(), + "num_joints", motion->getNumJointMotions(), + "asset_id", item->getAssetUUID(), + "priority", motion->getPriority()); + } +} diff --git a/indra/newview/llagentlistener.h b/indra/newview/llagentlistener.h index c544d089ce..05724ff443 100644 --- a/indra/newview/llagentlistener.h +++ b/indra/newview/llagentlistener.h @@ -31,6 +31,7 @@ #define LL_LLAGENTLISTENER_H #include "lleventapi.h" +#include "throttle.h" class LLAgent; class LLSD; @@ -48,7 +49,6 @@ private: void requestStand(LLSD const & event_data) const; void requestTouch(LLSD const & event_data) const; void resetAxes(const LLSD& event_data) const; - void getAxes(const LLSD& event_data) const; void getGroups(const LLSD& event) const; void getPosition(const LLSD& event_data) const; void startAutoPilot(const LLSD& event_data); @@ -58,11 +58,22 @@ private: void stopAutoPilot(const LLSD& event_data) const; void lookAt(LLSD const & event_data) const; - LLViewerObject * findObjectClosestTo( const LLVector3 & position ) const; + void setFollowCamParams(LLSD const & event_data) const; + void setFollowCamActive(LLSD const & event_data) const; + void removeFollowCamParams(LLSD const & event_data) const; + + void playAnimation(LLSD const &event_data); + void playAnimation_(const LLUUID& asset_id, const bool inworld); + void stopAnimation(LLSD const &event_data); + void getAnimationInfo(LLSD const &event_data); + + LLViewerObject * findObjectClosestTo( const LLVector3 & position, bool sit_target = false ) const; private: LLAgent & mAgent; LLUUID mFollowTarget; + + LogThrottle<LLError::LEVEL_DEBUG, void(const LLUUID &, const bool)> mPlayAnimThrottle; }; #endif // LL_LLAGENTLISTENER_H diff --git a/indra/newview/llaisapi.cpp b/indra/newview/llaisapi.cpp index ce4e8e9392..11c5ffecb6 100644 --- a/indra/newview/llaisapi.cpp +++ b/indra/newview/llaisapi.cpp @@ -1385,8 +1385,6 @@ void AISUpdate::parseCategory(const LLSD& category_map, S32 depth) && curr_cat->getVersion() > LLViewerInventoryCategory::VERSION_UNKNOWN && version > curr_cat->getVersion()) { - // Potentially should new_cat->setVersion(unknown) here, - // but might be waiting for a callback that would increment LL_DEBUGS("Inventory") << "Category " << category_id << " is stale. Known version: " << curr_cat->getVersion() << " server version: " << version << LL_ENDL; diff --git a/indra/newview/llappearancelistener.cpp b/indra/newview/llappearancelistener.cpp new file mode 100644 index 0000000000..0dab352311 --- /dev/null +++ b/indra/newview/llappearancelistener.cpp @@ -0,0 +1,146 @@ +/** + * @file llappearancelistener.cpp + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, 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 "llappearancelistener.h" + +#include "llappearancemgr.h" +#include "llinventoryfunctions.h" +#include "lltransutil.h" +#include "llwearableitemslist.h" +#include "stringize.h" + +LLAppearanceListener::LLAppearanceListener() + : LLEventAPI("LLAppearance", + "API to wear a specified outfit and wear/remove individual items") +{ + add("wearOutfit", + "Wear outfit by folder id: [\"folder_id\"] OR by folder name: [\"folder_name\"]\n" + "When [\"append\"] is true, outfit will be added to COF\n" + "otherwise it will replace current oufit", + &LLAppearanceListener::wearOutfit); + + add("wearItems", + "Wear items by id: [items_id]", + &LLAppearanceListener::wearItems, + llsd::map("items_id", LLSD(), "replace", LLSD())); + + add("detachItems", + "Detach items by id: [items_id]", + &LLAppearanceListener::detachItems, + llsd::map("items_id", LLSD())); + + add("getOutfitsList", + "Return the table with Outfits info(id and name)", + &LLAppearanceListener::getOutfitsList); + + add("getOutfitItems", + "Return the table of items with info(id : name, wearable_type, is_worn) inside specified outfit folder", + &LLAppearanceListener::getOutfitItems); +} + + +void LLAppearanceListener::wearOutfit(LLSD const &data) +{ + Response response(LLSD(), data); + if (!data.has("folder_id") && !data.has("folder_name")) + { + return response.error("Either [folder_id] or [folder_name] is required"); + } + + std::string error_msg; + bool result(false); + bool append = data.has("append") ? data["append"].asBoolean() : false; + if (data.has("folder_id")) + { + result = LLAppearanceMgr::instance().wearOutfit(data["folder_id"].asUUID(), error_msg, append); + } + else + { + result = LLAppearanceMgr::instance().wearOutfitByName(data["folder_name"].asString(), error_msg, append); + } + + if (!result) + { + response.error(error_msg); + } +} + +void LLAppearanceListener::wearItems(LLSD const &data) +{ + LLAppearanceMgr::instance().wearItemsOnAvatar( + LLSDParam<uuid_vec_t>(data["items_id"]), + true, data["replace"].asBoolean()); +} + +void LLAppearanceListener::detachItems(LLSD const &data) +{ + LLAppearanceMgr::instance().removeItemsFromAvatar( + LLSDParam<uuid_vec_t>(data["items_id"])); +} + +void LLAppearanceListener::getOutfitsList(LLSD const &data) +{ + Response response(LLSD(), data); + const LLUUID outfits_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + + LLIsType is_category(LLAssetType::AT_CATEGORY); + gInventory.collectDescendentsIf(outfits_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, is_category); + + response["outfits"] = llsd::toMap(cat_array, + [](const LLPointer<LLViewerInventoryCategory> &cat) + { return std::make_pair(cat->getUUID().asString(), cat->getName()); }); +} + +void LLAppearanceListener::getOutfitItems(LLSD const &data) +{ + Response response(LLSD(), data); + LLUUID outfit_id(data["outfit_id"].asUUID()); + LLViewerInventoryCategory *cat = gInventory.getCategory(outfit_id); + if (!cat || cat->getPreferredType() != LLFolderType::FT_OUTFIT) + { + return response.error(stringize(LLTrans::getString("OutfitNotFound"), outfit_id.asString())); + } + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t item_array; + + LLFindOutfitItems collector = LLFindOutfitItems(); + gInventory.collectDescendentsIf(outfit_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, collector); + + response["items"] = llsd::toMap(item_array, + [](const LLPointer<LLViewerInventoryItem> &it) + { + return std::make_pair( + it->getUUID().asString(), + llsd::map( + "name", it->getName(), + "wearable_type", LLWearableType::getInstance()->getTypeName(it->isWearableType() ? it->getWearableType() : LLWearableType::WT_NONE), + "is_worn", get_is_item_worn(it))); + }); +} diff --git a/indra/llcommon/llerrorlegacy.h b/indra/newview/llappearancelistener.h index 693e1501d5..04c5eac2eb 100644 --- a/indra/llcommon/llerrorlegacy.h +++ b/indra/newview/llappearancelistener.h @@ -1,11 +1,9 @@ /** - * @file llerrorlegacy.h - * @date January 2007 - * @brief old things from the older error system + * @file llappearancelistener.h * - * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2024, 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 @@ -25,8 +23,24 @@ * $/LicenseInfo$ */ -#ifndef LL_LLERRORLEGACY_H -#define LL_LLERRORLEGACY_H +#ifndef LL_LLAPPEARANCELISTENER_H +#define LL_LLAPPEARANCELISTENER_H + +#include "lleventapi.h" + +class LLAppearanceListener : public LLEventAPI +{ +public: + LLAppearanceListener(); + +private: + void wearOutfit(LLSD const &data); + void wearItems(LLSD const &data); + void detachItems(LLSD const &data); + void getOutfitsList(LLSD const &data); + void getOutfitItems(LLSD const &data); +}; + +#endif // LL_LLAPPEARANCELISTENER_H -#endif // LL_LLERRORLEGACY_H diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 946d674e8b..2877bb7c49 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -31,6 +31,7 @@ #include "llagent.h" #include "llagentcamera.h" #include "llagentwearables.h" +#include "llappearancelistener.h" #include "llappearancemgr.h" #include "llattachmentsmgr.h" #include "llcommandhandler.h" @@ -48,6 +49,7 @@ #include "lloutfitslist.h" #include "llselectmgr.h" #include "llsidepanelappearance.h" +#include "lltransutil.h" #include "llviewerobjectlist.h" #include "llvoavatar.h" #include "llvoavatarself.h" @@ -66,6 +68,8 @@ #include "llavatarpropertiesprocessor.h" +LLAppearanceListener sAppearanceListener; + namespace { const S32 BAKE_RETRY_MAX_COUNT = 5; @@ -113,26 +117,16 @@ public: LLOutfitUnLockTimer(F32 period) : LLEventTimer(period) { // restart timer on BOF changed event - LLOutfitObserver::instance().addBOFChangedCallback(boost::bind( - &LLOutfitUnLockTimer::reset, this)); + LLOutfitObserver::instance().addBOFChangedCallback([this]{ start(); }); stop(); } - /*virtual*/ - bool tick() + bool tick() override { - if(mEventTimer.hasExpired()) - { - LLAppearanceMgr::instance().setOutfitLocked(false); - } + LLAppearanceMgr::instance().setOutfitLocked(false); return false; } - void stop() { mEventTimer.stop(); } - void start() { mEventTimer.start(); } - void reset() { mEventTimer.reset(); } - bool getStarted() { return mEventTimer.getStarted(); } - - LLTimer& getEventTimer() { return mEventTimer;} + bool getStarted() { return isRunning(); } }; // support for secondlife:///app/appearance SLapps @@ -327,7 +321,7 @@ public: // virtual // Will be deleted after returning true - only safe to do this if all callbacks have fired. - bool tick() + bool tick() override { // mPendingRequests will be zero if all requests have been // responded to. mWaitTimes.empty() will be true if we have @@ -620,8 +614,8 @@ void LLBrokenLinkObserver::changed(U32 mask) if (id == mUUID) { // Might not be processed yet and it is not a - // good idea to update appearane here, postpone. - doOnIdleOneTime([this]() + // good idea to update appearance here, postpone. + doOnIdleOneTime([this] { postProcess(); }); @@ -1707,7 +1701,6 @@ void LLAppearanceMgr::setOutfitLocked(bool locked) mOutfitLocked = locked; if (locked) { - mUnlockOutfitTimer->reset(); mUnlockOutfitTimer->start(); } else @@ -2906,8 +2899,18 @@ void LLAppearanceMgr::wearInventoryCategoryOnAvatar( LLInventoryCategory* catego LLAppearanceMgr::changeOutfit(true, category->getUUID(), append); } -// FIXME do we really want to search entire inventory for matching name? -void LLAppearanceMgr::wearOutfitByName(const std::string& name) +bool LLAppearanceMgr::wearOutfitByName(const std::string& name, bool append) +{ + std::string error_msg; + if(!wearOutfitByName(name, error_msg, append)) + { + LL_WARNS() << error_msg << LL_ENDL; + return false; + } + return true; +} + +bool LLAppearanceMgr::wearOutfitByName(const std::string& name, std::string& error_msg, bool append) { LL_INFOS("Avatar") << self_av_string() << "Wearing category " << name << LL_ENDL; @@ -2940,15 +2943,38 @@ void LLAppearanceMgr::wearOutfitByName(const std::string& name) } } - if(cat) + return wearOutfit(stringize(std::quoted(name)), cat, error_msg, copy_items, append); +} + +bool LLAppearanceMgr::wearOutfit(const LLUUID &cat_id, std::string &error_msg, bool append) +{ + LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id); + return wearOutfit(stringize(cat_id), cat, error_msg, false, append); +} + +bool LLAppearanceMgr::wearOutfit(const std::string &desc, LLInventoryCategory* cat, + std::string &error_msg, bool copy_items, bool append) +{ + if (!cat) + { + error_msg = stringize(LLTrans::getString("OutfitNotFound"), desc); + return false; + } + // don't allow wearing a system folder + if (LLFolderType::lookupIsProtectedType(cat->getPreferredType())) { - LLAppearanceMgr::wearInventoryCategory(cat, copy_items, false); + error_msg = stringize(LLTrans::getString("SystemFolderNotWorn"), std::quoted(cat->getName())); + return false; } - else + bool can_wear = append ? getCanAddToCOF(cat->getUUID()) : getCanReplaceCOF(cat->getUUID()); + if (!can_wear) { - LL_WARNS() << "Couldn't find outfit " <<name<< " in wearOutfitByName()" - << LL_ENDL; + std::string msg = append ? LLTrans::getString("OutfitNotAdded") : LLTrans::getString("OutfitNotReplaced"); + error_msg = stringize(msg, std::quoted(cat->getName()), " , id: ", cat->getUUID()); + return false; } + wearInventoryCategory(cat, copy_items, append); + return true; } bool areMatchingWearables(const LLViewerInventoryItem *a, const LLViewerInventoryItem *b) diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index 6c45a32856..9e624f593f 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -59,7 +59,9 @@ public: void wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append); void wearInventoryCategoryOnAvatar(LLInventoryCategory* category, bool append); void wearCategoryFinal(const LLUUID& cat_id, bool copy_items, bool append); - void wearOutfitByName(const std::string& name); + bool wearOutfit(const LLUUID &cat_id, std::string &error_msg, bool append = false); + bool wearOutfitByName(const std::string &name, std::string &error_msg, bool append = false); + bool wearOutfitByName(const std::string &name, bool append = false); void changeOutfit(bool proceed, const LLUUID& category, bool append); void replaceCurrentOutfit(const LLUUID& new_outfit); void renameOutfit(const LLUUID& outfit_id); @@ -261,6 +263,10 @@ private: static void onOutfitRename(const LLSD& notification, const LLSD& response); + // used by both wearOutfit(LLUUID) and wearOutfitByName(std::string) + bool wearOutfit(const std::string &desc, LLInventoryCategory* cat, + std::string &error_msg, bool copy_items, bool append); + bool mAttachmentInvLinkEnabled; bool mOutfitIsDirty; bool mIsInUpdateAppearanceFromCOF; // to detect recursive calls. diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 3c748b7b06..291d549f28 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -29,6 +29,7 @@ #include "llappviewer.h" // Viewer includes +#include "coro_scheduler.h" #include "llversioninfo.h" #include "llfeaturemanager.h" #include "lluictrlfactory.h" @@ -60,6 +61,7 @@ #include "llslurl.h" #include "llstartup.h" #include "llfocusmgr.h" +#include "llluamanager.h" #include "llurlfloaterdispatchhandler.h" #include "llviewerjoystick.h" #include "llcalc.h" @@ -111,10 +113,12 @@ #include "llgltfmateriallist.h" // Linden library includes +#include "fsyspath.h" #include "llavatarnamecache.h" #include "lldiriterator.h" #include "llexperiencecache.h" #include "llimagej2c.h" +#include "llluamanager.h" #include "llmemory.h" #include "llprimitive.h" #include "llurlaction.h" @@ -180,7 +184,6 @@ #include "lltracker.h" #include "llviewerparcelmgr.h" #include "llworldmapview.h" -#include "llpostprocess.h" #include "lldebugview.h" #include "llconsole.h" @@ -384,6 +387,9 @@ 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"; +void processComposeSwitch(const std::string&, const std::string&, + const std::function<void(const LLSD&)>&); + //---------------------------------------------------------------------------- // List of entries from strings.xml to always replace @@ -759,6 +765,8 @@ bool LLAppViewer::init() //set the max heap size. initMaxHeapSize() ; LLCoros::instance().setStackSize(gSavedSettings.getS32("CoroutineStackSize")); + // Use our custom scheduler for coroutine scheduling. + llcoro::scheduler::use(); // Although initLoggingAndGetLastDuration() is the right place to mess with // setFatalFunction(), we can't query gSavedSettings until after @@ -1141,7 +1149,7 @@ bool LLAppViewer::init() gGLActive = false; -#if LL_RELEASE_FOR_DOWNLOAD +#if LL_RELEASE_FOR_DOWNLOAD && !LL_LINUX // Skip updater if this is a non-interactive instance if (!gSavedSettings.getBOOL("CmdLineSkipUpdater") && !gNonInteractive) { @@ -1204,22 +1212,10 @@ bool LLAppViewer::init() } #endif //LL_RELEASE_FOR_DOWNLOAD - { - // Iterate over --leap command-line options. But this is a bit tricky: if - // there's only one, it won't be an array at all. - LLSD LeapCommand(gSavedSettings.getLLSD("LeapCommand")); - LL_DEBUGS("InitInfo") << "LeapCommand: " << LeapCommand << LL_ENDL; - if (LeapCommand.isDefined() && !LeapCommand.isArray()) - { - // If LeapCommand is actually a scalar value, make an array of it. - // Have to do it in two steps because LeapCommand.append(LeapCommand) - // trashes content! :-P - LLSD item(LeapCommand); - LeapCommand.append(item); - } - for (const auto& leap : llsd::inArray(LeapCommand)) + processComposeSwitch( + "--leap", "LeapCommand", + [](const LLSD& leap) { - LL_INFOS("InitInfo") << "processing --leap \"" << leap << '"' << LL_ENDL; // We don't have any better description of this plugin than the // user-specified command line. Passing "" causes LLLeap to derive a // description from the command line itself. @@ -1227,8 +1223,40 @@ bool LLAppViewer::init() // don't consider any one --leap command mission-critical, so if one // fails, log it, shrug and carry on. LLLeap::create("", leap, false); // exception=false - } - } + }); + processComposeSwitch( + "--lua", "LuaChunk", + [](const LLSD& chunk) + { + // no completion callback: we don't need to know + LLLUAmanager::runScriptLine(chunk); + }); + processComposeSwitch( + "--luafile", "LuaScript", + [](const LLSD& script) + { + // no completion callback: we don't need to know + LLLUAmanager::runScriptFile(script); + }); + processComposeSwitch( + "LuaAutorunPath", "LuaAutorunPath", + [](const LLSD& directory) + { + // each directory can be relative to the viewer's install + // directory -- if directory is already absolute, operator/() + // preserves it + fsyspath abspath(fsyspath(gDirUtilp->getAppRODataDir()) / + fsyspath(directory.asString())); + std::string absdir(fsyspath(abspath).string()); + LL_DEBUGS("InitInfo") << "LuaAutorunPath: " << absdir << LL_ENDL; + LLDirIterator scripts(absdir, "*.lua"); + std::string script; + while (scripts.next(script)) + { + LL_DEBUGS("InitInfo") << "LuaAutorunPath: " << absdir << ": " << script << LL_ENDL; + LLLUAmanager::runScriptFile(fsyspath(abspath / fsyspath(script)).string(), true); + } + }); if (gSavedSettings.getBOOL("QAMode") && gSavedSettings.getS32("QAModeEventHostPort") > 0) { @@ -1304,6 +1332,27 @@ bool LLAppViewer::init() return true; } +void processComposeSwitch(const std::string& option, + const std::string& setting, + const std::function<void(const LLSD&)>& action) +{ + // Iterate over 'option' command-line options. But this is a bit tricky: + // if there's only one, it won't be an array at all. + LLSD args(gSavedSettings.getLLSD(setting)); + LL_DEBUGS("InitInfo") << option << ": " << args << LL_ENDL; + if (args.isDefined() && ! args.isArray()) + { + // If args is actually a scalar value, make an array of it. Have to do + // it in two steps because args.append(args) trashes content! :-P + args.append(LLSD(args)); + } + for (const auto& arg : llsd::inArray(args)) + { + LL_INFOS("InitInfo") << "processing " << option << ' ' << arg << LL_ENDL; + action(arg); + } +} + void LLAppViewer::initMaxHeapSize() { //set the max heap size. @@ -1929,8 +1978,6 @@ bool LLAppViewer::cleanup() SUBSYSTEM_CLEANUP(LLAvatarAppearance); - SUBSYSTEM_CLEANUP(LLPostProcess); - LLTracker::cleanupInstance(); // *FIX: This is handled in LLAppViewerWin32::cleanup(). @@ -2379,6 +2426,14 @@ void LLAppViewer::initLoggingAndGetLastDuration() { LL_WARNS("MarkerFile") << duration_log_msg << LL_ENDL; } + + std::string user_data_path_cef_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "cef.log"); + if (gDirUtilp->fileExists(user_data_path_cef_log)) + { + std::string user_data_path_cef_old = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "cef.old"); + LLFile::remove(user_data_path_cef_old, ENOENT); + LLFile::rename(user_data_path_cef_log, user_data_path_cef_old); + } } } @@ -3005,9 +3060,10 @@ void LLAppViewer::initStrings() std::string strings_path_full = gDirUtilp->findSkinnedFilenameBaseLang(LLDir::XUI, strings_file); if (strings_path_full.empty() || !LLFile::isfile(strings_path_full)) { + std::string crash_reason; if (strings_path_full.empty()) { - LL_WARNS() << "The file '" << strings_file << "' is not found" << LL_ENDL; + crash_reason = "The file '" + strings_file + "' is not found"; } else { @@ -3015,24 +3071,23 @@ void LLAppViewer::initStrings() int rc = LLFile::stat(strings_path_full, &st); if (rc != 0) { - LL_WARNS() << "The file '" << strings_path_full << "' failed to get status. Error code: " << rc << LL_ENDL; + crash_reason = "The file '" + strings_path_full + "' failed to get status. Error code: " + std::to_string(rc); } else if (S_ISDIR(st.st_mode)) { - LL_WARNS() << "The filename '" << strings_path_full << "' is a directory name" << LL_ENDL; + crash_reason = "The filename '" + strings_path_full + "' is a directory name"; } else { - LL_WARNS() << "The filename '" << strings_path_full << "' doesn't seem to be a regular file name" << LL_ENDL; + crash_reason = "The filename '" + strings_path_full + "' doesn't seem to be a regular file name"; } } // initial check to make sure files are there failed gDirUtilp->dumpCurrentDirectories(LLError::LEVEL_WARN); LLError::LLUserWarningMsg::showMissingFiles(); - LL_ERRS() << "Viewer failed to find localization and UI files." - << " Please reinstall viewer from https://secondlife.com/support/downloads" - << " and contact https://support.secondlife.com if issue persists after reinstall." << LL_ENDL; + LL_ERRS() << "Viewer failed to open some of localization and UI files." + << " " << crash_reason << "." << LL_ENDL; } LLTransUtil::parseStrings(strings_file, default_trans_args); LLTransUtil::parseLanguageStrings("language_settings.xml"); @@ -3292,10 +3347,10 @@ LLSD LLAppViewer::getViewerInfo() const LLVector3d pos = gAgent.getPositionGlobal(); info["POSITION"] = ll_sd_from_vector3d(pos); info["POSITION_LOCAL"] = ll_sd_from_vector3(gAgent.getPosAgentFromGlobal(pos)); - info["REGION"] = gAgent.getRegion()->getName(); + info["REGION"] = region->getName(); boost::regex regex("\\.(secondlife|lindenlab)\\..*"); - info["HOSTNAME"] = boost::regex_replace(gAgent.getRegion()->getSimHostName(), regex, ""); + info["HOSTNAME"] = boost::regex_replace(region->getSimHostName(), regex, ""); info["SERVER_VERSION"] = gLastVersionChannel; LLSLURL slurl; LLAgentUI::buildSLURL(slurl); @@ -4613,7 +4668,6 @@ void LLAppViewer::idle() LLFrameTimer::updateFrameTime(); LLFrameTimer::updateFrameCount(); - LLEventTimer::updateClass(); LLPerfStats::updateClass(); // LLApp::stepFrame() performs the above three calls plus mRunner.run(). @@ -5549,9 +5603,9 @@ void LLAppViewer::forceErrorThreadCrash() thread->start(); } -void LLAppViewer::initMainloopTimeout(const std::string& state, F32 secs) +void LLAppViewer::initMainloopTimeout(std::string_view state, F32 secs) { - if(!mMainloopTimeout) + if (!mMainloopTimeout) { mMainloopTimeout = new LLWatchdogTimeout(); resumeMainloopTimeout(state, secs); @@ -5560,20 +5614,20 @@ void LLAppViewer::initMainloopTimeout(const std::string& state, F32 secs) void LLAppViewer::destroyMainloopTimeout() { - if(mMainloopTimeout) + if (mMainloopTimeout) { delete mMainloopTimeout; - mMainloopTimeout = NULL; + mMainloopTimeout = nullptr; } } -void LLAppViewer::resumeMainloopTimeout(const std::string& state, F32 secs) +void LLAppViewer::resumeMainloopTimeout(std::string_view state, F32 secs) { - if(mMainloopTimeout) + if (mMainloopTimeout) { - if(secs < 0.0f) + if (secs < 0.0f) { - static LLCachedControl<F32> mainloop_timeout(gSavedSettings, "MainloopTimeoutDefault", 60); + static LLCachedControl<F32> mainloop_timeout(gSavedSettings, "MainloopTimeoutDefault", 60.f); secs = mainloop_timeout; } @@ -5584,19 +5638,19 @@ void LLAppViewer::resumeMainloopTimeout(const std::string& state, F32 secs) void LLAppViewer::pauseMainloopTimeout() { - if(mMainloopTimeout) + if (mMainloopTimeout) { mMainloopTimeout->stop(); } } -void LLAppViewer::pingMainloopTimeout(const std::string& state, F32 secs) +void LLAppViewer::pingMainloopTimeout(std::string_view state, F32 secs) { LL_PROFILE_ZONE_SCOPED_CATEGORY_APP; - if(mMainloopTimeout) + if (mMainloopTimeout) { - if(secs < 0.0f) + if (secs < 0.0f) { static LLCachedControl<F32> mainloop_timeout(gSavedSettings, "MainloopTimeoutDefault", 60); secs = mainloop_timeout; @@ -5723,4 +5777,3 @@ void LLAppViewer::metricsSend(bool enable_reporting) // resolution in time. gViewerAssetStats->restart(); } - diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index 4ce4259ed8..6e02869fba 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -183,11 +183,11 @@ public: // For thread debugging. // llstartup needs to control init. // llworld, send_agent_pause() also controls pause/resume. - void initMainloopTimeout(const std::string& state, F32 secs = -1.0f); + void initMainloopTimeout(std::string_view state, F32 secs = -1.0f); void destroyMainloopTimeout(); void pauseMainloopTimeout(); - void resumeMainloopTimeout(const std::string& state = "", F32 secs = -1.0f); - void pingMainloopTimeout(const std::string& state, F32 secs = -1.0f); + void resumeMainloopTimeout(std::string_view state = "", F32 secs = -1.0f); + void pingMainloopTimeout(std::string_view state, F32 secs = -1.0f); // Handle the 'login completed' event. // *NOTE:Mani Fix this for login abstraction!! diff --git a/indra/newview/llappviewerlinux.cpp b/indra/newview/llappviewerlinux.cpp index 1709970156..38f2f1ae7f 100644 --- a/indra/newview/llappviewerlinux.cpp +++ b/indra/newview/llappviewerlinux.cpp @@ -40,17 +40,56 @@ #include <exception> -#if LL_DBUS_ENABLED -# include "llappviewerlinux_api_dbus.h" - -// regrettable hacks to give us better runtime compatibility with older systems inside llappviewerlinux_api.h: -#define llg_return_if_fail(COND) do{if (!(COND)) return;}while(0) -#undef g_return_if_fail -#define g_return_if_fail(COND) llg_return_if_fail(COND) -// The generated API -# include "llappviewerlinux_api.h" +#include <gio/gio.h> +#include <resolv.h> + +#if (__GLIBC__*1000 + __GLIBC_MINOR__) >= 2034 +extern "C" +{ + int __res_nquery(res_state statep, + const char *dname, int qclass, int type, + unsigned char *answer, int anslen) + { + return res_nquery( statep, dname, qclass, type, answer, anslen ); + } + + int __dn_expand(const unsigned char *msg, + const unsigned char *eomorig, + const unsigned char *comp_dn, char *exp_dn, + int length) + { + return dn_expand( msg,eomorig,comp_dn,exp_dn,length); + } +} #endif +#if LL_SEND_CRASH_REPORTS +#include "breakpad/client/linux/handler/exception_handler.h" +#include "breakpad/common/linux/http_upload.h" +#include "lldir.h" +#include "../llcrashlogger/llcrashlogger.h" +#include "boost/json.hpp" +#endif + +#define VIEWERAPI_SERVICE "com.secondlife.ViewerAppAPIService" +#define VIEWERAPI_PATH "/com/secondlife/ViewerAppAPI" +#define VIEWERAPI_INTERFACE "com.secondlife.ViewerAppAPI" + +static const char * DBUS_SERVER = "<node name=\"/com/secondlife/ViewerAppAPI\">\n" + " <interface name=\"com.secondlife.ViewerAppAPI\">\n" + " <annotation name=\"org.freedesktop.DBus.GLib.CSymbol\" value=\"viewer_app_api\"/>\n" + " <method name=\"GoSLURL\">\n" + " <annotation name=\"org.freedesktop.DBus.GLib.CSymbol\" value=\"dispatchSLURL\"/>\n" + " <arg type=\"s\" name=\"slurl\" direction=\"in\" />\n" + " </method>\n" + " </interface>\n" + "</node>"; + +typedef struct +{ + GObject parent; +} ViewerAppAPI; + namespace { int gArgC = 0; @@ -81,6 +120,8 @@ int main( int argc, char **argv ) // install unexpected exception handler gOldTerminateHandler = std::set_terminate(exceptionTerminateHandler); + unsetenv( "LD_PRELOAD" ); // <FS:ND/> Get rid of any preloading, we do not want this to happen during startup of plugins. + bool ok = viewer_app_ptr->init(); if(!ok) { @@ -114,21 +155,76 @@ LLAppViewerLinux::~LLAppViewerLinux() { } -bool LLAppViewerLinux::init() +#if LL_SEND_CRASH_REPORTS +std::string gCrashLogger; +std::string gVersion; +std::string gBugsplatDB; +std::string gCrashBehavior; + +static bool dumpCallback(const google_breakpad::MinidumpDescriptor& descriptor, void* context, bool succeeded) { - // g_thread_init() must be called before *any* use of glib, *and* - // before any mutexes are held, *and* some of our third-party - // libraries likes to use glib functions; in short, do this here - // really early in app startup! - if (!g_thread_supported ()) g_thread_init (NULL); + if( fork() == 0 ) + execl( gCrashLogger.c_str(), gCrashLogger.c_str(), descriptor.path(), gVersion.c_str(), gBugsplatDB.c_str(), gCrashBehavior.c_str(), nullptr ); + return succeeded; +} +void setupBreadpad() +{ + std::string build_data_fname(gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "build_data.json")); + gCrashLogger = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "linux-crash-logger.bin"); + + llifstream inf(build_data_fname.c_str()); + if(!inf.is_open()) + { + LL_WARNS("BUGSPLAT") << "Can't initialize BugSplat, can't read '" << build_data_fname << "'" << LL_ENDL; + return; + } + + boost::json::error_code ec; + boost::json::value build_data = boost::json::parse(inf, ec); + if(ec.failed()) + { + LL_WARNS("BUGSPLAT") << "Can't initialize BugSplat, can't parse '" << build_data_fname << "': " + << ec.what() << LL_ENDL; + return; + } + + if (!build_data.is_object() || !build_data.as_object().contains("BugSplat DB")) + { + LL_WARNS("BUGSPLAT") << "Can't initialize BugSplat, no 'BugSplat DB' entry in '" << build_data_fname + << "'" << LL_ENDL; + return; + } + + gVersion = STRINGIZE( + LL_VIEWER_VERSION_MAJOR << '.' << LL_VIEWER_VERSION_MINOR << '.' << LL_VIEWER_VERSION_PATCH + << '.' << LL_VIEWER_VERSION_BUILD); + + boost::json::value BugSplat_DB = build_data.at("BugSplat DB"); + gBugsplatDB = boost::json::value_to<std::string>(BugSplat_DB); + + LL_INFOS("BUGSPLAT") << "Initializing with crash logger: " << gCrashLogger << " database: " << gBugsplatDB << " version: " << gVersion << LL_ENDL; + + google_breakpad::MinidumpDescriptor *descriptor = new google_breakpad::MinidumpDescriptor(gDirUtilp->getExpandedFilename(LL_PATH_DUMP, "")); + google_breakpad::ExceptionHandler *eh = new google_breakpad::ExceptionHandler(*descriptor, NULL, dumpCallback, NULL, true, -1); +} +#endif + +bool LLAppViewerLinux::init() +{ bool success = LLAppViewer::init(); #if LL_SEND_CRASH_REPORTS - if (success) + S32 nCrashSubmitBehavior = gCrashSettings.getS32("CrashSubmitBehavior"); + + // For the first version we just consider always send and create a nice dialog for CRASH_BEHAVIOR_ASK later. + if (success && nCrashSubmitBehavior != CRASH_BEHAVIOR_NEVER_SEND ) { - LLAppViewer* pApp = LLAppViewer::instance(); - pApp->initCrashReporting(); + if( nCrashSubmitBehavior == CRASH_BEHAVIOR_ASK ) + gCrashBehavior = "ask"; + else + gCrashBehavior = "send"; + setupBreadpad(); } #endif @@ -143,7 +239,7 @@ bool LLAppViewerLinux::restoreErrorTrap() } ///////////////////////////////////////// -#if LL_DBUS_ENABLED +#if LL_GLIB typedef struct { @@ -153,101 +249,77 @@ typedef struct static void viewerappapi_init(ViewerAppAPI *server); static void viewerappapi_class_init(ViewerAppAPIClass *klass); -/// - -// regrettable hacks to give us better runtime compatibility with older systems in general -static GType llg_type_register_static_simple_ONCE(GType parent_type, - const gchar *type_name, - guint class_size, - GClassInitFunc class_init, - guint instance_size, - GInstanceInitFunc instance_init, - GTypeFlags flags) -{ - static GTypeInfo type_info; - memset(&type_info, 0, sizeof(type_info)); - - type_info.class_size = class_size; - type_info.class_init = class_init; - type_info.instance_size = instance_size; - type_info.instance_init = instance_init; - - return g_type_register_static(parent_type, type_name, &type_info, flags); -} -#define llg_intern_static_string(S) (S) -#define g_intern_static_string(S) llg_intern_static_string(S) -#define g_type_register_static_simple(parent_type, type_name, class_size, class_init, instance_size, instance_init, flags) llg_type_register_static_simple_ONCE(parent_type, type_name, class_size, class_init, instance_size, instance_init, flags) - G_DEFINE_TYPE(ViewerAppAPI, viewerappapi, G_TYPE_OBJECT); void viewerappapi_class_init(ViewerAppAPIClass *klass) { } -static bool dbus_server_init = false; - -void viewerappapi_init(ViewerAppAPI *server) +static void dispatchSLURL(gchar const *slurl) { - // Connect to the default DBUS, register our service/API. - - if (!dbus_server_init) - { - GError *error = NULL; - - server->connection = lldbus_g_bus_get(DBUS_BUS_SESSION, &error); - if (server->connection) - { - lldbus_g_object_type_install_info(viewerappapi_get_type(), &dbus_glib_viewerapp_object_info); + LL_INFOS() << "Was asked to go to slurl: " << slurl << LL_ENDL; - lldbus_g_connection_register_g_object(server->connection, VIEWERAPI_PATH, G_OBJECT(server)); + std::string url = slurl; + LLMediaCtrl* web = NULL; + const bool trusted_browser = false; + LLURLDispatcher::dispatch(url, "", web, trusted_browser); +} - DBusGProxy *serverproxy = lldbus_g_proxy_new_for_name(server->connection, DBUS_SERVICE_DBUS, DBUS_PATH_DBUS, DBUS_INTERFACE_DBUS); +static void DoMethodeCall (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + LL_INFOS() << "DBUS message " << method_name << " from: " << sender << " interface: " << interface_name << LL_ENDL; + const gchar *slurl; - guint request_name_ret_unused; - // akin to org_freedesktop_DBus_request_name - if (lldbus_g_proxy_call(serverproxy, "RequestName", &error, G_TYPE_STRING, VIEWERAPI_SERVICE, G_TYPE_UINT, 0, G_TYPE_INVALID, G_TYPE_UINT, &request_name_ret_unused, G_TYPE_INVALID)) - { - // total success. - dbus_server_init = true; - } - else - { - LL_WARNS() << "Unable to register service name: " << error->message << LL_ENDL; - } + g_variant_get (parameters, "(&s)", &slurl); + dispatchSLURL(slurl); +} - g_object_unref(serverproxy); - } - else +GDBusNodeInfo *gBusNodeInfo = nullptr; +static const GDBusInterfaceVTable interface_vtable = { - g_warning("Unable to connect to dbus: %s", error->message); - } - - if (error) - g_error_free(error); - } + DoMethodeCall + }; +static void busAcquired(GDBusConnection *connection, const gchar *name, gpointer user_data) +{ + auto id = g_dbus_connection_register_object(connection, + VIEWERAPI_PATH, + gBusNodeInfo->interfaces[0], + &interface_vtable, + NULL, /* user_data */ + NULL, /* user_data_free_func */ + NULL); /* GError** */ + g_assert (id > 0); } -gboolean viewer_app_api_GoSLURL(ViewerAppAPI *obj, gchar *slurl, gboolean **success_rtn, GError **error) +static void nameAcquired(GDBusConnection *connection, const gchar *name, gpointer user_data) { - bool success = false; - - LL_INFOS() << "Was asked to go to slurl: " << slurl << LL_ENDL; +} - std::string url = slurl; - LLMediaCtrl* web = NULL; - const bool trusted_browser = false; - if (LLURLDispatcher::dispatch(url, "", web, trusted_browser)) - { - // bring window to foreground, as it has just been "launched" from a URL - // todo: hmm, how to get there from here? - //xxx->mWindow->bringToFront(); - success = true; - } +static void nameLost(GDBusConnection *connection, const gchar *name, gpointer user_data) +{ - *success_rtn = g_new (gboolean, 1); - (*success_rtn)[0] = (gboolean)success; +} +void viewerappapi_init(ViewerAppAPI *server) +{ + gBusNodeInfo = g_dbus_node_info_new_for_xml (DBUS_SERVER, NULL); + g_assert (gBusNodeInfo != NULL); + + g_bus_own_name(G_BUS_TYPE_SESSION, + VIEWERAPI_SERVICE, + G_BUS_NAME_OWNER_FLAGS_NONE, + busAcquired, + nameAcquired, + nameLost, + NULL, + NULL); - return TRUE; // the invokation succeeded, even if the actual dispatch didn't. } /// @@ -255,13 +327,6 @@ gboolean viewer_app_api_GoSLURL(ViewerAppAPI *obj, gchar *slurl, gboolean **succ //virtual bool LLAppViewerLinux::initSLURLHandler() { - if (!grab_dbus_syms(DBUSGLIB_DYLIB_DEFAULT_NAME)) - { - return false; // failed - } - - g_type_init(); - //ViewerAppAPI *api_server = (ViewerAppAPI*) g_object_new(viewerappapi_get_type(), NULL); @@ -271,49 +336,49 @@ bool LLAppViewerLinux::initSLURLHandler() //virtual bool LLAppViewerLinux::sendURLToOtherInstance(const std::string& url) { - if (!grab_dbus_syms(DBUSGLIB_DYLIB_DEFAULT_NAME)) + auto *pBus = g_bus_get_sync(G_BUS_TYPE_SESSION, NULL, nullptr); + + if( !pBus ) { - return false; // failed + LL_WARNS() << "Getting dbus failed." << LL_ENDL; + return false; } - bool success = false; - DBusGConnection *bus; - GError *error = NULL; - - g_type_init(); + auto pProxy = g_dbus_proxy_new_sync(pBus, G_DBUS_PROXY_FLAGS_NONE, nullptr, + VIEWERAPI_SERVICE, VIEWERAPI_PATH, + VIEWERAPI_INTERFACE, nullptr, nullptr); - bus = lldbus_g_bus_get (DBUS_BUS_SESSION, &error); - if (bus) + if( !pProxy ) { - gboolean rtn = FALSE; - DBusGProxy *remote_object = - lldbus_g_proxy_new_for_name(bus, VIEWERAPI_SERVICE, VIEWERAPI_PATH, VIEWERAPI_INTERFACE); - - if (lldbus_g_proxy_call(remote_object, "GoSLURL", &error, - G_TYPE_STRING, url.c_str(), G_TYPE_INVALID, - G_TYPE_BOOLEAN, &rtn, G_TYPE_INVALID)) - { - success = rtn; - } - else - { - LL_INFOS() << "Call-out to other instance failed (perhaps not running): " << error->message << LL_ENDL; - } - - g_object_unref(G_OBJECT(remote_object)); + LL_WARNS() << "Cannot create new dbus proxy." << LL_ENDL; + g_object_unref( pBus ); + return false; } - else + + auto *pArgs = g_variant_new( "(s)", url.c_str() ); + if( !pArgs ) { - LL_WARNS() << "Couldn't connect to session bus: " << error->message << LL_ENDL; + LL_WARNS() << "Cannot create new variant." << LL_ENDL; + g_object_unref( pBus ); + return false; } - if (error) - g_error_free(error); + auto pRes = g_dbus_proxy_call_sync(pProxy, + "GoSLURL", + pArgs, + G_DBUS_CALL_FLAGS_NONE, + -1, nullptr, nullptr); - return success; + + + if( pRes ) + g_variant_unref( pRes ); + g_object_unref( pProxy ); + g_object_unref( pBus ); + return true; } -#else // LL_DBUS_ENABLED +#else // LL_GLIB bool LLAppViewerLinux::initSLURLHandler() { return false; // not implemented without dbus @@ -322,7 +387,7 @@ bool LLAppViewerLinux::sendURLToOtherInstance(const std::string& url) { return false; // not implemented without dbus } -#endif // LL_DBUS_ENABLED +#endif // LL_GLIB void LLAppViewerLinux::initCrashReporting(bool reportFreeze) { @@ -338,15 +403,18 @@ void LLAppViewerLinux::initCrashReporting(bool reportFreeze) pid_str << LLApp::getPid(); std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_DUMP, ""); std::string appname = gDirUtilp->getExecutableFilename(); + std::string grid{ LLGridManager::getInstance()->getGridId() }; + std::string title{ LLAppViewer::instance()->getSecondLifeTitle() }; + std::string pidstr{ pid_str.str() }; // launch the actual crash logger const char * cmdargv[] = {cmd.c_str(), "-user", - (char*)LLGridManager::getInstance()->getGridId().c_str(), + grid.c_str(), "-name", - LLAppViewer::instance()->getSecondLifeTitle().c_str(), + title.c_str(), "-pid", - pid_str.str().c_str(), + pidstr.c_str(), "-dumpdir", logdir.c_str(), "-procname", diff --git a/indra/newview/llappviewerlinux.h b/indra/newview/llappviewerlinux.h index dde223878d..460ca721f1 100644 --- a/indra/newview/llappviewerlinux.h +++ b/indra/newview/llappviewerlinux.h @@ -27,17 +27,6 @@ #ifndef LL_LLAPPVIEWERLINUX_H #define LL_LLAPPVIEWERLINUX_H -extern "C" { -# include <glib.h> -} - -#if LL_DBUS_ENABLED -extern "C" { -# include <glib-object.h> -# include <dbus/dbus-glib.h> -} -#endif - #ifndef LL_LLAPPVIEWER_H #include "llappviewer.h" #endif @@ -70,21 +59,4 @@ protected: virtual bool sendURLToOtherInstance(const std::string& url); }; -#if LL_DBUS_ENABLED -typedef struct -{ - GObject parent; - DBusGConnection *connection; -} ViewerAppAPI; - -extern "C" { - gboolean viewer_app_api_GoSLURL(ViewerAppAPI *obj, gchar *slurl, gboolean **success_rtn, GError **error); -} - -#define VIEWERAPI_SERVICE "com.secondlife.ViewerAppAPIService" -#define VIEWERAPI_PATH "/com/secondlife/ViewerAppAPI" -#define VIEWERAPI_INTERFACE "com.secondlife.ViewerAppAPI" - -#endif // LL_DBUS_ENABLED - #endif // LL_LLAPPVIEWERLINUX_H diff --git a/indra/newview/llappviewerlinux_api.h b/indra/newview/llappviewerlinux_api.h deleted file mode 100644 index 3d1324dd19..0000000000 --- a/indra/newview/llappviewerlinux_api.h +++ /dev/null @@ -1,143 +0,0 @@ -/* Generated by dbus-binding-tool; do not edit! */ -/** - * $LicenseInfo:firstyear=2008&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 __dbus_glib_marshal_viewerapp_MARSHAL_H__ -#define __dbus_glib_marshal_viewerapp_MARSHAL_H__ - -#include <glib-object.h> - -G_BEGIN_DECLS - -#ifdef G_ENABLE_DEBUG -#define g_marshal_value_peek_boolean(v) g_value_get_boolean (v) -#define g_marshal_value_peek_char(v) g_value_get_char (v) -#define g_marshal_value_peek_uchar(v) g_value_get_uchar (v) -#define g_marshal_value_peek_int(v) g_value_get_int (v) -#define g_marshal_value_peek_uint(v) g_value_get_uint (v) -#define g_marshal_value_peek_long(v) g_value_get_long (v) -#define g_marshal_value_peek_ulong(v) g_value_get_ulong (v) -#define g_marshal_value_peek_int64(v) g_value_get_int64 (v) -#define g_marshal_value_peek_uint64(v) g_value_get_uint64 (v) -#define g_marshal_value_peek_enum(v) g_value_get_enum (v) -#define g_marshal_value_peek_flags(v) g_value_get_flags (v) -#define g_marshal_value_peek_float(v) g_value_get_float (v) -#define g_marshal_value_peek_double(v) g_value_get_double (v) -#define g_marshal_value_peek_string(v) (char*) g_value_get_string (v) -#define g_marshal_value_peek_param(v) g_value_get_param (v) -#define g_marshal_value_peek_boxed(v) g_value_get_boxed (v) -#define g_marshal_value_peek_pointer(v) g_value_get_pointer (v) -#define g_marshal_value_peek_object(v) g_value_get_object (v) -#else /* !G_ENABLE_DEBUG */ -/* WARNING: This code accesses GValues directly, which is UNSUPPORTED API. - * Do not access GValues directly in your code. Instead, use the - * g_value_get_*() functions - */ -#define g_marshal_value_peek_boolean(v) (v)->data[0].v_int -#define g_marshal_value_peek_char(v) (v)->data[0].v_int -#define g_marshal_value_peek_uchar(v) (v)->data[0].v_uint -#define g_marshal_value_peek_int(v) (v)->data[0].v_int -#define g_marshal_value_peek_uint(v) (v)->data[0].v_uint -#define g_marshal_value_peek_long(v) (v)->data[0].v_long -#define g_marshal_value_peek_ulong(v) (v)->data[0].v_ulong -#define g_marshal_value_peek_int64(v) (v)->data[0].v_int64 -#define g_marshal_value_peek_uint64(v) (v)->data[0].v_uint64 -#define g_marshal_value_peek_enum(v) (v)->data[0].v_long -#define g_marshal_value_peek_flags(v) (v)->data[0].v_ulong -#define g_marshal_value_peek_float(v) (v)->data[0].v_float -#define g_marshal_value_peek_double(v) (v)->data[0].v_double -#define g_marshal_value_peek_string(v) (v)->data[0].v_pointer -#define g_marshal_value_peek_param(v) (v)->data[0].v_pointer -#define g_marshal_value_peek_boxed(v) (v)->data[0].v_pointer -#define g_marshal_value_peek_pointer(v) (v)->data[0].v_pointer -#define g_marshal_value_peek_object(v) (v)->data[0].v_pointer -#endif /* !G_ENABLE_DEBUG */ - - -/* BOOLEAN:STRING,POINTER,POINTER (/tmp/dbus-binding-tool-c-marshallers.5XXD8T:1) */ -extern void dbus_glib_marshal_viewerapp_BOOLEAN__STRING_POINTER_POINTER (GClosure *closure, - GValue *return_value, - guint n_param_values, - const GValue *param_values, - gpointer invocation_hint, - gpointer marshal_data); -void -dbus_glib_marshal_viewerapp_BOOLEAN__STRING_POINTER_POINTER (GClosure *closure, - GValue *return_value, - guint n_param_values, - const GValue *param_values, - gpointer invocation_hint, - gpointer marshal_data) -{ - typedef gboolean (*GMarshalFunc_BOOLEAN__STRING_POINTER_POINTER) (gpointer data1, - gpointer arg_1, - gpointer arg_2, - gpointer arg_3, - gpointer data2); - register GMarshalFunc_BOOLEAN__STRING_POINTER_POINTER callback; - register GCClosure *cc = (GCClosure*) closure; - register gpointer data1, data2; - gboolean v_return; - - g_return_if_fail (return_value != NULL); - g_return_if_fail (n_param_values == 4); - - if (G_CCLOSURE_SWAP_DATA (closure)) - { - data1 = closure->data; - data2 = g_value_peek_pointer (param_values + 0); - } - else - { - data1 = g_value_peek_pointer (param_values + 0); - data2 = closure->data; - } - callback = (GMarshalFunc_BOOLEAN__STRING_POINTER_POINTER) (marshal_data ? marshal_data : cc->callback); - - v_return = callback (data1, - g_marshal_value_peek_string (param_values + 1), - g_marshal_value_peek_pointer (param_values + 2), - g_marshal_value_peek_pointer (param_values + 3), - data2); - - g_value_set_boolean (return_value, v_return); -} - -G_END_DECLS - -#endif /* __dbus_glib_marshal_viewerapp_MARSHAL_H__ */ - -#include <dbus/dbus-glib.h> -static const DBusGMethodInfo dbus_glib_viewerapp_methods[] = { - { (GCallback) viewer_app_api_GoSLURL, dbus_glib_marshal_viewerapp_BOOLEAN__STRING_POINTER_POINTER, 0 }, -}; - -const DBusGObjectInfo dbus_glib_viewerapp_object_info = { - 0, - dbus_glib_viewerapp_methods, - 1, -"com.secondlife.ViewerAppAPI\0GoSLURL\0S\0slurl\0I\0s\0success_ret\0O\0F\0N\0b\0\0\0", -"\0", -"\0" -}; - diff --git a/indra/newview/llappviewerlinux_api.xml b/indra/newview/llappviewerlinux_api.xml deleted file mode 100644 index fac35b7adc..0000000000 --- a/indra/newview/llappviewerlinux_api.xml +++ /dev/null @@ -1,14 +0,0 @@ -<?xml version="1.0" encoding="UTF-8" ?> - -<!-- dbus-binding-tool -mode=glib-server llappviewerlinux_api.xml -prefix=viewerapp -output=llappviewerlinux_api.h --> - -<node name="/com/secondlife/ViewerAppAPI"> - <interface name="com.secondlife.ViewerAppAPI"> - <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="viewer_app_api"/> - <method name="GoSLURL"> - <annotation name="org.freedesktop.DBus.GLib.CSymbol" value="viewer_app_api_GoSLURL"/> - <arg type="s" name="slurl" direction="in" /> - <arg type="b" name="success_ret" direction="out" /> - </method> - </interface> -</node> diff --git a/indra/newview/llappviewerlinux_api_dbus.cpp b/indra/newview/llappviewerlinux_api_dbus.cpp deleted file mode 100644 index 9aed8a98d4..0000000000 --- a/indra/newview/llappviewerlinux_api_dbus.cpp +++ /dev/null @@ -1,126 +0,0 @@ -/** - * @file llappviewerlinux_api_dbus.cpp - * @brief dynamic DBus symbol-grabbing code - * - * $LicenseInfo:firstyear=2008&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$ - */ - -#if LL_DBUS_ENABLED - -#include "linden_common.h" - -extern "C" { -#include <dbus/dbus-glib.h> - -#include "apr_pools.h" -#include "apr_dso.h" -} - -#define DEBUGMSG(...) do { LL_DEBUGS() << llformat(__VA_ARGS__) << LL_ENDL; } while(0) -#define INFOMSG(...) do { LL_INFOS() << llformat(__VA_ARGS__) << LL_ENDL; } while(0) -#define WARNMSG(...) do { LL_WARNS() << llformat(__VA_ARGS__) << LL_ENDL; } while(0) - -#define LL_DBUS_SYM(REQUIRED, DBUSSYM, RTN, ...) RTN (*ll##DBUSSYM)(__VA_ARGS__) = NULL -#include "llappviewerlinux_api_dbus_syms_raw.inc" -#undef LL_DBUS_SYM - -static bool sSymsGrabbed = false; -static apr_pool_t *sSymDBUSDSOMemoryPool = NULL; -static apr_dso_handle_t *sSymDBUSDSOHandleG = NULL; - -bool grab_dbus_syms(std::string dbus_dso_name) -{ - if (sSymsGrabbed) - { - // already have grabbed good syms - return true; - } - - bool sym_error = false; - bool rtn = false; - apr_status_t rv; - apr_dso_handle_t *sSymDBUSDSOHandle = NULL; - -#define LL_DBUS_SYM(REQUIRED, DBUSSYM, RTN, ...) do{rv = apr_dso_sym((apr_dso_handle_sym_t*)&ll##DBUSSYM, sSymDBUSDSOHandle, #DBUSSYM); if (rv != APR_SUCCESS) {INFOMSG("Failed to grab symbol: %s", #DBUSSYM); if (REQUIRED) sym_error = true;} else DEBUGMSG("grabbed symbol: %s from %p", #DBUSSYM, (void*)ll##DBUSSYM);}while(0) - - //attempt to load the shared library - apr_pool_create(&sSymDBUSDSOMemoryPool, NULL); - - if ( APR_SUCCESS == (rv = apr_dso_load(&sSymDBUSDSOHandle, - dbus_dso_name.c_str(), - sSymDBUSDSOMemoryPool) )) - { - INFOMSG("Found DSO: %s", dbus_dso_name.c_str()); - -#include "llappviewerlinux_api_dbus_syms_raw.inc" - - if ( sSymDBUSDSOHandle ) - { - sSymDBUSDSOHandleG = sSymDBUSDSOHandle; - sSymDBUSDSOHandle = NULL; - } - - rtn = !sym_error; - } - else - { - INFOMSG("Couldn't load DSO: %s", dbus_dso_name.c_str()); - rtn = false; // failure - } - - if (sym_error) - { - WARNMSG("Failed to find necessary symbols in DBUS-GLIB libraries."); - } -#undef LL_DBUS_SYM - - sSymsGrabbed = rtn; - return rtn; -} - - -void ungrab_dbus_syms() -{ - // should be safe to call regardless of whether we've - // actually grabbed syms. - - if ( sSymDBUSDSOHandleG ) - { - apr_dso_unload(sSymDBUSDSOHandleG); - sSymDBUSDSOHandleG = NULL; - } - - if ( sSymDBUSDSOMemoryPool ) - { - apr_pool_destroy(sSymDBUSDSOMemoryPool); - sSymDBUSDSOMemoryPool = NULL; - } - - // NULL-out all of the symbols we'd grabbed -#define LL_DBUS_SYM(REQUIRED, DBUSSYM, RTN, ...) do{ll##DBUSSYM = NULL;}while(0) -#include "llappviewerlinux_api_dbus_syms_raw.inc" -#undef LL_DBUS_SYM - - sSymsGrabbed = false; -} - -#endif // LL_DBUS_ENABLED diff --git a/indra/newview/llappviewerlinux_api_dbus_syms_raw.inc b/indra/newview/llappviewerlinux_api_dbus_syms_raw.inc deleted file mode 100644 index c0548e2fba..0000000000 --- a/indra/newview/llappviewerlinux_api_dbus_syms_raw.inc +++ /dev/null @@ -1,9 +0,0 @@ - -// required symbols to grab -LL_DBUS_SYM(true, dbus_g_bus_get, DBusGConnection*, DBusBusType, GError**); -LL_DBUS_SYM(true, dbus_g_proxy_new_for_name, DBusGProxy*, DBusGConnection*, const char *, const char*, const char*); -LL_DBUS_SYM(true, dbus_g_proxy_call, gboolean, DBusGProxy*, const char*, GError**, GType, ...); -LL_DBUS_SYM(true, dbus_g_object_type_install_info, void, GType, const DBusGObjectInfo*); -LL_DBUS_SYM(true, dbus_g_connection_register_g_object, void, DBusGConnection*, const char*, GObject*); - -// optional symbols to grab diff --git a/indra/newview/llappviewerlistener.cpp b/indra/newview/llappviewerlistener.cpp index 6d519b6fef..4690c91b61 100644 --- a/indra/newview/llappviewerlistener.cpp +++ b/indra/newview/llappviewerlistener.cpp @@ -35,6 +35,7 @@ // external library headers // other Linden headers #include "llappviewer.h" +#include "workqueue.h" LLAppViewerListener::LLAppViewerListener(const LLAppViewerGetter& getter): LLEventAPI("LLAppViewer", @@ -42,6 +43,9 @@ LLAppViewerListener::LLAppViewerListener(const LLAppViewerGetter& getter): mAppViewerGetter(getter) { // add() every method we want to be able to invoke via this event API. + add("userQuit", + "Ask to quit with user confirmation prompt", + &LLAppViewerListener::userQuit); add("requestQuit", "Ask to quit nicely", &LLAppViewerListener::requestQuit); @@ -50,14 +54,26 @@ LLAppViewerListener::LLAppViewerListener(const LLAppViewerGetter& getter): &LLAppViewerListener::forceQuit); } +void LLAppViewerListener::userQuit(const LLSD& event) +{ + LL_INFOS() << "Listener requested user quit" << LL_ENDL; + // Trying to engage this from (e.g.) a Lua-hosting C++ coroutine runs + // afoul of an assert in the logging machinery that LLMutex must be locked + // only from the main coroutine. + LL::WorkQueue::getInstance("mainloop")->post( + [appviewer=mAppViewerGetter()]{ appviewer->userQuit(); }); +} + void LLAppViewerListener::requestQuit(const LLSD& event) { LL_INFOS() << "Listener requested quit" << LL_ENDL; - mAppViewerGetter()->requestQuit(); + LL::WorkQueue::getInstance("mainloop")->post( + [appviewer=mAppViewerGetter()]{ appviewer->requestQuit(); }); } void LLAppViewerListener::forceQuit(const LLSD& event) { LL_INFOS() << "Listener requested force quit" << LL_ENDL; - mAppViewerGetter()->forceQuit(); + LL::WorkQueue::getInstance("mainloop")->post( + [appviewer=mAppViewerGetter()]{ appviewer->forceQuit(); }); } diff --git a/indra/newview/llappviewerlistener.h b/indra/newview/llappviewerlistener.h index 5ade3d3e04..e116175eb7 100644 --- a/indra/newview/llappviewerlistener.h +++ b/indra/newview/llappviewerlistener.h @@ -30,7 +30,7 @@ #define LL_LLAPPVIEWERLISTENER_H #include "lleventapi.h" -#include <boost/function.hpp> +#include <functional> class LLAppViewer; class LLSD; @@ -39,11 +39,12 @@ class LLSD; class LLAppViewerListener: public LLEventAPI { public: - typedef boost::function<LLAppViewer*(void)> LLAppViewerGetter; + typedef std::function<LLAppViewer*(void)> LLAppViewerGetter; /// Bind the LLAppViewer instance to use (e.g. LLAppViewer::instance()). LLAppViewerListener(const LLAppViewerGetter& getter); private: + void userQuit(const LLSD& event); void requestQuit(const LLSD& event); void forceQuit(const LLSD& event); diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index c5686f160a..ad817d5747 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -389,17 +389,10 @@ void ll_nvapi_init(NvDRSSessionHandle hSession) } } -//#define DEBUGGING_SEH_FILTER 1 -#if DEBUGGING_SEH_FILTER -# define WINMAIN DebuggingWinMain -#else -# define WINMAIN wWinMain -#endif - -int APIENTRY WINMAIN(HINSTANCE hInstance, - HINSTANCE hPrevInstance, - PWSTR pCmdLine, - int nCmdShow) +int APIENTRY wWinMain(HINSTANCE hInstance, + HINSTANCE hPrevInstance, + PWSTR pCmdLine, + int nCmdShow) { // Call Tracy first thing to have it allocate memory // https://github.com/wolfpld/tracy/issues/196 @@ -548,27 +541,6 @@ int APIENTRY WINMAIN(HINSTANCE hInstance, return 0; } -#if DEBUGGING_SEH_FILTER -// The compiler doesn't like it when you use __try/__except blocks -// in a method that uses object destructors. Go figure. -// This winmain just calls the real winmain inside __try. -// The __except calls our exception filter function. For debugging purposes. -int APIENTRY wWinMain(HINSTANCE hInstance, - HINSTANCE hPrevInstance, - PWSTR lpCmdLine, - int nCmdShow) -{ - __try - { - WINMAIN(hInstance, hPrevInstance, lpCmdLine, nCmdShow); - } - __except( viewer_windows_exception_handler( GetExceptionInformation() ) ) - { - _tprintf( _T("Exception handled.\n") ); - } -} -#endif - void LLAppViewerWin32::disableWinErrorReporting() { std::string executable_name = gDirUtilp->getExecutableFilename(); diff --git a/indra/newview/llblocklist.cpp b/indra/newview/llblocklist.cpp index 89516a8a84..bb1e3405d7 100644 --- a/indra/newview/llblocklist.cpp +++ b/indra/newview/llblocklist.cpp @@ -50,7 +50,7 @@ LLBlockList::LLBlockList(const Params& p) mMuteListSize = static_cast<U32>(LLMuteList::getInstance()->getMutes().size()); // Set up context menu. - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + ScopedRegistrarHelper registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; registrar.add ("Block.Action", boost::bind(&LLBlockList::onCustomAction, this, _2)); diff --git a/indra/newview/llbox.cpp b/indra/newview/llbox.cpp index dd12a02907..d0787a3902 100644 --- a/indra/newview/llbox.cpp +++ b/indra/newview/llbox.cpp @@ -76,16 +76,23 @@ void LLBox::renderface(S32 which_face) {7, 4, 0, 3} }; - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); + { //gGL.normal3fv(&normals[which_face][0]); - gGL.texCoord2f(1,0); - gGL.vertex3fv(&mVertex[ faces[which_face][0] ][0]); - gGL.texCoord2f(1,1); - gGL.vertex3fv(&mVertex[ faces[which_face][1] ][0]); - gGL.texCoord2f(0,1); - gGL.vertex3fv(&mVertex[ faces[which_face][2] ][0]); - gGL.texCoord2f(0,0); - gGL.vertex3fv(&mVertex[ faces[which_face][3] ][0]); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex3fv(&mVertex[faces[which_face][0]][0]); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex3fv(&mVertex[faces[which_face][1]][0]); + gGL.texCoord2f(0.f, 1.f); + gGL.vertex3fv(&mVertex[faces[which_face][2]][0]); + + gGL.texCoord2f(1.f, 0.f); + gGL.vertex3fv(&mVertex[faces[which_face][0]][0]); + gGL.texCoord2f(0.f, 1.f); + gGL.vertex3fv(&mVertex[faces[which_face][2]][0]); + gGL.texCoord2f(0.f, 0.f); + gGL.vertex3fv(&mVertex[faces[which_face][3]][0]); + } gGL.end(); } diff --git a/indra/newview/llcallbacklist.cpp b/indra/newview/llcallbacklist.cpp deleted file mode 100644 index c474f2885b..0000000000 --- a/indra/newview/llcallbacklist.cpp +++ /dev/null @@ -1,305 +0,0 @@ -/** - * @file llcallbacklist.cpp - * @brief A simple list of callback functions to call. - * - * $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 "llcallbacklist.h" -#include "lleventtimer.h" - -// Library includes -#include "llerror.h" - - -// -// Globals -// -LLCallbackList gIdleCallbacks; - -// -// Member functions -// - -LLCallbackList::LLCallbackList() -{ - // nothing -} - -LLCallbackList::~LLCallbackList() -{ -} - - -void LLCallbackList::addFunction( callback_t func, void *data) -{ - if (!func) - { - LL_ERRS() << "LLCallbackList::addFunction - function is NULL" << LL_ENDL; - return; - } - - // only add one callback per func/data pair - callback_pair_t t(func, data); - callback_list_t::iterator iter = std::find(mCallbackList.begin(), mCallbackList.end(), t); - if (iter == mCallbackList.end()) - { - mCallbackList.push_back(t); - } -} - - -BOOL LLCallbackList::containsFunction( callback_t func, void *data) -{ - callback_pair_t t(func, data); - callback_list_t::iterator iter = std::find(mCallbackList.begin(), mCallbackList.end(), t); - if (iter != mCallbackList.end()) - { - return TRUE; - } - else - { - return FALSE; - } -} - - -BOOL LLCallbackList::deleteFunction( callback_t func, void *data) -{ - callback_pair_t t(func, data); - callback_list_t::iterator iter = std::find(mCallbackList.begin(), mCallbackList.end(), t); - if (iter != mCallbackList.end()) - { - mCallbackList.erase(iter); - return TRUE; - } - else - { - return FALSE; - } -} - - -void LLCallbackList::deleteAllFunctions() -{ - mCallbackList.clear(); -} - - -void LLCallbackList::callFunctions() -{ - for (callback_list_t::iterator iter = mCallbackList.begin(); iter != mCallbackList.end(); ) - { - callback_list_t::iterator curiter = iter++; - curiter->first(curiter->second); - } -} - -// Shim class to allow arbitrary boost::bind -// expressions to be run as one-time idle callbacks. -class OnIdleCallbackOneTime -{ -public: - OnIdleCallbackOneTime(nullary_func_t callable): - mCallable(callable) - { - } - static void onIdle(void *data) - { - gIdleCallbacks.deleteFunction(onIdle, data); - OnIdleCallbackOneTime* self = reinterpret_cast<OnIdleCallbackOneTime*>(data); - self->call(); - delete self; - } - void call() - { - mCallable(); - } -private: - nullary_func_t mCallable; -}; - -void doOnIdleOneTime(nullary_func_t callable) -{ - OnIdleCallbackOneTime* cb_functor = new OnIdleCallbackOneTime(callable); - gIdleCallbacks.addFunction(&OnIdleCallbackOneTime::onIdle,cb_functor); -} - -// Shim class to allow generic boost functions to be run as -// recurring idle callbacks. Callable should return true when done, -// false to continue getting called. -class OnIdleCallbackRepeating -{ -public: - OnIdleCallbackRepeating(bool_func_t callable): - mCallable(callable) - { - } - // Will keep getting called until the callable returns true. - static void onIdle(void *data) - { - OnIdleCallbackRepeating* self = reinterpret_cast<OnIdleCallbackRepeating*>(data); - bool done = self->call(); - if (done) - { - gIdleCallbacks.deleteFunction(onIdle, data); - delete self; - } - } - bool call() - { - return mCallable(); - } -private: - bool_func_t mCallable; -}; - -void doOnIdleRepeating(bool_func_t callable) -{ - OnIdleCallbackRepeating* cb_functor = new OnIdleCallbackRepeating(callable); - gIdleCallbacks.addFunction(&OnIdleCallbackRepeating::onIdle,cb_functor); -} - -class NullaryFuncEventTimer: public LLEventTimer -{ -public: - NullaryFuncEventTimer(nullary_func_t callable, F32 seconds): - LLEventTimer(seconds), - mCallable(callable) - { - } - -private: - bool tick() - { - mCallable(); - return true; - } - - nullary_func_t mCallable; -}; - -// Call a given callable once after specified interval. -void doAfterInterval(nullary_func_t callable, F32 seconds) -{ - new NullaryFuncEventTimer(callable, seconds); -} - -class BoolFuncEventTimer: public LLEventTimer -{ -public: - BoolFuncEventTimer(bool_func_t callable, F32 seconds): - LLEventTimer(seconds), - mCallable(callable) - { - } -private: - bool tick() - { - return mCallable(); - } - - bool_func_t mCallable; -}; - -// Call a given callable every specified number of seconds, until it returns true. -void doPeriodically(bool_func_t callable, F32 seconds) -{ - new BoolFuncEventTimer(callable, seconds); -} - -#ifdef _DEBUG - -void test1(void *data) -{ - S32 *s32_data = (S32 *)data; - LL_INFOS() << "testfunc1 " << *s32_data << LL_ENDL; -} - - -void test2(void *data) -{ - S32 *s32_data = (S32 *)data; - LL_INFOS() << "testfunc2 " << *s32_data << LL_ENDL; -} - - -void -LLCallbackList::test() -{ - S32 a = 1; - S32 b = 2; - LLCallbackList *list = new LLCallbackList; - - LL_INFOS() << "Testing LLCallbackList" << LL_ENDL; - - if (!list->deleteFunction(NULL)) - { - LL_INFOS() << "passed 1" << LL_ENDL; - } - else - { - LL_INFOS() << "error, removed function from empty list" << LL_ENDL; - } - - // LL_INFOS() << "This should crash" << LL_ENDL; - // list->addFunction(NULL); - - list->addFunction(&test1, &a); - list->addFunction(&test1, &a); - - LL_INFOS() << "Expect: test1 1, test1 1" << LL_ENDL; - list->callFunctions(); - - list->addFunction(&test1, &b); - list->addFunction(&test2, &b); - - LL_INFOS() << "Expect: test1 1, test1 1, test1 2, test2 2" << LL_ENDL; - list->callFunctions(); - - if (list->deleteFunction(&test1, &b)) - { - LL_INFOS() << "passed 3" << LL_ENDL; - } - else - { - LL_INFOS() << "error removing function" << LL_ENDL; - } - - LL_INFOS() << "Expect: test1 1, test1 1, test2 2" << LL_ENDL; - list->callFunctions(); - - list->deleteAllFunctions(); - - LL_INFOS() << "Expect nothing" << LL_ENDL; - list->callFunctions(); - - LL_INFOS() << "nothing :-)" << LL_ENDL; - - delete list; - - LL_INFOS() << "test complete" << LL_ENDL; -} - -#endif // _DEBUG diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index a48e22bc73..3e02820feb 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -125,6 +125,7 @@ public: mUserNameTextBox(NULL), mTimeBoxTextBox(NULL), mNeedsTimeBox(true), + mIsFromScript(false), mAvatarNameCacheConnection() {} @@ -658,11 +659,13 @@ public: const LLUUID& getAvatarId () const { return mAvatarID;} - void setup(const LLChat& chat, const LLStyle::Params& style_params, const LLSD& args) + void setup(const LLChat& chat, const LLStyle::Params& style_params, const LLSD& args, bool is_script) { mAvatarID = chat.mFromID; mSessionID = chat.mSessionID; mSourceType = chat.mSourceType; + mIsFromScript = is_script; + mPrefix = mIsFromScript ? LLTrans::getString("ScriptBy") : ""; // To be able to report a message, we need a copy of it's text // and it's easier to store text directly than trying to get @@ -732,7 +735,7 @@ public: username_end == (chat.mFromName.length() - 1)) { mFrom = chat.mFromName.substr(0, username_start); - user_name->setValue(mFrom); + user_name->setValue(mPrefix + mFrom); if (gSavedSettings.getBOOL("NameTagShowUsernames")) { @@ -774,7 +777,7 @@ public: switch (mSourceType) { case CHAT_SOURCE_AGENT: - icon->setValue(chat.mFromID); + icon->setValue(mIsFromScript ? LLSD("Inv_Script") : LLSD(chat.mFromID)); break; case CHAT_SOURCE_OBJECT: icon->setValue(LLSD("OBJECT_Icon")); @@ -787,7 +790,7 @@ public: icon->setValue(LLSD("Command_Destinations_Icon")); break; case CHAT_SOURCE_UNKNOWN: - icon->setValue(LLSD("Unknown_Icon")); + icon->setValue(mIsFromScript ? LLSD("Inv_Script") : LLSD("Unknown_Icon")); } // In case the message came from an object, save the object info @@ -868,7 +871,7 @@ protected: LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandleObject.get(); if (!menu) { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + ScopedRegistrarHelper registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar registrar_enable; registrar.add("ObjectIcon.Action", boost::bind(&LLChatHistoryHeader::onObjectIconContextMenuItemClicked, this, _2)); registrar_enable.add("ObjectIcon.Visible", boost::bind(&LLChatHistoryHeader::onObjectIconContextMenuItemVisible, this, _2)); @@ -896,9 +899,9 @@ protected: LLMenuGL* menu = (LLMenuGL*)mPopupMenuHandleAvatar.get(); if (!menu) { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + ScopedRegistrarHelper registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar registrar_enable; - registrar.add("AvatarIcon.Action", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemClicked, this, _2)); + registrar.add("AvatarIcon.Action", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemClicked, this, _2), cb_info::UNTRUSTED_BLOCK); registrar_enable.add("AvatarIcon.Check", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemChecked, this, _2)); registrar_enable.add("AvatarIcon.Enable", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemEnabled, this, _2)); registrar_enable.add("AvatarIcon.Visible", boost::bind(&LLChatHistoryHeader::onAvatarIconContextMenuItemVisible, this, _2)); @@ -1029,7 +1032,7 @@ private: mFrom = av_name.getDisplayName(); LLTextBox* user_name = getChild<LLTextBox>("user_name"); - user_name->setValue( LLSD(av_name.getDisplayName() ) ); + user_name->setValue(LLSD(mPrefix + av_name.getDisplayName())); user_name->setToolTip( av_name.getUserName() ); if (gSavedSettings.getBOOL("NameTagShowUsernames") && @@ -1071,6 +1074,9 @@ protected: bool mNeedsTimeBox; + bool mIsFromScript; + std::string mPrefix; + private: boost::signals2::connection mAvatarNameCacheConnection; }; @@ -1088,6 +1094,7 @@ LLChatHistory::LLChatHistory(const LLChatHistory::Params& p) mTopHeaderPad(p.top_header_pad), mBottomHeaderPad(p.bottom_header_pad), mIsLastMessageFromLog(false), + mIsLastFromScript(false), mNotifyAboutUnreadMsg(p.notify_unread_msg) { LLTextEditor::Params editor_params(p); @@ -1185,11 +1192,11 @@ LLView* LLChatHistory::getSeparator() return separator; } -LLView* LLChatHistory::getHeader(const LLChat& chat,const LLStyle::Params& style_params, const LLSD& args) +LLView* LLChatHistory::getHeader(const LLChat& chat,const LLStyle::Params& style_params, const LLSD& args, bool is_script) { LLChatHistoryHeader* header = LLChatHistoryHeader::createInstance(mMessageHeaderFilename); if (header) - header->setup(chat, style_params, args); + header->setup(chat, style_params, args, is_script); return header; } @@ -1260,8 +1267,8 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL name_params.color(name_color); name_params.readonly_color(name_color); - std::string prefix = chat.mText.substr(0, 4); - + auto [message, is_lua] = LLStringUtil::withoutPrefix(chat.mText, LUA_PREFIX); + std::string prefix = message.substr(0, 4); //IRC styled /me messages. bool irc_me = prefix == "/me " || prefix == "/me'"; @@ -1337,6 +1344,7 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL // names showing if (args["show_names_for_p2p_conv"].asBoolean() && utf8str_trim(chat.mFromName).size()) { + std::string script_prefix = is_lua ? LLTrans::getString("ScriptBy") : ""; // Don't hotlink any messages from the system (e.g. "Second Life:"), so just add those in plain text. if (chat.mSourceType == CHAT_SOURCE_OBJECT && chat.mFromID.notNull()) { @@ -1361,7 +1369,7 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL link_params.overwriteFrom(LLStyleMap::instance().lookupAgent(chat.mFromID)); // Add link to avatar's inspector and delimiter to message. - mEditor->appendText(std::string(link_params.link_href) + delimiter, + mEditor->appendText(script_prefix + std::string(link_params.link_href) + delimiter, prependNewLineState, link_params); prependNewLineState = false; } @@ -1374,7 +1382,7 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL } else { - mEditor->appendText("<nolink>" + chat.mFromName + "</nolink>" + delimiter, + mEditor->appendText(script_prefix + "<nolink>" + chat.mFromName + "</nolink>" + delimiter, prependNewLineState, body_message_params); prependNewLineState = false; } @@ -1395,7 +1403,8 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL && mLastFromID == chat.mFromID && mLastMessageTime.notNull() && (new_message_time.secondsSinceEpoch() - mLastMessageTime.secondsSinceEpoch()) < 60.0 - && mIsLastMessageFromLog == message_from_log) //distinguish between current and previous chat session's histories + && mIsLastMessageFromLog == message_from_log //distinguish between current and previous chat session's histories + && mIsLastFromScript == is_lua) { view = getSeparator(); if (!view) @@ -1410,7 +1419,7 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL } else { - view = getHeader(chat, name_params, args); + view = getHeader(chat, name_params, args, is_lua); if (!view) { LL_WARNS() << "Failed to create header from " << mMessageHeaderFilename << ": can't append to history" << LL_ENDL; @@ -1439,6 +1448,7 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL mLastFromID = chat.mFromID; mLastMessageTime = new_message_time; mIsLastMessageFromLog = message_from_log; + mIsLastFromScript = is_lua; } // body of the message processing @@ -1493,7 +1503,7 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL // usual messages showing else if (!teleport_separator) { - std::string message = irc_me ? chat.mText.substr(3) : chat.mText; + message = irc_me ? message.substr(3) : message; //MESSAGE TEXT PROCESSING //*HACK getting rid of redundant sender names in system notifications sent using sender name (see EXT-5010) diff --git a/indra/newview/llchathistory.h b/indra/newview/llchathistory.h index b8364ff860..8acbea6ed5 100644 --- a/indra/newview/llchathistory.h +++ b/indra/newview/llchathistory.h @@ -101,7 +101,7 @@ class LLChatHistory : public LLUICtrl * Builds a message header. * @return pointer to LLView header object. */ - LLView* getHeader(const LLChat& chat,const LLStyle::Params& style_params, const LLSD& args); + LLView* getHeader(const LLChat& chat,const LLStyle::Params& style_params, const LLSD& args, bool is_script = false); public: ~LLChatHistory(); LLSD getValue() const; @@ -127,6 +127,7 @@ class LLChatHistory : public LLUICtrl LLDate mLastMessageTime; bool mIsLastMessageFromLog; bool mNotifyAboutUnreadMsg; + bool mIsLastFromScript; //std::string mLastMessageTimeStr; std::string mMessageHeaderFilename; diff --git a/indra/newview/llchatitemscontainerctrl.cpp b/indra/newview/llchatitemscontainerctrl.cpp index 550dfeb802..f1ed55e647 100644 --- a/indra/newview/llchatitemscontainerctrl.cpp +++ b/indra/newview/llchatitemscontainerctrl.cpp @@ -188,6 +188,7 @@ void LLFloaterIMNearbyChatToastPanel::init(LLSD& notification) int sType = notification["source"].asInteger(); mSourceType = (EChatSourceType)sType; + mIsFromScript = notification["is_lua"].asBoolean(); std::string color_name = notification["text_color"].asString(); @@ -215,7 +216,7 @@ void LLFloaterIMNearbyChatToastPanel::init(LLSD& notification) { std::string str_sender; - str_sender = fromName; + str_sender = mIsFromScript ? LLTrans::getString("ScriptBy") + fromName : fromName; str_sender+=" "; @@ -397,7 +398,7 @@ void LLFloaterIMNearbyChatToastPanel::draw() else if(mSourceType == CHAT_SOURCE_SYSTEM) icon->setValue(LLSD("SL_Logo")); else if(mSourceType == CHAT_SOURCE_AGENT) - icon->setValue(mFromID); + icon->setValue(mIsFromScript ? LLSD("Inv_Script") : LLSD(mFromID)); else if(!mFromID.isNull()) icon->setValue(mFromID); } diff --git a/indra/newview/llchatitemscontainerctrl.h b/indra/newview/llchatitemscontainerctrl.h index 4ae73a0c43..54b757d217 100644 --- a/indra/newview/llchatitemscontainerctrl.h +++ b/indra/newview/llchatitemscontainerctrl.h @@ -48,7 +48,8 @@ protected: LLFloaterIMNearbyChatToastPanel() : mIsDirty(false), - mSourceType(CHAT_SOURCE_OBJECT) + mSourceType(CHAT_SOURCE_OBJECT), + mIsFromScript(false) {}; public: ~LLFloaterIMNearbyChatToastPanel(){} @@ -58,6 +59,8 @@ public: const LLUUID& getFromID() const { return mFromID;} const std::string& getFromName() const { return mFromName; } + bool isFromScript() { return mIsFromScript; } + //void addText (const std::string& message , const LLStyle::Params& input_params = LLStyle::Params()); //void setMessage (const LLChat& msg); void snapToMessageHeight (); @@ -88,6 +91,7 @@ private: std::string mFromName; EChatSourceType mSourceType; LLChatMsgBox* mMsgText; + bool mIsFromScript; diff --git a/indra/newview/llchatmsgbox.cpp b/indra/newview/llchatmsgbox.cpp index b70b3eac95..eacbb4366d 100644 --- a/indra/newview/llchatmsgbox.cpp +++ b/indra/newview/llchatmsgbox.cpp @@ -41,6 +41,16 @@ public: mEditor(NULL) {} + /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const + { + ChatSeparator* copy = new ChatSeparator(mStart, mEnd); + if (mEditor) + { + copy->mEditor = ⌖ + } + return copy; + } + /*virtual*/ void linkToDocument(class LLTextBase* editor) { mEditor = editor; diff --git a/indra/newview/llchiclet.cpp b/indra/newview/llchiclet.cpp index 4c0f160f6f..ec2b570ccd 100644 --- a/indra/newview/llchiclet.cpp +++ b/indra/newview/llchiclet.cpp @@ -210,9 +210,9 @@ void LLNotificationChiclet::createMenu() return; } - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + ScopedRegistrarHelper registrar; registrar.add("NotificationWellChicletMenu.Action", - boost::bind(&LLNotificationChiclet::onMenuItemClicked, this, _2)); + boost::bind(&LLNotificationChiclet::onMenuItemClicked, this, _2), cb_info::UNTRUSTED_BLOCK); LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; enable_registrar.add("NotificationWellChicletMenu.EnableItem", @@ -1132,8 +1132,8 @@ void LLScriptChiclet::createPopupMenu() if(!canCreateMenu()) return; - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - registrar.add("ScriptChiclet.Action", boost::bind(&LLScriptChiclet::onMenuItemClicked, this, _2)); + ScopedRegistrarHelper registrar; + registrar.add("ScriptChiclet.Action", boost::bind(&LLScriptChiclet::onMenuItemClicked, this, _2), cb_info::UNTRUSTED_BLOCK); LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL> ("menu_script_chiclet.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); @@ -1215,8 +1215,8 @@ void LLInvOfferChiclet::createPopupMenu() if(!canCreateMenu()) return; - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - registrar.add("InvOfferChiclet.Action", boost::bind(&LLInvOfferChiclet::onMenuItemClicked, this, _2)); + ScopedRegistrarHelper registrar; + registrar.add("InvOfferChiclet.Action", boost::bind(&LLInvOfferChiclet::onMenuItemClicked, this, _2), cb_info::UNTRUSTED_BLOCK); LLMenuGL* menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL> ("menu_inv_offer_chiclet.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); diff --git a/indra/newview/llcofwearables.cpp b/indra/newview/llcofwearables.cpp index 47803edc73..b9df091f54 100644 --- a/indra/newview/llcofwearables.cpp +++ b/indra/newview/llcofwearables.cpp @@ -138,7 +138,7 @@ protected: /*virtual*/ LLContextMenu* createMenu() { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::ScopedRegistrarHelper registrar; registrar.add("Attachment.Touch", boost::bind(handleMultiple, handle_attachment_touch, mUUIDs)); registrar.add("Attachment.Edit", boost::bind(handleMultiple, handle_item_edit, mUUIDs)); @@ -191,7 +191,7 @@ protected: /*virtual*/ LLContextMenu* createMenu() { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::ScopedRegistrarHelper registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; LLUUID selected_id = mUUIDs.back(); @@ -251,9 +251,9 @@ protected: LLUUID selected_id = mUUIDs.back(); LLPanelOutfitEdit* panel_oe = dynamic_cast<LLPanelOutfitEdit*>(LLFloaterSidePanelContainer::getPanel("appearance", "panel_outfit_edit")); - registrar.add("BodyPart.Replace", boost::bind(&LLPanelOutfitEdit::onReplaceMenuItemClicked, panel_oe, selected_id)); - registrar.add("BodyPart.Edit", boost::bind(LLAgentWearables::editWearable, selected_id)); - registrar.add("BodyPart.Create", boost::bind(&CofBodyPartContextMenu::createNew, this, selected_id)); + registrar.add("BodyPart.Replace", { boost::bind(&LLPanelOutfitEdit::onReplaceMenuItemClicked, panel_oe, selected_id) }); + registrar.add("BodyPart.Edit", { boost::bind(LLAgentWearables::editWearable, selected_id) }); + registrar.add("BodyPart.Create", { boost::bind(&CofBodyPartContextMenu::createNew, this, selected_id) }); enable_registrar.add("BodyPart.OnEnable", boost::bind(&CofBodyPartContextMenu::onEnable, this, _2)); diff --git a/indra/newview/llconversationloglist.cpp b/indra/newview/llconversationloglist.cpp index 65863f0a5e..8376f5463c 100644 --- a/indra/newview/llconversationloglist.cpp +++ b/indra/newview/llconversationloglist.cpp @@ -47,11 +47,11 @@ LLConversationLogList::LLConversationLogList(const Params& p) LLConversationLog::instance().addObserver(this); // Set up context menu. - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + ScopedRegistrarHelper registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar check_registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - registrar.add ("Calllog.Action", boost::bind(&LLConversationLogList::onCustomAction, this, _2)); + registrar.add ("Calllog.Action", boost::bind(&LLConversationLogList::onCustomAction, this, _2), cb_info::UNTRUSTED_BLOCK); check_registrar.add ("Calllog.Check", boost::bind(&LLConversationLogList::isActionChecked,this, _2)); enable_registrar.add("Calllog.Enable", boost::bind(&LLConversationLogList::isActionEnabled,this, _2)); @@ -95,6 +95,7 @@ bool LLConversationLogList::handleRightMouseDown(S32 x, S32 y, MASK mask) { context_menu->buildDrawLabels(); context_menu->updateParent(LLMenuGL::sMenuContainer); + LLMenuGL::showPopup(this, context_menu, x, y); } diff --git a/indra/newview/llconversationview.cpp b/indra/newview/llconversationview.cpp index b19b6f8dec..a1f627c8cc 100644 --- a/indra/newview/llconversationview.cpp +++ b/indra/newview/llconversationview.cpp @@ -313,7 +313,7 @@ void LLConversationViewSession::draw() { // update the rotation angle of open folder arrow updateLabelRotation(); - drawOpenFolderArrow(default_params, sFgColor); + drawOpenFolderArrow(); } LLView::draw(); } diff --git a/indra/newview/lldirpicker.cpp b/indra/newview/lldirpicker.cpp index e967ff3df2..51157fa430 100644 --- a/indra/newview/lldirpicker.cpp +++ b/indra/newview/lldirpicker.cpp @@ -41,6 +41,11 @@ # include "llfilepicker.h" #endif +#ifdef LL_FLTK + #include "FL/Fl.H" + #include "FL/Fl_Native_File_Chooser.H" +#endif + #if LL_WINDOWS #include <shlobj.h> #endif @@ -214,20 +219,28 @@ LLDirPicker::LLDirPicker() : mFileName(NULL), mLocked(false) { +#ifndef LL_FLTK mFilePicker = new LLFilePicker(); +#endif reset(); } LLDirPicker::~LLDirPicker() { +#ifndef LL_FLTK delete mFilePicker; +#endif } void LLDirPicker::reset() { +#ifndef LL_FLTK if (mFilePicker) mFilePicker->reset(); +#else + mDir = ""; +#endif } bool LLDirPicker::getDir(std::string* filename, bool blocking) @@ -240,33 +253,39 @@ bool LLDirPicker::getDir(std::string* filename, bool blocking) return false; } -#if !LL_MESA_HEADLESS - - if (mFilePicker) +#ifdef LL_FLTK + gViewerWindow->getWindow()->beforeDialog(); + Fl_Native_File_Chooser flDlg; + flDlg.title(LLTrans::getString("choose_the_directory").c_str()); + flDlg.type(Fl_Native_File_Chooser::BROWSE_DIRECTORY ); + int res = flDlg.show(); + gViewerWindow->getWindow()->afterDialog(); + if( res == 0 ) { - GtkWindow* picker = mFilePicker->buildFilePicker(false, true, - "dirpicker"); - - if (picker) - { - gtk_window_set_title(GTK_WINDOW(picker), LLTrans::getString("choose_the_directory").c_str()); - gtk_widget_show_all(GTK_WIDGET(picker)); - gtk_main(); - return (!mFilePicker->getFirstFile().empty()); - } + char const *pDir = flDlg.filename(0); + if( pDir ) + mDir = pDir; } -#endif // !LL_MESA_HEADLESS - + else if( res == -1 ) + { + LL_WARNS() << "FLTK failed: " << flDlg.errmsg() << LL_ENDL; + } + return !mDir.empty(); +#endif return false; } std::string LLDirPicker::getDirName() { +#ifndef LL_FLTK if (mFilePicker) { return mFilePicker->getFirstFile(); } return ""; +#else + return mDir; +#endif } #else // not implemented diff --git a/indra/newview/lldirpicker.h b/indra/newview/lldirpicker.h index dc740caab2..2ac3db7c2e 100644 --- a/indra/newview/lldirpicker.h +++ b/indra/newview/lldirpicker.h @@ -77,8 +77,10 @@ private: #if LL_LINUX || LL_DARWIN // On Linux we just implement LLDirPicker on top of LLFilePicker +#ifndef LL_FLTK LLFilePicker *mFilePicker; #endif +#endif std::string* mFileName; diff --git a/indra/newview/lldonotdisturbnotificationstorage.h b/indra/newview/lldonotdisturbnotificationstorage.h index 6683646a9b..0dc2515e02 100644 --- a/indra/newview/lldonotdisturbnotificationstorage.h +++ b/indra/newview/lldonotdisturbnotificationstorage.h @@ -42,7 +42,7 @@ public: ~LLDoNotDisturbNotificationStorageTimer(); public: - bool tick(); + bool tick() override; }; class LLDoNotDisturbNotificationStorage : public LLParamSingleton<LLDoNotDisturbNotificationStorage>, public LLNotificationStorage diff --git a/indra/newview/lldynamictexture.cpp b/indra/newview/lldynamictexture.cpp index 0b9f76e7f6..33325e352f 100644 --- a/indra/newview/lldynamictexture.cpp +++ b/indra/newview/lldynamictexture.cpp @@ -200,8 +200,8 @@ bool LLViewerDynamicTexture::updateAllInstances() } llassert(preview_target.getWidth() >= LLPipeline::MAX_PREVIEW_WIDTH); llassert(preview_target.getHeight() >= LLPipeline::MAX_PREVIEW_WIDTH); - llassert(bake_target.getWidth() >= LLAvatarAppearanceDefines::SCRATCH_TEX_WIDTH); - llassert(bake_target.getHeight() >= LLAvatarAppearanceDefines::SCRATCH_TEX_HEIGHT); + llassert(bake_target.getWidth() >= (U32) LLAvatarAppearanceDefines::SCRATCH_TEX_WIDTH); + llassert(bake_target.getHeight() >= (U32) LLAvatarAppearanceDefines::SCRATCH_TEX_HEIGHT); preview_target.bindTarget(); preview_target.clear(); diff --git a/indra/newview/llexpandabletextbox.cpp b/indra/newview/llexpandabletextbox.cpp index 748e10160c..093f978e74 100644 --- a/indra/newview/llexpandabletextbox.cpp +++ b/indra/newview/llexpandabletextbox.cpp @@ -42,9 +42,18 @@ public: mEditor(editor), mStyle(style), mExpanderLabel(utf8str_to_wstring(more_text)) - {} + { + } + + /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const + { + LLStyleSP sp((&target == &mEditor) ? mStyle : mStyle->clone()); + LLExpanderSegment* copy = new LLExpanderSegment(sp, mStart, mEnd, LLStringUtil::null, target); + copy->mExpanderLabel = mExpanderLabel; + return copy; + } - /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { // more label always spans width of text box if (num_chars == 0) @@ -59,11 +68,13 @@ public: } return true; } - /*virtual*/ S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const + + /*virtual*/ S32 getOffset(S32 segment_local_x_coord, S32 start_offset, S32 num_chars, bool round) const { return start_offset; } - /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const + + /*virtual*/ S32 getNumChars(S32 num_pixels, S32 segment_offset, S32 line_offset, S32 max_chars, S32 line_ind) const { // require full line to ourselves if (line_offset == 0) @@ -77,7 +88,8 @@ public: return 0; } } - /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) + + /*virtual*/ F32 draw(S32 start, S32 end, S32 selection_start, S32 selection_end, const LLRectf& draw_rect) { F32 right_x; mStyle->getFont()->render(mExpanderLabel, start, @@ -91,6 +103,7 @@ public: mEditor.getUseEllipses(), mEditor.getUseColor()); return right_x; } + /*virtual*/ bool canEdit() const { return false; } // eat handleMouseDown event so we get the mouseup event /*virtual*/ bool handleMouseDown(S32 x, S32 y, MASK mask) { return true; } @@ -100,6 +113,7 @@ public: LLUI::getInstance()->getWindow()->setCursor(UI_CURSOR_HAND); return true; } + private: LLTextBase& mEditor; LLStyleSP mStyle; diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp index ce68474211..f3cb07739d 100644 --- a/indra/newview/llface.cpp +++ b/indra/newview/llface.cpp @@ -58,12 +58,6 @@ #include "llmeshrepository.h" #include "llskinningutil.h" -#if LL_LINUX -// Work-around spurious used before init warning on Vector4a -// -#pragma GCC diagnostic ignored "-Wuninitialized" -#endif - #define LL_MAX_INDICES_COUNT 1000000 static LLStaticHashedString sTextureIndexIn("texture_index_in"); @@ -842,7 +836,6 @@ bool LLFace::genVolumeBBoxes(const LLVolume &volume, S32 f, //VECTORIZE THIS LLMatrix4a mat_vert; mat_vert.loadu(mat_vert_in); - LLVector4a new_extents[2]; llassert(less_than_max_mag(face.mExtents[0])); llassert(less_than_max_mag(face.mExtents[1])); @@ -2268,6 +2261,14 @@ bool LLFace::calcPixelArea(F32& cos_angle_to_view_dir, F32& radius) center.mul(0.5f); size.setSub(mRiggedExtents[1], mRiggedExtents[0]); } + else if (mDrawablep && mVObjp.notNull() && mVObjp->getPartitionType() == LLViewerRegion::PARTITION_PARTICLE && mDrawablep->getSpatialGroup()) + { // use box of spatial group for particles (over approximates size, but we don't actually have a good size per particle) + LLSpatialGroup* group = mDrawablep->getSpatialGroup(); + const LLVector4a* extents = group->getExtents(); + size.setSub(extents[1], extents[0]); + center.setAdd(extents[1], extents[0]); + center.mul(0.5f); + } else { center.load3(getPositionAgent().mV); diff --git a/indra/newview/llfavoritesbar.cpp b/indra/newview/llfavoritesbar.cpp index 377710c170..49fd6a29a2 100644 --- a/indra/newview/llfavoritesbar.cpp +++ b/indra/newview/llfavoritesbar.cpp @@ -430,7 +430,7 @@ LLFavoritesBarCtrl::LLFavoritesBarCtrl(const LLFavoritesBarCtrl::Params& p) { // Register callback for menus with current registrar (will be parent panel's registrar) LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Favorites.DoToSelected", - boost::bind(&LLFavoritesBarCtrl::doToSelected, this, _2)); + { boost::bind(&LLFavoritesBarCtrl::doToSelected, this, _2), cb_info::UNTRUSTED_BLOCK }); // Add this if we need to selectively enable items LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Favorites.EnableSelected", diff --git a/indra/newview/llfeaturemanager.cpp b/indra/newview/llfeaturemanager.cpp index aa04221f4b..fcc3423b4d 100644 --- a/indra/newview/llfeaturemanager.cpp +++ b/indra/newview/llfeaturemanager.cpp @@ -40,6 +40,7 @@ #include "llappviewer.h" #include "llbufferstream.h" +#include "llexception.h" #include "llnotificationsutil.h" #include "llviewercontrol.h" #include "llworld.h" @@ -377,33 +378,6 @@ bool LLFeatureManager::parseFeatureTable(std::string filename) F32 gpu_benchmark(); -#if LL_WINDOWS - -F32 logExceptionBenchmark() -{ - // FIXME: gpu_benchmark uses many C++ classes on the stack to control state. - // SEH exceptions with our current exception handling options do not call - // destructors for these classes, resulting in an undefined state should - // this handler be invoked. - F32 gbps = -1; - __try - { - gbps = gpu_benchmark(); - } - __except (msc_exception_filter(GetExceptionCode(), GetExceptionInformation())) - { - // HACK - ensure that profiling is disabled - LLGLSLShader::finishProfile(false); - - // convert to C++ styled exception - char integer_string[32]; - sprintf(integer_string, "SEH, code: %lu\n", GetExceptionCode()); - throw std::exception(integer_string); - } - return gbps; -} -#endif - bool LLFeatureManager::loadGPUClass() { if (!gSavedSettings.getBOOL("SkipBenchmark")) @@ -413,14 +387,12 @@ bool LLFeatureManager::loadGPUClass() F32 gbps; try { -#if LL_WINDOWS - gbps = logExceptionBenchmark(); -#else - gbps = gpu_benchmark(); -#endif + gbps = LL::seh::catcher(gpu_benchmark); } catch (const std::exception& e) { + // HACK - ensure that profiling is disabled + LLGLSLShader::finishProfile(); gbps = -1.f; LL_WARNS("RenderInit") << "GPU benchmark failed: " << e.what() << LL_ENDL; } @@ -656,6 +628,14 @@ void LLFeatureManager::applyBaseMasks() { maskFeatures("Intel"); } + if (gGLManager.mIsApple) + { + maskFeatures("AppleGPU"); + } + else + { + maskFeatures("NonAppleGPU"); + } if (gGLManager.mGLVersion < 3.f) { maskFeatures("OpenGLPre30"); diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp index 0afb275d13..d5e3627d8e 100644 --- a/indra/newview/llfilepicker.cpp +++ b/indra/newview/llfilepicker.cpp @@ -65,6 +65,7 @@ LLFilePicker LLFilePicker::sInstance; #define MATERIAL_TEXTURES_FILTER L"GLTF Import (*.gltf; *.glb; *.tga; *.bmp; *.jpg; *.jpeg; *.png)\0*.gltf;*.glb;*.tga;*.bmp;*.jpg;*.jpeg;*.png\0" #define SCRIPT_FILTER L"Script files (*.lsl)\0*.lsl\0" #define DICTIONARY_FILTER L"Dictionary files (*.dic; *.xcu)\0*.dic;*.xcu\0" +#define LUA_FILTER L"Script files (*.lua)\0*.lua\0" #endif #ifdef LL_DARWIN @@ -241,6 +242,10 @@ bool LLFilePicker::setupFilter(ELoadFilter filter) mOFN.lpstrFilter = DICTIONARY_FILTER \ L"\0"; break; + case FFLOAD_LUA: + mOFN.lpstrFilter = LUA_FILTER \ + L"\0"; + break; default: res = false; break; diff --git a/indra/newview/llfilepicker.h b/indra/newview/llfilepicker.h index 75ff14f4cf..e0bd32fe70 100644 --- a/indra/newview/llfilepicker.h +++ b/indra/newview/llfilepicker.h @@ -54,19 +54,8 @@ #include <commdlg.h> #endif -extern "C" { -// mostly for Linux, possible on others -#if LL_GTK -# include "gtk/gtk.h" -#endif // LL_GTK -} - class LLFilePicker { -#ifdef LL_GTK - friend class LLDirPicker; - friend void chooser_responder(GtkWidget *, gint, gpointer); -#endif // LL_GTK public: // calling this before main() is undefined static LLFilePicker& instance( void ) { return sInstance; } @@ -90,6 +79,7 @@ public: FFLOAD_MATERIAL = 15, FFLOAD_MATERIAL_TEXTURE = 16, FFLOAD_HDRI = 17, + FFLOAD_LUA = 18, }; enum ESaveFilter @@ -184,14 +174,12 @@ private: void *userdata); #endif -#if LL_GTK - static void add_to_selectedfiles(gpointer data, gpointer user_data); - static void chooser_responder(GtkWidget *widget, gint response, gpointer user_data); - // we remember the last path that was accessed for a particular usage - std::map <std::string, std::string> mContextToPathMap; - std::string mCurContextName; - // we also remember the extension of the last added file. - std::string mCurrentExtension; +#if LL_FLTK + enum EType + { + eSaveFile, eOpenFile, eOpenMultiple + }; + bool openFileDialog( int32_t filter, bool blocking, EType aType ); #endif std::vector<std::string> mFiles; @@ -200,12 +188,6 @@ private: static LLFilePicker sInstance; -protected: -#if LL_GTK - GtkWindow* buildFilePicker(bool is_save, bool is_folder, - std::string context = "generic"); -#endif - public: // don't call these directly please. LLFilePicker(); diff --git a/indra/newview/llfloaterauction.cpp b/indra/newview/llfloaterauction.cpp index a87ddfd76e..4608d3913b 100644 --- a/indra/newview/llfloaterauction.cpp +++ b/indra/newview/llfloaterauction.cpp @@ -75,10 +75,10 @@ LLFloaterAuction::LLFloaterAuction(const LLSD& key) : LLFloater(key), mParcelID(-1) { - mCommitCallbackRegistrar.add("ClickSnapshot", boost::bind(&LLFloaterAuction::onClickSnapshot, this)); - mCommitCallbackRegistrar.add("ClickSellToAnyone", boost::bind(&LLFloaterAuction::onClickSellToAnyone, this)); - mCommitCallbackRegistrar.add("ClickStartAuction", boost::bind(&LLFloaterAuction::onClickStartAuction, this)); - mCommitCallbackRegistrar.add("ClickResetParcel", boost::bind(&LLFloaterAuction::onClickResetParcel, this)); + mCommitCallbackRegistrar.add("ClickSnapshot", { boost::bind(&LLFloaterAuction::onClickSnapshot, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("ClickSellToAnyone", { boost::bind(&LLFloaterAuction::onClickSellToAnyone, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("ClickStartAuction", { boost::bind(&LLFloaterAuction::onClickStartAuction, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("ClickResetParcel", { boost::bind(&LLFloaterAuction::onClickResetParcel, this), cb_info::UNTRUSTED_BLOCK }); } // Destroys the object diff --git a/indra/newview/llfloateravatarpicker.cpp b/indra/newview/llfloateravatarpicker.cpp index 08a54b7369..196c9e8818 100644 --- a/indra/newview/llfloateravatarpicker.cpp +++ b/indra/newview/llfloateravatarpicker.cpp @@ -111,7 +111,7 @@ LLFloaterAvatarPicker::LLFloaterAvatarPicker(const LLSD& key) mContextConeOutAlpha(CONTEXT_CONE_OUT_ALPHA), mContextConeFadeTime(CONTEXT_CONE_FADE_TIME) { - mCommitCallbackRegistrar.add("Refresh.FriendList", boost::bind(&LLFloaterAvatarPicker::populateFriend, this)); + mCommitCallbackRegistrar.add("Refresh.FriendList", { boost::bind(&LLFloaterAvatarPicker::populateFriend, this), cb_info::UNTRUSTED_THROTTLE }); } bool LLFloaterAvatarPicker::postBuild() diff --git a/indra/newview/llfloateravatarrendersettings.cpp b/indra/newview/llfloateravatarrendersettings.cpp index 9101e6eb29..16e2903324 100644 --- a/indra/newview/llfloateravatarrendersettings.cpp +++ b/indra/newview/llfloateravatarrendersettings.cpp @@ -51,7 +51,7 @@ protected: { LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - registrar.add("Settings.SetRendering", boost::bind(&LLFloaterAvatarRenderSettings::onCustomAction, mFloaterSettings, _2, mUUIDs.front())); + registrar.add("Settings.SetRendering", { boost::bind(&LLFloaterAvatarRenderSettings::onCustomAction, mFloaterSettings, _2, mUUIDs.front()) }); enable_registrar.add("Settings.IsSelected", boost::bind(&LLFloaterAvatarRenderSettings::isActionChecked, mFloaterSettings, _2, mUUIDs.front())); LLContextMenu* menu = createFromFile("menu_avatar_rendering_settings.xml"); @@ -75,7 +75,7 @@ LLFloaterAvatarRenderSettings::LLFloaterAvatarRenderSettings(const LLSD& key) { mContextMenu = new LLSettingsContextMenu(this); LLRenderMuteList::getInstance()->addObserver(&sAvatarRenderMuteListObserver); - mCommitCallbackRegistrar.add("Settings.AddNewEntry", boost::bind(&LLFloaterAvatarRenderSettings::onClickAdd, this, _2)); + mCommitCallbackRegistrar.add("Settings.AddNewEntry", {boost::bind(&LLFloaterAvatarRenderSettings::onClickAdd, this, _2)}); } LLFloaterAvatarRenderSettings::~LLFloaterAvatarRenderSettings() diff --git a/indra/newview/llfloaterbeacons.cpp b/indra/newview/llfloaterbeacons.cpp index 11c1c2a9f2..1b975f3553 100644 --- a/indra/newview/llfloaterbeacons.cpp +++ b/indra/newview/llfloaterbeacons.cpp @@ -49,7 +49,7 @@ LLFloaterBeacons::LLFloaterBeacons(const LLSD& seed) LLPipeline::setRenderHighlights( gSavedSettings.getBOOL("renderhighlights")); LLPipeline::setRenderBeacons( gSavedSettings.getBOOL("renderbeacons")); LLPipeline::setRenderMOAPBeacons( gSavedSettings.getBOOL("moapbeacon")); - mCommitCallbackRegistrar.add("Beacons.UICheck", boost::bind(&LLFloaterBeacons::onClickUICheck, this,_1)); + mCommitCallbackRegistrar.add("Beacons.UICheck", { boost::bind(&LLFloaterBeacons::onClickUICheck, this,_1) }); } bool LLFloaterBeacons::postBuild() diff --git a/indra/newview/llfloaterbulkpermission.cpp b/indra/newview/llfloaterbulkpermission.cpp index c09c02d32b..1d670bfb8c 100644 --- a/indra/newview/llfloaterbulkpermission.cpp +++ b/indra/newview/llfloaterbulkpermission.cpp @@ -56,12 +56,12 @@ LLFloaterBulkPermission::LLFloaterBulkPermission(const LLSD& seed) mDone(false) { mID.generate(); - mCommitCallbackRegistrar.add("BulkPermission.Ok", boost::bind(&LLFloaterBulkPermission::onOkBtn, this)); - mCommitCallbackRegistrar.add("BulkPermission.Apply", boost::bind(&LLFloaterBulkPermission::onApplyBtn, this)); - mCommitCallbackRegistrar.add("BulkPermission.Close", boost::bind(&LLFloaterBulkPermission::onCloseBtn, this)); - mCommitCallbackRegistrar.add("BulkPermission.CheckAll", boost::bind(&LLFloaterBulkPermission::onCheckAll, this)); - mCommitCallbackRegistrar.add("BulkPermission.UncheckAll", boost::bind(&LLFloaterBulkPermission::onUncheckAll, this)); - mCommitCallbackRegistrar.add("BulkPermission.CommitCopy", boost::bind(&LLFloaterBulkPermission::onCommitCopy, this)); + mCommitCallbackRegistrar.add("BulkPermission.Ok", { boost::bind(&LLFloaterBulkPermission::onOkBtn, this) }); + mCommitCallbackRegistrar.add("BulkPermission.Apply", { boost::bind(&LLFloaterBulkPermission::onApplyBtn, this) }); + mCommitCallbackRegistrar.add("BulkPermission.Close", { boost::bind(&LLFloaterBulkPermission::onCloseBtn, this) }); + mCommitCallbackRegistrar.add("BulkPermission.CheckAll", { boost::bind(&LLFloaterBulkPermission::onCheckAll, this) }); + mCommitCallbackRegistrar.add("BulkPermission.UncheckAll", { boost::bind(&LLFloaterBulkPermission::onUncheckAll, this) }); + mCommitCallbackRegistrar.add("BulkPermission.CommitCopy", { boost::bind(&LLFloaterBulkPermission::onCommitCopy, this) }); } bool LLFloaterBulkPermission::postBuild() diff --git a/indra/newview/llfloaterbump.cpp b/indra/newview/llfloaterbump.cpp index 162ad5e108..d56e6cdf20 100644 --- a/indra/newview/llfloaterbump.cpp +++ b/indra/newview/llfloaterbump.cpp @@ -51,18 +51,18 @@ LLFloaterBump::LLFloaterBump(const LLSD& key) : LLFloater(key) { - mCommitCallbackRegistrar.add("Avatar.SendIM", boost::bind(&LLFloaterBump::startIM, this)); - mCommitCallbackRegistrar.add("Avatar.ReportAbuse", boost::bind(&LLFloaterBump::reportAbuse, this)); - mCommitCallbackRegistrar.add("ShowAgentProfile", boost::bind(&LLFloaterBump::showProfile, this)); - mCommitCallbackRegistrar.add("Avatar.InviteToGroup", boost::bind(&LLFloaterBump::inviteToGroup, this)); - mCommitCallbackRegistrar.add("Avatar.Call", boost::bind(&LLFloaterBump::startCall, this)); + mCommitCallbackRegistrar.add("Avatar.SendIM", { boost::bind(&LLFloaterBump::startIM, this), cb_info::UNTRUSTED_THROTTLE }); + mCommitCallbackRegistrar.add("Avatar.ReportAbuse", { boost::bind(&LLFloaterBump::reportAbuse, this), cb_info::UNTRUSTED_THROTTLE }); + mCommitCallbackRegistrar.add("ShowAgentProfile", { boost::bind(&LLFloaterBump::showProfile, this), cb_info::UNTRUSTED_THROTTLE }); + mCommitCallbackRegistrar.add("Avatar.InviteToGroup", { boost::bind(&LLFloaterBump::inviteToGroup, this), cb_info::UNTRUSTED_THROTTLE }); + mCommitCallbackRegistrar.add("Avatar.Call", { boost::bind(&LLFloaterBump::startCall, this), cb_info::UNTRUSTED_BLOCK }); mEnableCallbackRegistrar.add("Avatar.EnableCall", boost::bind(&LLAvatarActions::canCall)); - mCommitCallbackRegistrar.add("Avatar.AddFriend", boost::bind(&LLFloaterBump::addFriend, this)); + mCommitCallbackRegistrar.add("Avatar.AddFriend", { boost::bind(&LLFloaterBump::addFriend, this), cb_info::UNTRUSTED_THROTTLE }); mEnableCallbackRegistrar.add("Avatar.EnableAddFriend", boost::bind(&LLFloaterBump::enableAddFriend, this)); - mCommitCallbackRegistrar.add("Avatar.Mute", boost::bind(&LLFloaterBump::muteAvatar, this)); + mCommitCallbackRegistrar.add("Avatar.Mute", { boost::bind(&LLFloaterBump::muteAvatar, this), cb_info::UNTRUSTED_BLOCK }); mEnableCallbackRegistrar.add("Avatar.EnableMute", boost::bind(&LLFloaterBump::enableMute, this)); - mCommitCallbackRegistrar.add("PayObject", boost::bind(&LLFloaterBump::payAvatar, this)); - mCommitCallbackRegistrar.add("Tools.LookAtSelection", boost::bind(&LLFloaterBump::zoomInAvatar, this)); + mCommitCallbackRegistrar.add("PayObject", { boost::bind(&LLFloaterBump::payAvatar, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Tools.LookAtSelection", { boost::bind(&LLFloaterBump::zoomInAvatar, this) }); } diff --git a/indra/newview/llfloaterbuyland.cpp b/indra/newview/llfloaterbuyland.cpp index 11505e3047..a38cc94328 100644 --- a/indra/newview/llfloaterbuyland.cpp +++ b/indra/newview/llfloaterbuyland.cpp @@ -163,6 +163,7 @@ public: void updateParcelInfo(); void updateCovenantInfo(); static void onChangeAgreeCovenant(LLUICtrl* ctrl, void* user_data); + void updateFloaterCovenant(const LLTextBase* source, const LLUUID &asset_id); void updateFloaterCovenantText(const std::string& string, const LLUUID &asset_id); void updateFloaterEstateName(const std::string& name); void updateFloaterLastModified(const std::string& text); @@ -201,6 +202,8 @@ public: void onVisibilityChanged ( const LLSD& new_visibility ); +private: + void onCovenantTextUpdated(const LLUUID& asset_id); }; // static @@ -222,6 +225,15 @@ void LLFloaterBuyLand::buyLand( } // static +void LLFloaterBuyLand::updateCovenant(const LLTextBase* source, const LLUUID& asset_id) +{ + if (LLFloaterBuyLandUI* floater = LLFloaterReg::findTypedInstance<LLFloaterBuyLandUI>("buy_land")) + { + floater->updateFloaterCovenant(source, asset_id); + } +} + +// static void LLFloaterBuyLand::updateCovenantText(const std::string& string, const LLUUID &asset_id) { LLFloaterBuyLandUI* floater = LLFloaterReg::findTypedInstance<LLFloaterBuyLandUI>("buy_land"); @@ -560,11 +572,24 @@ void LLFloaterBuyLandUI::onChangeAgreeCovenant(LLUICtrl* ctrl, void* user_data) } } +void LLFloaterBuyLandUI::updateFloaterCovenant(const LLTextBase* source, const LLUUID& asset_id) +{ + LLViewerTextEditor* editor = getChild<LLViewerTextEditor>("covenant_editor"); + editor->copyContents(source); + + onCovenantTextUpdated(asset_id); +} + void LLFloaterBuyLandUI::updateFloaterCovenantText(const std::string &string, const LLUUID& asset_id) { LLViewerTextEditor* editor = getChild<LLViewerTextEditor>("covenant_editor"); editor->setText(string); + onCovenantTextUpdated(asset_id); +} + +void LLFloaterBuyLandUI::onCovenantTextUpdated(const LLUUID& asset_id) +{ LLCheckBoxCtrl* check = getChild<LLCheckBoxCtrl>("agree_covenant"); LLTextBox* box = getChild<LLTextBox>("covenant_text"); if (asset_id.isNull()) diff --git a/indra/newview/llfloaterbuyland.h b/indra/newview/llfloaterbuyland.h index f750a4017a..732312f10f 100644 --- a/indra/newview/llfloaterbuyland.h +++ b/indra/newview/llfloaterbuyland.h @@ -27,6 +27,7 @@ #ifndef LL_LLFLOATERBUYLAND_H #define LL_LLFLOATERBUYLAND_H +class LLTextBase; class LLFloater; class LLViewerRegion; class LLParcelSelection; @@ -37,6 +38,7 @@ public: static void buyLand(LLViewerRegion* region, LLSafeHandle<LLParcelSelection> parcel, bool is_for_group); + static void updateCovenant(const LLTextBase* source, const LLUUID& asset_id); static void updateCovenantText(const std::string& string, const LLUUID& asset_id); static void updateEstateName(const std::string& name); static void updateLastModified(const std::string& text); diff --git a/indra/newview/llfloaterbvhpreview.cpp b/indra/newview/llfloaterbvhpreview.cpp index 3d81d01e16..b94c31ec04 100644 --- a/indra/newview/llfloaterbvhpreview.cpp +++ b/indra/newview/llfloaterbvhpreview.cpp @@ -406,7 +406,7 @@ void LLFloaterBvhPreview::draw() gGL.getTexUnit(0)->bind(mAnimPreview); - gGL.begin( LLRender::QUADS ); + gGL.begin(LLRender::TRIANGLES); { gGL.texCoord2f(0.f, 1.f); gGL.vertex2i(PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); @@ -414,6 +414,11 @@ void LLFloaterBvhPreview::draw() gGL.vertex2i(PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); gGL.texCoord2f(1.f, 0.f); gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); + + gGL.texCoord2f(0.f, 1.f); + gGL.vertex2i(PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); gGL.texCoord2f(1.f, 1.f); gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); } diff --git a/indra/newview/llfloatercamera.cpp b/indra/newview/llfloatercamera.cpp index 4a5a755696..2322103009 100644 --- a/indra/newview/llfloatercamera.cpp +++ b/indra/newview/llfloatercamera.cpp @@ -169,11 +169,11 @@ static LLPanelInjector<LLPanelCameraZoom> t_camera_zoom_panel("camera_zoom_panel void LLPanelCameraZoom::onCreate() { - mCommitCallbackRegistrar.add("Zoom.minus", boost::bind(&LLPanelCameraZoom::onZoomMinusHeldDown, this)); - mCommitCallbackRegistrar.add("Zoom.plus", boost::bind(&LLPanelCameraZoom::onZoomPlusHeldDown, this)); - mCommitCallbackRegistrar.add("Slider.value_changed", boost::bind(&LLPanelCameraZoom::onSliderValueChanged, this)); - mCommitCallbackRegistrar.add("Camera.track", boost::bind(&LLPanelCameraZoom::onCameraTrack, this)); - mCommitCallbackRegistrar.add("Camera.rotate", boost::bind(&LLPanelCameraZoom::onCameraRotate, this)); + mCommitCallbackRegistrar.add("Zoom.minus", { boost::bind(&LLPanelCameraZoom::onZoomMinusHeldDown, this) }); + mCommitCallbackRegistrar.add("Zoom.plus", { boost::bind(&LLPanelCameraZoom::onZoomPlusHeldDown, this) }); + mCommitCallbackRegistrar.add("Slider.value_changed", { boost::bind(&LLPanelCameraZoom::onSliderValueChanged, this) }); + mCommitCallbackRegistrar.add("Camera.track", { boost::bind(&LLPanelCameraZoom::onCameraTrack, this) }); + mCommitCallbackRegistrar.add("Camera.rotate", { boost::bind(&LLPanelCameraZoom::onCameraRotate, this) }); } bool LLPanelCameraZoom::postBuild() @@ -461,9 +461,9 @@ LLFloaterCamera::LLFloaterCamera(const LLSD& val) mPrevMode(CAMERA_CTRL_MODE_PAN) { LLHints::getInstance()->registerHintTarget("view_popup", getHandle()); - mCommitCallbackRegistrar.add("CameraPresets.ChangeView", boost::bind(&LLFloaterCamera::onClickCameraItem, _2)); - mCommitCallbackRegistrar.add("CameraPresets.Save", boost::bind(&LLFloaterCamera::onSavePreset, this)); - mCommitCallbackRegistrar.add("CameraPresets.ShowPresetsList", boost::bind(&LLFloaterReg::showInstance, "camera_presets", LLSD(), false)); + mCommitCallbackRegistrar.add("CameraPresets.ChangeView", {boost::bind(&LLFloaterCamera::onClickCameraItem, _2)}); + mCommitCallbackRegistrar.add("CameraPresets.Save", {boost::bind(&LLFloaterCamera::onSavePreset, this)}); + mCommitCallbackRegistrar.add("CameraPresets.ShowPresetsList", {boost::bind(&LLFloaterReg::showInstance, "camera_presets", LLSD(), false)}); } // virtual diff --git a/indra/newview/llfloatercamerapresets.cpp b/indra/newview/llfloatercamerapresets.cpp index b033af2564..d527541b76 100644 --- a/indra/newview/llfloatercamerapresets.cpp +++ b/indra/newview/llfloatercamerapresets.cpp @@ -90,8 +90,8 @@ LLCameraPresetFlatItem::LLCameraPresetFlatItem(const std::string &preset_name, b mPresetName(preset_name), mIsDefaultPrest(is_default) { - mCommitCallbackRegistrar.add("CameraPresets.Delete", boost::bind(&LLCameraPresetFlatItem::onDeleteBtnClick, this)); - mCommitCallbackRegistrar.add("CameraPresets.Reset", boost::bind(&LLCameraPresetFlatItem::onResetBtnClick, this)); + mCommitCallbackRegistrar.add("CameraPresets.Delete", { boost::bind(&LLCameraPresetFlatItem::onDeleteBtnClick, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("CameraPresets.Reset", { boost::bind(&LLCameraPresetFlatItem::onResetBtnClick, this), cb_info::UNTRUSTED_BLOCK }); buildFromFile("panel_camera_preset_item.xml"); } diff --git a/indra/newview/llfloaterconversationlog.cpp b/indra/newview/llfloaterconversationlog.cpp index 97399c9cf7..dc0f4ae555 100644 --- a/indra/newview/llfloaterconversationlog.cpp +++ b/indra/newview/llfloaterconversationlog.cpp @@ -35,7 +35,7 @@ LLFloaterConversationLog::LLFloaterConversationLog(const LLSD& key) : LLFloater(key), mConversationLogList(NULL) { - mCommitCallbackRegistrar.add("CallLog.Action", boost::bind(&LLFloaterConversationLog::onCustomAction, this, _2)); + mCommitCallbackRegistrar.add("CallLog.Action", { boost::bind(&LLFloaterConversationLog::onCustomAction, this, _2) }); mEnableCallbackRegistrar.add("CallLog.Check", boost::bind(&LLFloaterConversationLog::isActionChecked, this, _2)); } diff --git a/indra/newview/llfloatercreatelandmark.cpp b/indra/newview/llfloatercreatelandmark.cpp index 2ce8a7a212..77ea544c5d 100644 --- a/indra/newview/llfloatercreatelandmark.cpp +++ b/indra/newview/llfloatercreatelandmark.cpp @@ -31,6 +31,7 @@ #include "llagent.h" #include "llagentui.h" #include "llcombobox.h" +#include "llfloaterreg.h" #include "llinventoryfunctions.h" #include "llinventoryobserver.h" #include "lllandmarkactions.h" @@ -286,8 +287,7 @@ void LLFloaterCreateLandmark::onCreateFolderClicked() std::string folder_name = resp["message"].asString(); if (!folder_name.empty()) { - inventory_func_type func = boost::bind(&LLFloaterCreateLandmark::folderCreatedCallback, this, _1); - gInventory.createNewCategory(mLandmarksID, LLFolderType::FT_NONE, folder_name, func); + gInventory.createNewCategory(mLandmarksID, LLFolderType::FT_NONE, folder_name, folderCreatedCallback); gInventory.notifyObservers(); } } @@ -296,7 +296,11 @@ void LLFloaterCreateLandmark::onCreateFolderClicked() void LLFloaterCreateLandmark::folderCreatedCallback(LLUUID folder_id) { - populateFoldersList(folder_id); + LLFloaterCreateLandmark* floater = LLFloaterReg::findTypedInstance<LLFloaterCreateLandmark>("add_landmark"); + if (floater && !floater->isDead()) + { + floater->populateFoldersList(folder_id); + } } void LLFloaterCreateLandmark::onSaveClicked() @@ -389,6 +393,7 @@ void LLFloaterCreateLandmark::setItem(const uuid_set_t& items) { mItem = item; mAssetID = mItem->getAssetUUID(); + mParentID = mItem->getParentUUID(); setVisibleAndFrontmost(true); break; } @@ -418,8 +423,7 @@ void LLFloaterCreateLandmark::updateItem(const uuid_set_t& items, U32 mask) closeFloater(); } - LLUUID folder_id = mFolderCombo->getValue().asUUID(); - if (folder_id != mItem->getParentUUID()) + if (mParentID != mItem->getParentUUID()) { // user moved landmark in inventory, // assume that we are done all other changes should already be commited diff --git a/indra/newview/llfloatercreatelandmark.h b/indra/newview/llfloatercreatelandmark.h index fa6d001b8e..bcf9d8578c 100644 --- a/indra/newview/llfloatercreatelandmark.h +++ b/indra/newview/llfloatercreatelandmark.h @@ -62,13 +62,14 @@ private: void onSaveClicked(); void onCancelClicked(); - void folderCreatedCallback(LLUUID folder_id); + static void folderCreatedCallback(LLUUID folder_id); LLComboBox* mFolderCombo; LLLineEditor* mLandmarkTitleEditor; LLTextEditor* mNotesEditor; LLUUID mLandmarksID; LLUUID mAssetID; + LLUUID mParentID; LLLandmarksInventoryObserver* mInventoryObserver; LLPointer<LLInventoryItem> mItem; diff --git a/indra/newview/llfloatereditextdaycycle.cpp b/indra/newview/llfloatereditextdaycycle.cpp index fd58cd8aaf..626f06ce7e 100644 --- a/indra/newview/llfloatereditextdaycycle.cpp +++ b/indra/newview/llfloatereditextdaycycle.cpp @@ -183,8 +183,8 @@ LLFloaterEditExtDayCycle::LLFloaterEditExtDayCycle(const LLSD &key) : mLoadTrack(nullptr), mClearTrack(nullptr) { - mCommitCallbackRegistrar.add(EVNT_DAYTRACK, [this](LLUICtrl *ctrl, const LLSD &data) { onTrackSelectionCallback(data); }); - mCommitCallbackRegistrar.add(EVNT_PLAY, [this](LLUICtrl *ctrl, const LLSD &data) { onPlayActionCallback(data); }); + mCommitCallbackRegistrar.add(EVNT_DAYTRACK, { [this](LLUICtrl *ctrl, const LLSD &data) { onTrackSelectionCallback(data); }}); + mCommitCallbackRegistrar.add(EVNT_PLAY, { [this](LLUICtrl *ctrl, const LLSD &data) { onPlayActionCallback(data); }}); mScratchSky = LLSettingsVOSky::buildDefaultSky(); mScratchWater = LLSettingsVOWater::buildDefaultWater(); diff --git a/indra/newview/llfloateremojipicker.cpp b/indra/newview/llfloateremojipicker.cpp index cc13e5d059..ffbda0265d 100644 --- a/indra/newview/llfloateremojipicker.cpp +++ b/indra/newview/llfloateremojipicker.cpp @@ -57,8 +57,7 @@ static const S32 USED_EMOJIS_IMAGE_INDEX = 0x23F2; // https://www.compart.com/en/unicode/U+1F6D1 static const S32 EMPTY_LIST_IMAGE_INDEX = 0x1F6D1; // The following categories should follow the required alphabetic order -static const std::string RECENTLY_USED_CATEGORY = "1 recently used"; -static const std::string FREQUENTLY_USED_CATEGORY = "2 frequently used"; +static const std::string FREQUENTLY_USED_CATEGORY = "frequently used"; // Floater state related variables static std::list<llwchar> sRecentlyUsed; @@ -436,6 +435,7 @@ void LLFloaterEmojiPicker::fillGroups() LLButton::Params params; params.font = LLFontGL::getFontEmojiLarge(); + params.name = "all_categories"; LLRect rect; rect.mTop = mGroups->getRect().getHeight(); @@ -445,11 +445,10 @@ void LLFloaterEmojiPicker::fillGroups() params.name = "all_categories"; createGroupButton(params, rect, ALL_EMOJIS_IMAGE_INDEX); - // Create group and button for "Recently used" and/or "Frequently used" - if (!sRecentlyUsed.empty() || !sFrequentlyUsed.empty()) + // Create group and button for "Frequently used" + if (!sFrequentlyUsed.empty()) { std::map<std::string, std::vector<LLEmojiSearchResult>> cats; - fillCategoryRecentlyUsed(cats); fillCategoryFrequentlyUsed(cats); if (!cats.empty()) @@ -482,40 +481,6 @@ void LLFloaterEmojiPicker::fillGroups() resizeGroupButtons(); } -void LLFloaterEmojiPicker::fillCategoryRecentlyUsed(std::map<std::string, std::vector<LLEmojiSearchResult>>& cats) -{ - if (sRecentlyUsed.empty()) - return; - - std::vector<LLEmojiSearchResult> emojis; - - // In case of empty mFilterPattern we'd use sRecentlyUsed directly - if (!mFilterPattern.empty()) - { - // List all emojis in "Recently used" - const LLEmojiDictionary::emoji2descr_map_t& emoji2descr = LLEmojiDictionary::instance().getEmoji2Descr(); - std::size_t begin, end; - for (llwchar emoji : sRecentlyUsed) - { - auto e2d = emoji2descr.find(emoji); - if (e2d != emoji2descr.end() && !e2d->second->ShortCodes.empty()) - { - for (const std::string& shortcode : e2d->second->ShortCodes) - { - if (LLEmojiDictionary::searchInShortCode(begin, end, shortcode, mFilterPattern)) - { - emojis.emplace_back(emoji, shortcode, begin, end); - } - } - } - } - if (emojis.empty()) - return; - } - - cats.emplace(std::make_pair(RECENTLY_USED_CATEGORY, emojis)); -} - void LLFloaterEmojiPicker::fillCategoryFrequentlyUsed(std::map<std::string, std::vector<LLEmojiSearchResult>>& cats) { if (sFrequentlyUsed.empty()) @@ -756,7 +721,6 @@ void LLFloaterEmojiPicker::fillEmojisCategory(const std::vector<LLEmojiSearchRes { // Place the category title std::string title = - category == RECENTLY_USED_CATEGORY ? getString("title_for_recently_used") : category == FREQUENTLY_USED_CATEGORY ? getString("title_for_frequently_used") : isupper(category.front()) ? category : LLStringUtil::capitalize(category); LLEmojiGridDivider* div = new LLEmojiGridDivider(row_panel_params, title); @@ -769,21 +733,7 @@ void LLFloaterEmojiPicker::fillEmojisCategory(const std::vector<LLEmojiSearchRes { const LLEmojiDictionary::emoji2descr_map_t& emoji2descr = LLEmojiDictionary::instance().getEmoji2Descr(); LLEmojiSearchResult emoji { 0, "", 0, 0 }; - if (category == RECENTLY_USED_CATEGORY) - { - for (llwchar code : sRecentlyUsed) - { - const LLEmojiDictionary::emoji2descr_map_t::const_iterator& e2d = emoji2descr.find(code); - if (e2d != emoji2descr.end() && !e2d->second->ShortCodes.empty()) - { - emoji.Character = code; - emoji.String = e2d->second->ShortCodes.front(); - createEmojiIcon(emoji, category, row_panel_params, row_list_params, icon_params, - icon_rect, max_icons, bg, row, icon_index); - } - } - } - else if (category == FREQUENTLY_USED_CATEGORY) + if (category == FREQUENTLY_USED_CATEGORY) { for (const auto& code : sFrequentlyUsed) { diff --git a/indra/newview/llfloateremojipicker.h b/indra/newview/llfloateremojipicker.h index 669683eb9e..b807adb67d 100644 --- a/indra/newview/llfloateremojipicker.h +++ b/indra/newview/llfloateremojipicker.h @@ -60,7 +60,6 @@ public: private: void initialize(); void fillGroups(); - void fillCategoryRecentlyUsed(std::map<std::string, std::vector<LLEmojiSearchResult>>& cats); void fillCategoryFrequentlyUsed(std::map<std::string, std::vector<LLEmojiSearchResult>>& cats); void fillGroupEmojis(std::map<std::string, std::vector<LLEmojiSearchResult>>& cats, U32 index); void createGroupButton(LLButton::Params& params, const LLRect& rect, llwchar emoji); diff --git a/indra/newview/llfloatergesture.cpp b/indra/newview/llfloatergesture.cpp index 936096d8fe..0ff958c19b 100644 --- a/indra/newview/llfloatergesture.cpp +++ b/indra/newview/llfloatergesture.cpp @@ -122,11 +122,11 @@ LLFloaterGesture::LLFloaterGesture(const LLSD& key) mObserver = new LLFloaterGestureObserver(this); LLGestureMgr::instance().addObserver(mObserver); - mCommitCallbackRegistrar.add("Gesture.Action.ToggleActiveState", boost::bind(&LLFloaterGesture::onActivateBtnClick, this)); - mCommitCallbackRegistrar.add("Gesture.Action.ShowPreview", boost::bind(&LLFloaterGesture::onClickEdit, this)); - mCommitCallbackRegistrar.add("Gesture.Action.CopyPaste", boost::bind(&LLFloaterGesture::onCopyPasteAction, this, _2)); - mCommitCallbackRegistrar.add("Gesture.Action.SaveToCOF", boost::bind(&LLFloaterGesture::addToCurrentOutFit, this)); - mCommitCallbackRegistrar.add("Gesture.Action.Rename", boost::bind(&LLFloaterGesture::onRenameSelected, this)); + mCommitCallbackRegistrar.add("Gesture.Action.ToggleActiveState", { boost::bind(&LLFloaterGesture::onActivateBtnClick, this) }); + mCommitCallbackRegistrar.add("Gesture.Action.ShowPreview", { boost::bind(&LLFloaterGesture::onClickEdit, this) }); + mCommitCallbackRegistrar.add("Gesture.Action.CopyPaste", { boost::bind(&LLFloaterGesture::onCopyPasteAction, this, _2) }); + mCommitCallbackRegistrar.add("Gesture.Action.SaveToCOF", { boost::bind(&LLFloaterGesture::addToCurrentOutFit, this) }); + mCommitCallbackRegistrar.add("Gesture.Action.Rename", { boost::bind(&LLFloaterGesture::onRenameSelected, this) }); mEnableCallbackRegistrar.add("Gesture.EnableAction", boost::bind(&LLFloaterGesture::isActionEnabled, this, _2)); } diff --git a/indra/newview/llfloatergltfasseteditor.cpp b/indra/newview/llfloatergltfasseteditor.cpp index d2cf24f1dd..002fb3996f 100644 --- a/indra/newview/llfloatergltfasseteditor.cpp +++ b/indra/newview/llfloatergltfasseteditor.cpp @@ -46,8 +46,8 @@ LLFloaterGLTFAssetEditor::LLFloaterGLTFAssetEditor(const LLSD& key) { setTitle("GLTF Asset Editor (WIP)"); - mCommitCallbackRegistrar.add("PanelObject.menuDoToSelected", [this](LLUICtrl* ctrl, const LLSD& data) { onMenuDoToSelected(data); }); - mEnableCallbackRegistrar.add("PanelObject.menuEnable", [this](LLUICtrl* ctrl, const LLSD& data) { return onMenuEnableItem(data); }); + mCommitCallbackRegistrar.add("PanelObject.menuDoToSelected", { [this](LLUICtrl* ctrl, const LLSD& data) { onMenuDoToSelected(data); }}); + mEnableCallbackRegistrar.add("PanelObject.menuEnable", { [this](LLUICtrl* ctrl, const LLSD& data) { return onMenuEnableItem(data); }}); } LLFloaterGLTFAssetEditor::~LLFloaterGLTFAssetEditor() @@ -209,7 +209,7 @@ void LLFloaterGLTFAssetEditor::loadFromNode(S32 node_id, LLFolderViewFolder* par std::string name = node.mName; if (node.mName.empty()) { - name = getString("node_tittle"); + name = getString("node_title"); } else { @@ -243,7 +243,7 @@ void LLFloaterGLTFAssetEditor::loadFromNode(S32 node_id, LLFolderViewFolder* par std::string name = mAsset->mMeshes[node.mMesh].mName; if (name.empty()) { - name = getString("mesh_tittle"); + name = getString("mesh_title"); } loadItem(node.mMesh, name, LLGLTFFolderItem::TYPE_MESH, view); } @@ -253,7 +253,7 @@ void LLFloaterGLTFAssetEditor::loadFromNode(S32 node_id, LLFolderViewFolder* par std::string name = mAsset->mSkins[node.mSkin].mName; if (name.empty()) { - name = getString("skin_tittle"); + name = getString("skin_title"); } loadItem(node.mSkin, name, LLGLTFFolderItem::TYPE_SKIN, view); } @@ -306,7 +306,7 @@ void LLFloaterGLTFAssetEditor::loadFromSelection() std::string name = scene.mName; if (scene.mName.empty()) { - name = getString("scene_tittle"); + name = getString("scene_title"); } else { diff --git a/indra/newview/llfloatergodtools.cpp b/indra/newview/llfloatergodtools.cpp index 6c1d5b5cca..f2884c3a6e 100644 --- a/indra/newview/llfloatergodtools.cpp +++ b/indra/newview/llfloatergodtools.cpp @@ -431,15 +431,15 @@ const F32 PRICE_PER_METER_DEFAULT = 1.f; LLPanelRegionTools::LLPanelRegionTools() : LLPanel() { - mCommitCallbackRegistrar.add("RegionTools.ChangeAnything", boost::bind(&LLPanelRegionTools::onChangeAnything, this)); - mCommitCallbackRegistrar.add("RegionTools.ChangePrelude", boost::bind(&LLPanelRegionTools::onChangePrelude, this)); - mCommitCallbackRegistrar.add("RegionTools.BakeTerrain", boost::bind(&LLPanelRegionTools::onBakeTerrain, this)); - mCommitCallbackRegistrar.add("RegionTools.RevertTerrain", boost::bind(&LLPanelRegionTools::onRevertTerrain, this)); - mCommitCallbackRegistrar.add("RegionTools.SwapTerrain", boost::bind(&LLPanelRegionTools::onSwapTerrain, this)); - mCommitCallbackRegistrar.add("RegionTools.Refresh", boost::bind(&LLPanelRegionTools::onRefresh, this)); - mCommitCallbackRegistrar.add("RegionTools.ApplyChanges", boost::bind(&LLPanelRegionTools::onApplyChanges, this)); - mCommitCallbackRegistrar.add("RegionTools.SelectRegion", boost::bind(&LLPanelRegionTools::onSelectRegion, this)); - mCommitCallbackRegistrar.add("RegionTools.SaveState", boost::bind(&LLPanelRegionTools::onSaveState, this)); + mCommitCallbackRegistrar.add("RegionTools.ChangeAnything", { boost::bind(&LLPanelRegionTools::onChangeAnything, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("RegionTools.ChangePrelude", { boost::bind(&LLPanelRegionTools::onChangePrelude, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("RegionTools.BakeTerrain", { boost::bind(&LLPanelRegionTools::onBakeTerrain, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("RegionTools.RevertTerrain", { boost::bind(&LLPanelRegionTools::onRevertTerrain, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("RegionTools.SwapTerrain", { boost::bind(&LLPanelRegionTools::onSwapTerrain, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("RegionTools.Refresh", { boost::bind(&LLPanelRegionTools::onRefresh, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("RegionTools.ApplyChanges", { boost::bind(&LLPanelRegionTools::onApplyChanges, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("RegionTools.SelectRegion", { boost::bind(&LLPanelRegionTools::onSelectRegion, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("RegionTools.SaveState", { boost::bind(&LLPanelRegionTools::onSaveState, this), cb_info::UNTRUSTED_BLOCK }); } bool LLPanelRegionTools::postBuild() @@ -854,7 +854,8 @@ void LLPanelRegionTools::onSelectRegion() LLPanelGridTools::LLPanelGridTools() : LLPanel() { - mCommitCallbackRegistrar.add("GridTools.FlushMapVisibilityCaches", boost::bind(&LLPanelGridTools::onClickFlushMapVisibilityCaches, this)); + mCommitCallbackRegistrar.add("GridTools.FlushMapVisibilityCaches", + { boost::bind(&LLPanelGridTools::onClickFlushMapVisibilityCaches, this), cb_info::UNTRUSTED_BLOCK }); } // Destroys the object @@ -929,15 +930,15 @@ LLPanelObjectTools::LLPanelObjectTools() : LLPanel(), mTargetAvatar() { - mCommitCallbackRegistrar.add("ObjectTools.ChangeAnything", boost::bind(&LLPanelObjectTools::onChangeAnything, this)); - mCommitCallbackRegistrar.add("ObjectTools.DeletePublicOwnedBy", boost::bind(&LLPanelObjectTools::onClickDeletePublicOwnedBy, this)); - mCommitCallbackRegistrar.add("ObjectTools.DeleteAllScriptedOwnedBy", boost::bind(&LLPanelObjectTools::onClickDeleteAllScriptedOwnedBy, this)); - mCommitCallbackRegistrar.add("ObjectTools.DeleteAllOwnedBy", boost::bind(&LLPanelObjectTools::onClickDeleteAllOwnedBy, this)); - mCommitCallbackRegistrar.add("ObjectTools.ApplyChanges", boost::bind(&LLPanelObjectTools::onApplyChanges, this)); - mCommitCallbackRegistrar.add("ObjectTools.Set", boost::bind(&LLPanelObjectTools::onClickSet, this)); - mCommitCallbackRegistrar.add("ObjectTools.GetTopColliders", boost::bind(&LLPanelObjectTools::onGetTopColliders, this)); - mCommitCallbackRegistrar.add("ObjectTools.GetTopScripts", boost::bind(&LLPanelObjectTools::onGetTopScripts, this)); - mCommitCallbackRegistrar.add("ObjectTools.GetScriptDigest", boost::bind(&LLPanelObjectTools::onGetScriptDigest, this)); + mCommitCallbackRegistrar.add("ObjectTools.ChangeAnything", { boost::bind(&LLPanelObjectTools::onChangeAnything, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("ObjectTools.DeletePublicOwnedBy", { boost::bind(&LLPanelObjectTools::onClickDeletePublicOwnedBy, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("ObjectTools.DeleteAllScriptedOwnedBy", { boost::bind(&LLPanelObjectTools::onClickDeleteAllScriptedOwnedBy, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("ObjectTools.DeleteAllOwnedBy", { boost::bind(&LLPanelObjectTools::onClickDeleteAllOwnedBy, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("ObjectTools.ApplyChanges", { boost::bind(&LLPanelObjectTools::onApplyChanges, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("ObjectTools.Set", { boost::bind(&LLPanelObjectTools::onClickSet, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("ObjectTools.GetTopColliders", { boost::bind(&LLPanelObjectTools::onGetTopColliders, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("ObjectTools.GetTopScripts", { boost::bind(&LLPanelObjectTools::onGetTopScripts, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("ObjectTools.GetScriptDigest", { boost::bind(&LLPanelObjectTools::onGetScriptDigest, this), cb_info::UNTRUSTED_BLOCK }); } // Destroys the object @@ -1224,7 +1225,7 @@ const std::string AGENT_REGION = "Agent Region"; LLPanelRequestTools::LLPanelRequestTools(): LLPanel() { - mCommitCallbackRegistrar.add("GodTools.Request", boost::bind(&LLPanelRequestTools::onClickRequest, this)); + mCommitCallbackRegistrar.add("GodTools.Request", {boost::bind(&LLPanelRequestTools::onClickRequest, this), cb_info::UNTRUSTED_BLOCK }); } LLPanelRequestTools::~LLPanelRequestTools() diff --git a/indra/newview/llfloaterimagepreview.cpp b/indra/newview/llfloaterimagepreview.cpp index 711c33e73d..989e1d8d04 100644 --- a/indra/newview/llfloaterimagepreview.cpp +++ b/indra/newview/llfloaterimagepreview.cpp @@ -285,7 +285,7 @@ void LLFloaterImagePreview::draw() } gGL.color3f(1.f, 1.f, 1.f); - gGL.begin( LLRender::QUADS ); + gGL.begin(LLRender::TRIANGLES); { gGL.texCoord2f(mPreviewImageRect.mLeft, mPreviewImageRect.mTop); gGL.vertex2i(PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); @@ -293,6 +293,11 @@ void LLFloaterImagePreview::draw() gGL.vertex2i(PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); gGL.texCoord2f(mPreviewImageRect.mRight, mPreviewImageRect.mBottom); gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); + + gGL.texCoord2f(mPreviewImageRect.mRight, mPreviewImageRect.mBottom); + gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); + gGL.texCoord2f(mPreviewImageRect.mLeft, mPreviewImageRect.mTop); + gGL.vertex2i(PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); gGL.texCoord2f(mPreviewImageRect.mRight, mPreviewImageRect.mTop); gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); } @@ -317,7 +322,7 @@ void LLFloaterImagePreview::draw() gGL.getTexUnit(0)->bind(mAvatarPreview); } - gGL.begin( LLRender::QUADS ); + gGL.begin(LLRender::TRIANGLES); { gGL.texCoord2f(0.f, 1.f); gGL.vertex2i(PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); @@ -325,6 +330,11 @@ void LLFloaterImagePreview::draw() gGL.vertex2i(PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); gGL.texCoord2f(1.f, 0.f); gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); + + gGL.texCoord2f(1.f, 0.f); + gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_HPAD + PREF_BUTTON_HEIGHT + PREVIEW_HPAD); + gGL.texCoord2f(0.f, 1.f); + gGL.vertex2i(PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); gGL.texCoord2f(1.f, 1.f); gGL.vertex2i(r.getWidth() - PREVIEW_HPAD, PREVIEW_TEXTURE_HEIGHT + PREVIEW_VPAD); } diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp index e55bf50724..1e2d790cfc 100644 --- a/indra/newview/llfloaterimcontainer.cpp +++ b/indra/newview/llfloaterimcontainer.cpp @@ -76,14 +76,14 @@ LLFloaterIMContainer::LLFloaterIMContainer(const LLSD& seed, const Params& param mConversationEventQueue() { mEnableCallbackRegistrar.add("IMFloaterContainer.Check", boost::bind(&LLFloaterIMContainer::isActionChecked, this, _2)); - mCommitCallbackRegistrar.add("IMFloaterContainer.Action", boost::bind(&LLFloaterIMContainer::onCustomAction, this, _2)); + mCommitCallbackRegistrar.add("IMFloaterContainer.Action", { boost::bind(&LLFloaterIMContainer::onCustomAction, this, _2) }); mEnableCallbackRegistrar.add("Avatar.CheckItem", boost::bind(&LLFloaterIMContainer::checkContextMenuItem, this, _2)); mEnableCallbackRegistrar.add("Avatar.EnableItem", boost::bind(&LLFloaterIMContainer::enableContextMenuItem, this, _2)); mEnableCallbackRegistrar.add("Avatar.VisibleItem", boost::bind(&LLFloaterIMContainer::visibleContextMenuItem, this, _2)); - mCommitCallbackRegistrar.add("Avatar.DoToSelected", boost::bind(&LLFloaterIMContainer::doToSelected, this, _2)); + mCommitCallbackRegistrar.add("Avatar.DoToSelected", { boost::bind(&LLFloaterIMContainer::doToSelected, this, _2) }); - mCommitCallbackRegistrar.add("Group.DoToSelected", boost::bind(&LLFloaterIMContainer::doToSelectedGroup, this, _2)); + mCommitCallbackRegistrar.add("Group.DoToSelected", { boost::bind(&LLFloaterIMContainer::doToSelectedGroup, this, _2) }); // Firstly add our self to IMSession observers, so we catch session events LLIMMgr::getInstance()->addSessionObserver(this); diff --git a/indra/newview/llfloaterimnearbychat.cpp b/indra/newview/llfloaterimnearbychat.cpp index 28c651f0cd..6c3e8391cd 100644 --- a/indra/newview/llfloaterimnearbychat.cpp +++ b/indra/newview/llfloaterimnearbychat.cpp @@ -52,6 +52,7 @@ #include "llfirstuse.h" #include "llfloaterimnearbychat.h" +#include "llfloaterimnearbychatlistener.h" #include "llagent.h" // gAgent #include "llgesturemgr.h" #include "llmultigesture.h" @@ -71,6 +72,8 @@ S32 LLFloaterIMNearbyChat::sLastSpecialChatChannel = 0; +static LLFloaterIMNearbyChatListener sChatListener; + constexpr S32 EXPANDED_HEIGHT = 266; constexpr S32 COLLAPSED_HEIGHT = 60; constexpr S32 EXPANDED_MIN_HEIGHT = 150; @@ -106,7 +109,7 @@ LLFloaterIMNearbyChat::LLFloaterIMNearbyChat(const LLSD& llsd) // Required by LLFloaterIMSessionTab::mGearBtn // But nearby floater has no 'per agent' menu items, mEnableCallbackRegistrar.add("Avatar.EnableGearItem", boost::bind(&cb_do_nothing)); - mCommitCallbackRegistrar.add("Avatar.GearDoToSelected", boost::bind(&cb_do_nothing)); + mCommitCallbackRegistrar.add("Avatar.GearDoToSelected", { boost::bind(&cb_do_nothing) }); mEnableCallbackRegistrar.add("Avatar.CheckGearItem", boost::bind(&cb_do_nothing)); mMinFloaterHeight = EXPANDED_MIN_HEIGHT; diff --git a/indra/newview/llfloaterimnearbychathandler.cpp b/indra/newview/llfloaterimnearbychathandler.cpp index c920a3c898..20bf896562 100644 --- a/indra/newview/llfloaterimnearbychathandler.cpp +++ b/indra/newview/llfloaterimnearbychathandler.cpp @@ -300,12 +300,13 @@ void LLFloaterIMNearbyChatScreenChannel::addChat(LLSD& chat) { LLUUID fromID = chat["from_id"].asUUID(); // agent id or object id std::string from = chat["from"].asString(); + bool is_lua = chat["is_lua"].asBoolean(); LLToast* toast = m_active_toasts[0].get(); if (toast) { LLFloaterIMNearbyChatToastPanel* panel = dynamic_cast<LLFloaterIMNearbyChatToastPanel*>(toast->getPanel()); - if (panel && panel->messageID() == fromID && panel->getFromName() == from && panel->canAddText()) + if (panel && panel->messageID() == fromID && panel->getFromName() == from && panel->isFromScript() == is_lua && panel->canAddText()) { panel->addMessage(chat); toast->reshapeToPanel(); @@ -596,17 +597,22 @@ void LLFloaterIMNearbyChatHandler::processChat(const LLChat& chat_msg, { // Handle IRC styled messages. std::string toast_msg; + std::string msg_text = without_LUA_PREFIX(chat_msg.mText, chat_msg.mIsScript); if (chat_msg.mChatStyle == CHAT_STYLE_IRC) { + if (chat_msg.mIsScript) + { + toast_msg += LLTrans::getString("ScriptStr"); + } if (!chat_msg.mFromName.empty()) { toast_msg += chat_msg.mFromName; } - toast_msg += chat_msg.mText.substr(3); + toast_msg += msg_text.substr(3); } else { - toast_msg = chat_msg.mText; + toast_msg = msg_text; } bool chat_overlaps = false; @@ -666,6 +672,7 @@ void LLFloaterIMNearbyChatHandler::processChat(const LLChat& chat_msg, chat["color_alpha"] = r_color_alpha; chat["font_size"] = (S32)LLViewerChat::getChatFontSize() ; chat["message"] = toast_msg; + chat["is_lua"] = chat_msg.mIsScript; channel->addChat(chat); } diff --git a/indra/newview/llfloaterimnearbychatlistener.cpp b/indra/newview/llfloaterimnearbychatlistener.cpp index 43173d3680..0618741cc4 100644 --- a/indra/newview/llfloaterimnearbychatlistener.cpp +++ b/indra/newview/llfloaterimnearbychatlistener.cpp @@ -34,12 +34,14 @@ #include "llagent.h" #include "llchat.h" #include "llviewercontrol.h" +#include "stringize.h" +static const F32 CHAT_THROTTLE_PERIOD = 1.f; -LLFloaterIMNearbyChatListener::LLFloaterIMNearbyChatListener(LLFloaterIMNearbyChat & chatbar) +LLFloaterIMNearbyChatListener::LLFloaterIMNearbyChatListener() : LLEventAPI("LLChatBar", "LLChatBar listener to (e.g.) sendChat, etc."), - mChatbar(chatbar) + mLastThrottleTime(0) { add("sendChat", "Send chat to the simulator:\n" @@ -51,10 +53,19 @@ LLFloaterIMNearbyChatListener::LLFloaterIMNearbyChatListener(LLFloaterIMNearbyCh // "sendChat" command -void LLFloaterIMNearbyChatListener::sendChat(LLSD const & chat_data) const +void LLFloaterIMNearbyChatListener::sendChat(LLSD const & chat_data) { + F64 cur_time = LLTimer::getElapsedSeconds(); + + if (cur_time < mLastThrottleTime + CHAT_THROTTLE_PERIOD) + { + LL_DEBUGS("LLFloaterIMNearbyChatListener") << "'sendChat' was throttled" << LL_ENDL; + return; + } + mLastThrottleTime = cur_time; + // Extract the data - std::string chat_text = chat_data["message"].asString(); + std::string chat_text = LUA_PREFIX + chat_data["message"].asString(); S32 channel = 0; if (chat_data.has("channel")) @@ -81,20 +92,14 @@ void LLFloaterIMNearbyChatListener::sendChat(LLSD const & chat_data) const } // Have to prepend /42 style channel numbers - std::string chat_to_send; - if (channel == 0) - { - chat_to_send = chat_text; - } - else + if (channel) { - chat_to_send += "/"; - chat_to_send += chat_data["channel"].asString(); - chat_to_send += " "; - chat_to_send += chat_text; + chat_text = stringize("/", chat_data["channel"].asString(), " ", chat_text); } // Send it as if it was typed in - mChatbar.sendChatFromViewer(chat_to_send, type_o_chat, ((bool)(channel == 0)) && gSavedSettings.getBOOL("PlayChatAnim")); + LLFloaterIMNearbyChat::sendChatFromViewer(chat_text, type_o_chat, + (channel == 0) && + gSavedSettings.getBOOL("PlayChatAnim")); } diff --git a/indra/newview/llfloaterimnearbychatlistener.h b/indra/newview/llfloaterimnearbychatlistener.h index 96184d95b3..18a8bacfaa 100644 --- a/indra/newview/llfloaterimnearbychatlistener.h +++ b/indra/newview/llfloaterimnearbychatlistener.h @@ -38,12 +38,12 @@ class LLFloaterIMNearbyChat; class LLFloaterIMNearbyChatListener : public LLEventAPI { public: - LLFloaterIMNearbyChatListener(LLFloaterIMNearbyChat & chatbar); + LLFloaterIMNearbyChatListener(); private: - void sendChat(LLSD const & chat_data) const; + void sendChat(LLSD const & chat_data); - LLFloaterIMNearbyChat & mChatbar; + F64 mLastThrottleTime{ 0.0 }; }; #endif // LL_LLFLOATERIMNEARBYCHATLISTENER_H diff --git a/indra/newview/llfloaterimsession.cpp b/indra/newview/llfloaterimsession.cpp index 97e0d01b52..e85aac3810 100644 --- a/indra/newview/llfloaterimsession.cpp +++ b/indra/newview/llfloaterimsession.cpp @@ -92,7 +92,7 @@ LLFloaterIMSession::LLFloaterIMSession(const LLUUID& session_id) LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, this); mEnableCallbackRegistrar.add("Avatar.EnableGearItem", boost::bind(&LLFloaterIMSession::enableGearMenuItem, this, _2)); - mCommitCallbackRegistrar.add("Avatar.GearDoToSelected", boost::bind(&LLFloaterIMSession::GearDoToSelected, this, _2)); + mCommitCallbackRegistrar.add("Avatar.GearDoToSelected", { boost::bind(&LLFloaterIMSession::GearDoToSelected, this, _2), cb_info::UNTRUSTED_BLOCK }); mEnableCallbackRegistrar.add("Avatar.CheckGearItem", boost::bind(&LLFloaterIMSession::checkGearMenuItem, this, _2)); mVoiceChannelChanged = LLVoiceChannel::setCurrentVoiceChannelChangedCallback(boost::bind(&LLFloaterIMSession::onVoiceChannelChanged, this, _1)); diff --git a/indra/newview/llfloaterimsessiontab.cpp b/indra/newview/llfloaterimsessiontab.cpp index fe0916bf15..b2f2984c65 100644 --- a/indra/newview/llfloaterimsessiontab.cpp +++ b/indra/newview/llfloaterimsessiontab.cpp @@ -82,7 +82,7 @@ LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id) mSession = LLIMModel::getInstance()->findIMSession(mSessionID); mCommitCallbackRegistrar.add("IMSession.Menu.Action", - boost::bind(&LLFloaterIMSessionTab::onIMSessionMenuItemClicked, this, _2)); + { boost::bind(&LLFloaterIMSessionTab::onIMSessionMenuItemClicked, this, _2) }); mEnableCallbackRegistrar.add("IMSession.Menu.CompactExpandedModes.CheckItem", boost::bind(&LLFloaterIMSessionTab::onIMCompactExpandedMenuItemCheck, this, _2)); mEnableCallbackRegistrar.add("IMSession.Menu.ShowModes.CheckItem", @@ -93,8 +93,8 @@ LLFloaterIMSessionTab::LLFloaterIMSessionTab(const LLSD& session_id) // Right click menu handling mEnableCallbackRegistrar.add("Avatar.CheckItem", boost::bind(&LLFloaterIMSessionTab::checkContextMenuItem, this, _2)); mEnableCallbackRegistrar.add("Avatar.EnableItem", boost::bind(&LLFloaterIMSessionTab::enableContextMenuItem, this, _2)); - mCommitCallbackRegistrar.add("Avatar.DoToSelected", boost::bind(&LLFloaterIMSessionTab::doToSelected, this, _2)); - mCommitCallbackRegistrar.add("Group.DoToSelected", boost::bind(&cb_group_do_nothing)); + mCommitCallbackRegistrar.add("Avatar.DoToSelected", { boost::bind(&LLFloaterIMSessionTab::doToSelected, this, _2) }); + mCommitCallbackRegistrar.add("Group.DoToSelected", { boost::bind(&cb_group_do_nothing) }); mMinFloaterHeight = getMinHeight(); } diff --git a/indra/newview/llfloaterinspect.cpp b/indra/newview/llfloaterinspect.cpp index 4f993ca0e1..0f1eb0cef0 100644 --- a/indra/newview/llfloaterinspect.cpp +++ b/indra/newview/llfloaterinspect.cpp @@ -51,9 +51,9 @@ LLFloaterInspect::LLFloaterInspect(const LLSD& key) mOwnerNameCacheConnection(), mCreatorNameCacheConnection() { - mCommitCallbackRegistrar.add("Inspect.OwnerProfile", boost::bind(&LLFloaterInspect::onClickOwnerProfile, this)); - mCommitCallbackRegistrar.add("Inspect.CreatorProfile", boost::bind(&LLFloaterInspect::onClickCreatorProfile, this)); - mCommitCallbackRegistrar.add("Inspect.SelectObject", boost::bind(&LLFloaterInspect::onSelectObject, this)); + mCommitCallbackRegistrar.add("Inspect.OwnerProfile", { boost::bind(&LLFloaterInspect::onClickOwnerProfile, this) }); + mCommitCallbackRegistrar.add("Inspect.CreatorProfile", { boost::bind(&LLFloaterInspect::onClickCreatorProfile, this) }); + mCommitCallbackRegistrar.add("Inspect.SelectObject", { boost::bind(&LLFloaterInspect::onSelectObject, this) }); } bool LLFloaterInspect::postBuild() diff --git a/indra/newview/llfloaterlagmeter.cpp b/indra/newview/llfloaterlagmeter.cpp index 28fa8dea9a..26d7e3ead2 100644 --- a/indra/newview/llfloaterlagmeter.cpp +++ b/indra/newview/llfloaterlagmeter.cpp @@ -47,7 +47,7 @@ const std::string LAG_GOOD_IMAGE_NAME = "lag_status_good.tga"; LLFloaterLagMeter::LLFloaterLagMeter(const LLSD& key) : LLFloater(key) { - mCommitCallbackRegistrar.add("LagMeter.ClickShrink", boost::bind(&LLFloaterLagMeter::onClickShrink, this)); + mCommitCallbackRegistrar.add("LagMeter.ClickShrink", { boost::bind(&LLFloaterLagMeter::onClickShrink, this) }); } bool LLFloaterLagMeter::postBuild() diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp index bec76fe5e4..52a3e78d04 100644 --- a/indra/newview/llfloaterland.cpp +++ b/indra/newview/llfloaterland.cpp @@ -3124,6 +3124,16 @@ void LLPanelLandCovenant::refresh() } // static +void LLPanelLandCovenant::updateCovenant(const LLTextBase* source) +{ + if (LLPanelLandCovenant* self = LLFloaterLand::getCurrentPanelLandCovenant()) + { + LLViewerTextEditor* editor = self->getChild<LLViewerTextEditor>("covenant_editor"); + editor->copyContents(source); + } +} + +// static void LLPanelLandCovenant::updateCovenantText(const std::string &string) { LLPanelLandCovenant* self = LLFloaterLand::getCurrentPanelLandCovenant(); diff --git a/indra/newview/llfloaterland.h b/indra/newview/llfloaterland.h index 95f6a44a94..8af0caab33 100644 --- a/indra/newview/llfloaterland.h +++ b/indra/newview/llfloaterland.h @@ -50,6 +50,7 @@ class LLRadioGroup; class LLParcelSelectionObserver; class LLSpinCtrl; class LLTabContainer; +class LLTextBase; class LLTextBox; class LLTextEditor; class LLTextureCtrl; @@ -416,6 +417,7 @@ public: virtual ~LLPanelLandCovenant(); virtual bool postBuild(); void refresh(); + static void updateCovenant(const LLTextBase* source); static void updateCovenantText(const std::string& string); static void updateEstateName(const std::string& name); static void updateLastModified(const std::string& text); diff --git a/indra/newview/llfloaterlinkreplace.cpp b/indra/newview/llfloaterlinkreplace.cpp index c961070787..71dcc3d114 100644 --- a/indra/newview/llfloaterlinkreplace.cpp +++ b/indra/newview/llfloaterlinkreplace.cpp @@ -45,7 +45,7 @@ LLFloaterLinkReplace::LLFloaterLinkReplace(const LLSD& key) mTargetUUID(LLUUID::null), mBatchSize(gSavedSettings.getU32("LinkReplaceBatchSize")) { - mEventTimer.stop(); + stop(); } LLFloaterLinkReplace::~LLFloaterLinkReplace() @@ -202,7 +202,7 @@ void LLFloaterLinkReplace::onStartClickedResponse(const LLSD& notification, cons mStartBtn->setEnabled(false); mRefreshBtn->setEnabled(false); - mEventTimer.start(); + start(); tick(); } else @@ -298,7 +298,7 @@ void LLFloaterLinkReplace::decreaseOpenItemCount() mStatusText->setText(getString("ReplaceFinished")); mStartBtn->setEnabled(true); mRefreshBtn->setEnabled(true); - mEventTimer.stop(); + stop(); LL_INFOS() << "Inventory link replace finished." << LL_ENDL; } else @@ -320,7 +320,7 @@ bool LLFloaterLinkReplace::tick() { if (!mRemainingInventoryItems.size()) { - mEventTimer.stop(); + stop(); break; } diff --git a/indra/newview/llfloaterlinkreplace.h b/indra/newview/llfloaterlinkreplace.h index 7f9f0b59e1..31df083a11 100644 --- a/indra/newview/llfloaterlinkreplace.h +++ b/indra/newview/llfloaterlinkreplace.h @@ -86,10 +86,10 @@ public: LLFloaterLinkReplace(const LLSD& key); virtual ~LLFloaterLinkReplace(); - bool postBuild(); - virtual void onOpen(const LLSD& key); + bool postBuild() override; + void onOpen(const LLSD& key) override; - virtual bool tick(); + bool tick() override; private: void checkEnableStart(); diff --git a/indra/newview/llfloaterluadebug.cpp b/indra/newview/llfloaterluadebug.cpp new file mode 100644 index 0000000000..06877df816 --- /dev/null +++ b/indra/newview/llfloaterluadebug.cpp @@ -0,0 +1,168 @@ +/** + * @file llfloaterluadebug.cpp + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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 "llfloaterluadebug.h" + +#include "lllineeditor.h" +#include "lltexteditor.h" +#include "llviewermenufile.h" // LLFilePickerReplyThread + +#include "llagent.h" +#include "llappearancemgr.h" +#include "llfloaterreg.h" +#include "llfloaterimnearbychat.h" + +#include "llluamanager.h" +#include "llsdutil.h" +#include "lua_function.h" +#include "stringize.h" +#include "tempset.h" + + +LLFloaterLUADebug::LLFloaterLUADebug(const LLSD &key) + : LLFloater(key) +{ +} + + +bool LLFloaterLUADebug::postBuild() +{ + mResultOutput = getChild<LLTextEditor>("result_text"); + mLineInput = getChild<LLLineEditor>("lua_cmd"); + mScriptPath = getChild<LLLineEditor>("script_path"); + mOutConnection = LLEventPumps::instance().obtain("lua output") + .listen("LLFloaterLUADebug", + [mResultOutput=mResultOutput](const LLSD& data) + { + mResultOutput->pasteTextWithLinebreaks(data.asString()); + mResultOutput->addLineBreakChar(true); + return false; + }); + + getChild<LLButton>("execute_btn")->setClickedCallback(boost::bind(&LLFloaterLUADebug::onExecuteClicked, this)); + getChild<LLButton>("browse_btn")->setClickedCallback(boost::bind(&LLFloaterLUADebug::onBtnBrowse, this)); + getChild<LLButton>("run_btn")->setClickedCallback(boost::bind(&LLFloaterLUADebug::onBtnRun, this)); + mLineInput->setCommitCallback(boost::bind(&LLFloaterLUADebug::onExecuteClicked, this)); + mLineInput->setSelectAllonCommit(false); + + return true; +} + +LLFloaterLUADebug::~LLFloaterLUADebug() +{} + +void LLFloaterLUADebug::onExecuteClicked() +{ + // Empirically, running Lua code that indirectly invokes the + // "LLNotifications" listener can result (via mysterious labyrinthine + // viewer UI byways) in a recursive call to this handler. We've seen Bad + // Things happen to the viewer with a second call to runScriptLine() with + // the same cmd on the same LuaState. + if (mExecuting) + { + LL_DEBUGS("Lua") << "recursive call to onExecuteClicked()" << LL_ENDL; + return; + } + TempSet executing(mExecuting, true); + mResultOutput->setValue(""); + + std::string cmd = mLineInput->getText(); + LLLUAmanager::runScriptLine(cmd, [this](int count, const LLSD& result) + { + completion(count, result); + }); +} + +void LLFloaterLUADebug::onBtnBrowse() +{ + LLFilePickerReplyThread::startPicker(boost::bind(&LLFloaterLUADebug::runSelectedScript, this, _1), LLFilePicker::FFLOAD_LUA, false); +} + +void LLFloaterLUADebug::onBtnRun() +{ + std::vector<std::string> filenames; + std::string filepath = mScriptPath->getText(); + if (!filepath.empty()) + { + filenames.push_back(filepath); + runSelectedScript(filenames); + } +} + +void LLFloaterLUADebug::runSelectedScript(const std::vector<std::string> &filenames) +{ + if (mExecuting) + { + LL_DEBUGS("Lua") << "recursive call to runSelectedScript()" << LL_ENDL; + return; + } + TempSet executing(mExecuting, true); + mResultOutput->setValue(""); + + std::string filepath = filenames[0]; + if (!filepath.empty()) + { + mScriptPath->setText(filepath); + LLLUAmanager::runScriptFile(filepath, false, [this](int count, const LLSD &result) + { + completion(count, result); + }); + } +} + +void LLFloaterLUADebug::completion(int count, const LLSD& result) +{ + if (count < 0) + { + // error: show error message + LLStyle::Params params; + params.readonly_color = LLUIColorTable::instance().getColor("LtRed"); + mResultOutput->appendText(result.asString(), false, params); + mResultOutput->endOfDoc(); + return; + } + if (count == 0) + { + // no results + mResultOutput->pasteTextWithLinebreaks(stringize("ok ", ++mAck)); + return; + } + if (count == 1) + { + // single result + mResultOutput->pasteTextWithLinebreaks(stringize(result)); + return; + } + // multiple results + const char* sep = ""; + for (const auto& item : llsd::inArray(result)) + { + mResultOutput->insertText(sep); + mResultOutput->pasteTextWithLinebreaks(stringize(item)); + sep = ", "; + } +} diff --git a/indra/newview/llfloaterluadebug.h b/indra/newview/llfloaterluadebug.h new file mode 100644 index 0000000000..e27c63b74d --- /dev/null +++ b/indra/newview/llfloaterluadebug.h @@ -0,0 +1,72 @@ +/** + * @file llfloaterluadebug.h + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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_LLFLOATERLUADEBUG_H +#define LL_LLFLOATERLUADEBUG_H + +#include "llevents.h" +#include "llfloater.h" +#include "lua_function.h" + +extern "C" +{ +#include "luau/luacode.h" +#include "luau/lua.h" +#include "luau/luaconf.h" +#include "luau/lualib.h" +} + +class LLLineEditor; +class LLTextEditor; + +class LLFloaterLUADebug : + public LLFloater +{ + public: + LLFloaterLUADebug(const LLSD& key); + virtual ~LLFloaterLUADebug(); + + /*virtual*/ bool postBuild(); + + void onExecuteClicked(); + void onBtnBrowse(); + void onBtnRun(); + + void runSelectedScript(const std::vector<std::string> &filenames); + +private: + void completion(int count, const LLSD& result); + + LLTempBoundListener mOutConnection; + + LLTextEditor* mResultOutput; + LLLineEditor* mLineInput; + LLLineEditor* mScriptPath; + U32 mAck{ 0 }; + bool mExecuting{ false }; +}; + +#endif // LL_LLFLOATERLUADEBUG_H + diff --git a/indra/newview/llfloaterluascripts.cpp b/indra/newview/llfloaterluascripts.cpp new file mode 100644 index 0000000000..0eba45ec29 --- /dev/null +++ b/indra/newview/llfloaterluascripts.cpp @@ -0,0 +1,131 @@ +/** + * @file llfloaterluascriptsinfo.cpp + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, 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 "llfloaterluascripts.h" +#include "llevents.h" +#include <filesystem> +#include "llluamanager.h" +#include "llscrolllistctrl.h" +#include "llviewerwindow.h" +#include "llwindow.h" +#include "llviewermenu.h" + +const F32 REFRESH_INTERVAL = 1.0f; + +LLFloaterLUAScripts::LLFloaterLUAScripts(const LLSD &key) + : LLFloater(key), + mUpdateTimer(new LLTimer()), + mContextMenuHandle() +{ + mCommitCallbackRegistrar.add("Script.OpenFolder", {[this](LLUICtrl*, const LLSD &userdata) + { + if (mScriptList->hasSelectedItem()) + { + std::string target_folder_path = std::filesystem::path((mScriptList->getFirstSelected()->getColumn(1)->getValue().asString())).parent_path().string(); + gViewerWindow->getWindow()->openFolder(target_folder_path); + } + }, cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Script.Terminate", {[this](LLUICtrl*, const LLSD &userdata) + { + if (mScriptList->hasSelectedItem()) + { + std::string coro_name = mScriptList->getSelectedValue(); + LLCoros::instance().killreq(coro_name); + } + }, cb_info::UNTRUSTED_BLOCK }); +} + + +bool LLFloaterLUAScripts::postBuild() +{ + mScriptList = getChild<LLScrollListCtrl>("scripts_list"); + mScriptList->setRightMouseDownCallback([this](LLUICtrl *ctrl, S32 x, S32 y, MASK mask) { onScrollListRightClicked(ctrl, x, y);}); + + LLContextMenu *menu = LLUICtrlFactory::getInstance()->createFromFile<LLContextMenu>( + "menu_lua_scripts.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + if (menu) + { + mContextMenuHandle = menu->getHandle(); + } + + return true; +} + +LLFloaterLUAScripts::~LLFloaterLUAScripts() +{ + auto menu = mContextMenuHandle.get(); + if (menu) + { + menu->die(); + mContextMenuHandle.markDead(); + } +} + +void LLFloaterLUAScripts::draw() +{ + if (mUpdateTimer->checkExpirationAndReset(REFRESH_INTERVAL)) + { + populateScriptList(); + } + LLFloater::draw(); +} + +void LLFloaterLUAScripts::populateScriptList() +{ + S32 prev_pos = mScriptList->getScrollPos(); + LLSD prev_selected = mScriptList->getSelectedValue(); + mScriptList->clearRows(); + mScriptList->updateColumns(true); + std::map<std::string, std::string> scripts = LLLUAmanager::getScriptNames(); + for (auto &it : scripts) + { + LLSD row; + row["value"] = it.first; + row["columns"][0]["value"] = std::filesystem::path((it.second)).stem().string(); + row["columns"][0]["column"] = "script_name"; + row["columns"][1]["value"] = it.second; + row["columns"][1]["column"] = "script_path"; + mScriptList->addElement(row); + } + mScriptList->setScrollPos(prev_pos); + mScriptList->setSelectedByValue(prev_selected, true); +} + +void LLFloaterLUAScripts::onScrollListRightClicked(LLUICtrl *ctrl, S32 x, S32 y) +{ + LLScrollListItem *item = mScriptList->hitItem(x, y); + if (item) + { + mScriptList->selectItemAt(x, y, MASK_NONE); + auto menu = mContextMenuHandle.get(); + if (menu) + { + menu->show(x, y); + LLMenuGL::showPopup(this, menu, x, y); + } + } +} diff --git a/indra/newview/llappviewerlinux_api_dbus.h b/indra/newview/llfloaterluascripts.h index 2f4492bd7a..14ca42d6fb 100644 --- a/indra/newview/llappviewerlinux_api_dbus.h +++ b/indra/newview/llfloaterluascripts.h @@ -1,10 +1,9 @@ /** - * @file llappviewerlinux_api_dbus.h - * @brief DBus-glib symbol handling + * @file llfloaterluascriptsinfo.h * - * $LicenseInfo:firstyear=2008&license=viewerlgpl$ + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ * Second Life Viewer Source Code - * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2024, 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 @@ -24,21 +23,32 @@ * $/LicenseInfo$ */ -#include "linden_common.h" +#ifndef LL_LLFLOATERLUASCRIPTS_H +#define LL_LLFLOATERLUASCRIPTS_H -#if LL_DBUS_ENABLED +#include "llfloater.h" -extern "C" { -#include <dbus/dbus-glib.h> -} +class LLScrollListCtrl; -#define DBUSGLIB_DYLIB_DEFAULT_NAME "libdbus-glib-1.so.2" +class LLFloaterLUAScripts : + public LLFloater +{ + public: + LLFloaterLUAScripts(const LLSD &key); + virtual ~LLFloaterLUAScripts(); -bool grab_dbus_syms(std::string dbus_dso_name); -void ungrab_dbus_syms(); + bool postBuild(); + void draw(); -#define LL_DBUS_SYM(REQUIRED, DBUSSYM, RTN, ...) extern RTN (*ll##DBUSSYM)(__VA_ARGS__) -#include "llappviewerlinux_api_dbus_syms_raw.inc" -#undef LL_DBUS_SYM +private: + void populateScriptList(); + void onScrollListRightClicked(LLUICtrl *ctrl, S32 x, S32 y); + + std::unique_ptr<LLTimer> mUpdateTimer; + LLScrollListCtrl* mScriptList; + + LLHandle<LLContextMenu> mContextMenuHandle; +}; + +#endif // LL_LLFLOATERLUASCRIPTS_H -#endif // LL_DBUS_ENABLED diff --git a/indra/newview/llfloatermarketplacelistings.cpp b/indra/newview/llfloatermarketplacelistings.cpp index f20fea01c5..e91745fff0 100644 --- a/indra/newview/llfloatermarketplacelistings.cpp +++ b/indra/newview/llfloatermarketplacelistings.cpp @@ -58,7 +58,7 @@ LLPanelMarketplaceListings::LLPanelMarketplaceListings() , mSortOrder(LLInventoryFilter::SO_FOLDERS_BY_NAME) , mFilterListingFoldersOnly(false) { - mCommitCallbackRegistrar.add("Marketplace.ViewSort.Action", boost::bind(&LLPanelMarketplaceListings::onViewSortMenuItemClicked, this, _2)); + mCommitCallbackRegistrar.add("Marketplace.ViewSort.Action", { boost::bind(&LLPanelMarketplaceListings::onViewSortMenuItemClicked, this, _2) }); mEnableCallbackRegistrar.add("Marketplace.ViewSort.CheckItem", boost::bind(&LLPanelMarketplaceListings::onViewSortMenuItemCheck, this, _2)); } diff --git a/indra/newview/llfloatermemleak.cpp b/indra/newview/llfloatermemleak.cpp index b4bb45c864..4ec8cbf71d 100644 --- a/indra/newview/llfloatermemleak.cpp +++ b/indra/newview/llfloatermemleak.cpp @@ -48,12 +48,12 @@ LLFloaterMemLeak::LLFloaterMemLeak(const LLSD& key) : LLFloater(key) { setTitle("Memory Leaking Simulation Floater"); - mCommitCallbackRegistrar.add("MemLeak.ChangeLeakingSpeed", boost::bind(&LLFloaterMemLeak::onChangeLeakingSpeed, this)); - mCommitCallbackRegistrar.add("MemLeak.ChangeMaxMemLeaking", boost::bind(&LLFloaterMemLeak::onChangeMaxMemLeaking, this)); - mCommitCallbackRegistrar.add("MemLeak.Start", boost::bind(&LLFloaterMemLeak::onClickStart, this)); - mCommitCallbackRegistrar.add("MemLeak.Stop", boost::bind(&LLFloaterMemLeak::onClickStop, this)); - mCommitCallbackRegistrar.add("MemLeak.Release", boost::bind(&LLFloaterMemLeak::onClickRelease, this)); - mCommitCallbackRegistrar.add("MemLeak.Close", boost::bind(&LLFloaterMemLeak::onClickClose, this)); + mCommitCallbackRegistrar.add("MemLeak.ChangeLeakingSpeed", { boost::bind(&LLFloaterMemLeak::onChangeLeakingSpeed, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("MemLeak.ChangeMaxMemLeaking", { boost::bind(&LLFloaterMemLeak::onChangeMaxMemLeaking, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("MemLeak.Start", { boost::bind(&LLFloaterMemLeak::onClickStart, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("MemLeak.Stop", { boost::bind(&LLFloaterMemLeak::onClickStop, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("MemLeak.Release", { boost::bind(&LLFloaterMemLeak::onClickRelease, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("MemLeak.Close", { boost::bind(&LLFloaterMemLeak::onClickClose, this), cb_info::UNTRUSTED_BLOCK }); } //---------------------------------------------- diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 5ca727cf66..8332a430e6 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -782,16 +782,21 @@ void LLFloaterModelPreview::draw3dPreview() gGL.getTexUnit(0)->bind(mModelPreview); - gGL.begin( LLRender::QUADS ); + gGL.begin(LLRender::TRIANGLES); { gGL.texCoord2f(0.f, 1.f); - gGL.vertex2i(mPreviewRect.mLeft+1, mPreviewRect.mTop-1); + gGL.vertex2i(mPreviewRect.mLeft + 1, mPreviewRect.mTop - 1); gGL.texCoord2f(0.f, 0.f); - gGL.vertex2i(mPreviewRect.mLeft+1, mPreviewRect.mBottom+1); + gGL.vertex2i(mPreviewRect.mLeft + 1, mPreviewRect.mBottom + 1); gGL.texCoord2f(1.f, 0.f); - gGL.vertex2i(mPreviewRect.mRight-1, mPreviewRect.mBottom+1); + gGL.vertex2i(mPreviewRect.mRight - 1, mPreviewRect.mBottom + 1); + + gGL.texCoord2f(1.f, 0.f); + gGL.vertex2i(mPreviewRect.mRight - 1, mPreviewRect.mBottom + 1); gGL.texCoord2f(1.f, 1.f); - gGL.vertex2i(mPreviewRect.mRight-1, mPreviewRect.mTop-1); + gGL.vertex2i(mPreviewRect.mRight - 1, mPreviewRect.mTop - 1); + gGL.texCoord2f(0.f, 1.f); + gGL.vertex2i(mPreviewRect.mLeft + 1, mPreviewRect.mTop - 1); } gGL.end(); diff --git a/indra/newview/llfloatermyenvironment.cpp b/indra/newview/llfloatermyenvironment.cpp index 891e16a8ef..b8aa4c6f72 100644 --- a/indra/newview/llfloatermyenvironment.cpp +++ b/indra/newview/llfloatermyenvironment.cpp @@ -81,10 +81,10 @@ LLFloaterMyEnvironment::LLFloaterMyEnvironment(const LLSD& key) : mTypeFilter((0x01 << static_cast<U64>(LLSettingsType::ST_DAYCYCLE)) | (0x01 << static_cast<U64>(LLSettingsType::ST_SKY)) | (0x01 << static_cast<U64>(LLSettingsType::ST_WATER))), mSelectedAsset() { - mCommitCallbackRegistrar.add(ACTION_DOCREATE, [this](LLUICtrl *, const LLSD &userdata) { onDoCreate(userdata); }); - mCommitCallbackRegistrar.add(ACTION_DOEDIT, [this](LLUICtrl *, const LLSD &userdata) { mInventoryList->openSelected(); }); - mCommitCallbackRegistrar.add(ACTION_DOAPPLY, [this](LLUICtrl *, const LLSD &userdata) { onDoApply(userdata.asString()); }); - mCommitCallbackRegistrar.add(ACTION_COPYPASTE, [this](LLUICtrl *, const LLSD &userdata) { mInventoryList->doToSelected(userdata.asString()); }); + mCommitCallbackRegistrar.add(ACTION_DOCREATE, { [this](LLUICtrl *, const LLSD &userdata) { onDoCreate(userdata); } }); + mCommitCallbackRegistrar.add(ACTION_DOEDIT, { [this](LLUICtrl *, const LLSD &userdata) { mInventoryList->openSelected(); } }); + mCommitCallbackRegistrar.add(ACTION_DOAPPLY, { [this](LLUICtrl *, const LLSD &userdata) { onDoApply(userdata.asString()); } }); + mCommitCallbackRegistrar.add(ACTION_COPYPASTE, { [this](LLUICtrl *, const LLSD &userdata) { mInventoryList->doToSelected(userdata.asString()); } }); mEnableCallbackRegistrar.add(ENABLE_ACTION, [this](LLUICtrl *, const LLSD &userdata) { return canAction(userdata.asString()); }); mEnableCallbackRegistrar.add(ENABLE_CANAPPLY, [this](LLUICtrl *, const LLSD &userdata) { return canApply(userdata.asString()); }); diff --git a/indra/newview/llfloaternotificationsconsole.cpp b/indra/newview/llfloaternotificationsconsole.cpp index a819b30e30..883b285c06 100644 --- a/indra/newview/llfloaternotificationsconsole.cpp +++ b/indra/newview/llfloaternotificationsconsole.cpp @@ -151,7 +151,7 @@ bool LLNotificationChannelPanel::update(const LLSD& payload) LLFloaterNotificationConsole::LLFloaterNotificationConsole(const LLSD& key) : LLFloater(key) { - mCommitCallbackRegistrar.add("ClickAdd", boost::bind(&LLFloaterNotificationConsole::onClickAdd, this)); + mCommitCallbackRegistrar.add("ClickAdd", { boost::bind(&LLFloaterNotificationConsole::onClickAdd, this) }); } bool LLFloaterNotificationConsole::postBuild() diff --git a/indra/newview/llfloateropenobject.cpp b/indra/newview/llfloateropenobject.cpp index b06e35f65d..afd77ebd50 100644 --- a/indra/newview/llfloateropenobject.cpp +++ b/indra/newview/llfloateropenobject.cpp @@ -55,8 +55,8 @@ LLFloaterOpenObject::LLFloaterOpenObject(const LLSD& key) mPanelInventoryObject(NULL), mDirty(true) { - mCommitCallbackRegistrar.add("OpenObject.MoveToInventory", boost::bind(&LLFloaterOpenObject::onClickMoveToInventory, this)); - mCommitCallbackRegistrar.add("OpenObject.Cancel", boost::bind(&LLFloaterOpenObject::onClickCancel, this)); + mCommitCallbackRegistrar.add("OpenObject.MoveToInventory", { boost::bind(&LLFloaterOpenObject::onClickMoveToInventory, this), cb_info::UNTRUSTED_THROTTLE }); + mCommitCallbackRegistrar.add("OpenObject.Cancel", { boost::bind(&LLFloaterOpenObject::onClickCancel, this) }); } LLFloaterOpenObject::~LLFloaterOpenObject() diff --git a/indra/newview/llfloaterperformance.cpp b/indra/newview/llfloaterperformance.cpp index 315508f22b..756d22eea7 100644 --- a/indra/newview/llfloaterperformance.cpp +++ b/indra/newview/llfloaterperformance.cpp @@ -67,7 +67,7 @@ protected: { LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - registrar.add("Settings.SetRendering", boost::bind(&LLFloaterPerformance::onCustomAction, mFloaterPerformance, _2, mUUIDs.front())); + registrar.add("Settings.SetRendering", { boost::bind(&LLFloaterPerformance::onCustomAction, mFloaterPerformance, _2, mUUIDs.front()), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); enable_registrar.add("Settings.IsSelected", boost::bind(&LLFloaterPerformance::isActionChecked, mFloaterPerformance, _2, mUUIDs.front())); LLContextMenu* menu = createFromFile("menu_avatar_rendering_settings.xml"); @@ -115,12 +115,12 @@ bool LLFloaterPerformance::postBuild() mHUDList = mHUDsPanel->getChild<LLNameListCtrl>("hud_list"); mHUDList->setNameListType(LLNameListCtrl::SPECIAL); mHUDList->setHoverIconName("StopReload_Off"); - mHUDList->setIconClickedCallback(boost::bind(&LLFloaterPerformance::detachItem, this, _1)); + mHUDList->setIconClickedCallback(boost::bind(&LLFloaterPerformance::detachObject, this, _1)); mObjectList = mComplexityPanel->getChild<LLNameListCtrl>("obj_list"); mObjectList->setNameListType(LLNameListCtrl::SPECIAL); mObjectList->setHoverIconName("StopReload_Off"); - mObjectList->setIconClickedCallback(boost::bind(&LLFloaterPerformance::detachItem, this, _1)); + mObjectList->setIconClickedCallback(boost::bind(&LLFloaterPerformance::detachObject, this, _1)); mSettingsPanel->getChild<LLButton>("advanced_btn")->setCommitCallback(boost::bind(&LLFloaterPerformance::onClickAdvanced, this)); mSettingsPanel->getChild<LLButton>("defaults_btn")->setCommitCallback(boost::bind(&LLFloaterPerformance::onClickDefaults, this)); @@ -527,9 +527,13 @@ void LLFloaterPerformance::setFPSText() mTextFPSLabel->setValue(fps_text); } -void LLFloaterPerformance::detachItem(const LLUUID& item_id) +void LLFloaterPerformance::detachObject(const LLUUID& obj_id) { - LLAppearanceMgr::instance().removeItemFromAvatar(item_id); + LLViewerObject* obj = gObjectList.findObject(obj_id); + if (obj) + { + LLAppearanceMgr::instance().removeItemFromAvatar(obj->getAttachmentItemID()); + } } void LLFloaterPerformance::onClickAdvanced() diff --git a/indra/newview/llfloaterperformance.h b/indra/newview/llfloaterperformance.h index a7100eb350..6cca85a009 100644 --- a/indra/newview/llfloaterperformance.h +++ b/indra/newview/llfloaterperformance.h @@ -48,7 +48,7 @@ public: void hidePanels(); void showAutoadjustmentsPanel(); - void detachItem(const LLUUID& item_id); + void detachObject(const LLUUID& obj_id); void onAvatarListRightClick(LLUICtrl* ctrl, S32 x, S32 y); diff --git a/indra/newview/llfloaterperms.cpp b/indra/newview/llfloaterperms.cpp index 7311f0deb6..86a99aba39 100644 --- a/indra/newview/llfloaterperms.cpp +++ b/indra/newview/llfloaterperms.cpp @@ -107,9 +107,9 @@ static bool mCapSent = false; LLFloaterPermsDefault::LLFloaterPermsDefault(const LLSD& seed) : LLFloater(seed) { - mCommitCallbackRegistrar.add("PermsDefault.Copy", boost::bind(&LLFloaterPermsDefault::onCommitCopy, this, _2)); - mCommitCallbackRegistrar.add("PermsDefault.OK", boost::bind(&LLFloaterPermsDefault::onClickOK, this)); - mCommitCallbackRegistrar.add("PermsDefault.Cancel", boost::bind(&LLFloaterPermsDefault::onClickCancel, this)); + mCommitCallbackRegistrar.add("PermsDefault.Copy", { boost::bind(&LLFloaterPermsDefault::onCommitCopy, this, _2), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("PermsDefault.OK", { boost::bind(&LLFloaterPermsDefault::onClickOK, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("PermsDefault.Cancel", { boost::bind(&LLFloaterPermsDefault::onClickCancel, this), cb_info::UNTRUSTED_BLOCK }); } diff --git a/indra/newview/llfloaterpostprocess.cpp b/indra/newview/llfloaterpostprocess.cpp deleted file mode 100644 index 616c13cdc7..0000000000 --- a/indra/newview/llfloaterpostprocess.cpp +++ /dev/null @@ -1,229 +0,0 @@ -/** - * @file llfloaterpostprocess.cpp - * @brief LLFloaterPostProcess class definition - * - * $LicenseInfo:firstyear=2007&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 "llfloaterpostprocess.h" - -#include "llsliderctrl.h" -#include "llcheckboxctrl.h" -#include "llnotificationsutil.h" -#include "lluictrlfactory.h" -#include "llviewerdisplay.h" -#include "llpostprocess.h" -#include "llcombobox.h" -#include "lllineeditor.h" -#include "llviewerwindow.h" - - -LLFloaterPostProcess::LLFloaterPostProcess(const LLSD& key) - : LLFloater(key) -{ -} - -LLFloaterPostProcess::~LLFloaterPostProcess() -{ - - -} -bool LLFloaterPostProcess::postBuild() -{ - /// Color Filter Callbacks - childSetCommitCallback("ColorFilterToggle", &LLFloaterPostProcess::onBoolToggle, (char*)"enable_color_filter"); - //childSetCommitCallback("ColorFilterGamma", &LLFloaterPostProcess::onFloatControlMoved, &(gPostProcess->tweaks.gamma())); - childSetCommitCallback("ColorFilterBrightness", &LLFloaterPostProcess::onFloatControlMoved, (char*)"brightness"); - childSetCommitCallback("ColorFilterSaturation", &LLFloaterPostProcess::onFloatControlMoved, (char*)"saturation"); - childSetCommitCallback("ColorFilterContrast", &LLFloaterPostProcess::onFloatControlMoved, (char*)"contrast"); - - childSetCommitCallback("ColorFilterBaseR", &LLFloaterPostProcess::onColorControlRMoved, (char*)"contrast_base"); - childSetCommitCallback("ColorFilterBaseG", &LLFloaterPostProcess::onColorControlGMoved, (char*)"contrast_base"); - childSetCommitCallback("ColorFilterBaseB", &LLFloaterPostProcess::onColorControlBMoved, (char*)"contrast_base"); - childSetCommitCallback("ColorFilterBaseI", &LLFloaterPostProcess::onColorControlIMoved, (char*)"contrast_base"); - - /// Night Vision Callbacks - childSetCommitCallback("NightVisionToggle", &LLFloaterPostProcess::onBoolToggle, (char*)"enable_night_vision"); - childSetCommitCallback("NightVisionBrightMult", &LLFloaterPostProcess::onFloatControlMoved, (char*)"brightness_multiplier"); - childSetCommitCallback("NightVisionNoiseSize", &LLFloaterPostProcess::onFloatControlMoved, (char*)"noise_size"); - childSetCommitCallback("NightVisionNoiseStrength", &LLFloaterPostProcess::onFloatControlMoved, (char*)"noise_strength"); - - /// Bloom Callbacks - childSetCommitCallback("BloomToggle", &LLFloaterPostProcess::onBoolToggle, (char*)"enable_bloom"); - childSetCommitCallback("BloomExtract", &LLFloaterPostProcess::onFloatControlMoved, (char*)"extract_low"); - childSetCommitCallback("BloomSize", &LLFloaterPostProcess::onFloatControlMoved, (char*)"bloom_width"); - childSetCommitCallback("BloomStrength", &LLFloaterPostProcess::onFloatControlMoved, (char*)"bloom_strength"); - - // Effect loading and saving. - LLComboBox* comboBox = getChild<LLComboBox>("PPEffectsCombo"); - getChild<LLComboBox>("PPLoadEffect")->setCommitCallback(boost::bind(&LLFloaterPostProcess::onLoadEffect, this, comboBox)); - comboBox->setCommitCallback(boost::bind(&LLFloaterPostProcess::onChangeEffectName, this, _1)); - - LLLineEditor* editBox = getChild<LLLineEditor>("PPEffectNameEditor"); - getChild<LLComboBox>("PPSaveEffect")->setCommitCallback(boost::bind(&LLFloaterPostProcess::onSaveEffect, this, editBox)); - - syncMenu(); - return true; -} - -// Bool Toggle -void LLFloaterPostProcess::onBoolToggle(LLUICtrl* ctrl, void* userData) -{ - char const * boolVariableName = (char const *)userData; - - // check the bool - LLCheckBoxCtrl* cbCtrl = static_cast<LLCheckBoxCtrl*>(ctrl); - gPostProcess->tweaks[boolVariableName] = cbCtrl->getValue(); -} - -// Float Moved -void LLFloaterPostProcess::onFloatControlMoved(LLUICtrl* ctrl, void* userData) -{ - char const * floatVariableName = (char const *)userData; - LLSliderCtrl* sldrCtrl = static_cast<LLSliderCtrl*>(ctrl); - gPostProcess->tweaks[floatVariableName] = sldrCtrl->getValue(); -} - -// Color Moved -void LLFloaterPostProcess::onColorControlRMoved(LLUICtrl* ctrl, void* userData) -{ - char const * floatVariableName = (char const *)userData; - LLSliderCtrl* sldrCtrl = static_cast<LLSliderCtrl*>(ctrl); - gPostProcess->tweaks[floatVariableName][0] = sldrCtrl->getValue(); -} - -// Color Moved -void LLFloaterPostProcess::onColorControlGMoved(LLUICtrl* ctrl, void* userData) -{ - char const * floatVariableName = (char const *)userData; - LLSliderCtrl* sldrCtrl = static_cast<LLSliderCtrl*>(ctrl); - gPostProcess->tweaks[floatVariableName][1] = sldrCtrl->getValue(); -} - -// Color Moved -void LLFloaterPostProcess::onColorControlBMoved(LLUICtrl* ctrl, void* userData) -{ - char const * floatVariableName = (char const *)userData; - LLSliderCtrl* sldrCtrl = static_cast<LLSliderCtrl*>(ctrl); - gPostProcess->tweaks[floatVariableName][2] = sldrCtrl->getValue(); -} - -// Color Moved -void LLFloaterPostProcess::onColorControlIMoved(LLUICtrl* ctrl, void* userData) -{ - char const * floatVariableName = (char const *)userData; - LLSliderCtrl* sldrCtrl = static_cast<LLSliderCtrl*>(ctrl); - gPostProcess->tweaks[floatVariableName][3] = sldrCtrl->getValue(); -} - -void LLFloaterPostProcess::onLoadEffect(LLComboBox* comboBox) -{ - LLSD::String effectName(comboBox->getSelectedValue().asString()); - - gPostProcess->setSelectedEffect(effectName); - - syncMenu(); -} - -void LLFloaterPostProcess::onSaveEffect(LLLineEditor* editBox) -{ - std::string effectName(editBox->getValue().asString()); - - if (gPostProcess->mAllEffects.has(effectName)) - { - LLSD payload; - payload["effect_name"] = effectName; - LLNotificationsUtil::add("PPSaveEffectAlert", LLSD(), payload, boost::bind(&LLFloaterPostProcess::saveAlertCallback, this, _1, _2)); - } - else - { - gPostProcess->saveEffect(effectName); - syncMenu(); - } -} - -void LLFloaterPostProcess::onChangeEffectName(LLUICtrl* ctrl) -{ - // get the combo box and name - LLLineEditor* editBox = getChild<LLLineEditor>("PPEffectNameEditor"); - - // set the parameter's new name - editBox->setValue(ctrl->getValue()); -} - -bool LLFloaterPostProcess::saveAlertCallback(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); - - // if they choose save, do it. Otherwise, don't do anything - if (option == 0) - { - gPostProcess->saveEffect(notification["payload"]["effect_name"].asString()); - - syncMenu(); - } - return false; -} - -void LLFloaterPostProcess::syncMenu() -{ - // add the combo boxe contents - LLComboBox* comboBox = getChild<LLComboBox>("PPEffectsCombo"); - - comboBox->removeall(); - - LLSD::map_const_iterator currEffect; - for(currEffect = gPostProcess->mAllEffects.beginMap(); - currEffect != gPostProcess->mAllEffects.endMap(); - ++currEffect) - { - comboBox->add(currEffect->first); - } - - // set the current effect as selected. - comboBox->selectByValue(gPostProcess->getSelectedEffect()); - - /// Sync Color Filter Menu - getChild<LLUICtrl>("ColorFilterToggle")->setValue(gPostProcess->tweaks.useColorFilter()); - //getChild<LLUICtrl>("ColorFilterGamma")->setValue(gPostProcess->tweaks.gamma()); - getChild<LLUICtrl>("ColorFilterBrightness")->setValue(gPostProcess->tweaks.brightness()); - getChild<LLUICtrl>("ColorFilterSaturation")->setValue(gPostProcess->tweaks.saturation()); - getChild<LLUICtrl>("ColorFilterContrast")->setValue(gPostProcess->tweaks.contrast()); - getChild<LLUICtrl>("ColorFilterBaseR")->setValue(gPostProcess->tweaks.contrastBaseR()); - getChild<LLUICtrl>("ColorFilterBaseG")->setValue(gPostProcess->tweaks.contrastBaseG()); - getChild<LLUICtrl>("ColorFilterBaseB")->setValue(gPostProcess->tweaks.contrastBaseB()); - getChild<LLUICtrl>("ColorFilterBaseI")->setValue(gPostProcess->tweaks.contrastBaseIntensity()); - - /// Sync Night Vision Menu - getChild<LLUICtrl>("NightVisionToggle")->setValue(gPostProcess->tweaks.useNightVisionShader()); - getChild<LLUICtrl>("NightVisionBrightMult")->setValue(gPostProcess->tweaks.brightMult()); - getChild<LLUICtrl>("NightVisionNoiseSize")->setValue(gPostProcess->tweaks.noiseSize()); - getChild<LLUICtrl>("NightVisionNoiseStrength")->setValue(gPostProcess->tweaks.noiseStrength()); - - /// Sync Bloom Menu - getChild<LLUICtrl>("BloomToggle")->setValue(LLSD(gPostProcess->tweaks.useBloomShader())); - getChild<LLUICtrl>("BloomExtract")->setValue(gPostProcess->tweaks.extractLow()); - getChild<LLUICtrl>("BloomSize")->setValue(gPostProcess->tweaks.bloomWidth()); - getChild<LLUICtrl>("BloomStrength")->setValue(gPostProcess->tweaks.bloomStrength()); -} diff --git a/indra/newview/llfloaterpostprocess.h b/indra/newview/llfloaterpostprocess.h deleted file mode 100644 index 50b48d8410..0000000000 --- a/indra/newview/llfloaterpostprocess.h +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @file llfloaterpostprocess.h - * @brief LLFloaterPostProcess class definition - * - * $LicenseInfo:firstyear=2007&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_LLFLOATERPOSTPROCESS_H -#define LL_LLFLOATERPOSTPROCESS_H - -#include "llfloater.h" - -class LLButton; -class LLComboBox; -class LLLineEditor; -class LLSliderCtrl; -class LLTabContainer; -class LLPanelPermissions; -class LLPanelObject; -class LLPanelVolume; -class LLPanelContents; -class LLPanelFace; - -/** - * Menu for adjusting the post process settings of the world - */ -class LLFloaterPostProcess : public LLFloater -{ -public: - - LLFloaterPostProcess(const LLSD& key); - virtual ~LLFloaterPostProcess(); - bool postBuild(); - - /// post process callbacks - static void onBoolToggle(LLUICtrl* ctrl, void* userData); - static void onFloatControlMoved(LLUICtrl* ctrl, void* userData); - static void onColorControlRMoved(LLUICtrl* ctrl, void* userData); - static void onColorControlGMoved(LLUICtrl* ctrl, void* userData); - static void onColorControlBMoved(LLUICtrl* ctrl, void* userData); - static void onColorControlIMoved(LLUICtrl* ctrl, void* userData); - void onLoadEffect(LLComboBox* comboBox); - void onSaveEffect(LLLineEditor* editBox); - void onChangeEffectName(LLUICtrl* ctrl); - - /// prompts a user when overwriting an effect - bool saveAlertCallback(const LLSD& notification, const LLSD& response); - - /// sync up sliders - void syncMenu(); -}; - -#endif diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index e673752986..d60d41ae3c 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -312,43 +312,44 @@ LLFloaterPreference::LLFloaterPreference(const LLSD& key) registered_dialog = true; } - mCommitCallbackRegistrar.add("Pref.Cancel", boost::bind(&LLFloaterPreference::onBtnCancel, this, _2)); - mCommitCallbackRegistrar.add("Pref.OK", boost::bind(&LLFloaterPreference::onBtnOK, this, _2)); - - mCommitCallbackRegistrar.add("Pref.ClearCache", boost::bind(&LLFloaterPreference::onClickClearCache, this)); - mCommitCallbackRegistrar.add("Pref.WebClearCache", boost::bind(&LLFloaterPreference::onClickBrowserClearCache, this)); - mCommitCallbackRegistrar.add("Pref.SetCache", boost::bind(&LLFloaterPreference::onClickSetCache, this)); - mCommitCallbackRegistrar.add("Pref.ResetCache", boost::bind(&LLFloaterPreference::onClickResetCache, this)); - mCommitCallbackRegistrar.add("Pref.ClickSkin", boost::bind(&LLFloaterPreference::onClickSkin, this,_1, _2)); - mCommitCallbackRegistrar.add("Pref.SelectSkin", boost::bind(&LLFloaterPreference::onSelectSkin, this)); - mCommitCallbackRegistrar.add("Pref.SetSounds", boost::bind(&LLFloaterPreference::onClickSetSounds, this)); - mCommitCallbackRegistrar.add("Pref.ClickEnablePopup", boost::bind(&LLFloaterPreference::onClickEnablePopup, this)); - mCommitCallbackRegistrar.add("Pref.ClickDisablePopup", boost::bind(&LLFloaterPreference::onClickDisablePopup, this)); - mCommitCallbackRegistrar.add("Pref.LogPath", boost::bind(&LLFloaterPreference::onClickLogPath, this)); - mCommitCallbackRegistrar.add("Pref.RenderExceptions", boost::bind(&LLFloaterPreference::onClickRenderExceptions, this)); - mCommitCallbackRegistrar.add("Pref.AutoAdjustments", boost::bind(&LLFloaterPreference::onClickAutoAdjustments, this)); - mCommitCallbackRegistrar.add("Pref.HardwareDefaults", boost::bind(&LLFloaterPreference::setHardwareDefaults, this)); - mCommitCallbackRegistrar.add("Pref.AvatarImpostorsEnable", boost::bind(&LLFloaterPreference::onAvatarImpostorsEnable, this)); - mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxComplexity", boost::bind(&LLFloaterPreference::updateMaxComplexity, this)); - mCommitCallbackRegistrar.add("Pref.RenderOptionUpdate", boost::bind(&LLFloaterPreference::onRenderOptionEnable, this)); - mCommitCallbackRegistrar.add("Pref.WindowedMod", boost::bind(&LLFloaterPreference::onCommitWindowedMode, this)); - mCommitCallbackRegistrar.add("Pref.UpdateSliderText", boost::bind(&LLFloaterPreference::refreshUI,this)); - mCommitCallbackRegistrar.add("Pref.QualityPerformance", boost::bind(&LLFloaterPreference::onChangeQuality, this, _2)); - mCommitCallbackRegistrar.add("Pref.applyUIColor", boost::bind(&LLFloaterPreference::applyUIColor, this ,_1, _2)); - mCommitCallbackRegistrar.add("Pref.getUIColor", boost::bind(&LLFloaterPreference::getUIColor, this ,_1, _2)); - mCommitCallbackRegistrar.add("Pref.MaturitySettings", boost::bind(&LLFloaterPreference::onChangeMaturity, this)); - mCommitCallbackRegistrar.add("Pref.BlockList", boost::bind(&LLFloaterPreference::onClickBlockList, this)); - mCommitCallbackRegistrar.add("Pref.Proxy", boost::bind(&LLFloaterPreference::onClickProxySettings, this)); - mCommitCallbackRegistrar.add("Pref.TranslationSettings", boost::bind(&LLFloaterPreference::onClickTranslationSettings, this)); - mCommitCallbackRegistrar.add("Pref.AutoReplace", boost::bind(&LLFloaterPreference::onClickAutoReplace, this)); - mCommitCallbackRegistrar.add("Pref.PermsDefault", boost::bind(&LLFloaterPreference::onClickPermsDefault, this)); - mCommitCallbackRegistrar.add("Pref.RememberedUsernames", boost::bind(&LLFloaterPreference::onClickRememberedUsernames, this)); - mCommitCallbackRegistrar.add("Pref.SpellChecker", boost::bind(&LLFloaterPreference::onClickSpellChecker, this)); - mCommitCallbackRegistrar.add("Pref.Advanced", boost::bind(&LLFloaterPreference::onClickAdvanced, this)); + mCommitCallbackRegistrar.add("Pref.Cancel", { boost::bind(&LLFloaterPreference::onBtnCancel, this, _2), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.OK", { boost::bind(&LLFloaterPreference::onBtnOK, this, _2), cb_info::UNTRUSTED_BLOCK }); + + mCommitCallbackRegistrar.add("Pref.ClearCache", { boost::bind(&LLFloaterPreference::onClickClearCache, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.WebClearCache", { boost::bind(&LLFloaterPreference::onClickBrowserClearCache, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.SetCache", { boost::bind(&LLFloaterPreference::onClickSetCache, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.ResetCache", { boost::bind(&LLFloaterPreference::onClickResetCache, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.ClickSkin", { boost::bind(&LLFloaterPreference::onClickSkin, this,_1, _2), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.SelectSkin", { boost::bind(&LLFloaterPreference::onSelectSkin, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.SetSounds", { boost::bind(&LLFloaterPreference::onClickSetSounds, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.ClickEnablePopup", { boost::bind(&LLFloaterPreference::onClickEnablePopup, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.ClickDisablePopup", { boost::bind(&LLFloaterPreference::onClickDisablePopup, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.LogPath", { boost::bind(&LLFloaterPreference::onClickLogPath, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.RenderExceptions", { boost::bind(&LLFloaterPreference::onClickRenderExceptions, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.AutoAdjustments", { boost::bind(&LLFloaterPreference::onClickAutoAdjustments, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.HardwareDefaults", { boost::bind(&LLFloaterPreference::setHardwareDefaults, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.AvatarImpostorsEnable", { boost::bind(&LLFloaterPreference::onAvatarImpostorsEnable, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxNonImpostors", { boost::bind(&LLFloaterPreference::updateMaxNonImpostors, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxComplexity", { boost::bind(&LLFloaterPreference::updateMaxComplexity, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.RenderOptionUpdate", { boost::bind(&LLFloaterPreference::onRenderOptionEnable, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.WindowedMod", { boost::bind(&LLFloaterPreference::onCommitWindowedMode, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.UpdateSliderText", { boost::bind(&LLFloaterPreference::refreshUI,this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.QualityPerformance", { boost::bind(&LLFloaterPreference::onChangeQuality, this, _2), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.applyUIColor", { boost::bind(&LLFloaterPreference::applyUIColor, this ,_1, _2), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.getUIColor", { boost::bind(&LLFloaterPreference::getUIColor, this ,_1, _2), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.MaturitySettings", { boost::bind(&LLFloaterPreference::onChangeMaturity, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.BlockList", { boost::bind(&LLFloaterPreference::onClickBlockList, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.Proxy", { boost::bind(&LLFloaterPreference::onClickProxySettings, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.TranslationSettings", { boost::bind(&LLFloaterPreference::onClickTranslationSettings, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.AutoReplace", { boost::bind(&LLFloaterPreference::onClickAutoReplace, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.PermsDefault", { boost::bind(&LLFloaterPreference::onClickPermsDefault, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.RememberedUsernames", { boost::bind(&LLFloaterPreference::onClickRememberedUsernames, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.SpellChecker", { boost::bind(&LLFloaterPreference::onClickSpellChecker, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.Advanced", { boost::bind(&LLFloaterPreference::onClickAdvanced, this), cb_info::UNTRUSTED_BLOCK }); sSkin = gSavedSettings.getString("SkinCurrent"); - mCommitCallbackRegistrar.add("Pref.ClickActionChange", boost::bind(&LLFloaterPreference::onClickActionChange, this)); + mCommitCallbackRegistrar.add("Pref.ClickActionChange", { boost::bind(&LLFloaterPreference::onClickActionChange, this), cb_info::UNTRUSTED_BLOCK }); gSavedSettings.getControl("NameTagShowUsernames")->getCommitSignal()->connect(boost::bind(&handleNameTagOptionChanged, _2)); gSavedSettings.getControl("NameTagShowFriends")->getCommitSignal()->connect(boost::bind(&handleNameTagOptionChanged, _2)); @@ -360,10 +361,11 @@ LLFloaterPreference::LLFloaterPreference(const LLSD& key) LLAvatarPropertiesProcessor::getInstance()->addObserver( gAgent.getID(), this ); mComplexityChangedSignal = gSavedSettings.getControl("RenderAvatarMaxComplexity")->getCommitSignal()->connect(boost::bind(&LLFloaterPreference::updateComplexityText, this)); + mImpostorsChangedSignal = gSavedSettings.getControl("RenderAvatarMaxNonImpostors")->getSignal()->connect(boost::bind(&LLFloaterPreference::updateIndirectMaxNonImpostors, this, _2)); - mCommitCallbackRegistrar.add("Pref.ClearLog", boost::bind(&LLConversationLog::onClearLog, &LLConversationLog::instance())); - mCommitCallbackRegistrar.add("Pref.DeleteTranscripts", boost::bind(&LLFloaterPreference::onDeleteTranscripts, this)); - mCommitCallbackRegistrar.add("UpdateFilter", boost::bind(&LLFloaterPreference::onUpdateFilterTerm, this, false)); // <FS:ND/> Hook up for filtering + mCommitCallbackRegistrar.add("Pref.ClearLog", { boost::bind(&LLConversationLog::onClearLog, &LLConversationLog::instance()), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.DeleteTranscripts", { boost::bind(&LLFloaterPreference::onDeleteTranscripts, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("UpdateFilter", { boost::bind(&LLFloaterPreference::onUpdateFilterTerm, this, false), cb_info::UNTRUSTED_BLOCK }); // <FS:ND/> Hook up for filtering } void LLFloaterPreference::processProperties( void* pData, EAvatarProcessorType type ) @@ -543,6 +545,7 @@ LLFloaterPreference::~LLFloaterPreference() { LLConversationLog::instance().removeObserver(this); mComplexityChangedSignal.disconnect(); + mImpostorsChangedSignal.disconnect(); } void LLFloaterPreference::draw() @@ -1287,6 +1290,9 @@ void LLAvatarComplexityControls::setIndirectMaxArc() void LLFloaterPreference::refresh() { LLPanel::refresh(); + setMaxNonImpostorsText( + gSavedSettings.getU32("RenderAvatarMaxNonImpostors"), + getChild<LLTextBox>("IndirectMaxNonImpostorsText", true)); LLAvatarComplexityControls::setText( gSavedSettings.getU32("RenderAvatarMaxComplexity"), getChild<LLTextBox>("IndirectMaxComplexityText", true)); @@ -1561,6 +1567,44 @@ void LLAvatarComplexityControls::setRenderTimeText(F32 value, LLTextBox* text_bo } } +void LLFloaterPreference::updateMaxNonImpostors() +{ + // Called when the IndirectMaxNonImpostors control changes + // Responsible for fixing the slider label (IndirectMaxNonImpostorsText) and setting RenderAvatarMaxNonImpostors + LLSliderCtrl* ctrl = getChild<LLSliderCtrl>("IndirectMaxNonImpostors", true); + U32 value = ctrl->getValue().asInteger(); + + if (0 == value || LLVOAvatar::NON_IMPOSTORS_MAX_SLIDER <= value) + { + value = 0; + } + gSavedSettings.setU32("RenderAvatarMaxNonImpostors", value); + LLVOAvatar::updateImpostorRendering(value); // make it effective immediately + setMaxNonImpostorsText(value, getChild<LLTextBox>("IndirectMaxNonImpostorsText")); +} + +void LLFloaterPreference::updateIndirectMaxNonImpostors(const LLSD& newvalue) +{ + U32 value = newvalue.asInteger(); + if ((value != 0) && (value != gSavedSettings.getU32("IndirectMaxNonImpostors"))) + { + gSavedSettings.setU32("IndirectMaxNonImpostors", value); + } + setMaxNonImpostorsText(value, getChild<LLTextBox>("IndirectMaxNonImpostorsText")); +} + +void LLFloaterPreference::setMaxNonImpostorsText(U32 value, LLTextBox* text_box) +{ + if (0 == value) + { + text_box->setText(LLTrans::getString("no_limit")); + } + else + { + text_box->setText(llformat("%d", value)); + } +} + void LLFloaterPreference::updateMaxComplexity() { // Called when the IndirectMaxComplexity control changes @@ -1951,7 +1995,7 @@ public: :LLEventTimer(period), mCallback(cb) { - mEventTimer.stop(); + stop(); } virtual ~Updater(){} @@ -1959,15 +2003,15 @@ public: void update(const LLSD& new_value) { mNewValue = new_value; - mEventTimer.start(); + start(); } protected: - bool tick() + bool tick() override { mCallback(mNewValue); - mEventTimer.stop(); + stop(); return false; } @@ -1983,11 +2027,11 @@ LLPanelPreference::LLPanelPreference() : LLPanel(), mBandWidthUpdater(NULL) { - mCommitCallbackRegistrar.add("Pref.setControlFalse", boost::bind(&LLPanelPreference::setControlFalse,this, _2)); - mCommitCallbackRegistrar.add("Pref.updateMediaAutoPlayCheckbox", boost::bind(&LLPanelPreference::updateMediaAutoPlayCheckbox, this, _1)); - mCommitCallbackRegistrar.add("Pref.PrefDelete", boost::bind(&LLPanelPreference::deletePreset, this, _2)); - mCommitCallbackRegistrar.add("Pref.PrefSave", boost::bind(&LLPanelPreference::savePreset, this, _2)); - mCommitCallbackRegistrar.add("Pref.PrefLoad", boost::bind(&LLPanelPreference::loadPreset, this, _2)); + mCommitCallbackRegistrar.add("Pref.setControlFalse", { boost::bind(&LLPanelPreference::setControlFalse,this, _2), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.updateMediaAutoPlayCheckbox", { boost::bind(&LLPanelPreference::updateMediaAutoPlayCheckbox, this, _1), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.PrefDelete", { boost::bind(&LLPanelPreference::deletePreset, this, _2), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.PrefSave", { boost::bind(&LLPanelPreference::savePreset, this, _2), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.PrefLoad", { boost::bind(&LLPanelPreference::loadPreset, this, _2), cb_info::UNTRUSTED_BLOCK }); } //virtual @@ -3087,9 +3131,9 @@ LLFloaterPreferenceProxy::LLFloaterPreferenceProxy(const LLSD& key) : LLFloater(key), mSocksSettingsDirty(false) { - mCommitCallbackRegistrar.add("Proxy.OK", boost::bind(&LLFloaterPreferenceProxy::onBtnOk, this)); - mCommitCallbackRegistrar.add("Proxy.Cancel", boost::bind(&LLFloaterPreferenceProxy::onBtnCancel, this)); - mCommitCallbackRegistrar.add("Proxy.Change", boost::bind(&LLFloaterPreferenceProxy::onChangeSocksSettings, this)); + mCommitCallbackRegistrar.add("Proxy.OK", { boost::bind(&LLFloaterPreferenceProxy::onBtnOk, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Proxy.Cancel", { boost::bind(&LLFloaterPreferenceProxy::onBtnCancel, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Proxy.Change", { boost::bind(&LLFloaterPreferenceProxy::onChangeSocksSettings, this), cb_info::UNTRUSTED_BLOCK }); } LLFloaterPreferenceProxy::~LLFloaterPreferenceProxy() diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h index 51ed3d8179..40806c22fc 100644 --- a/indra/newview/llfloaterpreference.h +++ b/indra/newview/llfloaterpreference.h @@ -206,6 +206,9 @@ private: void onDeleteTranscripts(); void onDeleteTranscriptsResponse(const LLSD& notification, const LLSD& response); void updateDeleteTranscriptsButton(); + void updateMaxNonImpostors(); + void updateIndirectMaxNonImpostors(const LLSD& newvalue); + void setMaxNonImpostorsText(U32 value, LLTextBox* text_box); void updateMaxComplexity(); void updateComplexityText(); static bool loadFromFilename(const std::string& filename, std::map<std::string, std::string> &label_map); @@ -234,6 +237,7 @@ private: std::unique_ptr< ll::prefs::SearchData > mSearchData; bool mSearchDataDirty; + boost::signals2::connection mImpostorsChangedSignal; boost::signals2::connection mComplexityChangedSignal; void onUpdateFilterTerm( bool force = false ); diff --git a/indra/newview/llfloaterpreferencesgraphicsadvanced.cpp b/indra/newview/llfloaterpreferencesgraphicsadvanced.cpp index 1d48fe70f2..3cccecfa4a 100644 --- a/indra/newview/llfloaterpreferencesgraphicsadvanced.cpp +++ b/indra/newview/llfloaterpreferencesgraphicsadvanced.cpp @@ -45,12 +45,12 @@ LLFloaterPreferenceGraphicsAdvanced::LLFloaterPreferenceGraphicsAdvanced(const LLSD& key) : LLFloater(key) { - mCommitCallbackRegistrar.add("Pref.RenderOptionUpdate", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onRenderOptionEnable, this)); - mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxNonImpostors", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxNonImpostors,this)); - mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxComplexity", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxComplexity,this)); + mCommitCallbackRegistrar.add("Pref.RenderOptionUpdate", { boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onRenderOptionEnable, this) }); + mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxNonImpostors", { boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxNonImpostors,this) }); + mCommitCallbackRegistrar.add("Pref.UpdateIndirectMaxComplexity", { boost::bind(&LLFloaterPreferenceGraphicsAdvanced::updateMaxComplexity,this) }); - mCommitCallbackRegistrar.add("Pref.Cancel", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onBtnCancel, this, _2)); - mCommitCallbackRegistrar.add("Pref.OK", boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onBtnOK, this, _2)); + mCommitCallbackRegistrar.add("Pref.Cancel", { boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onBtnCancel, this, _2), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Pref.OK", { boost::bind(&LLFloaterPreferenceGraphicsAdvanced::onBtnOK, this, _2), cb_info::UNTRUSTED_BLOCK }); } LLFloaterPreferenceGraphicsAdvanced::~LLFloaterPreferenceGraphicsAdvanced() @@ -247,8 +247,8 @@ void LLFloaterPreferenceGraphicsAdvanced::updateIndirectMaxNonImpostors(const LL if ((value != 0) && (value != gSavedSettings.getU32("IndirectMaxNonImpostors"))) { gSavedSettings.setU32("IndirectMaxNonImpostors", value); - setMaxNonImpostorsText(value, getChild<LLTextBox>("IndirectMaxNonImpostorsText")); } + setMaxNonImpostorsText(value, getChild<LLTextBox>("IndirectMaxNonImpostorsText")); } void LLFloaterPreferenceGraphicsAdvanced::setMaxNonImpostorsText(U32 value, LLTextBox* text_box) diff --git a/indra/newview/llfloaterpreferencesgraphicsadvanced.h b/indra/newview/llfloaterpreferencesgraphicsadvanced.h index 61203be068..6f793c1379 100644 --- a/indra/newview/llfloaterpreferencesgraphicsadvanced.h +++ b/indra/newview/llfloaterpreferencesgraphicsadvanced.h @@ -61,6 +61,7 @@ protected: void onBtnOK(const LLSD& userdata); void onBtnCancel(const LLSD& userdata); + boost::signals2::connection mImpostorsChangedSignal; boost::signals2::connection mComplexityChangedSignal; boost::signals2::connection mComplexityModeChangedSignal; boost::signals2::connection mLODFactorChangedSignal; diff --git a/indra/newview/llfloaterpreferenceviewadvanced.cpp b/indra/newview/llfloaterpreferenceviewadvanced.cpp index 3481397daa..290742a4cd 100644 --- a/indra/newview/llfloaterpreferenceviewadvanced.cpp +++ b/indra/newview/llfloaterpreferenceviewadvanced.cpp @@ -37,7 +37,7 @@ LLFloaterPreferenceViewAdvanced::LLFloaterPreferenceViewAdvanced(const LLSD& key) : LLFloater(key) { - mCommitCallbackRegistrar.add("CommitSettings", boost::bind(&LLFloaterPreferenceViewAdvanced::onCommitSettings, this)); + mCommitCallbackRegistrar.add("CommitSettings", { boost::bind(&LLFloaterPreferenceViewAdvanced::onCommitSettings, this), cb_info::UNTRUSTED_BLOCK }); } LLFloaterPreferenceViewAdvanced::~LLFloaterPreferenceViewAdvanced() diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index 8070284e32..165fb5ef88 100644 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -259,8 +259,8 @@ bool LLFloaterRegionInfo::postBuild() panel = new LLPanelRegionGeneralInfo; mInfoPanels.push_back(panel); - panel->getCommitCallbackRegistrar().add("RegionInfo.ManageTelehub", boost::bind(&LLPanelRegionInfo::onClickManageTelehub, panel)); - panel->getCommitCallbackRegistrar().add("RegionInfo.ManageRestart", boost::bind(&LLPanelRegionInfo::onClickManageRestartSchedule, panel)); + panel->getCommitCallbackRegistrar().add("RegionInfo.ManageTelehub", { boost::bind(&LLPanelRegionInfo::onClickManageTelehub, panel) }); + panel->getCommitCallbackRegistrar().add("RegionInfo.ManageRestart", { boost::bind(&LLPanelRegionInfo::onClickManageRestartSchedule, panel) }); panel->buildFromFile("panel_region_general.xml"); mTab->addTabPanel(panel); @@ -2820,6 +2820,16 @@ void LLPanelEstateCovenant::setEstateName(const std::string& name) } // static +void LLPanelEstateCovenant::updateCovenant(const LLTextBase* source, const LLUUID& asset_id) +{ + if (LLPanelEstateCovenant* panelp = LLFloaterRegionInfo::getPanelCovenant()) + { + panelp->mEditor->copyContents(source); + panelp->setCovenantID(asset_id); + } +} + +// static void LLPanelEstateCovenant::updateCovenantText(const std::string& string, const LLUUID& asset_id) { LLPanelEstateCovenant* panelp = LLFloaterRegionInfo::getPanelCovenant(); @@ -4330,7 +4340,6 @@ void LLPanelRegionEnvironment::onChkAllowOverride(bool value) mAllowOverrideRestore = mAllowOverride; mAllowOverride = value; - std::string notification("EstateParcelEnvironmentOverride"); if (LLPanelEstateInfo::isLindenEstate()) notification = "ChangeLindenEstate"; diff --git a/indra/newview/llfloaterregioninfo.h b/indra/newview/llfloaterregioninfo.h index b604a28fc3..65c1291728 100644 --- a/indra/newview/llfloaterregioninfo.h +++ b/indra/newview/llfloaterregioninfo.h @@ -382,6 +382,7 @@ public: void* user_data, S32 status, LLExtStat ext_status); // Accessor functions + static void updateCovenant(const LLTextBase* source, const LLUUID& asset_id); static void updateCovenantText(const std::string& string, const LLUUID& asset_id); static void updateEstateName(const std::string& name); static void updateLastModified(const std::string& text); diff --git a/indra/newview/llfloaterregionrestarting.h b/indra/newview/llfloaterregionrestarting.h index ee836dd00d..016c90f944 100644 --- a/indra/newview/llfloaterregionrestarting.h +++ b/indra/newview/llfloaterregionrestarting.h @@ -42,10 +42,10 @@ public: private: LLFloaterRegionRestarting(const LLSD& key); virtual ~LLFloaterRegionRestarting(); - virtual bool postBuild(); - virtual bool tick(); - virtual void refresh(); - virtual void draw(); + bool postBuild() override; + bool tick() override; + void refresh() override; + void draw() override; virtual void regionChange(); std::string mName; diff --git a/indra/newview/llfloaterscriptedprefs.cpp b/indra/newview/llfloaterscriptedprefs.cpp index fa31cd72c1..fbeb256a9c 100644 --- a/indra/newview/llfloaterscriptedprefs.cpp +++ b/indra/newview/llfloaterscriptedprefs.cpp @@ -36,8 +36,8 @@ LLFloaterScriptEdPrefs::LLFloaterScriptEdPrefs(const LLSD& key) : LLFloater(key) , mEditor(NULL) { - mCommitCallbackRegistrar.add("ScriptPref.applyUIColor", boost::bind(&LLFloaterScriptEdPrefs::applyUIColor, this ,_1, _2)); - mCommitCallbackRegistrar.add("ScriptPref.getUIColor", boost::bind(&LLFloaterScriptEdPrefs::getUIColor, this ,_1, _2)); + mCommitCallbackRegistrar.add("ScriptPref.applyUIColor", { boost::bind(&LLFloaterScriptEdPrefs::applyUIColor, this ,_1, _2) }); + mCommitCallbackRegistrar.add("ScriptPref.getUIColor", { boost::bind(&LLFloaterScriptEdPrefs::getUIColor, this ,_1, _2) }); } bool LLFloaterScriptEdPrefs::postBuild() diff --git a/indra/newview/llfloatersettingscolor.cpp b/indra/newview/llfloatersettingscolor.cpp index d9c382a1dc..1ca2412907 100644 --- a/indra/newview/llfloatersettingscolor.cpp +++ b/indra/newview/llfloatersettingscolor.cpp @@ -43,8 +43,8 @@ LLFloaterSettingsColor::LLFloaterSettingsColor(const LLSD& key) : LLFloater(key), mSettingList(NULL) { - mCommitCallbackRegistrar.add("CommitSettings", boost::bind(&LLFloaterSettingsColor::onCommitSettings, this)); - mCommitCallbackRegistrar.add("ClickDefault", boost::bind(&LLFloaterSettingsColor::onClickDefault, this)); + mCommitCallbackRegistrar.add("CommitSettings", { boost::bind(&LLFloaterSettingsColor::onCommitSettings, this) }); + mCommitCallbackRegistrar.add("ClickDefault", { boost::bind(&LLFloaterSettingsColor::onClickDefault, this) }); } LLFloaterSettingsColor::~LLFloaterSettingsColor() diff --git a/indra/newview/llfloatersettingsdebug.cpp b/indra/newview/llfloatersettingsdebug.cpp index 8cc01f6dc6..0a01fff36d 100644 --- a/indra/newview/llfloatersettingsdebug.cpp +++ b/indra/newview/llfloatersettingsdebug.cpp @@ -34,14 +34,16 @@ #include "llcolorswatch.h" #include "llviewercontrol.h" #include "lltexteditor.h" +#include "llclipboard.h" +#include "llsdutil.h" LLFloaterSettingsDebug::LLFloaterSettingsDebug(const LLSD& key) : LLFloater(key), mSettingList(NULL) { - mCommitCallbackRegistrar.add("CommitSettings", boost::bind(&LLFloaterSettingsDebug::onCommitSettings, this)); - mCommitCallbackRegistrar.add("ClickDefault", boost::bind(&LLFloaterSettingsDebug::onClickDefault, this)); + mCommitCallbackRegistrar.add("CommitSettings", { boost::bind(&LLFloaterSettingsDebug::onCommitSettings, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("ClickDefault", { boost::bind(&LLFloaterSettingsDebug::onClickDefault, this), cb_info::UNTRUSTED_BLOCK }); } LLFloaterSettingsDebug::~LLFloaterSettingsDebug() @@ -64,6 +66,8 @@ bool LLFloaterSettingsDebug::postBuild() mSettingNameText = getChild<LLTextBox>("setting_name_txt"); mComment = getChild<LLTextEditor>("comment_text"); + mLLSDVal = getChild<LLTextEditor>("llsd_text"); + mCopyBtn = getChild<LLButton>("copy_btn"); getChild<LLFilterEditor>("filter_input")->setCommitCallback(boost::bind(&LLFloaterSettingsDebug::setSearchFilter, this, _2)); @@ -71,6 +75,8 @@ bool LLFloaterSettingsDebug::postBuild() mSettingList->setCommitOnSelectionChange(true); mSettingList->setCommitCallback(boost::bind(&LLFloaterSettingsDebug::onSettingSelect, this)); + mCopyBtn->setCommitCallback([this](LLUICtrl *ctrl, const LLSD ¶m) { onClickCopy(); }); + updateList(); gSavedSettings.getControl("DebugSettingsHideDefault")->getCommitSignal()->connect(boost::bind(&LLFloaterSettingsDebug::updateList, this, false)); @@ -205,6 +211,7 @@ void LLFloaterSettingsDebug::updateControl(LLControlVariable* controlp) mSettingNameText->setVisible(true); mSettingNameText->setText(controlp->getName()); mSettingNameText->setToolTip(controlp->getName()); + mCopyBtn->setVisible(true); mComment->setVisible(true); std::string old_text = mComment->getText(); @@ -465,6 +472,17 @@ void LLFloaterSettingsDebug::updateControl(LLControlVariable* controlp) mColorSwatch->setValue(sd); break; } + case TYPE_LLSD: + { + mLLSDVal->setVisible(true); + std::string new_text = ll_pretty_print_sd(sd); + // Don't setText if not nessesary, it will reset scroll + if (mLLSDVal->getText() != new_text) + { + mLLSDVal->setText(new_text); + } + break; + } default: mComment->setText(std::string("unknown")); break; @@ -631,7 +649,14 @@ void LLFloaterSettingsDebug::hideUIControls() mValText->setVisible(false); mDefaultButton->setVisible(false); mBooleanCombo->setVisible(false); + mLLSDVal->setVisible(false); mSettingNameText->setVisible(false); + mCopyBtn->setVisible(false); mComment->setVisible(false); } +void LLFloaterSettingsDebug::onClickCopy() +{ + std::string setting_name = mSettingNameText->getText(); + LLClipboard::instance().copyToClipboard(utf8str_to_wstring(setting_name), 0, narrow(setting_name.size())); +} diff --git a/indra/newview/llfloatersettingsdebug.h b/indra/newview/llfloatersettingsdebug.h index b813cf4a74..8849766867 100644 --- a/indra/newview/llfloatersettingsdebug.h +++ b/indra/newview/llfloatersettingsdebug.h @@ -49,6 +49,7 @@ public: void onCommitSettings(); void onClickDefault(); + void onClickCopy(); bool matchesSearchFilter(std::string setting_name); bool isSettingHidden(LLControlVariable* control); @@ -77,7 +78,9 @@ protected: LLUICtrl* mBooleanCombo = nullptr; LLUICtrl* mValText = nullptr; LLUICtrl* mDefaultButton = nullptr; + LLTextEditor* mLLSDVal = nullptr; LLTextBox* mSettingNameText = nullptr; + LLButton* mCopyBtn = nullptr; LLColorSwatchCtrl* mColorSwatch = nullptr; diff --git a/indra/newview/llfloatersnapshot.cpp b/indra/newview/llfloatersnapshot.cpp index 1f52f1d180..fb4537f22a 100644 --- a/indra/newview/llfloatersnapshot.cpp +++ b/indra/newview/llfloatersnapshot.cpp @@ -1297,7 +1297,8 @@ bool LLFloaterSnapshotBase::ImplBase::updatePreviewList(bool initialized) void LLFloaterSnapshotBase::ImplBase::updateLivePreview() { - if (ImplBase::updatePreviewList(true) && mFloater) + // don't update preview for hidden floater + if (mFloater && mFloater->isInVisibleChain() && ImplBase::updatePreviewList(true)) { LL_DEBUGS() << "changed" << LL_ENDL; updateControls(mFloater); diff --git a/indra/newview/llfloatertestinspectors.cpp b/indra/newview/llfloatertestinspectors.cpp index 7f8d122435..5b29dec6de 100644 --- a/indra/newview/llfloatertestinspectors.cpp +++ b/indra/newview/llfloatertestinspectors.cpp @@ -37,9 +37,9 @@ LLFloaterTestInspectors::LLFloaterTestInspectors(const LLSD& seed) : LLFloater(seed) { mCommitCallbackRegistrar.add("ShowAvatarInspector", - boost::bind(&LLFloaterTestInspectors::showAvatarInspector, this, _1, _2)); + { boost::bind(&LLFloaterTestInspectors::showAvatarInspector, this, _1, _2) }); mCommitCallbackRegistrar.add("ShowObjectInspector", - boost::bind(&LLFloaterTestInspectors::showObjectInspector, this, _1, _2)); + { boost::bind(&LLFloaterTestInspectors::showObjectInspector, this, _1, _2) }); } LLFloaterTestInspectors::~LLFloaterTestInspectors() diff --git a/indra/newview/llfloatertools.cpp b/indra/newview/llfloatertools.cpp index f6d8fcab36..5fe089aec5 100644 --- a/indra/newview/llfloatertools.cpp +++ b/indra/newview/llfloatertools.cpp @@ -387,21 +387,21 @@ LLFloaterTools::LLFloaterTools(const LLSD& key) mFactoryMap["Contents"] = LLCallbackMap(createPanelContents, this);//LLPanelContents mFactoryMap["land info panel"] = LLCallbackMap(createPanelLandInfo, this);//LLPanelLandInfo - mCommitCallbackRegistrar.add("BuildTool.setTool", boost::bind(&LLFloaterTools::setTool,this, _2)); - mCommitCallbackRegistrar.add("BuildTool.commitZoom", boost::bind(&commit_slider_zoom, _1)); - mCommitCallbackRegistrar.add("BuildTool.commitRadioFocus", boost::bind(&commit_radio_group_focus, _1)); - mCommitCallbackRegistrar.add("BuildTool.commitRadioMove", boost::bind(&commit_radio_group_move,_1)); - mCommitCallbackRegistrar.add("BuildTool.commitRadioEdit", boost::bind(&commit_radio_group_edit,_1)); - - mCommitCallbackRegistrar.add("BuildTool.gridMode", boost::bind(&commit_grid_mode,_1)); - mCommitCallbackRegistrar.add("BuildTool.selectComponent", boost::bind(&commit_select_component, this)); - mCommitCallbackRegistrar.add("BuildTool.gridOptions", boost::bind(&LLFloaterTools::onClickGridOptions,this)); - mCommitCallbackRegistrar.add("BuildTool.applyToSelection", boost::bind(&click_apply_to_selection, this)); - mCommitCallbackRegistrar.add("BuildTool.commitRadioLand", boost::bind(&commit_radio_group_land,_1)); - mCommitCallbackRegistrar.add("BuildTool.LandBrushForce", boost::bind(&commit_slider_dozer_force,_1)); - - mCommitCallbackRegistrar.add("BuildTool.LinkObjects", boost::bind(&LLSelectMgr::linkObjects, LLSelectMgr::getInstance())); - mCommitCallbackRegistrar.add("BuildTool.UnlinkObjects", boost::bind(&LLSelectMgr::unlinkObjects, LLSelectMgr::getInstance())); + mCommitCallbackRegistrar.add("BuildTool.setTool", { boost::bind(&LLFloaterTools::setTool,this, _2) }); + mCommitCallbackRegistrar.add("BuildTool.commitZoom", { boost::bind(&commit_slider_zoom, _1), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("BuildTool.commitRadioFocus", { boost::bind(&commit_radio_group_focus, _1), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("BuildTool.commitRadioMove", { boost::bind(&commit_radio_group_move,_1), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("BuildTool.commitRadioEdit", { boost::bind(&commit_radio_group_edit,_1), cb_info::UNTRUSTED_BLOCK }); + + mCommitCallbackRegistrar.add("BuildTool.gridMode", { boost::bind(&commit_grid_mode,_1), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("BuildTool.selectComponent", { boost::bind(&commit_select_component, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("BuildTool.gridOptions", { boost::bind(&LLFloaterTools::onClickGridOptions,this) }); + mCommitCallbackRegistrar.add("BuildTool.applyToSelection", { boost::bind(&click_apply_to_selection, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("BuildTool.commitRadioLand", { boost::bind(&commit_radio_group_land,_1), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("BuildTool.LandBrushForce", { boost::bind(&commit_slider_dozer_force,_1), cb_info::UNTRUSTED_BLOCK }); + + mCommitCallbackRegistrar.add("BuildTool.LinkObjects", { boost::bind(&LLSelectMgr::linkObjects, LLSelectMgr::getInstance()) }); + mCommitCallbackRegistrar.add("BuildTool.UnlinkObjects", { boost::bind(&LLSelectMgr::unlinkObjects, LLSelectMgr::getInstance()) }); mLandImpactsObserver = new LLLandImpactsObserver(); LLViewerParcelMgr::getInstance()->addObserver(mLandImpactsObserver); diff --git a/indra/newview/llfloatertopobjects.cpp b/indra/newview/llfloatertopobjects.cpp index 9bc8c63fa0..e379854160 100644 --- a/indra/newview/llfloatertopobjects.cpp +++ b/indra/newview/llfloatertopobjects.cpp @@ -76,16 +76,16 @@ LLFloaterTopObjects::LLFloaterTopObjects(const LLSD& key) mInitialized(false), mtotalScore(0.f) { - mCommitCallbackRegistrar.add("TopObjects.ShowBeacon", boost::bind(&LLFloaterTopObjects::onClickShowBeacon, this)); - mCommitCallbackRegistrar.add("TopObjects.ReturnSelected", boost::bind(&LLFloaterTopObjects::onReturnSelected, this)); - mCommitCallbackRegistrar.add("TopObjects.ReturnAll", boost::bind(&LLFloaterTopObjects::onReturnAll, this)); - mCommitCallbackRegistrar.add("TopObjects.Refresh", boost::bind(&LLFloaterTopObjects::onRefresh, this)); - mCommitCallbackRegistrar.add("TopObjects.GetByObjectName", boost::bind(&LLFloaterTopObjects::onGetByObjectName, this)); - mCommitCallbackRegistrar.add("TopObjects.GetByOwnerName", boost::bind(&LLFloaterTopObjects::onGetByOwnerName, this)); - mCommitCallbackRegistrar.add("TopObjects.GetByParcelName", boost::bind(&LLFloaterTopObjects::onGetByParcelName, this)); - mCommitCallbackRegistrar.add("TopObjects.CommitObjectsList",boost::bind(&LLFloaterTopObjects::onCommitObjectsList, this)); - - mCommitCallbackRegistrar.add("TopObjects.TeleportToSelected", boost::bind(&LLFloaterTopObjects::teleportToSelectedObject, this)); + mCommitCallbackRegistrar.add("TopObjects.ShowBeacon", { boost::bind(&LLFloaterTopObjects::onClickShowBeacon, this) }); + mCommitCallbackRegistrar.add("TopObjects.ReturnSelected", { boost::bind(&LLFloaterTopObjects::onReturnSelected, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("TopObjects.ReturnAll", { boost::bind(&LLFloaterTopObjects::onReturnAll, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("TopObjects.Refresh", { boost::bind(&LLFloaterTopObjects::onRefresh, this), cb_info::UNTRUSTED_THROTTLE }); + mCommitCallbackRegistrar.add("TopObjects.GetByObjectName", { boost::bind(&LLFloaterTopObjects::onGetByObjectName, this), cb_info::UNTRUSTED_THROTTLE }); + mCommitCallbackRegistrar.add("TopObjects.GetByOwnerName", { boost::bind(&LLFloaterTopObjects::onGetByOwnerName, this), cb_info::UNTRUSTED_THROTTLE }); + mCommitCallbackRegistrar.add("TopObjects.GetByParcelName", { boost::bind(&LLFloaterTopObjects::onGetByParcelName, this), cb_info::UNTRUSTED_THROTTLE }); + mCommitCallbackRegistrar.add("TopObjects.CommitObjectsList",{ boost::bind(&LLFloaterTopObjects::onCommitObjectsList, this), cb_info::UNTRUSTED_THROTTLE }); + + mCommitCallbackRegistrar.add("TopObjects.TeleportToSelected", { boost::bind(&LLFloaterTopObjects::teleportToSelectedObject, this), cb_info::UNTRUSTED_THROTTLE }); } LLFloaterTopObjects::~LLFloaterTopObjects() diff --git a/indra/newview/llfloatertoybox.cpp b/indra/newview/llfloatertoybox.cpp index f6257dbd3d..f56b2aa962 100644 --- a/indra/newview/llfloatertoybox.cpp +++ b/indra/newview/llfloatertoybox.cpp @@ -41,8 +41,8 @@ LLFloaterToybox::LLFloaterToybox(const LLSD& key) : LLFloater(key) , mToolBar(NULL) { - mCommitCallbackRegistrar.add("Toybox.RestoreDefaults", boost::bind(&LLFloaterToybox::onBtnRestoreDefaults, this)); - mCommitCallbackRegistrar.add("Toybox.ClearAll", boost::bind(&LLFloaterToybox::onBtnClearAll, this)); + mCommitCallbackRegistrar.add("Toybox.RestoreDefaults", { boost::bind(&LLFloaterToybox::onBtnRestoreDefaults, this) }); + mCommitCallbackRegistrar.add("Toybox.ClearAll", { boost::bind(&LLFloaterToybox::onBtnClearAll, this) }); } LLFloaterToybox::~LLFloaterToybox() diff --git a/indra/newview/llfloateruipreview.cpp b/indra/newview/llfloateruipreview.cpp index 990a299c50..12576c042b 100644 --- a/indra/newview/llfloateruipreview.cpp +++ b/indra/newview/llfloateruipreview.cpp @@ -253,7 +253,7 @@ class LLFadeEventTimer : public LLEventTimer { public: LLFadeEventTimer(F32 refresh, LLGUIPreviewLiveFile* parent); - bool tick(); + bool tick() override; LLGUIPreviewLiveFile* mParent; private: bool mFadingOut; // fades in then out; this is toggled in between diff --git a/indra/newview/llfloatervoiceeffect.cpp b/indra/newview/llfloatervoiceeffect.cpp index 9f7c9aba87..45b6616d1e 100644 --- a/indra/newview/llfloatervoiceeffect.cpp +++ b/indra/newview/llfloatervoiceeffect.cpp @@ -36,9 +36,9 @@ LLFloaterVoiceEffect::LLFloaterVoiceEffect(const LLSD& key) : LLFloater(key) { - mCommitCallbackRegistrar.add("VoiceEffect.Record", boost::bind(&LLFloaterVoiceEffect::onClickRecord, this)); - mCommitCallbackRegistrar.add("VoiceEffect.Play", boost::bind(&LLFloaterVoiceEffect::onClickPlay, this)); - mCommitCallbackRegistrar.add("VoiceEffect.Stop", boost::bind(&LLFloaterVoiceEffect::onClickStop, this)); + mCommitCallbackRegistrar.add("VoiceEffect.Record", { boost::bind(&LLFloaterVoiceEffect::onClickRecord, this) }); + mCommitCallbackRegistrar.add("VoiceEffect.Play", { boost::bind(&LLFloaterVoiceEffect::onClickPlay, this) }); + mCommitCallbackRegistrar.add("VoiceEffect.Stop", { boost::bind(&LLFloaterVoiceEffect::onClickStop, this) }); // mCommitCallbackRegistrar.add("VoiceEffect.Activate", boost::bind(&LLFloaterVoiceEffect::onClickActivate, this)); } diff --git a/indra/newview/llfloaterwebcontent.cpp b/indra/newview/llfloaterwebcontent.cpp index e1b6df6072..0cd20b7020 100644 --- a/indra/newview/llfloaterwebcontent.cpp +++ b/indra/newview/llfloaterwebcontent.cpp @@ -75,13 +75,13 @@ LLFloaterWebContent::LLFloaterWebContent( const Params& params ) mDisplayURL(""), mDevelopMode(params.dev_mode) // if called from "Develop" Menu, set a flag and change things to be more useful for devs { - mCommitCallbackRegistrar.add( "WebContent.Back", boost::bind( &LLFloaterWebContent::onClickBack, this )); - mCommitCallbackRegistrar.add( "WebContent.Forward", boost::bind( &LLFloaterWebContent::onClickForward, this )); - mCommitCallbackRegistrar.add( "WebContent.Reload", boost::bind( &LLFloaterWebContent::onClickReload, this )); - mCommitCallbackRegistrar.add( "WebContent.Stop", boost::bind( &LLFloaterWebContent::onClickStop, this )); - mCommitCallbackRegistrar.add( "WebContent.EnterAddress", boost::bind( &LLFloaterWebContent::onEnterAddress, this )); - mCommitCallbackRegistrar.add( "WebContent.PopExternal", boost::bind(&LLFloaterWebContent::onPopExternal, this)); - mCommitCallbackRegistrar.add( "WebContent.TestURL", boost::bind(&LLFloaterWebContent::onTestURL, this, _2)); + mCommitCallbackRegistrar.add( "WebContent.Back", { boost::bind( &LLFloaterWebContent::onClickBack, this ), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add( "WebContent.Forward", { boost::bind( &LLFloaterWebContent::onClickForward, this ), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add( "WebContent.Reload", { boost::bind( &LLFloaterWebContent::onClickReload, this ), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add( "WebContent.Stop", { boost::bind( &LLFloaterWebContent::onClickStop, this ), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add( "WebContent.EnterAddress", { boost::bind( &LLFloaterWebContent::onEnterAddress, this ), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add( "WebContent.PopExternal", { boost::bind(&LLFloaterWebContent::onPopExternal, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add( "WebContent.TestURL", { boost::bind(&LLFloaterWebContent::onTestURL, this, _2), cb_info::UNTRUSTED_BLOCK }); } bool LLFloaterWebContent::postBuild() diff --git a/indra/newview/llfloaterworldmap.cpp b/indra/newview/llfloaterworldmap.cpp index 30ed723db6..dda7266220 100755 --- a/indra/newview/llfloaterworldmap.cpp +++ b/indra/newview/llfloaterworldmap.cpp @@ -337,17 +337,17 @@ LLFloaterWorldMap::LLFloaterWorldMap(const LLSD& key) mFactoryMap["objects_mapview"] = LLCallbackMap(createWorldMapView, nullptr); - mCommitCallbackRegistrar.add("WMap.Coordinates", boost::bind(&LLFloaterWorldMap::onCoordinatesCommit, this)); - mCommitCallbackRegistrar.add("WMap.Location", boost::bind(&LLFloaterWorldMap::onLocationCommit, this)); - mCommitCallbackRegistrar.add("WMap.AvatarCombo", boost::bind(&LLFloaterWorldMap::onAvatarComboCommit, this)); - mCommitCallbackRegistrar.add("WMap.Landmark", boost::bind(&LLFloaterWorldMap::onLandmarkComboCommit, this)); - mCommitCallbackRegistrar.add("WMap.SearchResult", boost::bind(&LLFloaterWorldMap::onCommitSearchResult, this)); - mCommitCallbackRegistrar.add("WMap.GoHome", boost::bind(&LLFloaterWorldMap::onGoHome, this)); - mCommitCallbackRegistrar.add("WMap.Teleport", boost::bind(&LLFloaterWorldMap::onClickTeleportBtn, this)); - mCommitCallbackRegistrar.add("WMap.ShowTarget", boost::bind(&LLFloaterWorldMap::onShowTargetBtn, this)); - mCommitCallbackRegistrar.add("WMap.ShowAgent", boost::bind(&LLFloaterWorldMap::onShowAgentBtn, this)); - mCommitCallbackRegistrar.add("WMap.Clear", boost::bind(&LLFloaterWorldMap::onClearBtn, this)); - mCommitCallbackRegistrar.add("WMap.CopySLURL", boost::bind(&LLFloaterWorldMap::onCopySLURL, this)); + mCommitCallbackRegistrar.add("WMap.Coordinates", { boost::bind(&LLFloaterWorldMap::onCoordinatesCommit, this) }); + mCommitCallbackRegistrar.add("WMap.Location", { boost::bind(&LLFloaterWorldMap::onLocationCommit, this) }); + mCommitCallbackRegistrar.add("WMap.AvatarCombo", { boost::bind(&LLFloaterWorldMap::onAvatarComboCommit, this) }); + mCommitCallbackRegistrar.add("WMap.Landmark", { boost::bind(&LLFloaterWorldMap::onLandmarkComboCommit, this) }); + mCommitCallbackRegistrar.add("WMap.SearchResult", { boost::bind(&LLFloaterWorldMap::onCommitSearchResult, this) }); + mCommitCallbackRegistrar.add("WMap.GoHome", { boost::bind(&LLFloaterWorldMap::onGoHome, this) }); + mCommitCallbackRegistrar.add("WMap.Teleport", { boost::bind(&LLFloaterWorldMap::onClickTeleportBtn, this) }); + mCommitCallbackRegistrar.add("WMap.ShowTarget", { boost::bind(&LLFloaterWorldMap::onShowTargetBtn, this) }); + mCommitCallbackRegistrar.add("WMap.ShowAgent", { boost::bind(&LLFloaterWorldMap::onShowAgentBtn, this) }); + mCommitCallbackRegistrar.add("WMap.Clear", { boost::bind(&LLFloaterWorldMap::onClearBtn, this) }); + mCommitCallbackRegistrar.add("WMap.CopySLURL", { boost::bind(&LLFloaterWorldMap::onCopySLURL, this) }); gSavedSettings.getControl("PreferredMaturity")->getSignal()->connect(boost::bind(&LLFloaterWorldMap::onChangeMaturity, this)); } diff --git a/indra/newview/llflyoutcombobtn.cpp b/indra/newview/llflyoutcombobtn.cpp index 728f60e1c0..97cd637da1 100644 --- a/indra/newview/llflyoutcombobtn.cpp +++ b/indra/newview/llflyoutcombobtn.cpp @@ -41,7 +41,7 @@ LLFlyoutComboBtnCtrl::LLFlyoutComboBtnCtrl(LLPanel* parent, { // register action mapping before creating menu LLUICtrl::CommitCallbackRegistry::ScopedRegistrar save_registar; - save_registar.add("FlyoutCombo.Button.Action", [this](LLUICtrl *ctrl, const LLSD &data) { onFlyoutItemSelected(ctrl, data); }); + save_registar.add("FlyoutCombo.Button.Action", { [this](LLUICtrl *ctrl, const LLSD &data) { onFlyoutItemSelected(ctrl, data); } }); LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enabled_rgistar; enabled_rgistar.add("FlyoutCombo.Button.Check", [this](LLUICtrl *ctrl, const LLSD &data) { return onFlyoutItemCheck(ctrl, data); }); diff --git a/indra/newview/llglsandbox.cpp b/indra/newview/llglsandbox.cpp index 0248f5710f..112008172e 100644 --- a/indra/newview/llglsandbox.cpp +++ b/indra/newview/llglsandbox.cpp @@ -68,7 +68,7 @@ #include <vector> // Height of the yellow selection highlight posts for land -const F32 PARCEL_POST_HEIGHT = 0.666f; +constexpr F32 PARCEL_POST_HEIGHT = 0.666f; // Returns true if you got at least one object void LLToolSelectRect::handleRectangleSelection(S32 x, S32 y, MASK mask) @@ -178,27 +178,27 @@ void LLToolSelectRect::handleRectangleSelection(S32 x, S32 y, MASK mask) { std::vector<LLDrawable*> potentials; - for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); - iter != LLWorld::getInstance()->getRegionList().end(); ++iter) + for (LLViewerRegion* region : LLWorld::getInstance()->getRegionList()) { - LLViewerRegion* region = *iter; for (U32 i = 0; i < LLViewerRegion::NUM_PARTITIONS; i++) { - LLSpatialPartition* part = region->getSpatialPartition(i); - if (part) + if (LLSpatialPartition* part = region->getSpatialPartition(i)) { part->cull(*LLViewerCamera::getInstance(), &potentials, true); } } } - for (std::vector<LLDrawable*>::iterator iter = potentials.begin(); - iter != potentials.end(); iter++) + for (LLDrawable* drawable : potentials) { - LLDrawable* drawable = *iter; + if (!drawable) + { + continue; + } + LLViewerObject* vobjp = drawable->getVObj(); - if (!drawable || !vobjp || + if (!vobjp || vobjp->getPCode() != LL_PCODE_VOLUME || vobjp->isAttachment() || (deselect && !vobjp->isSelected())) @@ -244,7 +244,7 @@ void LLToolSelectRect::handleRectangleSelection(S32 x, S32 y, MASK mask) gViewerWindow->setup3DRender(); } -const F32 WIND_RELATIVE_ALTITUDE = 25.f; +constexpr F32 WIND_RELATIVE_ALTITUDE = 25.f; void LLWind::renderVectors() { @@ -266,14 +266,14 @@ void LLWind::renderVectors() x = mVelX[i + j*mSize] * WIND_SCALE_HACK; y = mVelY[i + j*mSize] * WIND_SCALE_HACK; gGL.pushMatrix(); - gGL.translatef((F32)i * region_width_meters/mSize, (F32)j * region_width_meters/mSize, 0.0); - gGL.color3f(0,1,0); + gGL.translatef((F32)i * region_width_meters/mSize, (F32)j * region_width_meters/mSize, 0.f); + gGL.color3f(0.f, 1.f, 0.f); gGL.begin(LLRender::POINTS); - gGL.vertex3f(0,0,0); + gGL.vertex3f(0.f, 0.f, 0.f); gGL.end(); - gGL.color3f(1,0,0); + gGL.color3f(1.f, 0.f, 0.f); gGL.begin(LLRender::LINES); - gGL.vertex3f(x * 0.1f, y * 0.1f ,0.f); + gGL.vertex3f(x * 0.1f, y * 0.1f, 0.f); gGL.vertex3f(x, y, 0.f); gGL.end(); gGL.popMatrix(); @@ -287,7 +287,7 @@ void LLWind::renderVectors() // Used by lltoolselectland void LLViewerParcelMgr::renderRect(const LLVector3d &west_south_bottom_global, - const LLVector3d &east_north_top_global ) + const LLVector3d &east_north_top_global) { LLGLSUIDefault gls_ui; gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); @@ -338,118 +338,23 @@ void LLViewerParcelMgr::renderRect(const LLVector3d &west_south_bottom_global, gGL.end(); gGL.color4f(1.f, 1.f, 0.f, 0.2f); - gGL.begin(LLRender::QUADS); - - gGL.vertex3f(west, north, nw_bottom); - gGL.vertex3f(west, north, nw_top); - gGL.vertex3f(east, north, ne_top); - gGL.vertex3f(east, north, ne_bottom); - - gGL.vertex3f(east, north, ne_bottom); - gGL.vertex3f(east, north, ne_top); - gGL.vertex3f(east, south, se_top); - gGL.vertex3f(east, south, se_bottom); - - gGL.vertex3f(east, south, se_bottom); - gGL.vertex3f(east, south, se_top); - gGL.vertex3f(west, south, sw_top); - gGL.vertex3f(west, south, sw_bottom); - - gGL.vertex3f(west, south, sw_bottom); - gGL.vertex3f(west, south, sw_top); - gGL.vertex3f(west, north, nw_top); - gGL.vertex3f(west, north, nw_bottom); - - gGL.end(); - - LLUI::setLineWidth(1.f); -} - -/* -void LLViewerParcelMgr::renderParcel(LLParcel* parcel ) -{ - S32 i; - S32 count = parcel->getBoxCount(); - for (i = 0; i < count; i++) + gGL.begin(LLRender::TRIANGLE_STRIP); { - const LLParcelBox& box = parcel->getBox(i); - - F32 west = box.mMin.mV[VX]; - F32 south = box.mMin.mV[VY]; - - F32 east = box.mMax.mV[VX]; - F32 north = box.mMax.mV[VY]; - - // HACK: At edge of last region of world, we need to make sure the region - // resolves correctly so we can get a height value. - const F32 FUDGE = 0.01f; - - F32 sw_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( west, south, 0.f ) ); - F32 se_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( east-FUDGE, south, 0.f ) ); - F32 ne_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( east-FUDGE, north-FUDGE, 0.f ) ); - F32 nw_bottom = LLWorld::getInstance()->resolveLandHeightAgent( LLVector3( west, north-FUDGE, 0.f ) ); - - // little hack to make nearby lines not Z-fight - east -= 0.1f; - north -= 0.1f; - - F32 sw_top = sw_bottom + POST_HEIGHT; - F32 se_top = se_bottom + POST_HEIGHT; - F32 ne_top = ne_bottom + POST_HEIGHT; - F32 nw_top = nw_bottom + POST_HEIGHT; - - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - LLGLDepthTest gls_depth(GL_TRUE); - - LLUI::setLineWidth(2.f); - gGL.color4f(0.f, 1.f, 1.f, 1.f); - - // Cheat and give this the same pick-name as land - gGL.begin(LLRender::LINES); - - gGL.vertex3f(west, north, nw_bottom); - gGL.vertex3f(west, north, nw_top); - - gGL.vertex3f(east, north, ne_bottom); - gGL.vertex3f(east, north, ne_top); - - gGL.vertex3f(east, south, se_bottom); - gGL.vertex3f(east, south, se_top); - - gGL.vertex3f(west, south, sw_bottom); - gGL.vertex3f(west, south, sw_top); - - gGL.end(); - - gGL.color4f(0.f, 1.f, 1.f, 0.2f); - gGL.begin(LLRender::QUADS); - gGL.vertex3f(west, north, nw_bottom); gGL.vertex3f(west, north, nw_top); - gGL.vertex3f(east, north, ne_top); - gGL.vertex3f(east, north, ne_bottom); - gGL.vertex3f(east, north, ne_bottom); gGL.vertex3f(east, north, ne_top); - gGL.vertex3f(east, south, se_top); - gGL.vertex3f(east, south, se_bottom); - gGL.vertex3f(east, south, se_bottom); gGL.vertex3f(east, south, se_top); gGL.vertex3f(west, south, sw_top); gGL.vertex3f(west, south, sw_bottom); - - gGL.vertex3f(west, south, sw_bottom); - gGL.vertex3f(west, south, sw_top); gGL.vertex3f(west, north, nw_top); gGL.vertex3f(west, north, nw_bottom); - - gGL.end(); - - LLUI::setLineWidth(1.f); } + gGL.end(); + + LLUI::setLineWidth(1.f); } -*/ // north = a wall going north/south. Need that info to set up texture @@ -493,6 +398,10 @@ void LLViewerParcelMgr::renderOneSegment(F32 x1, F32 y1, F32 x2, F32 y2, F32 hei gGL.vertex3f(x2, y2, z2); + gGL.vertex3f(x1, y1, z); + + gGL.vertex3f(x2, y2, z2); + z = z2+height; gGL.vertex3f(x2, y2, z); } @@ -523,18 +432,24 @@ void LLViewerParcelMgr::renderOneSegment(F32 x1, F32 y1, F32 x2, F32 y2, F32 hei } - gGL.texCoord2f(tex_coord1*0.5f+0.5f, z1*0.5f); + gGL.texCoord2f(tex_coord1 * 0.5f + 0.5f, z1 * 0.5f); gGL.vertex3f(x1, y1, z1); - gGL.texCoord2f(tex_coord2*0.5f+0.5f, z2*0.5f); + gGL.texCoord2f(tex_coord2 * 0.5f + 0.5f, z2 * 0.5f); gGL.vertex3f(x2, y2, z2); // top edge stairsteps - z = llmax(z2+height, z1+height); - gGL.texCoord2f(tex_coord2*0.5f+0.5f, z*0.5f); + z = llmax(z2 + height, z1 + height); + gGL.texCoord2f(tex_coord2 * 0.5f + 0.5f, z * 0.5f); + gGL.vertex3f(x2, y2, z); + + gGL.texCoord2f(tex_coord1 * 0.5f + 0.5f, z1 * 0.5f); + gGL.vertex3f(x1, y1, z1); + + gGL.texCoord2f(tex_coord2 * 0.5f + 0.5f, z * 0.5f); gGL.vertex3f(x2, y2, z); - gGL.texCoord2f(tex_coord1*0.5f+0.5f, z*0.5f); + gGL.texCoord2f(tex_coord1 * 0.5f + 0.5f, z * 0.5f); gGL.vertex3f(x1, y1, z); } } @@ -575,7 +490,7 @@ void LLViewerParcelMgr::renderHighlightSegments(const U8* segments, LLViewerRegi if (!has_segments) { has_segments = true; - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); } renderOneSegment(x1, y1, x2, y2, PARCEL_POST_HEIGHT, SOUTH_MASK, regionp); } @@ -591,7 +506,7 @@ void LLViewerParcelMgr::renderHighlightSegments(const U8* segments, LLViewerRegi if (!has_segments) { has_segments = true; - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); } renderOneSegment(x1, y1, x2, y2, PARCEL_POST_HEIGHT, WEST_MASK, regionp); } @@ -647,7 +562,7 @@ void LLViewerParcelMgr::renderCollisionSegments(U8* segments, bool use_pass, LLV gGL.getTexUnit(0)->bind(mBlockedImage); } - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); for (y = 0; y < STRIDE; y++) { @@ -923,7 +838,7 @@ struct ShaderProfileHelper } ~ShaderProfileHelper() { - LLGLSLShader::finishProfile(false); + LLGLSLShader::finishProfile(); } }; diff --git a/indra/newview/llgroupactions.cpp b/indra/newview/llgroupactions.cpp index ba9c9fa13f..1128939e29 100644 --- a/indra/newview/llgroupactions.cpp +++ b/indra/newview/llgroupactions.cpp @@ -37,6 +37,7 @@ #include "llfloatersidepanelcontainer.h" #include "llgroupmgr.h" #include "llfloaterimcontainer.h" +#include "llfloaterimsession.h" #include "llimview.h" // for gIMMgr #include "llnotificationsutil.h" #include "llstartup.h" @@ -46,7 +47,7 @@ // // Globals // -static GroupChatListener sGroupChatListener; +static LLGroupChatListener sGroupChatListener; class LLGroupHandler : public LLCommandHandler { @@ -519,7 +520,10 @@ void LLGroupActions::endIM(const LLUUID& group_id) LLUUID session_id = gIMMgr->computeSessionID(IM_SESSION_GROUP_START, group_id); if (session_id != LLUUID::null) { - gIMMgr->leaveSession(session_id); + if (LLFloaterIMSession *conversationFloater = LLFloaterIMSession::findInstance(session_id)) + { + LLFloater::onClickClose(conversationFloater); + } } } diff --git a/indra/newview/llgrouplist.cpp b/indra/newview/llgrouplist.cpp index 7659e5f082..14ac14cd85 100644 --- a/indra/newview/llgrouplist.cpp +++ b/indra/newview/llgrouplist.cpp @@ -141,7 +141,7 @@ void LLGroupList::enableForAgent(bool show_icons) gAgent.addListener(this, "new group"); // Set up context menu. - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + ScopedRegistrarHelper registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; registrar.add("People.Groups.Action", boost::bind(&LLGroupList::onContextMenuItemClick, this, _2)); diff --git a/indra/newview/llhttpretrypolicy.cpp b/indra/newview/llhttpretrypolicy.cpp index 3e55030610..c5692554c6 100644 --- a/indra/newview/llhttpretrypolicy.cpp +++ b/indra/newview/llhttpretrypolicy.cpp @@ -91,14 +91,14 @@ void LLAdaptiveRetryPolicy::onSuccess() void LLAdaptiveRetryPolicy::onFailure(S32 status, const LLSD& headers) { - F32 retry_header_time; + F32 retry_header_time{}; bool has_retry_header_time = getRetryAfter(headers,retry_header_time); onFailureCommon(status, has_retry_header_time, retry_header_time); } void LLAdaptiveRetryPolicy::onFailure(const LLCore::HttpResponse *response) { - F32 retry_header_time; + F32 retry_header_time{}; const LLCore::HttpHeaders::ptr_t headers = response->getHeaders(); bool has_retry_header_time = getRetryAfter(headers,retry_header_time); onFailureCommon(response->getStatus().getType(), has_retry_header_time, retry_header_time); @@ -184,4 +184,3 @@ bool LLAdaptiveRetryPolicy::getSecondsUntilRetryAfter(const std::string& retry_a return true; } - diff --git a/indra/newview/llhudeffectblob.cpp b/indra/newview/llhudeffectblob.cpp index b476226d05..bdb21fd96e 100644 --- a/indra/newview/llhudeffectblob.cpp +++ b/indra/newview/llhudeffectblob.cpp @@ -78,16 +78,23 @@ void LLHUDEffectBlob::render() LLVector3 u_scale = pixel_right * (F32)mPixelSize; LLVector3 v_scale = pixel_up * (F32)mPixelSize; - { gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); + { gGL.texCoord2f(0.f, 1.f); gGL.vertex3fv((v_scale - u_scale).mV); gGL.texCoord2f(0.f, 0.f); gGL.vertex3fv((-v_scale - u_scale).mV); gGL.texCoord2f(1.f, 0.f); gGL.vertex3fv((-v_scale + u_scale).mV); + + gGL.texCoord2f(0.f, 1.f); + gGL.vertex3fv((v_scale - u_scale).mV); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex3fv((-v_scale + u_scale).mV); gGL.texCoord2f(1.f, 1.f); gGL.vertex3fv((v_scale + u_scale).mV); - } gGL.end(); + } + gGL.end(); } gGL.popMatrix(); } diff --git a/indra/newview/llhudicon.cpp b/indra/newview/llhudicon.cpp index 8fa4118a40..1a4af470bd 100644 --- a/indra/newview/llhudicon.cpp +++ b/indra/newview/llhudicon.cpp @@ -152,7 +152,7 @@ void LLHUDIcon::render() gGL.getTexUnit(0)->bind(mImagep); } - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { gGL.texCoord2f(0.f, 1.f); gGL.vertex3fv(upper_left.mV); @@ -160,6 +160,11 @@ void LLHUDIcon::render() gGL.vertex3fv(lower_left.mV); gGL.texCoord2f(1.f, 0.f); gGL.vertex3fv(lower_right.mV); + + gGL.texCoord2f(0.f, 1.f); + gGL.vertex3fv(upper_left.mV); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex3fv(lower_right.mV); gGL.texCoord2f(1.f, 1.f); gGL.vertex3fv(upper_right.mV); } diff --git a/indra/newview/llhudnametag.cpp b/indra/newview/llhudnametag.cpp index 19ae35813c..66130a2744 100644 --- a/indra/newview/llhudnametag.cpp +++ b/indra/newview/llhudnametag.cpp @@ -275,7 +275,8 @@ void LLHUDNameTag::renderText() LLVector3 x_pixel_vec; LLVector3 y_pixel_vec; - LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); + LLViewerCamera* camera = LLViewerCamera::getInstance(); + camera->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); LLVector3 width_vec = mWidth * x_pixel_vec; LLVector3 height_vec = mHeight * y_pixel_vec; @@ -283,7 +284,7 @@ void LLHUDNameTag::renderText() mRadius = (width_vec + height_vec).magVec() * 0.5f; LLCoordGL screen_pos; - LLViewerCamera::getInstance()->projectPosAgentToScreen(mPositionAgent, screen_pos, false); + camera->projectPosAgentToScreen(mPositionAgent, screen_pos, false); LLVector2 screen_offset = updateScreenPos(mPositionOffset); @@ -310,7 +311,7 @@ void LLHUDNameTag::renderText() const S32 label_height = ll_round((mFontp->getLineHeight() * (F32)mLabelSegments.size() + (VERTICAL_PADDING / 3.f))); label_top_rect.mBottom = label_top_rect.mTop - label_height; LLColor4 label_top_color = text_color; - label_top_color.mV[VALPHA] = gSavedSettings.getF32("ChatBubbleOpacity") * alpha_factor; + label_top_color.mV[VALPHA] = bubble_opacity() * alpha_factor; mRoundedRectTopImgp->draw3D(render_position, x_pixel_vec, y_pixel_vec, label_top_rect, label_top_color); } @@ -645,12 +646,13 @@ LLVector2 LLHUDNameTag::updateScreenPos(LLVector2 &offset) LLVector2 screen_pos_vec; LLVector3 x_pixel_vec; LLVector3 y_pixel_vec; - LLViewerCamera::getInstance()->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); + LLViewerCamera* camera = LLViewerCamera::getInstance(); + camera->getPixelVectors(mPositionAgent, y_pixel_vec, x_pixel_vec); LLVector3 world_pos = mPositionAgent + (offset.mV[VX] * x_pixel_vec) + (offset.mV[VY] * y_pixel_vec); - if (!LLViewerCamera::getInstance()->projectPosAgentToScreen(world_pos, screen_pos, false) && mVisibleOffScreen) + if (!camera->projectPosAgentToScreen(world_pos, screen_pos, false) && mVisibleOffScreen) { // bubble off-screen, so find a spot for it along screen edge - LLViewerCamera::getInstance()->projectPosAgentToScreenEdge(world_pos, screen_pos); + camera->projectPosAgentToScreenEdge(world_pos, screen_pos); } screen_pos_vec.setVec((F32)screen_pos.mX, (F32)screen_pos.mY); diff --git a/indra/newview/llhudrender.cpp b/indra/newview/llhudrender.cpp index 4180e49e94..aa440c6cf5 100644 --- a/indra/newview/llhudrender.cpp +++ b/indra/newview/llhudrender.cpp @@ -63,6 +63,7 @@ void hud_render_text(const LLWString &wstr, const LLVector3 &pos_agent, const LLColor4& color, const bool orthographic) { + LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; LLViewerCamera* camera = LLViewerCamera::getInstance(); // Do cheap plane culling LLVector3 dir_vec = pos_agent - camera->getOrigin(); diff --git a/indra/newview/llimprocessing.cpp b/indra/newview/llimprocessing.cpp index c1e68e0288..590cd09a31 100644 --- a/indra/newview/llimprocessing.cpp +++ b/indra/newview/llimprocessing.cpp @@ -488,7 +488,7 @@ void LLIMProcessing::processNewMessage(LLUUID from_id, case IM_NOTHING_SPECIAL: // p2p IM // Don't show dialog, just do IM if (!gAgent.isGodlike() - && gAgent.getRegion()->isPrelude() + && gAgent.inPrelude() && to_id.isNull()) { // do nothing -- don't distract newbies in diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 06cf9919b6..1402cc8c37 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -3551,6 +3551,7 @@ void LLIMMgr::inviteToSession( && voice_invite && "VoiceInviteQuestionDefault" == question_type) { LL_INFOS("IMVIEW") << "Rejecting voice call from initiating muted resident " << caller_name << LL_ENDL; + payload["voice_channel_info"] = voice_channel_info; LLIncomingCallDialog::processCallResponse(1, payload); return; } @@ -3599,6 +3600,7 @@ void LLIMMgr::inviteToSession( send_do_not_disturb_message(gMessageSystem, caller_id, session_id); } // silently decline the call + payload["voice_channel_info"] = voice_channel_info; LLIncomingCallDialog::processCallResponse(1, payload); return; } diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index 99b19c9fa9..233fb075e8 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -51,7 +51,7 @@ class LLSessionTimeoutTimer : public LLEventTimer public: LLSessionTimeoutTimer(const LLUUID& session_id, F32 period) : LLEventTimer(period), mSessionId(session_id) {} virtual ~LLSessionTimeoutTimer() {}; - /* virtual */ bool tick(); + bool tick() override; private: LLUUID mSessionId; diff --git a/indra/newview/llinspectgroup.cpp b/indra/newview/llinspectgroup.cpp index db48061c56..c39380105b 100644 --- a/indra/newview/llinspectgroup.cpp +++ b/indra/newview/llinspectgroup.cpp @@ -97,11 +97,11 @@ LLInspectGroup::LLInspectGroup(const LLSD& sd) mGroupID() // set in onOpen() { mCommitCallbackRegistrar.add("InspectGroup.ViewProfile", - boost::bind(&LLInspectGroup::onClickViewProfile, this)); + { boost::bind(&LLInspectGroup::onClickViewProfile, this), cb_info::UNTRUSTED_THROTTLE }); mCommitCallbackRegistrar.add("InspectGroup.Join", - boost::bind(&LLInspectGroup::onClickJoin, this)); + { boost::bind(&LLInspectGroup::onClickJoin, this), cb_info::UNTRUSTED_THROTTLE }); mCommitCallbackRegistrar.add("InspectGroup.Leave", - boost::bind(&LLInspectGroup::onClickLeave, this)); + { boost::bind(&LLInspectGroup::onClickLeave, this), cb_info::UNTRUSTED_BLOCK }); // can't make the properties request until the widgets are constructed // as it might return immediately, so do it in postBuild. diff --git a/indra/newview/llinspectobject.cpp b/indra/newview/llinspectobject.cpp index eb2cdb8632..874ad41551 100644 --- a/indra/newview/llinspectobject.cpp +++ b/indra/newview/llinspectobject.cpp @@ -129,14 +129,14 @@ LLInspectObject::LLInspectObject(const LLSD& sd) { // can't make the properties request until the widgets are constructed // as it might return immediately, so do it in postBuild. - mCommitCallbackRegistrar.add("InspectObject.Buy", boost::bind(&LLInspectObject::onClickBuy, this)); - mCommitCallbackRegistrar.add("InspectObject.Pay", boost::bind(&LLInspectObject::onClickPay, this)); - mCommitCallbackRegistrar.add("InspectObject.TakeFreeCopy", boost::bind(&LLInspectObject::onClickTakeFreeCopy, this)); - mCommitCallbackRegistrar.add("InspectObject.Touch", boost::bind(&LLInspectObject::onClickTouch, this)); - mCommitCallbackRegistrar.add("InspectObject.Sit", boost::bind(&LLInspectObject::onClickSit, this)); - mCommitCallbackRegistrar.add("InspectObject.Open", boost::bind(&LLInspectObject::onClickOpen, this)); - mCommitCallbackRegistrar.add("InspectObject.MoreInfo", boost::bind(&LLInspectObject::onClickMoreInfo, this)); - mCommitCallbackRegistrar.add("InspectObject.ZoomIn", boost::bind(&LLInspectObject::onClickZoomIn, this)); + mCommitCallbackRegistrar.add("InspectObject.Buy", {boost::bind(&LLInspectObject::onClickBuy, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("InspectObject.Pay", {boost::bind(&LLInspectObject::onClickPay, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("InspectObject.TakeFreeCopy", {boost::bind(&LLInspectObject::onClickTakeFreeCopy, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("InspectObject.Touch", {boost::bind(&LLInspectObject::onClickTouch, this), cb_info::UNTRUSTED_BLOCK}); + mCommitCallbackRegistrar.add("InspectObject.Sit", {boost::bind(&LLInspectObject::onClickSit, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("InspectObject.Open", {boost::bind(&LLInspectObject::onClickOpen, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("InspectObject.MoreInfo", {boost::bind(&LLInspectObject::onClickMoreInfo, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("InspectObject.ZoomIn", {boost::bind(&LLInspectObject::onClickZoomIn, this), cb_info::UNTRUSTED_BLOCK}); } diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 2e8b1b94fe..896c7d8d70 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -844,7 +844,7 @@ void LLInvFVBridge::getClipboardEntries(bool show_asset_id, disabled_items.push_back(std::string("Copy")); } - if (isAgentInventory() && !single_folder_root) + if (isAgentInventory() && !single_folder_root && !isMarketplaceListingsFolder()) { items.push_back(std::string("New folder from selected")); items.push_back(std::string("Subfolder Separator")); @@ -5321,7 +5321,7 @@ void LLFolderBridge::dropToMyOutfits(LLInventoryCategory* inv_cat, LLPointer<LLI // Note: creation will take time, so passing folder id to callback is slightly unreliable, // but so is collecting and passing descendants' ids - inventory_func_type func = boost::bind(&LLFolderBridge::outfitFolderCreatedCallback, this, inv_cat->getUUID(), _1, cb); + inventory_func_type func = boost::bind(outfitFolderCreatedCallback, inv_cat->getUUID(), _1, cb, mInventoryPanel); gInventory.createNewCategory(dest_id, LLFolderType::FT_OUTFIT, inv_cat->getName(), @@ -5329,11 +5329,25 @@ void LLFolderBridge::dropToMyOutfits(LLInventoryCategory* inv_cat, LLPointer<LLI inv_cat->getThumbnailUUID()); } -void LLFolderBridge::outfitFolderCreatedCallback(LLUUID cat_source_id, LLUUID cat_dest_id, LLPointer<LLInventoryCallback> cb) +void LLFolderBridge::outfitFolderCreatedCallback(LLUUID cat_source_id, + LLUUID cat_dest_id, + LLPointer<LLInventoryCallback> cb, + LLHandle<LLInventoryPanel> inventory_panel) { LLInventoryModel::cat_array_t* categories; LLInventoryModel::item_array_t* items; - getInventoryModel()->getDirectDescendentsOf(cat_source_id, categories, items); + + LLInventoryPanel* panel = inventory_panel.get(); + if (!panel) + { + return; + } + LLInventoryModel* model = panel->getModel(); + if (!model) + { + return; + } + model->getDirectDescendentsOf(cat_source_id, categories, items); LLInventoryObject::const_object_list_t link_array; diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index 746b79ce87..3e7f74384b 100644 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -378,7 +378,10 @@ public: static void staticFolderOptionsMenu(); protected: - void outfitFolderCreatedCallback(LLUUID cat_source_id, LLUUID cat_dest_id, LLPointer<LLInventoryCallback> cb); + static void outfitFolderCreatedCallback(LLUUID cat_source_id, + LLUUID cat_dest_id, + LLPointer<LLInventoryCallback> cb, + LLHandle<LLInventoryPanel> inventory_panel); void callback_pasteFromClipboard(const LLSD& notification, const LLSD& response); void perform_pasteFromClipboard(); void gatherMessage(std::string& message, S32 depth, LLError::ELevel log_level); diff --git a/indra/newview/llinventoryfilter.cpp b/indra/newview/llinventoryfilter.cpp index 5c0905af3c..01f2c6c525 100644 --- a/indra/newview/llinventoryfilter.cpp +++ b/indra/newview/llinventoryfilter.cpp @@ -959,7 +959,7 @@ void LLInventoryFilter::setFilterSubString(const std::string& string) boost::char_separator<char> sep("+"); tokenizer tokens(filter_sub_string_new, sep); - for (auto token_iter : tokens) + for (const auto& token_iter : tokens) { mFilterTokens.push_back(token_iter); } @@ -1025,7 +1025,7 @@ void LLInventoryFilter::setFilterSubString(const std::string& string) } // Cancel out UUID once the search string is modified - if (mFilterOps.mFilterTypes == FILTERTYPE_UUID) + if (mFilterOps.mFilterTypes & FILTERTYPE_UUID) { mFilterOps.mFilterTypes &= ~FILTERTYPE_UUID; mFilterOps.mFilterUUID = LLUUID::null; @@ -1707,7 +1707,7 @@ std::string LLInventoryFilter::getEmptyLookupMessage(bool is_empty_folder) const } } -bool LLInventoryFilter::areDateLimitsSet() +bool LLInventoryFilter::areDateLimitsSet() const { return mFilterOps.mMinDate != time_min() || mFilterOps.mMaxDate != time_max() diff --git a/indra/newview/llinventoryfilter.h b/indra/newview/llinventoryfilter.h index 7e64a03e73..612a161ba2 100644 --- a/indra/newview/llinventoryfilter.h +++ b/indra/newview/llinventoryfilter.h @@ -341,7 +341,7 @@ public: bool checkAgainstFilterThumbnails(const LLUUID& object_id) const; private: - bool areDateLimitsSet(); + bool areDateLimitsSet() const; bool checkAgainstFilterSubString(const std::string& desc) const; bool checkAgainstFilterType(const class LLFolderViewModelItemInventory* listener) const; bool checkAgainstFilterType(const LLInventoryItem* item) const; diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index dadd0590a9..fdf77ab8df 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -2758,6 +2758,19 @@ bool LLNameCategoryCollector::operator()( return false; } +bool LLNameItemCollector::operator()( + LLInventoryCategory* cat, LLInventoryItem* item) +{ + if(item) + { + if (!LLStringUtil::compareInsensitive(mName, item->getName())) + { + return true; + } + } + return false; +} + bool LLFindCOFValidItems::operator()(LLInventoryCategory* cat, LLInventoryItem* item) { diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h index a25c0d5ad6..cd6f319f66 100644 --- a/indra/newview/llinventoryfunctions.h +++ b/indra/newview/llinventoryfunctions.h @@ -189,6 +189,8 @@ public: virtual ~LLInventoryCollectFunctor(){}; virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) = 0; + virtual bool exceedsLimit() { return false; } + static bool itemTransferCommonlyAllowed(const LLInventoryItem* item); }; @@ -392,6 +394,22 @@ protected: }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLNameItemCollector +// +// Collects items based on case-insensitive match of prefix +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLNameItemCollector : public LLInventoryCollectFunctor +{ +public: + LLNameItemCollector(const std::string& name) : mName(name) {} + virtual ~LLNameItemCollector() {} + virtual bool operator()(LLInventoryCategory* cat, + LLInventoryItem* item); +protected: + std::string mName; +}; + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLFindCOFValidItems // // Collects items that can be legitimately linked to in the COF. diff --git a/indra/newview/llinventorygallerymenu.cpp b/indra/newview/llinventorygallerymenu.cpp index dbf4821ca1..c84b9abba0 100644 --- a/indra/newview/llinventorygallerymenu.cpp +++ b/indra/newview/llinventorygallerymenu.cpp @@ -87,13 +87,15 @@ void modify_outfit(bool append, const LLUUID& cat_id, LLInventoryModel* model) LLContextMenu* LLInventoryGalleryContextMenu::createMenu() { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::ScopedRegistrarHelper registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; - registrar.add("Inventory.DoToSelected", boost::bind(&LLInventoryGalleryContextMenu::doToSelected, this, _2)); - registrar.add("Inventory.FileUploadLocation", boost::bind(&LLInventoryGalleryContextMenu::fileUploadLocation, this, _2)); - registrar.add("Inventory.EmptyTrash", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH)); - registrar.add("Inventory.EmptyLostAndFound", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND)); + registrar.add("Inventory.DoToSelected", boost::bind(&LLInventoryGalleryContextMenu::doToSelected, this, _2), LLUICtrl::cb_info::UNTRUSTED_BLOCK); + registrar.add("Inventory.FileUploadLocation", boost::bind(&LLInventoryGalleryContextMenu::fileUploadLocation, this, _2), LLUICtrl::cb_info::UNTRUSTED_BLOCK); + registrar.add("Inventory.EmptyTrash", + boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH), LLUICtrl::cb_info::UNTRUSTED_BLOCK); + registrar.add("Inventory.EmptyLostAndFound", + boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND), LLUICtrl::cb_info::UNTRUSTED_BLOCK); registrar.add("Inventory.DoCreate", [this](LLUICtrl*, const LLSD& data) { if (mRootFolder) @@ -104,10 +106,11 @@ LLContextMenu* LLInventoryGalleryContextMenu::createMenu() { mGallery->doCreate(mUUIDs.front(), data); } - }); + }, + LLUICtrl::cb_info::UNTRUSTED_BLOCK); std::set<LLUUID> uuids(mUUIDs.begin(), mUUIDs.end()); - registrar.add("Inventory.Share", boost::bind(&LLAvatarActions::shareWithAvatars, uuids, gFloaterView->getParentFloater(mGallery))); + registrar.add("Inventory.Share", boost::bind(&LLAvatarActions::shareWithAvatars, uuids, gFloaterView->getParentFloater(mGallery)), LLUICtrl::cb_info::UNTRUSTED_BLOCK); enable_registrar.add("Inventory.CanSetUploadLocation", boost::bind(&LLInventoryGalleryContextMenu::canSetUploadLocation, this, _2)); diff --git a/indra/newview/llinventorylistener.cpp b/indra/newview/llinventorylistener.cpp new file mode 100644 index 0000000000..79726c3e0b --- /dev/null +++ b/indra/newview/llinventorylistener.cpp @@ -0,0 +1,392 @@ +/** + * @file llinventorylistener.cpp + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, 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 "llinventorylistener.h" + +#include "llappearancemgr.h" +#include "llinventoryfunctions.h" +#include "lltransutil.h" +#include "llwearableitemslist.h" +#include "resultset.h" +#include "stringize.h" +#include <algorithm> // std::min() + +constexpr S32 MAX_ITEM_LIMIT = 100; + +LLInventoryListener::LLInventoryListener() + : LLEventAPI("LLInventory", + "API for interactions with viewer Inventory items") +{ + add("getItemsInfo", + "Return information about items or folders defined in [\"item_ids\"]:\n" + "reply will contain [\"items\"] and [\"categories\"] result set keys", + &LLInventoryListener::getItemsInfo, + llsd::map("item_ids", LLSD(), "reply", LLSD())); + + add("getFolderTypeNames", + "Return the table of folder type names, contained in [\"names\"]\n", + &LLInventoryListener::getFolderTypeNames, + llsd::map("reply", LLSD())); + + add("getAssetTypeNames", + "Return the table of asset type names, contained in [\"names\"]\n", + &LLInventoryListener::getAssetTypeNames, + llsd::map("reply", LLSD())); + + add("getBasicFolderID", + "Return the UUID of the folder by specified folder type name, for example:\n" + "\"Textures\", \"My outfits\", \"Sounds\" and other basic folders which have associated type", + &LLInventoryListener::getBasicFolderID, + llsd::map("ft_name", LLSD(), "reply", LLSD())); + + add("getDirectDescendants", + "Return result set keys [\"categories\"] and [\"items\"] for the direct\n" + "descendants of the [\"folder_id\"]", + &LLInventoryListener::getDirectDescendants, + llsd::map("folder_id", LLSD(), "reply", LLSD())); + + add("collectDescendantsIf", + "Return result set keys [\"categories\"] and [\"items\"] for the descendants\n" + "of the [\"folder_id\"], if it passes specified filters:\n" + "[\"name\"] is a substring of object's name,\n" + "[\"desc\"] is a substring of object's description,\n" + "asset [\"type\"] corresponds to the string name of the object's asset type\n" + "[\"limit\"] sets item count limit in result set (default unlimited)\n" + "[\"filter_links\"]: EXCLUDE_LINKS - don't show links, ONLY_LINKS - only show links, INCLUDE_LINKS - show links too (default)", + &LLInventoryListener::collectDescendantsIf, + llsd::map("folder_id", LLSD(), "reply", LLSD())); + +/*==========================================================================*| + add("getSingle", + "Return LLSD [\"single\"] for a single folder or item from the specified\n" + "[\"result\"] key at the specified 0-relative [\"index\"].", + &LLInventoryListener::getSingle, + llsd::map("result", LLSD::Integer(), "index", LLSD::Integer(), + "reply", LLSD::String())); +|*==========================================================================*/ + + add("getSlice", + stringize( + "Return an LLSD array [\"slice\"] from the specified [\"result\"] key\n" + "starting at 0-relative [\"index\"] with (up to) [\"count\"] entries.\n" + "count is limited to ", MAX_ITEM_LIMIT, " (default and max)."), + &LLInventoryListener::getSlice, + llsd::map("result", LLSD::Integer(), "index", LLSD::Integer(), + "reply", LLSD::String())); + + add("closeResult", + "Release resources associated with specified [\"result\"] key,\n" + "or keys if [\"result\"] is an array.", + &LLInventoryListener::closeResult, + llsd::map("result", LLSD())); +} + +// This struct captures (possibly large) category results from +// getDirectDescendants() and collectDescendantsIf(). +struct CatResultSet: public LL::ResultSet +{ + CatResultSet(): LL::ResultSet("categories") {} + LLInventoryModel::cat_array_t mCategories; + + int getLength() const override { return narrow(mCategories.size()); } + LLSD getSingle(int index) const override + { + auto cat = mCategories[index]; + return llsd::map("name", cat->getName(), + "parent_id", cat->getParentUUID(), + "type", LLFolderType::lookup(cat->getPreferredType())); + } +}; + +// This struct captures (possibly large) item results from +// getDirectDescendants() and collectDescendantsIf(). +struct ItemResultSet: public LL::ResultSet +{ + ItemResultSet(): LL::ResultSet("items") {} + LLInventoryModel::item_array_t mItems; + + int getLength() const override { return narrow(mItems.size()); } + LLSD getSingle(int index) const override + { + auto item = mItems[index]; + return llsd::map("name", item->getName(), + "parent_id", item->getParentUUID(), + "desc", item->getDescription(), + "inv_type", LLInventoryType::lookup(item->getInventoryType()), + "asset_type", LLAssetType::lookup(item->getType()), + "creation_date", LLSD::Integer(item->getCreationDate()), + "asset_id", item->getAssetUUID(), + "is_link", item->getIsLinkType(), + "linked_id", item->getLinkedUUID()); + } +}; + +void LLInventoryListener::getItemsInfo(LLSD const &data) +{ + Response response(LLSD(), data); + + auto catresult = new CatResultSet; + auto itemresult = new ItemResultSet; + + uuid_vec_t ids = LLSDParam<uuid_vec_t>(data["item_ids"]); + for (auto &it : ids) + { + LLViewerInventoryItem* item = gInventory.getItem(it); + if (item) + { + itemresult->mItems.push_back(item); + } + else + { + LLViewerInventoryCategory *cat = gInventory.getCategory(it); + if (cat) + { + catresult->mCategories.push_back(cat); + } + } + } + // Each of categories and items is a { result set key, total length } pair. + response["categories"] = catresult->getKeyLength(); + response["items"] = itemresult->getKeyLength(); +} + +void LLInventoryListener::getFolderTypeNames(LLSD const &data) +{ + Response response(llsd::map("names", LLFolderType::getTypeNames()), data); +} + +void LLInventoryListener::getAssetTypeNames(LLSD const &data) +{ + Response response(llsd::map("names", LLAssetType::getTypeNames()), data); +} + +void LLInventoryListener::getBasicFolderID(LLSD const &data) +{ + Response response(llsd::map("id", gInventory.findCategoryUUIDForType(LLFolderType::lookup(data["ft_name"].asString()))), data); +} + + +void LLInventoryListener::getDirectDescendants(LLSD const &data) +{ + Response response(LLSD(), data); + LLInventoryModel::cat_array_t* cats; + LLInventoryModel::item_array_t* items; + gInventory.getDirectDescendentsOf(data["folder_id"], cats, items); + + auto catresult = new CatResultSet; + auto itemresult = new ItemResultSet; + + catresult->mCategories = *cats; + itemresult->mItems = *items; + + response["categories"] = catresult->getKeyLength(); + response["items"] = itemresult->getKeyLength(); +} + +struct LLFilteredCollector : public LLInventoryCollectFunctor +{ + enum EFilterLink + { + INCLUDE_LINKS, // show links too + EXCLUDE_LINKS, // don't show links + ONLY_LINKS // only show links + }; + + LLFilteredCollector(LLSD const &data); + virtual ~LLFilteredCollector() {} + virtual bool operator()(LLInventoryCategory *cat, LLInventoryItem *item) override; + virtual bool exceedsLimit() override + { + // mItemLimit == 0 means unlimited + return (mItemLimit && mItemLimit <= mItemCount); + } + + protected: + bool checkagainstType(LLInventoryCategory *cat, LLInventoryItem *item); + bool checkagainstNameDesc(LLInventoryCategory *cat, LLInventoryItem *item); + bool checkagainstLinks(LLInventoryCategory *cat, LLInventoryItem *item); + + LLAssetType::EType mType; + std::string mName; + std::string mDesc; + EFilterLink mLinkFilter; + + S32 mItemLimit; + S32 mItemCount; +}; + +void LLInventoryListener::collectDescendantsIf(LLSD const &data) +{ + Response response(LLSD(), data); + LLUUID folder_id(data["folder_id"].asUUID()); + LLViewerInventoryCategory *cat = gInventory.getCategory(folder_id); + if (!cat) + { + return response.error(stringize("Folder ", std::quoted(data["folder_id"].asString()), " was not found")); + } + auto catresult = new CatResultSet; + auto itemresult = new ItemResultSet; + + LLFilteredCollector collector = LLFilteredCollector(data); + + // Populate results directly into the catresult and itemresult arrays. + // TODO: sprinkle count-based coroutine yields into the real + // collectDescendentsIf() method so it doesn't steal too many cycles. + gInventory.collectDescendentsIf( + folder_id, + catresult->mCategories, + itemresult->mItems, + LLInventoryModel::EXCLUDE_TRASH, + collector); + + response["categories"] = catresult->getKeyLength(); + response["items"] = itemresult->getKeyLength(); +} + +/*==========================================================================*| +void LLInventoryListener::getSingle(LLSD const& data) +{ + auto result = LL::ResultSet::getInstance(data["result"]); + sendReply(llsd::map("single", result->getSingle(data["index"])), data); +} +|*==========================================================================*/ + +void LLInventoryListener::getSlice(LLSD const& data) +{ + auto result = LL::ResultSet::getInstance(data["result"]); + int count = data.has("count")? data["count"].asInteger() : MAX_ITEM_LIMIT; + LL_DEBUGS("Lua") << *result << ".getSlice(" << data["index"].asInteger() + << ", " << count << ')' << LL_ENDL; + auto pair{ result->getSliceStart(data["index"], std::min(count, MAX_ITEM_LIMIT)) }; + sendReply(llsd::map("slice", pair.first, "start", pair.second), data); +} + +void LLInventoryListener::closeResult(LLSD const& data) +{ + LLSD results = data["result"]; + if (results.isInteger()) + { + results = llsd::array(results); + } + for (const auto& result : llsd::inArray(results)) + { + auto ptr = LL::ResultSet::getInstance(result); + if (ptr) + { + delete ptr.get(); + } + } +} + +LLFilteredCollector::LLFilteredCollector(LLSD const &data) : + mType(LLAssetType::EType::AT_UNKNOWN), + mLinkFilter(INCLUDE_LINKS), + mItemLimit(0), + mItemCount(0) +{ + + mName = data["name"].asString(); + mDesc = data["desc"].asString(); + + if (data.has("type")) + { + mType = LLAssetType::lookup(data["type"]); + } + if (data.has("filter_links")) + { + if (data["filter_links"] == "EXCLUDE_LINKS") + { + mLinkFilter = EXCLUDE_LINKS; + } + else if (data["filter_links"] == "ONLY_LINKS") + { + mLinkFilter = ONLY_LINKS; + } + } + if (data["limit"].isInteger()) + { + mItemLimit = std::max(data["limit"].asInteger(), 1); + } +} + +bool LLFilteredCollector::operator()(LLInventoryCategory *cat, LLInventoryItem *item) +{ + bool passed = checkagainstType(cat, item); + passed = passed && checkagainstNameDesc(cat, item); + passed = passed && checkagainstLinks(cat, item); + + if (passed) + { + ++mItemCount; + } + return passed; +} + +bool LLFilteredCollector::checkagainstNameDesc(LLInventoryCategory *cat, LLInventoryItem *item) +{ + std::string name, desc; + bool passed(true); + if (cat) + { + if (!mDesc.empty()) return false; + name = cat->getName(); + } + if (item) + { + name = item->getName(); + passed = (mDesc.empty() || (item->getDescription().find(mDesc) != std::string::npos)); + } + + return passed && (mName.empty() || name.find(mName) != std::string::npos); +} + +bool LLFilteredCollector::checkagainstType(LLInventoryCategory *cat, LLInventoryItem *item) +{ + if (mType == LLAssetType::AT_UNKNOWN) + { + return true; + } + if (cat && (mType == LLAssetType::AT_CATEGORY)) + { + return true; + } + if (item && item->getType() == mType) + { + return true; + } + return false; +} + +bool LLFilteredCollector::checkagainstLinks(LLInventoryCategory *cat, LLInventoryItem *item) +{ + bool is_link = cat ? cat->getIsLinkType() : item->getIsLinkType(); + if (is_link && (mLinkFilter == EXCLUDE_LINKS)) + return false; + if (!is_link && (mLinkFilter == ONLY_LINKS)) + return false; + return true; +} diff --git a/indra/newview/llinventorylistener.h b/indra/newview/llinventorylistener.h new file mode 100644 index 0000000000..a05385f2c8 --- /dev/null +++ b/indra/newview/llinventorylistener.h @@ -0,0 +1,53 @@ +/** + * @file llinventorylistener.h + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2024, 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_LLINVENTORYLISTENER_H +#define LL_LLINVENTORYLISTENER_H + +#include "lleventapi.h" +#include "llinventoryfunctions.h" + +class LLInventoryListener : public LLEventAPI +{ +public: + LLInventoryListener(); + +private: + void getItemsInfo(LLSD const &data); + void getFolderTypeNames(LLSD const &data); + void getAssetTypeNames(LLSD const &data); + void getBasicFolderID(LLSD const &data); + void getDirectDescendants(LLSD const &data); + void collectDescendantsIf(LLSD const &data); +/*==========================================================================*| + void getSingle(LLSD const& data); +|*==========================================================================*/ + void getSlice(LLSD const& data); + void closeResult(LLSD const& data); +}; + +#endif // LL_LLINVENTORYLISTENER_H + diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index d57cb13362..2449a226c3 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -1282,6 +1282,10 @@ void LLInventoryModel::collectDescendentsIf(const LLUUID& id, { for (auto& cat : *cat_array) { + if (add.exceedsLimit()) + { + break; + } if(add(cat,NULL)) { cats.push_back(cat); @@ -1297,6 +1301,10 @@ void LLInventoryModel::collectDescendentsIf(const LLUUID& id, { for (auto& item : *item_array) { + if (add.exceedsLimit()) + { + break; + } if(add(NULL, item)) { items.push_back(item); @@ -2037,8 +2045,8 @@ void LLInventoryModel::deleteObject(const LLUUID& id, bool fix_broken_links, boo { LL_WARNS(LOG_INV) << "Deleting cat " << id << " while it still has child cats" << LL_ENDL; } - delete cat_list; mParentChildCategoryTree.erase(id); + delete cat_list; } addChangedMask(LLInventoryObserver::REMOVE, id); @@ -4824,6 +4832,17 @@ std::string LLInventoryModel::getFullPath(const LLInventoryObject *obj) const return result; } +/* +const LLInventoryObject* LLInventoryModel::findByFullPath(const std::string& path) +{ + vector<std::string> path_elts; + boost::algorithm::split(path_elts, path, boost::is_any_of("/")); + for(path_elts, auto e) + { + } +} +*/ + ///---------------------------------------------------------------------------- /// Local function definitions ///---------------------------------------------------------------------------- @@ -5035,4 +5054,3 @@ void LLInventoryModel::FetchItemHttpHandler::processFailure(const char * const r << LLCoreHttpUtil::responseToString(response) << "]" << LL_ENDL; gInventory.notifyObservers(); } - diff --git a/indra/newview/llinventorymodelbackgroundfetch.cpp b/indra/newview/llinventorymodelbackgroundfetch.cpp index d8e6bf380e..2cf1554957 100644 --- a/indra/newview/llinventorymodelbackgroundfetch.cpp +++ b/indra/newview/llinventorymodelbackgroundfetch.cpp @@ -30,6 +30,7 @@ #include "llaisapi.h" #include "llagent.h" #include "llappviewer.h" +#include "llappearancemgr.h" #include "llcallbacklist.h" #include "llinventorymodel.h" #include "llinventorypanel.h" @@ -470,6 +471,22 @@ void LLInventoryModelBackgroundFetch::fetchCOF(nullary_func_t callback) callback(); LLUUID cat_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CURRENT_OUTFIT); LLInventoryModelBackgroundFetch::getInstance()->onAISFolderCalback(cat_id, id, FT_DEFAULT); + + if (id.notNull()) + { + // COF might have fetched base outfit folder through a link, but it hasn't + // fetched base outfit's content, which doesn't nessesary match COF, + // so make sure it's up to date + LLUUID baseoutfit_id = LLAppearanceMgr::getInstance()->getBaseOutfitUUID(); + if (baseoutfit_id.notNull()) + { + LLViewerInventoryCategory* cat = gInventory.getCategory(baseoutfit_id); + if (!cat || cat->getVersion() == LLViewerInventoryCategory::VERSION_UNKNOWN) + { + LLInventoryModelBackgroundFetch::getInstance()->fetchFolderAndLinks(baseoutfit_id, no_op); + } + } + } }); // start idle loop to track completion diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index e4d1010231..b8833dcd05 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -177,15 +177,15 @@ LLInventoryPanel::LLInventoryPanel(const LLInventoryPanel::Params& p) : } // context menu callbacks - mCommitCallbackRegistrar.add("Inventory.DoToSelected", boost::bind(&LLInventoryPanel::doToSelected, this, _2)); - mCommitCallbackRegistrar.add("Inventory.EmptyTrash", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH)); - mCommitCallbackRegistrar.add("Inventory.EmptyLostAndFound", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND)); - mCommitCallbackRegistrar.add("Inventory.DoCreate", boost::bind(&LLInventoryPanel::doCreate, this, _2)); - mCommitCallbackRegistrar.add("Inventory.AttachObject", boost::bind(&LLInventoryPanel::attachObject, this, _2)); - mCommitCallbackRegistrar.add("Inventory.BeginIMSession", boost::bind(&LLInventoryPanel::beginIMSession, this)); - mCommitCallbackRegistrar.add("Inventory.Share", boost::bind(&LLAvatarActions::shareWithAvatars, this)); - mCommitCallbackRegistrar.add("Inventory.FileUploadLocation", boost::bind(&LLInventoryPanel::fileUploadLocation, this, _2)); - mCommitCallbackRegistrar.add("Inventory.OpenNewFolderWindow", boost::bind(&LLInventoryPanel::openSingleViewInventory, this, LLUUID())); + mCommitCallbackRegistrar.add("Inventory.DoToSelected", { boost::bind(&LLInventoryPanel::doToSelected, this, _2), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.EmptyTrash", { boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.EmptyLostAndFound", { boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.DoCreate", { boost::bind(&LLInventoryPanel::doCreate, this, _2), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.AttachObject", { boost::bind(&LLInventoryPanel::attachObject, this, _2), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.BeginIMSession", { boost::bind(&LLInventoryPanel::beginIMSession, this), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.Share", { boost::bind(&LLAvatarActions::shareWithAvatars, this), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.FileUploadLocation", { boost::bind(&LLInventoryPanel::fileUploadLocation, this, _2), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.OpenNewFolderWindow", { boost::bind(&LLInventoryPanel::openSingleViewInventory, this, LLUUID()), LLUICtrl::cb_info::UNTRUSTED_THROTTLE }); } LLFolderView * LLInventoryPanel::createFolderRoot(LLUUID root_id ) @@ -580,8 +580,8 @@ void LLInventoryPanel::itemChanged(const LLUUID& item_id, U32 mask, const LLInve if (model_item && view_item && viewmodel_item) { const LLUUID& idp = viewmodel_item->getUUID(); - view_item->destroyView(); removeItemID(idp); + view_item->destroyView(); } LLInventoryObject const* objectp = mInventory->getObject(item_id); @@ -2214,9 +2214,9 @@ LLInventorySingleFolderPanel::LLInventorySingleFolderPanel(const Params& params) getFilter().setEmptyLookupMessage("InventorySingleFolderNoMatches"); getFilter().setDefaultEmptyLookupMessage("InventorySingleFolderEmpty"); - mCommitCallbackRegistrar.replace("Inventory.DoToSelected", boost::bind(&LLInventorySingleFolderPanel::doToSelected, this, _2)); - mCommitCallbackRegistrar.replace("Inventory.DoCreate", boost::bind(&LLInventorySingleFolderPanel::doCreate, this, _2)); - mCommitCallbackRegistrar.replace("Inventory.Share", boost::bind(&LLInventorySingleFolderPanel::doShare, this)); + mCommitCallbackRegistrar.replace("Inventory.DoToSelected", { boost::bind(&LLInventorySingleFolderPanel::doToSelected, this, _2), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.replace("Inventory.DoCreate", { boost::bind(&LLInventorySingleFolderPanel::doCreate, this, _2), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.replace("Inventory.Share", { boost::bind(&LLInventorySingleFolderPanel::doShare, this), cb_info::UNTRUSTED_BLOCK }); } LLInventorySingleFolderPanel::~LLInventorySingleFolderPanel() diff --git a/indra/newview/lljoystickbutton.cpp b/indra/newview/lljoystickbutton.cpp index 4eaf69c39d..10a1cd6b71 100644 --- a/indra/newview/lljoystickbutton.cpp +++ b/indra/newview/lljoystickbutton.cpp @@ -637,18 +637,24 @@ void LLJoystickCameraRotate::drawRotatedImage( LLPointer<LLUIImage> image, S32 r gGL.color4fv(UI_VERTEX_COLOR.mV); - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { - gGL.texCoord2fv( uv[ (rotations + 0) % 4]); - gGL.vertex2i(width, height ); + gGL.texCoord2fv(uv[(rotations + 0) % 4]); + gGL.vertex2i(width, height); + + gGL.texCoord2fv(uv[(rotations + 1) % 4]); + gGL.vertex2i(0, height); - gGL.texCoord2fv( uv[ (rotations + 1) % 4]); - gGL.vertex2i(0, height ); + gGL.texCoord2fv(uv[(rotations + 2) % 4]); + gGL.vertex2i(0, 0); + + gGL.texCoord2fv(uv[(rotations + 0) % 4]); + gGL.vertex2i(width, height); - gGL.texCoord2fv( uv[ (rotations + 2) % 4]); + gGL.texCoord2fv(uv[(rotations + 2) % 4]); gGL.vertex2i(0, 0); - gGL.texCoord2fv( uv[ (rotations + 3) % 4]); + gGL.texCoord2fv(uv[(rotations + 3) % 4]); gGL.vertex2i(width, 0); } gGL.end(); @@ -909,7 +915,7 @@ void LLJoystickQuaternion::drawRotatedImage(LLPointer<LLUIImage> image, S32 rota gGL.color4fv(UI_VERTEX_COLOR.mV); - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { gGL.texCoord2fv(uv[(rotations + 0) % 4]); gGL.vertex2i(width, height); @@ -920,6 +926,12 @@ void LLJoystickQuaternion::drawRotatedImage(LLPointer<LLUIImage> image, S32 rota gGL.texCoord2fv(uv[(rotations + 2) % 4]); gGL.vertex2i(0, 0); + gGL.texCoord2fv(uv[(rotations + 0) % 4]); + gGL.vertex2i(width, height); + + gGL.texCoord2fv(uv[(rotations + 1) % 4]); + gGL.vertex2i(0, height); + gGL.texCoord2fv(uv[(rotations + 3) % 4]); gGL.vertex2i(width, 0); } diff --git a/indra/newview/lllocalbitmaps.cpp b/indra/newview/lllocalbitmaps.cpp index 31c9eb8966..81b2d23cf7 100644 --- a/indra/newview/lllocalbitmaps.cpp +++ b/indra/newview/lllocalbitmaps.cpp @@ -996,17 +996,12 @@ LLLocalBitmapTimer::~LLLocalBitmapTimer() void LLLocalBitmapTimer::startTimer() { - mEventTimer.start(); + start(); } void LLLocalBitmapTimer::stopTimer() { - mEventTimer.stop(); -} - -bool LLLocalBitmapTimer::isRunning() -{ - return mEventTimer.getStarted(); + stop(); } bool LLLocalBitmapTimer::tick() diff --git a/indra/newview/lllocalbitmaps.h b/indra/newview/lllocalbitmaps.h index e169f96e70..4bad9ff9a5 100644 --- a/indra/newview/lllocalbitmaps.h +++ b/indra/newview/lllocalbitmaps.h @@ -120,8 +120,7 @@ class LLLocalBitmapTimer : public LLEventTimer public: void startTimer(); void stopTimer(); - bool isRunning(); - bool tick(); + bool tick() override; }; diff --git a/indra/newview/lllocalgltfmaterials.cpp b/indra/newview/lllocalgltfmaterials.cpp index fab18f2d26..3af1433a40 100644 --- a/indra/newview/lllocalgltfmaterials.cpp +++ b/indra/newview/lllocalgltfmaterials.cpp @@ -294,17 +294,12 @@ LLLocalGLTFMaterialTimer::~LLLocalGLTFMaterialTimer() void LLLocalGLTFMaterialTimer::startTimer() { - mEventTimer.start(); + start(); } void LLLocalGLTFMaterialTimer::stopTimer() { - mEventTimer.stop(); -} - -bool LLLocalGLTFMaterialTimer::isRunning() -{ - return mEventTimer.getStarted(); + stop(); } bool LLLocalGLTFMaterialTimer::tick() diff --git a/indra/newview/lllocalgltfmaterials.h b/indra/newview/lllocalgltfmaterials.h index b806b54508..9c2d1bb287 100644 --- a/indra/newview/lllocalgltfmaterials.h +++ b/indra/newview/lllocalgltfmaterials.h @@ -89,8 +89,7 @@ public: public: void startTimer(); void stopTimer(); - bool isRunning(); - bool tick(); + bool tick() override; }; class LLLocalGLTFMaterialMgr : public LLSingleton<LLLocalGLTFMaterialMgr> diff --git a/indra/newview/lllocationinputctrl.cpp b/indra/newview/lllocationinputctrl.cpp index 54dd5792a0..2c91fe06e9 100644 --- a/indra/newview/lllocationinputctrl.cpp +++ b/indra/newview/lllocationinputctrl.cpp @@ -377,7 +377,7 @@ LLLocationInputCtrl::LLLocationInputCtrl(const LLLocationInputCtrl::Params& p) addChild(mParcelIcon[SEE_AVATARS_ICON]); // Register callbacks and load the location field context menu (NB: the order matters). - LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Navbar.Action", boost::bind(&LLLocationInputCtrl::onLocationContextMenuItemClicked, this, _2)); + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Navbar.Action", { boost::bind(&LLLocationInputCtrl::onLocationContextMenuItemClicked, this, _2) }); LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Navbar.EnableMenuItem", boost::bind(&LLLocationInputCtrl::onLocationContextMenuItemEnabled, this, _2)); setPrearrangeCallback(boost::bind(&LLLocationInputCtrl::onLocationPrearrange, this, _2)); diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp new file mode 100644 index 0000000000..7fe5c1ece0 --- /dev/null +++ b/indra/newview/llluamanager.cpp @@ -0,0 +1,483 @@ +/** + * @file llluamanager.cpp + * @brief classes and functions for interfacing with LUA. + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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 "llluamanager.h" + +#include "fsyspath.h" +#include "llcoros.h" +#include "llerror.h" +#include "lleventcoro.h" +#include "llsdutil.h" +#include "llviewercontrol.h" +#include "lua_function.h" +#include "lualistener.h" +#include "stringize.h" + +#include <boost/algorithm/string/replace.hpp> + +#include "luau/luacode.h" +#include "luau/lua.h" +#include "luau/luaconf.h" +#include "luau/lualib.h" + +#include <sstream> +#include <string_view> +#include <vector> + +S32 LLLUAmanager::sAutorunScriptCount = 0; +S32 LLLUAmanager::sScriptCount = 0; +std::map<std::string, std::string> LLLUAmanager::sScriptNames; + +lua_function(sleep, "sleep(seconds): pause the running coroutine") +{ + lua_checkdelta(L, -1); + lua_Number seconds = lua_tonumber(L, -1); + lua_pop(L, 1); + llcoro::suspendUntilTimeout(narrow(seconds)); + LuaState::getParent(L).set_interrupts_counter(0); + return 0; +}; + +// This function consumes ALL Lua stack arguments and returns concatenated +// message string +std::string lua_print_msg(lua_State* L, std::string_view level) +{ + // On top of existing Lua arguments, we're going to push tostring() and + // duplicate each existing stack entry so we can stringize each one. + lluau_checkstack(L, 2); + luaL_where(L, 1); + // start with the 'where' info at the top of the stack + std::ostringstream out; + out << lua_tostring(L, -1); + lua_pop(L, 1); + const char* sep = ""; // 'where' info ends with ": " + // now iterate over arbitrary args, calling Lua tostring() on each and + // concatenating with separators + for (int p = 1, top = lua_gettop(L); p <= top; ++p) + { + out << sep; + sep = " "; + // push Lua tostring() function -- note, semantically different from + // lua_tostring()! + lua_getglobal(L, "tostring"); + // Now the stack is arguments 1 .. N, plus tostring(). + // Push a copy of the argument at index p. + lua_pushvalue(L, p); + // pop tostring() and arg-p, pushing tostring(arg-p) + // (ignore potential error code from lua_pcall() because, if there was + // an error, we expect the stack top to be an error message -- which + // we'll print) + lua_pcall(L, 1, 1, 0); + out << lua_tostring(L, -1); + lua_pop(L, 1); + } + // pop everything + lua_settop(L, 0); + // capture message string + std::string msg{ out.str() }; + // put message out there for any interested party (*koff* LLFloaterLUADebug *koff*) + LLEventPumps::instance().obtain("lua output").post(stringize(level, ": ", msg)); + + llcoro::suspend(); + return msg; +} + +lua_function(print_debug, "print_debug(args...): DEBUG level logging") +{ + LL_DEBUGS("Lua") << lua_print_msg(L, "DEBUG") << LL_ENDL; + return 0; +} + +// also used for print(); see LuaState constructor +lua_function(print_info, "print_info(args...): INFO level logging") +{ + LL_INFOS("Lua") << lua_print_msg(L, "INFO") << LL_ENDL; + return 0; +} + +lua_function(print_warning, "print_warning(args...): WARNING level logging") +{ + LL_WARNS("Lua") << lua_print_msg(L, "WARN") << LL_ENDL; + return 0; +} + +lua_function(post_on, "post_on(pumpname, data): post specified data to specified LLEventPump") +{ + lua_checkdelta(L, -2); + std::string pumpname{ lua_tostdstring(L, 1) }; + LLSD data{ lua_tollsd(L, 2) }; + lua_pop(L, 2); + LL_DEBUGS("Lua") << "post_on('" << pumpname << "', " << data << ")" << LL_ENDL; + LLEventPumps::instance().obtain(pumpname).post(data); + return 0; +} + +lua_function(get_event_pumps, + "get_event_pumps():\n" + "Returns replypump, commandpump: names of LLEventPumps specific to this chunk.\n" + "Events posted to replypump are queued for get_event_next().\n" + "post_on(commandpump, ...) to engage LLEventAPI operations (see helpleap()).") +{ + lua_checkdelta(L, 2); + lluau_checkstack(L, 2); + auto& listener{ LuaState::obtainListener(L) }; + // return the reply pump name and the command pump name on caller's lua_State + lua_pushstdstring(L, listener.getReplyName()); + lua_pushstdstring(L, listener.getCommandName()); + return 2; +} + +lua_function(get_event_next, + "get_event_next():\n" + "Returns the next (pumpname, data) pair from the replypump whose name\n" + "is returned by get_event_pumps(). Blocks the calling chunk until an\n" + "event becomes available.") +{ + lua_checkdelta(L, 2); + lluau_checkstack(L, 2); + auto& listener{ LuaState::obtainListener(L) }; + const auto& [pump, data]{ listener.getNext() }; + lua_pushstdstring(L, pump); + lua_pushllsd(L, data); + LuaState::getParent(L).set_interrupts_counter(0); + return 2; +} + +LLCoros::Future<LLLUAmanager::script_result> +LLLUAmanager::startScriptFile(const std::string& filename) +{ + // Despite returning from startScriptFile(), we need this Promise to + // remain alive until the callback has fired. + auto promise{ std::make_shared<LLCoros::Promise<script_result>>() }; + runScriptFile(filename, false, + [promise](int count, LLSD result) + { promise->set_value({ count, result }); }); + return LLCoros::getFuture(*promise); +} + +LLLUAmanager::script_result LLLUAmanager::waitScriptFile(const std::string& filename) +{ + return startScriptFile(filename).get(); +} + +void LLLUAmanager::runScriptFile(const std::string &filename, bool autorun, + script_result_fn result_cb) +{ + // A script_result_fn will be called when LuaState::expr() completes. + LLCoros::instance().launch(filename, [filename, autorun, result_cb]() + { + ScriptObserver observer(LLCoros::getName(), filename); + LLSD paths(gSavedSettings.getLLSD("LuaCommandPath")); + LL_DEBUGS("Lua") << "LuaCommandPath = " << paths << LL_ENDL; + // allow LuaCommandPath to be specified relative to install dir + ScriptCommand command(filename, paths, gDirUtilp->getAppRODataDir()); + auto error = command.error(); + if (! error.empty()) + { + if (result_cb) + { + result_cb(-1, error); + } + return; + } + + llifstream in_file; + in_file.open(command.script); + // At this point, since ScriptCommand did not report an error, we + // should be able to assume that 'script' exists. If we can't open it, + // something else is wrong?! + llassert(in_file.is_open()); + if (autorun) + { + sAutorunScriptCount++; + } + sScriptCount++; + + LuaState L; + std::string text{std::istreambuf_iterator<char>(in_file), {}}; + auto [count, result] = L.expr(command.script, text, command.args); + if (result_cb) + { + result_cb(count, result); + } + }); +} + +LLCoros::Future<LLLUAmanager::script_result> +LLLUAmanager::startScriptLine(const std::string& chunk) +{ + // Despite returning from startScriptLine(), we need this Promise to + // remain alive until the callback has fired. + auto promise{ std::make_shared<LLCoros::Promise<script_result>>() }; + runScriptLine(chunk, + [promise](int count, LLSD result) + { promise->set_value({ count, result }); }); + return LLCoros::getFuture(*promise); +} + +LLLUAmanager::script_result LLLUAmanager::waitScriptLine(const std::string& chunk) +{ + return startScriptLine(chunk).get(); +} + +void LLLUAmanager::runScriptLine(const std::string& chunk, script_result_fn result_cb) +{ + // find a suitable abbreviation for the chunk string + std::string shortchunk{ chunk }; + const size_t shortlen = 40; + std::string::size_type eol = shortchunk.find_first_of("\r\n"); + if (eol != std::string::npos) + shortchunk = shortchunk.substr(0, eol); + if (shortchunk.length() > shortlen) + shortchunk = stringize(shortchunk.substr(0, shortlen), "..."); + + std::string desc{ "lua: " + shortchunk }; + LLCoros::instance().launch(desc, [desc, chunk, result_cb]() + { + LuaState L; + auto [count, result] = L.expr(desc, chunk); + if (result_cb) + { + result_cb(count, result); + } + }); +} + +std::string read_file(const std::string &name) +{ + llifstream in_file; + in_file.open(name.c_str()); + + if (in_file.is_open()) + { + return std::string{std::istreambuf_iterator<char>(in_file), {}}; + } + + return {}; +} + +lua_function(require, "require(module_name) : load module_name.lua from known places") +{ + lua_checkdelta(L); + std::string name = lua_tostdstring(L, 1); + lua_pop(L, 1); + + // resolveRequire() does not return in case of error. + LLRequireResolver::resolveRequire(L, name); + + // resolveRequire() returned the newly-loaded module on the stack top. + // Return it. + return 1; +} + +// push loaded module or throw Lua error +void LLRequireResolver::resolveRequire(lua_State *L, std::string path) +{ + LLRequireResolver resolver(L, std::move(path)); + // findModule() pushes the loaded module or throws a Lua error. + resolver.findModule(); +} + +LLRequireResolver::LLRequireResolver(lua_State *L, const std::string& path) : + mPathToResolve(fsyspath(path).lexically_normal()), + L(L) +{ + mSourceDir = lluau::source_path(L).parent_path(); + + if (mPathToResolve.is_absolute()) + luaL_argerrorL(L, 1, "cannot require a full path"); +} + +// push the loaded module or throw a Lua error +void LLRequireResolver::findModule() +{ + // If mPathToResolve is absolute, this replaces mSourceDir. + auto absolutePath = (mSourceDir / mPathToResolve).u8string(); + + // Push _MODULES table on stack for checking and saving to the cache + luaL_findtable(L, LUA_REGISTRYINDEX, "_MODULES", 1); + // Remove that stack entry no matter how we exit + LuaRemover rm_MODULES(L, -1); + + // Check if the module is already in _MODULES table, read from file + // otherwise. + // findModuleImpl() pushes module if found, nothing if not, may throw Lua + // error. + if (findModuleImpl(absolutePath)) + return; + + // not already cached - prep error message just in case + auto fail{ + [L=L, path=mPathToResolve.u8string()]() + { luaL_error(L, "could not find require('%s')", path.data()); }}; + + if (mPathToResolve.is_absolute()) + { + // no point searching known directories for an absolute path + fail(); + } + + LLSD lib_paths(gSavedSettings.getLLSD("LuaRequirePath")); + LL_DEBUGS("Lua") << "LuaRequirePath = " << lib_paths << LL_ENDL; + for (const auto& path : llsd::inArray(lib_paths)) + { + // if path is already absolute, operator/() preserves it + auto abspath(fsyspath(gDirUtilp->getAppRODataDir()) / path.asString()); + std::string absolutePathOpt = (abspath / mPathToResolve).u8string(); + + if (absolutePathOpt.empty()) + luaL_error(L, "error requiring module '%s'", mPathToResolve.u8string().data()); + + if (findModuleImpl(absolutePathOpt)) + return; + } + + // not found + fail(); +} + +// expects _MODULES table on stack top (and leaves it there) +// - if found, pushes loaded module and returns true +// - not found, pushes nothing and returns false +// - may throw Lua error +bool LLRequireResolver::findModuleImpl(const std::string& absolutePath) +{ + std::string possibleSuffixedPaths[] = {absolutePath + ".luau", absolutePath + ".lua"}; + + for (const auto& suffixedPath : possibleSuffixedPaths) + { + // Check _MODULES cache for module + lua_getfield(L, -1, suffixedPath.data()); + if (!lua_isnil(L, -1)) + { + return true; + } + lua_pop(L, 1); + + // Try to read the matching file + std::string source = read_file(suffixedPath); + if (!source.empty()) + { + // Try to run the loaded source. This will leave either a string + // error message or the module contents on the stack top. + runModule(suffixedPath, source); + + // If the stack top is an error message string, raise it. + if (lua_isstring(L, -1)) + lua_error(L); + + // duplicate the new module: _MODULES newmodule newmodule + lua_pushvalue(L, -1); + // store _MODULES[found path] = newmodule + lua_setfield(L, -3, suffixedPath.data()); + + return true; + } + } + + return false; +} + +// push string error message or new module +void LLRequireResolver::runModule(const std::string& desc, const std::string& code) +{ + // Here we just loaded a new module 'code', need to run it and get its result. + lua_State *ML = lua_mainthread(L); + + { + // If loadstring() returns (! LUA_OK) then there's an error message on + // the stack. If it returns LUA_OK then the newly-loaded module code + // is on the stack. + LL_DEBUGS("Lua") << "Loading module " << desc << LL_ENDL; + if (lluau::loadstring(ML, desc, code) != LUA_OK) + { + // error message on stack top + LL_DEBUGS("Lua") << "Error loading module " << desc << ": " + << lua_tostring(ML, -1) << LL_ENDL; + lua_pushliteral(ML, "loadstring: "); + // stack contains error, "loadstring: " + // swap: insert stack top at position -2 + lua_insert(ML, -2); + // stack contains "loadstring: ", error + lua_concat(ML, 2); + // stack contains "loadstring: " + error + } + else // module code on stack top + { + // push debug module + lua_getglobal(ML, "debug"); + // push debug.traceback + lua_getfield(ML, -1, "traceback"); + // stack contains module code, debug, debug.traceback + // ditch debug + lua_replace(ML, -2); + // stack contains module code, debug.traceback + // swap: insert stack top at position -2 + lua_insert(ML, -2); + // stack contains debug.traceback, module code + LL_DEBUGS("Lua") << "Loaded module " << desc << ", running" << LL_ENDL; + // no arguments, one return value + // pass debug.traceback as the error function + int status = lua_pcall(ML, 0, 1, -2); + // lua_pcall() has popped the module code and replaced it with its + // return value. Regardless of status or the type of the stack + // top, get rid of debug.traceback on the stack. + lua_remove(ML, -2); + + if (status == LUA_OK) + { + auto top{ lua_gettop(ML) }; + std::string type{ (top == 0)? "nothing" + : lua_typename(ML, lua_type(ML, -1)) }; + LL_DEBUGS("Lua") << "Module " << desc << " returned " << type << LL_ENDL; + if ((top == 0) || ! (lua_istable(ML, -1) || lua_isfunction(ML, -1))) + { + lua_pushfstring(ML, "module %s must return a table or function, not %s", + desc.data(), type.data()); + } + } + else if (status == LUA_YIELD) + { + LL_DEBUGS("Lua") << "Module " << desc << " yielded" << LL_ENDL; + lua_pushfstring(ML, "module %s can not yield", desc.data()); + } + else + { + llassert(lua_isstring(ML, -1)); + LL_DEBUGS("Lua") << "Module " << desc << " error: " + << lua_tostring(ML, -1) << LL_ENDL; + } + } + } + // There's now a return value (string error message or module) on top of ML. + // Move return value to L's stack. + if (ML != L) + { + lua_xmove(ML, L, 1); + } +} diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h new file mode 100644 index 0000000000..b98b5d4ef6 --- /dev/null +++ b/indra/newview/llluamanager.h @@ -0,0 +1,133 @@ +/** + * @file llluamanager.h + * @brief classes and functions for interfacing with LUA. + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, 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_LLLUAMANAGER_H +#define LL_LLLUAMANAGER_H + +#include "fsyspath.h" +#include "llcoros.h" +#include "llsd.h" +#include <functional> +#include <string> +#include <utility> // std::pair + +#include "luau/lua.h" +#include "luau/lualib.h" + +class LuaState; + +class LLLUAmanager +{ + friend class ScriptObserver; + +public: + // Pass a callback with this signature to obtain the result, if any, of + // running a script or source string. + // count < 0 means error, and result.asString() is the error message. + // count == 0 with result.isUndefined() means the script returned no results. + // count == 1 means the script returned one result. + // count > 1 with result.isArray() means the script returned multiple + // results, represented as the entries of the result array. + typedef std::function<void(int count, const LLSD& result)> script_result_fn; + // same semantics as script_result_fn parameters + typedef std::pair<int, LLSD> script_result; + + // Run the script specified by the command line passed as @a filename. + // This can be followed by some number of command-line arguments, which + // a Lua script can view using either '...' or predefined global 'arg'. + // The script pathname or its arguments can be quoted using 'single + // quotes' or "double quotes", or special characters can be \escaped. + // runScriptFile() recognizes the case in which the whole 'filename' + // string is a path containing spaces; if so no arguments are permitted. + // In either form, if the script pathname isn't absolute, it is sought on + // LuaCommandPath. + // If autorun is true, statistics will count this as an autorun script. + static void runScriptFile(const std::string &filename, bool autorun = false, + script_result_fn result_cb = {}); + // Start running a Lua script file, returning an LLCoros::Future whose + // get() method will pause the calling coroutine until it can deliver the + // (count, result) pair described above. Between startScriptFile() and + // Future::get(), the caller and the Lua script coroutine will run + // concurrently. + // @a filename is as described for runScriptFile(). + static LLCoros::Future<script_result> startScriptFile(const std::string& filename); + // Run a Lua script file, and pause the calling coroutine until it completes. + // The return value is the (count, result) pair described above. + // @a filename is as described for runScriptFile(). + static script_result waitScriptFile(const std::string& filename); + + static void runScriptLine(const std::string &chunk, script_result_fn result_cb = {}); + // Start running a Lua chunk, returning an LLCoros::Future whose + // get() method will pause the calling coroutine until it can deliver the + // (count, result) pair described above. Between startScriptLine() and + // Future::get(), the caller and the Lua script coroutine will run + // concurrently. + static LLCoros::Future<script_result> startScriptLine(const std::string& chunk); + // Run a Lua chunk, and pause the calling coroutine until it completes. + // The return value is the (count, result) pair described above. + static script_result waitScriptLine(const std::string& chunk); + + static const std::map<std::string, std::string> getScriptNames() { return sScriptNames; } + + static S32 sAutorunScriptCount; + static S32 sScriptCount; + + private: + static std::map<std::string, std::string> sScriptNames; +}; + +class LLRequireResolver +{ + public: + static void resolveRequire(lua_State *L, std::string path); + + private: + fsyspath mPathToResolve; + fsyspath mSourceDir; + + LLRequireResolver(lua_State *L, const std::string& path); + + void findModule(); + lua_State *L; + + bool findModuleImpl(const std::string& absolutePath); + void runModule(const std::string& desc, const std::string& code); +}; + +// RAII class to guarantee that a script entry is erased even when coro is terminated +class ScriptObserver +{ + public: + ScriptObserver(const std::string &coro_name, const std::string &filename) : mCoroName(coro_name) + { + LLLUAmanager::sScriptNames[mCoroName] = filename; + } + ~ScriptObserver() { LLLUAmanager::sScriptNames.erase(mCoroName); } + + private: + std::string mCoroName; +}; +#endif diff --git a/indra/newview/llmanip.cpp b/indra/newview/llmanip.cpp index 9a0b1bfcd6..8bcfaa36e8 100644 --- a/indra/newview/llmanip.cpp +++ b/indra/newview/llmanip.cpp @@ -574,9 +574,6 @@ void LLManip::renderTickValue(const LLVector3& pos, F32 value, const std::string gGL.scalef(inv_zoom_amt, inv_zoom_amt, inv_zoom_amt); } - LLColor4 shadow_color = LLColor4::black; - shadow_color.mV[VALPHA] = color.mV[VALPHA] * 0.5f; - if (fractional_portion != 0) { fraction_string = llformat("%c%02d%s", LLResMgr::getInstance()->getDecimalPoint(), fractional_portion, suffix.c_str()); diff --git a/indra/newview/llmanipscale.cpp b/indra/newview/llmanipscale.cpp index 19868f3c3e..9966a8eedb 100644 --- a/indra/newview/llmanipscale.cpp +++ b/indra/newview/llmanipscale.cpp @@ -618,43 +618,22 @@ void LLManipScale::renderFaces( const LLBBox& bbox ) { gGL.color4fv( default_normal_color.mV ); LLGLDepthTest gls_depth(GL_FALSE); - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLE_STRIP); { - // Face 0 - gGL.vertex3f(min.mV[VX], max.mV[VY], max.mV[VZ]); - gGL.vertex3f(min.mV[VX], min.mV[VY], max.mV[VZ]); - gGL.vertex3f(max.mV[VX], min.mV[VY], max.mV[VZ]); - gGL.vertex3f(max.mV[VX], max.mV[VY], max.mV[VZ]); - - // Face 1 - gGL.vertex3f(max.mV[VX], min.mV[VY], max.mV[VZ]); + gGL.vertex3f(min.mV[VX], max.mV[VY], min.mV[VZ]); + gGL.vertex3f(max.mV[VX], max.mV[VY], min.mV[VZ]); + gGL.vertex3f(min.mV[VX], min.mV[VY], min.mV[VZ]); gGL.vertex3f(max.mV[VX], min.mV[VY], min.mV[VZ]); + gGL.vertex3f(max.mV[VX], min.mV[VY], max.mV[VZ]); gGL.vertex3f(max.mV[VX], max.mV[VY], min.mV[VZ]); gGL.vertex3f(max.mV[VX], max.mV[VY], max.mV[VZ]); - - // Face 2 gGL.vertex3f(min.mV[VX], max.mV[VY], min.mV[VZ]); gGL.vertex3f(min.mV[VX], max.mV[VY], max.mV[VZ]); - gGL.vertex3f(max.mV[VX], max.mV[VY], max.mV[VZ]); - gGL.vertex3f(max.mV[VX], max.mV[VY], min.mV[VZ]); - - // Face 3 - gGL.vertex3f(min.mV[VX], max.mV[VY], max.mV[VZ]); - gGL.vertex3f(min.mV[VX], max.mV[VY], min.mV[VZ]); gGL.vertex3f(min.mV[VX], min.mV[VY], min.mV[VZ]); gGL.vertex3f(min.mV[VX], min.mV[VY], max.mV[VZ]); - - // Face 4 - gGL.vertex3f(min.mV[VX], min.mV[VY], max.mV[VZ]); - gGL.vertex3f(min.mV[VX], min.mV[VY], min.mV[VZ]); - gGL.vertex3f(max.mV[VX], min.mV[VY], min.mV[VZ]); gGL.vertex3f(max.mV[VX], min.mV[VY], max.mV[VZ]); - - // Face 5 - gGL.vertex3f(min.mV[VX], min.mV[VY], min.mV[VZ]); - gGL.vertex3f(min.mV[VX], max.mV[VY], min.mV[VZ]); - gGL.vertex3f(max.mV[VX], max.mV[VY], min.mV[VZ]); - gGL.vertex3f(max.mV[VX], min.mV[VY], min.mV[VZ]); + gGL.vertex3f(min.mV[VX], max.mV[VY], max.mV[VZ]); + gGL.vertex3f(max.mV[VX], max.mV[VY], max.mV[VZ]); } gGL.end(); } diff --git a/indra/newview/llmediactrl.cpp b/indra/newview/llmediactrl.cpp index b39a976ebd..49260d5c50 100644 --- a/indra/newview/llmediactrl.cpp +++ b/indra/newview/llmediactrl.cpp @@ -345,8 +345,8 @@ bool LLMediaCtrl::handleRightMouseDown( S32 x, S32 y, MASK mask ) auto menu = mContextMenuHandle.get(); if (!menu) { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registar; - registar.add("Open.WebInspector", boost::bind(&LLMediaCtrl::onOpenWebInspector, this)); + LLUICtrl::ScopedRegistrarHelper registrar; + registrar.add("Open.WebInspector", boost::bind(&LLMediaCtrl::onOpenWebInspector, this)); // stinson 05/05/2014 : use this as the parent of the context menu if the static menu // container has yet to be created @@ -843,7 +843,7 @@ void LLMediaCtrl::draw() calcOffsetsAndSize(&x_offset, &y_offset, &width, &height); // draw the browser - gGL.begin( LLRender::QUADS ); + gGL.begin(LLRender::TRIANGLES); if (! media_plugin->getTextureCoordsOpenGL()) { // render using web browser reported width and height, instead of trying to invert GL scale @@ -856,6 +856,12 @@ void LLMediaCtrl::draw() gGL.texCoord2f( 0.f, max_v ); gGL.vertex2i( x_offset, y_offset ); + gGL.texCoord2f(max_u, 0.f); + gGL.vertex2i(x_offset + width, y_offset + height); + + gGL.texCoord2f(0.f, max_v); + gGL.vertex2i(x_offset, y_offset); + gGL.texCoord2f( max_u, max_v ); gGL.vertex2i( x_offset + width, y_offset ); } @@ -871,6 +877,12 @@ void LLMediaCtrl::draw() gGL.texCoord2f( 0.f, 0.f ); gGL.vertex2i( x_offset, y_offset ); + gGL.texCoord2f(max_u, max_v); + gGL.vertex2i(x_offset + width, y_offset + height); + + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2i(x_offset, y_offset); + gGL.texCoord2f( max_u, 0.f ); gGL.vertex2i( x_offset + width, y_offset ); } diff --git a/indra/newview/llmediadataclient.h b/indra/newview/llmediadataclient.h index ca035e79e0..a5e6e43e3f 100644 --- a/indra/newview/llmediadataclient.h +++ b/indra/newview/llmediadataclient.h @@ -219,7 +219,7 @@ protected: { public: RetryTimer(F32 time, Request::ptr_t); - virtual bool tick(); + bool tick() override; private: // back-pointer Request::ptr_t mRequest; @@ -286,7 +286,7 @@ private: { public: QueueTimer(F32 time, LLMediaDataClient *mdc); - virtual bool tick(); + bool tick() override; private: // back-pointer LLPointer<LLMediaDataClient> mMDC; diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index 532a87bbd1..a8585c7f23 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -3692,20 +3692,63 @@ S32 LLMeshRepository::update() return static_cast<S32>(size); } -void LLMeshRepository::unregisterMesh(LLVOVolume* vobj) +void LLMeshRepository::unregisterMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail) { - for (auto& lod : mLoadingMeshes) + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + + llassert((mesh_params.getSculptType() & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH); + llassert(mesh_params.getSculptID().notNull()); + auto& lod = mLoadingMeshes[detail]; + auto param_iter = lod.find(mesh_params.getSculptID()); + if (param_iter != lod.end()) { - for (auto& param : lod) + vector_replace_with_last(param_iter->second, vobj); + llassert(!vector_replace_with_last(param_iter->second, vobj)); + if (param_iter->second.empty()) { - vector_replace_with_last(param.second, vobj); + lod.erase(param_iter); } } +} - for (auto& skin_pair : mLoadingSkins) +void LLMeshRepository::unregisterSkinInfo(const LLUUID& mesh_id, LLVOVolume* vobj) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + + llassert(mesh_id.notNull()); + auto skin_pair_iter = mLoadingSkins.find(mesh_id); + if (skin_pair_iter != mLoadingSkins.end()) + { + vector_replace_with_last(skin_pair_iter->second, vobj); + llassert(!vector_replace_with_last(skin_pair_iter->second, vobj)); + if (skin_pair_iter->second.empty()) + { + mLoadingSkins.erase(skin_pair_iter); + } + } +} + +// Lots of dead objects make expensive calls to +// LLMeshRepository::unregisterMesh which may delay shutdown. Avoid this by +// preemptively unregistering all meshes. +// We can also do this safely if all objects are confirmed dead for some other +// reason. +void LLMeshRepository::unregisterAllMeshes() +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; + + // The size of mLoadingMeshes and mLoadingSkins may be large and thus + // expensive to iterate over in LLVOVolume::~LLVOVolume. + // This is unnecessary during shutdown, so we ignore the referenced objects in the + // least expensive way which is still safe: by clearing these containers. + // Clear now and not in LLMeshRepository::shutdown because + // LLMeshRepository::notifyLoadedMeshes could (depending on invocation + // order) reference a pointer to an object after it has been deleted. + for (auto& lod : mLoadingMeshes) { - vector_replace_with_last(skin_pair.second, vobj); + lod.clear(); } + mLoadingSkins.clear(); } S32 LLMeshRepository::loadMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail, S32 last_lod) @@ -3900,6 +3943,7 @@ void LLMeshRepository::notifyLoadedMeshes() for (auto iter = mSkinMap.begin(), ender = mSkinMap.end(); iter != ender;) { auto copy_iter = iter++; + LLUUID id = copy_iter->first; //skinbytes += U64Bytes(sizeof(LLMeshSkinInfo)); //skinbytes += U64Bytes(copy_iter->second->mJointNames.size() * sizeof(std::string)); @@ -3913,7 +3957,6 @@ void LLMeshRepository::notifyLoadedMeshes() } // erase from background thread - LLUUID id = iter->first; mThread->mWorkQueue.post([=]() { mThread->mSkinMap.erase(id); diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index b5c2424578..d864a07615 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -641,10 +641,12 @@ public: LLMeshRepository(); void init(); + void unregisterAllMeshes(); void shutdown(); S32 update(); - void unregisterMesh(LLVOVolume* volume); + void unregisterMesh(LLVOVolume* vobj, const LLVolumeParams& mesh_params, S32 detail); + void unregisterSkinInfo(const LLUUID& mesh_id, LLVOVolume* vobj); //mesh management functions S32 loadMesh(LLVOVolume* volume, const LLVolumeParams& mesh_params, S32 detail = 0, S32 last_lod = -1); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index ef09cfa55b..1e8b7d39cc 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -134,25 +134,17 @@ std::string getLodSuffix(S32 lod) void FindModel(LLModelLoader::scene& scene, const std::string& name_to_match, LLModel*& baseModelOut, LLMatrix4& matOut) { - LLModelLoader::scene::iterator base_iter = scene.begin(); - bool found = false; - while (!found && (base_iter != scene.end())) + for (auto scene_iter = scene.begin(); scene_iter != scene.end(); scene_iter++) { - matOut = base_iter->first; - - LLModelLoader::model_instance_list::iterator base_instance_iter = base_iter->second.begin(); - while (!found && (base_instance_iter != base_iter->second.end())) + for (auto model_iter = scene_iter->second.begin(); model_iter != scene_iter->second.end(); model_iter++) { - LLModelInstance& base_instance = *base_instance_iter++; - LLModel* base_model = base_instance.mModel; - - if (base_model && (base_model->mLabel == name_to_match)) + if (model_iter->mModel && (model_iter->mModel->mLabel == name_to_match)) { - baseModelOut = base_model; + baseModelOut = model_iter->mModel; + matOut = scene_iter->first; return; } } - base_iter++; } } @@ -212,9 +204,12 @@ LLModelPreview::LLModelPreview(S32 width, S32 height, LLFloater* fmp) LLModelPreview::~LLModelPreview() { + LLMutexLock lock(this); + if (mModelLoader) { mModelLoader->shutdown(); + mModelLoader = NULL; } if (mPreviewAvatar) @@ -262,7 +257,7 @@ void LLModelPreview::updateDimentionsAndOffsets() accounted.insert(instance.mModel); // update instance skin info for each lods pelvisZoffset - for (int j = 0; j<LLModel::NUM_LODS; ++j) + for (int j = 0; j < LLModel::NUM_LODS; ++j) { if (instance.mLOD[j]) { @@ -303,7 +298,7 @@ void LLModelPreview::rebuildUploadData() bool legacyMatching = gSavedSettings.getBOOL("ImporterLegacyMatching"); U32 load_state = 0; - for (LLModelLoader::scene::iterator iter = mBaseScene.begin(); iter != mBaseScene.end(); ++iter) + for (auto iter = mBaseScene.begin(); iter != mBaseScene.end(); ++iter) { //for each transform in scene LLMatrix4 mat = iter->first; @@ -322,9 +317,9 @@ void LLModelPreview::rebuildUploadData() mat *= scale_mat; - for (LLModelLoader::model_instance_list::iterator model_iter = iter->second.begin(); model_iter != iter->second.end();) + for (auto model_iter = iter->second.begin(); model_iter != iter->second.end(); ++model_iter) { // for each instance with said transform applied - LLModelInstance instance = *model_iter++; + LLModelInstance instance = *model_iter; LLModel* base_model = instance.mModel; @@ -910,7 +905,7 @@ void LLModelPreview::clearIncompatible(S32 lod) { mBaseModel = mModel[lod]; mBaseScene = mScene[lod]; - mVertexBuffer[5].clear(); + mVertexBuffer[LLModel::NUM_LODS].clear(); replaced_base_model = true; } } @@ -1132,7 +1127,7 @@ void LLModelPreview::loadModelCallback(S32 loaded_lod) mBaseModel = mModel[loaded_lod]; mBaseScene = mScene[loaded_lod]; - mVertexBuffer[5].clear(); + mVertexBuffer[LLModel::NUM_LODS].clear(); } else { @@ -1248,7 +1243,7 @@ void LLModelPreview::loadModelCallback(S32 loaded_lod) { if (!mBaseModel.empty()) { - const std::string& model_name = mBaseModel[0]->getName(); + std::string model_name = mBaseModel.front()->getName(); LLLineEditor* description_form = mFMP->getChild<LLLineEditor>("description_form"); if (description_form->getText().empty()) { @@ -1269,6 +1264,8 @@ void LLModelPreview::loadModelCallback(S32 loaded_lod) void LLModelPreview::resetPreviewTarget() { + LLMutexLock lock(this); + if (mModelLoader) { mPreviewTarget = (mModelLoader->mExtents[0] + mModelLoader->mExtents[1]) * 0.5f; @@ -1314,7 +1311,7 @@ void LLModelPreview::generateNormals() (*it)->generateNormals(angle_cutoff); } - mVertexBuffer[5].clear(); + mVertexBuffer[LLModel::NUM_LODS].clear(); } bool perform_copy = mModelFacesCopy[which_lod].empty(); @@ -2156,7 +2153,7 @@ void LLModelPreview::updateStatusMessages() S32 total_verts[LLModel::NUM_LODS]; S32 total_submeshes[LLModel::NUM_LODS]; - for (U32 i = 0; i < LLModel::NUM_LODS - 1; i++) + for (U32 i = 0; i < LLModel::NUM_LODS; i++) { total_tris[i] = 0; total_verts[i] = 0; @@ -2460,12 +2457,16 @@ void LLModelPreview::updateStatusMessages() } } - if (mModelNoErrors && mModelLoader) + if (mModelNoErrors) { - if (!mModelLoader->areTexturesReady() && mFMP->childGetValue("upload_textures").asBoolean()) + LLMutexLock lock(this); + if (mModelLoader) { - // Some textures are still loading, prevent upload until they are done - mModelNoErrors = false; + if (!mModelLoader->areTexturesReady() && mFMP->childGetValue("upload_textures").asBoolean()) + { + // Some textures are still loading, prevent upload until they are done + mModelNoErrors = false; + } } } @@ -2794,10 +2795,10 @@ void LLModelPreview::genBuffers(S32 lod, bool include_skin_weights) { LLModelLoader::model_list* model = NULL; - if (lod < 0 || lod > 4) + if (lod < 0 || lod >= LLModel::NUM_LODS) { model = &mBaseModel; - lod = 5; + lod = LLModel::NUM_LODS; } else { @@ -3034,8 +3035,9 @@ void LLModelPreview::loadedCallback( S32 lod, void* opaque) { - LLModelPreview* pPreview = static_cast< LLModelPreview* >(opaque); - if (pPreview && !LLModelPreview::sIgnoreLoadedCallback) + LLModelPreview* pPreview = static_cast<LLModelPreview*>(opaque); + LLMutexLock lock(pPreview); + if (pPreview && pPreview->mModelLoader && !LLModelPreview::sIgnoreLoadedCallback) { // Load loader's warnings into floater's log tab const LLSD out = pPreview->mModelLoader->logOut(); @@ -3056,7 +3058,9 @@ void LLModelPreview::loadedCallback( } const LLVOAvatar* avatarp = pPreview->getPreviewAvatar(); - if (avatarp) { // set up ground plane for possible rendering + if (avatarp && avatarp->mRoot && avatarp->mDrawable) + { + // set up ground plane for possible rendering const LLVector3 root_pos = avatarp->mRoot->getPosition(); const LLVector4a* ext = avatarp->mDrawable->getSpatialExtents(); const LLVector4a min = ext[0], max = ext[1]; @@ -3200,12 +3204,12 @@ bool LLModelPreview::render() LLMutexLock lock(this); mNeedsUpdate = false; - bool edges = mViewOption["show_edges"]; - bool joint_overrides = mViewOption["show_joint_overrides"]; - bool joint_positions = mViewOption["show_joint_positions"]; - bool skin_weight = mViewOption["show_skin_weight"]; - bool textures = mViewOption["show_textures"]; - bool physics = mViewOption["show_physics"]; + bool show_edges = mViewOption["show_edges"]; + bool show_joint_overrides = mViewOption["show_joint_overrides"]; + bool show_joint_positions = mViewOption["show_joint_positions"]; + bool show_skin_weight = mViewOption["show_skin_weight"]; + bool show_textures = mViewOption["show_textures"]; + bool show_physics = mViewOption["show_physics"]; S32 width = getWidth(); S32 height = getHeight(); @@ -3282,15 +3286,15 @@ bool LLModelPreview::render() fmp->childSetValue("upload_skin", true); mFirstSkinUpdate = false; upload_skin = true; - skin_weight = true; + show_skin_weight = true; mViewOption["show_skin_weight"] = true; } fmp->enableViewOption("show_skin_weight"); - fmp->setViewOptionEnabled("show_joint_overrides", skin_weight); - fmp->setViewOptionEnabled("show_joint_positions", skin_weight); + fmp->setViewOptionEnabled("show_joint_overrides", show_skin_weight); + fmp->setViewOptionEnabled("show_joint_positions", show_skin_weight); mFMP->childEnable("upload_skin"); - mFMP->childSetValue("show_skin_weight", skin_weight); + mFMP->childSetValue("show_skin_weight", show_skin_weight); } else if ((flags & LEGACY_RIG_FLAG_TOO_MANY_JOINTS) > 0) @@ -3313,11 +3317,12 @@ bool LLModelPreview::render() fmp->disableViewOption("show_joint_overrides"); fmp->disableViewOption("show_joint_positions"); - skin_weight = false; + show_skin_weight = false; mFMP->childSetValue("show_skin_weight", false); - fmp->setViewOptionEnabled("show_skin_weight", skin_weight); + fmp->setViewOptionEnabled("show_skin_weight", show_skin_weight); } } + //if (this) return TRUE; if (upload_skin && !has_skin_weights) { //can't upload skin weights if model has no skin weights @@ -3380,7 +3385,7 @@ bool LLModelPreview::render() F32 z_near = 0.001f; F32 z_far = mCameraDistance*10.0f + mPreviewScale.magVec() + mCameraOffset.magVec(); - if (skin_weight) + if (show_skin_weight) { target_pos = getPreviewAvatar()->getPositionAgent() + offset; z_near = 0.01f; @@ -3390,7 +3395,7 @@ bool LLModelPreview::render() refresh(); } - gObjectPreviewProgram.bind(skin_weight); + gObjectPreviewProgram.bind(show_skin_weight); gGL.loadIdentity(); gPipeline.enableLightsPreview(); @@ -3399,7 +3404,7 @@ bool LLModelPreview::render() LLQuaternion(mCameraYaw, LLVector3::z_axis); LLQuaternion av_rot = camera_rot; - F32 camera_distance = skin_weight ? SKIN_WEIGHT_CAMERA_DISTANCE : mCameraDistance; + F32 camera_distance = show_skin_weight ? SKIN_WEIGHT_CAMERA_DISTANCE : mCameraDistance; LLViewerCamera::getInstance()->setOriginAndLookAt( target_pos + ((LLVector3(camera_distance, 0.f, 0.f) + offset) * av_rot), // camera LLVector3::z_axis, // up @@ -3415,9 +3420,9 @@ bool LLModelPreview::render() gGL.pushMatrix(); gGL.color4fv(PREVIEW_EDGE_COL.mV); - if (!mBaseModel.empty() && mVertexBuffer[5].empty()) + if (!mBaseModel.empty() && mVertexBuffer[LLModel::NUM_LODS].empty()) { - genBuffers(-1, skin_weight); + genBuffers(-1, show_skin_weight); //genBuffers(3); } @@ -3432,7 +3437,7 @@ bool LLModelPreview::render() if (!vb_vec.empty()) { const LLVertexBuffer* buff = vb_vec[0]; - regen = buff->hasDataType(LLVertexBuffer::TYPE_WEIGHT4) != skin_weight; + regen = buff->hasDataType(LLVertexBuffer::TYPE_WEIGHT4) != show_skin_weight; } else { @@ -3443,15 +3448,15 @@ bool LLModelPreview::render() if (regen) { - genBuffers(mPreviewLOD, skin_weight); + genBuffers(mPreviewLOD, show_skin_weight); } - if (physics && mVertexBuffer[LLModel::LOD_PHYSICS].empty()) + if (show_physics && mVertexBuffer[LLModel::LOD_PHYSICS].empty()) { genBuffers(LLModel::LOD_PHYSICS, false); } - if (!skin_weight) + if (!show_skin_weight) { for (LLMeshUploadThread::instance_list::iterator iter = mUploadData.begin(); iter != mUploadData.end(); ++iter) { @@ -3473,11 +3478,7 @@ bool LLModelPreview::render() auto num_models = mVertexBuffer[mPreviewLOD][model].size(); for (size_t i = 0; i < num_models; ++i) { - LLVertexBuffer* buffer = mVertexBuffer[mPreviewLOD][model][i]; - - buffer->setBuffer(); - - if (textures) + if (show_textures) { auto materialCnt = instance.mModel->mMaterialList.size(); if (i < materialCnt) @@ -3501,10 +3502,16 @@ bool LLModelPreview::render() gGL.diffuseColor4fv(PREVIEW_BASE_COL.mV); } + // Zero this variable for an obligatory buffer initialization + // See https://github.com/secondlife/viewer/issues/912 + LLVertexBuffer::sGLRenderBuffer = 0; + LLVertexBuffer* buffer = mVertexBuffer[mPreviewLOD][model][i]; + buffer->setBuffer(); buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0); + gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.diffuseColor4fv(PREVIEW_EDGE_COL.mV); - if (edges) + if (show_edges) { glLineWidth(PREVIEW_EDGE_WIDTH); glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); @@ -3517,7 +3524,7 @@ bool LLModelPreview::render() gGL.popMatrix(); } - if (physics) + if (show_physics) { glClear(GL_DEPTH_BUFFER_BIT); @@ -3621,11 +3628,13 @@ bool LLModelPreview::render() if (pass > 0){ for (size_t i = 0; i < num_models; ++i) { - LLVertexBuffer* buffer = mVertexBuffer[LLModel::LOD_PHYSICS][model][i]; - gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.diffuseColor4fv(PREVIEW_PSYH_FILL_COL.mV); + // Zero this variable for an obligatory buffer initialization + // See https://github.com/secondlife/viewer/issues/912 + LLVertexBuffer::sGLRenderBuffer = 0; + LLVertexBuffer* buffer = mVertexBuffer[LLModel::LOD_PHYSICS][model][i]; buffer->setBuffer(); buffer->drawRange(LLRender::TRIANGLES, 0, buffer->getNumVerts() - 1, buffer->getNumIndices(), 0); @@ -3685,10 +3694,11 @@ bool LLModelPreview::render() auto num_models = mVertexBuffer[LLModel::LOD_PHYSICS][model].size(); for (size_t v = 0; v < num_models; ++v) { + // Zero this variable for an obligatory buffer initialization + // See https://github.com/secondlife/viewer/issues/912 + LLVertexBuffer::sGLRenderBuffer = 0; LLVertexBuffer* buffer = mVertexBuffer[LLModel::LOD_PHYSICS][model][v]; - buffer->setBuffer(); - LLStrider<LLVector3> pos_strider; buffer->getVertexStrider(pos_strider, 0); LLVector4a* pos = (LLVector4a*)pos_strider.get(); @@ -3752,7 +3762,7 @@ bool LLModelPreview::render() U32 joint_count = LLSkinningUtil::getMeshJointCount(skin); auto bind_count = skin->mAlternateBindMatrix.size(); - if (joint_overrides + if (show_joint_overrides && bind_count > 0 && joint_count == bind_count) { @@ -3797,14 +3807,12 @@ bool LLModelPreview::render() for (U32 i = 0, e = static_cast<U32>(mVertexBuffer[mPreviewLOD][model].size()); i < e; ++i) { - LLVertexBuffer* buffer = mVertexBuffer[mPreviewLOD][model][i]; - model->mSkinInfo.updateHash(); LLRenderPass::uploadMatrixPalette(mPreviewAvatar, &model->mSkinInfo); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - if (textures) + if (show_textures) { auto materialCnt = instance.mModel->mMaterialList.size(); if (i < materialCnt) @@ -3828,10 +3836,14 @@ bool LLModelPreview::render() gGL.diffuseColor4fv(PREVIEW_BASE_COL.mV); } + // Zero this variable for an obligatory buffer initialization + // See https://github.com/secondlife/viewer/issues/912 + LLVertexBuffer::sGLRenderBuffer = 0; + LLVertexBuffer* buffer = mVertexBuffer[mPreviewLOD][model][i]; buffer->setBuffer(); buffer->draw(LLRender::TRIANGLES, buffer->getNumIndices(), 0); - if (edges) + if (show_edges) { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gGL.diffuseColor4fv(PREVIEW_EDGE_COL.mV); @@ -3846,7 +3858,7 @@ bool LLModelPreview::render() } } - if (joint_positions) + if (show_joint_positions) { LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; if (shader) diff --git a/indra/newview/llnetmap.cpp b/indra/newview/llnetmap.cpp index 3f370b1ab5..483ddcdd63 100644 --- a/indra/newview/llnetmap.cpp +++ b/indra/newview/llnetmap.cpp @@ -128,7 +128,7 @@ LLNetMap::~LLNetMap() bool LLNetMap::postBuild() { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar commitRegistrar; + LLUICtrl::ScopedRegistrarHelper commitRegistrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enableRegistrar; enableRegistrar.add("Minimap.Zoom.Check", boost::bind(&LLNetMap::isZoomChecked, this, _2)); @@ -298,15 +298,22 @@ void LLNetMap::draw() // Draw using texture. gGL.getTexUnit(0)->bind(regionp->getLand().getSTexture()); - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); + { gGL.texCoord2f(0.f, 1.f); gGL.vertex2f(left, top); gGL.texCoord2f(0.f, 0.f); gGL.vertex2f(left, bottom); gGL.texCoord2f(1.f, 0.f); gGL.vertex2f(right, bottom); + + gGL.texCoord2f(0.f, 1.f); + gGL.vertex2f(left, top); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex2f(right, bottom); gGL.texCoord2f(1.f, 1.f); gGL.vertex2f(right, top); + } gGL.end(); gGL.flush(); @@ -347,15 +354,22 @@ void LLNetMap::draw() F32 image_half_width = 0.5f*mObjectMapPixels; F32 image_half_height = 0.5f*mObjectMapPixels; - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); + { gGL.texCoord2f(0.f, 1.f); gGL.vertex2f(map_center_agent.mV[VX] - image_half_width, image_half_height + map_center_agent.mV[VY]); gGL.texCoord2f(0.f, 0.f); gGL.vertex2f(map_center_agent.mV[VX] - image_half_width, map_center_agent.mV[VY] - image_half_height); gGL.texCoord2f(1.f, 0.f); gGL.vertex2f(image_half_width + map_center_agent.mV[VX], map_center_agent.mV[VY] - image_half_height); + + gGL.texCoord2f(0.f, 1.f); + gGL.vertex2f(map_center_agent.mV[VX] - image_half_width, image_half_height + map_center_agent.mV[VY]); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex2f(image_half_width + map_center_agent.mV[VX], map_center_agent.mV[VY] - image_half_height); gGL.texCoord2f(1.f, 1.f); gGL.vertex2f(image_half_width + map_center_agent.mV[VX], image_half_height + map_center_agent.mV[VY]); + } gGL.end(); for (LLWorld::region_list_t::const_iterator iter = LLWorld::getInstance()->getRegionList().begin(); diff --git a/indra/newview/lloutfitgallery.cpp b/indra/newview/lloutfitgallery.cpp index 72fb9464d8..dba294cef4 100644 --- a/indra/newview/lloutfitgallery.cpp +++ b/indra/newview/lloutfitgallery.cpp @@ -1157,7 +1157,7 @@ void LLOutfitGalleryItem::setDefaultImage() LLContextMenu* LLOutfitGalleryContextMenu::createMenu() { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::ScopedRegistrarHelper registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; LLUUID selected_id = mUUIDs.front(); @@ -1169,8 +1169,8 @@ LLContextMenu* LLOutfitGalleryContextMenu::createMenu() boost::bind(&LLAppearanceMgr::takeOffOutfit, &LLAppearanceMgr::instance(), selected_id)); registrar.add("Outfit.Edit", boost::bind(editOutfit)); registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id)); - registrar.add("Outfit.Delete", boost::bind(LLOutfitGallery::onRemoveOutfit, selected_id)); - registrar.add("Outfit.Create", boost::bind(&LLOutfitGalleryContextMenu::onCreate, this, _2)); + registrar.add("Outfit.Delete", boost::bind(LLOutfitGallery::onRemoveOutfit, selected_id), LLUICtrl::cb_info::UNTRUSTED_BLOCK); + registrar.add("Outfit.Create", boost::bind(&LLOutfitGalleryContextMenu::onCreate, this, _2), LLUICtrl::cb_info::UNTRUSTED_BLOCK); registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitGalleryContextMenu::onThumbnail, this, selected_id)); registrar.add("Outfit.Save", boost::bind(&LLOutfitGalleryContextMenu::onSave, this, selected_id)); enable_registrar.add("Outfit.OnEnable", boost::bind(&LLOutfitGalleryContextMenu::onEnable, this, _2)); diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp index 0f5f7aebf8..0233a22c2a 100644 --- a/indra/newview/lloutfitslist.cpp +++ b/indra/newview/lloutfitslist.cpp @@ -924,8 +924,8 @@ void LLOutfitListBase::onIdleRefreshList() if (cat) { std::string name = cat->getName(); - updateChangedCategoryName(cat, name); - } + updateChangedCategoryName(cat, name); + } curent_time = LLTimer::getTotalSeconds(); if (curent_time >= end_time) @@ -1053,7 +1053,7 @@ void LLOutfitListBase::deselectOutfit(const LLUUID& category_id) LLContextMenu* LLOutfitContextMenu::createMenu() { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::ScopedRegistrarHelper registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; LLUUID selected_id = mUUIDs.front(); @@ -1064,8 +1064,8 @@ LLContextMenu* LLOutfitContextMenu::createMenu() registrar.add("Outfit.TakeOff", boost::bind(&LLAppearanceMgr::takeOffOutfit, &LLAppearanceMgr::instance(), selected_id)); registrar.add("Outfit.Edit", boost::bind(editOutfit)); - registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id)); - registrar.add("Outfit.Delete", boost::bind(&LLOutfitListBase::removeSelected, mOutfitList)); + registrar.add("Outfit.Rename", boost::bind(renameOutfit, selected_id), LLUICtrl::cb_info::UNTRUSTED_BLOCK); + registrar.add("Outfit.Delete", boost::bind(&LLOutfitListBase::removeSelected, mOutfitList), LLUICtrl::cb_info::UNTRUSTED_BLOCK); registrar.add("Outfit.Thumbnail", boost::bind(&LLOutfitContextMenu::onThumbnail, this, selected_id)); registrar.add("Outfit.Save", boost::bind(&LLOutfitContextMenu::onSave, this, selected_id)); @@ -1163,7 +1163,7 @@ LLOutfitListGearMenuBase::LLOutfitListGearMenuBase(LLOutfitListBase* olist) { llassert_always(mOutfitList); - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::ScopedRegistrarHelper registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; registrar.add("Gear.Wear", boost::bind(&LLOutfitListGearMenuBase::onWear, this)); diff --git a/indra/newview/llpanelblockedlist.cpp b/indra/newview/llpanelblockedlist.cpp index 7d55ba3265..01d4f426de 100644 --- a/indra/newview/llpanelblockedlist.cpp +++ b/indra/newview/llpanelblockedlist.cpp @@ -62,7 +62,7 @@ const std::string BLOCKED_PARAM_NAME = "blocked_to_select"; LLPanelBlockedList::LLPanelBlockedList() : LLPanel() { - mCommitCallbackRegistrar.add("Block.Action", boost::bind(&LLPanelBlockedList::onCustomAction, this, _2)); + mCommitCallbackRegistrar.add("Block.Action", { boost::bind(&LLPanelBlockedList::onCustomAction, this, _2), cb_info::UNTRUSTED_BLOCK }); mEnableCallbackRegistrar.add("Block.Check", boost::bind(&LLPanelBlockedList::isActionChecked, this, _2)); } diff --git a/indra/newview/llpaneleditwearable.cpp b/indra/newview/llpaneleditwearable.cpp index 282b6d4a0a..a90c17f56e 100644 --- a/indra/newview/llpaneleditwearable.cpp +++ b/indra/newview/llpaneleditwearable.cpp @@ -642,8 +642,8 @@ LLPanelEditWearable::LLPanelEditWearable() , mWearablePtr(NULL) , mWearableItem(NULL) { - mCommitCallbackRegistrar.add("ColorSwatch.Commit", boost::bind(&LLPanelEditWearable::onColorSwatchCommit, this, _1)); - mCommitCallbackRegistrar.add("TexturePicker.Commit", boost::bind(&LLPanelEditWearable::onTexturePickerCommit, this, _1)); + mCommitCallbackRegistrar.add("ColorSwatch.Commit", { boost::bind(&LLPanelEditWearable::onColorSwatchCommit, this, _1) }); + mCommitCallbackRegistrar.add("TexturePicker.Commit", { boost::bind(&LLPanelEditWearable::onTexturePickerCommit, this, _1) }); } //virtual diff --git a/indra/newview/llpanelemojicomplete.cpp b/indra/newview/llpanelemojicomplete.cpp index cb89a5910e..0d6ca1cb94 100644 --- a/indra/newview/llpanelemojicomplete.cpp +++ b/indra/newview/llpanelemojicomplete.cpp @@ -68,6 +68,9 @@ LLPanelEmojiComplete::LLPanelEmojiComplete(const LLPanelEmojiComplete::Params& p { LLScrollbar::Params sbparams; sbparams.orientation(LLScrollbar::VERTICAL); + sbparams.doc_size((S32)mTotalEmojis); + sbparams.doc_pos(0); + sbparams.page_size((S32)mVisibleEmojis); sbparams.change_callback([this](S32 index, LLScrollbar*) { onScrollbarChange(index); }); mScrollbar = LLUICtrlFactory::create<LLScrollbar>(sbparams); addChild(mScrollbar); @@ -280,8 +283,7 @@ void LLPanelEmojiComplete::onCommit() { if (mCurSelected < mTotalEmojis) { - LLSD value(wstring_to_utf8str(LLWString(1, mEmojis[mCurSelected].Character))); - setValue(value); + setValue(ll_convert_to<std::string>(mEmojis[mCurSelected].Character)); LLUICtrl::onCommit(); } } diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp index 544b6fbc9c..7a63c6e10d 100644 --- a/indra/newview/llpanelface.cpp +++ b/indra/newview/llpanelface.cpp @@ -470,7 +470,7 @@ LLPanelFace::LLPanelFace() mNeedMediaTitle(true) { USE_TEXTURE = LLTrans::getString("use_texture"); - mCommitCallbackRegistrar.add("PanelFace.menuDoToSelected", boost::bind(&LLPanelFace::menuDoToSelected, this, _2)); + mCommitCallbackRegistrar.add("PanelFace.menuDoToSelected", { boost::bind(&LLPanelFace::menuDoToSelected, this, _2) }); mEnableCallbackRegistrar.add("PanelFace.menuEnable", boost::bind(&LLPanelFace::menuEnableItem, this, _2)); } @@ -5199,4 +5199,3 @@ void LLPanelFace::LLSelectedTE::getMaxDiffuseRepeats(F32& repeats, bool& identic } max_diff_repeats_func; identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &max_diff_repeats_func, repeats ); } - diff --git a/indra/newview/llpanelface.h b/indra/newview/llpanelface.h index dfd3201533..4d2ce208b4 100644 --- a/indra/newview/llpanelface.h +++ b/indra/newview/llpanelface.h @@ -479,7 +479,7 @@ private: ReturnType (LLMaterial::* const MaterialGetFunc)() const > static void getTEMaterialValue(DataType& data_to_return, bool& identical,DataType default_value, bool has_tolerance = false, DataType tolerance = DataType()) { - DataType data_value; + DataType data_value{}; struct GetTEMaterialVal : public LLSelectedTEGetFunctor<DataType> { GetTEMaterialVal(DataType default_value) : _default(default_value) {} @@ -512,7 +512,7 @@ private: ReturnType (LLTextureEntry::* const TEGetFunc)() const > static void getTEValue(DataType& data_to_return, bool& identical, DataType default_value, bool has_tolerance = false, DataType tolerance = DataType()) { - DataType data_value; + DataType data_value {}; struct GetTEVal : public LLSelectedTEGetFunctor<DataType> { GetTEVal(DataType default_value) : _default(default_value) {} @@ -707,4 +707,3 @@ public: }; #endif - diff --git a/indra/newview/llpanelgroupbulk.cpp b/indra/newview/llpanelgroupbulk.cpp index 433db74cda..8032e207cd 100644 --- a/indra/newview/llpanelgroupbulk.cpp +++ b/indra/newview/llpanelgroupbulk.cpp @@ -56,6 +56,7 @@ LLPanelGroupBulkImpl::LLPanelGroupBulkImpl(const LLUUID& group_id) : mGroupID(group_id), mBulkAgentList(NULL), mOKButton(NULL), + mAddButton(nullptr), mRemoveButton(NULL), mGroupName(NULL), mLoadingText(), @@ -79,29 +80,18 @@ LLPanelGroupBulkImpl::~LLPanelGroupBulkImpl() } } -// static -void LLPanelGroupBulkImpl::callbackClickAdd(void* userdata) +void LLPanelGroupBulkImpl::callbackClickAdd(LLPanelGroupBulk* panelp) { - if (LLPanelGroupBulk* panelp = (LLPanelGroupBulk*)userdata) - { - // Right now this is hard coded with some knowledge that it is part - // of a floater since the avatar picker needs to be added as a dependent - // floater to the parent floater. - // Soon the avatar picker will be embedded into this panel - // instead of being it's own separate floater. But that is next week. - // This will do for now. -jwolk May 10, 2006 - LLView* button = panelp->findChild<LLButton>("add_button"); - LLFloater* root_floater = gFloaterView->getParentFloater(panelp); - LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show( - [&](const uuid_vec_t& agent_ids, const std::vector<LLAvatarName>&) - { - panelp->mImplementation->addUsers(agent_ids); - }, true, false, false, root_floater->getName(), button); - if (picker) + LLFloater* root_floater = gFloaterView->getParentFloater(panelp); + LLFloaterAvatarPicker* picker = LLFloaterAvatarPicker::show( + [this](const uuid_vec_t& agent_ids, const std::vector<LLAvatarName>&) { - root_floater->addDependentFloater(picker); - LLGroupMgr::getInstance()->sendCapGroupMembersRequest(panelp->mImplementation->mGroupID); - } + addUsers(agent_ids); + }, true, false, false, root_floater->getName(), mAddButton); + if (picker) + { + root_floater->addDependentFloater(picker); + LLGroupMgr::getInstance()->sendCapGroupMembersRequest(mGroupID); } } diff --git a/indra/newview/llpanelgroupbulkban.cpp b/indra/newview/llpanelgroupbulkban.cpp index 3c764887a6..1d3edad0f3 100644 --- a/indra/newview/llpanelgroupbulkban.cpp +++ b/indra/newview/llpanelgroupbulkban.cpp @@ -68,35 +68,26 @@ bool LLPanelGroupBulkBan::postBuild() mImplementation->mBulkAgentList->setCommitCallback(LLPanelGroupBulkImpl::callbackSelect, mImplementation); } - LLButton* button = getChild<LLButton>("add_button", recurse); - if ( button ) + mImplementation->mAddButton = getChild<LLButton>("add_button", recurse); + // default to opening avatarpicker automatically + mImplementation->mAddButton->setClickedCallback( + [this](LLUICtrl* ctrl, const LLSD& param) { - // default to opening avatarpicker automatically - // (*impl::callbackClickAdd)((void*)this); - button->setClickedCallback(LLPanelGroupBulkImpl::callbackClickAdd, this); - } + mImplementation->callbackClickAdd(this); + }); mImplementation->mRemoveButton = getChild<LLButton>("remove_button", recurse); - if ( mImplementation->mRemoveButton ) - { - mImplementation->mRemoveButton->setClickedCallback(LLPanelGroupBulkImpl::callbackClickRemove, mImplementation); - mImplementation->mRemoveButton->setEnabled(false); - } + mImplementation->mRemoveButton->setClickedCallback(LLPanelGroupBulkImpl::callbackClickRemove, mImplementation); + mImplementation->mRemoveButton->setEnabled(false); mImplementation->mOKButton = getChild<LLButton>("ban_button", recurse); - if ( mImplementation->mOKButton ) - { - mImplementation->mOKButton->setClickedCallback(LLPanelGroupBulkBan::callbackClickSubmit, this); - mImplementation->mOKButton->setEnabled(false); - } + mImplementation->mOKButton->setClickedCallback(LLPanelGroupBulkBan::callbackClickSubmit, this); + mImplementation->mOKButton->setEnabled(false); - button = getChild<LLButton>("cancel_button", recurse); - if ( button ) - { - button->setClickedCallback(LLPanelGroupBulkImpl::callbackClickCancel, mImplementation); - } + LLButton* button = getChild<LLButton>("cancel_button", recurse); + button->setClickedCallback(LLPanelGroupBulkImpl::callbackClickCancel, mImplementation); mImplementation->mTooManySelected = getString("ban_selection_too_large"); mImplementation->mBanNotPermitted = getString("ban_not_permitted"); diff --git a/indra/newview/llpanelgroupbulkimpl.h b/indra/newview/llpanelgroupbulkimpl.h index 5a479f8117..5515bd6d9a 100644 --- a/indra/newview/llpanelgroupbulkimpl.h +++ b/indra/newview/llpanelgroupbulkimpl.h @@ -44,7 +44,7 @@ public: LLPanelGroupBulkImpl(const LLUUID& group_id); ~LLPanelGroupBulkImpl(); - static void callbackClickAdd(void* userdata); + void callbackClickAdd(LLPanelGroupBulk* panelp); static void callbackClickRemove(void* userdata); static void callbackClickCancel(void* userdata); @@ -70,6 +70,7 @@ public: LLNameListCtrl* mBulkAgentList; LLButton* mOKButton; + LLButton* mAddButton; LLButton* mRemoveButton; LLTextBox* mGroupName; diff --git a/indra/newview/llpanellandmarks.cpp b/indra/newview/llpanellandmarks.cpp index fb7ccbfe4c..5a0cd1a823 100644 --- a/indra/newview/llpanellandmarks.cpp +++ b/indra/newview/llpanellandmarks.cpp @@ -458,10 +458,10 @@ void LLLandmarksPanel::initLandmarksPanel(LLPlacesInventoryPanel* inventory_list // List Commands Handlers void LLLandmarksPanel::initListCommandsHandlers() { - mCommitCallbackRegistrar.add("Places.LandmarksGear.Add.Action", boost::bind(&LLLandmarksPanel::onAddAction, this, _2)); - mCommitCallbackRegistrar.add("Places.LandmarksGear.CopyPaste.Action", boost::bind(&LLLandmarksPanel::onClipboardAction, this, _2)); - mCommitCallbackRegistrar.add("Places.LandmarksGear.Custom.Action", boost::bind(&LLLandmarksPanel::onCustomAction, this, _2)); - mCommitCallbackRegistrar.add("Places.LandmarksGear.Folding.Action", boost::bind(&LLLandmarksPanel::onFoldingAction, this, _2)); + mCommitCallbackRegistrar.add("Places.LandmarksGear.Add.Action", { boost::bind(&LLLandmarksPanel::onAddAction, this, _2) }); + mCommitCallbackRegistrar.add("Places.LandmarksGear.CopyPaste.Action", { boost::bind(&LLLandmarksPanel::onClipboardAction, this, _2) }); + mCommitCallbackRegistrar.add("Places.LandmarksGear.Custom.Action", { boost::bind(&LLLandmarksPanel::onCustomAction, this, _2) }); + mCommitCallbackRegistrar.add("Places.LandmarksGear.Folding.Action", { boost::bind(&LLLandmarksPanel::onFoldingAction, this, _2)} ); mEnableCallbackRegistrar.add("Places.LandmarksGear.Check", boost::bind(&LLLandmarksPanel::isActionChecked, this, _2)); mEnableCallbackRegistrar.add("Places.LandmarksGear.Enable", boost::bind(&LLLandmarksPanel::isActionEnabled, this, _2)); mGearLandmarkMenu = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>("menu_places_gear_landmark.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index ed80c8b732..cae11a942c 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -81,15 +81,16 @@ bool LLPanelLogin::sCredentialSet = false; LLPointer<LLCredential> load_user_credentials(std::string &user_key) { - if (gSecAPIHandler->hasCredentialMap("login_list", LLGridManager::getInstance()->getGrid())) + std::string grid{ LLGridManager::instance().getGrid() }; + if (gSecAPIHandler->hasCredentialMap("login_list", grid)) { // user_key should be of "name Resident" format - return gSecAPIHandler->loadFromCredentialMap("login_list", LLGridManager::getInstance()->getGrid(), user_key); + return gSecAPIHandler->loadFromCredentialMap("login_list", grid, user_key); } else { // legacy (or legacy^2, since it also tries to load from settings) - return gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); + return gSecAPIHandler->loadCredential(grid); } } @@ -223,7 +224,8 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, sendChildToBack(getChildView("forgot_password_text")); sendChildToBack(getChildView("sign_up_text")); - std::string current_grid = LLGridManager::getInstance()->getGrid(); + LLGridManager& gridmgr{ LLGridManager::instance() }; + std::string current_grid = gridmgr.getGrid(); if (!mFirstLoginThisInstall) { LLComboBox* favorites_combo = getChild<LLComboBox>("start_location_combo"); @@ -237,7 +239,7 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, // Load all of the grids, sorted, and then add a bar and the current grid at the top server_choice_combo->removeall(); - std::map<std::string, std::string> known_grids = LLGridManager::getInstance()->getKnownGrids(); + std::map<std::string, std::string> known_grids = gridmgr.getKnownGrids(); for (std::map<std::string, std::string>::iterator grid_choice = known_grids.begin(); grid_choice != known_grids.end(); grid_choice++) @@ -251,7 +253,7 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, server_choice_combo->sortByName(); LL_DEBUGS("AppInit") << "adding current " << current_grid << LL_ENDL; - server_choice_combo->add(LLGridManager::getInstance()->getGridLabel(), + server_choice_combo->add(gridmgr.getGridLabel(), current_grid, ADD_TOP); server_choice_combo->selectFirstItem(); @@ -760,6 +762,7 @@ void LLPanelLogin::onUpdateStartSLURL(const LLSLURL& new_start_slurl) LL_DEBUGS("AppInit")<<new_start_slurl.asString()<<LL_ENDL; LLComboBox* location_combo = sInstance->getChild<LLComboBox>("start_location_combo"); + LLGridManager& gridmgr{ LLGridManager::instance() }; /* * Determine whether or not the new_start_slurl modifies the grid. * @@ -774,17 +777,17 @@ void LLPanelLogin::onUpdateStartSLURL(const LLSLURL& new_start_slurl) { case LLSLURL::LOCATION: { - std::string slurl_grid = LLGridManager::getInstance()->getGrid(new_start_slurl.getGrid()); + std::string slurl_grid = gridmgr.getGrid(new_start_slurl.getGrid()); if ( ! slurl_grid.empty() ) // is that a valid grid? { - if ( slurl_grid != LLGridManager::getInstance()->getGrid() ) // new grid? + if ( slurl_grid != gridmgr.getGrid() ) // new grid? { // the slurl changes the grid, so update everything to match - LLGridManager::getInstance()->setGridChoice(slurl_grid); + gridmgr.setGridChoice(slurl_grid); // update the grid selector to match the slurl LLComboBox* server_combo = sInstance->getChild<LLComboBox>("server_combo"); - std::string server_label(LLGridManager::getInstance()->getGridLabel(slurl_grid)); + std::string server_label(gridmgr.getGridLabel(slurl_grid)); server_combo->setSimple(server_label); updateServer(); // to change the links and splash screen @@ -831,8 +834,7 @@ void LLPanelLogin::autologinToLocation(const LLSLURL& slurl) if ( LLPanelLogin::sInstance != NULL ) { - void* unused_parameter = 0; - LLPanelLogin::sInstance->onClickConnect(unused_parameter); + LLPanelLogin::sInstance->onClickConnect(false); } } @@ -872,7 +874,8 @@ void LLPanelLogin::loadLoginPage() { if (!sInstance) return; - LLURI login_page = LLURI(LLGridManager::getInstance()->getLoginPage()); + LLGridManager& gridmgr{ LLGridManager::instance() }; + LLURI login_page = LLURI(gridmgr.getLoginPage()); LLSD params(login_page.queryMap()); LL_DEBUGS("AppInit") << "login_page: " << login_page << LL_ENDL; @@ -899,7 +902,7 @@ void LLPanelLogin::loadLoginPage() params["channel"] = LLVersionInfo::instance().getChannel(); // Grid - params["grid"] = LLGridManager::getInstance()->getGridId(); + params["grid"] = gridmgr.getGridId(); // add OS info params["os"] = LLOSInfo::instance().getOSStringSimple(); @@ -916,7 +919,7 @@ void LLPanelLogin::loadLoginPage() login_page.path(), params)); - gViewerWindow->setMenuBackgroundColor(false, !LLGridManager::getInstance()->isInProductionGrid()); + gViewerWindow->setMenuBackgroundColor(false, !gridmgr.isInProductionGrid()); LLMediaCtrl* web_browser = sInstance->getChild<LLMediaCtrl>("login_html"); if (web_browser->getCurrentNavUrl() != login_uri.asString()) @@ -949,9 +952,10 @@ void LLPanelLogin::onClickConnect(bool commit_fields) // the grid definitions may come from a user-supplied grids.xml, so they may not be good LL_DEBUGS("AppInit")<<"grid "<<combo_val.asString()<<LL_ENDL; + LLGridManager& gridmgr{ LLGridManager::instance() }; try { - LLGridManager::getInstance()->setGridChoice(combo_val.asString()); + gridmgr.setGridChoice(combo_val.asString()); } catch (LLInvalidGridName ex) { @@ -984,7 +988,7 @@ void LLPanelLogin::onClickConnect(bool commit_fields) std::string identifier_type; cred->identifierType(identifier_type); LLSD allowed_credential_types; - LLGridManager::getInstance()->getLoginIdentifierTypes(allowed_credential_types); + gridmgr.getLoginIdentifierTypes(allowed_credential_types); // check the typed in credential type against the credential types expected by the server. for(LLSD::array_iterator i = allowed_credential_types.beginArray(); @@ -1133,6 +1137,7 @@ void LLPanelLogin::updateServer() { if (sInstance) { + LLGridManager& gridmgr{ LLGridManager::instance() }; try { // if they've selected another grid, we should load the credentials @@ -1152,7 +1157,7 @@ void LLPanelLogin::updateServer() // populate dropbox and setFields // Note: following call is related to initializeLoginInfo() - LLPointer<LLCredential> credential = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); + LLPointer<LLCredential> credential = gSecAPIHandler->loadCredential(gridmgr.getGrid()); sInstance->populateUserList(credential); // restore creds @@ -1165,12 +1170,12 @@ void LLPanelLogin::updateServer() { // populate dropbox and setFields // Note: following call is related to initializeLoginInfo() - LLPointer<LLCredential> credential = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); + LLPointer<LLCredential> credential = gSecAPIHandler->loadCredential(gridmgr.getGrid()); sInstance->populateUserList(credential); } // update the login panel links - bool system_grid = LLGridManager::getInstance()->isSystemGrid(); + bool system_grid = gridmgr.isSystemGrid(); // Want to vanish not only create_new_account_btn, but also the // title text over it, so turn on/off the whole layout_panel element. @@ -1219,11 +1224,12 @@ void LLPanelLogin::populateUserList(LLPointer<LLCredential> credential) getChild<LLUICtrl>("password_edit")->setValue(std::string()); mUsernameLength = 0; mPasswordLength = 0; + std::string grid{ LLGridManager::instance().getGrid() }; - if (gSecAPIHandler->hasCredentialMap("login_list", LLGridManager::getInstance()->getGrid())) + if (gSecAPIHandler->hasCredentialMap("login_list", grid)) { LLSecAPIHandler::credential_map_t credencials; - gSecAPIHandler->loadCredentialMap("login_list", LLGridManager::getInstance()->getGrid(), credencials); + gSecAPIHandler->loadCredentialMap("login_list", grid, credencials); LLSecAPIHandler::credential_map_t::iterator cr_iter = credencials.begin(); LLSecAPIHandler::credential_map_t::iterator cr_end = credencials.end(); @@ -1278,7 +1284,8 @@ void LLPanelLogin::onSelectServer() LLComboBox* server_combo = getChild<LLComboBox>("server_combo"); LLSD server_combo_val = server_combo->getSelectedValue(); LL_INFOS("AppInit") << "grid "<<server_combo_val.asString()<< LL_ENDL; - LLGridManager::getInstance()->setGridChoice(server_combo_val.asString()); + auto& gridmgr{ LLGridManager::instance() }; + gridmgr.setGridChoice(server_combo_val.asString()); addFavoritesToStartLocation(); /* @@ -1308,7 +1315,7 @@ void LLPanelLogin::onSelectServer() LLSLURL slurl(location); // generata a slurl from the location combo contents if (location.empty() || (slurl.getType() == LLSLURL::LOCATION - && slurl.getGrid() != LLGridManager::getInstance()->getGrid()) + && slurl.getGrid() != gridmgr.getGrid()) ) { // the grid specified by the location is not this one, so clear the combo @@ -1338,7 +1345,7 @@ bool LLPanelLogin::getShowFavorites() } // static -std::string LLPanelLogin::getUserName(LLPointer<LLCredential> &cred) +std::string LLPanelLogin::getUserName(const LLPointer<LLCredential> &cred) { if (cred.isNull()) { diff --git a/indra/newview/llpanellogin.h b/indra/newview/llpanellogin.h index a1bf25fb05..ce708e7a0e 100644 --- a/indra/newview/llpanellogin.h +++ b/indra/newview/llpanellogin.h @@ -85,7 +85,7 @@ public: static bool getShowFavorites(); // extract name from cred in a format apropriate for username field - static std::string getUserName(LLPointer<LLCredential> &cred); + static std::string getUserName(const LLPointer<LLCredential> &cred); private: friend class LLPanelLoginListener; diff --git a/indra/newview/llpanelloginlistener.cpp b/indra/newview/llpanelloginlistener.cpp index fb6f034b7f..aeaf2d1d00 100644 --- a/indra/newview/llpanelloginlistener.cpp +++ b/indra/newview/llpanelloginlistener.cpp @@ -32,9 +32,19 @@ #include "llpanelloginlistener.h" // STL headers // std headers +#include <iomanip> +#include <memory> // external library headers // other Linden headers +#include "llcombobox.h" #include "llpanellogin.h" +#include "llsdutil.h" +#include "llsecapi.h" +#include "llslurl.h" +#include "llstartup.h" +#include "lluictrl.h" +#include "llviewernetwork.h" +#include "stringize.h" LLPanelLoginListener::LLPanelLoginListener(LLPanelLogin* instance): LLEventAPI("LLPanelLogin", "Access to LLPanelLogin methods"), @@ -43,9 +53,195 @@ LLPanelLoginListener::LLPanelLoginListener(LLPanelLogin* instance): add("onClickConnect", "Pretend user clicked the \"Log In\" button", &LLPanelLoginListener::onClickConnect); + add("login", + "Login to Second Life with saved credentials.\n" + "Pass [\"username\"] to select credentials previously saved with that username.\n" + "Pass [\"slurl\"] to specify target location.\n" + "Pass [\"grid\"] to select one of the valid grids in grids.xml.", + &LLPanelLoginListener::login, + llsd::map("reply", LLSD::String())); + add("savedLogins", + "Return \"logins\" as a list of {} saved login names.\n" + "Pass [\"grid\"] to select one of the valid grids in grids.xml.", + &LLPanelLoginListener::savedLogins, + llsd::map("reply", LLSD::String())); } void LLPanelLoginListener::onClickConnect(const LLSD&) const { mPanel->onClickConnect(false); } + +namespace +{ + + std::string gridkey(const std::string& grid) + { + if (grid.find('.') != std::string::npos) + { + return grid; + } + else + { + return stringize("util.", grid, ".lindenlab.com"); + } + } + +} // anonymous namespace + +void LLPanelLoginListener::login(const LLSD& args) const +{ + // Although we require a "reply" key, don't instantiate a Response object: + // if things go well, response to our invoker will come later, once the + // viewer gets the response from the login server. + std::string username(args["username"]), slurlstr(args["slurl"]), grid(args["grid"]); + LL_DEBUGS("AppInit") << "Login with saved credentials"; + if (! username.empty()) + { + LL_CONT << " for user " << std::quoted(username); + } + if (! slurlstr.empty()) + { + LL_CONT << " to location " << std::quoted(slurlstr); + } + if (! grid.empty()) + { + LL_CONT << " on grid " << std::quoted(grid); + } + LL_CONT << LL_ENDL; + + // change grid first, allowing slurl to override + auto& gridmgr{ LLGridManager::instance() }; + if (! grid.empty()) + { + grid = gridkey(grid); + // setGridChoice() can throw LLInvalidGridName -- but if so, let it + // propagate, trusting that LLEventAPI will catch it and send an + // appropriate reply. + auto server_combo = mPanel->getChild<LLComboBox>("server_combo"); + server_combo->setSimple(gridmgr.getGridLabel(grid)); + LLPanelLogin::updateServer(); + } + if (! slurlstr.empty()) + { + LLSLURL slurl(slurlstr); + if (! slurl.isSpatial()) + { + // don't bother with LLTHROW() because we expect LLEventAPI to + // catch and report back to invoker + throw DispatchError(stringize("Invalid start SLURL ", std::quoted(slurlstr))); + } + // Bypass LLStartUp::setStartSLURL() because, after validating as + // above, it bounces right back to LLPanelLogin::onUpdateStartSLURL(). + // It also sets the "NextLoginLocation" user setting, but as with grid, + // we don't yet know whether that's desirable for scripted login. + LLPanelLogin::onUpdateStartSLURL(slurl); + } + if (! username.empty()) + { + // Transform (e.g.) "Nat Linden" to the internal form expected by + // loadFromCredentialMap(), e.g. "nat_linden" + LLStringUtil::toLower(username); + LLStringUtil::replaceChar(username, ' ', '_'); + LLStringUtil::replaceChar(username, '.', '_'); + + // call gridmgr.getGrid() because our caller didn't necessarily pass + // ["grid"] -- or it might have been overridden by the SLURL + auto cred = gSecAPIHandler->loadFromCredentialMap("login_list", + gridmgr.getGrid(), + username); + LLPanelLogin::setFields(cred); + } + + if (mPanel->getChild<LLUICtrl>("username_combo")->getValue().asString().empty() || + mPanel->getChild<LLUICtrl>("password_edit") ->getValue().asString().empty()) + { + // as above, let LLEventAPI report back to invoker + throw DispatchError(stringize("Unrecognized username ", std::quoted(username))); + } + + // Here we're about to trigger actual login, which is all we can do in + // this method. All subsequent responses must come via the "login" + // LLEventPump. + LLEventPumps::instance().obtain("login").listen( + "Lua login", + [args] + (const LLBoundListener& conn, const LLSD& update) + { + LLSD response{ llsd::map("ok", false) }; + if (update["change"] == "connect") + { + // success! + response["ok"] = true; + } + else if (update["change"] == "disconnect") + { + // Does this ever actually happen? + // LLLoginInstance::handleDisconnect() says it's a placeholder. + response["error"] = "login disconnect"; + } + else if (update["change"] == "fail.login") + { + // why? + // LLLoginInstance::handleLoginFailure() has special code to + // handle "tos", "update" and "mfa_challenge". Do not respond + // automatically because these things are SUPPOSED to engage + // the user. + // Copy specific response fields because there's an enormous + // chunk of stuff that comes back on success. + LLSD data{ update["data"] }; + const char* fields[] = { + "reason", + "error_code" + }; + for (auto field : fields) + { + response[field] = data[field]; + } + response["error"] = update["message"]; + } + else + { + // Ignore all other "change" values: LLLogin sends multiple update + // events. Only a few of them (above) indicate completion. + return; + } + // For all the completion cases, disconnect from the "login" + // LLEventPump. + conn.disconnect(); + // and at last, send response to the original invoker + sendReply(response, args); + }); + + // Turn the Login crank. + mPanel->onClickConnect(false); +} + +LLSD LLPanelLoginListener::savedLogins(const LLSD& args) const +{ + LL_INFOS() << "called with " << args << LL_ENDL; + std::string grid = (args.has("grid")? gridkey(args["grid"].asString()) + : LLGridManager::instance().getGrid()); + if (! gSecAPIHandler->hasCredentialMap("login_list", grid)) + { + LL_INFOS() << "no creds for " << grid << LL_ENDL; + return llsd::map("error", + stringize("No saved logins for grid ", std::quoted(grid))); + } + LLSecAPIHandler::credential_map_t creds; + gSecAPIHandler->loadCredentialMap("login_list", grid, creds); + auto result = llsd::map( + "logins", + llsd::toArray( + creds, + [](const auto& pair) + { + return llsd::map( + "name", + LLPanelLogin::getUserName(pair.second), + "key", + pair.first); + })); + LL_INFOS() << "returning creds " << result << LL_ENDL; + return result; +} diff --git a/indra/newview/llpanelloginlistener.h b/indra/newview/llpanelloginlistener.h index b552ccd5b0..f2f4d70ff9 100644 --- a/indra/newview/llpanelloginlistener.h +++ b/indra/newview/llpanelloginlistener.h @@ -40,6 +40,8 @@ public: private: void onClickConnect(const LLSD&) const; + void login(const LLSD&) const; + LLSD savedLogins(const LLSD&) const; LLPanelLogin* mPanel; }; diff --git a/indra/newview/llpanelmaininventory.cpp b/indra/newview/llpanelmaininventory.cpp index 377af4384a..48f32a05ba 100644 --- a/indra/newview/llpanelmaininventory.cpp +++ b/indra/newview/llpanelmaininventory.cpp @@ -78,9 +78,9 @@ static LLPanelInjector<LLPanelMainInventory> t_inventory("panel_main_inventory") class LLFloaterInventoryFinder : public LLFloater { public: - LLFloaterInventoryFinder( LLPanelMainInventory* inventory_view); - virtual void draw(); - /*virtual*/ bool postBuild(); + LLFloaterInventoryFinder(LLPanelMainInventory* inventory_view); + void draw(); + bool postBuild(); void changeFilter(LLInventoryFilter* filter); void updateElementsFromFilter(); bool getCheckShowEmpty(); @@ -90,17 +90,35 @@ public: void onCreatorSelfFilterCommit(); void onCreatorOtherFilterCommit(); - static void onTimeAgo(LLUICtrl*, void *); - static void onCloseBtn(void* user_data); - static void selectAllTypes(void* user_data); - static void selectNoTypes(void* user_data); + void onTimeAgo(); + void onCloseBtn(); + void selectAllTypes(); + void selectNoTypes(); private: - LLPanelMainInventory* mPanelMainInventory; - LLSpinCtrl* mSpinSinceDays; - LLSpinCtrl* mSpinSinceHours; - LLCheckBoxCtrl* mCreatorSelf; - LLCheckBoxCtrl* mCreatorOthers; - LLInventoryFilter* mFilter; + LLPanelMainInventory* mPanelMainInventory{ nullptr }; + LLSpinCtrl* mSpinSinceDays{ nullptr }; + LLSpinCtrl* mSpinSinceHours{ nullptr }; + LLCheckBoxCtrl* mCreatorSelf{ nullptr }; + LLCheckBoxCtrl* mCreatorOthers{ nullptr }; + LLInventoryFilter* mFilter{ nullptr }; + + LLCheckBoxCtrl* mCheckAnimation{ nullptr }; + LLCheckBoxCtrl* mCheckCallingCard{ nullptr }; + LLCheckBoxCtrl* mCheckClothing{ nullptr }; + LLCheckBoxCtrl* mCheckGesture{ nullptr }; + LLCheckBoxCtrl* mCheckLandmark{ nullptr }; + LLCheckBoxCtrl* mCheckMaterial{ nullptr }; + LLCheckBoxCtrl* mCheckNotecard{ nullptr }; + LLCheckBoxCtrl* mCheckObject{ nullptr }; + LLCheckBoxCtrl* mCheckScript{ nullptr }; + LLCheckBoxCtrl* mCheckSounds{ nullptr }; + LLCheckBoxCtrl* mCheckTexture{ nullptr }; + LLCheckBoxCtrl* mCheckSnapshot{ nullptr }; + LLCheckBoxCtrl* mCheckSettings{ nullptr }; + LLCheckBoxCtrl* mCheckShowEmpty{ nullptr }; + LLCheckBoxCtrl* mCheckSinceLogoff{ nullptr }; + + LLRadioGroup* mRadioDateSearchDirection{ nullptr }; }; ///---------------------------------------------------------------------------- @@ -125,14 +143,16 @@ LLPanelMainInventory::LLPanelMainInventory(const LLPanel::Params& p) mGalleryRootUpdatedConnection() { // Menu Callbacks (non contex menus) - mCommitCallbackRegistrar.add("Inventory.DoToSelected", boost::bind(&LLPanelMainInventory::doToSelected, this, _2)); - mCommitCallbackRegistrar.add("Inventory.CloseAllFolders", boost::bind(&LLPanelMainInventory::closeAllFolders, this)); - mCommitCallbackRegistrar.add("Inventory.EmptyTrash", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH)); - mCommitCallbackRegistrar.add("Inventory.EmptyLostAndFound", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND)); - mCommitCallbackRegistrar.add("Inventory.DoCreate", boost::bind(&LLPanelMainInventory::doCreate, this, _2)); - mCommitCallbackRegistrar.add("Inventory.ShowFilters", boost::bind(&LLPanelMainInventory::toggleFindOptions, this)); - mCommitCallbackRegistrar.add("Inventory.ResetFilters", boost::bind(&LLPanelMainInventory::resetFilters, this)); - mCommitCallbackRegistrar.add("Inventory.SetSortBy", boost::bind(&LLPanelMainInventory::setSortBy, this, _2)); + mCommitCallbackRegistrar.add("Inventory.DoToSelected", { boost::bind(&LLPanelMainInventory::doToSelected, this, _2), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.CloseAllFolders", { boost::bind(&LLPanelMainInventory::closeAllFolders, this) }); + mCommitCallbackRegistrar.add("Inventory.EmptyTrash", { boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, + "ConfirmEmptyTrash", LLFolderType::FT_TRASH), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.EmptyLostAndFound", { boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, + "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.DoCreate", { boost::bind(&LLPanelMainInventory::doCreate, this, _2) }); + mCommitCallbackRegistrar.add("Inventory.ShowFilters", { boost::bind(&LLPanelMainInventory::toggleFindOptions, this) }); + mCommitCallbackRegistrar.add("Inventory.ResetFilters", { boost::bind(&LLPanelMainInventory::resetFilters, this) }); + mCommitCallbackRegistrar.add("Inventory.SetSortBy", { boost::bind(&LLPanelMainInventory::setSortBy, this, _2) }); mEnableCallbackRegistrar.add("Inventory.EnvironmentEnabled", [](LLUICtrl *, const LLSD &) { return LLPanelMainInventory::hasSettingsInventory(); }); mEnableCallbackRegistrar.add("Inventory.MaterialsEnabled", [](LLUICtrl *, const LLSD &) { return LLPanelMainInventory::hasMaterialsInventory(); }); @@ -734,7 +754,6 @@ bool LLPanelMainInventory::filtersVisible(void* user_data) void LLPanelMainInventory::onClearSearch() { bool initially_active = false; - LLFloater *finder = getFinder(); if (mActivePanel && (getActivePanel() != mWornItemsPanel)) { initially_active = mActivePanel->getFilter().isNotDefault(); @@ -743,9 +762,9 @@ void LLPanelMainInventory::onClearSearch() mActivePanel->setFilterLinks(LLInventoryFilter::FILTERLINK_INCLUDE_LINKS); } - if (finder) + if (LLFloaterInventoryFinder* finder = getFinder()) { - LLFloaterInventoryFinder::selectAllTypes(finder); + finder->selectAllTypes(); } // re-open folders that were initially open in case filter was active @@ -1145,36 +1164,53 @@ bool LLFloaterInventoryFinder::postBuild() const LLRect& viewrect = mPanelMainInventory->getRect(); setRect(LLRect(viewrect.mLeft - getRect().getWidth(), viewrect.mTop, viewrect.mLeft, viewrect.mTop - getRect().getHeight())); - childSetAction("All", selectAllTypes, this); - childSetAction("None", selectNoTypes, this); + childSetAction("All", [this](LLUICtrl*, const LLSD&) { selectAllTypes(); }); + childSetAction("None", [this](LLUICtrl*, const LLSD&) { selectNoTypes(); }); mSpinSinceHours = getChild<LLSpinCtrl>("spin_hours_ago"); - childSetCommitCallback("spin_hours_ago", onTimeAgo, this); + mSpinSinceHours->setCommitCallback([this](LLUICtrl*, const LLSD&) { onTimeAgo(); }); mSpinSinceDays = getChild<LLSpinCtrl>("spin_days_ago"); - childSetCommitCallback("spin_days_ago", onTimeAgo, this); + mSpinSinceDays->setCommitCallback([this](LLUICtrl*, const LLSD&) { onTimeAgo(); }); mCreatorSelf = getChild<LLCheckBoxCtrl>("check_created_by_me"); mCreatorOthers = getChild<LLCheckBoxCtrl>("check_created_by_others"); mCreatorSelf->setCommitCallback(boost::bind(&LLFloaterInventoryFinder::onCreatorSelfFilterCommit, this)); mCreatorOthers->setCommitCallback(boost::bind(&LLFloaterInventoryFinder::onCreatorOtherFilterCommit, this)); - childSetAction("Close", onCloseBtn, this); + mCheckAnimation = getChild<LLCheckBoxCtrl>("check_animation"); + mCheckCallingCard = getChild<LLCheckBoxCtrl>("check_calling_card"); + mCheckClothing = getChild<LLCheckBoxCtrl>("check_clothing"); + mCheckGesture = getChild<LLCheckBoxCtrl>("check_gesture"); + mCheckLandmark = getChild<LLCheckBoxCtrl>("check_landmark"); + mCheckMaterial = getChild<LLCheckBoxCtrl>("check_material"); + mCheckNotecard = getChild<LLCheckBoxCtrl>("check_notecard"); + mCheckObject = getChild<LLCheckBoxCtrl>("check_object"); + mCheckScript = getChild<LLCheckBoxCtrl>("check_script"); + mCheckSounds = getChild<LLCheckBoxCtrl>("check_sound"); + mCheckTexture = getChild<LLCheckBoxCtrl>("check_texture"); + mCheckSnapshot = getChild<LLCheckBoxCtrl>("check_snapshot"); + mCheckSettings = getChild<LLCheckBoxCtrl>("check_settings"); + mCheckShowEmpty = getChild<LLCheckBoxCtrl>("check_show_empty"); + mCheckSinceLogoff = getChild<LLCheckBoxCtrl>("check_since_logoff"); + + mRadioDateSearchDirection = getChild<LLRadioGroup>("date_search_direction"); + + childSetAction("Close", [this](LLUICtrl*, const LLSD&) { onCloseBtn(); }); updateElementsFromFilter(); + return true; } -void LLFloaterInventoryFinder::onTimeAgo(LLUICtrl *ctrl, void *user_data) -{ - LLFloaterInventoryFinder *self = (LLFloaterInventoryFinder *)user_data; - if (!self) return; - if ( self->mSpinSinceDays->get() || self->mSpinSinceHours->get() ) +void LLFloaterInventoryFinder::onTimeAgo() +{ + if (mSpinSinceDays->get() || mSpinSinceHours->get()) { - self->getChild<LLUICtrl>("check_since_logoff")->setValue(false); + mCheckSinceLogoff->setValue(false); - U32 days = (U32)self->mSpinSinceDays->get(); - U32 hours = (U32)self->mSpinSinceHours->get(); + U32 days = (U32)mSpinSinceDays->get(); + U32 hours = (U32)mSpinSinceHours->get(); if (hours >= 24) { // Try to handle both cases of spinner clicking and text input in a sensible fashion as best as possible. @@ -1190,11 +1226,11 @@ void LLFloaterInventoryFinder::onTimeAgo(LLUICtrl *ctrl, void *user_data) days = hours / 24; } hours = (U32)hours % 24; - self->mSpinSinceHours->setFocus(false); - self->mSpinSinceDays->setFocus(false); - self->mSpinSinceDays->set((F32)days); - self->mSpinSinceHours->set((F32)hours); - self->mSpinSinceHours->setFocus(true); + mSpinSinceHours->setFocus(false); + mSpinSinceDays->setFocus(false); + mSpinSinceDays->set((F32)days); + mSpinSinceHours->set((F32)hours); + mSpinSinceHours->setFocus(true); } } } @@ -1223,29 +1259,28 @@ void LLFloaterInventoryFinder::updateElementsFromFilter() // update the ui elements setTitle(mFilter->getName()); - getChild<LLUICtrl>("check_animation")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_ANIMATION)); - - getChild<LLUICtrl>("check_calling_card")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_CALLINGCARD)); - getChild<LLUICtrl>("check_clothing")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_WEARABLE)); - getChild<LLUICtrl>("check_gesture")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_GESTURE)); - getChild<LLUICtrl>("check_landmark")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_LANDMARK)); - getChild<LLUICtrl>("check_material")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_MATERIAL)); - getChild<LLUICtrl>("check_notecard")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_NOTECARD)); - getChild<LLUICtrl>("check_object")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_OBJECT)); - getChild<LLUICtrl>("check_script")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_LSL)); - getChild<LLUICtrl>("check_sound")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_SOUND)); - getChild<LLUICtrl>("check_texture")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_TEXTURE)); - getChild<LLUICtrl>("check_snapshot")->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_SNAPSHOT)); - getChild<LLUICtrl>("check_settings")->setValue((S32)(filter_types & 0x1 << LLInventoryType::IT_SETTINGS)); - getChild<LLUICtrl>("check_show_empty")->setValue(show_folders == LLInventoryFilter::SHOW_ALL_FOLDERS); - - getChild<LLUICtrl>("check_created_by_me")->setValue(show_created_by_me); - getChild<LLUICtrl>("check_created_by_others")->setValue(show_created_by_others); - - getChild<LLUICtrl>("check_since_logoff")->setValue(mFilter->isSinceLogoff()); + mCheckAnimation->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_ANIMATION)); + mCheckCallingCard->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_CALLINGCARD)); + mCheckClothing->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_WEARABLE)); + mCheckGesture->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_GESTURE)); + mCheckLandmark->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_LANDMARK)); + mCheckMaterial->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_MATERIAL)); + mCheckNotecard->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_NOTECARD)); + mCheckObject->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_OBJECT)); + mCheckScript->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_LSL)); + mCheckSounds->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_SOUND)); + mCheckTexture->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_TEXTURE)); + mCheckSnapshot->setValue((S32) (filter_types & 0x1 << LLInventoryType::IT_SNAPSHOT)); + mCheckSettings->setValue((S32)(filter_types & 0x1 << LLInventoryType::IT_SETTINGS)); + mCheckShowEmpty->setValue(show_folders == LLInventoryFilter::SHOW_ALL_FOLDERS); + + mCreatorSelf->setValue(show_created_by_me); + mCreatorOthers->setValue(show_created_by_others); + + mCheckSinceLogoff->setValue(mFilter->isSinceLogoff()); mSpinSinceHours->set((F32)(hours % 24)); mSpinSinceDays->set((F32)(hours / 24)); - getChild<LLRadioGroup>("date_search_direction")->setSelectedIndex(date_search_direction); + mRadioDateSearchDirection->setSelectedIndex(date_search_direction); } void LLFloaterInventoryFinder::draw() @@ -1253,80 +1288,80 @@ void LLFloaterInventoryFinder::draw() U64 filter = 0xffffffffffffffffULL; bool filtered_by_all_types = true; - if (!getChild<LLUICtrl>("check_animation")->getValue()) + if (!mCheckAnimation->getValue()) { filter &= ~(0x1 << LLInventoryType::IT_ANIMATION); filtered_by_all_types = false; } - if (!getChild<LLUICtrl>("check_calling_card")->getValue()) + if (!mCheckCallingCard->getValue()) { filter &= ~(0x1 << LLInventoryType::IT_CALLINGCARD); filtered_by_all_types = false; } - if (!getChild<LLUICtrl>("check_clothing")->getValue()) + if (!mCheckClothing->getValue()) { filter &= ~(0x1 << LLInventoryType::IT_WEARABLE); filtered_by_all_types = false; } - if (!getChild<LLUICtrl>("check_gesture")->getValue()) + if (!mCheckGesture->getValue()) { filter &= ~(0x1 << LLInventoryType::IT_GESTURE); filtered_by_all_types = false; } - if (!getChild<LLUICtrl>("check_landmark")->getValue()) + if (!mCheckLandmark->getValue()) { filter &= ~(0x1 << LLInventoryType::IT_LANDMARK); filtered_by_all_types = false; } - if (!getChild<LLUICtrl>("check_material")->getValue()) + if (!mCheckMaterial->getValue()) { filter &= ~(0x1 << LLInventoryType::IT_MATERIAL); filtered_by_all_types = false; } - if (!getChild<LLUICtrl>("check_notecard")->getValue()) + if (!mCheckNotecard->getValue()) { filter &= ~(0x1 << LLInventoryType::IT_NOTECARD); filtered_by_all_types = false; } - if (!getChild<LLUICtrl>("check_object")->getValue()) + if (!mCheckObject->getValue()) { filter &= ~(0x1 << LLInventoryType::IT_OBJECT); filter &= ~(0x1 << LLInventoryType::IT_ATTACHMENT); filtered_by_all_types = false; } - if (!getChild<LLUICtrl>("check_script")->getValue()) + if (!mCheckScript->getValue()) { filter &= ~(0x1 << LLInventoryType::IT_LSL); filtered_by_all_types = false; } - if (!getChild<LLUICtrl>("check_sound")->getValue()) + if (!mCheckSounds->getValue()) { filter &= ~(0x1 << LLInventoryType::IT_SOUND); filtered_by_all_types = false; } - if (!getChild<LLUICtrl>("check_texture")->getValue()) + if (!mCheckTexture->getValue()) { filter &= ~(0x1 << LLInventoryType::IT_TEXTURE); filtered_by_all_types = false; } - if (!getChild<LLUICtrl>("check_snapshot")->getValue()) + if (!mCheckSnapshot->getValue()) { filter &= ~(0x1 << LLInventoryType::IT_SNAPSHOT); filtered_by_all_types = false; } - if (!getChild<LLUICtrl>("check_settings")->getValue()) + if (!mCheckSettings->getValue()) { filter &= ~(0x1 << LLInventoryType::IT_SETTINGS); filtered_by_all_types = false; @@ -1444,65 +1479,56 @@ void LLFloaterInventoryFinder::onCreatorOtherFilterCommit() bool LLFloaterInventoryFinder::getCheckShowEmpty() { - return getChild<LLUICtrl>("check_show_empty")->getValue(); + return mCheckShowEmpty->getValue(); } bool LLFloaterInventoryFinder::getCheckSinceLogoff() { - return getChild<LLUICtrl>("check_since_logoff")->getValue(); + return mCheckSinceLogoff->getValue(); } U32 LLFloaterInventoryFinder::getDateSearchDirection() { - return getChild<LLRadioGroup>("date_search_direction")->getSelectedIndex(); + return mRadioDateSearchDirection->getSelectedIndex(); } -void LLFloaterInventoryFinder::onCloseBtn(void* user_data) +void LLFloaterInventoryFinder::onCloseBtn() { - LLFloaterInventoryFinder* finderp = (LLFloaterInventoryFinder*)user_data; - finderp->closeFloater(); + closeFloater(); } -// static -void LLFloaterInventoryFinder::selectAllTypes(void* user_data) -{ - LLFloaterInventoryFinder* self = (LLFloaterInventoryFinder*)user_data; - if(!self) return; - - self->getChild<LLUICtrl>("check_animation")->setValue(true); - self->getChild<LLUICtrl>("check_calling_card")->setValue(true); - self->getChild<LLUICtrl>("check_clothing")->setValue(true); - self->getChild<LLUICtrl>("check_gesture")->setValue(true); - self->getChild<LLUICtrl>("check_landmark")->setValue(true); - self->getChild<LLUICtrl>("check_material")->setValue(true); - self->getChild<LLUICtrl>("check_notecard")->setValue(true); - self->getChild<LLUICtrl>("check_object")->setValue(true); - self->getChild<LLUICtrl>("check_script")->setValue(true); - self->getChild<LLUICtrl>("check_sound")->setValue(true); - self->getChild<LLUICtrl>("check_texture")->setValue(true); - self->getChild<LLUICtrl>("check_snapshot")->setValue(true); - self->getChild<LLUICtrl>("check_settings")->setValue(true); +void LLFloaterInventoryFinder::selectAllTypes() +{ + mCheckAnimation->setValue(true); + mCheckCallingCard->setValue(true); + mCheckClothing->setValue(true); + mCheckGesture->setValue(true); + mCheckLandmark->setValue(true); + mCheckMaterial->setValue(true); + mCheckNotecard->setValue(true); + mCheckObject->setValue(true); + mCheckScript->setValue(true); + mCheckSounds->setValue(true); + mCheckTexture->setValue(true); + mCheckSnapshot->setValue(true); + mCheckSettings->setValue(true); } -//static -void LLFloaterInventoryFinder::selectNoTypes(void* user_data) -{ - LLFloaterInventoryFinder* self = (LLFloaterInventoryFinder*)user_data; - if(!self) return; - - self->getChild<LLUICtrl>("check_animation")->setValue(false); - self->getChild<LLUICtrl>("check_calling_card")->setValue(false); - self->getChild<LLUICtrl>("check_clothing")->setValue(false); - self->getChild<LLUICtrl>("check_gesture")->setValue(false); - self->getChild<LLUICtrl>("check_landmark")->setValue(false); - self->getChild<LLUICtrl>("check_material")->setValue(false); - self->getChild<LLUICtrl>("check_notecard")->setValue(false); - self->getChild<LLUICtrl>("check_object")->setValue(false); - self->getChild<LLUICtrl>("check_script")->setValue(false); - self->getChild<LLUICtrl>("check_sound")->setValue(false); - self->getChild<LLUICtrl>("check_texture")->setValue(false); - self->getChild<LLUICtrl>("check_snapshot")->setValue(false); - self->getChild<LLUICtrl>("check_settings")->setValue(false); +void LLFloaterInventoryFinder::selectNoTypes() +{ + mCheckAnimation->setValue(false); + mCheckCallingCard->setValue(false); + mCheckClothing->setValue(false); + mCheckGesture->setValue(false); + mCheckLandmark->setValue(false); + mCheckMaterial->setValue(false); + mCheckNotecard->setValue(false); + mCheckObject->setValue(false); + mCheckScript->setValue(false); + mCheckSounds->setValue(false); + mCheckTexture->setValue(false); + mCheckSnapshot->setValue(false); + mCheckSettings->setValue(false); } ////////////////////////////////////////////////////////////////////////////////// @@ -1516,7 +1542,7 @@ void LLPanelMainInventory::initListCommandsHandlers() mBackBtn->setCommitCallback(boost::bind(&LLPanelMainInventory::onBackFolderClicked, this)); mForwardBtn->setCommitCallback(boost::bind(&LLPanelMainInventory::onForwardFolderClicked, this)); - mCommitCallbackRegistrar.add("Inventory.GearDefault.Custom.Action", boost::bind(&LLPanelMainInventory::onCustomAction, this, _2)); + mCommitCallbackRegistrar.add("Inventory.GearDefault.Custom.Action", { boost::bind(&LLPanelMainInventory::onCustomAction, this, _2), cb_info::UNTRUSTED_BLOCK }); mEnableCallbackRegistrar.add("Inventory.GearDefault.Check", boost::bind(&LLPanelMainInventory::isActionChecked, this, _2)); mEnableCallbackRegistrar.add("Inventory.GearDefault.Enable", boost::bind(&LLPanelMainInventory::isActionEnabled, this, _2)); mEnableCallbackRegistrar.add("Inventory.GearDefault.Visible", boost::bind(&LLPanelMainInventory::isActionVisible, this, _2)); diff --git a/indra/newview/llpanelmediasettingssecurity.cpp b/indra/newview/llpanelmediasettingssecurity.cpp index 6e4e9f426d..7041f7945d 100644 --- a/indra/newview/llpanelmediasettingssecurity.cpp +++ b/indra/newview/llpanelmediasettingssecurity.cpp @@ -49,8 +49,8 @@ LLPanelMediaSettingsSecurity::LLPanelMediaSettingsSecurity() : mParent( NULL ) { - mCommitCallbackRegistrar.add("Media.whitelistAdd", boost::bind(&LLPanelMediaSettingsSecurity::onBtnAdd, this)); - mCommitCallbackRegistrar.add("Media.whitelistDelete", boost::bind(&LLPanelMediaSettingsSecurity::onBtnDel, this)); + mCommitCallbackRegistrar.add("Media.whitelistAdd", { boost::bind(&LLPanelMediaSettingsSecurity::onBtnAdd, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Media.whitelistDelete", { boost::bind(&LLPanelMediaSettingsSecurity::onBtnDel, this), cb_info::UNTRUSTED_BLOCK }); // build dialog from XML buildFromFile( "panel_media_settings_security.xml"); diff --git a/indra/newview/llpanelnearbymedia.cpp b/indra/newview/llpanelnearbymedia.cpp index 2dd4866da3..86a88d6793 100644 --- a/indra/newview/llpanelnearbymedia.cpp +++ b/indra/newview/llpanelnearbymedia.cpp @@ -89,24 +89,24 @@ LLPanelNearByMedia::LLPanelNearByMedia() gSavedSettings.getControl("ParcelMediaAutoPlayEnable")->getSignal()->connect(boost::bind(&LLPanelNearByMedia::handleMediaAutoPlayChanged, this, _2)); - mCommitCallbackRegistrar.add("MediaListCtrl.EnableAll", boost::bind(&LLPanelNearByMedia::onClickEnableAll, this)); - mCommitCallbackRegistrar.add("MediaListCtrl.DisableAll", boost::bind(&LLPanelNearByMedia::onClickDisableAll, this)); - mCommitCallbackRegistrar.add("MediaListCtrl.GoMediaPrefs", boost::bind(&LLPanelNearByMedia::onAdvancedButtonClick, this)); - mCommitCallbackRegistrar.add("MediaListCtrl.MoreLess", boost::bind(&LLPanelNearByMedia::onMoreLess, this)); - mCommitCallbackRegistrar.add("SelectedMediaCtrl.Stop", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaStop, this)); - mCommitCallbackRegistrar.add("SelectedMediaCtrl.Play", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaPlay, this)); - mCommitCallbackRegistrar.add("SelectedMediaCtrl.Pause", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaPause, this)); - mCommitCallbackRegistrar.add("SelectedMediaCtrl.Mute", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaMute, this)); - mCommitCallbackRegistrar.add("SelectedMediaCtrl.Volume", boost::bind(&LLPanelNearByMedia::onCommitSelectedMediaVolume, this)); - mCommitCallbackRegistrar.add("SelectedMediaCtrl.Zoom", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaZoom, this)); - mCommitCallbackRegistrar.add("SelectedMediaCtrl.Unzoom", boost::bind(&LLPanelNearByMedia::onClickSelectedMediaUnzoom, this)); + mCommitCallbackRegistrar.add("MediaListCtrl.EnableAll", { boost::bind(&LLPanelNearByMedia::onClickEnableAll, this) }); + mCommitCallbackRegistrar.add("MediaListCtrl.DisableAll", { boost::bind(&LLPanelNearByMedia::onClickDisableAll, this) }); + mCommitCallbackRegistrar.add("MediaListCtrl.GoMediaPrefs", { boost::bind(&LLPanelNearByMedia::onAdvancedButtonClick, this) }); + mCommitCallbackRegistrar.add("MediaListCtrl.MoreLess", { boost::bind(&LLPanelNearByMedia::onMoreLess, this) }); + mCommitCallbackRegistrar.add("SelectedMediaCtrl.Stop", { boost::bind(&LLPanelNearByMedia::onClickSelectedMediaStop, this) }); + mCommitCallbackRegistrar.add("SelectedMediaCtrl.Play", { boost::bind(&LLPanelNearByMedia::onClickSelectedMediaPlay, this) }); + mCommitCallbackRegistrar.add("SelectedMediaCtrl.Pause", { boost::bind(&LLPanelNearByMedia::onClickSelectedMediaPause, this) }); + mCommitCallbackRegistrar.add("SelectedMediaCtrl.Mute", { boost::bind(&LLPanelNearByMedia::onClickSelectedMediaMute, this) }); + mCommitCallbackRegistrar.add("SelectedMediaCtrl.Volume", { boost::bind(&LLPanelNearByMedia::onCommitSelectedMediaVolume, this) }); + mCommitCallbackRegistrar.add("SelectedMediaCtrl.Zoom", { boost::bind(&LLPanelNearByMedia::onClickSelectedMediaZoom, this) }); + mCommitCallbackRegistrar.add("SelectedMediaCtrl.Unzoom", { boost::bind(&LLPanelNearByMedia::onClickSelectedMediaUnzoom, this) }); // Context menu handler. mCommitCallbackRegistrar.add("SelectedMediaCtrl.Action", - [this](LLUICtrl* ctrl, const LLSD& data) + {[this](LLUICtrl* ctrl, const LLSD& data) { onMenuAction(data); - }); + }}); mEnableCallbackRegistrar.add("SelectedMediaCtrl.Visible", [this](LLUICtrl* ctrl, const LLSD& data) { diff --git a/indra/newview/llpanelobject.cpp b/indra/newview/llpanelobject.cpp index 0a3a2e753a..76eb2fd017 100644 --- a/indra/newview/llpanelobject.cpp +++ b/indra/newview/llpanelobject.cpp @@ -296,7 +296,7 @@ LLPanelObject::LLPanelObject() mHasClipboardRot(false), mSizeChanged(false) { - mCommitCallbackRegistrar.add("PanelObject.menuDoToSelected", boost::bind(&LLPanelObject::menuDoToSelected, this, _2)); + mCommitCallbackRegistrar.add("PanelObject.menuDoToSelected", { boost::bind(&LLPanelObject::menuDoToSelected, this, _2) }); mEnableCallbackRegistrar.add("PanelObject.menuEnable", boost::bind(&LLPanelObject::menuEnableItem, this, _2)); } diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp index ef7986603b..dcd7fdf30d 100644 --- a/indra/newview/llpanelobjectinventory.cpp +++ b/indra/newview/llpanelobjectinventory.cpp @@ -1332,14 +1332,16 @@ LLPanelObjectInventory::LLPanelObjectInventory(const LLPanelObjectInventory::Par mShowRootFolder(p.show_root_folder) { // Setup context menu callbacks - mCommitCallbackRegistrar.add("Inventory.DoToSelected", boost::bind(&LLPanelObjectInventory::doToSelected, this, _2)); - mCommitCallbackRegistrar.add("Inventory.EmptyTrash", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH)); - mCommitCallbackRegistrar.add("Inventory.EmptyLostAndFound", boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND)); - mCommitCallbackRegistrar.add("Inventory.DoCreate", boost::bind(&do_nothing)); - mCommitCallbackRegistrar.add("Inventory.AttachObject", boost::bind(&do_nothing)); - mCommitCallbackRegistrar.add("Inventory.BeginIMSession", boost::bind(&do_nothing)); - mCommitCallbackRegistrar.add("Inventory.Share", boost::bind(&LLAvatarActions::shareWithAvatars, this)); - mCommitCallbackRegistrar.add("Inventory.FileUploadLocation", boost::bind(&do_nothing)); + mCommitCallbackRegistrar.add("Inventory.DoToSelected", { boost::bind(&LLPanelObjectInventory::doToSelected, this, _2), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.EmptyTrash", + { boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyTrash", LLFolderType::FT_TRASH), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.EmptyLostAndFound", + { boost::bind(&LLInventoryModel::emptyFolderType, &gInventory, "ConfirmEmptyLostAndFound", LLFolderType::FT_LOST_AND_FOUND), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.DoCreate", { boost::bind(&do_nothing) }); + mCommitCallbackRegistrar.add("Inventory.AttachObject", { boost::bind(&do_nothing) }); + mCommitCallbackRegistrar.add("Inventory.BeginIMSession", { boost::bind(&do_nothing) }); + mCommitCallbackRegistrar.add("Inventory.Share", { boost::bind(&LLAvatarActions::shareWithAvatars, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.FileUploadLocation", { boost::bind(&do_nothing) }); } // Destroys the object diff --git a/indra/newview/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp index 4cd4afaa5a..60017db51d 100644 --- a/indra/newview/llpaneloutfitedit.cpp +++ b/indra/newview/llpaneloutfitedit.cpp @@ -157,7 +157,7 @@ public: { LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - registrar.add("Wearable.Create", boost::bind(onCreate, _2)); + registrar.add("Wearable.Create", { boost::bind(onCreate, _2), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); llassert(LLMenuGL::sMenuContainer != NULL); LLToggleableMenu* menu = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>( @@ -226,7 +226,7 @@ public: LLHandle<LLView> flat_list_handle = flat_list->getHandle(); LLHandle<LLPanel> inventory_panel_handle = inventory_panel->getHandle(); - registrar.add("AddWearable.Gear.Sort", boost::bind(onSort, flat_list_handle, inventory_panel_handle, _2)); + registrar.add("AddWearable.Gear.Sort", { boost::bind(onSort, flat_list_handle, inventory_panel_handle, _2) }); enable_registrar.add("AddWearable.Gear.Check", boost::bind(onCheck, flat_list_handle, inventory_panel_handle, _2)); enable_registrar.add("AddWearable.Gear.Visible", boost::bind(onVisible, inventory_panel_handle, _2)); diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp index 25672db318..ca1a4e258d 100644 --- a/indra/newview/llpanelpeople.cpp +++ b/indra/newview/llpanelpeople.cpp @@ -308,10 +308,10 @@ public: : LLEventTimer(period), LLPanelPeople::Updater(cb) { - mEventTimer.stop(); + stop(); } - virtual bool tick() // from LLEventTimer + bool tick() override // from LLEventTimer { return false; } @@ -348,13 +348,13 @@ public: LLAvatarTracker::instance().removeObserver(this); } - /*virtual*/ void changed(U32 mask) + void changed(U32 mask) override { if (mIsActive) { // events can arrive quickly in bulk - we need not process EVERY one of them - // so we wait a short while to let others pile-in, and process them in aggregate. - mEventTimer.start(); + start(); } // save-up all the mask-bits which have come-in @@ -362,7 +362,7 @@ public: } - /*virtual*/ bool tick() + bool tick() override { if (!mIsActive) return false; @@ -372,14 +372,13 @@ public: } // Stop updates. - mEventTimer.stop(); + stop(); mMask = 0; return false; } - // virtual - void setActive(bool active) + void setActive(bool active) override { mIsActive = active; if (active) @@ -488,22 +487,22 @@ public: setActive(false); } - /*virtual*/ void setActive(bool val) + void setActive(bool val) override { if (val) { // update immediately and start regular updates update(); - mEventTimer.start(); + start(); } else { // stop regular updates - mEventTimer.stop(); + stop(); } } - /*virtual*/ bool tick() + bool tick() override { update(); return false; @@ -543,18 +542,18 @@ LLPanelPeople::LLPanelPeople() mRecentListUpdater = new LLRecentListUpdater(boost::bind(&LLPanelPeople::updateRecentList, this)); mButtonsUpdater = new LLButtonsUpdater(boost::bind(&LLPanelPeople::updateButtons, this)); - mCommitCallbackRegistrar.add("People.AddFriend", boost::bind(&LLPanelPeople::onAddFriendButtonClicked, this)); - mCommitCallbackRegistrar.add("People.AddFriendWizard", boost::bind(&LLPanelPeople::onAddFriendWizButtonClicked, this)); - mCommitCallbackRegistrar.add("People.DelFriend", boost::bind(&LLPanelPeople::onDeleteFriendButtonClicked, this)); - mCommitCallbackRegistrar.add("People.Group.Minus", boost::bind(&LLPanelPeople::onGroupMinusButtonClicked, this)); - mCommitCallbackRegistrar.add("People.Chat", boost::bind(&LLPanelPeople::onChatButtonClicked, this)); - mCommitCallbackRegistrar.add("People.Gear", boost::bind(&LLPanelPeople::onGearButtonClicked, this, _1)); - - mCommitCallbackRegistrar.add("People.Group.Plus.Action", boost::bind(&LLPanelPeople::onGroupPlusMenuItemClicked, this, _2)); - mCommitCallbackRegistrar.add("People.Friends.ViewSort.Action", boost::bind(&LLPanelPeople::onFriendsViewSortMenuItemClicked, this, _2)); - mCommitCallbackRegistrar.add("People.Nearby.ViewSort.Action", boost::bind(&LLPanelPeople::onNearbyViewSortMenuItemClicked, this, _2)); - mCommitCallbackRegistrar.add("People.Groups.ViewSort.Action", boost::bind(&LLPanelPeople::onGroupsViewSortMenuItemClicked, this, _2)); - mCommitCallbackRegistrar.add("People.Recent.ViewSort.Action", boost::bind(&LLPanelPeople::onRecentViewSortMenuItemClicked, this, _2)); + mCommitCallbackRegistrar.add("People.AddFriend", { boost::bind(&LLPanelPeople::onAddFriendButtonClicked, this) }); + mCommitCallbackRegistrar.add("People.AddFriendWizard", { boost::bind(&LLPanelPeople::onAddFriendWizButtonClicked, this) }); + mCommitCallbackRegistrar.add("People.DelFriend", { boost::bind(&LLPanelPeople::onDeleteFriendButtonClicked, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("People.Group.Minus", { boost::bind(&LLPanelPeople::onGroupMinusButtonClicked, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("People.Chat", { boost::bind(&LLPanelPeople::onChatButtonClicked, this) }); + mCommitCallbackRegistrar.add("People.Gear", { boost::bind(&LLPanelPeople::onGearButtonClicked, this, _1) }); + + mCommitCallbackRegistrar.add("People.Group.Plus.Action", { boost::bind(&LLPanelPeople::onGroupPlusMenuItemClicked, this, _2), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("People.Friends.ViewSort.Action", { boost::bind(&LLPanelPeople::onFriendsViewSortMenuItemClicked, this, _2) }); + mCommitCallbackRegistrar.add("People.Nearby.ViewSort.Action", { boost::bind(&LLPanelPeople::onNearbyViewSortMenuItemClicked, this, _2) }); + mCommitCallbackRegistrar.add("People.Groups.ViewSort.Action", { boost::bind(&LLPanelPeople::onGroupsViewSortMenuItemClicked, this, _2) }); + mCommitCallbackRegistrar.add("People.Recent.ViewSort.Action", { boost::bind(&LLPanelPeople::onRecentViewSortMenuItemClicked, this, _2) }); mEnableCallbackRegistrar.add("People.Friends.ViewSort.CheckItem", boost::bind(&LLPanelPeople::onFriendsViewSortMenuItemCheck, this, _2)); mEnableCallbackRegistrar.add("People.Recent.ViewSort.CheckItem", boost::bind(&LLPanelPeople::onRecentViewSortMenuItemCheck, this, _2)); diff --git a/indra/newview/llpanelpeoplemenus.cpp b/indra/newview/llpanelpeoplemenus.cpp index f8a73ddb46..283be752ed 100644 --- a/indra/newview/llpanelpeoplemenus.cpp +++ b/indra/newview/llpanelpeoplemenus.cpp @@ -58,7 +58,7 @@ NearbyPeopleContextMenu gNearbyPeopleContextMenu; LLContextMenu* PeopleContextMenu::createMenu() { // set up the callbacks for all of the avatar menu items - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::ScopedRegistrarHelper registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; LLContextMenu* menu; @@ -68,21 +68,21 @@ LLContextMenu* PeopleContextMenu::createMenu() const LLUUID& id = mUUIDs.front(); registrar.add("Avatar.Profile", boost::bind(&LLAvatarActions::showProfile, id)); - registrar.add("Avatar.AddFriend", boost::bind(&LLAvatarActions::requestFriendshipDialog, id)); - registrar.add("Avatar.RemoveFriend", boost::bind(&LLAvatarActions::removeFriendDialog, id)); + registrar.add("Avatar.AddFriend", boost::bind(&LLAvatarActions::requestFriendshipDialog, id), LLUICtrl::cb_info::UNTRUSTED_THROTTLE); + registrar.add("Avatar.RemoveFriend", boost::bind(&LLAvatarActions::removeFriendDialog, id), LLUICtrl::cb_info::UNTRUSTED_BLOCK); registrar.add("Avatar.IM", boost::bind(&LLAvatarActions::startIM, id)); - registrar.add("Avatar.Call", boost::bind(&LLAvatarActions::startCall, id)); - registrar.add("Avatar.OfferTeleport", boost::bind(&PeopleContextMenu::offerTeleport, this)); + registrar.add("Avatar.Call", boost::bind(&LLAvatarActions::startCall, id), LLUICtrl::cb_info::UNTRUSTED_BLOCK); + registrar.add("Avatar.OfferTeleport", boost::bind(&PeopleContextMenu::offerTeleport, this), LLUICtrl::cb_info::UNTRUSTED_BLOCK); registrar.add("Avatar.ZoomIn", boost::bind(&handle_zoom_to_object, id)); registrar.add("Avatar.ShowOnMap", boost::bind(&LLAvatarActions::showOnMap, id)); - registrar.add("Avatar.Share", boost::bind(&LLAvatarActions::share, id)); - registrar.add("Avatar.Pay", boost::bind(&LLAvatarActions::pay, id)); + registrar.add("Avatar.Share", boost::bind(&LLAvatarActions::share, id), LLUICtrl::cb_info::UNTRUSTED_BLOCK); + registrar.add("Avatar.Pay", boost::bind(&LLAvatarActions::pay, id), LLUICtrl::cb_info::UNTRUSTED_BLOCK); registrar.add("Avatar.BlockUnblock", boost::bind(&LLAvatarActions::toggleBlock, id)); - registrar.add("Avatar.InviteToGroup", boost::bind(&LLAvatarActions::inviteToGroup, id)); + registrar.add("Avatar.InviteToGroup", boost::bind(&LLAvatarActions::inviteToGroup, id), LLUICtrl::cb_info::UNTRUSTED_BLOCK); registrar.add("Avatar.TeleportRequest", boost::bind(&PeopleContextMenu::requestTeleport, this)); - registrar.add("Avatar.Calllog", boost::bind(&LLAvatarActions::viewChatHistory, id)); - registrar.add("Avatar.Freeze", boost::bind(&LLAvatarActions::freezeAvatar, id)); - registrar.add("Avatar.Eject", boost::bind(&PeopleContextMenu::eject, this)); + registrar.add("Avatar.Calllog", boost::bind(&LLAvatarActions::viewChatHistory, id), LLUICtrl::cb_info::UNTRUSTED_THROTTLE); + registrar.add("Avatar.Freeze", boost::bind(&LLAvatarActions::freezeAvatar, id), LLUICtrl::cb_info::UNTRUSTED_THROTTLE); + registrar.add("Avatar.Eject", boost::bind(&PeopleContextMenu::eject, this), LLUICtrl::cb_info::UNTRUSTED_THROTTLE); enable_registrar.add("Avatar.EnableItem", boost::bind(&PeopleContextMenu::enableContextMenuItem, this, _2)); diff --git a/indra/newview/llpanelplaceprofile.cpp b/indra/newview/llpanelplaceprofile.cpp index 18588514f8..87f05f2028 100644 --- a/indra/newview/llpanelplaceprofile.cpp +++ b/indra/newview/llpanelplaceprofile.cpp @@ -629,6 +629,11 @@ void LLPanelPlaceProfile::updateCovenantText(const std::string &text) mCovenantText->setText(text); } +void LLPanelPlaceProfile::updateCovenant(const LLTextBase* source) +{ + mCovenantText->copyContents(source); +} + void LLPanelPlaceProfile::onForSaleBannerClick() { LLViewerParcelMgr* mgr = LLViewerParcelMgr::getInstance(); diff --git a/indra/newview/llpanelplaceprofile.h b/indra/newview/llpanelplaceprofile.h index 45a20fb86a..f562be0f5d 100644 --- a/indra/newview/llpanelplaceprofile.h +++ b/indra/newview/llpanelplaceprofile.h @@ -31,6 +31,7 @@ class LLAccordionCtrl; class LLIconCtrl; +class LLTextBase; class LLTextEditor; class LLPanelPlaceProfile : public LLPanelPlaceInfo @@ -60,6 +61,7 @@ public: void updateEstateName(const std::string& name); void updateEstateOwnerName(const std::string& name); void updateCovenantText(const std::string &text); + void updateCovenant(const LLTextBase* source); private: void onForSaleBannerClick(); diff --git a/indra/newview/llpanelplaces.cpp b/indra/newview/llpanelplaces.cpp index 7deb1d9fd4..5fb20d4b69 100644 --- a/indra/newview/llpanelplaces.cpp +++ b/indra/newview/llpanelplaces.cpp @@ -315,7 +315,7 @@ bool LLPanelPlaces::postBuild() , _7 // EAcceptance* accept )); - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + ScopedRegistrarHelper registrar; registrar.add("Places.OverflowMenu.Action", boost::bind(&LLPanelPlaces::onOverflowMenuItemClicked, this, _2)); LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; enable_registrar.add("Places.OverflowMenu.Enable", boost::bind(&LLPanelPlaces::onOverflowMenuItemEnable, this, _2)); diff --git a/indra/newview/llpanelpresetscamerapulldown.cpp b/indra/newview/llpanelpresetscamerapulldown.cpp index 5b0aa28223..d1497719e0 100644 --- a/indra/newview/llpanelpresetscamerapulldown.cpp +++ b/indra/newview/llpanelpresetscamerapulldown.cpp @@ -48,8 +48,8 @@ // Default constructor LLPanelPresetsCameraPulldown::LLPanelPresetsCameraPulldown() { - mCommitCallbackRegistrar.add("Presets.toggleCameraFloater", boost::bind(&LLPanelPresetsCameraPulldown::onViewButtonClick, this, _2)); - mCommitCallbackRegistrar.add("PresetsCamera.RowClick", boost::bind(&LLPanelPresetsCameraPulldown::onRowClick, this, _2)); + mCommitCallbackRegistrar.add("Presets.toggleCameraFloater", { boost::bind(&LLPanelPresetsCameraPulldown::onViewButtonClick, this, _2) }); + mCommitCallbackRegistrar.add("PresetsCamera.RowClick", { boost::bind(&LLPanelPresetsCameraPulldown::onRowClick, this, _2) }); buildFromFile( "panel_presets_camera_pulldown.xml"); } diff --git a/indra/newview/llpanelpresetspulldown.cpp b/indra/newview/llpanelpresetspulldown.cpp index 3b0f1273df..3d564ef540 100644 --- a/indra/newview/llpanelpresetspulldown.cpp +++ b/indra/newview/llpanelpresetspulldown.cpp @@ -50,9 +50,9 @@ LLPanelPresetsPulldown::LLPanelPresetsPulldown() { mHoverTimer.stop(); - mCommitCallbackRegistrar.add("Presets.GoGraphicsPrefs", boost::bind(&LLPanelPresetsPulldown::onGraphicsButtonClick, this, _2)); - mCommitCallbackRegistrar.add("Presets.GoAutofpsPrefs", boost::bind(&LLPanelPresetsPulldown::onAutofpsButtonClick, this, _2)); - mCommitCallbackRegistrar.add("Presets.RowClick", boost::bind(&LLPanelPresetsPulldown::onRowClick, this, _2)); + mCommitCallbackRegistrar.add("Presets.GoGraphicsPrefs", { boost::bind(&LLPanelPresetsPulldown::onGraphicsButtonClick, this, _2) }); + mCommitCallbackRegistrar.add("Presets.GoAutofpsPrefs", { boost::bind(&LLPanelPresetsPulldown::onAutofpsButtonClick, this, _2) }); + mCommitCallbackRegistrar.add("Presets.RowClick", { boost::bind(&LLPanelPresetsPulldown::onRowClick, this, _2) }); buildFromFile( "panel_presets_pulldown.xml"); } diff --git a/indra/newview/llpanelprimmediacontrols.cpp b/indra/newview/llpanelprimmediacontrols.cpp index 4db0a5b59d..1af585708e 100644 --- a/indra/newview/llpanelprimmediacontrols.cpp +++ b/indra/newview/llpanelprimmediacontrols.cpp @@ -101,28 +101,28 @@ LLPanelPrimMediaControls::LLPanelPrimMediaControls() : mSecureURL(false), mMediaPlaySliderCtrlMouseDownValue(0.0) { - mCommitCallbackRegistrar.add("MediaCtrl.Close", boost::bind(&LLPanelPrimMediaControls::onClickClose, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Back", boost::bind(&LLPanelPrimMediaControls::onClickBack, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Forward", boost::bind(&LLPanelPrimMediaControls::onClickForward, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Home", boost::bind(&LLPanelPrimMediaControls::onClickHome, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Stop", boost::bind(&LLPanelPrimMediaControls::onClickStop, this)); - mCommitCallbackRegistrar.add("MediaCtrl.MediaStop", boost::bind(&LLPanelPrimMediaControls::onClickMediaStop, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Reload", boost::bind(&LLPanelPrimMediaControls::onClickReload, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Play", boost::bind(&LLPanelPrimMediaControls::onClickPlay, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Pause", boost::bind(&LLPanelPrimMediaControls::onClickPause, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Open", boost::bind(&LLPanelPrimMediaControls::onClickOpen, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Zoom", boost::bind(&LLPanelPrimMediaControls::onClickZoom, this)); - mCommitCallbackRegistrar.add("MediaCtrl.CommitURL", boost::bind(&LLPanelPrimMediaControls::onCommitURL, this)); - mCommitCallbackRegistrar.add("MediaCtrl.MouseDown", boost::bind(&LLPanelPrimMediaControls::onMediaPlaySliderCtrlMouseDown, this)); - mCommitCallbackRegistrar.add("MediaCtrl.MouseUp", boost::bind(&LLPanelPrimMediaControls::onMediaPlaySliderCtrlMouseUp, this)); - mCommitCallbackRegistrar.add("MediaCtrl.CommitVolumeUp", boost::bind(&LLPanelPrimMediaControls::onCommitVolumeUp, this)); - mCommitCallbackRegistrar.add("MediaCtrl.CommitVolumeDown", boost::bind(&LLPanelPrimMediaControls::onCommitVolumeDown, this)); - mCommitCallbackRegistrar.add("MediaCtrl.Volume", boost::bind(&LLPanelPrimMediaControls::onCommitVolumeSlider, this)); - mCommitCallbackRegistrar.add("MediaCtrl.ToggleMute", boost::bind(&LLPanelPrimMediaControls::onToggleMute, this)); - mCommitCallbackRegistrar.add("MediaCtrl.ShowVolumeSlider", boost::bind(&LLPanelPrimMediaControls::showVolumeSlider, this)); - mCommitCallbackRegistrar.add("MediaCtrl.HideVolumeSlider", boost::bind(&LLPanelPrimMediaControls::hideVolumeSlider, this)); - mCommitCallbackRegistrar.add("MediaCtrl.SkipBack", boost::bind(&LLPanelPrimMediaControls::onClickSkipBack, this)); - mCommitCallbackRegistrar.add("MediaCtrl.SkipForward", boost::bind(&LLPanelPrimMediaControls::onClickSkipForward, this)); + mCommitCallbackRegistrar.add("MediaCtrl.Close", { boost::bind(&LLPanelPrimMediaControls::onClickClose, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.Back", { boost::bind(&LLPanelPrimMediaControls::onClickBack, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.Forward", { boost::bind(&LLPanelPrimMediaControls::onClickForward, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.Home", { boost::bind(&LLPanelPrimMediaControls::onClickHome, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.Stop", { boost::bind(&LLPanelPrimMediaControls::onClickStop, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.MediaStop", { boost::bind(&LLPanelPrimMediaControls::onClickMediaStop, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.Reload", { boost::bind(&LLPanelPrimMediaControls::onClickReload, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.Play", { boost::bind(&LLPanelPrimMediaControls::onClickPlay, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.Pause", { boost::bind(&LLPanelPrimMediaControls::onClickPause, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.Open", { boost::bind(&LLPanelPrimMediaControls::onClickOpen, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.Zoom", { boost::bind(&LLPanelPrimMediaControls::onClickZoom, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.CommitURL", { boost::bind(&LLPanelPrimMediaControls::onCommitURL, this), LLUICtrl::cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("MediaCtrl.MouseDown", { boost::bind(&LLPanelPrimMediaControls::onMediaPlaySliderCtrlMouseDown, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.MouseUp", { boost::bind(&LLPanelPrimMediaControls::onMediaPlaySliderCtrlMouseUp, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.CommitVolumeUp", { boost::bind(&LLPanelPrimMediaControls::onCommitVolumeUp, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.CommitVolumeDown", { boost::bind(&LLPanelPrimMediaControls::onCommitVolumeDown, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.Volume", { boost::bind(&LLPanelPrimMediaControls::onCommitVolumeSlider, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.ToggleMute", { boost::bind(&LLPanelPrimMediaControls::onToggleMute, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.ShowVolumeSlider", { boost::bind(&LLPanelPrimMediaControls::showVolumeSlider, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.HideVolumeSlider", { boost::bind(&LLPanelPrimMediaControls::hideVolumeSlider, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.SkipBack", { boost::bind(&LLPanelPrimMediaControls::onClickSkipBack, this) }); + mCommitCallbackRegistrar.add("MediaCtrl.SkipForward", { boost::bind(&LLPanelPrimMediaControls::onClickSkipForward, this) }); buildFromFile( "panel_prim_media_controls.xml"); mInactivityTimer.reset(); diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index 08605f7cf4..2086706bf8 100644 --- a/indra/newview/llpanelprofile.cpp +++ b/indra/newview/llpanelprofile.cpp @@ -772,7 +772,7 @@ void LLPanelProfileSecondLife::onOpen(const LLSD& key) } // Init menu, menu needs to be created in scope of a registar to work correctly. - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar commit; + ScopedRegistrarHelper commit; commit.add("Profile.Commit", [this](LLUICtrl*, const LLSD& userdata) { onCommitMenu(userdata); }); LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable; diff --git a/indra/newview/llpanelprofilepicks.cpp b/indra/newview/llpanelprofilepicks.cpp index 08f3d3af5a..11a10ebd2c 100644 --- a/indra/newview/llpanelprofilepicks.cpp +++ b/indra/newview/llpanelprofilepicks.cpp @@ -246,6 +246,8 @@ void LLPanelProfilePicks::onClickNewBtn() select_tab(true). label(pick_panel->getPickName())); updateButtons(); + + pick_panel->addLocationChangedCallbacks(); } void LLPanelProfilePicks::onClickDelete() @@ -571,10 +573,12 @@ void LLPanelProfilePick::setAvatarId(const LLUUID& avatar_id) { mPickName->setEnabled(true); mPickDescription->setEnabled(true); + mSetCurrentLocationButton->setVisible(true); } else { mSnapshotCtrl->setEnabled(false); + mSetCurrentLocationButton->setVisible(false); } } @@ -585,6 +589,7 @@ bool LLPanelProfilePick::postBuild() mSaveButton = getChild<LLButton>("save_changes_btn"); mCreateButton = getChild<LLButton>("create_changes_btn"); mCancelButton = getChild<LLButton>("cancel_changes_btn"); + mSetCurrentLocationButton = getChild<LLButton>("set_to_curr_location_btn"); mSnapshotCtrl = getChild<LLTextureCtrl>("pick_snapshot"); mSnapshotCtrl->setCommitCallback(boost::bind(&LLPanelProfilePick::onSnapshotChanged, this)); @@ -597,6 +602,7 @@ bool LLPanelProfilePick::postBuild() mSaveButton->setCommitCallback(boost::bind(&LLPanelProfilePick::onClickSave, this)); mCreateButton->setCommitCallback(boost::bind(&LLPanelProfilePick::onClickSave, this)); mCancelButton->setCommitCallback(boost::bind(&LLPanelProfilePick::onClickCancel, this)); + mSetCurrentLocationButton->setCommitCallback(boost::bind(&LLPanelProfilePick::onClickSetLocation, this)); mPickName->setKeystrokeCallback(boost::bind(&LLPanelProfilePick::onPickChanged, this, _1), NULL); mPickName->setEnabled(false); @@ -759,6 +765,32 @@ bool LLPanelProfilePick::isDirty() const return false; } +void LLPanelProfilePick::onClickSetLocation() +{ + // Save location for later use. + setPosGlobal(gAgent.getPositionGlobal()); + + std::string parcel_name, region_name; + + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (parcel) + { + mParcelId = parcel->getID(); + parcel_name = parcel->getName(); + } + + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + region_name = region->getName(); + } + + setPickLocation(createLocationText(getLocationNotice(), parcel_name, region_name, getPosGlobal())); + + mLocationChanged = true; + enableSaveButton(true); +} + void LLPanelProfilePick::onClickSave() { if (mRegionCallbackConnection.connected()) @@ -769,6 +801,10 @@ void LLPanelProfilePick::onClickSave() { mParcelCallbackConnection.disconnect(); } + if (mLocationChanged) + { + onClickSetLocation(); + } sendUpdate(); mLocationChanged = false; @@ -816,6 +852,12 @@ void LLPanelProfilePick::processParcelInfo(const LLParcelData& parcel_data) } } +void LLPanelProfilePick::addLocationChangedCallbacks() +{ + mRegionCallbackConnection = gAgent.addRegionChangedCallback([this]() { onClickSetLocation(); }); + mParcelCallbackConnection = gAgent.addParcelChangedCallback([this]() { onClickSetLocation(); }); +} + void LLPanelProfilePick::sendUpdate() { LLPickData pick_data; diff --git a/indra/newview/llpanelprofilepicks.h b/indra/newview/llpanelprofilepicks.h index e3f50f5576..5e5d4ff81e 100644 --- a/indra/newview/llpanelprofilepicks.h +++ b/indra/newview/llpanelprofilepicks.h @@ -138,6 +138,8 @@ public: void setParcelID(const LLUUID& parcel_id) override { mParcelId = parcel_id; } void setErrorStatus(S32 status, const std::string& reason) override {}; + void addLocationChangedCallbacks(); + protected: /** @@ -200,6 +202,11 @@ public: void resetDirty() override; /** + * Callback for "Set Location" button click + */ + void onClickSetLocation(); + + /** * Callback for "Save" and "Create" button click */ void onClickSave(); @@ -221,6 +228,7 @@ protected: LLTextureCtrl* mSnapshotCtrl; LLLineEditor* mPickName; LLTextEditor* mPickDescription; + LLButton* mSetCurrentLocationButton; LLButton* mSaveButton; LLButton* mCreateButton; LLButton* mCancelButton; @@ -236,7 +244,7 @@ protected: bool mLocationChanged; bool mNewPick; - bool mIsEditing; + bool mIsEditing; void onDescriptionFocusReceived(); }; diff --git a/indra/newview/llpanelsnapshot.cpp b/indra/newview/llpanelsnapshot.cpp index 32c9f6f402..56c0294dbe 100644 --- a/indra/newview/llpanelsnapshot.cpp +++ b/indra/newview/llpanelsnapshot.cpp @@ -37,6 +37,7 @@ // newview #include "llsidetraypanelcontainer.h" +#include "llsnapshotlivepreview.h" #include "llviewercontrol.h" // gSavedSettings #include "llagentbenefits.h" @@ -99,6 +100,17 @@ void LLPanelSnapshot::onOpen(const LLSD& key) { getParentByType<LLFloater>()->notify(LLSD().with("image-format-change", true)); } + + // If resolution is set to "Current Window", force a snapshot update + // each time a snapshot panel is opened to determine the correct + // image size (and upload fee) depending on the snapshot type. + if (mSnapshotFloater && getChild<LLUICtrl>(getImageSizeComboName())->getValue().asString() == "[i0,i0]") + { + if (LLSnapshotLivePreview* preview = mSnapshotFloater->getPreviewView()) + { + preview->mForceUpdateSnapshot = true; + } + } } LLSnapshotModel::ESnapshotFormat LLPanelSnapshot::getImageFormat() const diff --git a/indra/newview/llpanelsnapshotinventory.cpp b/indra/newview/llpanelsnapshotinventory.cpp index 96b17acc40..dda14c1e32 100644 --- a/indra/newview/llpanelsnapshotinventory.cpp +++ b/indra/newview/llpanelsnapshotinventory.cpp @@ -42,85 +42,43 @@ /** * The panel provides UI for saving snapshot as an inventory texture. */ -class LLPanelSnapshotInventoryBase - : public LLPanelSnapshot -{ - LOG_CLASS(LLPanelSnapshotInventoryBase); - -public: - LLPanelSnapshotInventoryBase(); - - /*virtual*/ bool postBuild(); -protected: - void onSend(); - /*virtual*/ LLSnapshotModel::ESnapshotType getSnapshotType(); -}; - class LLPanelSnapshotInventory - : public LLPanelSnapshotInventoryBase + : public LLPanelSnapshot { LOG_CLASS(LLPanelSnapshotInventory); public: LLPanelSnapshotInventory(); - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); + bool postBuild() override; + void onOpen(const LLSD& key) override; void onResolutionCommit(LLUICtrl* ctrl); private: - /*virtual*/ std::string getWidthSpinnerName() const { return "inventory_snapshot_width"; } - /*virtual*/ std::string getHeightSpinnerName() const { return "inventory_snapshot_height"; } - /*virtual*/ std::string getAspectRatioCBName() const { return "inventory_keep_aspect_check"; } - /*virtual*/ std::string getImageSizeComboName() const { return "texture_size_combo"; } - /*virtual*/ std::string getImageSizePanelName() const { return LLStringUtil::null; } - /*virtual*/ void updateControls(const LLSD& info); - -}; - -class LLPanelOutfitSnapshotInventory - : public LLPanelSnapshotInventoryBase -{ - LOG_CLASS(LLPanelOutfitSnapshotInventory); - -public: - LLPanelOutfitSnapshotInventory(); - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); + std::string getWidthSpinnerName() const override { return "inventory_snapshot_width"; } + std::string getHeightSpinnerName() const override { return "inventory_snapshot_height"; } + std::string getAspectRatioCBName() const override { return "inventory_keep_aspect_check"; } + std::string getImageSizeComboName() const override { return "texture_size_combo"; } + std::string getImageSizePanelName() const override { return LLStringUtil::null; } + LLSnapshotModel::ESnapshotType getSnapshotType() override; + void updateControls(const LLSD& info) override; -private: - /*virtual*/ std::string getWidthSpinnerName() const { return ""; } - /*virtual*/ std::string getHeightSpinnerName() const { return ""; } - /*virtual*/ std::string getAspectRatioCBName() const { return ""; } - /*virtual*/ std::string getImageSizeComboName() const { return "texture_size_combo"; } - /*virtual*/ std::string getImageSizePanelName() const { return LLStringUtil::null; } - /*virtual*/ void updateControls(const LLSD& info); - - /*virtual*/ void cancel(); + void onSend(); + void updateUploadCost(); + S32 calculateUploadCost(); }; static LLPanelInjector<LLPanelSnapshotInventory> panel_class1("llpanelsnapshotinventory"); -static LLPanelInjector<LLPanelOutfitSnapshotInventory> panel_class2("llpaneloutfitsnapshotinventory"); - -LLPanelSnapshotInventoryBase::LLPanelSnapshotInventoryBase() -{ -} - -bool LLPanelSnapshotInventoryBase::postBuild() -{ - return LLPanelSnapshot::postBuild(); -} - -LLSnapshotModel::ESnapshotType LLPanelSnapshotInventoryBase::getSnapshotType() +LLSnapshotModel::ESnapshotType LLPanelSnapshotInventory::getSnapshotType() { return LLSnapshotModel::SNAPSHOT_TEXTURE; } LLPanelSnapshotInventory::LLPanelSnapshotInventory() { - mCommitCallbackRegistrar.add("Inventory.Save", boost::bind(&LLPanelSnapshotInventory::onSend, this)); - mCommitCallbackRegistrar.add("Inventory.Cancel", boost::bind(&LLPanelSnapshotInventory::cancel, this)); + mCommitCallbackRegistrar.add("Inventory.Save", { boost::bind(&LLPanelSnapshotInventory::onSend, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("Inventory.Cancel", { boost::bind(&LLPanelSnapshotInventory::cancel, this), cb_info::UNTRUSTED_BLOCK }); } // virtual @@ -130,12 +88,14 @@ bool LLPanelSnapshotInventory::postBuild() getChild<LLSpinCtrl>(getHeightSpinnerName())->setAllowEdit(false); getChild<LLUICtrl>(getImageSizeComboName())->setCommitCallback(boost::bind(&LLPanelSnapshotInventory::onResolutionCommit, this, _1)); - return LLPanelSnapshotInventoryBase::postBuild(); + return LLPanelSnapshot::postBuild(); } // virtual void LLPanelSnapshotInventory::onOpen(const LLSD& key) { + updateUploadCost(); + LLPanelSnapshot::onOpen(key); } @@ -144,6 +104,8 @@ void LLPanelSnapshotInventory::updateControls(const LLSD& info) { const bool have_snapshot = info.has("have-snapshot") ? info["have-snapshot"].asBoolean() : true; getChild<LLUICtrl>("save_btn")->setEnabled(have_snapshot); + + updateUploadCost(); } void LLPanelSnapshotInventory::onResolutionCommit(LLUICtrl* ctrl) @@ -153,21 +115,9 @@ void LLPanelSnapshotInventory::onResolutionCommit(LLUICtrl* ctrl) getChild<LLSpinCtrl>(getHeightSpinnerName())->setVisible(!current_window_selected); } -void LLPanelSnapshotInventoryBase::onSend() +void LLPanelSnapshotInventory::onSend() { - S32 w = 0; - S32 h = 0; - - if( mSnapshotFloater ) - { - LLSnapshotLivePreview* preview = mSnapshotFloater->getPreviewView(); - if( preview ) - { - preview->getSize(w, h); - } - } - - S32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(w, h); + S32 expected_upload_cost = calculateUploadCost(); if (can_afford_transaction(expected_upload_cost)) { if (mSnapshotFloater) @@ -188,36 +138,24 @@ void LLPanelSnapshotInventoryBase::onSend() } } -LLPanelOutfitSnapshotInventory::LLPanelOutfitSnapshotInventory() +void LLPanelSnapshotInventory::updateUploadCost() { - mCommitCallbackRegistrar.add("Inventory.SaveOutfitPhoto", boost::bind(&LLPanelOutfitSnapshotInventory::onSend, this)); - mCommitCallbackRegistrar.add("Inventory.SaveOutfitCancel", boost::bind(&LLPanelOutfitSnapshotInventory::cancel, this)); + getChild<LLUICtrl>("hint_lbl")->setTextArg("[UPLOAD_COST]", llformat("%d", calculateUploadCost())); } -// virtual -bool LLPanelOutfitSnapshotInventory::postBuild() +S32 LLPanelSnapshotInventory::calculateUploadCost() { - return LLPanelSnapshotInventoryBase::postBuild(); -} - -// virtual -void LLPanelOutfitSnapshotInventory::onOpen(const LLSD& key) -{ - getChild<LLUICtrl>("hint_lbl")->setTextArg("[UPLOAD_COST]", llformat("%d", LLAgentBenefitsMgr::current().getTextureUploadCost())); - LLPanelSnapshot::onOpen(key); -} - -// virtual -void LLPanelOutfitSnapshotInventory::updateControls(const LLSD& info) -{ - const bool have_snapshot = info.has("have-snapshot") ? info["have-snapshot"].asBoolean() : true; - getChild<LLUICtrl>("save_btn")->setEnabled(have_snapshot); -} + S32 w = 0; + S32 h = 0; -void LLPanelOutfitSnapshotInventory::cancel() -{ if (mSnapshotFloater) { - mSnapshotFloater->closeFloater(); + if (LLSnapshotLivePreview* preview = mSnapshotFloater->getPreviewView()) + { + w = preview->getEncodedImageWidth(); + h = preview->getEncodedImageHeight(); + } } + + return LLAgentBenefitsMgr::current().getTextureUploadCost(w, h); } diff --git a/indra/newview/llpanelsnapshotlocal.cpp b/indra/newview/llpanelsnapshotlocal.cpp index 366030c0fa..d1807ca3f8 100644 --- a/indra/newview/llpanelsnapshotlocal.cpp +++ b/indra/newview/llpanelsnapshotlocal.cpp @@ -47,18 +47,18 @@ class LLPanelSnapshotLocal public: LLPanelSnapshotLocal(); - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); + bool postBuild() override; + void onOpen(const LLSD& key) override; private: - /*virtual*/ std::string getWidthSpinnerName() const { return "local_snapshot_width"; } - /*virtual*/ std::string getHeightSpinnerName() const { return "local_snapshot_height"; } - /*virtual*/ std::string getAspectRatioCBName() const { return "local_keep_aspect_check"; } - /*virtual*/ std::string getImageSizeComboName() const { return "local_size_combo"; } - /*virtual*/ std::string getImageSizePanelName() const { return "local_image_size_lp"; } - /*virtual*/ LLSnapshotModel::ESnapshotFormat getImageFormat() const; - /*virtual*/ LLSnapshotModel::ESnapshotType getSnapshotType(); - /*virtual*/ void updateControls(const LLSD& info); + std::string getWidthSpinnerName() const override { return "local_snapshot_width"; } + std::string getHeightSpinnerName() const override { return "local_snapshot_height"; } + std::string getAspectRatioCBName() const override { return "local_keep_aspect_check"; } + std::string getImageSizeComboName() const override { return "local_size_combo"; } + std::string getImageSizePanelName() const override { return "local_image_size_lp"; } + LLSnapshotModel::ESnapshotFormat getImageFormat() const override; + LLSnapshotModel::ESnapshotType getSnapshotType() override; + void updateControls(const LLSD& info) override; S32 mLocalFormat; @@ -75,7 +75,7 @@ static LLPanelInjector<LLPanelSnapshotLocal> panel_class("llpanelsnapshotlocal") LLPanelSnapshotLocal::LLPanelSnapshotLocal() { mLocalFormat = gSavedSettings.getS32("SnapshotFormat"); - mCommitCallbackRegistrar.add("Local.Cancel", boost::bind(&LLPanelSnapshotLocal::cancel, this)); + mCommitCallbackRegistrar.add("Local.Cancel", { boost::bind(&LLPanelSnapshotLocal::cancel, this) }); } // virtual diff --git a/indra/newview/llpanelsnapshotoptions.cpp b/indra/newview/llpanelsnapshotoptions.cpp index 962d3bba16..eadf8df188 100644 --- a/indra/newview/llpanelsnapshotoptions.cpp +++ b/indra/newview/llpanelsnapshotoptions.cpp @@ -30,12 +30,8 @@ #include "llsidetraypanelcontainer.h" #include "llfloatersnapshot.h" // FIXME: create a snapshot model -#include "llsnapshotlivepreview.h" #include "llfloaterreg.h" -#include "llagentbenefits.h" - - /** * Provides several ways to save a snapshot. */ @@ -46,12 +42,9 @@ class LLPanelSnapshotOptions public: LLPanelSnapshotOptions(); - ~LLPanelSnapshotOptions(); - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); + bool postBuild() override; private: - void updateUploadCost(); void openPanel(const std::string& panel_name); void onSaveToProfile(); void onSaveToEmail(); @@ -65,14 +58,10 @@ static LLPanelInjector<LLPanelSnapshotOptions> panel_class("llpanelsnapshotoptio LLPanelSnapshotOptions::LLPanelSnapshotOptions() { - mCommitCallbackRegistrar.add("Snapshot.SaveToProfile", boost::bind(&LLPanelSnapshotOptions::onSaveToProfile, this)); - mCommitCallbackRegistrar.add("Snapshot.SaveToEmail", boost::bind(&LLPanelSnapshotOptions::onSaveToEmail, this)); - mCommitCallbackRegistrar.add("Snapshot.SaveToInventory", boost::bind(&LLPanelSnapshotOptions::onSaveToInventory, this)); - mCommitCallbackRegistrar.add("Snapshot.SaveToComputer", boost::bind(&LLPanelSnapshotOptions::onSaveToComputer, this)); -} - -LLPanelSnapshotOptions::~LLPanelSnapshotOptions() -{ + mCommitCallbackRegistrar.add("Snapshot.SaveToProfile", { boost::bind(&LLPanelSnapshotOptions::onSaveToProfile, this) }); + mCommitCallbackRegistrar.add("Snapshot.SaveToEmail", { boost::bind(&LLPanelSnapshotOptions::onSaveToEmail, this) }); + mCommitCallbackRegistrar.add("Snapshot.SaveToInventory", { boost::bind(&LLPanelSnapshotOptions::onSaveToInventory, this) }); + mCommitCallbackRegistrar.add("Snapshot.SaveToComputer", { boost::bind(&LLPanelSnapshotOptions::onSaveToComputer, this) }); } // virtual @@ -82,30 +71,6 @@ bool LLPanelSnapshotOptions::postBuild() return LLPanel::postBuild(); } -// virtual -void LLPanelSnapshotOptions::onOpen(const LLSD& key) -{ - updateUploadCost(); -} - -void LLPanelSnapshotOptions::updateUploadCost() -{ - S32 w = 0; - S32 h = 0; - - if( mSnapshotFloater ) - { - LLSnapshotLivePreview* preview = mSnapshotFloater->getPreviewView(); - if( preview ) - { - preview->getSize(w, h); - } - } - - S32 upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost(w, h); - getChild<LLUICtrl>("save_to_inventory_btn")->setLabelArg("[AMOUNT]", llformat("%d", upload_cost)); -} - void LLPanelSnapshotOptions::openPanel(const std::string& panel_name) { LLSideTrayPanelContainer* parent = dynamic_cast<LLSideTrayPanelContainer*>(getParent()); diff --git a/indra/newview/llpanelsnapshotpostcard.cpp b/indra/newview/llpanelsnapshotpostcard.cpp index 23e8789e3f..3a17a1d58c 100644 --- a/indra/newview/llpanelsnapshotpostcard.cpp +++ b/indra/newview/llpanelsnapshotpostcard.cpp @@ -56,18 +56,18 @@ class LLPanelSnapshotPostcard public: LLPanelSnapshotPostcard(); - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); + bool postBuild() override; + void onOpen(const LLSD& key) override; private: - /*virtual*/ std::string getWidthSpinnerName() const { return "postcard_snapshot_width"; } - /*virtual*/ std::string getHeightSpinnerName() const { return "postcard_snapshot_height"; } - /*virtual*/ std::string getAspectRatioCBName() const { return "postcard_keep_aspect_check"; } - /*virtual*/ std::string getImageSizeComboName() const { return "postcard_size_combo"; } - /*virtual*/ std::string getImageSizePanelName() const { return "postcard_image_size_lp"; } - /*virtual*/ LLSnapshotModel::ESnapshotFormat getImageFormat() const { return LLSnapshotModel::SNAPSHOT_FORMAT_JPEG; } - /*virtual*/ LLSnapshotModel::ESnapshotType getSnapshotType(); - /*virtual*/ void updateControls(const LLSD& info); + std::string getWidthSpinnerName() const override { return "postcard_snapshot_width"; } + std::string getHeightSpinnerName() const override { return "postcard_snapshot_height"; } + std::string getAspectRatioCBName() const override { return "postcard_keep_aspect_check"; } + std::string getImageSizeComboName() const override { return "postcard_size_combo"; } + std::string getImageSizePanelName() const override { return "postcard_image_size_lp"; } + LLSnapshotModel::ESnapshotFormat getImageFormat() const override { return LLSnapshotModel::SNAPSHOT_FORMAT_JPEG; } + LLSnapshotModel::ESnapshotType getSnapshotType() override; + void updateControls(const LLSD& info) override; bool missingSubjMsgAlertCallback(const LLSD& notification, const LLSD& response); static void sendPostcardFinished(LLSD result); @@ -86,8 +86,8 @@ static LLPanelInjector<LLPanelSnapshotPostcard> panel_class("llpanelsnapshotpost LLPanelSnapshotPostcard::LLPanelSnapshotPostcard() : mHasFirstMsgFocus(false) { - mCommitCallbackRegistrar.add("Postcard.Send", boost::bind(&LLPanelSnapshotPostcard::onSend, this)); - mCommitCallbackRegistrar.add("Postcard.Cancel", boost::bind(&LLPanelSnapshotPostcard::cancel, this)); + mCommitCallbackRegistrar.add("Postcard.Send", { boost::bind(&LLPanelSnapshotPostcard::onSend, this), cb_info::UNTRUSTED_THROTTLE }); + mCommitCallbackRegistrar.add("Postcard.Cancel", { boost::bind(&LLPanelSnapshotPostcard::cancel, this), cb_info::UNTRUSTED_THROTTLE }); } diff --git a/indra/newview/llpanelsnapshotprofile.cpp b/indra/newview/llpanelsnapshotprofile.cpp index aa257dea9e..960b507099 100644 --- a/indra/newview/llpanelsnapshotprofile.cpp +++ b/indra/newview/llpanelsnapshotprofile.cpp @@ -49,17 +49,17 @@ class LLPanelSnapshotProfile public: LLPanelSnapshotProfile(); - /*virtual*/ bool postBuild(); - /*virtual*/ void onOpen(const LLSD& key); + bool postBuild() override; + void onOpen(const LLSD& key) override; private: - /*virtual*/ std::string getWidthSpinnerName() const { return "profile_snapshot_width"; } - /*virtual*/ std::string getHeightSpinnerName() const { return "profile_snapshot_height"; } - /*virtual*/ std::string getAspectRatioCBName() const { return "profile_keep_aspect_check"; } - /*virtual*/ std::string getImageSizeComboName() const { return "profile_size_combo"; } - /*virtual*/ std::string getImageSizePanelName() const { return "profile_image_size_lp"; } - /*virtual*/ LLSnapshotModel::ESnapshotFormat getImageFormat() const { return LLSnapshotModel::SNAPSHOT_FORMAT_PNG; } - /*virtual*/ void updateControls(const LLSD& info); + std::string getWidthSpinnerName() const override { return "profile_snapshot_width"; } + std::string getHeightSpinnerName() const override { return "profile_snapshot_height"; } + std::string getAspectRatioCBName() const override { return "profile_keep_aspect_check"; } + std::string getImageSizeComboName() const override { return "profile_size_combo"; } + std::string getImageSizePanelName() const override { return "profile_image_size_lp"; } + LLSnapshotModel::ESnapshotFormat getImageFormat() const override { return LLSnapshotModel::SNAPSHOT_FORMAT_PNG; } + void updateControls(const LLSD& info) override; void onSend(); }; @@ -68,8 +68,8 @@ static LLPanelInjector<LLPanelSnapshotProfile> panel_class("llpanelsnapshotprofi LLPanelSnapshotProfile::LLPanelSnapshotProfile() { - mCommitCallbackRegistrar.add("PostToProfile.Send", boost::bind(&LLPanelSnapshotProfile::onSend, this)); - mCommitCallbackRegistrar.add("PostToProfile.Cancel", boost::bind(&LLPanelSnapshotProfile::cancel, this)); + mCommitCallbackRegistrar.add("PostToProfile.Send", { boost::bind(&LLPanelSnapshotProfile::onSend, this), cb_info::UNTRUSTED_BLOCK }); + mCommitCallbackRegistrar.add("PostToProfile.Cancel", { boost::bind(&LLPanelSnapshotProfile::cancel, this), cb_info::UNTRUSTED_BLOCK }); } // virtual diff --git a/indra/newview/llpanelteleporthistory.cpp b/indra/newview/llpanelteleporthistory.cpp index 902412d359..ebfdafdfe2 100644 --- a/indra/newview/llpanelteleporthistory.cpp +++ b/indra/newview/llpanelteleporthistory.cpp @@ -402,7 +402,7 @@ LLTeleportHistoryPanel::~LLTeleportHistoryPanel() bool LLTeleportHistoryPanel::postBuild() { - mCommitCallbackRegistrar.add("TeleportHistory.GearMenu.Action", boost::bind(&LLTeleportHistoryPanel::onGearMenuAction, this, _2)); + mCommitCallbackRegistrar.add("TeleportHistory.GearMenu.Action", { boost::bind(&LLTeleportHistoryPanel::onGearMenuAction, this, _2), cb_info::UNTRUSTED_THROTTLE }); mEnableCallbackRegistrar.add("TeleportHistory.GearMenu.Enable", boost::bind(&LLTeleportHistoryPanel::isActionEnabled, this, _2)); // init menus before list, since menus are passed to list @@ -939,10 +939,9 @@ void LLTeleportHistoryPanel::onAccordionTabRightClick(LLView *view, S32 x, S32 y // set up the callbacks for all of the avatar menu items // (N.B. callbacks don't take const refs as mID is local scope) - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - - registrar.add("TeleportHistory.TabOpen", boost::bind(&LLTeleportHistoryPanel::onAccordionTabOpen, this, tab)); - registrar.add("TeleportHistory.TabClose", boost::bind(&LLTeleportHistoryPanel::onAccordionTabClose, this, tab)); + ScopedRegistrarHelper registrar; + registrar.add("TeleportHistory.TabOpen", boost::bind(&LLTeleportHistoryPanel::onAccordionTabOpen, this, tab)); + registrar.add("TeleportHistory.TabClose", boost::bind(&LLTeleportHistoryPanel::onAccordionTabClose, this, tab)); // create the context menu from the XUI llassert(LLMenuGL::sMenuContainer != NULL); diff --git a/indra/newview/llpaneltopinfobar.cpp b/indra/newview/llpaneltopinfobar.cpp index e7ac11d570..d8a9de596d 100644 --- a/indra/newview/llpaneltopinfobar.cpp +++ b/indra/newview/llpaneltopinfobar.cpp @@ -133,7 +133,7 @@ bool LLPanelTopInfoBar::handleRightMouseDown(S32 x, S32 y, MASK mask) if(!LLUICtrl::CommitCallbackRegistry::getValue("TopInfoBar.Action")) { LLUICtrl::CommitCallbackRegistry::currentRegistrar() - .add("TopInfoBar.Action", boost::bind(&LLPanelTopInfoBar::onContextMenuItemClicked, this, _2)); + .add("TopInfoBar.Action", { boost::bind(&LLPanelTopInfoBar::onContextMenuItemClicked, this, _2) }); } show_topinfobar_context_menu(this, x, y); return true; diff --git a/indra/newview/llpanelvoiceeffect.cpp b/indra/newview/llpanelvoiceeffect.cpp index a0129b2cb1..47f898e3ed 100644 --- a/indra/newview/llpanelvoiceeffect.cpp +++ b/indra/newview/llpanelvoiceeffect.cpp @@ -42,7 +42,7 @@ static LLPanelInjector<LLPanelVoiceEffect> t_panel_voice_effect("panel_voice_eff LLPanelVoiceEffect::LLPanelVoiceEffect() : mVoiceEffectCombo(NULL) { - mCommitCallbackRegistrar.add("Voice.CommitVoiceEffect", boost::bind(&LLPanelVoiceEffect::onCommitVoiceEffect, this)); + mCommitCallbackRegistrar.add("Voice.CommitVoiceEffect", { boost::bind(&LLPanelVoiceEffect::onCommitVoiceEffect, this), cb_info::UNTRUSTED_BLOCK }); } LLPanelVoiceEffect::~LLPanelVoiceEffect() diff --git a/indra/newview/llpanelvolume.cpp b/indra/newview/llpanelvolume.cpp index 951dc45a78..adbf2ebf6e 100644 --- a/indra/newview/llpanelvolume.cpp +++ b/indra/newview/llpanelvolume.cpp @@ -223,7 +223,7 @@ LLPanelVolume::LLPanelVolume() { setMouseOpaque(false); - mCommitCallbackRegistrar.add("PanelVolume.menuDoToSelected", boost::bind(&LLPanelVolume::menuDoToSelected, this, _2)); + mCommitCallbackRegistrar.add("PanelVolume.menuDoToSelected", { boost::bind(&LLPanelVolume::menuDoToSelected, this, _2) }); mEnableCallbackRegistrar.add("PanelVolume.menuEnable", boost::bind(&LLPanelVolume::menuEnableItem, this, _2)); } diff --git a/indra/newview/llpanelvolumepulldown.cpp b/indra/newview/llpanelvolumepulldown.cpp index 046bcd7f59..05b21d4d48 100644 --- a/indra/newview/llpanelvolumepulldown.cpp +++ b/indra/newview/llpanelvolumepulldown.cpp @@ -48,10 +48,10 @@ // Default constructor LLPanelVolumePulldown::LLPanelVolumePulldown() { - mCommitCallbackRegistrar.add("Vol.setControlFalse", boost::bind(&LLPanelVolumePulldown::setControlFalse, this, _2)); - mCommitCallbackRegistrar.add("Vol.SetSounds", boost::bind(&LLPanelVolumePulldown::onClickSetSounds, this)); - mCommitCallbackRegistrar.add("Vol.updateCheckbox", boost::bind(&LLPanelVolumePulldown::updateCheckbox, this, _1, _2)); - mCommitCallbackRegistrar.add("Vol.GoAudioPrefs", boost::bind(&LLPanelVolumePulldown::onAdvancedButtonClick, this, _2)); + mCommitCallbackRegistrar.add("Vol.setControlFalse", { boost::bind(&LLPanelVolumePulldown::setControlFalse, this, _2) }); + mCommitCallbackRegistrar.add("Vol.SetSounds", { boost::bind(&LLPanelVolumePulldown::onClickSetSounds, this) }); + mCommitCallbackRegistrar.add("Vol.updateCheckbox", { boost::bind(&LLPanelVolumePulldown::updateCheckbox, this, _1, _2) }); + mCommitCallbackRegistrar.add("Vol.GoAudioPrefs", { boost::bind(&LLPanelVolumePulldown::onAdvancedButtonClick, this, _2) }); buildFromFile( "panel_volume_pulldown.xml"); } diff --git a/indra/newview/llpanelwearing.cpp b/indra/newview/llpanelwearing.cpp index c1534c9abd..1056f73d58 100644 --- a/indra/newview/llpanelwearing.cpp +++ b/indra/newview/llpanelwearing.cpp @@ -62,7 +62,7 @@ public: LLWearingGearMenu(LLPanelWearing* panel_wearing) : mMenu(NULL), mPanelWearing(panel_wearing) { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::ScopedRegistrarHelper registrar; LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; registrar.add("Gear.TouchAttach", boost::bind(&LLWearingGearMenu::handleMultiple, this, handle_attachment_touch)); @@ -103,7 +103,7 @@ class LLWearingContextMenu : public LLListContextMenu protected: /* virtual */ LLContextMenu* createMenu() { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::ScopedRegistrarHelper registrar; registrar.add("Wearing.TouchAttach", boost::bind(handleMultiple, handle_attachment_touch, mUUIDs)); registrar.add("Wearing.EditItem", boost::bind(handleMultiple, handle_item_edit, mUUIDs)); @@ -180,7 +180,7 @@ public: protected: /* virtual */ LLContextMenu* createMenu() { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::ScopedRegistrarHelper registrar; registrar.add("Wearing.EditItem", boost::bind(&LLPanelWearing::onEditAttachment, mPanelWearing)); registrar.add("Wearing.Detach", boost::bind(&LLPanelWearing::onRemoveAttachment, mPanelWearing)); diff --git a/indra/newview/llphysicsshapebuilderutil.cpp b/indra/newview/llphysicsshapebuilderutil.cpp index 37534feadc..eb0df1194e 100644 --- a/indra/newview/llphysicsshapebuilderutil.cpp +++ b/indra/newview/llphysicsshapebuilderutil.cpp @@ -28,6 +28,26 @@ #include "llphysicsshapebuilderutil.h" +#include "llmeshrepository.h" + +bool LLPhysicsVolumeParams::hasDecomposition() const + { + if (!isMeshSculpt()) + { + return false; + } + + LLUUID mesh_id = getSculptID(); + if (mesh_id.isNull()) + { + return false; + } + + LLModel::Decomposition* decomp = gMeshRepo.getDecomposition(mesh_id); + + return decomp != NULL; +} + /* static */ void LLPhysicsShapeBuilderUtil::determinePhysicsShape( const LLPhysicsVolumeParams& volume_params, const LLVector3& scale, PhysicsShapeSpecification& specOut) { @@ -200,19 +220,32 @@ void LLPhysicsShapeBuilderUtil::determinePhysicsShape( const LLPhysicsVolumePara { specOut.mType = PhysicsShapeSpecification::PRIM_CONVEX; } - else if (volume_params.isMeshSculpt() && - // Check overall dimensions, not individual triangles. - (scale.mV[0] < SHAPE_BUILDER_USER_MESH_CONVEXIFICATION_SIZE || - scale.mV[1] < SHAPE_BUILDER_USER_MESH_CONVEXIFICATION_SIZE || - scale.mV[2] < SHAPE_BUILDER_USER_MESH_CONVEXIFICATION_SIZE - ) ) + else if (volume_params.isMeshSculpt()) { - // Server distinguishes between user-specified or default convex mesh, vs server's thin-triangle override, but we don't. - specOut.mType = PhysicsShapeSpecification::PRIM_CONVEX; + // Check overall dimensions, not individual triangles. + if (scale.mV[0] < SHAPE_BUILDER_USER_MESH_CONVEXIFICATION_SIZE + || scale.mV[1] < SHAPE_BUILDER_USER_MESH_CONVEXIFICATION_SIZE + || scale.mV[2] < SHAPE_BUILDER_USER_MESH_CONVEXIFICATION_SIZE + ) + { + if (volume_params.hasDecomposition()) + { + specOut.mType = PhysicsShapeSpecification::USER_MESH; + } + else + { + // Server distinguishes between user-specified or default convex mesh, vs server's thin-triangle override, but we don't. + specOut.mType = PhysicsShapeSpecification::PRIM_CONVEX; + } + } + else + { + specOut.mType = PhysicsShapeSpecification::USER_MESH; + } } - else if ( volume_params.isSculpt() ) // Is a sculpt of any kind (mesh or legacy) + else if ( volume_params.isSculpt() ) { - specOut.mType = volume_params.isMeshSculpt() ? PhysicsShapeSpecification::USER_MESH : PhysicsShapeSpecification::SCULPT; + specOut.mType = PhysicsShapeSpecification::SCULPT; } else // Resort to mesh { diff --git a/indra/newview/llphysicsshapebuilderutil.h b/indra/newview/llphysicsshapebuilderutil.h index 33c2d0a8b6..01c173523b 100644 --- a/indra/newview/llphysicsshapebuilderutil.h +++ b/indra/newview/llphysicsshapebuilderutil.h @@ -79,6 +79,8 @@ public: bool shouldForceConvex() const { return mForceConvex; } + bool hasDecomposition() const; + private: bool mForceConvex; }; diff --git a/indra/newview/llpreviewanim.cpp b/indra/newview/llpreviewanim.cpp index 7d11c09738..01a3902516 100644 --- a/indra/newview/llpreviewanim.cpp +++ b/indra/newview/llpreviewanim.cpp @@ -46,7 +46,7 @@ const S32 ADVANCED_VPAD = 3; LLPreviewAnim::LLPreviewAnim(const LLSD& key) : LLPreview( key ) { - mCommitCallbackRegistrar.add("PreviewAnim.Play", boost::bind(&LLPreviewAnim::play, this, _2)); + mCommitCallbackRegistrar.add("PreviewAnim.Play", { boost::bind(&LLPreviewAnim::play, this, _2) }); } // virtual diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp index 02a4c7fb26..fa99432603 100644 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -503,7 +503,7 @@ bool LLScriptEdCore::postBuild() LLSyntaxIdLSL::getInstance()->initialize(); processKeywords(); - mCommitCallbackRegistrar.add("FontSize.Set", boost::bind(&LLScriptEdCore::onChangeFontSize, this, _2)); + mCommitCallbackRegistrar.add("FontSize.Set", { boost::bind(&LLScriptEdCore::onChangeFontSize, this, _2) }); mEnableCallbackRegistrar.add("FontSize.Check", boost::bind(&LLScriptEdCore::isFontSizeChecked, this, _2)); LLToggleableMenu *context_menu = LLUICtrlFactory::getInstance()->createFromFile<LLToggleableMenu>( diff --git a/indra/newview/llsaveoutfitcombobtn.cpp b/indra/newview/llsaveoutfitcombobtn.cpp index 6418c310c1..bc1bdfdcda 100644 --- a/indra/newview/llsaveoutfitcombobtn.cpp +++ b/indra/newview/llsaveoutfitcombobtn.cpp @@ -39,7 +39,7 @@ LLSaveOutfitComboBtn::LLSaveOutfitComboBtn(LLPanel* parent, bool saveAsDefaultAc mParent(parent), mSaveAsDefaultAction(saveAsDefaultAction) { // register action mapping before creating menu - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar save_registar; + LLUICtrl::ScopedRegistrarHelper save_registar; save_registar.add("Outfit.Save.Action", boost::bind( &LLSaveOutfitComboBtn::saveOutfit, this, false)); save_registar.add("Outfit.SaveAs.Action", boost::bind( diff --git a/indra/newview/llsecapi.h b/indra/newview/llsecapi.h index 14dda20225..73920d35ad 100644 --- a/indra/newview/llsecapi.h +++ b/indra/newview/llsecapi.h @@ -312,7 +312,7 @@ public: mIdentifier = identifier; mAuthenticator = authenticator; } - virtual LLSD getIdentifier() { return mIdentifier; } + virtual LLSD getIdentifier() const { return mIdentifier; } virtual void identifierType(std::string& idType); virtual LLSD getAuthenticator() { return mAuthenticator; } virtual void authenticatorType(std::string& authType); diff --git a/indra/newview/llsetkeybinddialog.cpp b/indra/newview/llsetkeybinddialog.cpp index 5dbd579b45..021a9feb73 100644 --- a/indra/newview/llsetkeybinddialog.cpp +++ b/indra/newview/llsetkeybinddialog.cpp @@ -46,14 +46,12 @@ public: :LLEventTimer(period), mMask(mask), mCallback(cb) - { - mEventTimer.start(); - } + {} virtual ~Updater(){} protected: - bool tick() + bool tick() override { mCallback(mMask); // Deletes itseft after execution diff --git a/indra/newview/llsnapshotlivepreview.cpp b/indra/newview/llsnapshotlivepreview.cpp index 0b73aa493c..ea95d71b27 100644 --- a/indra/newview/llsnapshotlivepreview.cpp +++ b/indra/newview/llsnapshotlivepreview.cpp @@ -295,13 +295,20 @@ void LLSnapshotLivePreview::draw() gGL.pushMatrix(); { gGL.translatef((F32)rect.mLeft, (F32)rect.mBottom + TOP_PANEL_HEIGHT, 0.f); - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { gGL.texCoord2f(uv_width, uv_height); - gGL.vertex2i(rect.getWidth(), rect.getHeight() ); + gGL.vertex2i(rect.getWidth(), rect.getHeight()); gGL.texCoord2f(0.f, uv_height); - gGL.vertex2i(0, rect.getHeight() ); + gGL.vertex2i(0, rect.getHeight()); + + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2i(0, 0); + + + gGL.texCoord2f(uv_width, uv_height); + gGL.vertex2i(rect.getWidth(), rect.getHeight()); gGL.texCoord2f(0.f, 0.f); gGL.vertex2i(0, 0); @@ -357,13 +364,18 @@ void LLSnapshotLivePreview::draw() S32 y2 = gViewerWindow->getWindowHeightScaled() + TOP_PANEL_HEIGHT; gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { gGL.color4f(1.f, 1.f, 1.f, 0.f); gGL.vertex2i(x1, y1); gGL.vertex2i(x1 + gViewerWindow->getWindowWidthScaled(), y2); gGL.color4f(1.f, 1.f, 1.f, SHINE_OPACITY); gGL.vertex2i(x2 + gViewerWindow->getWindowWidthScaled(), y2); + + gGL.color4f(1.f, 1.f, 1.f, 0.f); + gGL.vertex2i(x1, y1); + gGL.color4f(1.f, 1.f, 1.f, SHINE_OPACITY); + gGL.vertex2i(x2 + gViewerWindow->getWindowWidthScaled(), y2); gGL.vertex2i(x2, y1); gGL.color4f(1.f, 1.f, 1.f, SHINE_OPACITY); @@ -371,6 +383,11 @@ void LLSnapshotLivePreview::draw() gGL.vertex2i(x2 + gViewerWindow->getWindowWidthScaled(), y2); gGL.color4f(1.f, 1.f, 1.f, 0.f); gGL.vertex2i(x3 + gViewerWindow->getWindowWidthScaled(), y2); + + gGL.color4f(1.f, 1.f, 1.f, SHINE_OPACITY); + gGL.vertex2i(x2, y1); + gGL.color4f(1.f, 1.f, 1.f, 0.f); + gGL.vertex2i(x3 + gViewerWindow->getWindowWidthScaled(), y2); gGL.vertex2i(x3, y1); } gGL.end(); @@ -406,13 +423,19 @@ void LLSnapshotLivePreview::draw() LLRect& rect = mImageRect[old_image_index]; gGL.translatef((F32)rect.mLeft, (F32)rect.mBottom - ll_round(getRect().getHeight() * 2.f * (fall_interp * fall_interp)), 0.f); gGL.rotatef(-45.f * fall_interp, 0.f, 0.f, 1.f); - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { gGL.texCoord2f(uv_width, uv_height); - gGL.vertex2i(rect.getWidth(), rect.getHeight() ); + gGL.vertex2i(rect.getWidth(), rect.getHeight()); gGL.texCoord2f(0.f, uv_height); - gGL.vertex2i(0, rect.getHeight() ); + gGL.vertex2i(0, rect.getHeight()); + + gGL.texCoord2f(0.f, 0.f); + gGL.vertex2i(0, 0); + + gGL.texCoord2f(uv_width, uv_height); + gGL.vertex2i(rect.getWidth(), rect.getHeight()); gGL.texCoord2f(0.f, 0.f); gGL.vertex2i(0, 0); @@ -667,18 +690,25 @@ bool LLSnapshotLivePreview::onIdle( void* snapshot_preview ) return false; } + static LLCachedControl<bool> auto_snapshot(gSavedSettings, "AutoSnapshot", false); + static LLCachedControl<bool> freeze_time(gSavedSettings, "FreezeTime", false); + static LLCachedControl<bool> use_freeze_frame(gSavedSettings, "UseFreezeFrame", false); + static LLCachedControl<bool> render_ui(gSavedSettings, "RenderUIInSnapshot", false); + static LLCachedControl<bool> render_hud(gSavedSettings, "RenderHUDInSnapshot", false); + static LLCachedControl<bool> render_no_post(gSavedSettings, "RenderSnapshotNoPost", false); + // If we're in freeze-frame and/or auto update mode and camera has moved, update snapshot. LLVector3 new_camera_pos = LLViewerCamera::getInstance()->getOrigin(); LLQuaternion new_camera_rot = LLViewerCamera::getInstance()->getQuaternion(); if (previewp->mForceUpdateSnapshot || - (((gSavedSettings.getBOOL("AutoSnapshot") && LLView::isAvailable(previewp->mViewContainer)) || - (gSavedSettings.getBOOL("FreezeTime") && previewp->mAllowFullScreenPreview)) && + (((auto_snapshot && LLView::isAvailable(previewp->mViewContainer)) || + (freeze_time && previewp->mAllowFullScreenPreview)) && (new_camera_pos != previewp->mCameraPos || dot(new_camera_rot, previewp->mCameraRot) < 0.995f))) { previewp->mCameraPos = new_camera_pos; previewp->mCameraRot = new_camera_rot; // request a new snapshot whenever the camera moves, with a time delay - bool new_snapshot = gSavedSettings.getBOOL("AutoSnapshot") || previewp->mForceUpdateSnapshot; + bool new_snapshot = auto_snapshot || previewp->mForceUpdateSnapshot; LL_DEBUGS("Snapshot") << "camera moved, updating thumbnail" << LL_ENDL; previewp->updateSnapshot( new_snapshot, // whether a new snapshot is needed or merely invalidate the existing one @@ -716,10 +746,10 @@ bool LLSnapshotLivePreview::onIdle( void* snapshot_preview ) previewp->getHeight(), previewp->mKeepAspectRatio,//gSavedSettings.getBOOL("KeepAspectForSnapshot"), previewp->getSnapshotType() == LLSnapshotModel::SNAPSHOT_TEXTURE, - previewp->mAllowRenderUI && gSavedSettings.getBOOL("RenderUIInSnapshot"), - gSavedSettings.getBOOL("RenderHUDInSnapshot"), + previewp->mAllowRenderUI && render_ui, + render_hud, false, - gSavedSettings.getBOOL("RenderSnapshotNoPost"), + render_no_post, previewp->mSnapshotBufferType, previewp->getMaxImageSize())) { @@ -731,7 +761,7 @@ bool LLSnapshotLivePreview::onIdle( void* snapshot_preview ) previewp->estimateDataSize(); // Full size preview is set: get the decoded image result and save it for animation - if (gSavedSettings.getBOOL("UseFreezeFrame") && previewp->mAllowFullScreenPreview) + if (use_freeze_frame && previewp->mAllowFullScreenPreview) { previewp->prepareFreezeFrame(); } @@ -744,7 +774,7 @@ bool LLSnapshotLivePreview::onIdle( void* snapshot_preview ) previewp->generateThumbnailImage(true) ; } previewp->getWindow()->decBusyCount(); - previewp->setVisible(gSavedSettings.getBOOL("UseFreezeFrame") && previewp->mAllowFullScreenPreview); // only show fullscreen preview when in freeze frame mode + previewp->setVisible(use_freeze_frame && previewp->mAllowFullScreenPreview); // only show fullscreen preview when in freeze frame mode previewp->mSnapshotActive = false; LL_DEBUGS("Snapshot") << "done creating snapshot" << LL_ENDL; } @@ -864,7 +894,9 @@ LLPointer<LLImageRaw> LLSnapshotLivePreview::getEncodedImage() else { // Update mFormattedImage if necessary - getFormattedImage(); + lock.unlock(); + getFormattedImage(); // will apply filters to mPreviewImage with a lock + lock.lock(); if (getSnapshotFormat() == LLSnapshotModel::SNAPSHOT_FORMAT_BMP) { // BMP hack : copy instead of decode otherwise decode will crash. diff --git a/indra/newview/llspeakers.h b/indra/newview/llspeakers.h index ad2461f60f..077a949c05 100644 --- a/indra/newview/llspeakers.h +++ b/indra/newview/llspeakers.h @@ -37,7 +37,7 @@ class LLSpeakerMgr; class LLAvatarName; // data for a given participant in a voice channel -class LLSpeaker : public LLRefCount, public LLOldEvents::LLObservable, public LLHandleProvider<LLSpeaker>, public boost::signals2::trackable +class LLSpeaker : public LLRefCount, public LLOldEvents::LLObservable, public LLHandleProvider<LLSpeaker> { public: typedef enum e_speaker_type @@ -159,7 +159,7 @@ public: * * If action callback is not specified returns true. Instance will be deleted by LLEventTimer::updateClass(). */ - virtual bool tick(); + bool tick() override; /** * Clears the callback. diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 3b027df460..dd8c6d989e 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -181,7 +181,6 @@ #include "llnamelistctrl.h" #include "llnamebox.h" #include "llnameeditor.h" -#include "llpostprocess.h" #include "llagentlanguage.h" #include "llwearable.h" #include "llinventorybridge.h" @@ -404,10 +403,10 @@ bool idle_startup() static bool first_call = true; if (first_call) { + first_call = false; // Other phases get handled when startup state changes, // need to capture the initial state as well. LLStartUp::getPhases().startPhase(LLStartUp::getStartupStateString()); - first_call = false; } gViewerWindow->showCursor(); @@ -1295,10 +1294,6 @@ bool idle_startup() LLDrawable::initClass(); display_startup(); - // init the shader managers - LLPostProcess::initClass(); - display_startup(); - LLAvatarAppearance::initClass("avatar_lad.xml","avatar_skeleton.xml"); display_startup(); diff --git a/indra/newview/lltoast.cpp b/indra/newview/lltoast.cpp index 84503e66a5..43b7294b9a 100644 --- a/indra/newview/lltoast.cpp +++ b/indra/newview/lltoast.cpp @@ -43,34 +43,21 @@ LLToastLifeTimer::LLToastLifeTimer(LLToast* toast, F32 period) { } -/*virtual*/ bool LLToastLifeTimer::tick() { - if (mEventTimer.hasExpired()) - { - mToast->expire(); - } + mToast->expire(); return false; } -void LLToastLifeTimer::stop() -{ - mEventTimer.stop(); -} - -void LLToastLifeTimer::start() -{ - mEventTimer.start(); -} - void LLToastLifeTimer::restart() { - mEventTimer.reset(); + // start() discards any previously-running mTimer + start(); } bool LLToastLifeTimer::getStarted() { - return mEventTimer.getStarted(); + return isRunning(); } void LLToastLifeTimer::setPeriod(F32 period) @@ -78,13 +65,6 @@ void LLToastLifeTimer::setPeriod(F32 period) mPeriod = period; } -F32 LLToastLifeTimer::getRemainingTimeF32() -{ - F32 et = mEventTimer.getElapsedTimeF32(); - if (!getStarted() || et > mPeriod) return 0.0f; - return mPeriod - et; -} - //-------------------------------------------------------------------------- LLToast::Params::Params() : can_fade("can_fade", true), @@ -337,7 +317,7 @@ void LLToast::setFading(bool transparent) F32 LLToast::getTimeLeftToLive() { - F32 time_to_live = mTimer->getRemainingTimeF32(); + F32 time_to_live = mTimer->getRemaining(); if (!mIsFading) { diff --git a/indra/newview/lltoast.h b/indra/newview/lltoast.h index cf116bfadf..dd2ced7352 100644 --- a/indra/newview/lltoast.h +++ b/indra/newview/lltoast.h @@ -53,15 +53,11 @@ public: LLToastLifeTimer(LLToast* toast, F32 period); /*virtual*/ - bool tick(); - void stop(); - void start(); + bool tick() override; void restart(); bool getStarted(); void setPeriod(F32 period); - F32 getRemainingTimeF32(); - LLTimer& getEventTimer() { return mEventTimer;} private : LLToast* mToast; }; diff --git a/indra/newview/lltoastalertpanel.h b/indra/newview/lltoastalertpanel.h index 167a7b2363..c2cdd775f1 100644 --- a/indra/newview/lltoastalertpanel.h +++ b/indra/newview/lltoastalertpanel.h @@ -82,14 +82,10 @@ private: struct ButtonData { - ButtonData() - : mWidth(0) - {} - - LLButton* mButton; + LLButton* mButton = nullptr; std::string mURL; - U32 mURLExternal; - S32 mWidth; + U32 mURLExternal = 0; + S32 mWidth = 0; }; std::vector<ButtonData> mButtonData; diff --git a/indra/newview/lltoastimpanel.cpp b/indra/newview/lltoastimpanel.cpp index 42ee30409d..df79af4926 100644 --- a/indra/newview/lltoastimpanel.cpp +++ b/indra/newview/lltoastimpanel.cpp @@ -87,7 +87,10 @@ LLToastIMPanel::LLToastIMPanel(LLToastIMPanel::Params &p) : LLToastPanel(p.notif { LLAvatarName avatar_name; LLAvatarNameCache::get(p.avatar_id, &avatar_name); - p.message = "[From " + avatar_name.getDisplayName() + "]\n" + p.message; + // move Lua prefix from the message field to the [From] field + auto [message, is_lua] = LLStringUtil::withoutPrefix(p.message, LUA_PREFIX); + std::string prefix = is_lua ? "LUA - " : ""; + p.message = "[From " + prefix + avatar_name.getDisplayName() + "]\n" + message; } style_params.font.style = "NORMAL"; mMessage->setText(p.message, style_params); diff --git a/indra/newview/lltoolmgr.cpp b/indra/newview/lltoolmgr.cpp index 07963a7bed..2ffb571201 100644 --- a/indra/newview/lltoolmgr.cpp +++ b/indra/newview/lltoolmgr.cpp @@ -84,9 +84,9 @@ LLToolMgr::LLToolMgr() LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.Active", boost::bind(&LLToolMgr::inEdit, this)); LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.Enabled", boost::bind(&LLToolMgr::canEdit, this)); LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Build.EnabledOrActive", boost::bind(&LLToolMgr::buildEnabledOrActive, this)); - LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Build.Toggle", boost::bind(&LLToolMgr::toggleBuildMode, this, _2)); + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Build.Toggle", { boost::bind(&LLToolMgr::toggleBuildMode, this, _2) }); LLUICtrl::EnableCallbackRegistry::currentRegistrar().add("Marketplace.Enabled", boost::bind(&LLToolMgr::canAccessMarketplace, this)); - LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Marketplace.Toggle", boost::bind(&LLToolMgr::toggleMarketplace, this, _2)); + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Marketplace.Toggle", { boost::bind(&LLToolMgr::toggleMarketplace, this, _2) }); gToolNull = new LLTool(LLStringUtil::null); // Does nothing setCurrentTool(gToolNull); diff --git a/indra/newview/lltoolmorph.cpp b/indra/newview/lltoolmorph.cpp index 24cfca5eee..8ce9a8b632 100644 --- a/indra/newview/lltoolmorph.cpp +++ b/indra/newview/lltoolmorph.cpp @@ -285,7 +285,7 @@ void LLVisualParamHint::draw(F32 alpha) gGL.color4f(1.f, 1.f, 1.f, alpha); LLGLSUIDefault gls_ui; - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { gGL.texCoord2i(0, 1); gGL.vertex2i(0, mFullHeight); @@ -293,6 +293,11 @@ void LLVisualParamHint::draw(F32 alpha) gGL.vertex2i(0, 0); gGL.texCoord2i(1, 0); gGL.vertex2i(mFullWidth, 0); + + gGL.texCoord2i(0, 1); + gGL.vertex2i(0, mFullHeight); + gGL.texCoord2i(1, 0); + gGL.vertex2i(mFullWidth, 0); gGL.texCoord2i(1, 1); gGL.vertex2i(mFullWidth, mFullHeight); } diff --git a/indra/newview/lltoolplacer.cpp b/indra/newview/lltoolplacer.cpp index b15bb5efd5..2ac5d6b6b1 100644 --- a/indra/newview/lltoolplacer.cpp +++ b/indra/newview/lltoolplacer.cpp @@ -164,13 +164,17 @@ bool LLToolPlacer::addObject( LLPCode pcode, S32 x, S32 y, U8 use_physics ) bool b_hit_land = false; S32 hit_face = -1; LLViewerObject* hit_obj = NULL; - U8 state = 0; bool success = raycastForNewObjPos( x, y, &hit_obj, &hit_face, &b_hit_land, &ray_start_region, &ray_end_region, ®ionp ); if( !success ) { return false; } + return rezNewObject(pcode,hit_obj, hit_face, b_hit_land, ray_start_region, ray_end_region, regionp, use_physics); +} +bool LLToolPlacer::rezNewObject(LLPCode pcode, LLViewerObject * hit_obj, S32 hit_face, bool b_hit_land, LLVector3 ray_start_region, + LLVector3 ray_end_region, LLViewerRegion* regionp, U8 use_physics) +{ if( hit_obj && (hit_obj->isAvatar() || hit_obj->isAttachment()) ) { // Can't create objects on avatars or attachments @@ -195,6 +199,8 @@ bool LLToolPlacer::addObject( LLPCode pcode, S32 x, S32 y, U8 use_physics ) bool create_selected = false; LLVolumeParams volume_params; + U8 state = 0; + switch (pcode) { case LL_PCODE_LEGACY_GRASS: diff --git a/indra/newview/lltoolplacer.h b/indra/newview/lltoolplacer.h index f9501f83b2..92446c319b 100644 --- a/indra/newview/lltoolplacer.h +++ b/indra/newview/lltoolplacer.h @@ -50,6 +50,10 @@ public: static void setObjectType( LLPCode type ) { sObjectType = type; } static LLPCode getObjectType() { return sObjectType; } +// static bool addObject(LLPCode pcode, S32 x, S32 y, U8 use_physics); + static bool rezNewObject(LLPCode pcode, LLViewerObject* hit_obj, S32 hit_face, bool b_hit_land, LLVector3 ray_start_region, + LLVector3 ray_end_region, LLViewerRegion *regionp, U8 use_physics); + protected: static LLPCode sObjectType; diff --git a/indra/newview/lltoolselect.cpp b/indra/newview/lltoolselect.cpp index 5ccda7d4eb..3bd459f5b0 100644 --- a/indra/newview/lltoolselect.cpp +++ b/indra/newview/lltoolselect.cpp @@ -55,17 +55,17 @@ LLToolSelect::LLToolSelect( LLToolComposite* composite ) : LLTool( std::string("Select"), composite ), mIgnoreGroup( false ) { - } +} // True if you selected an object. bool LLToolSelect::handleMouseDown(S32 x, S32 y, MASK mask) { // do immediate pick query bool pick_rigged = false; //gSavedSettings.getBOOL("AnimatedObjectsAllowLeftClick"); - bool pick_transparent = gSavedSettings.getBOOL("SelectInvisibleObjects"); - bool pick_reflection_probe = gSavedSettings.getBOOL("SelectReflectionProbes"); + static LLCachedControl<bool> select_invisible_objects(gSavedSettings, "SelectInvisibleObjects"); + static LLCachedControl<bool> select_reflection_probes(gSavedSettings, "SelectReflectionProbes"); - mPick = gViewerWindow->pickImmediate(x, y, pick_transparent, pick_rigged, false, true, pick_reflection_probe); + mPick = gViewerWindow->pickImmediate(x, y, select_invisible_objects, pick_rigged, false, true, select_reflection_probes); // Pass mousedown to agent LLTool::handleMouseDown(x, y, mask); @@ -73,7 +73,6 @@ bool LLToolSelect::handleMouseDown(S32 x, S32 y, MASK mask) return mPick.getObject().notNull(); } - // static LLObjectSelectionHandle LLToolSelect::handleObjectSelection(const LLPickInfo& pick, bool ignore_group, bool temp_select, bool select_root) { diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp index beae71e7bf..44eb8461f1 100644 --- a/indra/newview/lluilistener.cpp +++ b/indra/newview/lluilistener.cpp @@ -34,55 +34,195 @@ // std headers // external library headers // other Linden headers +#include "llmenugl.h" +#include "lltoolbarview.h" #include "llui.h" // getRootView(), resolvePath() #include "lluictrl.h" #include "llerror.h" +#include "llviewermenufile.h" // close_all_windows() +extern LLMenuBarGL* gMenuBarView; + +#define THROTTLE_PERIOD 1.5 // required seconds between throttled functions +#define MIN_THROTTLE 0.5 LLUIListener::LLUIListener(): LLEventAPI("UI", - "LLUICtrl::CommitCallbackRegistry listener.\n" - "Capable of invoking any function (with parameter) you can specify in XUI.") + "Operations to manipulate the viewer's user interface.") { add("call", "Invoke the operation named by [\"function\"], passing [\"parameter\"],\n" "as if from a user gesture on a menu -- or a button click.", &LLUIListener::call, - LLSD().with("function", LLSD())); + llsd::map("function", LLSD(), "reply", LLSD())); + + add("callables", + "Return a list [\"callables\"] of dicts {name, access} of functions registered to\n" + "invoke with \"call\".\n" + "access has values \"allow\", \"block\" or \"throttle\".", + &LLUIListener::callables, + llsd::map("reply", LLSD::String())); add("getValue", "For the UI control identified by the path in [\"path\"], return the control's\n" "current value as [\"value\"] reply.", &LLUIListener::getValue, - LLSDMap("path", LLSD())("reply", LLSD())); + llsd::map("path", LLSD(), "reply", LLSD())); + + add("getTopMenus", + "List names of Top menus suitable for passing as \"parent_menu\"", + &LLUIListener::getTopMenus, + llsd::map("reply", LLSD::String())); + + LLSD required_args = llsd::map("name", LLSD(), "label", LLSD(), "reply", LLSD()); + add("addMenu", + "Add new drop-down menu [\"name\"] with displayed [\"label\"] to the Top menu.", + &LLUIListener::addMenu, + required_args); + + required_args.insert("parent_menu", LLSD()); + add("addMenuBranch", + "Add new menu branch [\"name\"] with displayed [\"label\"]\n" + "to the [\"parent_menu\"] within the Top menu.", + &LLUIListener::addMenuBranch, + required_args); + + add("addMenuItem", + "Add new menu item [\"name\"] with displayed [\"label\"]\n" + "and call-on-click UI function [\"func\"] with optional [\"param\"]\n" + "to the [\"parent_menu\"] within the Top menu.\n" + "If [\"pos\"] is present, insert at specified 0-relative position.", + &LLUIListener::addMenuItem, + required_args.with("func", LLSD())); + + add("addMenuSeparator", + "Add menu separator to the [\"parent_menu\"] within the Top menu.\n" + "If [\"pos\"] is present, insert at specified 0-relative position.", + &LLUIListener::addMenuSeparator, + llsd::map("parent_menu", LLSD(), "reply", LLSD())); + + add("setMenuVisible", + "Set menu [\"name\"] visibility to [\"visible\"]", + &LLUIListener::setMenuVisible, + llsd::map("name", LLSD(), "visible", LLSD(), "reply", LLSD())); + + add("defaultToolbars", + "Restore default toolbar buttons", + &LLUIListener::restoreDefaultToolbars); + + add("clearAllToolbars", + "Clear all buttons off the toolbars", + &LLUIListener::clearAllToolbars); + + add("addToolbarBtn", + "Add [\"btn_name\"] toolbar button to the [\"toolbar\"]:\n" + "\"left\", \"right\", \"bottom\" (default is \"bottom\")\n" + "Position of the command in the original list can be specified as [\"rank\"],\n" + "where 0 means the first item", + &LLUIListener::addToolbarBtn, + llsd::map("btn_name", LLSD(), "reply", LLSD())); + + add("removeToolbarBtn", + "Remove [\"btn_name\"] toolbar button off the toolbar,\n" + "return [\"rank\"] (old position) of the command in the original list,\n" + "rank 0 is the first position,\n" + "rank -1 means that [\"btn_name\"] was not found", + &LLUIListener::removeToolbarBtn, + llsd::map("btn_name", LLSD(), "reply", LLSD())); + + add("getToolbarBtnNames", + "Return the table of Toolbar buttons names", + &LLUIListener::getToolbarBtnNames, + llsd::map("reply", LLSD())); + + add("closeAllFloaters", + "Close all the floaters", + &LLUIListener::closeAllFloaters); } -void LLUIListener::call(const LLSD& event) const +typedef LLUICtrl::CommitCallbackInfo cb_info; +void LLUIListener::call(const LLSD& event) { - LLUICtrl::commit_callback_t* func = - LLUICtrl::CommitCallbackRegistry::getValue(event["function"]); - if (! func) + Response response(LLSD(), event); + cb_info *info = LLUICtrl::CommitCallbackRegistry::getValue(event["function"]); + if (!info || !info->callback_func) + { + return response.error(stringize("Function ", std::quoted(event["function"].asString()), " was not found")); + } + if (info->handle_untrusted == cb_info::UNTRUSTED_BLOCK) + { + return response.error(stringize("Function ", std::quoted(event["function"].asString()), " may not be called from the script")); + } + + //Separate UNTRUSTED_THROTTLE and UNTRUSTED_ALLOW functions to have different timeout + F64 *throttlep, period; + if (info->handle_untrusted == cb_info::UNTRUSTED_THROTTLE) { - // This API is intended for use by a script. It's a fire-and-forget - // API: we provide no reply. Therefore, a typo in the script will - // provide no feedback whatsoever to that script. To rub the coder's - // nose in such an error, crump rather than quietly ignoring it. - LL_ERRS("LLUIListener") << "function '" << event["function"] << "' not found" << LL_ENDL; + throttlep = &mLastUntrustedThrottle; + period = THROTTLE_PERIOD; } else { - // Interestingly, view_listener_t::addMenu() (addCommit(), - // addEnable()) constructs a commit_callback_t callable that accepts - // two parameters but discards the first. Only the second is passed to - // handleEvent(). Therefore we feel completely safe passing NULL for - // the first parameter. - (*func)(NULL, event["parameter"]); + throttlep = &mLastMinThrottle; + period = MIN_THROTTLE; + } + + F64 cur_time = LLTimer::getElapsedSeconds(); + F64 time_delta = *throttlep + period; + if (cur_time < time_delta) + { + LL_WARNS("LLUIListener") << "Throttled function " << std::quoted(event["function"].asString()) << LL_ENDL; + return; } + *throttlep = cur_time; + + // Interestingly, view_listener_t::addMenu() (addCommit(), + // addEnable()) constructs a commit_callback_t callable that accepts + // two parameters but discards the first. Only the second is passed to + // handleEvent(). Therefore we feel completely safe passing NULL for + // the first parameter. + (info->callback_func)(NULL, event["parameter"]); } -void LLUIListener::getValue(const LLSD&event) const +void LLUIListener::callables(const LLSD& event) const { - LLSD reply = LLSD::emptyMap(); + Response response(LLSD(), event); + + using Registry = LLUICtrl::CommitCallbackRegistry; + using Method = Registry::Registrar& (*)(); + static Method registrars[] = + { + &Registry::defaultRegistrar, + &Registry::currentRegistrar, + }; + LLSD list; + for (auto method : registrars) + { + auto& registrar{ (*method)() }; + for (auto it = registrar.beginItems(), end = registrar.endItems(); it != end; ++it) + { + LLSD entry{ llsd::map("name", it->first) }; + switch (it->second.handle_untrusted) + { + case cb_info::UNTRUSTED_ALLOW: + entry["access"] = "allow"; + break; + case cb_info::UNTRUSTED_BLOCK: + entry["access"] = "block"; + break; + case cb_info::UNTRUSTED_THROTTLE: + entry["access"] = "throttle"; + break; + } + list.append(entry); + } + } + response["callables"] = list; +} + +void LLUIListener::getValue(const LLSD& event) const +{ + Response response(LLSD(), event); const LLView* root = LLUI::getInstance()->getRootView(); const LLView* view = LLUI::getInstance()->resolvePath(root, event["path"].asString()); @@ -90,12 +230,192 @@ void LLUIListener::getValue(const LLSD&event) const if (ctrl) { - reply["value"] = ctrl->getValue(); + response["value"] = ctrl->getValue(); } else { - // *TODO: ??? return something indicating failure to resolve + response.error(stringize("UI control ", std::quoted(event["path"].asString()), " was not found")); + } +} + +void LLUIListener::getTopMenus(const LLSD& event) const +{ + Response response(LLSD(), event); + response["menus"] = llsd::toArray( + *gMenuBarView->getChildList(), + [](auto childp) {return childp->getName(); }); +} + +LLMenuGL::Params get_params(const LLSD&event) +{ + LLMenuGL::Params item_params; + item_params.name = event["name"]; + item_params.label = event["label"]; + item_params.can_tear_off = true; + return item_params; +} + +LLMenuGL* get_parent_menu(LLEventAPI::Response& response, const LLSD&event) +{ + auto parent_menu_name{ event["parent_menu"].asString() }; + LLMenuGL* parent_menu = gMenuBarView->findChildMenuByName(parent_menu_name, true); + if(!parent_menu) + { + response.error(stringize("Parent menu ", std::quoted(parent_menu_name), " was not found")); + } + return parent_menu; +} + +// Return event["pos"].asInteger() if passed, but clamp (0 <= pos <= size). +// Reserve -1 return to mean event has no "pos" key. +S32 get_pos(const LLSD& event, U32 size) +{ + return event["pos"].isInteger()? llclamp(event["pos"].asInteger(), 0, size) : -1; +} + +void LLUIListener::addMenu(const LLSD&event) const +{ + Response response(LLSD(), event); + LLMenuGL::Params item_params = get_params(event); + if(!gMenuBarView->appendMenu(LLUICtrlFactory::create<LLMenuGL>(item_params))) + { + response.error(stringize("Menu ", std::quoted(event["name"].asString()), " was not added")); + } +} + +void LLUIListener::addMenuBranch(const LLSD&event) const +{ + Response response(LLSD(), event); + if(LLMenuGL* parent_menu = get_parent_menu(response, event)) + { + LLMenuGL::Params item_params = get_params(event); + if(!parent_menu->appendMenu(LLUICtrlFactory::create<LLMenuGL>(item_params))) + { + response.error(stringize("Menu branch ", std::quoted(event["name"].asString()), " was not added")); + } + } +} + +void LLUIListener::addMenuItem(const LLSD&event) const +{ + Response response(LLSD(), event); + LLMenuItemCallGL::Params item_params; + item_params.name = event["name"]; + item_params.label = event["label"]; + LLUICtrl::CommitCallbackParam item_func; + item_func.function_name = event["func"]; + if (event.has("param")) + { + item_func.parameter = event["param"]; } + item_params.on_click = item_func; + if(LLMenuGL* parent_menu = get_parent_menu(response, event)) + { + auto item = LLUICtrlFactory::create<LLMenuItemCallGL>(item_params); + // Clamp pos to getItemCount(), meaning append. If pos exceeds that, + // insert() will silently ignore the request. + auto pos = get_pos(event, parent_menu->getItemCount()); + if (pos >= 0) + { + // insert() returns void: we just have to assume it worked. + parent_menu->insert(pos, item); + } + else if (! parent_menu->append(item)) + { + response.error(stringize("Menu item ", std::quoted(event["name"].asString()), + " was not added")); + } + } +} + +void LLUIListener::addMenuSeparator(const LLSD&event) const +{ + Response response(LLSD(), event); + if(LLMenuGL* parent_menu = get_parent_menu(response, event)) + { + // Clamp pos to getItemCount(), meaning append. If pos exceeds that, + // insert() will silently ignore the request. + auto pos = get_pos(event, parent_menu->getItemCount()); + if (pos >= 0) + { + // Even though addSeparator() does not accept a position, + // LLMenuItemSeparatorGL isa LLMenuItemGL, so we can use insert(). + LLMenuItemSeparatorGL::Params p; + LLMenuItemGL* separator = LLUICtrlFactory::create<LLMenuItemSeparatorGL>(p); + // insert() returns void: we just have to assume it worked. + parent_menu->insert(pos, separator); + } + else if (! parent_menu->addSeparator()) + { + response.error("Separator was not added"); + } + } +} - sendReply(reply, event); +void LLUIListener::setMenuVisible(const LLSD &event) const +{ + Response response(LLSD(), event); + std::string menu_name(event["name"]); + if (!gMenuBarView->getItem(menu_name)) + { + return response.error(stringize("Menu ", std::quoted(menu_name), " was not found")); + } + gMenuBarView->setItemVisible(menu_name, event["visible"].asBoolean()); +} + +void LLUIListener::restoreDefaultToolbars(const LLSD &event) const +{ + LLToolBarView::loadDefaultToolbars(); +} + +void LLUIListener::clearAllToolbars(const LLSD &event) const +{ + LLToolBarView::clearAllToolbars(); +} + +void LLUIListener::addToolbarBtn(const LLSD &event) const +{ + Response response(LLSD(), event); + + typedef LLToolBarEnums::EToolBarLocation ToolBarLocation; + ToolBarLocation toolbar = ToolBarLocation::TOOLBAR_BOTTOM; + if (event.has("toolbar")) + { + if (event["toolbar"] == "left") + { + toolbar = ToolBarLocation::TOOLBAR_LEFT; + } + else if (event["toolbar"] == "right") + { + toolbar = ToolBarLocation::TOOLBAR_RIGHT; + } + else if (event["toolbar"] != "bottom") + { + return response.error(stringize("Toolbar name ", std::quoted(event["toolbar"].asString()), " is not correct. Toolbar names are: left, right, bottom")); + } + } + S32 rank = event.has("rank") ? event["rank"].asInteger() : LLToolBar::RANK_NONE; + if(!gToolBarView->addCommand(event["btn_name"].asString(), toolbar, rank)) + { + response.error(stringize("Toolbar button ", std::quoted(event["btn_name"].asString()), " was not found")); + } +} + +void LLUIListener::removeToolbarBtn(const LLSD &event) const +{ + Response response(LLSD(), event); + + S32 old_rank = LLToolBar::RANK_NONE; + gToolBarView->removeCommand(event["btn_name"].asString(), old_rank); + response["rank"] = old_rank; +} + +void LLUIListener::getToolbarBtnNames(const LLSD &event) const +{ + Response response(llsd::map("cmd_names", LLCommandManager::instance().getCommandNames()), event); +} + +void LLUIListener::closeAllFloaters(const LLSD &event) const +{ + close_all_windows(); } diff --git a/indra/newview/lluilistener.h b/indra/newview/lluilistener.h index 70455c2c68..8fc8e6ebb4 100644 --- a/indra/newview/lluilistener.h +++ b/indra/newview/lluilistener.h @@ -40,8 +40,27 @@ public: LLUIListener(); private: - void call(const LLSD& event) const; - void getValue(const LLSD&event) const; + void call(const LLSD& event); + void callables(const LLSD& event) const; + void getValue(const LLSD& event) const; + void getTopMenus(const LLSD& event) const; + + void addMenu(const LLSD&event) const; + void addMenuBranch(const LLSD&event) const; + void addMenuItem(const LLSD&event) const; + void addMenuSeparator(const LLSD&event) const; + void setMenuVisible(const LLSD &event) const; + + void restoreDefaultToolbars(const LLSD &event) const; + void clearAllToolbars(const LLSD &event) const; + void addToolbarBtn(const LLSD &event) const; + void removeToolbarBtn(const LLSD &event) const; + void getToolbarBtnNames(const LLSD &event) const; + + void closeAllFloaters(const LLSD &event) const; + + F64 mLastUntrustedThrottle {0}; + F64 mLastMinThrottle {0}; }; #endif /* ! defined(LL_LLUILISTENER_H) */ diff --git a/indra/newview/llurldispatcher.cpp b/indra/newview/llurldispatcher.cpp index 39a9f0f8bc..166542324d 100644 --- a/indra/newview/llurldispatcher.cpp +++ b/indra/newview/llurldispatcher.cpp @@ -289,6 +289,8 @@ public: LLEventAPI::add("teleport", "Teleport to specified [\"regionname\"] at\n" "specified region-relative [\"x\"], [\"y\"], [\"z\"].\n" + "If [\"regionname\"] is \"home\", ignore [\"x\"], [\"y\"], [\"z\"]\n" + "and teleport home.\n" "If [\"regionname\"] omitted, teleport to GLOBAL\n" "coordinates [\"x\"], [\"y\"], [\"z\"].", &LLTeleportHandler::from_event); @@ -328,7 +330,12 @@ public: void from_event(const LLSD& params) const { Response response(LLSD(), params); - if (params.has("regionname")) + if (params["regionname"].asString() == "home") + { + gAgent.teleportHome(); + response["message"] = "Teleporting home"; + } + else if (params.has("regionname")) { // region specified, coordinates (if any) are region-local LLVector3 local_pos( diff --git a/indra/newview/llviewerchat.cpp b/indra/newview/llviewerchat.cpp index 8b01c4ef88..5daccb6784 100644 --- a/indra/newview/llviewerchat.cpp +++ b/indra/newview/llviewerchat.cpp @@ -30,6 +30,7 @@ // newview includes #include "llagent.h" // gAgent #include "llslurl.h" +#include "lltrans.h" #include "lluicolor.h" #include "lluicolortable.h" #include "llviewercontrol.h" // gSavedSettings @@ -220,8 +221,7 @@ S32 LLViewerChat::getChatFontSize() //static void LLViewerChat::formatChatMsg(const LLChat& chat, std::string& formated_msg) { - std::string tmpmsg = chat.mText; - + std::string tmpmsg = without_LUA_PREFIX(chat.mText, chat.mIsScript); if(chat.mChatStyle == CHAT_STYLE_IRC) { formated_msg = chat.mFromName + tmpmsg.substr(3); @@ -231,6 +231,11 @@ void LLViewerChat::formatChatMsg(const LLChat& chat, std::string& formated_msg) formated_msg = tmpmsg; } + if (chat.mIsScript) + { + formated_msg = LLTrans::getString("ScriptStr") + formated_msg; + } + } //static diff --git a/indra/newview/llviewercontrollistener.cpp b/indra/newview/llviewercontrollistener.cpp index 6f77e21fcc..4e39b03903 100644 --- a/indra/newview/llviewercontrollistener.cpp +++ b/indra/newview/llviewercontrollistener.cpp @@ -141,7 +141,8 @@ void LLViewerControlListener::set(LLSD const & request) if (request.has("value")) { - info.control->setValue(request["value"]); + LL_WARNS("LLViewerControlListener") << "Changing debug setting " << std::quoted(info.key) << " to " << request["value"] << LL_ENDL; + info.control->setValue(request["value"], false); } else { @@ -158,7 +159,9 @@ void LLViewerControlListener::toggle(LLSD const & request) if (info.control->isType(TYPE_BOOLEAN)) { - info.control->set(! info.control->get().asBoolean()); + bool value = !info.control->get().asBoolean(); + LL_WARNS("LLViewerControlListener") << "Toggling debug setting " << std::quoted(info.key) << " to " << value << LL_ENDL; + info.control->set(value, false); } else { diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index 301ea5c5f6..1f4502323c 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -28,58 +28,69 @@ #include "llviewerdisplay.h" -#include "llgl.h" -#include "llrender.h" -#include "llglheaders.h" -#include "llgltfmateriallist.h" +#include "fsyspath.h" +#include "hexdump.h" #include "llagent.h" #include "llagentcamera.h" -#include "llviewercontrol.h" +#include "llappviewer.h" #include "llcoord.h" #include "llcriticaldamp.h" +#include "llcubemap.h" #include "lldir.h" -#include "lldynamictexture.h" #include "lldrawpoolalpha.h" +#include "lldrawpoolbump.h" +#include "lldrawpoolwater.h" +#include "lldynamictexture.h" +#include "llenvironment.h" +#include "llfasttimer.h" #include "llfeaturemanager.h" -//#include "llfirstuse.h" +#include "llfloatertools.h" +#include "llfocusmgr.h" +#include "llgl.h" +#include "llglheaders.h" +#include "llgltfmateriallist.h" #include "llhudmanager.h" #include "llimagepng.h" +#include "llmachineid.h" #include "llmemory.h" +#include "llparcel.h" +#include "llperfstats.h" +#include "llrender.h" +#include "llscenemonitor.h" +#include "llsdjson.h" #include "llselectmgr.h" #include "llsky.h" +#include "llspatialpartition.h" +#include "llstartup.h" #include "llstartup.h" +#include "lltooldraganddrop.h" #include "lltoolfocus.h" #include "lltoolmgr.h" -#include "lltooldraganddrop.h" #include "lltoolpie.h" #include "lltracker.h" #include "lltrans.h" #include "llui.h" +#include "lluuid.h" +#include "llversioninfo.h" #include "llviewercamera.h" +#include "llviewercontrol.h" +#include "llviewernetwork.h" #include "llviewerobjectlist.h" #include "llviewerparcelmgr.h" +#include "llviewerregion.h" +#include "llviewershadermgr.h" +#include "llviewertexturelist.h" #include "llviewerwindow.h" #include "llvoavatarself.h" #include "llvograss.h" #include "llworld.h" #include "pipeline.h" -#include "llspatialpartition.h" -#include "llappviewer.h" -#include "llstartup.h" -#include "llviewershadermgr.h" -#include "llfasttimer.h" -#include "llfloatertools.h" -#include "llviewertexturelist.h" -#include "llfocusmgr.h" -#include "llcubemap.h" -#include "llviewerregion.h" -#include "lldrawpoolwater.h" -#include "lldrawpoolbump.h" -#include "llpostprocess.h" -#include "llscenemonitor.h" -#include "llenvironment.h" -#include "llperfstats.h" +#include <boost/json.hpp> + +#include <filesystem> +#include <iomanip> +#include <sstream> #include <glm/glm.hpp> #include <glm/gtc/matrix_transform.hpp> @@ -88,13 +99,13 @@ extern LLPointer<LLViewerTexture> gStartTexture; extern bool gShiftFrame; -LLPointer<LLViewerTexture> gDisconnectedImagep = NULL; +LLPointer<LLViewerTexture> gDisconnectedImagep = nullptr; // used to toggle renderer back on after teleport bool gTeleportDisplay = false; LLFrameTimer gTeleportDisplayTimer; LLFrameTimer gTeleportArrivalTimer; -const F32 RESTORE_GL_TIME = 5.f; // Wait this long while reloading textures before we raise the curtain +constexpr F32 RESTORE_GL_TIME = 5.f; // Wait this long while reloading textures before we raise the curtain bool gForceRenderLandFence = false; bool gDisplaySwapBuffers = false; @@ -108,9 +119,9 @@ bool gSnapshotNoPost = false; bool gShaderProfileFrame = false; // This is how long the sim will try to teleport you before giving up. -const F32 TELEPORT_EXPIRY = 15.0f; +constexpr F32 TELEPORT_EXPIRY = 15.0f; // Additional time (in seconds) to wait per attachment -const F32 TELEPORT_EXPIRY_PER_ATTACHMENT = 3.f; +constexpr F32 TELEPORT_EXPIRY_PER_ATTACHMENT = 3.f; U32 gRecentFrameCount = 0; // number of 'recent' frames LLFrameTimer gRecentFPSTime; @@ -118,8 +129,6 @@ LLFrameTimer gRecentMemoryTime; LLFrameTimer gAssetStorageLogTime; // Rendering stuff -void pre_show_depth_buffer(); -void post_show_depth_buffer(); void render_ui(F32 zoom_factor = 1.f, int subfield = 0); void swap(); void render_hud_attachments(); @@ -127,6 +136,9 @@ void render_ui_3d(); void render_ui_2d(); void render_disconnected_background(); +void getProfileStatsContext(boost::json::object& stats); +std::string getProfileStatsFilename(); + void display_startup() { if ( !gViewerWindow @@ -197,7 +209,8 @@ void display_update_camera() F32 final_far = gAgentCamera.mDrawDistance; if (gCubeSnapshot) { - final_far = gSavedSettings.getF32("RenderReflectionProbeDrawDistance"); + static LLCachedControl<F32> reflection_probe_draw_distance(gSavedSettings, "RenderReflectionProbeDrawDistance", 64.f); + final_far = reflection_probe_draw_distance(); } else if (CAMERA_MODE_CUSTOMIZE_AVATAR == gAgentCamera.getCameraMode()) @@ -218,7 +231,7 @@ void display_update_camera() void display_stats() { LL_PROFILE_ZONE_SCOPED; - const F32 FPS_LOG_FREQUENCY = 10.f; + constexpr F32 FPS_LOG_FREQUENCY = 10.f; if (gRecentFPSTime.getElapsedTimeF32() >= FPS_LOG_FREQUENCY) { LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("DS - FPS"); @@ -227,7 +240,7 @@ void display_stats() gRecentFrameCount = 0; gRecentFPSTime.reset(); } - F32 mem_log_freq = gSavedSettings.getF32("MemoryLogFrequency"); + static LLCachedControl<F32> mem_log_freq(gSavedSettings, "MemoryLogFrequency", 600.f); if (mem_log_freq > 0.f && gRecentMemoryTime.getElapsedTimeF32() >= mem_log_freq) { LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("DS - Memory"); @@ -237,7 +250,7 @@ void display_stats() LLMemory::logMemoryInfo(true) ; gRecentMemoryTime.reset(); } - const F32 ASSET_STORAGE_LOG_FREQUENCY = 60.f; + constexpr F32 ASSET_STORAGE_LOG_FREQUENCY = 60.f; if (gAssetStorageLogTime.getElapsedTimeF32() >= ASSET_STORAGE_LOG_FREQUENCY) { LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("DS - Asset Storage"); @@ -524,8 +537,10 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) LLImageGL::updateStats(gFrameTimeSeconds); - LLVOAvatar::sRenderName = gSavedSettings.getS32("AvatarNameTagMode"); - LLVOAvatar::sRenderGroupTitles = (gSavedSettings.getBOOL("NameTagShowGroupTitles") && gSavedSettings.getS32("AvatarNameTagMode")); + static LLCachedControl<S32> avatar_name_tag_mode(gSavedSettings, "AvatarNameTagMode", 1); + static LLCachedControl<bool> name_tag_show_group_titles(gSavedSettings, "NameTagShowGroupTitles", true); + LLVOAvatar::sRenderName = avatar_name_tag_mode; + LLVOAvatar::sRenderGroupTitles = name_tag_show_group_titles && avatar_name_tag_mode > 0; gPipeline.mBackfaceCull = true; gFrameCount++; @@ -748,7 +763,7 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) } gGL.setColorMask(true, true); - glClearColor(0,0,0,0); + glClearColor(0.f, 0.f, 0.f, 0.f); LLGLState::checkStates(); @@ -918,7 +933,7 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) gPipeline.mRT->deferredScreen.bindTarget(); if (gUseWireframe) { - F32 g = 0.5f; + constexpr F32 g = 0.5f; glClearColor(g, g, g, 1.f); } else @@ -932,8 +947,8 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) gPipeline.mRT->screen.bindTarget(); if (LLPipeline::sUnderWaterRender && !gPipeline.canUseWindLightShaders()) { - const LLColor4 &col = LLEnvironment::instance().getCurrentWater()->getWaterFogColor(); - glClearColor(col.mV[0], col.mV[1], col.mV[2], 0.f); + const LLColor4& col = LLEnvironment::instance().getCurrentWater()->getWaterFogColor(); + glClearColor(col.mV[VRED], col.mV[VGREEN], col.mV[VBLUE], 0.f); } gPipeline.mRT->screen.clear(); } @@ -948,11 +963,12 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) LL_PROFILE_ZONE_NAMED_CATEGORY_DISPLAY("display - 5") LLViewerCamera::sCurCameraID = LLViewerCamera::CAMERA_WORLD; - if (gSavedSettings.getBOOL("RenderDepthPrePass")) + static LLCachedControl<bool> render_depth_pre_pass(gSavedSettings, "RenderDepthPrePass", false); + if (render_depth_pre_pass) { gGL.setColorMask(false, false); - static const U32 types[] = { + constexpr U32 types[] = { LLRenderPass::PASS_SIMPLE, LLRenderPass::PASS_FULLBRIGHT, LLRenderPass::PASS_SHINY @@ -1027,8 +1043,87 @@ void display(bool rebuild, F32 zoom_factor, int subfield, bool for_snapshot) if (gShaderProfileFrame) { gShaderProfileFrame = false; - LLGLSLShader::finishProfile(); + boost::json::value stats{ boost::json::object_kind }; + getProfileStatsContext(stats.as_object()); + LLGLSLShader::finishProfile(stats); + + auto report_name = getProfileStatsFilename(); + std::ofstream outf(report_name); + if (! outf) + { + LL_WARNS() << "Couldn't write to " << std::quoted(report_name) << LL_ENDL; + } + else + { + outf << stats; + LL_INFOS() << "(also dumped to " << std::quoted(report_name) << ")" << LL_ENDL; + } + } +} + +void getProfileStatsContext(boost::json::object& stats) +{ + // populate the context with info from LLFloaterAbout + auto contextit = stats.emplace("context", + LlsdToJson(LLAppViewer::instance()->getViewerInfo())).first; + auto& context = contextit->value().as_object(); + + // then add a few more things + unsigned char unique_id[MAC_ADDRESS_BYTES]{}; + LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); + context.emplace("machine", stringize(LL::hexdump(unique_id, sizeof(unique_id)))); + context.emplace("grid", LLGridManager::instance().getGrid()); + LLViewerRegion* region = gAgent.getRegion(); + if (region) + { + context.emplace("regionid", stringize(region->getRegionID())); + } + LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); + if (parcel) + { + context.emplace("parcel", parcel->getName()); + context.emplace("parcelid", parcel->getLocalID()); } + context.emplace("time", LLDate::now().toHTTPDateString("%Y-%m-%dT%H:%M:%S")); +} + +std::string getProfileStatsFilename() +{ + std::ostringstream basebuff; + // viewer build + basebuff << "profile.v" << LLVersionInfo::instance().getBuild(); + // machine ID: zero-initialize unique_id in case LLMachineID fails + unsigned char unique_id[MAC_ADDRESS_BYTES]{}; + LLMachineID::getUniqueID(unique_id, sizeof(unique_id)); + basebuff << ".m" << LL::hexdump(unique_id, sizeof(unique_id)); + // region ID + LLViewerRegion *region = gAgent.getRegion(); + basebuff << ".r" << (region? region->getRegionID() : LLUUID()); + // local parcel ID + LLParcel* parcel = LLViewerParcelMgr::instance().getAgentParcel(); + basebuff << ".p" << (parcel? parcel->getLocalID() : 0); + // date/time -- omit seconds for now + auto now = LLDate::now(); + basebuff << ".t" << LLDate::now().toHTTPDateString("%Y-%m-%dT%H-%M-"); + // put this candidate file in our logs directory + auto base = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, basebuff.str()); + S32 sec; + now.split(nullptr, nullptr, nullptr, nullptr, nullptr, &sec); + // Loop over finished filename, incrementing sec until we find one that + // doesn't yet exist. Should rarely loop (only if successive calls within + // same second), may produce (e.g.) sec==61, but avoids collisions and + // preserves chronological filename sort order. + std::string name; + std::error_code ec; + do + { + // base + missing 2-digit seconds, append ".json" + // post-increment sec in case we have to try again + name = stringize(base, std::setw(2), std::setfill('0'), sec++, ".json"); + } while (std::filesystem::exists(fsyspath(name), ec)); + // Ignoring ec means we might potentially return a name that does already + // exist -- but if we can't check its existence, what more can we do? + return name; } // WIP simplified copy of display() that does minimal work @@ -1087,7 +1182,7 @@ void display_cube_face() gGL.setColorMask(true, true); - glClearColor(0, 0, 0, 0); + glClearColor(0.f, 0.f, 0.f, 0.f); gPipeline.generateSunShadow(*LLViewerCamera::getInstance()); glClear(GL_DEPTH_BUFFER_BIT); // | GL_STENCIL_BUFFER_BIT); @@ -1123,7 +1218,7 @@ void display_cube_face() } else { - glClearColor(1, 0, 1, 1); + glClearColor(1.f, 0.f, 1.f, 1.f); } gPipeline.mRT->deferredScreen.clear(); @@ -1164,11 +1259,12 @@ void render_hud_attachments() { LLPipeline::sRenderingHUDs = true; LLCamera hud_cam = *LLViewerCamera::getInstance(); - hud_cam.setOrigin(-1.f,0,0); - hud_cam.setAxes(LLVector3(1,0,0), LLVector3(0,1,0), LLVector3(0,0,1)); + hud_cam.setOrigin(-1.f, 0.f, 0.f); + hud_cam.setAxes(LLVector3(1.f, 0.f, 0.f), LLVector3(0.f, 1.f, 0.f), LLVector3(0.f, 0.f, 1.f)); LLViewerCamera::updateFrustumPlanes(hud_cam, true); - bool render_particles = gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES) && gSavedSettings.getBOOL("RenderHUDParticles"); + static LLCachedControl<bool> render_hud_particles(gSavedSettings, "RenderHUDParticles", false); + bool render_particles = gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_PARTICLES) && render_hud_particles; //only render hud objects gPipeline.pushRenderTypeMask(); @@ -1528,10 +1624,11 @@ void render_ui_3d() stop_glerror(); gUIProgram.bind(); - gGL.color4f(1, 1, 1, 1); + gGL.color4f(1.f, 1.f, 1.f, 1.f); // Coordinate axes - if (gSavedSettings.getBOOL("ShowAxes")) + static LLCachedControl<bool> show_axes(gSavedSettings, "ShowAxes"); + if (show_axes()) { draw_axes(); } @@ -1591,7 +1688,7 @@ void render_ui_2d() gGL.pushMatrix(); S32 half_width = (gViewerWindow->getWorldViewWidthScaled() / 2); S32 half_height = (gViewerWindow->getWorldViewHeightScaled() / 2); - gGL.scalef(LLUI::getScaleFactor().mV[0], LLUI::getScaleFactor().mV[1], 1.f); + gGL.scalef(LLUI::getScaleFactor().mV[VX], LLUI::getScaleFactor().mV[VY], 1.f); gGL.translatef((F32)half_width, (F32)half_height, 0.f); F32 zoom = gAgentCamera.mHUDCurZoom; gGL.scalef(zoom,zoom,1.f); @@ -1613,7 +1710,7 @@ void render_ui_2d() gPipeline.mUIScreen.bindTarget(); gGL.setColorMask(true, true); { - static const S32 pad = 8; + constexpr S32 pad = 8; LLView::sDirtyRect.mLeft -= pad; LLView::sDirtyRect.mRight += pad; @@ -1666,8 +1763,6 @@ void render_ui_2d() gViewerWindow->draw(); } - - // reset current origin for font rendering, in case of tiling render LLFontGL::sCurOrigin.set(0, 0); } @@ -1676,7 +1771,7 @@ void render_disconnected_background() { gUIProgram.bind(); - gGL.color4f(1,1,1,1); + gGL.color4f(1.f, 1.f, 1.f, 1.f); if (!gDisconnectedImagep && gDisconnected) { LL_INFOS() << "Loading last bitmap..." << LL_ENDL; @@ -1716,7 +1811,7 @@ void render_disconnected_background() raw->expandToPowerOfTwo(); - gDisconnectedImagep = LLViewerTextureManager::getLocalTexture(raw.get(), false ); + gDisconnectedImagep = LLViewerTextureManager::getLocalTexture(raw.get(), false); gStartTexture = gDisconnectedImagep; gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); } @@ -1751,6 +1846,5 @@ void render_disconnected_background() void display_cleanup() { - gDisconnectedImagep = NULL; + gDisconnectedImagep = nullptr; } - diff --git a/indra/newview/llviewerdisplay.h b/indra/newview/llviewerdisplay.h index 673f51600d..2ec02f09fd 100644 --- a/indra/newview/llviewerdisplay.h +++ b/indra/newview/llviewerdisplay.h @@ -27,8 +27,6 @@ #ifndef LL_LLVIEWERDISPLAY_H #define LL_LLVIEWERDISPLAY_H -class LLPostProcess; - void display_startup(); void display_cleanup(); diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index 3f2d01ee33..b2a7d875ab 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -96,6 +96,8 @@ #include "llfloaterlandholdings.h" #include "llfloaterlinkreplace.h" #include "llfloaterloadprefpreset.h" +#include "llfloaterluadebug.h" +#include "llfloaterluascripts.h" #include "llfloatermap.h" #include "llfloatermarketplacelistings.h" #include "llfloatermediasettings.h" @@ -116,7 +118,6 @@ #include "llfloaterpay.h" #include "llfloaterperformance.h" #include "llfloaterperms.h" -#include "llfloaterpostprocess.h" #include "llfloaterpreference.h" #include "llfloaterpreferencesgraphicsadvanced.h" #include "llfloaterpreferenceviewadvanced.h" @@ -362,7 +363,6 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("emoji_picker", "floater_emoji_picker.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEmojiPicker>); LLFloaterReg::add("emoji_complete", "floater_emoji_complete.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterEmojiComplete>); - LLFloaterReg::add("env_post_process", "floater_post_process.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterPostProcess>); LLFloaterReg::add("env_fixed_environmentent_water", "floater_fixedenvironment.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterFixedEnvironmentWater>); LLFloaterReg::add("env_fixed_environmentent_sky", "floater_fixedenvironment.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterFixedEnvironmentSky>); @@ -413,6 +413,9 @@ void LLViewerFloaterReg::registerFloaters() LLFloaterReg::add("linkreplace", "floater_linkreplace.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLinkReplace>); LLFloaterReg::add("load_pref_preset", "floater_load_pref_preset.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLoadPrefPreset>); + LLFloaterReg::add("lua_debug", "floater_lua_debug.xml", (LLFloaterBuildFunc) &LLFloaterReg::build<LLFloaterLUADebug>); + LLFloaterReg::add("lua_scripts", "floater_lua_scripts.xml", (LLFloaterBuildFunc) &LLFloaterReg::build<LLFloaterLUAScripts>); + LLFloaterReg::add("mem_leaking", "floater_mem_leaking.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMemLeak>); LLFloaterReg::add("media_settings", "floater_media_settings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMediaSettings>); diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index e2022cae37..4c9910fd0a 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -71,6 +71,9 @@ #include "llclipboard.h" #include "llhttpretrypolicy.h" #include "llsettingsvo.h" +#include "llinventorylistener.h" + +LLInventoryListener sInventoryListener; // do-nothing ops for use in callbacks. void no_op_inventory_func(const LLUUID&) {} diff --git a/indra/newview/llviewerjointattachment.cpp b/indra/newview/llviewerjointattachment.cpp index e733dafcae..511fac9788 100644 --- a/indra/newview/llviewerjointattachment.cpp +++ b/indra/newview/llviewerjointattachment.cpp @@ -86,13 +86,17 @@ U32 LLViewerJointAttachment::drawShape( F32 pixelArea, bool first_pass, bool is_ LLGLDisable cull_face(GL_CULL_FACE); gGL.color4f(1.f, 1.f, 1.f, 1.f); - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); { gGL.vertex3f(-0.1f, 0.1f, 0.f); gGL.vertex3f(-0.1f, -0.1f, 0.f); gGL.vertex3f(0.1f, -0.1f, 0.f); + + gGL.vertex3f(-0.1f, 0.1f, 0.f); + gGL.vertex3f(0.1f, -0.1f, 0.f); gGL.vertex3f(0.1f, 0.1f, 0.f); - }gGL.end(); + } + gGL.end(); } return 0; } diff --git a/indra/newview/llviewerjointmesh.cpp b/indra/newview/llviewerjointmesh.cpp index f0567b18c4..da7ad71336 100644 --- a/indra/newview/llviewerjointmesh.cpp +++ b/indra/newview/llviewerjointmesh.cpp @@ -407,10 +407,16 @@ void LLViewerJointMesh::updateFaceData(LLFace *face, F32 pixel_area, bool damp_w F32* vw = (F32*) vertex_weightsp.get(); F32* cw = (F32*) clothing_weightsp.get(); - S32 tc_size = (num_verts*2*sizeof(F32)+0xF) & ~0xF; - LLVector4a::memcpyNonAliased16(tc, (F32*) mMesh->getTexCoords(), tc_size); - S32 vw_size = (num_verts*sizeof(F32)+0xF) & ~0xF; - LLVector4a::memcpyNonAliased16(vw, (F32*) mMesh->getWeights(), vw_size); + //S32 tc_size = (num_verts*2*sizeof(F32)+0xF) & ~0xF; + //LLVector4a::memcpyNonAliased16(tc, (F32*) mMesh->getTexCoords(), tc_size); + //S32 vw_size = (num_verts*sizeof(F32)+0xF) & ~0xF; + //LLVector4a::memcpyNonAliased16(vw, (F32*) mMesh->getWeights(), vw_size); + + // Both allocated in LLPolyMeshSharedData::allocateVertexData(unsigned int) + + memcpy(tc, mMesh->getTexCoords(), num_verts*2*sizeof(F32) ); + memcpy(vw, mMesh->getWeights(), num_verts*sizeof(F32) ); + LLVector4a::memcpyNonAliased16(cw, (F32*) mMesh->getClothingWeights(), num_verts*4*sizeof(F32)); } diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index 9739cac311..92db598410 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -688,10 +688,10 @@ void LLViewerMedia::updateMedia(void *dummy_arg) static LLCachedControl<bool> inworld_media_enabled(gSavedSettings, "AudioStreamingMedia", true); static LLCachedControl<bool> inworld_audio_enabled(gSavedSettings, "AudioStreamingMusic", true); - U32 max_instances = gSavedSettings.getU32("PluginInstancesTotal"); - U32 max_normal = gSavedSettings.getU32("PluginInstancesNormal"); - U32 max_low = gSavedSettings.getU32("PluginInstancesLow"); - F32 max_cpu = gSavedSettings.getF32("PluginInstancesCPULimit"); + static LLCachedControl<U32> max_instances(gSavedSettings, "PluginInstancesTotal", 8); + static LLCachedControl<U32> max_normal(gSavedSettings, "PluginInstancesNormal", 2); + static LLCachedControl<U32> max_low(gSavedSettings, "PluginInstancesLow", 4); + static LLCachedControl<F32> max_cpu(gSavedSettings, "PluginInstancesCPULimit", 0.9); // Setting max_cpu to 0.0 disables CPU usage checking. bool check_cpu_usage = (max_cpu != 0.0f); @@ -829,7 +829,8 @@ void LLViewerMedia::updateMedia(void *dummy_arg) } else { - if(gAudiop && LLViewerMedia::hasParcelAudio() && restore_parcel_audio && gSavedSettings.getBOOL("MediaTentativeAutoPlay")) + static LLCachedControl<bool> auto_play(gSavedSettings, "MediaTentativeAutoPlay", true); + if(gAudiop && LLViewerMedia::hasParcelAudio() && restore_parcel_audio && auto_play()) { LLViewerAudio::getInstance()->startInternetStreamWithAutoFade(LLViewerMedia::getParcelAudioURL()); restore_parcel_audio = false; @@ -880,7 +881,8 @@ void LLViewerMedia::updateMedia(void *dummy_arg) } } - if(gSavedSettings.getBOOL("MediaPerformanceManagerDebug")) + static LLCachedControl<bool> perf_debug(gSavedSettings, "MediaPerformanceManagerDebug", false); + if(perf_debug()) { // Give impls the same ordering as the priority list // they're already in the right order for this. @@ -1728,8 +1730,6 @@ LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_ std::string user_data_path_cache = gDirUtilp->getCacheDir(false); user_data_path_cache += gDirUtilp->getDirDelimiter(); - std::string user_data_path_cef_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "cef_log.txt"); - // See if the plugin executable exists llstat s; if(LLFile::stat(launcher_name, &s)) @@ -1738,14 +1738,13 @@ LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_ } else if(LLFile::stat(plugin_name, &s)) { -#if !LL_LINUX LL_WARNS_ONCE("Media") << "Couldn't find plugin at " << plugin_name << LL_ENDL; -#endif } else { media_source = new LLPluginClassMedia(owner); media_source->setSize(default_width, default_height); + std::string user_data_path_cef_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "cef.log"); media_source->setUserDataPath(user_data_path_cache, gDirUtilp->getUserName(), user_data_path_cef_log); media_source->setLanguageCode(LLUI::getLanguage()); media_source->setZoomFactor(zoom_factor); @@ -1771,6 +1770,11 @@ LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_ bool media_plugin_debugging_enabled = gSavedSettings.getBOOL("MediaPluginDebugging"); media_source->enableMediaPluginDebugging( media_plugin_debugging_enabled || clean_browser); +#if LL_LINUX + bool media_plugin_pipewire_volume_catcher = gSavedSettings.getBOOL("MediaPluginPipeWireVolumeCatcher"); + media_source->enablePipeWireVolumeCatcher( media_plugin_pipewire_volume_catcher ); +#endif + // need to set agent string here before instance created media_source->setBrowserUserAgent(LLViewerMedia::getInstance()->getCurrentUserAgent()); @@ -1792,9 +1796,7 @@ LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_ } } } -#if !LL_LINUX LL_WARNS_ONCE("Plugin") << "plugin initialization failed for mime type: " << media_type << LL_ENDL; -#endif if(gAgent.isInitialized()) { @@ -2766,7 +2768,8 @@ bool LLViewerMediaImpl::handleUnicodeCharHere(llwchar uni_char) { LLSD native_key_data = gViewerWindow->getWindow()->getNativeKeyData(); - mMediaSource->textInput(wstring_to_utf8str(LLWString(1, uni_char)), gKeyboard->currentMask(false), native_key_data); + mMediaSource->textInput(ll_convert_to<std::string>(uni_char), + gKeyboard->currentMask(false), native_key_data); } } diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index bbf2dfad13..ed0d7953f7 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -78,6 +78,7 @@ #include "llfloatertools.h" #include "llfloaterworldmap.h" #include "llfloaterbuildoptions.h" +#include "fsyspath.h" #include "llavataractions.h" #include "lllandmarkactions.h" #include "llgroupmgr.h" @@ -90,6 +91,7 @@ #include "llinventorybridge.h" #include "llinventorydefines.h" #include "llinventoryfunctions.h" +#include "llluamanager.h" #include "llpanellogin.h" #include "llpanelblockedlist.h" #include "llpanelmaininventory.h" @@ -171,6 +173,8 @@ extern bool gShaderProfileFrame; // Globals // +LLUIListener sUIListener; + LLMenuBarGL *gMenuBarView = NULL; LLViewerMenuHolderGL *gMenuHolder = NULL; LLMenuGL *gPopupMenuView = NULL; @@ -343,8 +347,6 @@ private: static LLMenuParcelObserver* gMenuParcelObserver = NULL; -static LLUIListener sUIListener; - LLMenuParcelObserver::LLMenuParcelObserver() { mLandBuyHandle = gMenuLand->getChild<LLMenuItemCallGL>("Land Buy")->getHandle(); @@ -9167,80 +9169,89 @@ class LLToolsSelectTool : public view_listener_t }; /// WINDLIGHT callbacks -class LLWorldEnvSettings : public view_listener_t +void defocusEnvFloaters() { - void defocusEnvFloaters() + // currently there is only one instance of each floater + std::vector<std::string> env_floaters_names = {"env_edit_extdaycycle", "env_fixed_environmentent_water", + "env_fixed_environmentent_sky"}; + for (std::vector<std::string>::const_iterator it = env_floaters_names.begin(); it != env_floaters_names.end(); ++it) { - //currently there is only one instance of each floater - std::vector<std::string> env_floaters_names = { "env_edit_extdaycycle", "env_fixed_environmentent_water", "env_fixed_environmentent_sky" }; - for (std::vector<std::string>::const_iterator it = env_floaters_names.begin(); it != env_floaters_names.end(); ++it) + LLFloater *env_floater = LLFloaterReg::findTypedInstance<LLFloater>(*it); + if (env_floater) { - LLFloater* env_floater = LLFloaterReg::findTypedInstance<LLFloater>(*it); - if (env_floater) - { - env_floater->setFocus(false); - } + env_floater->setFocus(false); } } +} - bool handleEvent(const LLSD& userdata) +bool handle_env_setting_event(std::string event_name) +{ + if (event_name == "sunrise") { - std::string event_name = userdata.asString(); - - if (event_name == "sunrise") - { - LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNRISE, LLEnvironment::TRANSITION_INSTANT); - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); - defocusEnvFloaters(); - } - else if (event_name == "noon") - { - LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDDAY, LLEnvironment::TRANSITION_INSTANT); - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); - defocusEnvFloaters(); - } - else if (event_name == "legacy noon") - { - LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_LEGACY_MIDDAY, LLEnvironment::TRANSITION_INSTANT); - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); - defocusEnvFloaters(); - } - else if (event_name == "sunset") - { - LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNSET, LLEnvironment::TRANSITION_INSTANT); - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); - defocusEnvFloaters(); - } - else if (event_name == "midnight") - { - LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDNIGHT, LLEnvironment::TRANSITION_INSTANT); - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); - defocusEnvFloaters(); - } - else if (event_name == "region") - { - // reset probe data when reverting back to region sky setting - gPipeline.mReflectionMapManager.reset(); + LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNRISE, + LLEnvironment::TRANSITION_INSTANT); + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); + defocusEnvFloaters(); + } + else if (event_name == "noon") + { + LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDDAY, + LLEnvironment::TRANSITION_INSTANT); + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); + defocusEnvFloaters(); + } + else if (event_name == "legacy noon") + { + LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_LEGACY_MIDDAY, LLEnvironment::TRANSITION_INSTANT); + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); + defocusEnvFloaters(); + } + else if (event_name == "sunset") + { + LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNSET, + LLEnvironment::TRANSITION_INSTANT); + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); + defocusEnvFloaters(); + } + else if (event_name == "midnight") + { + LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDNIGHT, + LLEnvironment::TRANSITION_INSTANT); + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); + defocusEnvFloaters(); + } + else if (event_name == "region") + { + // reset probe data when reverting back to region sky setting + gPipeline.mReflectionMapManager.reset(); - LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_LOCAL); - LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); - defocusEnvFloaters(); - } - else if (event_name == "pause_clouds") - { - if (LLEnvironment::instance().isCloudScrollPaused()) - LLEnvironment::instance().resumeCloudScroll(); + LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_LOCAL); + LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT); + defocusEnvFloaters(); + } + else if (event_name == "pause_clouds") + { + if (LLEnvironment::instance().isCloudScrollPaused()) + LLEnvironment::instance().resumeCloudScroll(); else - LLEnvironment::instance().pauseCloudScroll(); - } - else if (event_name == "adjust_tool") - { - LLFloaterReg::showInstance("env_adjust_snapshot"); - } - else if (event_name == "my_environs") - { - LLFloaterReg::showInstance("my_environments"); - } + LLEnvironment::instance().pauseCloudScroll(); + } + else if (event_name == "adjust_tool") + { + LLFloaterReg::showInstance("env_adjust_snapshot"); + } + else if (event_name == "my_environs") + { + LLFloaterReg::showInstance("my_environments"); + } + return true; +} + +class LLWorldEnvSettings : public view_listener_t +{ + bool handleEvent(const LLSD& userdata) + { + handle_env_setting_event(userdata.asString()); return true; } @@ -9348,17 +9359,6 @@ class LLWorldEnableEnvPreset : public view_listener_t } }; - -/// Post-Process callbacks -class LLWorldPostProcess : public view_listener_t -{ - bool handleEvent(const LLSD& userdata) - { - LLFloaterReg::showInstance("env_post_process"); - return true; - } -}; - class LLWorldCheckBanLines : public view_listener_t { bool handleEvent(const LLSD& userdata) @@ -9466,6 +9466,18 @@ void LLUploadCostCalculator::calculateCost(const std::string& asset_type_str) mCostStr = std::to_string(upload_cost); } +void lua_run_script(const LLSD& userdata) +{ + std::string script_path = userdata.asString(); + if (script_path.empty()) + { + LL_WARNS() << "Script name is not specified" << LL_ENDL; + return; + } + + LLLUAmanager::runScriptFile(script_path); +} + void show_navbar_context_menu(LLView* ctrl, S32 x, S32 y) { static LLMenuGL* show_navbar_context_menu = LLUICtrlFactory::getInstance()->createFromFile<LLMenuGL>("menu_hide_navbar.xml", @@ -9526,16 +9538,18 @@ void initialize_edit_menu() } +typedef LLUICtrl::CommitCallbackInfo cb_info; + void initialize_spellcheck_menu() { - LLUICtrl::CommitCallbackRegistry::Registrar& commit = LLUICtrl::CommitCallbackRegistry::currentRegistrar(); + LLUICtrl::CommitRegistrarHelper registrar(LLUICtrl::CommitCallbackRegistry::defaultRegistrar()); LLUICtrl::EnableCallbackRegistry::Registrar& enable = LLUICtrl::EnableCallbackRegistry::currentRegistrar(); - commit.add("SpellCheck.ReplaceWithSuggestion", boost::bind(&handle_spellcheck_replace_with_suggestion, _1, _2)); + registrar.add("SpellCheck.ReplaceWithSuggestion", boost::bind(&handle_spellcheck_replace_with_suggestion, _1, _2), cb_info::UNTRUSTED_BLOCK); enable.add("SpellCheck.VisibleSuggestion", boost::bind(&visible_spellcheck_suggestion, _1, _2)); - commit.add("SpellCheck.AddToDictionary", boost::bind(&handle_spellcheck_add_to_dictionary, _1)); + registrar.add("SpellCheck.AddToDictionary", boost::bind(&handle_spellcheck_add_to_dictionary, _1), cb_info::UNTRUSTED_BLOCK); enable.add("SpellCheck.EnableAddToDictionary", boost::bind(&enable_spellcheck_add_to_dictionary, _1)); - commit.add("SpellCheck.AddToIgnore", boost::bind(&handle_spellcheck_add_to_ignore, _1)); + registrar.add("SpellCheck.AddToIgnore", boost::bind(&handle_spellcheck_add_to_ignore, _1), cb_info::UNTRUSTED_BLOCK); enable.add("SpellCheck.EnableAddToIgnore", boost::bind(&enable_spellcheck_add_to_ignore, _1)); } @@ -9559,8 +9573,8 @@ void initialize_menus() bool mMult; }; + LLUICtrl::CommitRegistrarHelper registrar(LLUICtrl::CommitCallbackRegistry::currentRegistrar()); LLUICtrl::EnableCallbackRegistry::Registrar& enable = LLUICtrl::EnableCallbackRegistry::currentRegistrar(); - LLUICtrl::CommitCallbackRegistry::Registrar& commit = LLUICtrl::CommitCallbackRegistry::currentRegistrar(); // Generic enable and visible // Don't prepend MenuName.Foo because these can be used in any menu. @@ -9575,15 +9589,15 @@ void initialize_menus() enable.add("Conversation.IsConversationLoggingAllowed", boost::bind(&LLFloaterIMContainer::isConversationLoggingAllowed)); // Agent - commit.add("Agent.toggleFlying", boost::bind(&LLAgent::toggleFlying)); + registrar.add("Agent.toggleFlying", boost::bind(&LLAgent::toggleFlying)); enable.add("Agent.enableFlyLand", boost::bind(&enable_fly_land)); - commit.add("Agent.PressMicrophone", boost::bind(&LLAgent::pressMicrophone, _2)); - commit.add("Agent.ReleaseMicrophone", boost::bind(&LLAgent::releaseMicrophone, _2)); - commit.add("Agent.ToggleMicrophone", boost::bind(&LLAgent::toggleMicrophone, _2)); + registrar.add("Agent.PressMicrophone", boost::bind(&LLAgent::pressMicrophone, _2), cb_info::UNTRUSTED_BLOCK); + registrar.add("Agent.ReleaseMicrophone", boost::bind(&LLAgent::releaseMicrophone, _2), cb_info::UNTRUSTED_BLOCK); + registrar.add("Agent.ToggleMicrophone", boost::bind(&LLAgent::toggleMicrophone, _2), cb_info::UNTRUSTED_BLOCK); enable.add("Agent.IsMicrophoneOn", boost::bind(&LLAgent::isMicrophoneOn, _2)); enable.add("Agent.IsActionAllowed", boost::bind(&LLAgent::isActionAllowed, _2)); - commit.add("Agent.ToggleHearMediaSoundFromAvatar", boost::bind(&LLAgent::toggleHearMediaSoundFromAvatar)); - commit.add("Agent.ToggleHearVoiceFromAvatar", boost::bind(&LLAgent::toggleHearVoiceFromAvatar)); + registrar.add("Agent.ToggleHearMediaSoundFromAvatar", boost::bind(&LLAgent::toggleHearMediaSoundFromAvatar), cb_info::UNTRUSTED_BLOCK); + registrar.add("Agent.ToggleHearVoiceFromAvatar", boost::bind(&LLAgent::toggleHearVoiceFromAvatar), cb_info::UNTRUSTED_BLOCK); // File menu init_menu_file(); @@ -9593,12 +9607,12 @@ void initialize_menus() view_listener_t::addMenu(new LLEnableEditShape(), "Edit.EnableEditShape"); view_listener_t::addMenu(new LLEnableHoverHeight(), "Edit.EnableHoverHeight"); view_listener_t::addMenu(new LLEnableEditPhysics(), "Edit.EnableEditPhysics"); - commit.add("CustomizeAvatar", boost::bind(&handle_customize_avatar)); - commit.add("NowWearing", boost::bind(&handle_now_wearing)); - commit.add("EditOutfit", boost::bind(&handle_edit_outfit)); - commit.add("EditShape", boost::bind(&handle_edit_shape)); - commit.add("HoverHeight", boost::bind(&handle_hover_height)); - commit.add("EditPhysics", boost::bind(&handle_edit_physics)); + registrar.add("CustomizeAvatar", boost::bind(&handle_customize_avatar)); + registrar.add("NowWearing", boost::bind(&handle_now_wearing)); + registrar.add("EditOutfit", boost::bind(&handle_edit_outfit)); + registrar.add("EditShape", boost::bind(&handle_edit_shape)); + registrar.add("HoverHeight", boost::bind(&handle_hover_height)); + registrar.add("EditPhysics", boost::bind(&handle_edit_physics)); // View menu view_listener_t::addMenu(new LLViewMouselook(), "View.Mouselook"); @@ -9651,7 +9665,6 @@ void initialize_menus() view_listener_t::addMenu(new LLWorldEnableEnvSettings(), "World.EnableEnvSettings"); view_listener_t::addMenu(new LLWorldEnvPreset(), "World.EnvPreset"); view_listener_t::addMenu(new LLWorldEnableEnvPreset(), "World.EnableEnvPreset"); - view_listener_t::addMenu(new LLWorldPostProcess(), "World.PostProcess"); view_listener_t::addMenu(new LLWorldCheckBanLines() , "World.CheckBanLines"); view_listener_t::addMenu(new LLWorldShowBanLines() , "World.ShowBanLines"); @@ -9668,14 +9681,14 @@ void initialize_menus() view_listener_t::addMenu(new LLToolsSnapObjectXY(), "Tools.SnapObjectXY"); view_listener_t::addMenu(new LLToolsUseSelectionForGrid(), "Tools.UseSelectionForGrid"); view_listener_t::addMenu(new LLToolsSelectNextPartFace(), "Tools.SelectNextPart"); - commit.add("Tools.Link", boost::bind(&handle_link_objects)); - commit.add("Tools.Unlink", boost::bind(&LLSelectMgr::unlinkObjects, LLSelectMgr::getInstance())); + registrar.add("Tools.Link", boost::bind(&handle_link_objects)); + registrar.add("Tools.Unlink", boost::bind(&LLSelectMgr::unlinkObjects, LLSelectMgr::getInstance())); view_listener_t::addMenu(new LLToolsStopAllAnimations(), "Tools.StopAllAnimations"); view_listener_t::addMenu(new LLToolsReleaseKeys(), "Tools.ReleaseKeys"); view_listener_t::addMenu(new LLToolsEnableReleaseKeys(), "Tools.EnableReleaseKeys"); - commit.add("Tools.LookAtSelection", boost::bind(&handle_look_at_selection, _2)); - commit.add("Tools.BuyOrTake", boost::bind(&handle_buy_or_take)); - commit.add("Tools.TakeCopy", boost::bind(&handle_take_copy)); + registrar.add("Tools.LookAtSelection", boost::bind(&handle_look_at_selection, _2)); + registrar.add("Tools.BuyOrTake", boost::bind(&handle_buy_or_take), cb_info::UNTRUSTED_THROTTLE); + registrar.add("Tools.TakeCopy", boost::bind(&handle_take_copy), cb_info::UNTRUSTED_THROTTLE); view_listener_t::addMenu(new LLToolsSaveToObjectInventory(), "Tools.SaveToObjectInventory"); view_listener_t::addMenu(new LLToolsSelectedScriptAction(), "Tools.SelectedScriptAction"); @@ -9726,7 +9739,7 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedToggleInfoDisplay(), "Advanced.ToggleInfoDisplay"); view_listener_t::addMenu(new LLAdvancedCheckInfoDisplay(), "Advanced.CheckInfoDisplay"); view_listener_t::addMenu(new LLAdvancedSelectedTextureInfo(), "Advanced.SelectedTextureInfo"); - commit.add("Advanced.SelectedMaterialInfo", boost::bind(&handle_selected_material_info)); + registrar.add("Advanced.SelectedMaterialInfo", boost::bind(&handle_selected_material_info)); view_listener_t::addMenu(new LLAdvancedToggleWireframe(), "Advanced.ToggleWireframe"); view_listener_t::addMenu(new LLAdvancedCheckWireframe(), "Advanced.CheckWireframe"); // Develop > Render @@ -9739,13 +9752,14 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedClickRenderShadowOption(), "Advanced.ClickRenderShadowOption"); view_listener_t::addMenu(new LLAdvancedClickRenderProfile(), "Advanced.ClickRenderProfile"); view_listener_t::addMenu(new LLAdvancedClickRenderBenchmark(), "Advanced.ClickRenderBenchmark"); - view_listener_t::addMenu(new LLAdvancedClickHDRIPreview(), "Advanced.ClickHDRIPreview"); - view_listener_t::addMenu(new LLAdvancedClickGLTFOpen(), "Advanced.ClickGLTFOpen"); - view_listener_t::addMenu(new LLAdvancedClickGLTFSaveAs(), "Advanced.ClickGLTFSaveAs"); - view_listener_t::addMenu(new LLAdvancedClickGLTFUpload(), "Advanced.ClickGLTFUpload"); - view_listener_t::addMenu(new LLAdvancedClickGLTFEdit(), "Advanced.ClickGLTFEdit"); - view_listener_t::addMenu(new LLAdvancedClickResizeWindow(), "Advanced.ClickResizeWindow"); - view_listener_t::addMenu(new LLAdvancedPurgeShaderCache(), "Advanced.ClearShaderCache"); + view_listener_t::addMenu(new LLAdvancedClickHDRIPreview(), "Advanced.ClickHDRIPreview", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedClickGLTFOpen(), "Advanced.ClickGLTFOpen", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedClickGLTFSaveAs(), "Advanced.ClickGLTFSaveAs", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedClickGLTFUpload(), "Advanced.ClickGLTFUpload", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedClickGLTFEdit(), "Advanced.ClickGLTFEdit", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedClickResizeWindow(), "Advanced.ClickResizeWindow", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedPurgeShaderCache(), "Advanced.ClearShaderCache", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedRebuildTerrain(), "Advanced.RebuildTerrain", cb_info::UNTRUSTED_BLOCK); #ifdef TOGGLE_HACKED_GODLIKE_VIEWER view_listener_t::addMenu(new LLAdvancedHandleToggleHackedGodmode(), "Advanced.HandleToggleHackedGodmode"); @@ -9768,15 +9782,15 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedTerrainDeleteLocalPaintMap(), "Advanced.TerrainDeleteLocalPaintMap"); // Advanced > UI - commit.add("Advanced.WebBrowserTest", boost::bind(&handle_web_browser_test, _2)); // sigh! this one opens the MEDIA browser - commit.add("Advanced.WebContentTest", boost::bind(&handle_web_content_test, _2)); // this one opens the Web Content floater - commit.add("Advanced.ShowURL", boost::bind(&handle_show_url, _2)); - commit.add("Advanced.ReportBug", boost::bind(&handle_report_bug, _2)); + registrar.add("Advanced.WebBrowserTest", boost::bind(&handle_web_browser_test, _2)); // sigh! this one opens the MEDIA browser + registrar.add("Advanced.WebContentTest", boost::bind(&handle_web_content_test, _2)); // this one opens the Web Content floater + registrar.add("Advanced.ShowURL", boost::bind(&handle_show_url, _2)); + registrar.add("Advanced.ReportBug", boost::bind(&handle_report_bug, _2)); view_listener_t::addMenu(new LLAdvancedBuyCurrencyTest(), "Advanced.BuyCurrencyTest"); view_listener_t::addMenu(new LLAdvancedDumpSelectMgr(), "Advanced.DumpSelectMgr"); view_listener_t::addMenu(new LLAdvancedDumpInventory(), "Advanced.DumpInventory"); - commit.add("Advanced.DumpTimers", boost::bind(&handle_dump_timers) ); - commit.add("Advanced.DumpFocusHolder", boost::bind(&handle_dump_focus) ); + registrar.add("Advanced.DumpTimers", boost::bind(&handle_dump_timers), cb_info::UNTRUSTED_THROTTLE); + registrar.add("Advanced.DumpFocusHolder", boost::bind(&handle_dump_focus)); view_listener_t::addMenu(new LLAdvancedPrintSelectedObjectInfo(), "Advanced.PrintSelectedObjectInfo"); view_listener_t::addMenu(new LLAdvancedPrintAgentInfo(), "Advanced.PrintAgentInfo"); view_listener_t::addMenu(new LLAdvancedToggleDebugClicks(), "Advanced.ToggleDebugClicks"); @@ -9797,11 +9811,11 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedCheckDebugWindowProc(), "Advanced.CheckDebugWindowProc"); // Advanced > XUI - commit.add("Advanced.ReloadColorSettings", boost::bind(&LLUIColorTable::loadFromSettings, LLUIColorTable::getInstance())); + registrar.add("Advanced.ReloadColorSettings", boost::bind(&LLUIColorTable::loadFromSettings, LLUIColorTable::getInstance())); view_listener_t::addMenu(new LLAdvancedToggleXUINames(), "Advanced.ToggleXUINames"); view_listener_t::addMenu(new LLAdvancedCheckXUINames(), "Advanced.CheckXUINames"); view_listener_t::addMenu(new LLAdvancedSendTestIms(), "Advanced.SendTestIMs"); - commit.add("Advanced.FlushNameCaches", boost::bind(&handle_flush_name_caches)); + registrar.add("Advanced.FlushNameCaches", boost::bind(&handle_flush_name_caches), cb_info::UNTRUSTED_BLOCK); // Advanced > Character > Grab Baked Texture view_listener_t::addMenu(new LLAdvancedGrabBakedTexture(), "Advanced.GrabBakedTexture"); @@ -9812,8 +9826,8 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedEnableAppearanceToXML(), "Advanced.EnableAppearanceToXML"); view_listener_t::addMenu(new LLAdvancedToggleCharacterGeometry(), "Advanced.ToggleCharacterGeometry"); - view_listener_t::addMenu(new LLAdvancedTestMale(), "Advanced.TestMale"); - view_listener_t::addMenu(new LLAdvancedTestFemale(), "Advanced.TestFemale"); + view_listener_t::addMenu(new LLAdvancedTestMale(), "Advanced.TestMale", cb_info::UNTRUSTED_THROTTLE); + view_listener_t::addMenu(new LLAdvancedTestFemale(), "Advanced.TestFemale", cb_info::UNTRUSTED_THROTTLE); // Advanced > Character > Animation Speed view_listener_t::addMenu(new LLAdvancedAnimTenFaster(), "Advanced.AnimTenFaster"); @@ -9845,7 +9859,7 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedDropPacket(), "Advanced.DropPacket"); // Advanced > Cache - view_listener_t::addMenu(new LLAdvancedPurgeDiskCache(), "Advanced.PurgeDiskCache"); + view_listener_t::addMenu(new LLAdvancedPurgeDiskCache(), "Advanced.PurgeDiskCache", cb_info::UNTRUSTED_BLOCK); // Advanced > Recorder view_listener_t::addMenu(new LLAdvancedAgentPilot(), "Advanced.AgentPilot"); @@ -9854,25 +9868,25 @@ void initialize_menus() view_listener_t::addMenu(new LLAdvancedViewerEventRecorder(), "Advanced.EventRecorder"); // Advanced > Debugging - view_listener_t::addMenu(new LLAdvancedForceErrorBreakpoint(), "Advanced.ForceErrorBreakpoint"); - view_listener_t::addMenu(new LLAdvancedForceErrorLlerror(), "Advanced.ForceErrorLlerror"); - view_listener_t::addMenu(new LLAdvancedForceErrorLlerrorMsg(), "Advanced.ForceErrorLlerrorMsg"); - view_listener_t::addMenu(new LLAdvancedForceErrorBadMemoryAccess(), "Advanced.ForceErrorBadMemoryAccess"); - view_listener_t::addMenu(new LLAdvancedForceErrorBadMemoryAccessCoro(), "Advanced.ForceErrorBadMemoryAccessCoro"); - view_listener_t::addMenu(new LLAdvancedForceErrorInfiniteLoop(), "Advanced.ForceErrorInfiniteLoop"); - view_listener_t::addMenu(new LLAdvancedForceErrorSoftwareException(), "Advanced.ForceErrorSoftwareException"); - view_listener_t::addMenu(new LLAdvancedForceOSException(), "Advanced.ForceErrorOSException"); - view_listener_t::addMenu(new LLAdvancedForceErrorSoftwareExceptionCoro(), "Advanced.ForceErrorSoftwareExceptionCoro"); - view_listener_t::addMenu(new LLAdvancedForceErrorDriverCrash(), "Advanced.ForceErrorDriverCrash"); - view_listener_t::addMenu(new LLAdvancedForceErrorCoroutineCrash(), "Advanced.ForceErrorCoroutineCrash"); - view_listener_t::addMenu(new LLAdvancedForceErrorThreadCrash(), "Advanced.ForceErrorThreadCrash"); - view_listener_t::addMenu(new LLAdvancedForceErrorDisconnectViewer(), "Advanced.ForceErrorDisconnectViewer"); + view_listener_t::addMenu(new LLAdvancedForceErrorBreakpoint(), "Advanced.ForceErrorBreakpoint", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedForceErrorLlerror(), "Advanced.ForceErrorLlerror", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedForceErrorLlerrorMsg(), "Advanced.ForceErrorLlerrorMsg", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedForceErrorBadMemoryAccess(), "Advanced.ForceErrorBadMemoryAccess", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedForceErrorBadMemoryAccessCoro(), "Advanced.ForceErrorBadMemoryAccessCoro", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedForceErrorInfiniteLoop(), "Advanced.ForceErrorInfiniteLoop", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedForceErrorSoftwareException(), "Advanced.ForceErrorSoftwareException", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedForceOSException(), "Advanced.ForceErrorOSException", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedForceErrorSoftwareExceptionCoro(), "Advanced.ForceErrorSoftwareExceptionCoro", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedForceErrorDriverCrash(), "Advanced.ForceErrorDriverCrash", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedForceErrorCoroutineCrash(), "Advanced.ForceErrorCoroutineCrash", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedForceErrorThreadCrash(), "Advanced.ForceErrorThreadCrash", cb_info::UNTRUSTED_BLOCK); + view_listener_t::addMenu(new LLAdvancedForceErrorDisconnectViewer(), "Advanced.ForceErrorDisconnectViewer", cb_info::UNTRUSTED_BLOCK); // Advanced (toplevel) view_listener_t::addMenu(new LLAdvancedToggleShowObjectUpdates(), "Advanced.ToggleShowObjectUpdates"); view_listener_t::addMenu(new LLAdvancedCheckShowObjectUpdates(), "Advanced.CheckShowObjectUpdates"); view_listener_t::addMenu(new LLAdvancedCompressImage(), "Advanced.CompressImage"); - view_listener_t::addMenu(new LLAdvancedCompressFileTest(), "Advanced.CompressFileTest"); + view_listener_t::addMenu(new LLAdvancedCompressFileTest(), "Advanced.CompressFileTest", cb_info::UNTRUSTED_BLOCK); view_listener_t::addMenu(new LLAdvancedShowDebugSettings(), "Advanced.ShowDebugSettings"); view_listener_t::addMenu(new LLAdvancedEnableViewAdminOptions(), "Advanced.EnableViewAdminOptions"); view_listener_t::addMenu(new LLAdvancedToggleViewAdminOptions(), "Advanced.ToggleViewAdminOptions"); @@ -9887,11 +9901,11 @@ void initialize_menus() view_listener_t::addMenu(new LLDevelopSetLoggingLevel(), "Develop.SetLoggingLevel"); //Develop (clear cache immediately) - commit.add("Develop.ClearCache", boost::bind(&handle_cache_clear_immediately) ); + registrar.add("Develop.ClearCache", boost::bind(&handle_cache_clear_immediately), cb_info::UNTRUSTED_BLOCK); // Develop (Fonts debugging) - commit.add("Develop.Fonts.Dump", boost::bind(&LLFontGL::dumpFonts)); - commit.add("Develop.Fonts.DumpTextures", boost::bind(&LLFontGL::dumpFontTextures)); + registrar.add("Develop.Fonts.Dump", boost::bind(&LLFontGL::dumpFonts), cb_info::UNTRUSTED_THROTTLE); + registrar.add("Develop.Fonts.DumpTextures", boost::bind(&LLFontGL::dumpFontTextures), cb_info::UNTRUSTED_THROTTLE); // Admin >Object view_listener_t::addMenu(new LLAdminForceTakeCopy(), "Admin.ForceTakeCopy"); @@ -9928,20 +9942,20 @@ void initialize_menus() view_listener_t::addMenu(new LLObjectMute(), "Avatar.Mute"); view_listener_t::addMenu(new LLAvatarAddFriend(), "Avatar.AddFriend"); view_listener_t::addMenu(new LLAvatarAddContact(), "Avatar.AddContact"); - commit.add("Avatar.Freeze", boost::bind(&handle_avatar_freeze, LLSD())); + registrar.add("Avatar.Freeze", boost::bind(&handle_avatar_freeze, LLSD())); view_listener_t::addMenu(new LLAvatarDebug(), "Avatar.Debug"); view_listener_t::addMenu(new LLAvatarVisibleDebug(), "Avatar.VisibleDebug"); view_listener_t::addMenu(new LLAvatarInviteToGroup(), "Avatar.InviteToGroup"); - commit.add("Avatar.Eject", boost::bind(&handle_avatar_eject, LLSD())); - commit.add("Avatar.ShowInspector", boost::bind(&handle_avatar_show_inspector)); + registrar.add("Avatar.Eject", boost::bind(&handle_avatar_eject, LLSD())); + registrar.add("Avatar.ShowInspector", boost::bind(&handle_avatar_show_inspector)); view_listener_t::addMenu(new LLAvatarSendIM(), "Avatar.SendIM"); - view_listener_t::addMenu(new LLAvatarCall(), "Avatar.Call"); + view_listener_t::addMenu(new LLAvatarCall(), "Avatar.Call", cb_info::UNTRUSTED_BLOCK); enable.add("Avatar.EnableCall", boost::bind(&LLAvatarActions::canCall)); - view_listener_t::addMenu(new LLAvatarReportAbuse(), "Avatar.ReportAbuse"); + view_listener_t::addMenu(new LLAvatarReportAbuse(), "Avatar.ReportAbuse", cb_info::UNTRUSTED_THROTTLE); view_listener_t::addMenu(new LLAvatarToggleMyProfile(), "Avatar.ToggleMyProfile"); view_listener_t::addMenu(new LLAvatarTogglePicks(), "Avatar.TogglePicks"); view_listener_t::addMenu(new LLAvatarToggleSearch(), "Avatar.ToggleSearch"); - view_listener_t::addMenu(new LLAvatarResetSkeleton(), "Avatar.ResetSkeleton"); + view_listener_t::addMenu(new LLAvatarResetSkeleton(), "Avatar.ResetSkeleton", cb_info::UNTRUSTED_THROTTLE); view_listener_t::addMenu(new LLAvatarEnableResetSkeleton(), "Avatar.EnableResetSkeleton"); view_listener_t::addMenu(new LLAvatarResetSkeletonAndAnimations(), "Avatar.ResetSkeletonAndAnimations"); view_listener_t::addMenu(new LLAvatarResetSelfSkeleton(), "Avatar.ResetSelfSkeleton"); @@ -9949,22 +9963,22 @@ void initialize_menus() enable.add("Avatar.IsMyProfileOpen", boost::bind(&my_profile_visible)); enable.add("Avatar.IsPicksTabOpen", boost::bind(&picks_tab_visible)); - commit.add("Avatar.OpenMarketplace", boost::bind(&LLWeb::loadURLExternal, gSavedSettings.getString("MarketplaceURL"))); + registrar.add("Avatar.OpenMarketplace", boost::bind(&LLWeb::loadURLExternal, gSavedSettings.getString("MarketplaceURL"))); view_listener_t::addMenu(new LLAvatarEnableAddFriend(), "Avatar.EnableAddFriend"); enable.add("Avatar.EnableFreezeEject", boost::bind(&enable_freeze_eject, _2)); // Object pie menu view_listener_t::addMenu(new LLObjectBuild(), "Object.Build"); - commit.add("Object.Touch", boost::bind(&handle_object_touch)); - commit.add("Object.ShowOriginal", boost::bind(&handle_object_show_original)); - commit.add("Object.SitOrStand", boost::bind(&handle_object_sit_or_stand)); - commit.add("Object.Delete", boost::bind(&handle_object_delete)); + registrar.add("Object.Touch", boost::bind(&handle_object_touch)); + registrar.add("Object.ShowOriginal", boost::bind(&handle_object_show_original)); + registrar.add("Object.SitOrStand", boost::bind(&handle_object_sit_or_stand)); + registrar.add("Object.Delete", boost::bind(&handle_object_delete)); view_listener_t::addMenu(new LLObjectAttachToAvatar(true), "Object.AttachToAvatar"); view_listener_t::addMenu(new LLObjectAttachToAvatar(false), "Object.AttachAddToAvatar"); view_listener_t::addMenu(new LLObjectReturn(), "Object.Return"); - commit.add("Object.Duplicate", boost::bind(&LLSelectMgr::duplicate, LLSelectMgr::getInstance())); - view_listener_t::addMenu(new LLObjectReportAbuse(), "Object.ReportAbuse"); + registrar.add("Object.Duplicate", boost::bind(&LLSelectMgr::duplicate, LLSelectMgr::getInstance())); + view_listener_t::addMenu(new LLObjectReportAbuse(), "Object.ReportAbuse", cb_info::UNTRUSTED_THROTTLE); view_listener_t::addMenu(new LLObjectMute(), "Object.Mute"); enable.add("Object.VisibleTake", boost::bind(&visible_take_object)); @@ -9973,15 +9987,15 @@ void initialize_menus() enable.add("Object.EnableTakeMultiple", boost::bind(&enable_take_objects)); enable.add("Object.VisibleBuy", boost::bind(&visible_buy_object)); - commit.add("Object.Buy", boost::bind(&handle_buy)); - commit.add("Object.Edit", boost::bind(&handle_object_edit)); - commit.add("Object.EditGLTFMaterial", boost::bind(&handle_object_edit_gltf_material)); - commit.add("Object.Inspect", boost::bind(&handle_object_inspect)); - commit.add("Object.Open", boost::bind(&handle_object_open)); - commit.add("Object.Take", boost::bind(&handle_take, false)); - commit.add("Object.TakeSeparate", boost::bind(&handle_take, true)); - commit.add("Object.TakeSeparateCopy", boost::bind(&handle_take_separate_copy)); - commit.add("Object.ShowInspector", boost::bind(&handle_object_show_inspector)); + registrar.add("Object.Buy", boost::bind(&handle_buy), cb_info::UNTRUSTED_THROTTLE); + registrar.add("Object.Edit", boost::bind(&handle_object_edit)); + registrar.add("Object.EditGLTFMaterial", boost::bind(&handle_object_edit_gltf_material)); + registrar.add("Object.Inspect", boost::bind(&handle_object_inspect)); + registrar.add("Object.Open", boost::bind(&handle_object_open)); + registrar.add("Object.Take", boost::bind(&handle_take, false)); + registrar.add("Object.TakeSeparate", boost::bind(&handle_take, true)); + registrar.add("Object.TakeSeparateCopy", boost::bind(&handle_take_separate_copy)); + registrar.add("Object.ShowInspector", boost::bind(&handle_object_show_inspector)); enable.add("Object.EnableInspect", boost::bind(&enable_object_inspect)); enable.add("Object.EnableEditGLTFMaterial", boost::bind(&enable_object_edit_gltf_material)); enable.add("Object.EnableOpen", boost::bind(&enable_object_open)); @@ -10000,7 +10014,7 @@ void initialize_menus() enable.add("Object.EnableMute", boost::bind(&enable_object_mute)); enable.add("Object.EnableUnmute", boost::bind(&enable_object_unmute)); enable.add("Object.EnableBuy", boost::bind(&enable_buy_object)); - commit.add("Object.ZoomIn", boost::bind(&handle_look_at_selection, "zoom")); + registrar.add("Object.ZoomIn", boost::bind(&handle_look_at_selection, "zoom")); // Attachment pie menu enable.add("Attachment.Label", boost::bind(&onEnableAttachmentLabel, _1, _2)); @@ -10022,14 +10036,14 @@ void initialize_menus() view_listener_t::addMenu(new LLMuteParticle(), "Particle.Mute"); view_listener_t::addMenu(new LLLandEnableBuyPass(), "Land.EnableBuyPass"); - commit.add("Land.Buy", boost::bind(&handle_buy_land)); + registrar.add("Land.Buy", boost::bind(&handle_buy_land), cb_info::UNTRUSTED_THROTTLE); // Generic actions - commit.add("ReportAbuse", boost::bind(&handle_report_abuse)); - commit.add("BuyCurrency", boost::bind(&handle_buy_currency)); + registrar.add("ReportAbuse", boost::bind(&handle_report_abuse), cb_info::UNTRUSTED_THROTTLE); + registrar.add("BuyCurrency", boost::bind(&handle_buy_currency), cb_info::UNTRUSTED_THROTTLE); view_listener_t::addMenu(new LLShowHelp(), "ShowHelp"); view_listener_t::addMenu(new LLToggleHelp(), "ToggleHelp"); - view_listener_t::addMenu(new LLToggleSpeak(), "ToggleSpeak"); + view_listener_t::addMenu(new LLToggleSpeak(), "ToggleSpeak", cb_info::UNTRUSTED_BLOCK); view_listener_t::addMenu(new LLPromptShowURL(), "PromptShowURL"); view_listener_t::addMenu(new LLShowAgentProfile(), "ShowAgentProfile"); view_listener_t::addMenu(new LLShowAgentProfilePicks(), "ShowAgentProfilePicks"); @@ -10038,18 +10052,18 @@ void initialize_menus() view_listener_t::addMenu(new LLToggleShaderControl(), "ToggleShaderControl"); view_listener_t::addMenu(new LLCheckControl(), "CheckControl"); view_listener_t::addMenu(new LLGoToObject(), "GoToObject"); - commit.add("PayObject", boost::bind(&handle_give_money_dialog)); + registrar.add("PayObject", boost::bind(&handle_give_money_dialog)); - commit.add("Inventory.NewWindow", boost::bind(&LLPanelMainInventory::newWindow)); + registrar.add("Inventory.NewWindow", boost::bind(&LLPanelMainInventory::newWindow), cb_info::UNTRUSTED_THROTTLE); enable.add("EnablePayObject", boost::bind(&enable_pay_object)); enable.add("EnablePayAvatar", boost::bind(&enable_pay_avatar)); enable.add("EnableEdit", boost::bind(&enable_object_edit)); enable.add("EnableMuteParticle", boost::bind(&enable_mute_particle)); - commit.add("Pathfinding.Linksets.Select", boost::bind(&LLFloaterPathfindingLinksets::openLinksetsWithSelectedObjects)); + registrar.add("Pathfinding.Linksets.Select", boost::bind(&LLFloaterPathfindingLinksets::openLinksetsWithSelectedObjects)); enable.add("EnableSelectInPathfindingLinksets", boost::bind(&enable_object_select_in_pathfinding_linksets)); enable.add("VisibleSelectInPathfindingLinksets", boost::bind(&visible_object_select_in_pathfinding_linksets)); - commit.add("Pathfinding.Characters.Select", boost::bind(&LLFloaterPathfindingCharacters::openCharactersWithSelectedObjects)); + registrar.add("Pathfinding.Characters.Select", boost::bind(&LLFloaterPathfindingCharacters::openCharactersWithSelectedObjects)); enable.add("EnableSelectInPathfindingCharacters", boost::bind(&enable_object_select_in_pathfinding_characters)); enable.add("Advanced.EnableErrorOSException", boost::bind(&enable_os_exception)); enable.add("EnableGLTF", boost::bind(&enable_gltf)); @@ -10065,4 +10079,6 @@ void initialize_menus() view_listener_t::addMenu(new LLEditableSelected(), "EditableSelected"); view_listener_t::addMenu(new LLEditableSelectedMono(), "EditableSelectedMono"); view_listener_t::addMenu(new LLToggleUIHints(), "ToggleUIHints"); + + registrar.add("Lua.RunScript", boost::bind(&lua_run_script, _2), cb_info::UNTRUSTED_BLOCK); } diff --git a/indra/newview/llviewermenu.h b/indra/newview/llviewermenu.h index 68c3dbc126..5e4b24c7e8 100644 --- a/indra/newview/llviewermenu.h +++ b/indra/newview/llviewermenu.h @@ -109,6 +109,7 @@ void handle_give_money_dialog(); bool enable_pay_object(); bool enable_buy_object(); bool handle_go_to(); +bool handle_env_setting_event(std::string event_name); // Convert strings to internal types U32 render_type_from_string(std::string_view render_type); diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index ce66dbc03f..2a871a24af 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -906,16 +906,22 @@ class LLFileEnableCloseAllWindows : public view_listener_t } }; +void close_all_windows() +{ + bool app_quitting = false; + gFloaterView->closeAllChildren(app_quitting); + LLFloaterSnapshot *floater_snapshot = LLFloaterSnapshot::findInstance(); + if (floater_snapshot) + floater_snapshot->closeFloater(app_quitting); + if (gMenuHolder) + gMenuHolder->hideMenus(); +} + class LLFileCloseAllWindows : public view_listener_t { bool handleEvent(const LLSD& userdata) { - bool app_quitting = false; - gFloaterView->closeAllChildren(app_quitting); - LLFloaterSnapshot* floater_snapshot = LLFloaterSnapshot::findInstance(); - if (floater_snapshot) - floater_snapshot->closeFloater(app_quitting); - if (gMenuHolder) gMenuHolder->hideMenus(); + close_all_windows(); return true; } }; diff --git a/indra/newview/llviewermenufile.h b/indra/newview/llviewermenufile.h index d99f9dc4c6..6b628f07de 100644 --- a/indra/newview/llviewermenufile.h +++ b/indra/newview/llviewermenufile.h @@ -74,6 +74,8 @@ bool get_bulk_upload_expected_cost( void do_bulk_upload(std::vector<std::string> filenames, bool allow_2k); +void close_all_windows(); + //consider moving all file pickers below to more suitable place class LLFilePickerThread : public LLThread { //multi-threaded file picker (runs system specific file picker in background and calls "notify" from main thread) diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index dc2258644f..a81ce58323 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -2425,8 +2425,11 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) bool ircstyle = false; + auto [message, is_script] = LLStringUtil::withoutPrefix(mesg, LUA_PREFIX); + chat.mIsScript = is_script; + // Look for IRC-style emotes here so chatbubbles work - std::string prefix = mesg.substr(0, 4); + std::string prefix = message.substr(0, 4); if (prefix == "/me " || prefix == "/me'") { ircstyle = true; @@ -2468,10 +2471,19 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) else { chat.mText = ""; + auto [msg_without_prefix, is_lua] = LLStringUtil::withoutPrefix(mesg, LUA_PREFIX); + std::string prefix; + if (is_lua) + { + prefix = LUA_PREFIX; + } switch(chat.mChatType) { case CHAT_TYPE_WHISPER: - chat.mText = LLTrans::getString("whisper") + " "; + prefix += LLTrans::getString("whisper") + " "; + break; + case CHAT_TYPE_SHOUT: + prefix += LLTrans::getString("shout") + " "; break; case CHAT_TYPE_OWNER: if (RlvActions::isRlvEnabled()) @@ -2495,9 +2507,6 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) case CHAT_TYPE_NORMAL: case CHAT_TYPE_DIRECT: break; - case CHAT_TYPE_SHOUT: - chat.mText = LLTrans::getString("shout") + " "; - break; case CHAT_TYPE_START: case CHAT_TYPE_STOP: LL_WARNS("Messaging") << "Got chat type start/stop in main chat processing." << LL_ENDL; @@ -2507,7 +2516,7 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) break; } - chat.mText += mesg; + chat.mText = prefix + msg_without_prefix; } // We have a real utterance now, so can stop showing "..." and proceed. @@ -2588,6 +2597,11 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data) msg_notify["from_id"] = chat.mFromID; msg_notify["source_type"] = chat.mSourceType; on_new_message(msg_notify); + + + msg_notify["chat_type"] = chat.mChatType; + msg_notify["message"] = mesg; + LLEventPumps::instance().obtain("LLNearbyChat").post(msg_notify); } } @@ -2733,7 +2747,7 @@ public: virtual ~LLPostTeleportNotifiers(); //function to be called at the supplied frequency - virtual bool tick(); + bool tick() override; }; LLPostTeleportNotifiers::LLPostTeleportNotifiers() : LLEventTimer( 2.0 ) @@ -6759,7 +6773,8 @@ void onCovenantLoadComplete(const LLUUID& asset_uuid, { LL_DEBUGS("Messaging") << "onCovenantLoadComplete()" << LL_ENDL; std::string covenant_text; - if(0 == status) + std::unique_ptr<LLViewerTextEditor> editorp; + if (0 == status) { LLFileSystem file(asset_uuid, type, LLFileSystem::READ); @@ -6780,13 +6795,13 @@ void onCovenantLoadComplete(const LLUUID& asset_uuid, { LL_WARNS("Messaging") << "Problem importing estate covenant." << LL_ENDL; covenant_text = "Problem importing estate covenant."; + delete editor; } else { // Version 0 (just text, doesn't include version number) - covenant_text = editor->getText(); + editorp.reset(editor); // Use covenant from editorp; } - delete editor; } else { @@ -6812,17 +6827,32 @@ void onCovenantLoadComplete(const LLUUID& asset_uuid, LL_WARNS("Messaging") << "Problem loading notecard: " << status << LL_ENDL; } - LLPanelEstateCovenant::updateCovenantText(covenant_text, asset_uuid); - LLPanelLandCovenant::updateCovenantText(covenant_text); - LLFloaterBuyLand::updateCovenantText(covenant_text, asset_uuid); - LLPanelPlaceProfile* panel = LLFloaterSidePanelContainer::getPanel<LLPanelPlaceProfile>("places", "panel_place_profile"); - if (panel) + if (editorp) { - panel->updateCovenantText(covenant_text); + LLPanelEstateCovenant::updateCovenant(editorp.get(), asset_uuid); + LLPanelLandCovenant::updateCovenant(editorp.get()); + LLFloaterBuyLand::updateCovenant(editorp.get(), asset_uuid); + } + else + { + LLPanelEstateCovenant::updateCovenantText(covenant_text, asset_uuid); + LLPanelLandCovenant::updateCovenantText(covenant_text); + LLFloaterBuyLand::updateCovenantText(covenant_text, asset_uuid); } -} + if (LLPanelPlaceProfile* panel = LLFloaterSidePanelContainer::getPanel<LLPanelPlaceProfile>("places", "panel_place_profile")) + { + if (editorp) + { + panel->updateCovenant(editorp.get()); + } + else + { + panel->updateCovenantText(covenant_text); + } + } +} void process_feature_disabled_message(LLMessageSystem* msg, void**) { diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 86440fca48..7741f1fbe7 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -2325,6 +2325,12 @@ U32 LLViewerObject::processUpdateMessage(LLMessageSystem *mesgsys, // Set the rotation of the object followed by adjusting for the accumulated angular velocity (llSetTargetOmega) setRotation(new_rot * mAngularVelocityRot); + if ((mFlags & FLAGS_SERVER_AUTOPILOT) && asAvatar() && asAvatar()->isSelf()) + { + gAgent.resetAxes(); + gAgent.rotate(new_rot); + gAgentCamera.resetView(); + } setChanged(ROTATED | SILHOUETTE); } @@ -2917,24 +2923,33 @@ void LLViewerObject::fetchInventoryFromServer() delete mInventory; mInventory = NULL; - // Results in processTaskInv - LLMessageSystem* msg = gMessageSystem; - msg->newMessageFast(_PREHASH_RequestTaskInventory); - msg->nextBlockFast(_PREHASH_AgentData); - msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); - msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); - msg->nextBlockFast(_PREHASH_InventoryData); - msg->addU32Fast(_PREHASH_LocalID, mLocalID); - msg->sendReliable(mRegionp->getHost()); - // This will get reset by doInventoryCallback or processTaskInv mInvRequestState = INVENTORY_REQUEST_PENDING; + + if (mRegionp && !mRegionp->getCapability("RequestTaskInventory").empty()) + { + LLCoros::instance().launch("LLViewerObject::fetchInventoryFromCapCoro()", + boost::bind(&LLViewerObject::fetchInventoryFromCapCoro, mID)); + } + else + { + LL_WARNS() << "Using old task inventory path!" << LL_ENDL; + // Results in processTaskInv + LLMessageSystem *msg = gMessageSystem; + msg->newMessageFast(_PREHASH_RequestTaskInventory); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgent.getID()); + msg->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); + msg->nextBlockFast(_PREHASH_InventoryData); + msg->addU32Fast(_PREHASH_LocalID, mLocalID); + msg->sendReliable(mRegionp->getHost()); + } } } void LLViewerObject::fetchInventoryDelayed(const F64 &time_seconds) { - // unless already waiting, drop previous request and shedule an update + // unless already waiting, drop previous request and schedule an update if (mInvRequestState != INVENTORY_REQUEST_WAIT) { if (mInvRequestXFerId != 0) @@ -2965,6 +2980,80 @@ void LLViewerObject::fetchInventoryDelayedCoro(const LLUUID task_inv, const F64 } } +//static +void LLViewerObject::fetchInventoryFromCapCoro(const LLUUID task_inv) +{ + LLViewerObject *obj = gObjectList.findObject(task_inv); + if (obj) + { + LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); + LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t + httpAdapter(new LLCoreHttpUtil::HttpCoroutineAdapter("TaskInventoryRequest", httpPolicy)); + LLCore::HttpRequest::ptr_t httpRequest(new LLCore::HttpRequest); + std::string url = obj->mRegionp->getCapability("RequestTaskInventory") + "?task_id=" + obj->mID.asString(); + // If we already have a copy of the inventory then add it so the server won't re-send something we already have. + // We expect this case to crop up in the case of failed inventory mutations, but it might happen otherwise as well. + if (obj->mInventorySerialNum && obj->mInventory) + url += "&inventory_serial=" + std::to_string(obj->mInventorySerialNum); + + obj->mInvRequestState = INVENTORY_XFER; + LLSD result = httpAdapter->getAndSuspend(httpRequest, url); + + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + // Object may have gone away while we were suspended, double-check that it still exists + obj = gObjectList.findObject(task_inv); + if (!obj) + { + LL_WARNS() << "Object " << task_inv << " went away while fetching inventory, dropping result" << LL_ENDL; + return; + } + + bool potentially_stale = false; + if (status) + { + // Dealing with inventory serials is kind of funky. They're monotonically increasing and 16 bits, + // so we expect them to overflow, but we can use inv serial < expected serial as a signal that we may + // have mutated the task inventory since we kicked off the request, and those mutations may have not + // been taken into account yet. Of course, those mutations may have actually failed which would result + // in the inv serial never increasing. + // + // When we detect this case, set the expected inv serial to the inventory serial we actually received + // and kick off a re-request after a slight delay. + S16 serial = (S16)result["inventory_serial"].asInteger(); + potentially_stale = serial < obj->mExpectedInventorySerialNum; + LL_INFOS() << "Inventory loaded for " << task_inv << LL_ENDL; + obj->mInventorySerialNum = serial; + obj->mExpectedInventorySerialNum = serial; + obj->loadTaskInvLLSD(result); + } + else if (status.getType() == 304) + { + LL_INFOS() << "Inventory wasn't changed on server!" << LL_ENDL; + obj->mInvRequestState = INVENTORY_REQUEST_STOPPED; + // Even though it wasn't necessary to send a response, we still may have mutated + // the inventory since we kicked off the request, check for that case. + potentially_stale = obj->mInventorySerialNum < obj->mExpectedInventorySerialNum; + // Set this to what we already have so that we don't re-request a second time. + obj->mExpectedInventorySerialNum = obj->mInventorySerialNum; + } + else + { + // Not sure that there's anything sensible we can do to recover here, retrying in a loop would be bad. + LL_WARNS() << "Error status while requesting task inventory: " << status.toString() << LL_ENDL; + obj->mInvRequestState = INVENTORY_REQUEST_STOPPED; + } + + if (potentially_stale) + { + // Stale? I guess we can use what we got for now, but we'll have to re-request + LL_WARNS() << "Stale inv_serial? Re-requesting." << LL_ENDL; + obj->fetchInventoryDelayed(INVENTORY_UPDATE_WAIT_TIME_OUTDATED); + } + } +} + LLControlAvatar *LLViewerObject::getControlAvatar() { return getRootEdit()->mControlAvatar.get(); @@ -3140,6 +3229,20 @@ void LLViewerObject::processTaskInv(LLMessageSystem* msg, void** user_data) S16 serial = 0; msg->getS16Fast(_PREHASH_InventoryData, _PREHASH_Serial, serial); + if (object->mRegionp && !object->mRegionp->getCapability("RequestTaskInventory").empty()) + { + // It seems that simulator may ask us to re-download the task inventory if an update to the inventory + // happened out-of-band while we had the object selected (like if a script is saved.) + // + // If we're meant to use the HTTP capability, ignore the contents of the UDP message and fetch the + // inventory via the CAP so that we don't flow down the UDP inventory request path unconditionally here. + // We shouldn't need to wait, as any updates should already be ready to fetch by this point. + LL_INFOS() << "Handling unsolicited ReplyTaskInventory for " << task_id << LL_ENDL; + object->mExpectedInventorySerialNum = serial; + object->fetchInventoryFromServer(); + return; + } + if (serial == object->mInventorySerialNum && serial < object->mExpectedInventorySerialNum) { @@ -3347,6 +3450,47 @@ bool LLViewerObject::loadTaskInvFile(const std::string& filename) return true; } +void LLViewerObject::loadTaskInvLLSD(const LLSD& inv_result) +{ + if (inv_result.has("contents")) + { + if(mInventory) + { + mInventory->clear(); // will deref and delete it + } + else + { + mInventory = new LLInventoryObject::object_list_t; + } + + // Synthesize the "Contents" category, the viewer expects it, but it isn't sent. + LLPointer<LLInventoryObject> inv = new LLInventoryObject(mID, LLUUID::null, LLAssetType::AT_CATEGORY, "Contents"); + mInventory->push_front(inv); + + const LLSD& inventory = inv_result["contents"]; + for (const auto& inv_entry : llsd::inArray(inventory)) + { + if (inv_entry.has("item_id")) + { + LLPointer<LLViewerInventoryItem> inv = new LLViewerInventoryItem; + inv->unpackMessage(inv_entry); + mInventory->push_front(inv); + } + else + { + LL_WARNS_ONCE() << "Unknown inventory entry while reading from inventory file. Entry: '" + << inv_entry << "'" << LL_ENDL; + } + } + } + else + { + LL_WARNS() << "unable to load task inventory: " << inv_result << LL_ENDL; + return; + } + doInventoryCallback(); +} + void LLViewerObject::doInventoryCallback() { for (callback_list_t::iterator iter = mInventoryCallbacks.begin(); @@ -5097,8 +5241,7 @@ void LLViewerObject::updateTEMaterialTextures(U8 te) LLUUID mat_id = getRenderMaterialID(te); if (mat == nullptr && mat_id.notNull()) { - mat = (LLFetchedGLTFMaterial*) gGLTFMaterialList.getMaterial(mat_id); - llassert(mat == nullptr || dynamic_cast<LLFetchedGLTFMaterial*>(gGLTFMaterialList.getMaterial(mat_id)) != nullptr); + mat = gGLTFMaterialList.getMaterial(mat_id); if (mat->isFetching()) { // material is not loaded yet, rebuild draw info when the object finishes loading mat->onMaterialComplete([id=getID()] diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h index b6846c6716..09f813accc 100644 --- a/indra/newview/llviewerobject.h +++ b/indra/newview/llviewerobject.h @@ -688,6 +688,7 @@ private: // forms task inventory request after some time passed, marks request as pending void fetchInventoryDelayed(const F64 &time_seconds); static void fetchInventoryDelayedCoro(const LLUUID task_inv, const F64 time_seconds); + static void fetchInventoryFromCapCoro(const LLUUID task_inv); public: // @@ -822,6 +823,7 @@ protected: static void processTaskInvFile(void** user_data, S32 error_code, LLExtStat ext_status); bool loadTaskInvFile(const std::string& filename); + void loadTaskInvLLSD(const LLSD &inv_result); void doInventoryCallback(); bool isOnMap(); diff --git a/indra/newview/llviewerobjectlist.cpp b/indra/newview/llviewerobjectlist.cpp index 9e1d86faac..6167129077 100644 --- a/indra/newview/llviewerobjectlist.cpp +++ b/indra/newview/llviewerobjectlist.cpp @@ -65,6 +65,7 @@ #include "lltoolmgr.h" #include "lltoolpie.h" #include "llkeyboard.h" +#include "llmeshrepository.h" #include "u64.h" #include "llviewertexturelist.h" #include "lldatapacker.h" @@ -1419,6 +1420,10 @@ void LLViewerObjectList::cleanDeadObjects(bool use_timer) // No dead objects, don't need to scan object list. return; } + if ((LLApp::isExiting()) || (mNumDeadObjects == (S32)mObjects.size())) + { + gMeshRepo.unregisterAllMeshes(); + } LL_PROFILE_ZONE_SCOPED; diff --git a/indra/newview/llviewerparcelmediaautoplay.h b/indra/newview/llviewerparcelmediaautoplay.h index 96b569f126..9759cf6a17 100644 --- a/indra/newview/llviewerparcelmediaautoplay.h +++ b/indra/newview/llviewerparcelmediaautoplay.h @@ -35,7 +35,7 @@ class LLViewerParcelMediaAutoPlay : LLEventTimer, public LLSingleton<LLViewerPar { LLSINGLETON(LLViewerParcelMediaAutoPlay); public: - virtual bool tick() override; + bool tick() override; static void playStarted(); private: diff --git a/indra/newview/llviewerparceloverlay.cpp b/indra/newview/llviewerparceloverlay.cpp index 2e9b5de72b..019e870829 100755 --- a/indra/newview/llviewerparceloverlay.cpp +++ b/indra/newview/llviewerparceloverlay.cpp @@ -33,6 +33,7 @@ #include "llfloaterreg.h" #include "llgl.h" #include "llrender.h" +#include "lluicolor.h" #include "v4color.h" #include "v2math.h" @@ -50,8 +51,8 @@ #include "pipeline.h" -static const U8 OVERLAY_IMG_COMPONENTS = 4; -static const F32 LINE_WIDTH = 0.0625f; +static constexpr U8 OVERLAY_IMG_COMPONENTS = 4; +static constexpr F32 LINE_WIDTH = 0.0625f; bool LLViewerParcelOverlay::sColorSetInitialized = false; LLUIColor LLViewerParcelOverlay::sAvailColor; @@ -62,9 +63,9 @@ LLUIColor LLViewerParcelOverlay::sForSaleColor; LLUIColor LLViewerParcelOverlay::sAuctionColor; LLViewerParcelOverlay::LLViewerParcelOverlay(LLViewerRegion* region, F32 region_width_meters) -: mRegion( region ), - mParcelGridsPerEdge( S32( region_width_meters / PARCEL_GRID_STEP_METERS ) ), - mDirty( false ), +: mRegion(region), + mParcelGridsPerEdge(S32(region_width_meters / PARCEL_GRID_STEP_METERS)), + mDirty(false), mTimeSinceLastUpdate(), mOverlayTextureIdx(-1) { @@ -91,7 +92,7 @@ LLViewerParcelOverlay::LLViewerParcelOverlay(LLViewerRegion* region, F32 region_ // Initialize the GL texture with empty data. // // Create the base texture. - U8 *raw = mImageRaw->getData(); + U8* raw = mImageRaw->getData(); const S32 COUNT = mParcelGridsPerEdge * mParcelGridsPerEdge * OVERLAY_IMG_COMPONENTS; for (S32 i = 0; i < COUNT; i++) { @@ -158,10 +159,10 @@ bool LLViewerParcelOverlay::encroachesOwned(const std::vector<LLBBox>& boxes) co LLVector3 min = boxes[i].getMinAgent(); LLVector3 max = boxes[i].getMaxAgent(); - S32 left = S32(llclamp((min.mV[VX] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1)); - S32 right = S32(llclamp((max.mV[VX] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1)); - S32 top = S32(llclamp((min.mV[VY] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1)); - S32 bottom = S32(llclamp((max.mV[VY] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1)); + S32 left = S32(llclamp((min.mV[VX] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1.f)); + S32 right = S32(llclamp((max.mV[VX] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1.f)); + S32 top = S32(llclamp((min.mV[VY] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1.f)); + S32 bottom = S32(llclamp((max.mV[VY] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1.f)); for (S32 row = top; row <= bottom; row++) { @@ -186,10 +187,10 @@ bool LLViewerParcelOverlay::encroachesOnUnowned(const std::vector<LLBBox>& boxes LLVector3 min = boxes[i].getMinAgent(); LLVector3 max = boxes[i].getMaxAgent(); - S32 left = S32(llclamp((min.mV[VX] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1)); - S32 right = S32(llclamp((max.mV[VX] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1)); - S32 top = S32(llclamp((min.mV[VY] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1)); - S32 bottom = S32(llclamp((max.mV[VY] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1)); + S32 left = S32(llclamp((min.mV[VX] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1.f)); + S32 right = S32(llclamp((max.mV[VX] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1.f)); + S32 top = S32(llclamp((min.mV[VY] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1.f)); + S32 bottom = S32(llclamp((max.mV[VY] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1.f)); for (S32 row = top; row <= bottom; row++) { @@ -223,10 +224,10 @@ bool LLViewerParcelOverlay::encroachesOnNearbyParcel(const std::vector<LLBBox>& return true; } - S32 left = S32(llclamp((min.mV[VX] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1)); - S32 right = S32(llclamp((max.mV[VX] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1)); - S32 bottom = S32(llclamp((min.mV[VY] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1)); - S32 top = S32(llclamp((max.mV[VY] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1)); + S32 left = S32(llclamp((min.mV[VX] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1.f)); + S32 right = S32(llclamp((max.mV[VX] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1.f)); + S32 bottom = S32(llclamp((min.mV[VY] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1.f)); + S32 top = S32(llclamp((max.mV[VY] / PARCEL_GRID_STEP_METERS), 0.f, REGION_WIDTH_METERS - 1.f)); const S32 GRIDS_PER_EDGE = mParcelGridsPerEdge; @@ -348,11 +349,11 @@ void LLViewerParcelOverlay::updateOverlayTexture() const LLColor4U auction = sAuctionColor.get(); // Create the base texture. - U8 *raw = mImageRaw->getData(); + U8* raw = mImageRaw->getData(); const S32 COUNT = mParcelGridsPerEdge * mParcelGridsPerEdge; S32 max = mOverlayTextureIdx + mParcelGridsPerEdge; if (max > COUNT) max = COUNT; - S32 pixel_index = mOverlayTextureIdx*OVERLAY_IMG_COMPONENTS; + S32 pixel_index = mOverlayTextureIdx * OVERLAY_IMG_COMPONENTS; S32 i; for (i = mOverlayTextureIdx; i < max; i++) { @@ -361,7 +362,7 @@ void LLViewerParcelOverlay::updateOverlayTexture() U8 r,g,b,a; // Color stored in low three bits - switch( ownership & 0x7 ) + switch (ownership & 0x7) { case PARCEL_PUBLIC: r = avail.mV[VRED]; @@ -407,10 +408,10 @@ void LLViewerParcelOverlay::updateOverlayTexture() break; } - raw[pixel_index + 0] = (U8)r; - raw[pixel_index + 1] = (U8)g; - raw[pixel_index + 2] = (U8)b; - raw[pixel_index + 3] = (U8)a; + raw[pixel_index + VRED] = (U8)r; + raw[pixel_index + VGREEN] = (U8)g; + raw[pixel_index + VBLUE] = (U8)b; + raw[pixel_index + VALPHA] = (U8)a; pixel_index += OVERLAY_IMG_COMPONENTS; } @@ -431,11 +432,10 @@ void LLViewerParcelOverlay::updateOverlayTexture() } } - -void LLViewerParcelOverlay::uncompressLandOverlay(S32 chunk, U8 *packed_overlay) +void LLViewerParcelOverlay::uncompressLandOverlay(S32 chunk, U8* packed_overlay) { // Unpack the message data into the ownership array - S32 size = mParcelGridsPerEdge * mParcelGridsPerEdge; + S32 size = mParcelGridsPerEdge * mParcelGridsPerEdge; S32 chunk_size = size / PARCEL_OVERLAY_CHUNKS; memcpy(mOwnership + chunk*chunk_size, packed_overlay, chunk_size); /*Flawfinder: ignore*/ @@ -460,16 +460,16 @@ void LLViewerParcelOverlay::updatePropertyLines() mEdges.clear(); - const F32 GRID_STEP = PARCEL_GRID_STEP_METERS; + constexpr F32 GRID_STEP = PARCEL_GRID_STEP_METERS; const S32 GRIDS_PER_EDGE = mParcelGridsPerEdge; for (S32 row = 0; row < GRIDS_PER_EDGE; row++) { for (S32 col = 0; col < GRIDS_PER_EDGE; col++) { - U8 overlay = mOwnership[row*GRIDS_PER_EDGE+col]; + U8 overlay = mOwnership[row * GRIDS_PER_EDGE + col]; S32 colorIndex = overlay & PARCEL_COLOR_MASK; - switch(colorIndex) + switch (colorIndex) { case PARCEL_SELF: case PARCEL_GROUP: @@ -483,11 +483,11 @@ void LLViewerParcelOverlay::updatePropertyLines() const LLColor4U& color = colors[colorIndex]; - F32 left = col*GRID_STEP; - F32 right = left+GRID_STEP; + F32 left = col * GRID_STEP; + F32 right = left + GRID_STEP; - F32 bottom = row*GRID_STEP; - F32 top = bottom+GRID_STEP; + F32 bottom = row * GRID_STEP; + F32 top = bottom + GRID_STEP; // West edge if (overlay & PARCEL_WEST_LINE) @@ -528,99 +528,123 @@ void LLViewerParcelOverlay::addPropertyLine(F32 start_x, F32 start_y, F32 dx, F3 Edge& edge = mEdges.back(); edge.color = color; + // Detailized rendering vertices: + // A B C D E F G + // *-*------*--------*--------*------*-* : 'outside' vertices are placed right on the border + // *------*--------*--------*------* : 'inside' vertices are shifted on LINE_WIDTH inside + + // Simplified rendering vertices: + // A G + // *-----------------------------------* + // *-----------------------------------* + F32 outside_x = start_x; F32 outside_y = start_y; - F32 outside_z = 0.f; - F32 inside_x = start_x + tick_dx; - F32 inside_y = start_y + tick_dy; - F32 inside_z = 0.f; + F32 outside_z = land.resolveHeightRegion(outside_x, outside_y); + F32 inside_x = start_x + tick_dx; + F32 inside_y = start_y + tick_dy; + F32 inside_z = land.resolveHeightRegion(inside_x, inside_y); + + auto move = [&](F32 distance) + { + outside_x += dx * distance; + outside_y += dy * distance; + outside_z = land.resolveHeightRegion(outside_x, outside_y); + inside_x += dx * distance; + inside_y += dy * distance; + inside_z = land.resolveHeightRegion(inside_x, inside_y); + }; - auto split = [&](const LLVector3& start, F32 x, F32 y, F32 z, F32 part) + auto split = [&](U32 lod, const LLVector4a& start, F32 x, F32 y, F32 z, F32 part) { - F32 new_x = start.mV[0] + (x - start.mV[0]) * part; - F32 new_y = start.mV[1] + (y - start.mV[1]) * part; - F32 new_z = start.mV[2] + (z - start.mV[2]) * part; - edge.vertices.emplace_back(new_x, new_y, new_z); + F32 new_x = start[VX] + (x - start[VX]) * part; + F32 new_y = start[VY] + (y - start[VY]) * part; + F32 new_z = start[VZ] + (z - start[VZ]) * part; + edge.pushVertex(lod, new_x, new_y, new_z, water_z); }; - auto checkForSplit = [&]() + auto checkForSplit = [&](U32 lod) { - const LLVector3& last_outside = edge.vertices.back(); - F32 z0 = last_outside.mV[2]; + const std::vector<LLVector4a>& vertices = edge.verticesUnderWater[lod]; + const LLVector4a& last_outside = vertices.back(); + F32 z0 = last_outside[VZ]; F32 z1 = outside_z; if ((z0 >= water_z && z1 >= water_z) || (z0 < water_z && z1 < water_z)) return; F32 part = (water_z - z0) / (z1 - z0); - const LLVector3& last_inside = edge.vertices[edge.vertices.size() - 2]; - split(last_inside, inside_x, inside_y, inside_z, part); - split(last_outside, outside_x, outside_y, outside_z, part); + const LLVector4a& last_inside = vertices[vertices.size() - 2]; + split(lod, last_inside, inside_x, inside_y, inside_z, part); + split(lod, last_outside, outside_x, outside_y, outside_z, part); }; - // First part, only one vertex - outside_z = land.resolveHeightRegion( outside_x, outside_y ); - - edge.vertices.emplace_back(outside_x, outside_y, outside_z); - - inside_x += dx * LINE_WIDTH; - inside_y += dy * LINE_WIDTH; - - outside_x += dx * LINE_WIDTH; - outside_y += dy * LINE_WIDTH; - - // Then the "actual edge" - inside_z = land.resolveHeightRegion( inside_x, inside_y ); - outside_z = land.resolveHeightRegion( outside_x, outside_y ); + auto pushTwoVertices = [&](U32 lod) + { + LLVector3 out(outside_x, outside_y, outside_z); + LLVector3 in(inside_x, inside_y, inside_z); + if (fabs(inside_z - outside_z) < LINE_WIDTH / 5) + { + edge.pushVertex(lod, inside_x, inside_y, inside_z, water_z); + } + else + { + // Make the line thinner if heights differ too much + LLVector3 dist(in - out); + F32 coef = dist.length() / LINE_WIDTH; + LLVector3 new_in(out + dist / coef); + edge.pushVertex(lod, new_in[VX], new_in[VY], new_in[VZ], water_z); + } + edge.pushVertex(lod, outside_x, outside_y, outside_z, water_z); + }; - edge.vertices.emplace_back(inside_x, inside_y, inside_z); - edge.vertices.emplace_back(outside_x, outside_y, outside_z); + // Point A simplified (first two vertices) + pushTwoVertices(1); - inside_x += dx * (dx - LINE_WIDTH); - inside_y += dy * (dy - LINE_WIDTH); + // Point A detailized (only one vertex) + edge.pushVertex(0, outside_x, outside_y, outside_z, water_z); - outside_x += dx * (dx - LINE_WIDTH); - outside_y += dy * (dy - LINE_WIDTH); + // Point B (two vertices) + move(LINE_WIDTH); + pushTwoVertices(0); - // Middle part, full width - const S32 GRID_STEP = S32( PARCEL_GRID_STEP_METERS ); - for (S32 i = 1; i < GRID_STEP; i++) + // Points C, D, E + F32 distance = 1.f - LINE_WIDTH; + constexpr U32 GRID_STEP = (U32)PARCEL_GRID_STEP_METERS; + for (U32 i = 1; i < GRID_STEP; ++i) { - inside_z = land.resolveHeightRegion( inside_x, inside_y ); - outside_z = land.resolveHeightRegion( outside_x, outside_y ); - - checkForSplit(); - - edge.vertices.emplace_back(inside_x, inside_y, inside_z); - edge.vertices.emplace_back(outside_x, outside_y, outside_z); - - inside_x += dx; - inside_y += dy; - - outside_x += dx; - outside_y += dy; + move(distance); + checkForSplit(0); + pushTwoVertices(0); + distance = 1.f; } - // Extra buffer for edge - inside_x -= dx * LINE_WIDTH; - inside_y -= dy * LINE_WIDTH; - - outside_x -= dx * LINE_WIDTH; - outside_y -= dy * LINE_WIDTH; - - inside_z = land.resolveHeightRegion( inside_x, inside_y ); - outside_z = land.resolveHeightRegion( outside_x, outside_y ); + // Point F (two vertices) + move(1.f - LINE_WIDTH); + checkForSplit(0); + pushTwoVertices(0); - checkForSplit(); + // Point G simplified (last two vertices) + move(LINE_WIDTH); + checkForSplit(1); + pushTwoVertices(1); - edge.vertices.emplace_back(inside_x, inside_y, inside_z); - edge.vertices.emplace_back(outside_x, outside_y, outside_z); - - outside_x += dx * LINE_WIDTH; - outside_y += dy * LINE_WIDTH; + // Point G detailized (only one vertex) + edge.pushVertex(0, outside_x, outside_y, outside_z, water_z); +} - // Last edge is not drawn to the edge - outside_z = land.resolveHeightRegion( outside_x, outside_y ); +void LLViewerParcelOverlay::Edge::pushVertex(U32 lod, F32 x, F32 y, F32 z, F32 water_z) +{ + verticesUnderWater[lod].emplace_back(x, y, z); + gGL.transform(verticesUnderWater[lod].back()); - edge.vertices.emplace_back(outside_x, outside_y, outside_z); + if (z >= water_z) + { + verticesAboveWater[lod].push_back(verticesUnderWater[lod].back()); + } + else + { + verticesAboveWater[lod].emplace_back(x, y, water_z); + gGL.transform(verticesAboveWater[lod].back()); + } } void LLViewerParcelOverlay::setDirty() @@ -700,6 +724,8 @@ void LLViewerParcelOverlay::renderPropertyLines() // Stomp the camera into two dimensions LLVector3 camera_region = mRegion->getPosRegionFromGlobal( gAgentCamera.getCameraPositionGlobal() ); + bool draw_underwater = camera_region.mV[VZ] < water_z || + !gPipeline.hasRenderType(LLPipeline::RENDER_TYPE_WATER); // Set up a cull plane 2 * PARCEL_GRID_STEP_METERS behind // the camera. The cull plane normal is the camera's at axis. @@ -707,15 +733,23 @@ void LLViewerParcelOverlay::renderPropertyLines() cull_plane_point *= -2.f * PARCEL_GRID_STEP_METERS; cull_plane_point += camera_region; - bool render_hidden = LLSelectMgr::sRenderHiddenSelections && LLFloaterReg::instanceVisible("build"); + bool render_hidden = !draw_underwater && + LLSelectMgr::sRenderHiddenSelections && + LLFloaterReg::instanceVisible("build"); - const F32 PROPERTY_LINE_CLIP_DIST_SQUARED = 256.f * 256.f; + constexpr F32 PROPERTY_LINE_CLIP_DIST_SQUARED = 256.f * 256.f; + constexpr F32 PROPERTY_LINE_LOD0_DIST_SQUARED = PROPERTY_LINE_CLIP_DIST_SQUARED / 25.f; for (const Edge& edge : mEdges) { - LLVector3 center = edge.vertices[edge.vertices.size() >> 1]; - - if (dist_vec_squared2D(center, camera_region) > PROPERTY_LINE_CLIP_DIST_SQUARED) + const std::vector<LLVector4a>& vertices0 = edge.verticesAboveWater[0]; + const F32* first = vertices0.front().getF32ptr(); + const F32* last = vertices0.back().getF32ptr(); + LLVector3 center((first[VX] + last[VX]) / 2, (first[VY] + last[VY]) / 2, (first[VZ] + last[VZ]) / 2); + gGL.untransform(center); + + F32 dist_squared = dist_vec_squared(center, camera_region); + if (dist_squared > PROPERTY_LINE_CLIP_DIST_SQUARED) { continue; } @@ -729,43 +763,33 @@ void LLViewerParcelOverlay::renderPropertyLines() continue; } + U32 lod = dist_squared < PROPERTY_LINE_LOD0_DIST_SQUARED ? 0 : 1; + gGL.begin(LLRender::TRIANGLE_STRIP); gGL.color4ubv(edge.color.mV); - for (const LLVector3& vertex : edge.vertices) + if (draw_underwater) { - if (render_hidden || camera_z < water_z || vertex.mV[2] >= water_z) - { - gGL.vertex3fv(vertex.mV); - } - else - { - LLVector3 visible = vertex; - visible.mV[2] = water_z; - gGL.vertex3fv(visible.mV); - } + gGL.vertexBatchPreTransformed(edge.verticesUnderWater[lod]); } - - gGL.end(); - - if (render_hidden) + else { - LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_GREATER); + gGL.vertexBatchPreTransformed(edge.verticesAboveWater[lod]); - gGL.begin(LLRender::TRIANGLE_STRIP); + if (render_hidden) + { + LLGLDepthTest depth(GL_TRUE, GL_FALSE, GL_GREATER); - LLColor4U color = edge.color; - color.mV[3] /= 4; - gGL.color4ubv(color.mV); + LLColor4U color = edge.color; + color.mV[VALPHA] /= 4; + gGL.color4ubv(color.mV); - for (const LLVector3& vertex : edge.vertices) - { - gGL.vertex3fv(vertex.mV); + gGL.vertexBatchPreTransformed(edge.verticesUnderWater[lod]); } - - gGL.end(); } + + gGL.end(); } gGL.popMatrix(); @@ -790,7 +814,7 @@ void grid_2d_part_lines(const F32 left, const F32 top, const F32 right, const F3 gGL.end(); } -void LLViewerParcelOverlay::renderPropertyLinesOnMinimap(F32 scale_pixels_per_meter, const F32 *parcel_outline_color) +void LLViewerParcelOverlay::renderPropertyLinesOnMinimap(F32 scale_pixels_per_meter, const F32* parcel_outline_color) { static LLCachedControl<bool> show(gSavedSettings, "MiniMapShowPropertyLines"); @@ -801,8 +825,8 @@ void LLViewerParcelOverlay::renderPropertyLinesOnMinimap(F32 scale_pixels_per_me LLVector3 origin_agent = mRegion->getOriginAgent(); LLVector3 rel_region_pos = origin_agent - gAgentCamera.getCameraPositionAgent(); - F32 region_left = rel_region_pos.mV[0] * scale_pixels_per_meter; - F32 region_bottom = rel_region_pos.mV[1] * scale_pixels_per_meter; + F32 region_left = rel_region_pos.mV[VX] * scale_pixels_per_meter; + F32 region_bottom = rel_region_pos.mV[VY] * scale_pixels_per_meter; F32 map_parcel_width = PARCEL_GRID_STEP_METERS * scale_pixels_per_meter; const S32 GRIDS_PER_EDGE = mParcelGridsPerEdge; diff --git a/indra/newview/llviewerparceloverlay.h b/indra/newview/llviewerparceloverlay.h index 03ae464cb8..7271c85701 100644 --- a/indra/newview/llviewerparceloverlay.h +++ b/indra/newview/llviewerparceloverlay.h @@ -34,12 +34,11 @@ #include "llframetimer.h" #include "lluuid.h" #include "llviewertexture.h" -#include "llgl.h" -#include "lluicolor.h" class LLViewerRegion; class LLVector3; class LLColor4U; +class LLUIColor; class LLVector2; class LLViewerParcelOverlay : public LLGLUpdate @@ -65,19 +64,18 @@ public: bool isSoundLocal(const LLVector3& pos) const; - bool isBuildCameraAllowed(const LLVector3& pos) const; F32 getOwnedRatio() const; // Returns the number of vertices drawn void renderPropertyLines(); void renderPropertyLinesOnMinimap(F32 scale_pixels_per_meter, const F32* parcel_outline_color); - U8 ownership( const LLVector3& pos) const; - U8 parcelLineFlags( const LLVector3& pos) const; + U8 ownership(const LLVector3& pos) const; + U8 parcelLineFlags(const LLVector3& pos) const; U8 parcelLineFlags(S32 row, S32 col) const; // MANIPULATE - void uncompressLandOverlay(S32 chunk, U8 *compressed_overlay); + void uncompressLandOverlay(S32 chunk, U8* compressed_overlay); // Indicate property lines and overlay texture need to be rebuilt. void setDirty(); @@ -88,8 +86,7 @@ public: private: // This is in parcel rows and columns, not grid rows and columns // Stored in bottom three bits. - U8 ownership(S32 row, S32 col) const - { return parcelFlags(row, col, (U8)0x7); } + U8 ownership(S32 row, S32 col) const { return parcelFlags(row, col, (U8)0x7); } U8 parcelFlags(S32 row, S32 col, U8 flags) const; @@ -119,7 +116,10 @@ private: struct Edge { - std::vector<LLVector3> vertices; + void pushVertex(U32 lod, F32 x, F32 y, F32 z, F32 water_z); + // LOD: 0 - detailized, 1 - simplified + std::vector<LLVector4a> verticesAboveWater[2]; + std::vector<LLVector4a> verticesUnderWater[2]; LLColor4U color; }; diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index d59dfb88a8..03d5563c65 100755 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -3236,6 +3236,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames) capabilityNames.append("FetchInventory2"); capabilityNames.append("FetchInventoryDescendents2"); capabilityNames.append("IncrementCOFVersion"); + capabilityNames.append("RequestTaskInventory"); AISAPI::getCapNames(capabilityNames); capabilityNames.append("InterestList"); diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index 73aabf49d1..ff86684499 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -66,6 +66,7 @@ #include "llinventorymodel.h" #include "lluiusage.h" #include "lltranslate.h" +#include "llluamanager.h" // "Minimal Vulkan" to get max API Version @@ -620,6 +621,10 @@ void send_viewer_stats(bool include_preferences) system["shader_level"] = shader_level; + LLSD &scripts = body["scripts"]; + scripts["lua_scripts"] = LLLUAmanager::sScriptCount; + scripts["lua_auto_scripts"] = LLLUAmanager::sAutorunScriptCount; + LLSD &download = body["downloads"]; download["world_kbytes"] = F64Kilobytes(gTotalWorldData).value(); @@ -774,6 +779,8 @@ void send_viewer_stats(bool include_preferences) LL_INFOS("LogViewerStatsPacket") << "Sending viewer statistics: " << body << LL_ENDL; + + // <ND> Do those lines even do anything sane in regard of debug logging? LL_DEBUGS("LogViewerStatsPacket"); std::string filename("viewer_stats_packet.xml"); llofstream of(filename.c_str()); diff --git a/indra/newview/llviewerstatsrecorder.cpp b/indra/newview/llviewerstatsrecorder.cpp index 58065ecce5..3e0e3b28f2 100644 --- a/indra/newview/llviewerstatsrecorder.cpp +++ b/indra/newview/llviewerstatsrecorder.cpp @@ -309,5 +309,3 @@ F32 LLViewerStatsRecorder::getTimeSinceStart() { return (F32) (LLFrameTimer::getTotalSeconds() - mFileOpenTime); } - - diff --git a/indra/newview/llviewertexteditor.cpp b/indra/newview/llviewertexteditor.cpp index 5793a28b80..210cd62d6f 100644 --- a/indra/newview/llviewertexteditor.cpp +++ b/indra/newview/llviewertexteditor.cpp @@ -179,6 +179,16 @@ public: mToolTip = inv_item->getName() + '\n' + inv_item->getDescription(); } + /*virtual*/ LLTextSegmentPtr clone(LLTextBase& target) const + { + LLTextEditor* editor = dynamic_cast<LLTextEditor*>(&target); + llassert(editor); + if (!editor) + return nullptr; + + return new LLEmbeddedItemSegment(mStart, mImage, mItem, *editor); + } + /*virtual*/ bool getDimensionsF32(S32 first_char, S32 num_chars, F32& width, S32& height) const { if (num_chars == 0) diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 08e9151940..7b89ae4e44 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -964,7 +964,7 @@ void LLViewerTextureList::updateImageDecodePriority(LLViewerFetchedTexture* imag // this is an alternative to decaying mMaxVirtualSize over time // that keeps textures from continously downrezzing and uprezzing in the background - if (LLViewerTexture::sDesiredDiscardBias > 1.5f || + if (LLViewerTexture::sDesiredDiscardBias > 1.5f || (!on_screen && LLViewerTexture::sDesiredDiscardBias > 1.f)) { imagep->mMaxVirtualSize = 0.f; diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 0e7d82fd90..c747319940 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -195,7 +195,6 @@ #include "llviewerjoystick.h" #include "llviewermenufile.h" // LLFilePickerReplyThread #include "llviewernetwork.h" -#include "llpostprocess.h" #include "llfloaterimnearbychat.h" #include "llagentui.h" #include "llwearablelist.h" @@ -3776,19 +3775,21 @@ void LLViewerWindow::updateUI() void LLViewerWindow::updateLayout() { - LLTool* tool = LLToolMgr::getInstance()->getCurrentTool(); + LLToolMgr* tool_mgr = LLToolMgr::getInstance(); + LLTool* tool = tool_mgr->getCurrentTool(); + LLCachedControl<bool> freeze_time(gSavedSettings, "FreezeTime"); if (gFloaterTools != NULL && tool != NULL && tool != gToolNull && tool != LLToolCompInspect::getInstance() && tool != LLToolDragAndDrop::getInstance() - && !gSavedSettings.getBOOL("FreezeTime")) + && !freeze_time()) { // Suppress the toolbox view if our source tool was the pie tool, // and we've overridden to something else. bool suppress_toolbox = - (LLToolMgr::getInstance()->getBaseTool() == LLToolPie::getInstance()) && - (LLToolMgr::getInstance()->getCurrentTool() != LLToolPie::getInstance()); + (tool_mgr->getBaseTool() == LLToolPie::getInstance()) && + (tool_mgr->getCurrentTool() != LLToolPie::getInstance()); LLMouseHandler *captor = gFocusMgr.getMouseCapture(); // With the null, inspect, or drag and drop tool, don't muck @@ -3798,7 +3799,7 @@ void LLViewerWindow::updateLayout() || (tool != LLToolPie::getInstance() // not default tool && tool != LLToolCompGun::getInstance() // not coming out of mouselook && !suppress_toolbox // not override in third person - && LLToolMgr::getInstance()->getCurrentToolset()->isShowFloaterTools() + && tool_mgr->getCurrentToolset()->isShowFloaterTools() && (!captor || dynamic_cast<LLView*>(captor) != NULL))) // not dragging { // Force floater tools to be visible (unless minimized) @@ -4255,15 +4256,15 @@ void LLViewerWindow::pickAsync( S32 x, bool pick_unselectable, bool pick_reflection_probes) { + static LLCachedControl<bool> select_invisible_objects(gSavedSettings, "SelectInvisibleObjects"); // "Show Debug Alpha" means no object actually transparent bool in_build_mode = LLFloaterReg::instanceVisible("build"); - if (LLDrawPoolAlpha::sShowDebugAlpha - || (in_build_mode && gSavedSettings.getBOOL("SelectInvisibleObjects"))) + if (LLDrawPoolAlpha::sShowDebugAlpha || (in_build_mode && select_invisible_objects)) { pick_transparent = true; } - LLPickInfo pick_info(LLCoordGL(x, y_from_bot), mask, pick_transparent, pick_rigged, false, pick_reflection_probes, pick_unselectable, true, callback); + LLPickInfo pick_info(LLCoordGL(x, y_from_bot), mask, pick_transparent, pick_rigged, false, pick_reflection_probes, true, pick_unselectable, callback); schedulePick(pick_info); } @@ -4287,7 +4288,6 @@ void LLViewerWindow::schedulePick(LLPickInfo& pick_info) mWindow->delayInputProcessing(); } - void LLViewerWindow::performPick() { if (!mPicks.empty()) @@ -4321,8 +4321,9 @@ void LLViewerWindow::returnEmptyPicks() // Performs the GL object/land pick. LLPickInfo LLViewerWindow::pickImmediate(S32 x, S32 y_from_bot, bool pick_transparent, bool pick_rigged, bool pick_particle, bool pick_unselectable, bool pick_reflection_probe) { + static LLCachedControl<bool> select_invisible_objects(gSavedSettings, "SelectInvisibleObjects"); bool in_build_mode = LLFloaterReg::instanceVisible("build"); - if ((in_build_mode && gSavedSettings.getBOOL("SelectInvisibleObjects")) || LLDrawPoolAlpha::sShowDebugAlpha) + if ((in_build_mode && select_invisible_objects) || LLDrawPoolAlpha::sShowDebugAlpha) { // build mode allows interaction with all transparent objects // "Show Debug Alpha" means no object actually transparent @@ -4330,7 +4331,7 @@ LLPickInfo LLViewerWindow::pickImmediate(S32 x, S32 y_from_bot, bool pick_transp } // shortcut queueing in mPicks and just update mLastPick in place - MASK key_mask = gKeyboard->currentMask(true); + MASK key_mask = gKeyboard->currentMask(true); mLastPick = LLPickInfo(LLCoordGL(x, y_from_bot), key_mask, pick_transparent, pick_rigged, pick_particle, pick_reflection_probe, true, false, NULL); mLastPick.fetchResults(); @@ -4851,7 +4852,7 @@ bool LLViewerWindow::saveSnapshot(const std::string& filepath, S32 image_width, LL_INFOS() << "Saving snapshot to: " << filepath << LL_ENDL; LLPointer<LLImageRaw> raw = new LLImageRaw; - bool success = rawSnapshot(raw, image_width, image_height, true, false, show_ui, show_hud, do_rebuild); + bool success = rawSnapshot(raw, image_width, image_height, true, false, show_ui, show_hud, do_rebuild, 0, type); if (success) { @@ -5760,11 +5761,6 @@ void LLViewerWindow::stopGL() gBox.cleanupGL(); - if(gPostProcess) - { - gPostProcess->invalidate(); - } - gTextureList.destroyGL(); stop_glerror(); @@ -6050,14 +6046,14 @@ LLPickInfo::LLPickInfo(const LLCoordGL& mouse_pos, bool pick_rigged, bool pick_particle, bool pick_reflection_probe, - bool pick_uv_coords, + bool pick_surface_info, bool pick_unselectable, void (*pick_callback)(const LLPickInfo& pick_info)) : mMousePt(mouse_pos), mKeyMask(keyboard_mask), mPickCallback(pick_callback), mPickType(PICK_INVALID), - mWantSurfaceInfo(pick_uv_coords), + mWantSurfaceInfo(pick_surface_info), mObjectFace(-1), mUVCoords(-1.f, -1.f), mSTCoords(-1.f, -1.f), diff --git a/indra/newview/llviewerwindowlistener.cpp b/indra/newview/llviewerwindowlistener.cpp index da7e18af5c..52f413792a 100644 --- a/indra/newview/llviewerwindowlistener.cpp +++ b/indra/newview/llviewerwindowlistener.cpp @@ -43,22 +43,13 @@ LLViewerWindowListener::LLViewerWindowListener(LLViewerWindow* llviewerwindow): mViewerWindow(llviewerwindow) { // add() every method we want to be able to invoke via this event API. - LLSD saveSnapshotArgs; - saveSnapshotArgs["filename"] = LLSD::String(); - saveSnapshotArgs["reply"] = LLSD::String(); - // The following are optional, so don't build them into required prototype. -// saveSnapshotArgs["width"] = LLSD::Integer(); -// saveSnapshotArgs["height"] = LLSD::Integer(); -// saveSnapshotArgs["showui"] = LLSD::Boolean(); -// saveSnapshotArgs["showhud"] = LLSD::Boolean(); -// saveSnapshotArgs["rebuild"] = LLSD::Boolean(); -// saveSnapshotArgs["type"] = LLSD::String(); add("saveSnapshot", - "Save screenshot: [\"filename\"], [\"width\"], [\"height\"], [\"showui\"], [\"showhud\"], [\"rebuild\"], [\"type\"]\n" + "Save screenshot: [\"filename\"] (extension may be specified: bmp, jpeg, png)\n" + "[\"width\"], [\"height\"], [\"showui\"], [\"showhud\"], [\"rebuild\"], [\"type\"]\n" "type: \"COLOR\", \"DEPTH\"\n" - "Post on [\"reply\"] an event containing [\"ok\"]", + "Post on [\"reply\"] an event containing [\"result\"]", &LLViewerWindowListener::saveSnapshot, - saveSnapshotArgs); + llsd::map("filename", LLSD::String(), "reply", LLSD())); add("requestReshape", "Resize the window: [\"w\"], [\"h\"]", &LLViewerWindowListener::requestReshape); @@ -66,12 +57,15 @@ LLViewerWindowListener::LLViewerWindowListener(LLViewerWindow* llviewerwindow): void LLViewerWindowListener::saveSnapshot(const LLSD& event) const { + Response response(LLSD(), event); + typedef std::map<LLSD::String, LLSnapshotModel::ESnapshotLayerType> TypeMap; TypeMap types; #define tp(name) types[#name] = LLSnapshotModel::SNAPSHOT_TYPE_##name tp(COLOR); tp(DEPTH); -#undef tp +#undef tp + // Our add() call should ensure that the incoming LLSD does in fact // contain our required arguments. Deal with the optional ones. S32 width (mViewerWindow->getWindowWidthRaw()); @@ -94,14 +88,36 @@ void LLViewerWindowListener::saveSnapshot(const LLSD& event) const TypeMap::const_iterator found = types.find(event["type"]); if (found == types.end()) { - LL_ERRS("LLViewerWindowListener") << "LLViewerWindowListener::saveSnapshot(): " - << "unrecognized type " << event["type"] << LL_ENDL; - return; + return response.error(stringize("Unrecognized type ", std::quoted(event["type"].asString()), " [\"COLOR\"] or [\"DEPTH\"] is expected.")); } type = found->second; } - bool ok = mViewerWindow->saveSnapshot(event["filename"], width, height, showui, showhud, rebuild, type); - sendReply(LLSDMap("ok", ok), event); + + std::string filename(event["filename"]); + if (filename.empty()) + { + return response.error(stringize("File path is empty.")); + } + + LLSnapshotModel::ESnapshotFormat format(LLSnapshotModel::SNAPSHOT_FORMAT_BMP); + std::string ext = gDirUtilp->getExtension(filename); + if (ext.empty()) + { + filename.append(".bmp"); + } + else if (ext == "png") + { + format = LLSnapshotModel::SNAPSHOT_FORMAT_PNG; + } + else if (ext == "jpeg" || ext == "jpg") + { + format = LLSnapshotModel::SNAPSHOT_FORMAT_JPEG; + } + else if (ext != "bmp") + { + return response.error(stringize("Unrecognized format. [\"png\"], [\"jpeg\"] or [\"bmp\"] is expected.")); + } + response["result"] = mViewerWindow->saveSnapshot(filename, width, height, showui, showhud, rebuild, type, format); } void LLViewerWindowListener::requestReshape(LLSD const & event_data) const diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index ef0bb3b926..8178dade8b 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -113,6 +113,7 @@ #include "llskinningutil.h" #include "llperfstats.h" +#include "lleventapi.h" #include <boost/lexical_cast.hpp> @@ -2406,8 +2407,6 @@ void LLVOAvatar::updateMeshData() { if (mDrawable.notNull()) { - stop_glerror(); - S32 f_num = 0 ; const U32 VERTEX_NUMBER_THRESHOLD = 128 ;//small number of this means each part of an avatar has its own vertex buffer. const auto num_parts = mMeshLOD.size(); @@ -2534,7 +2533,6 @@ void LLVOAvatar::updateMeshData() } } - stop_glerror(); buff->unmapBuffer(); if(!f_num) @@ -5427,14 +5425,6 @@ U32 LLVOAvatar::renderImpostor(LLColor4U color, S32 diffuse_channel) gGL.setSceneBlendType(LLRender::BT_ADD); gGL.getTexUnit(diffuse_channel)->unbind(LLTexUnit::TT_TEXTURE); - // gGL.begin(LLRender::QUADS); - // gGL.vertex3fv((pos+left-up).mV); - // gGL.vertex3fv((pos-left-up).mV); - // gGL.vertex3fv((pos-left+up).mV); - // gGL.vertex3fv((pos+left+up).mV); - // gGL.end(); - - gGL.begin(LLRender::LINES); gGL.color4f(1.f,1.f,1.f,1.f); F32 thickness = llmax(F32(5.0f-5.0f*(gFrameTimeSeconds-mLastImpostorUpdateFrameTime)),1.0f); @@ -5455,15 +5445,22 @@ U32 LLVOAvatar::renderImpostor(LLColor4U color, S32 diffuse_channel) gGL.color4ubv(color.mV); gGL.getTexUnit(diffuse_channel)->bind(&mImpostor); - gGL.begin(LLRender::QUADS); - gGL.texCoord2f(0,0); - gGL.vertex3fv((pos+left-up).mV); - gGL.texCoord2f(1,0); - gGL.vertex3fv((pos-left-up).mV); - gGL.texCoord2f(1,1); - gGL.vertex3fv((pos-left+up).mV); - gGL.texCoord2f(0,1); - gGL.vertex3fv((pos+left+up).mV); + gGL.begin(LLRender::TRIANGLES); + { + gGL.texCoord2f(0.f, 0.f); + gGL.vertex3fv((pos + left - up).mV); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex3fv((pos - left - up).mV); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex3fv((pos - left + up).mV); + + gGL.texCoord2f(0.f, 0.f); + gGL.vertex3fv((pos + left - up).mV); + gGL.texCoord2f(1.f, 1.f); + gGL.vertex3fv((pos - left + up).mV); + gGL.texCoord2f(0.f, 1.f); + gGL.vertex3fv((pos + left + up).mV); + } gGL.end(); gGL.flush(); } @@ -10754,9 +10751,8 @@ void LLVOAvatar::updateRiggingInfo() getAssociatedVolumes(volumes); { - LL_PROFILE_ZONE_NAMED_CATEGORY_AVATAR("update rig info - get key") - HBXXH128 hash; - + LL_PROFILE_ZONE_NAMED_CATEGORY_AVATAR("update rig info - get key"); + size_t hash = 0; // Get current rigging info key for (LLVOVolume* vol : volumes) { @@ -10765,22 +10761,20 @@ void LLVOAvatar::updateRiggingInfo() const LLUUID& mesh_id = vol->getVolume()->getParams().getSculptID(); S32 max_lod = llmax(vol->getLOD(), vol->mLastRiggingInfoLOD); - hash.update(mesh_id.mData, sizeof(mesh_id.mData)); - hash.update(&max_lod, sizeof(max_lod)); + boost::hash_combine(hash, mesh_id); + boost::hash_combine(hash, max_lod); } } - LLUUID curr_rigging_info_key = hash.digest(); - // Check for key change, which indicates some change in volume composition or LOD. - if (curr_rigging_info_key == mLastRiggingInfoKey) + if (hash == mLastRiggingInfoKey) { return; } // Something changed. Update. - mLastRiggingInfoKey = curr_rigging_info_key; + mLastRiggingInfoKey = hash; } mJointRiggingInfoTab.clear(); @@ -11824,3 +11818,33 @@ bool LLVOAvatar::isBuddy() const return is_friend; } +class LLVOAvatarListener : public LLEventAPI +{ + public: + LLVOAvatarListener() : LLEventAPI("LLVOAvatar", "LLVOAvatar listener to retrieve avatar info") + { + add("getSpeed", + "Return the avatar movement speed in the XY plane", + &LLVOAvatarListener::getSpeed, + LLSD().with("reply", LLSD())); + add("isInAir", + "Return the info whether avatar is in the air, and if so the time in the air", + &LLVOAvatarListener::isInAir, + LLSD().with("reply", LLSD())); + } + + private: + void getSpeed(const LLSD &request) const + { + LLVector3 avatar_velocity = gAgentAvatarp->getCharacterVelocity() * gAgentAvatarp->getTimeDilation(); + avatar_velocity.mV[VZ] = 0.f; + Response response(llsd::map("value", avatar_velocity.magVec()), request); + } + void isInAir(const LLSD &request) const + { + Response response(llsd::map("value", gAgentAvatarp->mInAir, + "duration", gAgentAvatarp->mInAir ? gAgentAvatarp->mTimeInAir.getElapsedTimeF32() : 0), request); + } +}; + +static LLVOAvatarListener VOAvatarListener; diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index ff1cbc34fc..dd1725c322 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -226,7 +226,7 @@ public: // virtual void updateRiggingInfo(); // This encodes mesh id and LOD, so we can see whether display is up-to-date. - LLUUID mLastRiggingInfoKey; + size_t mLastRiggingInfoKey; std::set<LLUUID> mActiveOverrideMeshes; virtual void onActiveOverrideMeshesChanged(); diff --git a/indra/newview/llvograss.cpp b/indra/newview/llvograss.cpp index 67adcbb244..fdd39a0e30 100644 --- a/indra/newview/llvograss.cpp +++ b/indra/newview/llvograss.cpp @@ -729,7 +729,6 @@ void LLGrassPartition::getGeometry(LLSpatialGroup* group) } } - buffer->unmapBuffer(); mFaceList.clear(); } diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index 1e934ade59..4659fa5f71 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -1014,6 +1014,7 @@ bool LLVivoxVoiceClient::startAndLaunchDaemon() std::string old_log = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SLVoice.old"); if (gDirUtilp->fileExists(new_log)) { + LLFile::remove(old_log, ENOENT); LLFile::rename(new_log, old_log); } diff --git a/indra/newview/llvoicewebrtc.cpp b/indra/newview/llvoicewebrtc.cpp index 3d684e5a1b..7161f5af5e 100644 --- a/indra/newview/llvoicewebrtc.cpp +++ b/indra/newview/llvoicewebrtc.cpp @@ -87,6 +87,8 @@ namespace { const F32 SPEAKING_AUDIO_LEVEL = 0.30; + const uint32_t PEER_GAIN_CONVERSION_FACTOR = 220; + static const std::string REPORTED_VOICE_SERVER_TYPE = "Secondlife WebRTC Gateway"; // Don't send positional updates more frequently than this: @@ -2170,7 +2172,7 @@ LLVoiceWebRTCConnection::LLVoiceWebRTCConnection(const LLUUID ®ionID, const s // retries wait a short period...randomize it so // all clients don't try to reconnect at once. - mRetryWaitSecs = (F32)((F32) rand() / (RAND_MAX)) + 0.5f; + mRetryWaitSecs = static_cast<F32>(rand()) / static_cast<F32>(RAND_MAX) + 0.5f; mWebRTCPeerConnectionInterface = llwebrtc::newPeerConnection(); mWebRTCPeerConnectionInterface->setSignalingObserver(this); @@ -2443,7 +2445,7 @@ void LLVoiceWebRTCConnection::setSpeakerVolume(F32 volume) void LLVoiceWebRTCConnection::setUserVolume(const LLUUID& id, F32 volume) { - boost::json::object root = {{"ug", {id.asString(), (uint32_t) (volume * 200)}}}; + boost::json::object root = { { "ug", { { id.asString(), (uint32_t)(volume * PEER_GAIN_CONVERSION_FACTOR) } } } }; std::string json_data = boost::json::serialize(root); if (mWebRTCDataInterface) { @@ -2453,7 +2455,7 @@ void LLVoiceWebRTCConnection::setUserVolume(const LLUUID& id, F32 volume) void LLVoiceWebRTCConnection::setUserMute(const LLUUID& id, bool mute) { - boost::json::object root = {{"m", {id.asString(), mute}}}; + boost::json::object root = { { "m", { { id.asString(), mute } } } }; std::string json_data = boost::json::serialize(root); if (mWebRTCDataInterface) { @@ -2766,7 +2768,7 @@ bool LLVoiceWebRTCConnection::connectionStateMachine() case VOICE_STATE_SESSION_UP: { mRetryWaitPeriod = 0; - mRetryWaitSecs = (F32)((F32)rand() / (RAND_MAX)) + 0.5f; + mRetryWaitSecs = (F32)((F32)rand() / (F32)RAND_MAX) + 0.5f; // we'll stay here as long as the session remains up. if (mShutDown) @@ -2800,7 +2802,7 @@ bool LLVoiceWebRTCConnection::connectionStateMachine() { // back off the retry period, and do it by a small random // bit so all clients don't reconnect at once. - mRetryWaitSecs += (F32)((F32) rand() / (RAND_MAX)) + 0.5f; + mRetryWaitSecs += static_cast<F32>(rand()) / static_cast<F32>(RAND_MAX) + 0.5f; mRetryWaitPeriod = 0; } } diff --git a/indra/newview/llvopartgroup.cpp b/indra/newview/llvopartgroup.cpp index 8f792c1042..ec32a79829 100644 --- a/indra/newview/llvopartgroup.cpp +++ b/indra/newview/llvopartgroup.cpp @@ -54,63 +54,6 @@ void LLVOPartGroup::initClass() void LLVOPartGroup::restoreGL() { - //TODO: optimize out binormal mask here. Specular and normal coords as well. -#if 0 - sVB = new LLVertexBuffer(VERTEX_DATA_MASK | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2); - U32 count = LL_MAX_PARTICLE_COUNT; - if (!sVB->allocateBuffer(count*4, count*6)) - { - LL_WARNS() << "Failed to allocate Vertex Buffer to " - << count*4 << " vertices and " - << count * 6 << " indices" << LL_ENDL; - // we are likelly to crash at following getTexCoord0Strider(), so unref and return - sVB = NULL; - return; - } - - //indices and texcoords are always the same, set once - LLStrider<U16> indicesp; - - LLStrider<LLVector4a> verticesp; - - sVB->getIndexStrider(indicesp); - sVB->getVertexStrider(verticesp); - - LLVector4a v; - v.set(0,0,0,0); - - - U16 vert_offset = 0; - - for (U32 i = 0; i < LL_MAX_PARTICLE_COUNT; i++) - { - *indicesp++ = vert_offset + 0; - *indicesp++ = vert_offset + 1; - *indicesp++ = vert_offset + 2; - - *indicesp++ = vert_offset + 1; - *indicesp++ = vert_offset + 3; - *indicesp++ = vert_offset + 2; - - *verticesp++ = v; - - vert_offset += 4; - } - - LLStrider<LLVector2> texcoordsp; - sVB->getTexCoord0Strider(texcoordsp); - - for (U32 i = 0; i < LL_MAX_PARTICLE_COUNT; i++) - { - *texcoordsp++ = LLVector2(0.f, 1.f); - *texcoordsp++ = LLVector2(0.f, 0.f); - *texcoordsp++ = LLVector2(1.f, 1.f); - *texcoordsp++ = LLVector2(1.f, 0.f); - } - - sVB->unmapBuffer(); -#endif - } //static @@ -955,7 +898,6 @@ void LLParticlePartition::getGeometry(LLSpatialGroup* group) } } - buffer->unmapBuffer(); mFaceList.clear(); } diff --git a/indra/newview/llvosurfacepatch.cpp b/indra/newview/llvosurfacepatch.cpp index fdccf34e6a..294d36b0a9 100644 --- a/indra/newview/llvosurfacepatch.cpp +++ b/indra/newview/llvosurfacepatch.cpp @@ -1078,7 +1078,6 @@ void LLTerrainPartition::getGeometry(LLSpatialGroup* group) gen_terrain_tangents(index_offset, indices_index, vertices, normals, tangents, indices, region_width); } - buffer->unmapBuffer(); mFaceList.clear(); } diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 2859f8b1c2..31f1bbcc21 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -250,10 +250,12 @@ LLVOVolume::~LLVOVolume() delete mVolumeImpl; mVolumeImpl = NULL; - gMeshRepo.unregisterMesh(this); + unregisterOldMeshAndSkin(); if(!mMediaImplList.empty()) { + LL_PROFILE_ZONE_NAMED_CATEGORY_MEDIA("delete volume media list"); + for(U32 i = 0 ; i < mMediaImplList.size() ; i++) { if(mMediaImplList[i].notNull()) @@ -998,6 +1000,28 @@ LLDrawable *LLVOVolume::createDrawable(LLPipeline *pipeline) return mDrawable; } +// Inverse of gMeshRepo.loadMesh and gMeshRepo.getSkinInfo, combined into one function +// Assume a Collada mesh never changes after being set. +void LLVOVolume::unregisterOldMeshAndSkin() +{ + if (mVolumep) + { + const LLVolumeParams& params = mVolumep->getParams(); + if ((params.getSculptType() & LL_SCULPT_TYPE_MASK) == LL_SCULPT_TYPE_MESH) + { + // object is being deleted, so it will no longer need to request + // meshes. + for (S32 lod = 0; lod != LLVolumeLODGroup::NUM_LODS; ++lod) + { + gMeshRepo.unregisterMesh(this, params, lod); + } + // This volume may or may not have a skin + gMeshRepo.unregisterSkinInfo(params.getSculptID(), this); + } + } +} + + bool LLVOVolume::setVolume(const LLVolumeParams ¶ms_in, const S32 detail, bool unique_volume) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; @@ -5779,8 +5803,7 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) { type = LLDrawPool::POOL_GLTF_PBR; } - else - if (type != LLDrawPool::POOL_ALPHA && force_simple) + else if (type != LLDrawPool::POOL_ALPHA && force_simple) { type = LLDrawPool::POOL_SIMPLE; } diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h index 97a5131260..10dbf1349f 100644 --- a/indra/newview/llvovolume.h +++ b/indra/newview/llvovolume.h @@ -227,6 +227,7 @@ public: void setTexture(const S32 face); S32 getIndexInTex(U32 ch) const {return mIndexInTex[ch];} + void unregisterOldMeshAndSkin(); /*virtual*/ bool setVolume(const LLVolumeParams &volume_params, const S32 detail, bool unique_volume = false) override; void updateSculptTexture(); void setIndexInTex(U32 ch, S32 index) { mIndexInTex[ch] = index ;} diff --git a/indra/newview/llwatchdog.cpp b/indra/newview/llwatchdog.cpp index 0a1d346266..bf171fe954 100644 --- a/indra/newview/llwatchdog.cpp +++ b/indra/newview/llwatchdog.cpp @@ -29,7 +29,7 @@ #include "llwatchdog.h" #include "llthread.h" -const U32 WATCHDOG_SLEEP_TIME_USEC = 1000000; +constexpr U32 WATCHDOG_SLEEP_TIME_USEC = 1000000U; // This class runs the watchdog timing thread. class LLWatchdogTimerThread : public LLThread @@ -51,7 +51,7 @@ public: mSleepMsecs = 1; } - /* virtual */ void run() + void run() override { while(!mStopping) { @@ -83,7 +83,7 @@ void LLWatchdogEntry::start() void LLWatchdogEntry::stop() { // this can happen very late in the shutdown sequence - if (! LLWatchdog::wasDeleted()) + if (!LLWatchdog::wasDeleted()) { LLWatchdog::getInstance()->remove(this); } @@ -117,7 +117,7 @@ void LLWatchdogTimeout::setTimeout(F32 d) mTimeout = d; } -void LLWatchdogTimeout::start(const std::string& state) +void LLWatchdogTimeout::start(std::string_view state) { if (mTimeout == 0) { @@ -139,9 +139,9 @@ void LLWatchdogTimeout::stop() mTimer.stop(); } -void LLWatchdogTimeout::ping(const std::string& state) +void LLWatchdogTimeout::ping(std::string_view state) { - if(!state.empty()) + if (!state.empty()) { mPingState = state; } @@ -151,7 +151,7 @@ void LLWatchdogTimeout::ping(const std::string& state) // LLWatchdog LLWatchdog::LLWatchdog() :mSuspectsAccessMutex() - ,mTimer(NULL) + ,mTimer(nullptr) ,mLastClockCount(0) { } @@ -176,7 +176,7 @@ void LLWatchdog::remove(LLWatchdogEntry* e) void LLWatchdog::init() { - if(!mSuspectsAccessMutex && !mTimer) + if (!mSuspectsAccessMutex && !mTimer) { mSuspectsAccessMutex = new LLMutex(); mTimer = new LLWatchdogTimerThread(); @@ -191,17 +191,17 @@ void LLWatchdog::init() void LLWatchdog::cleanup() { - if(mTimer) + if (mTimer) { mTimer->stop(); delete mTimer; - mTimer = NULL; + mTimer = nullptr; } - if(mSuspectsAccessMutex) + if (mSuspectsAccessMutex) { delete mSuspectsAccessMutex; - mSuspectsAccessMutex = NULL; + mSuspectsAccessMutex = nullptr; } mLastClockCount = 0; @@ -214,12 +214,12 @@ void LLWatchdog::run() // Check the time since the last call to run... // If the time elapsed is two times greater than the regualr sleep time // reset the active timeouts. - const U32 TIME_ELAPSED_MULTIPLIER = 2; + constexpr U32 TIME_ELAPSED_MULTIPLIER = 2; U64 current_time = LLTimer::getTotalTime(); U64 current_run_delta = current_time - mLastClockCount; mLastClockCount = current_time; - if(current_run_delta > (WATCHDOG_SLEEP_TIME_USEC * TIME_ELAPSED_MULTIPLIER)) + if (current_run_delta > (WATCHDOG_SLEEP_TIME_USEC * TIME_ELAPSED_MULTIPLIER)) { LL_INFOS() << "Watchdog thread delayed: resetting entries." << LL_ENDL; for (const auto& suspect : mSuspects) @@ -233,7 +233,7 @@ void LLWatchdog::run() std::find_if(mSuspects.begin(), mSuspects.end(), [](const LLWatchdogEntry* suspect){ return ! suspect->isAlive(); }); - if(result != mSuspects.end()) + if (result != mSuspects.end()) { // error!!! if(mTimer) @@ -251,7 +251,7 @@ void LLWatchdog::run() void LLWatchdog::lockThread() { - if(mSuspectsAccessMutex != NULL) + if (mSuspectsAccessMutex) { mSuspectsAccessMutex->lock(); } @@ -259,7 +259,7 @@ void LLWatchdog::lockThread() void LLWatchdog::unlockThread() { - if(mSuspectsAccessMutex != NULL) + if (mSuspectsAccessMutex) { mSuspectsAccessMutex->unlock(); } diff --git a/indra/newview/llwatchdog.h b/indra/newview/llwatchdog.h index fe8932e298..1931c582b0 100644 --- a/indra/newview/llwatchdog.h +++ b/indra/newview/llwatchdog.h @@ -56,14 +56,14 @@ public: LLWatchdogTimeout(); virtual ~LLWatchdogTimeout(); - /* virtual */ bool isAlive() const; - /* virtual */ void reset(); - /* virtual */ void start() { start(""); } - /* virtual */ void stop(); + bool isAlive() const override; + void reset() override; + void start() override { start(""); } + void stop() override; - void start(const std::string& state); + void start(std::string_view state); void setTimeout(F32 d); - void ping(const std::string& state); + void ping(std::string_view state); const std::string& getState() {return mPingState; } private: diff --git a/indra/newview/llwearableitemslist.cpp b/indra/newview/llwearableitemslist.cpp index 8ce1a745c3..217d46d1d2 100644 --- a/indra/newview/llwearableitemslist.cpp +++ b/indra/newview/llwearableitemslist.cpp @@ -33,7 +33,6 @@ #include "llagentwearables.h" #include "llappearancemgr.h" -#include "llinventoryfunctions.h" #include "llinventoryicon.h" #include "llgesturemgr.h" #include "lltransutil.h" @@ -41,14 +40,6 @@ #include "llviewermenu.h" #include "llvoavatarself.h" -class LLFindOutfitItems : public LLInventoryCollectFunctor -{ -public: - LLFindOutfitItems() {} - virtual ~LLFindOutfitItems() {} - virtual bool operator()(LLInventoryCategory* cat, - LLInventoryItem* item); -}; bool LLFindOutfitItems::operator()(LLInventoryCategory* cat, LLInventoryItem* item) @@ -894,7 +885,7 @@ void LLWearableItemsList::ContextMenu::show(LLView* spawning_view, LLWearableTyp } LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; - registrar.add("Wearable.CreateNew", boost::bind(createNewWearableByType, w_type)); + registrar.add("Wearable.CreateNew", { boost::bind(createNewWearableByType, w_type), cb_info::UNTRUSTED_THROTTLE }); menup = createFromFile("menu_wearable_list_item.xml"); if (!menup) { @@ -920,7 +911,7 @@ void LLWearableItemsList::ContextMenu::show(LLView* spawning_view, LLWearableTyp // virtual LLContextMenu* LLWearableItemsList::ContextMenu::createMenu() { - LLUICtrl::CommitCallbackRegistry::ScopedRegistrar registrar; + LLUICtrl::ScopedRegistrarHelper registrar; const uuid_vec_t& ids = mUUIDs; // selected items IDs LLUUID selected_id = ids.front(); // ID of the first selected item diff --git a/indra/newview/llwearableitemslist.h b/indra/newview/llwearableitemslist.h index 3fe1059176..1a22d6b366 100644 --- a/indra/newview/llwearableitemslist.h +++ b/indra/newview/llwearableitemslist.h @@ -32,6 +32,7 @@ #include "llsingleton.h" // newview +#include "llinventoryfunctions.h" #include "llinventoryitemslist.h" #include "llinventorylistitem.h" #include "lllistcontextmenu.h" @@ -507,4 +508,11 @@ protected: LLWearableType::EType mMenuWearableType; }; +struct LLFindOutfitItems : public LLInventoryCollectFunctor +{ + LLFindOutfitItems() {} + virtual ~LLFindOutfitItems() {} + virtual bool operator()(LLInventoryCategory *cat, LLInventoryItem *item); +}; + #endif //LL_LLWEARABLEITEMSLIST_H diff --git a/indra/newview/llwindowlistener.cpp b/indra/newview/llwindowlistener.cpp index ebcdd537a5..601abdf5cf 100644 --- a/indra/newview/llwindowlistener.cpp +++ b/indra/newview/llwindowlistener.cpp @@ -54,7 +54,7 @@ LLWindowListener::LLWindowListener(LLViewerWindow *window, const KeyboardGetter& "Given [\"keysym\"], [\"keycode\"] or [\"char\"], inject the specified "; std::string keyExplain = "(integer keycode values, or keysym string from any addKeyName() call in\n" - "http://bitbucket.org/lindenlab/viewer-release/src/tip/indra/llwindow/llkeyboard.cpp )\n"; + "https://github.com/secondlife/viewer/blob/main/indra/llwindow/llkeyboard.cpp#L68-L124)\n"; std::string mask = "Specify optional [\"mask\"] as an array containing any of \"CTL\", \"ALT\",\n" "\"SHIFT\" or \"MAC_CONTROL\"; the corresponding modifier bits will be combined\n" diff --git a/indra/newview/llworldmapview.cpp b/indra/newview/llworldmapview.cpp index ca854ac7f7..6b2bd3e6fb 100755 --- a/indra/newview/llworldmapview.cpp +++ b/indra/newview/llworldmapview.cpp @@ -462,11 +462,16 @@ void LLWorldMapView::draw() gGL.color4f(0.2f, 0.0f, 0.0f, 0.4f); gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); + { gGL.vertex2f(left, top); gGL.vertex2f(left, bottom); gGL.vertex2f(right, bottom); + + gGL.vertex2f(left, top); + gGL.vertex2f(right, bottom); gGL.vertex2f(right, top); + } gGL.end(); } else if (show_for_sale && (level <= DRAW_LANDFORSALE_THRESHOLD)) @@ -483,15 +488,22 @@ void LLWorldMapView::draw() { gGL.getTexUnit(0)->bind(overlayimage); gGL.color4f(1.f, 1.f, 1.f, 1.f); - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); + { gGL.texCoord2f(0.f, 1.f); gGL.vertex3f(left, top, -0.5f); gGL.texCoord2f(0.f, 0.f); gGL.vertex3f(left, bottom, -0.5f); gGL.texCoord2f(1.f, 0.f); gGL.vertex3f(right, bottom, -0.5f); + + gGL.texCoord2f(0.f, 1.f); + gGL.vertex3f(left, top, -0.5f); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex3f(right, bottom, -0.5f); gGL.texCoord2f(1.f, 1.f); gGL.vertex3f(right, top, -0.5f); + } gGL.end(); } } @@ -737,15 +749,22 @@ bool LLWorldMapView::drawMipmapLevel(S32 width, S32 height, S32 level, bool load gGL.color4f(1.f, 1.0f, 1.0f, 1.0f); - gGL.begin(LLRender::QUADS); + gGL.begin(LLRender::TRIANGLES); + { gGL.texCoord2f(0.f, 1.f); gGL.vertex3f(left, top, 0.f); gGL.texCoord2f(0.f, 0.f); gGL.vertex3f(left, bottom, 0.f); gGL.texCoord2f(1.f, 0.f); gGL.vertex3f(right, bottom, 0.f); + + gGL.texCoord2f(0.f, 1.f); + gGL.vertex3f(left, top, 0.f); + gGL.texCoord2f(1.f, 0.f); + gGL.vertex3f(right, bottom, 0.f); gGL.texCoord2f(1.f, 1.f); gGL.vertex3f(right, top, 0.f); + } gGL.end(); #if DEBUG_DRAW_TILE drawTileOutline(level, top, left, bottom, right); diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 13dfd48643..eb353beb11 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -125,7 +125,7 @@ #define A_GCC 1 #pragma GCC diagnostic ignored "-Wunused-function" #pragma GCC diagnostic ignored "-Wunused-variable" -#if LL_LINUX +#if LL_LINUX && defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic ignored "-Wrestrict" #endif #endif @@ -576,6 +576,7 @@ void LLPipeline::init() connectRefreshCachedSettingsSafe("RenderScreenSpaceReflectionAdaptiveStepMultiplier"); connectRefreshCachedSettingsSafe("RenderScreenSpaceReflectionGlossySamples"); connectRefreshCachedSettingsSafe("RenderBufferVisualization"); + connectRefreshCachedSettingsSafe("RenderBufferClearOnInvalidate"); connectRefreshCachedSettingsSafe("RenderMirrors"); connectRefreshCachedSettingsSafe("RenderHeroProbeUpdateRate"); connectRefreshCachedSettingsSafe("RenderHeroProbeConservativeUpdateMultiplier"); @@ -1084,6 +1085,7 @@ void LLPipeline::refreshCachedSettings() RenderScreenSpaceReflectionAdaptiveStepMultiplier = gSavedSettings.getF32("RenderScreenSpaceReflectionAdaptiveStepMultiplier"); RenderScreenSpaceReflectionGlossySamples = gSavedSettings.getS32("RenderScreenSpaceReflectionGlossySamples"); RenderBufferVisualization = gSavedSettings.getS32("RenderBufferVisualization"); + LLRenderTarget::sClearOnInvalidate = gSavedSettings.getBOOL("RenderBufferClearOnInvalidate"); RenderMirrors = gSavedSettings.getBOOL("RenderMirrors"); RenderHeroProbeUpdateRate = gSavedSettings.getS32("RenderHeroProbeUpdateRate"); RenderHeroProbeConservativeUpdateMultiplier = gSavedSettings.getS32("RenderHeroProbeConservativeUpdateMultiplier"); @@ -3731,6 +3733,7 @@ void LLPipeline::postSort(LLCamera &camera) } } + LLVertexBuffer::flushBuffers(); // LLSpatialGroup::sNoDelete = false; LL_PUSH_CALLSTACKS(); } @@ -7069,7 +7072,7 @@ void LLPipeline::tonemap(LLRenderTarget* src, LLRenderTarget* dst) LLSettingsSky::ptr_t psky = LLEnvironment::instance().getCurrentSky(); - bool no_post = gSnapshotNoPost || (buildNoPost && gFloaterTools->isAvailable()); + bool no_post = gSnapshotNoPost || psky->getReflectionProbeAmbiance(should_auto_adjust) == 0.f || (buildNoPost && gFloaterTools->isAvailable()); LLGLSLShader& shader = no_post ? gNoPostTonemapProgram : gDeferredPostTonemapProgram; shader.bind(); @@ -7146,7 +7149,7 @@ void LLPipeline::copyScreenSpaceReflections(LLRenderTarget* src, LLRenderTarget* LLRenderTarget& depth_src = mRT->deferredScreen; dst->bindTarget(); - dst->clear(); + dst->invalidate(); gCopyDepthProgram.bind(); S32 diff_map = gCopyDepthProgram.getTextureChannel(LLShaderMgr::DIFFUSE_MAP); @@ -7333,7 +7336,7 @@ void LLPipeline::applyFXAA(LLRenderTarget* src, LLRenderTarget* dst) // bake out texture2D with RGBL for FXAA shader mFXAAMap.bindTarget(); - mFXAAMap.clear(GL_COLOR_BUFFER_BIT); + mFXAAMap.invalidate(GL_COLOR_BUFFER_BIT); shader = &gGlowCombineFXAAProgram; shader->bind(); @@ -7432,6 +7435,7 @@ void LLPipeline::generateSMAABuffers(LLRenderTarget* src) LLGLSLShader& edge_shader = gSMAAEdgeDetectProgram[fsaa_quality]; dest.bindTarget(); + // SMAA utilizes discard, so the background color matters dest.clear(GL_COLOR_BUFFER_BIT); edge_shader.bind(); @@ -7475,7 +7479,7 @@ void LLPipeline::generateSMAABuffers(LLRenderTarget* src) LLGLSLShader& blend_weights_shader = gSMAABlendWeightsProgram[fsaa_quality]; dest.bindTarget(); - dest.clear(GL_COLOR_BUFFER_BIT); + dest.invalidate(GL_COLOR_BUFFER_BIT); blend_weights_shader.bind(); blend_weights_shader.uniform4fv(sSmaaRTMetrics, 1, rt_metrics); @@ -7551,7 +7555,7 @@ void LLPipeline::applySMAA(LLRenderTarget* src, LLRenderTarget* dst) LLGLSLShader& blend_shader = gSMAANeighborhoodBlendProgram[fsaa_quality]; bound_target->bindTarget(); - bound_target->clear(GL_COLOR_BUFFER_BIT); + bound_target->invalidate(GL_COLOR_BUFFER_BIT); blend_shader.bind(); blend_shader.uniform4fv(sSmaaRTMetrics, 1, rt_metrics); @@ -8329,9 +8333,7 @@ void LLPipeline::renderDeferredLighting() LLGLSLShader& sun_shader = gCubeSnapshot ? gDeferredSunProbeProgram : gDeferredSunProgram; bindDeferredShader(sun_shader, deferred_light_target); mScreenTriangleVB->setBuffer(); - glClearColor(1, 1, 1, 1); - deferred_light_target->clear(GL_COLOR_BUFFER_BIT); - glClearColor(0, 0, 0, 0); + deferred_light_target->invalidate(GL_COLOR_BUFFER_BIT); sun_shader.uniform2f(LLShaderMgr::DEFERRED_SCREEN_RES, (GLfloat)deferred_light_target->getWidth(), @@ -8355,9 +8357,7 @@ void LLPipeline::renderDeferredLighting() LL_PROFILE_GPU_ZONE("soften shadow"); // blur lightmap screen_target->bindTarget(); - glClearColor(1, 1, 1, 1); - screen_target->clear(GL_COLOR_BUFFER_BIT); - glClearColor(0, 0, 0, 0); + screen_target->invalidate(GL_COLOR_BUFFER_BIT); bindDeferredShader(gDeferredBlurLightProgram); @@ -8409,11 +8409,8 @@ void LLPipeline::renderDeferredLighting() deferred_light_target->flush(); unbindDeferredShader(gDeferredBlurLightProgram); } - screen_target->bindTarget(); - // clear color buffer here - zeroing alpha (glow) is important or it will accumulate against sky - glClearColor(0, 0, 0, 0); - screen_target->clear(GL_COLOR_BUFFER_BIT); + screen_target->invalidate(GL_COLOR_BUFFER_BIT); if (RenderDeferredAtmospheric) { // apply sunlight contribution @@ -10896,11 +10893,16 @@ void LLPipeline::generateImpostor(LLVOAvatar* avatar, bool preview_avatar, bool gGL.diffuseColor4fv(LLColor4::pink.mV ); } - gGL.begin(LLRender::QUADS); - gGL.vertex3f(-1, -1, clip_plane); - gGL.vertex3f(1, -1, clip_plane); - gGL.vertex3f(1, 1, clip_plane); - gGL.vertex3f(-1, 1, clip_plane); + gGL.begin(LLRender::TRIANGLES); + { + gGL.vertex3f(-1.f, -1.f, clip_plane); + gGL.vertex3f(1.f, -1.f, clip_plane); + gGL.vertex3f(1.f, 1.f, clip_plane); + + gGL.vertex3f(-1.f, -1.f, clip_plane); + gGL.vertex3f(1.f, 1.f, clip_plane); + gGL.vertex3f(-1.f, 1.f, clip_plane); + } gGL.end(); gGL.flush(); diff --git a/indra/newview/scripts/lua/frame_profile.lua b/indra/newview/scripts/lua/frame_profile.lua new file mode 100644 index 0000000000..3c6353ff68 --- /dev/null +++ b/indra/newview/scripts/lua/frame_profile.lua @@ -0,0 +1,24 @@ +-- Trigger Develop -> Render Tests -> Frame Profile + +LLAgent = require 'LLAgent' +startup = require 'startup' +Timer = (require 'timers').Timer +UI = require 'UI' + +startup.wait('STATE_STARTED') + +-- teleport to http://maps.secondlife.com/secondlife/Bug%20Island/220/224/27 +print(LLAgent.teleport{regionname='Bug Island', x=220, y=224, z=27}) +Timer(10, 'wait') +LLAgent.setCamera{camera_pos={220, 224, 26}, camera_locked=true, + focus_pos ={228, 232, 26}, focus_locked=true} +Timer(1, 'wait') +-- This freezes the viewer for perceptible realtime +UI.popup:tip('starting Render Tests -> Frame Profile') +UI.call("Advanced.ClickRenderProfile") +Timer(1, 'wait') +LLAgent.removeCamParams() +LLAgent.setFollowCamActive(false) + +-- Home, James! +print(LLAgent.teleport('home')) diff --git a/indra/newview/scripts/lua/frame_profile_quit.lua b/indra/newview/scripts/lua/frame_profile_quit.lua new file mode 100644 index 0000000000..ad136c86d2 --- /dev/null +++ b/indra/newview/scripts/lua/frame_profile_quit.lua @@ -0,0 +1,34 @@ +-- Trigger Develop -> Render Tests -> Frame Profile and quit + +assert(arg.n == 3, 'Usage: frame_profile_quit.lua x y z (for camera focus)') +-- Try coercing string arguments to numbers, using Lua's implicit conversion. +-- If the args passed as x, y, z won't convert nicely to numbers, better find +-- out now. +focus = {arg[1]+0, arg[2]+0, arg[3]+0} + +LLAgent = require 'LLAgent' +logout = require 'logout' +startup = require 'startup' +Timer = (require 'timers').Timer +UI = require 'UI' + +startup.wait('STATE_STARTED') + +-- Figure out where we are +camera = LLAgent.getRegionPosition() +-- assume z is the agent's feet, add 2 meters +camera[2] += 2 + +Timer(10, 'wait') +LLAgent.setCamera{camera_pos=camera, camera_locked=true, + focus_pos=focus, focus_locked=true} +Timer(1, 'wait') +-- This freezes the viewer for perceptible realtime +UI.popup:tip('starting Render Tests -> Frame Profile') +UI.call("Advanced.ClickRenderProfile") +Timer(1, 'wait') +LLAgent.removeCamParams() +LLAgent.setFollowCamActive(false) + +-- done +logout() diff --git a/indra/newview/scripts/lua/luafloater_camera_control.xml b/indra/newview/scripts/lua/luafloater_camera_control.xml new file mode 100644 index 0000000000..0601a363e5 --- /dev/null +++ b/indra/newview/scripts/lua/luafloater_camera_control.xml @@ -0,0 +1,244 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + legacy_header_height="18" + height="350" + layout="topleft" + name="camera_demo" + title="Follow camera control" + width="360"> + <text + type="string" + follows="left|top" + height="10" + width="100" + layout="topleft" + top="30" + left="10" + name="camera_lbl"> + Camera position: + </text> + <text + type="string" + follows="left|top" + height="10" + width="10" + layout="topleft" + top_pad="12" + left="10" + value="X" + name="cam_x_lbl"/> + <line_editor + border_style="line" + border_thickness="1" + follows="left|bottom" + font="SansSerif" + height="20" + layout="topleft" + max_length_bytes="50" + name="cam_x" + top_delta="-2" + left_pad="5" + width="65" /> + <button + follows="left|bottom" + height="20" + image_overlay="Icon_Copy" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + layout="topleft" + tool_tip="Use Agent position" + name="agent_cam_btn" + left_pad="10" + width="20" > + </button> + <text + type="string" + follows="left|top" + height="10" + width="10" + layout="topleft" + top_pad="7" + left="10" + value="Y" + name="cam_y_lbl"/> + <line_editor + border_style="line" + border_thickness="1" + follows="left|bottom" + font="SansSerif" + height="20" + layout="topleft" + max_length_bytes="50" + name="cam_y" + top_delta="-2" + left_pad="5" + width="65" /> + <text + type="string" + follows="left|top" + height="10" + width="10" + layout="topleft" + top_pad="7" + left="10" + value="Z" + name="cam_z_lbl"/> + <line_editor + border_style="line" + border_thickness="1" + follows="left|bottom" + font="SansSerif" + height="20" + layout="topleft" + max_length_bytes="50" + name="cam_z" + top_delta="-2" + left_pad="5" + width="65" /> + <text + type="string" + follows="left|top" + height="10" + width="100" + layout="topleft" + top="30" + left="145" + name="focus_lbl"> + Focus position: + </text> + <text + type="string" + follows="left|top" + height="10" + width="10" + layout="topleft" + top_pad="12" + left="145" + value="X" + name="cam_x_lbl"/> + <line_editor + border_style="line" + border_thickness="1" + follows="left|bottom" + font="SansSerif" + height="20" + layout="topleft" + max_length_bytes="50" + name="focus_x" + top_delta="-2" + left_pad="5" + width="60" /> + <button + follows="left|bottom" + height="20" + image_overlay="Icon_Copy" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + layout="topleft" + tool_tip="Use Agent position" + name="agent_focus_btn" + left_pad="10" + width="20" > + </button> + <text + type="string" + follows="left|top" + height="10" + width="10" + layout="topleft" + top_pad="7" + left="145" + value="Y" + name="cam_y_lbl"/> + <line_editor + border_style="line" + border_thickness="1" + follows="left|bottom" + font="SansSerif" + height="20" + layout="topleft" + max_length_bytes="50" + name="focus_y" + top_delta="-2" + left_pad="5" + width="60" /> + <text + type="string" + follows="left|top" + height="10" + width="10" + layout="topleft" + top_pad="7" + left="145" + value="Z" + name="cam_z_lbl"/> + <line_editor + border_style="line" + border_thickness="1" + follows="left|bottom" + font="SansSerif" + height="20" + layout="topleft" + max_length_bytes="50" + name="focus_z" + top_delta="-2" + left_pad="5" + width="60" /> + <text + type="string" + follows="left|top" + height="10" + width="100" + layout="topleft" + top="30" + left="270" + name="focus_lbl"> + Lock: + </text> + <check_box + width="80" + height="21" + layout="topleft" + follows="top|left" + top_pad="12" + label="Camera" + name="lock_camera_ctrl"/> + <check_box + width="80" + height="21" + layout="topleft" + follows="top|left" + top_pad="2" + label="Focus" + name="lock_focus_ctrl"/> + <button + follows="left|bottom" + height="25" + label="Update params" + layout="topleft" + name="update_btn" + left="10" + top="140" + width="120" > + </button> + <button + follows="left|bottom" + height="25" + label="Reset" + layout="topleft" + name="reset_btn" + left_pad="15" + width="90" > + </button> + <text_editor + follows="top|left" + font="SansSerif" + height="160" + left="5" + enabled="false" + name="events_editor" + top_pad="15" + word_wrap="true" + max_length="65536" + width="350"/> +</floater> diff --git a/indra/newview/scripts/lua/luafloater_demo.xml b/indra/newview/scripts/lua/luafloater_demo.xml new file mode 100644 index 0000000000..b2273d7718 --- /dev/null +++ b/indra/newview/scripts/lua/luafloater_demo.xml @@ -0,0 +1,93 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + legacy_header_height="18" + height="300" + layout="topleft" + name="lua_demo" + title="LUA" + width="320"> + <check_box + height="16" + label="Disable button" + layout="topleft" + top_pad="35" + left="5" + name="disable_ctrl" + width="146" /> + <line_editor + border_style="line" + border_thickness="1" + follows="left|bottom" + font="SansSerif" + height="20" + layout="topleft" + max_length_bytes="50" + name="openfloater_cmd" + top_delta="25" + width="100" /> + <button + follows="left|bottom" + height="23" + label="Open floater" + layout="topleft" + name="open_btn" + top_delta="25" + width="100" > + </button> + <text + type="string" + follows="left|top" + height="10" + layout="topleft" + top="30" + left_delta="170" + name="title_lbl"> + Select title + </text> + <combo_box + follows="top|left" + height="23" + name="title_cmb" + top_pad="5" + width="100"> + <combo_box.item + label="LUA" + value="LUA" /> + <combo_box.item + label="LUA floater" + value="LUA floater" /> + <combo_box.item + label="LUA-U title" + value="LUA-U title" /> + </combo_box> + <text + type="string" + follows="left|bottom" + height="10" + layout="topleft" + top_delta="50" + name="show_time_lbl"> + Double click me + </text> + <text + type="string" + follows="left|top" + height="10" + layout="topleft" + top_pad="15" + font="SansSerif" + text_color="white" + left_delta="15" + name="time_lbl"/> + <text_editor + follows="top|left" + font="SansSerif" + height="140" + left="5" + enabled="false" + name="events_editor" + top_pad="15" + word_wrap="true" + max_length="65536" + width="310"/> +</floater> diff --git a/indra/newview/scripts/lua/luafloater_gesture_list.xml b/indra/newview/scripts/lua/luafloater_gesture_list.xml new file mode 100644 index 0000000000..a38a04eed0 --- /dev/null +++ b/indra/newview/scripts/lua/luafloater_gesture_list.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + legacy_header_height="18" + height="150" + layout="topleft" + name="lua_gestures" + title="Gestures" + width="320"> + <scroll_list + draw_heading="false" + left="5" + width="310" + height="115" + top_pad ="25" + follows="all" + name="gesture_list"> + <scroll_list.columns + name="gesture_name" + label="Name"/> + </scroll_list> +</floater> diff --git a/indra/newview/scripts/lua/luafloater_outfits_list.xml b/indra/newview/scripts/lua/luafloater_outfits_list.xml new file mode 100644 index 0000000000..8cab864308 --- /dev/null +++ b/indra/newview/scripts/lua/luafloater_outfits_list.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + legacy_header_height="18" + height="205" + layout="topleft" + name="lua_outfits" + title="Outfits" + width="325"> + <scroll_list + draw_heading="false" + left="5" + width="315" + height="150" + top_pad ="25" + follows="all" + name="outfits_list"> + <scroll_list.columns + name="outfit_name" + label="Name"/> + </scroll_list> + <button + follows="left|bottom" + height="23" + label="Replace COF" + layout="topleft" + name="replace_btn" + enabled="false" + top_pad="5" + width="95" > + </button> + <button + follows="left|bottom" + height="23" + label="Add to COF" + left_pad="7" + layout="topleft" + name="add_btn" + enabled="false" + width="95" > + </button> + <button + follows="left|bottom" + height="23" + label="Show wearables" + left_pad="7" + layout="topleft" + name="select_btn" + enabled="false" + width="111" > + </button> +</floater> diff --git a/indra/newview/scripts/lua/luafloater_speedometer.xml b/indra/newview/scripts/lua/luafloater_speedometer.xml new file mode 100644 index 0000000000..54b99c7d48 --- /dev/null +++ b/indra/newview/scripts/lua/luafloater_speedometer.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + legacy_header_height="18" + height="60" + layout="topleft" + name="lua_speedometer" + title="Speedometer" + can_minimize="false" + width="170"> + <text + type="string" + follows="left|top" + font="SansSerifBold" + font.size="Huge" + text_color="White" + layout="topleft" + top="25" + left_delta="30" + width="70" + height="30" + name="speed_lbl"/> + <text + type="string" + follows="left|top" + width="55" + height="30" + font="SansSerifBold" + font.size="Huge" + text_color="White" + layout="topleft" + left_pad="2" + name="mps_lbl"> + m/s + </text> +</floater> diff --git a/indra/newview/scripts/lua/qtest.lua b/indra/newview/scripts/lua/qtest.lua new file mode 100644 index 0000000000..9526f58b04 --- /dev/null +++ b/indra/newview/scripts/lua/qtest.lua @@ -0,0 +1,146 @@ +-- Exercise the Queue, WaitQueue, ErrorQueue family + +Queue = require('Queue') +WaitQueue = require('WaitQueue') +ErrorQueue = require('ErrorQueue') +util = require('util') + +inspect = require('inspect') + +-- resume() wrapper to propagate errors +function resume(co, ...) + -- if there's an idiom other than table.pack() to assign an arbitrary + -- number of return values, I don't yet know it + local ok_result = table.pack(coroutine.resume(co, ...)) + if not ok_result[1] then + -- if [1] is false, then [2] is the error message + error(ok_result[2]) + end + -- ok is true, whew, just return the rest of the values + return table.unpack(ok_result, 2) +end + +-- ------------------ Queue variables are instance-specific ------------------ +q1 = Queue() +q2 = Queue() + +q1:Enqueue(17) + +assert(not q1:IsEmpty()) +assert(q2:IsEmpty()) +assert(q1:Dequeue() == 17) +assert(q1:Dequeue() == nil) +assert(q2:Dequeue() == nil) + +-- ----------------------------- test WaitQueue ------------------------------ +q1 = WaitQueue() +q2 = WaitQueue() +result = {} +values = { 1, 1, 2, 3, 5, 8, 13, 21 } + +for i, value in pairs(values) do + q1:Enqueue(value) +end +-- close() while not empty tests that queue drains before reporting done +q1:close() + +-- ensure that WaitQueue instance variables are in fact independent +assert(q2:IsEmpty()) + +-- consumer() coroutine to pull from the passed q until closed +function consumer(desc, q) + print(string.format('consumer(%s) start', desc)) + local value = q:Dequeue() + while value ~= nil do + print(string.format('consumer(%s) got %q', desc, value)) + table.insert(result, value) + value = q:Dequeue() + end + print(string.format('consumer(%s) done', desc)) +end + +-- run two consumers +coa = coroutine.create(consumer) +cob = coroutine.create(consumer) +-- Since consumer() doesn't yield while it can still retrieve values, +-- consumer(a) will dequeue all values from q1 and return when done. +resume(coa, 'a', q1) +-- consumer(b) will wake up to find the queue empty and closed. +resume(cob, 'b', q1) +coroutine.close(coa) +coroutine.close(cob) + +print('values:', inspect(values)) +print('result:', inspect(result)) + +assert(util.equal(values, result)) + +-- try incrementally enqueueing values +q3 = WaitQueue() +result = {} +values = { 'This', 'is', 'a', 'test', 'script' } + +coros = {} +for _, name in {'a', 'b'} do + local coro = coroutine.create(consumer) + table.insert(coros, coro) + -- Resuming both coroutines should leave them both waiting for a queue item. + resume(coro, name, q3) +end + +for _, s in pairs(values) do + print(string.format('Enqueue(%q)', s)) + q3:Enqueue(s) +end +q3:close() + +function joinall(coros) + local running + local errors = 0 + repeat + running = false + for i, coro in pairs(coros) do + if coroutine.status(coro) == 'suspended' then + running = true + -- directly call coroutine.resume() instead of our resume() + -- wrapper because we explicitly check for errors here + local ok, message = coroutine.resume(coro) + if not ok then + print('*** ' .. message) + errors += 1 + end + if coroutine.status(coro) == 'dead' then + coros[i] = nil + end + end + end + until not running + return errors +end + +joinall(coros) + +print(string.format('%q', table.concat(result, ' '))) +assert(util.equal(values, result)) + +-- ----------------------------- test ErrorQueue ----------------------------- +q4 = ErrorQueue() +result = {} +values = { 'This', 'is', 'a', 'test', 'script' } + +coros = {} +for _, name in {'a', 'b'} do + local coro = coroutine.create(consumer) + table.insert(coros, coro) + -- Resuming both coroutines should leave them both waiting for a queue item. + resume(coro, name, q4) +end + +for i = 1, 4 do + print(string.format('Enqueue(%q)', values[i])) + q4:Enqueue(values[i]) +end +q4:Error('something went wrong') + +assert(joinall(coros) == 2) + diff --git a/indra/newview/scripts/lua/require/ErrorQueue.lua b/indra/newview/scripts/lua/require/ErrorQueue.lua new file mode 100644 index 0000000000..e6e9a5ef48 --- /dev/null +++ b/indra/newview/scripts/lua/require/ErrorQueue.lua @@ -0,0 +1,37 @@ +-- ErrorQueue isa WaitQueue with the added feature that a producer can push an +-- error through the queue. Once that error is dequeued, every consumer will +-- raise that error. + +local WaitQueue = require('WaitQueue') +local function dbg(...) end +-- local dbg = require('printf') +local util = require('util') + +local ErrorQueue = WaitQueue() + +util.classctor(ErrorQueue) + +function ErrorQueue:Error(message) + -- Setting Error() is a marker, like closing the queue. Once we reach the + -- error, every subsequent Dequeue() call will raise the same error. + dbg('Setting self._closed to %q', message) + self._closed = message + self:_wake_waiters() +end + +function ErrorQueue:Dequeue() + local value = WaitQueue.Dequeue(self) + dbg('ErrorQueue:Dequeue: base Dequeue() got %s', value) + if value ~= nil then + -- queue not yet closed, show caller + return value + end + if self._closed == true then + -- WaitQueue:close() sets true: queue has only been closed, tell caller + return nil + end + -- self._closed is a message set by Error() + error(self._closed) +end + +return ErrorQueue diff --git a/indra/newview/scripts/lua/require/LLAgent.lua b/indra/newview/scripts/lua/require/LLAgent.lua new file mode 100644 index 0000000000..4a1132fe7e --- /dev/null +++ b/indra/newview/scripts/lua/require/LLAgent.lua @@ -0,0 +1,143 @@ +local leap = require 'leap' +local mapargs = require 'mapargs' + +local LLAgent = {} + +function LLAgent.getRegionPosition() + return leap.request('LLAgent', {op = 'getPosition'}).region +end + +function LLAgent.getGlobalPosition() + return leap.request('LLAgent', {op = 'getPosition'}).global +end + +-- Return array information about the agent's groups +-- id: group id\n" +-- name: group name\n" +-- insignia: group insignia texture id +-- notices: bool indicating if this user accepts notices from this group +-- display: bool indicating if this group is listed in the user's profile +-- contrib: user's land contribution to this group +function LLAgent.getGroups() + return leap.request('LLAgent', {op = 'getGroups'}).groups +end + +-- Use LL.leaphelp('LLAgent') and see 'setCameraParams' to get more info about params +-- -- TYPE -- DEFAULT -- RANGE +-- LLAgent.setCamera{ [, camera_pos] -- vector3 +-- [, focus_pos] -- vector3 +-- [, focus_offset] -- vector3 -- {1,0,0} -- {-10,-10,-10} to {10,10,10} +-- [, distance] -- float (meters) -- 3 -- 0.5 to 50 +-- [, focus_threshold] -- float (meters) -- 1 -- 0 to 4 +-- [, camera_threshold] -- float (meters) -- 1 -- 0 to 4 +-- [, focus_lag] -- float (seconds) -- 0.1 -- 0 to 3 +-- [, camera_lag] -- float (seconds) -- 0.1 -- 0 to 3 +-- [, camera_pitch] -- float (degrees) -- 0 -- -45 to 80 +-- [, behindness_angle] -- float (degrees) -- 10 -- 0 to 180 +-- [, behindness_lag] -- float (seconds) -- 0 -- 0 to 3 +-- [, camera_locked] -- bool -- false +-- [, focus_locked]} -- bool -- false +function LLAgent.setCamera(...) + local args = mapargs('camera_pos,focus_pos,focus_offset,focus_lag,camera_lag,' .. + 'distance,focus_threshold,camera_threshold,camera_pitch,' .. + 'camera_locked,focus_locked,behindness_angle,behindness_lag', ...) + args.op = 'setCameraParams' + leap.send('LLAgent', args) +end + +function LLAgent.setFollowCamActive(active) + leap.send('LLAgent', {op = 'setFollowCamActive', active = active}) +end + +function LLAgent.removeCamParams() + leap.send('LLAgent', {op = 'removeCameraParams'}) +end + +-- Play specified animation by "item_id" locally +-- if "inworld" is specified as true, animation will be played inworld instead +function LLAgent.playAnimation(...) + local args = mapargs('item_id,inworld', ...) + args.op = 'playAnimation' + return leap.request('LLAgent', args) +end + +function LLAgent.stopAnimation(item_id) + return leap.request('LLAgent', {op = 'stopAnimation', item_id=item_id}) +end + +-- Get animation info by "item_id" +-- reply contains "duration", "is_loop", "num_joints", "asset_id", "priority" +function LLAgent.getAnimationInfo(item_id) + return leap.request('LLAgent', {op = 'getAnimationInfo', item_id=item_id}).anim_info +end + +-- Teleport to specified "regionname" at specified region-relative "x", "y", "z". +-- If "regionname" is "home", ignore "x", "y", "z" and teleport home. +-- If "regionname" omitted, teleport to GLOBAL coordinates "x", "y", "z". +function LLAgent.teleport(...) + local args = mapargs('regionname,x,y,z', ...) + args.op = 'teleport' + return leap.request('LLTeleportHandler', args).message +end + +-- Call with no arguments to sit on the ground. +-- Otherwise specify "obj_uuid" to sit on, +-- or region "position" {x, y, z} where to find closest object to sit on. +-- For example: LLAgent.requestSit{position=LLAgent.getRegionPosition()} +-- Your avatar should be close enough to the object you want to sit on +function LLAgent.requestSit(...) + local args = mapargs('obj_uuid,position', ...) + args.op = 'requestSit' + return leap.request('LLAgent', args) +end + +function LLAgent.requestStand() + leap.send('LLAgent', {op = 'requestStand'}) +end + +-- *************************************************************************** +-- Autopilot +-- *************************************************************************** +LLAgent.autoPilotPump = "LLAutopilot" + +-- Start the autopilot to move to "target_global" location using specified parameters +-- LLAgent.startAutoPilot{ target_global array of target global {x, y, z} position +-- [, allow_flying] allow flying during autopilot [default: true] +-- [, stop_distance] target maximum distance from target [default: autopilot guess] +-- [, behavior_name] name of the autopilot behavior [default: (script name)] +-- [, target_rotation] array of [x, y, z, w] quaternion values [default: no target] +-- [, rotation_threshold] target maximum angle from target facing rotation [default: 0.03 radians] +-- an event with "success" flag is sent to "LLAutopilot" event pump, when auto pilot is terminated +function LLAgent.startAutoPilot(...) + local args = mapargs('target_global,allow_flying,stop_distance,behavior_name,target_rotation,rotation_threshold', ...) + args.op = 'startAutoPilot' + leap.send('LLAgent', args) +end + +-- Update target location for currently running autopilot +function LLAgent.setAutoPilotTarget(target_global) + leap.send('LLAgent', {op = 'setAutoPilotTarget', target_global=target_global}) +end + +-- Start the autopilot to move to the specified target location +-- either "leader_id" (uuid of target) or "avatar_name" (avatar full name: use just first name for 'Resident') should be specified +-- "allow_flying" [default: true], "stop_distance" [default: autopilot guess] +function LLAgent.startFollowPilot(...) + local args = mapargs('leader_id,avatar_name,allow_flying,stop_distance', ...) + args.op = 'startFollowPilot' + return leap.request('LLAgent', args) +end + +-- Stop the autopilot system: "user_cancel" indicates whether or not to act as though user canceled autopilot [default: false] +function LLAgent.stopAutoPilot(...) + local args = mapargs('user_cancel', ...) + args.op = 'stopAutoPilot' + leap.send('LLAgent', args) +end + +-- Get information about current state of the autopilot +function LLAgent.getAutoPilot() + return leap.request('LLAgent', {op = 'getAutoPilot'}) +end + +return LLAgent diff --git a/indra/newview/scripts/lua/require/LLAppearance.lua b/indra/newview/scripts/lua/require/LLAppearance.lua new file mode 100644 index 0000000000..f533d22daf --- /dev/null +++ b/indra/newview/scripts/lua/require/LLAppearance.lua @@ -0,0 +1,31 @@ +local leap = require 'leap' + +local LLAppearance = {} + +function LLAppearance.wearOutfit(folder, action) + action = action or 'add' + leap.request('LLAppearance', {op='wearOutfit', append = (action == 'add'), folder_id=folder}) +end + +function LLAppearance.wearOutfitByName(folder, action) + action = action or 'add' + leap.request('LLAppearance', {op='wearOutfit', append = (action == 'add'), folder_name=folder}) +end + +function LLAppearance.wearItems(items_id, replace) + leap.send('LLAppearance', {op='wearItems', replace = replace, items_id=items_id}) +end + +function LLAppearance.detachItems(items_id) + leap.send('LLAppearance', {op='detachItems', items_id=items_id}) +end + +function LLAppearance.getOutfitsList() + return leap.request('LLAppearance', {op='getOutfitsList'})['outfits'] +end + +function LLAppearance.getOutfitItems(id) + return leap.request('LLAppearance', {op='getOutfitItems', outfit_id = id})['items'] +end + +return LLAppearance diff --git a/indra/newview/scripts/lua/require/LLChat.lua b/indra/newview/scripts/lua/require/LLChat.lua new file mode 100644 index 0000000000..3ac3bab746 --- /dev/null +++ b/indra/newview/scripts/lua/require/LLChat.lua @@ -0,0 +1,39 @@ +local leap = require 'leap' + +local LLChat = {} + +-- *************************************************************************** +-- Nearby chat +-- *************************************************************************** +LLChat.nearbyChatPump = "LLNearbyChat" + +-- 0 is public nearby channel, other channels are used to communicate with LSL scripts +function LLChat.sendNearby(msg, channel) + leap.send('LLChatBar', {op='sendChat', message=msg, channel=channel}) +end + +function LLChat.sendWhisper(msg) + leap.send('LLChatBar', {op='sendChat', type='whisper', message=msg}) +end + +function LLChat.sendShout(msg) + leap.send('LLChatBar', {op='sendChat', type='shout', message=msg}) +end + +-- *************************************************************************** +-- Group chat +-- *************************************************************************** + +function LLChat.startGroupChat(group_id) + return leap.request('GroupChat', {op='startGroupChat', group_id=group_id}) +end + +function LLChat.leaveGroupChat(group_id) + leap.send('GroupChat', {op='leaveGroupChat', group_id=group_id}) +end + +function LLChat.sendGroupIM(msg, group_id) + leap.send('GroupChat', {op='sendGroupIM', message=msg, group_id=group_id}) +end + +return LLChat diff --git a/indra/newview/scripts/lua/require/LLDebugSettings.lua b/indra/newview/scripts/lua/require/LLDebugSettings.lua new file mode 100644 index 0000000000..cff1a63c21 --- /dev/null +++ b/indra/newview/scripts/lua/require/LLDebugSettings.lua @@ -0,0 +1,17 @@ +local leap = require 'leap' + +local LLDebugSettings = {} + +function LLDebugSettings.set(name, value) + leap.request('LLViewerControl', {op='set', group='Global', key=name, value=value}) +end + +function LLDebugSettings.toggle(name) + leap.request('LLViewerControl', {op='toggle', group='Global', key=name}) +end + +function LLDebugSettings.get(name) + return leap.request('LLViewerControl', {op='get', group='Global', key=name})['value'] +end + +return LLDebugSettings diff --git a/indra/newview/scripts/lua/require/LLFloaterAbout.lua b/indra/newview/scripts/lua/require/LLFloaterAbout.lua new file mode 100644 index 0000000000..a6e42d364f --- /dev/null +++ b/indra/newview/scripts/lua/require/LLFloaterAbout.lua @@ -0,0 +1,11 @@ +-- Engage the LLFloaterAbout LLEventAPI + +local leap = require 'leap' + +local LLFloaterAbout = {} + +function LLFloaterAbout.getInfo() + return leap.request('LLFloaterAbout', {op='getInfo'}) +end + +return LLFloaterAbout diff --git a/indra/newview/scripts/lua/require/LLGesture.lua b/indra/newview/scripts/lua/require/LLGesture.lua new file mode 100644 index 0000000000..343b611e2c --- /dev/null +++ b/indra/newview/scripts/lua/require/LLGesture.lua @@ -0,0 +1,23 @@ +-- Engage the LLGesture LLEventAPI + +local leap = require 'leap' + +local LLGesture = {} + +function LLGesture.getActiveGestures() + return leap.request('LLGesture', {op='getActiveGestures'})['gestures'] +end + +function LLGesture.isGesturePlaying(id) + return leap.request('LLGesture', {op='isGesturePlaying', id=id})['playing'] +end + +function LLGesture.startGesture(id) + leap.send('LLGesture', {op='startGesture', id=id}) +end + +function LLGesture.stopGesture(id) + leap.send('LLGesture', {op='stopGesture', id=id}) +end + +return LLGesture diff --git a/indra/newview/scripts/lua/require/LLInventory.lua b/indra/newview/scripts/lua/require/LLInventory.lua new file mode 100644 index 0000000000..2c80a8602b --- /dev/null +++ b/indra/newview/scripts/lua/require/LLInventory.lua @@ -0,0 +1,67 @@ +local leap = require 'leap' +local mapargs = require 'mapargs' +local result_view = require 'result_view' + +local function result(keys) + -- capture result_view() instances for both categories and items + local result_table = { + categories=result_view(keys.categories), + items=result_view(keys.items), + -- call result_table:close() to release result sets before garbage + -- collection or script completion + close = function(self) + result_view.close(keys.categories[1], keys.items[1]) + end + } + -- When the result_table is destroyed, close its result_views. + return LL.setdtor('LLInventory result', result_table, result_table.close) +end + +local LLInventory = {} + +-- Get the items/folders info by provided IDs, +-- reply will contain "items" and "categories" tables accordingly +function LLInventory.getItemsInfo(item_ids) + return result(leap.request('LLInventory', {op = 'getItemsInfo', item_ids=item_ids})) +end + +-- Get the table of folder type names, which can be later used to get the ID of the basic folders +function LLInventory.getFolderTypeNames() + return leap.request('LLInventory', {op = 'getFolderTypeNames'}).names +end + +-- Get the UUID of the basic folder("Textures", "My outfits", "Sounds" etc.) by specified folder type name +function LLInventory.getBasicFolderID(ft_name) + return leap.request('LLInventory', {op = 'getBasicFolderID', ft_name=ft_name}).id +end + +-- Get the table of asset type names, which can be later used to get the specific items via LLInventory.collectDescendantsIf(...) +function LLInventory.getAssetTypeNames() + return leap.request('LLInventory', {op = 'getAssetTypeNames'}).names +end + +-- Get the direct descendants of the 'folder_id' provided, +-- reply will contain "items" and "categories" tables accordingly +function LLInventory.getDirectDescendants(folder_id) + return result(leap.request('LLInventory', {op = 'getDirectDescendants', folder_id=folder_id})) +end +-- backwards compatibility +LLInventory.getDirectDescendents = LLInventory.getDirectDescendants + +-- Get the descendants of the 'folder_id' provided, which pass specified filters +-- reply will contain "items" and "categories" tables accordingly +-- LLInventory.collectDescendantsIf{ folder_id -- parent folder ID +-- [, name] -- name (substring) +-- [, desc] -- description (substring) +-- [, type] -- asset type +-- [, limit] -- item count limit in reply, maximum and default is 100 +-- [, filter_links]} -- EXCLUDE_LINKS - don't show links, ONLY_LINKS - only show links, INCLUDE_LINKS - show links too (default) +function LLInventory.collectDescendantsIf(...) + local args = mapargs('folder_id,name,desc,type,filter_links,limit', ...) + args.op = 'collectDescendantsIf' + return result(leap.request('LLInventory', args)) +end +-- backwards compatibility +LLInventory.collectDescendentsIf = LLInventory.collectDescendantsIf + +return LLInventory diff --git a/indra/newview/scripts/lua/require/LLListener.lua b/indra/newview/scripts/lua/require/LLListener.lua new file mode 100644 index 0000000000..b05f966097 --- /dev/null +++ b/indra/newview/scripts/lua/require/LLListener.lua @@ -0,0 +1,50 @@ +local fiber = require 'fiber' +local inspect = require 'inspect' +local leap = require 'leap' +local util = require 'util' + +local LLListener = {} +local waitfor = {} +local listener_name = {} + +function LLListener:new(pump_name) + local obj = setmetatable({}, self) + self.__index = self + obj.name = 'Listener:' .. pump_name + obj._pump = pump_name + + return obj +end + +util.classctor(LLListener) + +function LLListener:handleMessages(event_data) + print(inspect(event_data)) + return true +end + +function LLListener:start() + _pump = self._pump + waitfor = leap.WaitFor(-1, self.name) + function waitfor:filter(pump, data) + if _pump == pump then + return data + end + end + + fiber.launch(self.name, function() + event = waitfor:wait() + while event and self:handleMessages(event) do + event = waitfor:wait() + end + end) + + listener_name = leap.request(leap.cmdpump(), {op='listen', source=_pump, listener="LLListener", tweak=true}).listener +end + +function LLListener:stop() + leap.send(leap.cmdpump(), {op='stoplistening', source=self._pump, listener=listener_name}) + waitfor:close() +end + +return LLListener diff --git a/indra/newview/scripts/lua/require/Queue.lua b/indra/newview/scripts/lua/require/Queue.lua new file mode 100644 index 0000000000..5bc72e4057 --- /dev/null +++ b/indra/newview/scripts/lua/require/Queue.lua @@ -0,0 +1,51 @@ +-- from https://create.roblox.com/docs/luau/queues#implementing-queues, +-- amended per https://www.lua.org/pil/16.1.html + +-- While coding some scripting in Lua +-- I found that I needed a queua +-- I thought of linked list +-- But had to resist +-- For fear it might be too obscua. + +local util = require 'util' + +local Queue = {} + +function Queue:new() + local obj = setmetatable({}, self) + self.__index = self + + obj._first = 0 + obj._last = -1 + obj._queue = {} + + return obj +end + +util.classctor(Queue) + +-- Check if the queue is empty +function Queue:IsEmpty() + return self._first > self._last +end + +-- Add a value to the queue +function Queue:Enqueue(value) + local last = self._last + 1 + self._last = last + self._queue[last] = value +end + +-- Remove a value from the queue +function Queue:Dequeue() + if self:IsEmpty() then + return nil + end + local first = self._first + local value = self._queue[first] + self._queue[first] = nil + self._first = first + 1 + return value +end + +return Queue diff --git a/indra/newview/scripts/lua/require/Region.lua b/indra/newview/scripts/lua/require/Region.lua new file mode 100644 index 0000000000..e4eefece33 --- /dev/null +++ b/indra/newview/scripts/lua/require/Region.lua @@ -0,0 +1,17 @@ +LLFloaterAbout = require 'LLFloaterAbout' + +local Region = {} + +function Region.getInfo() + info = LLFloaterAbout.getInfo() + return { + HOSTNAME=info.HOSTNAME, + POSITION=info.POSITION, + POSITION_LOCAL=info.POSITION_LOCAL, + REGION=info.REGION, + SERVER_VERSION=info.SERVER_VERSION, + SLURL=info.SLURL, + } +end + +return Region diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua new file mode 100644 index 0000000000..cf2695917e --- /dev/null +++ b/indra/newview/scripts/lua/require/UI.lua @@ -0,0 +1,243 @@ +-- Engage the viewer's UI + +local leap = require 'leap' +local mapargs = require 'mapargs' +local result_view = require 'result_view' +local Timer = (require 'timers').Timer +local util = require 'util' + +-- Allow lazily accessing UI submodules on demand, e.g. a reference to +-- UI.Floater lazily loads the UI/Floater module. +local UI = util.submoduledir({}, 'UI') + +-- *************************************************************************** +-- registered menu actions +-- *************************************************************************** +function UI.call(func, parameter) + -- 'call' is fire-and-forget + leap.request('UI', {op='call', ['function']=func, parameter=parameter}) +end + +function UI.callables() + return leap.request('UI', {op='callables'}).callables +end + +function UI.getValue(path) + return leap.request('UI', {op='getValue', path=path})['value'] +end + +-- *************************************************************************** +-- UI views +-- *************************************************************************** +-- Either: +-- wreq{op='Something', a=1, b=2, ...} +-- or: +-- (args should be local, as this wreq() call modifies it) +-- local args = {a=1, b=2, ...} +-- wreq('Something', args) +local function wreq(op_or_data, data_if_op) + if data_if_op ~= nil then + -- this is the wreq(op, data) form + data_if_op.op = op_or_data + op_or_data = data_if_op + end + return leap.request('LLWindow', op_or_data) +end + +-- omit 'parent' to list all view paths +function UI.listviews(parent) + return wreq{op='getPaths', under=parent} +end + +function UI.viewinfo(path) + return wreq{op='getInfo', path=path} +end + +-- *************************************************************************** +-- mouse actions +-- *************************************************************************** +-- pass a table: +-- UI.click{path=path +-- [, button='LEFT' | 'CENTER' | 'RIGHT'] +-- [, x=x, y=y] +-- [, hold=duration]} +function UI.click(...) + local args = mapargs('path,button,x,y,hold', ...) + args.button = args.button or 'LEFT' + local hold = args.hold or 1.0 + wreq('mouseMove', args) + wreq('mouseDown', args) + Timer(hold, 'wait') + wreq('mouseUp', args) +end + +-- pass a table as for UI.click() +function UI.doubleclick(...) + local args = mapargs('path,button,x,y', ...) + args.button = args.button or 'LEFT' + wreq('mouseDown', args) + wreq('mouseUp', args) + wreq('mouseDown', args) + wreq('mouseUp', args) +end + +-- UI.drag{path=, xoff=, yoff=} +function UI.drag(...) + local args = mapargs('path,xoff,yoff', ...) + -- query the specified path + local rect = UI.viewinfo(args.path).rect + local centerx = math.floor(rect.left + (rect.right - rect.left)/2) + local centery = math.floor(rect.bottom + (rect.top - rect.bottom)/2) + wreq{op='mouseMove', path=args.path, x=centerx, y=centery} + wreq{op='mouseDown', path=args.path, button='LEFT'} + wreq{op='mouseMove', path=args.path, x=centerx + args.xoff, y=centery + args.yoff} + wreq{op='mouseUp', path=args.path, button='LEFT'} +end + +-- *************************************************************************** +-- keyboard actions +-- *************************************************************************** +-- pass a table: +-- UI.keypress{ +-- [path=path] -- if omitted, default input field +-- [, char='x'] -- requires one of char, keycode, keysym +-- [, keycode=120] +-- keysym per https://github.com/secondlife/viewer/blob/main/indra/llwindow/llkeyboard.cpp#L68-L124 +-- [, keysym='Enter'] +-- [, mask={'SHIFT', 'CTL', 'ALT', 'MAC_CONTROL'}] -- some subset of these +-- } +function UI.keypress(...) + local args = mapargs('path,char,keycode,keysym,mask', ...) + if args.char == '\n' then + args.char = nil + args.keysym = 'Enter' + end + return wreq('keyDown', args) +end + +-- UI.type{text=, path=} +function UI.type(...) + local args = mapargs('text,path', ...) + if #args.text > 0 then + -- The caller's path may be specified in a way that requires recursively + -- searching parts of the LLView tree. No point in doing that more than + -- once. Capture the actual path found by that first call and use that for + -- subsequent calls. + local path = UI.keypress{path=args.path, char=string.sub(args.text, 1, 1)}.path + for i = 2, #args.text do + UI.keypress{path=path, char=string.sub(args.text, i, i)} + end + end +end + +-- *************************************************************************** +-- Snapshot +-- *************************************************************************** +-- UI.snapshot{filename=filename -- extension may be specified: bmp, jpeg, png +-- [, type='COLOR' | 'DEPTH'] +-- [, width=width][, height=height] -- uses current window size if not specified +-- [, showui=true][, showhud=true] +-- [, rebuild=false]} +function UI.snapshot(...) + local args = mapargs('filename,width,height,showui,showhud,rebuild,type', ...) + args.op = 'saveSnapshot' + return leap.request('LLViewerWindow', args).result +end + +-- *************************************************************************** +-- Top menu +-- *************************************************************************** + +function UI.getTopMenus() + return leap.request('UI', {op='getTopMenus'}).menus +end + +function UI.addMenu(...) + local args = mapargs('name,label', ...) + args.op = 'addMenu' + return leap.request('UI', args) +end + +function UI.setMenuVisible(name, visible) + return leap.request('UI', {op='setMenuVisible', name=name, visible=visible}) +end + +function UI.addMenuBranch(...) + local args = mapargs('name,label,parent_menu', ...) + args.op = 'addMenuBranch' + return leap.request('UI', args) +end + +-- see UI.callables() for valid values of 'func' +function UI.addMenuItem(...) + local args = mapargs('name,label,parent_menu,func,param,pos', ...) + args.op = 'addMenuItem' + return leap.request('UI', args) +end + +function UI.addMenuSeparator(...) + local args = mapargs('parent_menu,pos', ...) + args.op = 'addMenuSeparator' + return leap.request('UI', args) +end + +-- *************************************************************************** +-- Toolbar buttons +-- *************************************************************************** +-- Clears all buttons off the toolbars +function UI.clearAllToolbars() + leap.send('UI', {op='clearAllToolbars'}) +end + +function UI.defaultToolbars() + leap.send('UI', {op='defaultToolbars'}) +end + +-- UI.addToolbarBtn{btn_name=btn_name +-- [, toolbar= bottom] -- left, right, bottom -- default is bottom +-- [, rank=1]} -- position on the toolbar, starts at 0 (0 - first position, 1 - second position etc.) +function UI.addToolbarBtn(...) + local args = mapargs('btn_name,toolbar,rank', ...) + args.op = 'addToolbarBtn' + return leap.request('UI', args) +end + +-- Returns the rank(position) of the command in the original list +function UI.removeToolbarBtn(btn_name) + return leap.request('UI', {op = 'removeToolbarBtn', btn_name=btn_name}).rank +end + +function UI.getToolbarBtnNames() + return leap.request('UI', {op = 'getToolbarBtnNames'}).cmd_names +end + +-- *************************************************************************** +-- Floaters +-- *************************************************************************** +function UI.showFloater(floater_name) + leap.send("LLFloaterReg", {op = "showInstance", name = floater_name}) +end + +function UI.hideFloater(floater_name) + leap.send("LLFloaterReg", {op = "hideInstance", name = floater_name}) +end + +function UI.toggleFloater(floater_name) + leap.send("LLFloaterReg", {op = "toggleInstance", name = floater_name}) +end + +function UI.isFloaterVisible(floater_name) + return leap.request("LLFloaterReg", {op = "instanceVisible", name = floater_name}).visible +end + +function UI.closeAllFloaters() + return leap.send("UI", {op = "closeAllFloaters"}) +end + +function UI.getFloaterNames() + local key_length = leap.request("LLFloaterReg", {op = "getFloaterNames"}).floaters + local view = result_view(key_length) + return LL.setdtor('registered floater names', view, view.close) +end + +return UI diff --git a/indra/newview/scripts/lua/require/UI/Floater.lua b/indra/newview/scripts/lua/require/UI/Floater.lua new file mode 100644 index 0000000000..d057a74386 --- /dev/null +++ b/indra/newview/scripts/lua/require/UI/Floater.lua @@ -0,0 +1,146 @@ +-- Floater base class + +local leap = require 'leap' +local fiber = require 'fiber' +local util = require 'util' + +-- list of all the events that a LLLuaFloater might send +local event_list = leap.request("LLFloaterReg", {op="getFloaterEvents"}).events +local event_set = {} +for _, event in pairs(event_list) do + event_set[event] = true +end + +local function _event(event_name) + if not event_set[event_name] then + error("Incorrect event name: " .. event_name, 3) + end + return event_name +end + +-- --------------------------------------------------------------------------- +local Floater = {} + +-- Pass: +-- relative file path to floater's XUI definition file +-- optional: sign up for additional events for defined control +-- {<control_name>={action1, action2, ...}} +function Floater:new(path, extra) + local obj = setmetatable({}, self) + self.__index = self + + local path_parts = string.split(path, '/') + obj.name = 'Floater ' .. path_parts[#path_parts] + + obj._command = {op="showLuaFloater", xml_path=LL.abspath(path)} + if extra then + -- validate each of the actions for each specified control + for control, actions in pairs(extra) do + for _, action in pairs(actions) do + _event(action) + end + end + obj._command.extra_events = extra + end + + return obj +end + +util.classctor(Floater) + +function Floater:show() + -- leap.eventstream() returns the first response, and launches a + -- background fiber to call the passed callback with all subsequent + -- responses. + local event = leap.eventstream( + 'LLFloaterReg', + self._command, + -- handleEvents() returns false when done. + -- eventstream() expects a true return when done. + function(event) return not self:handleEvents(event) end) + self._pump = event.command_name + -- we might need the returned reqid to cancel the eventstream() fiber + self.reqid = event.reqid + + -- The response to 'showLuaFloater' *is* the 'post_build' event. Check if + -- subclass has a post_build() method. Honor the convention that if + -- handleEvents() returns false, we're done. + if not self:handleEvents(event) then + return + end +end + +function Floater:post(action) + leap.send(self._pump, action) +end + +function Floater:request(action) + return leap.request(self._pump, action) +end + +-- local inspect = require 'inspect' + +function Floater:handleEvents(event_data) + local event = event_data.event + if event_set[event] == nil then + LL.print_warning(string.format('%s received unknown event %q', self.name, event)) + end + + -- Before checking for a general (e.g.) commit() method, first look for + -- commit_ctrl_name(): in other words, concatenate the event name with the + -- ctrl_name, with an underscore between. If there exists such a specific + -- method, call that. + local handler, ret + if event_data.ctrl_name then + local specific = event .. '_' .. event_data.ctrl_name + handler = self[specific] + if handler then + ret = handler(self, event_data) + -- Avoid 'return ret or true' because we explicitly want to allow + -- the handler to return false. + if ret ~= nil then + return ret + else + return true + end + end + end + + -- No specific "event_on_ctrl()" method found; try just "event()" + handler = self[event] + if handler then + ret = handler(self, event_data) + if ret ~= nil then + return ret + end +-- else +-- print(string.format('%s ignoring event %s', self.name, inspect(event_data))) + end + + -- We check for event() method before recognizing floater_close in case + -- the consumer needs to react specially to closing the floater. Now that + -- we've checked, recognize it ourselves. Returning false terminates the + -- anonymous fiber function launched by leap.eventstream(). + if event == _event('floater_close') then + LL.print_warning(self.name .. ' closed') + return false + end + return true +end + +-- onCtrl() permits a different dispatch style in which the general event() +-- method explicitly calls (e.g.) +-- self:onCtrl(event_data, { +-- ctrl_name=function() +-- self:post(...) +-- end, +-- ... +-- }) +function Floater:onCtrl(event_data, ctrl_map) + local handler = ctrl_map[event_data.ctrl_name] + if handler then + handler() + end +end + +return Floater diff --git a/indra/newview/scripts/lua/require/UI/popup.lua b/indra/newview/scripts/lua/require/UI/popup.lua new file mode 100644 index 0000000000..8ccf3b87f3 --- /dev/null +++ b/indra/newview/scripts/lua/require/UI/popup.lua @@ -0,0 +1,82 @@ +local leap = require 'leap' +local mapargs = require 'mapargs' +local util = require 'util' + +-- notification is any name defined in notifications.xml as +-- <notification name=> +-- vars is a table providing values for [VAR] substitution keys in the +-- notification body +-- payload prepopulates the response table +-- wait=false means fire and forget, returning nil +-- wait=true waits for user response: +-- * If the viewer returns a table containing exactly one key=true pair, +-- popup() returns just that key. If the key is a string containing an +-- underscore, e.g. 'OK_okcancelbuttons', it's truncated at the first +-- underscore, e.g. 'OK'. +-- * Otherwise the viewer's response is returned unchanged. To suppress the +-- above transformations, pass a non-empty payload table; this will cause +-- the viewer to return a table with at least two keys. +local popup = util.setmetamethods{ + -- this gets called when a consumer calls popup(notification, vars, payload) + __call = function(self, ...) + local args = mapargs('notification,vars,payload,wait', ...) + -- we use convenience argument names different from 'LLNotifications' + -- listener + newargs = {op='requestAdd', + name=args.notification, + substitutions=args.vars, + payload=args.payload} + -- Specifically test (wait == false), NOT (not wait), because we treat + -- nil (omitted, default true) differently than false (explicitly + -- DON'T wait). + if args.wait == false then + leap.send('LLNotifications', newargs) + else + local response = leap.request('LLNotifications', newargs).response + -- response is typically a table. It might have multiple keys, + -- e.g. if caller passed non-empty payload. In that case, just + -- return the whole thing. + if type(response) ~= 'table' then + return response + end + -- get first key=value pair, if any + local key, value = next(response) + if (not key) or next(response, key) then + -- key == nil means response is empty + -- next(response, non-nil first key) ~= nil means at least two keys + return response + end + -- Here response is a table containing exactly one key. The + -- notifications system typically returns a table of the form + -- {OK_okcancelbuttons=true}, which is tricky to test for because it + -- varies with each set of buttons. + if value == true then + -- change {key=true} to plain key + response = key + if type(response) == 'string' then + -- change 'OK_okcancelbuttons' to plain 'OK' + response = string.split(response, '_')[1] + end + end + return response + end + end +} + +function popup:alert(message, payload) + return self('GenericAlert', {MESSAGE=message, payload=payload}) +end + +function popup:alertOK(message, payload) + return self('GenericAlertOK', {MESSAGE=message, payload=payload}) +end + +function popup:alertYesCancel(message, payload) + return self('GenericAlertYesCancel', {MESSAGE=message, payload=payload}) +end + +function popup:tip(message, payload) + self{'SystemMessageTip', {MESSAGE=message, payload=payload}, wait=false} +end + +return popup diff --git a/indra/newview/scripts/lua/require/WaitQueue.lua b/indra/newview/scripts/lua/require/WaitQueue.lua new file mode 100644 index 0000000000..7e10d03295 --- /dev/null +++ b/indra/newview/scripts/lua/require/WaitQueue.lua @@ -0,0 +1,88 @@ +-- WaitQueue isa Queue with the added feature that when the queue is empty, +-- the Dequeue() operation blocks the calling coroutine until some other +-- coroutine Enqueue()s a new value. + +local fiber = require('fiber') +local Queue = require('Queue') +local util = require('util') + +local function dbg(...) end +-- local dbg = require('printf') + +local WaitQueue = Queue() + +function WaitQueue:new() + local obj = Queue() + setmetatable(obj, self) + self.__index = self + + obj._waiters = {} + obj._closed = false + return obj +end + +util.classctor(WaitQueue) + +function WaitQueue:Enqueue(value) + if self._closed then + error("can't Enqueue() on closed Queue") + end + -- can't simply call Queue:Enqueue(value)! That calls the method on the + -- Queue class definition, instead of calling Queue:Enqueue() on self. + -- Hand-expand the Queue:Enqueue() syntactic sugar. + Queue.Enqueue(self, value) + self:_wake_waiters() +end + +function WaitQueue:_wake_waiters() + -- WaitQueue is designed to support multi-producer, multi-consumer use + -- cases. With multiple consumers, if more than one is trying to + -- Dequeue() from an empty WaitQueue, we'll have multiple waiters. + -- Unlike OS threads, with cooperative concurrency it doesn't make sense + -- to "notify all": we need wake only one of the waiting Dequeue() + -- callers. + if ((not self:IsEmpty()) or self._closed) and next(self._waiters) then + -- Pop the oldest waiting coroutine instead of the most recent, for + -- more-or-less round robin fairness. But skip any coroutines that + -- have gone dead in the meantime. + local waiter = table.remove(self._waiters, 1) + while waiter and fiber.status(waiter) == "dead" do + waiter = table.remove(self._waiters, 1) + end + -- do we still have at least one waiting coroutine? + if waiter then + -- don't pass the head item: let the resumed coroutine retrieve it + fiber.wake(waiter) + end + end +end + +function WaitQueue:Dequeue() + while self:IsEmpty() do + -- Don't check for closed until the queue is empty: producer can close + -- the queue while there are still items left, and we want the + -- consumer(s) to retrieve those last few items. + if self._closed then + dbg('WaitQueue:Dequeue(): closed') + return nil + end + dbg('WaitQueue:Dequeue(): waiting') + -- add the running coroutine to the list of waiters + dbg('WaitQueue:Dequeue() running %s', tostring(coroutine.running() or 'main')) + table.insert(self._waiters, fiber.running()) + -- then let somebody else run + fiber.wait() + end + -- here we're sure this queue isn't empty + dbg('WaitQueue:Dequeue() calling Queue.Dequeue()') + return Queue.Dequeue(self) +end + +function WaitQueue:close() + self._closed = true + -- close() is like Enqueueing an end marker. If there are waiting + -- consumers, give them a chance to see we're closed. + self:_wake_waiters() +end + +return WaitQueue diff --git a/indra/newview/scripts/lua/require/coro.lua b/indra/newview/scripts/lua/require/coro.lua new file mode 100644 index 0000000000..616a797e95 --- /dev/null +++ b/indra/newview/scripts/lua/require/coro.lua @@ -0,0 +1,67 @@ +-- Manage Lua coroutines + +local coro = {} + +coro._coros = {} + +-- Launch a Lua coroutine: create and resume. +-- Returns: new coroutine, values yielded or returned from initial resume() +-- If initial resume() encountered an error, propagates the error. +function coro.launch(func, ...) + local co = coroutine.create(func) + table.insert(coro._coros, co) + return co, coro.resume(co, ...) +end + +-- resume() wrapper to propagate errors +function coro.resume(co, ...) + -- if there's an idiom other than table.pack() to assign an arbitrary + -- number of return values, I don't yet know it + local ok_result = table.pack(coroutine.resume(co, ...)) + if not ok_result[1] then + -- if [1] is false, then [2] is the error message + error(ok_result[2]) + end + -- ok is true, whew, just return the rest of the values + return table.unpack(ok_result, 2) +end + +-- yield to other coroutines even if you don't know whether you're in a +-- created coroutine or the main coroutine +function coro.yield(...) + if coroutine.running() then + -- this is a real coroutine, yield normally + return coroutine.yield(...) + else + -- This is the main coroutine: coroutine.yield() doesn't work. + -- But we can take a spin through previously-launched coroutines. + -- Walk a copy of coro._coros in case any of these coroutines launches + -- another: next() forbids creating new entries during traversal. + for co in coro._live_coros_iter, table.clone(coro._coros) do + coro.resume(co) + end + end +end + +-- Walk coro._coros table, returning running or suspended coroutines. +-- Once a coroutine becomes dead, remove it from _coros and don't return it. +function coro._live_coros() + return coro._live_coros_iter, coro._coros +end + +-- iterator function for _live_coros() +function coro._live_coros_iter(t, idx) + local k, co = next(t, idx) + while k and coroutine.status(co) == 'dead' do +-- t[k] = nil + -- See coro.yield(): sometimes we traverse a copy of _coros, but if we + -- discover a dead coroutine in that copy, delete it from _coros + -- anyway. Deleting it from a temporary copy does nothing. + coro._coros[k] = nil + coroutine.close(co) + k, co = next(t, k) + end + return co +end + +return coro diff --git a/indra/newview/scripts/lua/require/fiber.lua b/indra/newview/scripts/lua/require/fiber.lua new file mode 100644 index 0000000000..b3c684dd67 --- /dev/null +++ b/indra/newview/scripts/lua/require/fiber.lua @@ -0,0 +1,346 @@ +-- Organize Lua coroutines into fibers. + +-- In this usage, the difference between coroutines and fibers is that fibers +-- have a scheduler. Yielding a fiber means allowing other fibers, plural, to +-- run: it's more than just returning control to the specific Lua thread that +-- resumed the running coroutine. + +-- fiber.launch() creates a new fiber ready to run. +-- fiber.status() reports (augmented) status of the passed fiber: instead of +-- 'suspended', it returns either 'ready' or 'waiting' +-- fiber.yield() allows other fibers to run, but leaves the calling fiber +-- ready to run. +-- fiber.wait() marks the running fiber not ready, and resumes other fibers. +-- fiber.wake() marks the designated suspended fiber ready to run, but does +-- not yet resume it. +-- fiber.run() runs all current fibers until all have terminated (successfully +-- or with an error). + +local printf = require 'printf' +local function dbg(...) end +-- local dbg = printf +local coro = require 'coro' + +local fiber = {} + +-- The tables in which we track fibers must have weak keys so dead fibers +-- can be garbage-collected. +local weak_values = {__mode='v'} +local weak_keys = {__mode='k'} + +-- Track each current fiber as being either ready to run or not ready +-- (waiting). wait() moves the running fiber from ready to waiting; wake() +-- moves the designated fiber from waiting back to ready. +-- The ready table is used as a list so yield() can go round robin. +local ready = setmetatable({'main'}, weak_keys) +-- The waiting table is used as a set because order doesn't matter. +local waiting = setmetatable({}, weak_keys) + +-- Every fiber has a name, for diagnostic purposes. Names must be unique. +-- A colliding name will be suffixed with an integer. +-- Predefine 'main' with our marker so nobody else claims that name. +local names = setmetatable({main='main'}, weak_keys) +local byname = setmetatable({main='main'}, weak_values) +-- each colliding name has its own distinct suffix counter +local suffix = {} + +-- Specify a nullary idle() callback to be called whenever there are no ready +-- fibers but there are waiting fibers. The idle() callback is responsible for +-- changing zero or more waiting fibers to ready fibers by calling +-- fiber.wake(), although a given call may leave them all still waiting. +-- When there are no ready fibers, it's a good idea for the idle() function to +-- return control to a higher-level execution agent. Simply returning without +-- changing any fiber's status will spin the CPU. +-- The idle() callback can return non-nil to exit fiber.run() with that value. +function fiber._idle() + error('fiber.yield(): you must first call set_idle(nullary idle() function)') +end + +function fiber.set_idle(func) + fiber._idle = func +end + +-- Launch a new Lua fiber, ready to run. +function fiber.launch(name, func, ...) + local args = table.pack(...) + local co = coroutine.create(function() func(table.unpack(args)) end) + -- a new fiber is ready to run + table.insert(ready, co) + local namekey = name + while byname[namekey] do + if not suffix[name] then + suffix[name] = 1 + end + suffix[name] += 1 + namekey = name .. tostring(suffix[name]) + end + -- found a namekey not yet in byname: set it + byname[namekey] = co + -- and remember it as this fiber's name + names[co] = namekey +-- dbg('launch(%s)', namekey) +-- dbg('byname[%s] = %s', namekey, tostring(byname[namekey])) +-- dbg('names[%s] = %s', tostring(co), names[co]) +-- dbg('ready[-1] = %s', tostring(ready[#ready])) +end + +-- for debugging +function format_all() + output = {} + table.insert(output, 'Ready fibers:' .. if next(ready) then '' else ' none') + for _, co in pairs(ready) do + table.insert(output, string.format(' %s: %s', fiber.get_name(co), fiber.status(co))) + end + table.insert(output, 'Waiting fibers:' .. if next(waiting) then '' else ' none') + for co in pairs(waiting) do + table.insert(output, string.format(' %s: %s', fiber.get_name(co), fiber.status(co))) + end + return table.concat(output, '\n') +end + +function fiber.print_all() + print(format_all()) +end + +-- return either the running coroutine or, if called from the main thread, +-- 'main' +function fiber.running() + return coroutine.running() or 'main' +end + +-- Query a fiber's name (nil for the running fiber) +function fiber.get_name(co) + return names[co or fiber.running()] or 'unknown' +end + +-- Query status of the passed fiber +function fiber.status(co) + local running = coroutine.running() + if (not co) or co == running then + -- silly to ask the status of the running fiber: it's 'running' + return 'running' + end + if co ~= 'main' then + -- for any coroutine but main, consult coroutine.status() + local status = coroutine.status(co) + if status ~= 'suspended' then + return status + end + -- here co is suspended, answer needs further refinement + else + -- co == 'main' + if not running then + -- asking about 'main' from the main fiber + return 'running' + end + -- asking about 'main' from some other fiber, so presumably main is suspended + end + -- here we know co is suspended -- but is it ready to run? + if waiting[co] then + return 'waiting' + end + -- not waiting should imply ready: sanity check + if table.find(ready, co) then + return 'ready' + end + -- Calls within yield() between popping the next ready fiber and + -- re-appending it to the list are in this state. Once we're done + -- debugging yield(), we could reinstate either of the below. +-- error(string.format('fiber.status(%s) is stumped', fiber.get_name(co))) +-- print(string.format('*** fiber.status(%s) is stumped', fiber.get_name(co))) + return '(unknown)' +end + +-- change the running fiber's status to waiting +local function set_waiting() + -- if called from the main fiber, inject a 'main' marker into the list + co = fiber.running() + -- delete from ready list + local i = table.find(ready, co) + if i then + table.remove(ready, i) + end + -- add to waiting list + waiting[co] = true +end + +-- Suspend the current fiber until some other fiber calls fiber.wake() on it +function fiber.wait() + dbg('Fiber %q waiting', fiber.get_name()) + set_waiting() + -- now yield to other fibers + fiber.yield() +end + +-- Mark a suspended fiber as being ready to run +function fiber.wake(co) + if not waiting[co] then + error(string.format('fiber.wake(%s) but status=%s, ready=%s, waiting=%s', + names[co], fiber.status(co), ready[co], waiting[co])) + end + -- delete from waiting list + waiting[co] = nil + -- add to end of ready list + table.insert(ready, co) + dbg('Fiber %q ready', fiber.get_name(co)) + -- but don't yet resume it: that happens next time we reach yield() +end + +-- pop and return the next not-dead fiber in the ready list, or nil if none remain +local function live_ready_iter() + -- don't write: + -- for co in table.remove, ready, 1 + -- because it would keep passing a new second parameter! + for co in function() return table.remove(ready, 1) end do + dbg('%s live_ready_iter() sees %s, status %s', + fiber.get_name(), fiber.get_name(co), fiber.status(co)) + -- keep removing the head entry until we find one that's not dead, + -- discarding any dead coroutines along the way + if co == 'main' or coroutine.status(co) ~= 'dead' then + dbg('%s live_ready_iter() returning %s', + fiber.get_name(), fiber.get_name(co)) + return co + end + end + dbg('%s live_ready_iter() returning nil', fiber.get_name()) + return nil +end + +-- prune the set of waiting fibers +local function prune_waiting() + for waiter in pairs(waiting) do + if waiter ~= 'main' and coroutine.status(waiter) == 'dead' then + waiting[waiter] = nil + end + end +end + +-- Run other ready fibers, leaving this one ready, returning after a cycle. +-- Returns: +-- * true, nil if there remain other live fibers, whether ready or waiting, +-- but it's our turn to run +-- * false, nil if this is the only remaining fiber +-- * nil, x if configured idle() callback returns non-nil x +local function scheduler() + dbg('scheduler():\n%s', format_all()) + -- scheduler() is asymmetric because Lua distinguishes the main thread + -- from other coroutines. The main thread can't yield; it can only resume + -- other coroutines. So although an arbitrary coroutine could resume still + -- other arbitrary coroutines, it could NOT resume the main thread because + -- the main thread can't yield. Therefore, scheduler() delegates its real + -- processing to the main thread. If called from a coroutine, pass control + -- back to the main thread. + if coroutine.running() then + -- this is a real coroutine, yield normally to main thread + coroutine.yield() + -- main certainly still exists + return true + end + + -- This is the main fiber: coroutine.yield() doesn't work. + -- Instead, resume each of the ready fibers. + -- Prune the set of waiting fibers after every time fiber business logic + -- runs (i.e. other fibers might have terminated or hit error), such as + -- here on entry. + prune_waiting() + local others, idle_stop + repeat + for co in live_ready_iter do + -- seize the opportunity to make sure the viewer isn't shutting down + LL.check_stop() + -- before we re-append co, is it the only remaining entry? + others = next(ready) + -- co is live, re-append it to the ready list + table.insert(ready, co) + if co == 'main' then + -- Since we know the caller is the main fiber, it's our turn. + -- Tell caller if there are other ready or waiting fibers. + return others or next(waiting) + end + -- not main, but some other ready coroutine: + -- use coro.resume() so we'll propagate any error encountered + coro.resume(co) + prune_waiting() + end + -- Here there are no ready fibers. Are there any waiting fibers? + if not next(waiting) then + return false + end + -- there are waiting fibers: call consumer's configured idle() function + idle_stop = fiber._idle() + if idle_stop ~= nil then + return nil, idle_stop + end + prune_waiting() + -- loop "forever", that is, until: + -- * main is ready, or + -- * there are neither ready fibers nor waiting fibers, or + -- * fiber._idle() returned non-nil + until false +end + +-- Let other fibers run. This is useful in either of two cases: +-- * fiber.wait() calls this to run other fibers while this one is waiting. +-- fiber.yield() (and therefore fiber.wait()) works from the main thread as +-- well as from explicitly-launched fibers, without the caller having to +-- care. +-- * A long-running fiber that doesn't often call fiber.wait() should sprinkle +-- in fiber.yield() calls to interleave processing on other fibers. +function fiber.yield() + -- The difference between this and fiber.run() is that fiber.yield() + -- assumes its caller has work to do. yield() returns to its caller as + -- soon as scheduler() pops this fiber from the ready list. fiber.run() + -- continues looping until all other fibers have terminated, or the + -- set_idle() callback tells it to stop. + local others, idle_done = scheduler() + -- scheduler() returns either if we're ready, or if idle_done ~= nil. + if idle_done ~= nil then + -- Returning normally from yield() means the caller can carry on with + -- its pending work. But in this case scheduler() returned because the + -- configured set_idle() function interrupted it -- not because we're + -- actually ready. Don't return normally. + error('fiber.set_idle() interrupted yield() with: ' .. tostring(idle_done)) + end + -- We're ready! Just return to caller. In this situation we don't care + -- whether there are other ready fibers. + dbg('fiber.yield() returning to %s (%sothers are ready)', + fiber.get_name(), ((not others) and "no " or "")) +end + +-- Run fibers until all but main have terminated: return nil. +-- Or until configured idle() callback returns x ~= nil: return x. +function fiber.run() + -- A fiber calling run() is not also doing other useful work. Remove the + -- calling fiber from the ready list. Otherwise yield() would keep seeing + -- that our caller is ready and return to us, instead of realizing that + -- all coroutines are waiting and call idle(). But don't say we're + -- waiting, either, because then when all other fibers have terminated + -- we'd call idle() forever waiting for something to make us ready again. + local i = table.find(ready, fiber.running()) + if i then + table.remove(ready, i) + end + local others, idle_done + repeat + dbg('%s calling fiber.run() calling scheduler()', fiber.get_name()) + others, idle_done = scheduler() + dbg("%s fiber.run()'s scheduler() returned %s, %s", fiber.get_name(), + tostring(others), tostring(idle_done)) + until (not others) + dbg('%s fiber.run() done', fiber.get_name()) + -- For whatever it's worth, put our own fiber back in the ready list. + table.insert(ready, fiber.running()) + -- Once there are no more waiting fibers, and the only ready fiber is + -- us, return to caller. All previously-launched fibers are done. Possibly + -- the chunk is done, or the chunk may decide to launch a new batch of + -- fibers. + return idle_done +end + +-- Make sure we finish up with a call to run(). That allows a consuming script +-- to kick off some number of fibers, do some work on the main thread and then +-- fall off the end of the script without explicitly calling fiber.run(). +-- run() ensures the rest of the fibers run to completion (or error). +LL.atexit(fiber.run) + +return fiber diff --git a/indra/newview/scripts/lua/require/inspect.lua b/indra/newview/scripts/lua/require/inspect.lua new file mode 100644 index 0000000000..9900a0b81b --- /dev/null +++ b/indra/newview/scripts/lua/require/inspect.lua @@ -0,0 +1,371 @@ +local _tl_compat; if (tonumber((_VERSION or ''):match('[%d.]*$')) or 0) < 5.3 then local p, m = pcall(require, 'compat53.module'); if p then _tl_compat = m end end; local math = _tl_compat and _tl_compat.math or math; local string = _tl_compat and _tl_compat.string or string; local table = _tl_compat and _tl_compat.table or table +local inspect = {Options = {}, } + + + + + + + + + + + + + + + + + +inspect._VERSION = 'inspect.lua 3.1.0' +inspect._URL = 'http://github.com/kikito/inspect.lua' +inspect._DESCRIPTION = 'human-readable representations of tables' +inspect._LICENSE = [[ + MIT LICENSE + + Copyright (c) 2022 Enrique García Cota + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +]] +inspect.KEY = setmetatable({}, { __tostring = function() return 'inspect.KEY' end }) +inspect.METATABLE = setmetatable({}, { __tostring = function() return 'inspect.METATABLE' end }) + +local tostring = tostring +local rep = string.rep +local match = string.match +local char = string.char +local gsub = string.gsub +local fmt = string.format + +local _rawget +if rawget then + _rawget = rawget +else + _rawget = function(t, k) return t[k] end +end + +local function rawpairs(t) + return next, t, nil +end + + + +local function smartQuote(str) + if match(str, '"') and not match(str, "'") then + return "'" .. str .. "'" + end + return '"' .. gsub(str, '"', '\\"') .. '"' +end + + +local shortControlCharEscapes = { + ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", + ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v", ["\127"] = "\\127", +} +local longControlCharEscapes = { ["\127"] = "\127" } +for i = 0, 31 do + local ch = char(i) + if not shortControlCharEscapes[ch] then + shortControlCharEscapes[ch] = "\\" .. i + longControlCharEscapes[ch] = fmt("\\%03d", i) + end +end + +local function escape(str) + return (gsub(gsub(gsub(str, "\\", "\\\\"), + "(%c)%f[0-9]", longControlCharEscapes), + "%c", shortControlCharEscapes)) +end + +local luaKeywords = { + ['and'] = true, + ['break'] = true, + ['do'] = true, + ['else'] = true, + ['elseif'] = true, + ['end'] = true, + ['false'] = true, + ['for'] = true, + ['function'] = true, + ['goto'] = true, + ['if'] = true, + ['in'] = true, + ['local'] = true, + ['nil'] = true, + ['not'] = true, + ['or'] = true, + ['repeat'] = true, + ['return'] = true, + ['then'] = true, + ['true'] = true, + ['until'] = true, + ['while'] = true, +} + +local function isIdentifier(str) + return type(str) == "string" and + not not str:match("^[_%a][_%a%d]*$") and + not luaKeywords[str] +end + +local flr = math.floor +local function isSequenceKey(k, sequenceLength) + return type(k) == "number" and + flr(k) == k and + 1 <= (k) and + k <= sequenceLength +end + +local defaultTypeOrders = { + ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, + ['function'] = 5, ['userdata'] = 6, ['thread'] = 7, +} + +local function sortKeys(a, b) + local ta, tb = type(a), type(b) + + + if ta == tb and (ta == 'string' or ta == 'number') then + return (a) < (b) + end + + local dta = defaultTypeOrders[ta] or 100 + local dtb = defaultTypeOrders[tb] or 100 + + + return dta == dtb and ta < tb or dta < dtb +end + +local function getKeys(t) + + local seqLen = 1 + while _rawget(t, seqLen) ~= nil do + seqLen = seqLen + 1 + end + seqLen = seqLen - 1 + + local keys, keysLen = {}, 0 + for k in rawpairs(t) do + if not isSequenceKey(k, seqLen) then + keysLen = keysLen + 1 + keys[keysLen] = k + end + end + table.sort(keys, sortKeys) + return keys, keysLen, seqLen +end + +local function countCycles(x, cycles) + if type(x) == "table" then + if cycles[x] then + cycles[x] = cycles[x] + 1 + else + cycles[x] = 1 + for k, v in rawpairs(x) do + countCycles(k, cycles) + countCycles(v, cycles) + end + countCycles(getmetatable(x), cycles) + end + end +end + +local function makePath(path, a, b) + local newPath = {} + local len = #path + for i = 1, len do newPath[i] = path[i] end + + newPath[len + 1] = a + newPath[len + 2] = b + + return newPath +end + + +local function processRecursive(process, + item, + path, + visited) + if item == nil then return nil end + if visited[item] then return visited[item] end + + local processed = process(item, path) + if type(processed) == "table" then + local processedCopy = {} + visited[item] = processedCopy + local processedKey + + for k, v in rawpairs(processed) do + processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) + if processedKey ~= nil then + processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) + end + end + + local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) + if type(mt) ~= 'table' then mt = nil end + setmetatable(processedCopy, mt) + processed = processedCopy + end + return processed +end + +local function puts(buf, str) + buf.n = buf.n + 1 + buf[buf.n] = str +end + + + +local Inspector = {} + + + + + + + + + + +local Inspector_mt = { __index = Inspector } + +local function tabify(inspector) + puts(inspector.buf, inspector.newline .. rep(inspector.indent, inspector.level)) +end + +function Inspector:getId(v) + local id = self.ids[v] + local ids = self.ids + if not id then + local tv = type(v) + id = (ids[tv] or 0) + 1 + ids[v], ids[tv] = id, id + end + return tostring(id) +end + +function Inspector:putValue(v) + local buf = self.buf + local tv = type(v) + if tv == 'string' then + puts(buf, smartQuote(escape(v))) + elseif tv == 'number' or tv == 'boolean' or tv == 'nil' or + tv == 'cdata' or tv == 'ctype' then + puts(buf, tostring(v)) + elseif tv == 'table' and not self.ids[v] then + local t = v + + if t == inspect.KEY or t == inspect.METATABLE then + puts(buf, tostring(t)) + elseif self.level >= self.depth then + puts(buf, '{...}') + else + if self.cycles[t] > 1 then puts(buf, fmt('<%d>', self:getId(t))) end + + local keys, keysLen, seqLen = getKeys(t) + + puts(buf, '{') + self.level = self.level + 1 + + for i = 1, seqLen + keysLen do + if i > 1 then puts(buf, ',') end + if i <= seqLen then + puts(buf, ' ') + self:putValue(t[i]) + else + local k = keys[i - seqLen] + tabify(self) + if isIdentifier(k) then + puts(buf, k) + else + puts(buf, "[") + self:putValue(k) + puts(buf, "]") + end + puts(buf, ' = ') + self:putValue(t[k]) + end + end + + local mt = getmetatable(t) + if type(mt) == 'table' then + if seqLen + keysLen > 0 then puts(buf, ',') end + tabify(self) + puts(buf, '<metatable> = ') + self:putValue(mt) + end + + self.level = self.level - 1 + + if keysLen > 0 or type(mt) == 'table' then + tabify(self) + elseif seqLen > 0 then + puts(buf, ' ') + end + + puts(buf, '}') + end + + else + puts(buf, fmt('<%s %d>', tv, self:getId(v))) + end +end + + + + +function inspect.inspect(root, options) + options = options or {} + + local depth = options.depth or (math.huge) + local newline = options.newline or '\n' + local indent = options.indent or ' ' + local process = options.process + + if process then + root = processRecursive(process, root, {}, {}) + end + + local cycles = {} + countCycles(root, cycles) + + local inspector = setmetatable({ + buf = { n = 0 }, + ids = {}, + cycles = cycles, + depth = depth, + level = 0, + newline = newline, + indent = indent, + }, Inspector_mt) + + inspector:putValue(root) + + return table.concat(inspector.buf) +end + +setmetatable(inspect, { + __call = function(_, root, options) + return inspect.inspect(root, options) + end, +}) + +return inspect diff --git a/indra/newview/scripts/lua/require/leap.lua b/indra/newview/scripts/lua/require/leap.lua new file mode 100644 index 0000000000..82f91ce9e9 --- /dev/null +++ b/indra/newview/scripts/lua/require/leap.lua @@ -0,0 +1,550 @@ +-- Lua implementation of LEAP (LLSD Event API Plugin) protocol +-- +-- This module supports Lua scripts run by the Second Life viewer. +-- +-- LEAP protocol passes LLSD objects, converted to/from Lua tables, in both +-- directions. A typical LLSD object is a map containing keys 'pump' and +-- 'data'. +-- +-- The viewer's Lua post_to(pump, data) function posts 'data' to the +-- LLEventPump 'pump'. This is typically used to engage an LLEventAPI method. +-- +-- Similarly, the viewer gives each Lua script its own LLEventPump with a +-- unique name. That name is returned by get_event_pumps(). Every event +-- received on that LLEventPump is queued for retrieval by get_event_next(), +-- which returns (pump, data): the name of the LLEventPump on which the event +-- was received and the received event data. When the queue is empty, +-- get_event_next() blocks the calling Lua script until the next event is +-- received. +-- +-- Usage: +-- 1. Launch some number of Lua coroutines. The code in each coroutine may +-- call leap.send(), leap.request() or leap.generate(). leap.send() returns +-- immediately ("fire and forget"). leap.request() blocks the calling +-- coroutine until it receives and returns the viewer's response to its +-- request. leap.generate() expects an arbitrary number of responses to the +-- original request. +-- 2. To handle events from the viewer other than direct responses to +-- requests, instantiate a leap.WaitFor object with a filter(pump, data) +-- override method that returns non-nil for desired events. A coroutine may +-- call wait() on any such WaitFor. +-- 3. Once the coroutines have been launched, call leap.process() on the main +-- coroutine. process() retrieves incoming events from the viewer and +-- dispatches them to waiting request() or generate() calls, or to +-- appropriate WaitFor instances. process() returns when either +-- get_event_next() raises an error or the viewer posts nil to the script's +-- reply pump to indicate it's done. +-- 4. Alternatively, a running coroutine may call leap.done() to break out of +-- leap.process(). process() won't notice until the next event from the +-- viewer, though. + +local fiber = require('fiber') +local ErrorQueue = require('ErrorQueue') +local inspect = require('inspect') +local function dbg(...) end +-- local dbg = require('printf') +local util = require('util') + +local leap = {} + +-- reply: string name of reply LLEventPump. Any events the viewer posts to +-- this pump will be queued for get_event_next(). We usually specify it as the +-- reply pump for requests to internal viewer services. +-- command: string name of command LLEventPump. post_to(command, ...) +-- engages LLLeapListener operations such as listening on a specified other +-- LLEventPump, etc. +local reply, command = LL.get_event_pumps() +-- Dict of features added to the LEAP protocol since baseline implementation. +-- Before engaging a new feature that might break an older viewer, we can +-- check for the presence of that feature key. This table is solely about the +-- LEAP protocol itself, the way we communicate with the viewer. To discover +-- whether a given listener exists, or supports a particular operation, use +-- command's "getAPI" operation. +-- For Lua, command's "getFeatures" operation suffices? +-- leap._features = {} + +-- Each outstanding request() or generate() call has a corresponding +-- WaitForReqid object (later in this module) to handle the +-- response(s). If an incoming event contains an echoed ["reqid"] key, +-- we can look up the appropriate WaitForReqid object more efficiently +-- in a dict than by tossing such objects into the usual waitfors list. +-- Note: the ["reqid"] must be unique, otherwise we could end up +-- replacing an earlier WaitForReqid object in pending with a +-- later one. That means that no incoming event will ever be given to +-- the old WaitForReqid object. Any coroutine waiting on the discarded +-- WaitForReqid object would therefore wait forever. +-- pending is NOT a weak table because the caller of request() or generate() +-- never sees the WaitForReqid object. pending holds the only reference, so +-- it should NOT be garbage-collected. +local pending = {} +-- Our consumer will instantiate some number of WaitFor subclass objects. +-- As these are traversed in descending priority order, we must keep +-- them in a list. +-- Anyone who instantiates a WaitFor subclass object should retain a reference +-- to it. Once the consuming script drops the reference, allow Lua to +-- garbage-collect the WaitFor despite its entry in waitfors. +local weak_values = {__mode='v'} +local waitfors = setmetatable({}, weak_values) +-- It has been suggested that we should use UUIDs as ["reqid"] values, +-- since UUIDs are guaranteed unique. However, as the "namespace" for +-- ["reqid"] values is our very own reply pump, we can get away with +-- an integer. +leap._reqid = 0 +-- break leap.process() loop +leap._done = false + +-- get the name of the reply pump +function leap.replypump() + return reply +end + +-- get the name of the command pump +function leap.cmdpump() + return command +end + +-- Fire and forget. Send the specified request LLSD, expecting no reply. +-- In fact, should the request produce an eventual reply, it will be +-- treated as an unsolicited event. +-- +-- See also request(), generate(). +function leap.send(pump, data, reqid) + local data = data + if type(data) == 'table' then + data = table.clone(data) + data['reply'] = reply + if reqid ~= nil then + data['reqid'] = reqid + end + end + dbg('leap.send(%s, %s) calling post_on()', pump, data) + LL.post_on(pump, data) +end + +-- common setup code shared by request() and generate() +local function requestSetup(pump, data) + -- invent a new, unique reqid + leap._reqid += 1 + local reqid = leap._reqid + -- Instantiate a new WaitForReqid object. The priority is irrelevant + -- because, unlike the WaitFor base class, WaitForReqid does not + -- self-register on our waitfors list. Instead, capture the new + -- WaitForReqid object in pending so dispatch() can find it. + local waitfor = leap.WaitForReqid(reqid) + pending[reqid] = waitfor + -- Pass reqid to send() to stamp it into (a copy of) the request data. + dbg('requestSetup(%s, %s) storing %s', pump, data, waitfor.name) + leap.send(pump, data, reqid) + return reqid, waitfor +end + +-- Send the specified request LLSD, expecting exactly one reply. Block +-- the calling coroutine until we receive that reply. +-- +-- Every request() (or generate()) LLSD block we send will get stamped +-- with a distinct ["reqid"] value. The requested event API must echo the +-- same ["reqid"] field in each reply associated with that request. This way +-- we can correctly dispatch interleaved replies from different requests. +-- +-- If the desired event API doesn't support the ["reqid"] echo convention, +-- you should use send() instead -- since request() or generate() would +-- wait forever for a reply stamped with that ["reqid"] -- and intercept +-- any replies using WaitFor. +-- +-- Unless the request data already contains a ["reply"] key, we insert +-- reply=self.replypump to try to ensure that the expected reply will be +-- returned over the socket. +-- +-- See also send(), generate(). +function leap.request(pump, data) + local reqid, waitfor = requestSetup(pump, data) + dbg('leap.request(%s, %s) about to wait on %s', pump, data, tostring(waitfor)) + local ok, response = pcall(waitfor.wait, waitfor) + dbg('leap.request(%s, %s) got %s: %s', pump, data, ok, response) + -- kill off temporary WaitForReqid object, even if error + pending[reqid] = nil + if not ok then + error(response) + elseif response.error then + error(response.error) + else + return response + end +end + +-- Send the specified request LLSD, expecting an arbitrary number of replies. +-- Each one is returned on request. +-- +-- Usage: +-- sequence = leap.generate(pump, data) +-- repeat +-- response = sequence.next() +-- until last(response) +-- (last() means whatever test the caller wants to perform on response) +-- sequence.done() +-- +-- See request() remarks about ["reqid"]. +-- +-- Note: this seems like a prime use case for Lua coroutines. But in a script +-- using fibers.lua, a "wild" coroutine confuses the fiber scheduler. If +-- generate() were itself a coroutine, it would call WaitForReqid:wait(), +-- which would yield -- thereby resuming generate() WITHOUT waiting. +function leap.generate(pump, data, checklast) + -- Invent a new, unique reqid. Arrange to handle incoming events + -- bearing that reqid. Stamp the outbound request with that reqid, and + -- send it. + local reqid, waitfor = requestSetup(pump, data) + return { + next = function() + dbg('leap.generate(%s).next() about to wait on %s', reqid, tostring(waitfor)) + local ok, response = pcall(waitfor.wait, waitfor) + dbg('leap.generate(%s).next() got %s: %s', reqid, ok, response) + if not ok then + error(response) + elseif response.error then + error(response.error) + else + return response + end + end, + done = function() + -- cleanup consists of removing our WaitForReqid from pending + pending[reqid] = nil + end + } +end + +-- Send the specified request LLSD, expecting an immediate reply followed by +-- an arbitrary number of subsequent replies with the same reqid. Block the +-- calling coroutine until the first (immediate) reply, but launch a separate +-- fiber on which to call the passed callback with later replies. +-- +-- Once the callback returns true, the background fiber terminates. +function leap.eventstream(pump, data, callback) + local reqid, waitfor = requestSetup(pump, data) + local response = waitfor:wait() + if response.error then + -- clean up our WaitForReqid + waitfor:close() + error(response.error) + end + -- No error, so far so good: + -- call the callback with the first response just in case + dbg('leap.eventstream(%s): first callback', reqid) + local ok, done = pcall(callback, response) + dbg('leap.eventstream(%s) got %s, %s', reqid, ok, done) + if not ok then + -- clean up our WaitForReqid + waitfor:close() + error(done) + end + if done then + return response + end + -- callback didn't throw an error, and didn't say stop, + -- so set up to handle subsequent events + -- TODO: distinguish "daemon" fibers that can be terminated even if waiting + fiber.launch( + pump, + function () + local ok, done + local nth = 1 + repeat + event = waitfor:wait() + if not event then + -- wait() returns nil once the queue is closed (e.g. cancelreq()) + ok, done = true, true + else + nth += 1 + dbg('leap.eventstream(%s): callback %d', reqid, nth) + ok, done = pcall(callback, event) + dbg('leap.eventstream(%s) got %s, %s', reqid, ok, done) + end + -- not ok means callback threw an error (caught as 'done') + -- done means callback succeeded but wants to stop + until (not ok) or done + -- once we break this loop, clean up our WaitForReqid + waitfor:close() + if not ok then + -- can't reflect the error back to our caller + LL.print_warning(fiber.get_name() .. ': ' .. done) + end + end) + return response +end + +-- we might want to clean up after leap.eventstream() even if the callback has +-- not yet returned true +function leap.cancelreq(reqid) + dbg('cancelreq(%s)', reqid) + local waitfor = pending[reqid] + if waitfor ~= nil then + -- close() removes the pending entry and also closes the queue, + -- breaking the background fiber's wait loop. + dbg('cancelreq(%s) canceling %s', reqid, waitfor.name) + waitfor:close() + end +end + +local function cleanup(message) + -- We're done: clean up all pending coroutines. + -- Iterate over copies of the pending and waitfors tables, since the + -- close() operation modifies the real tables. + for i, waitfor in pairs(table.clone(pending)) do + waitfor:close() + end + for i, waitfor in pairs(table.clone(waitfors)) do + waitfor:close() + end +end + +-- Handle an incoming (pump, data) event with no recognizable ['reqid'] +local function unsolicited(pump, data) + -- we maintain waitfors in descending priority order, so the first waitfor + -- to claim this event is the one with the highest priority + for i, waitfor in pairs(waitfors) do + dbg('unsolicited() checking %s', waitfor.name) + if waitfor:handle(pump, data) then + return + end + end + LL.print_debug(string.format('unsolicited(%s, %s) discarding unclaimed event', + pump, inspect(data))) +end + +-- Route incoming (pump, data) event to the appropriate waiting coroutine. +local function dispatch(pump, data) + local reqid = data['reqid'] + -- if the response has no 'reqid', it's not from request() or generate() + if reqid == nil then +-- dbg('dispatch() found no reqid; calling unsolicited(%s, %s)', pump, data) + return unsolicited(pump, data) + end + -- have reqid; do we have a WaitForReqid? + local waitfor = pending[reqid] + if waitfor == nil then +-- dbg('dispatch() found no WaitForReqid(%s); calling unsolicited(%s, %s)', reqid, pump, data) + return unsolicited(pump, data) + end + -- found the right WaitForReqid object, let it handle the event +-- dbg('dispatch() calling %s.handle(%s, %s)', waitfor.name, pump, data) + waitfor:handle(pump, data) +end + +-- We configure fiber.set_idle() function. fiber.yield() calls the configured +-- idle callback whenever there are waiting fibers but no ready fibers. In +-- our case, that means it's time to fetch another incoming viewer event. +fiber.set_idle(function () + -- If someone has called leap.done(), then tell fiber.yield() to break loop. + if leap._done then + cleanup('done') + return 'done' + end + dbg('leap.idle() calling get_event_next()') + local ok, pump, data = pcall(LL.get_event_next) + dbg('leap.idle() got %s: %s, %s', ok, pump, data) + -- ok false means get_event_next() raised a Lua error, pump is message + if not ok then + cleanup(pump) + error(pump) + end + -- data nil means get_event_next() returned (pump, LLSD()) to indicate done + if not data then + cleanup('end') + return 'end' + end + -- got a real pump, data pair + dispatch(pump, data) + -- return to fiber.yield(): any incoming message might result in one or + -- more fibers becoming ready +end) + +function leap.done() + leap._done = true +end + +-- called by WaitFor.enable() +local function registerWaitFor(waitfor) + table.insert(waitfors, waitfor) + -- keep waitfors sorted in descending order of specified priority + table.sort(waitfors, + function (lhs, rhs) return lhs.priority > rhs.priority end) +end + +-- called by WaitFor.disable() +local function unregisterWaitFor(waitfor) + local i = table.find(waitfors, waitfor) + if i ~= nil then + waitfors[i] = nil + end +end + +-- ****************************************************************************** +-- WaitFor and friends +-- ****************************************************************************** + +-- An unsolicited event is handled by the highest-priority WaitFor subclass +-- object willing to accept it. If no such object is found, the unsolicited +-- event is discarded. +-- +-- * First, instantiate a WaitFor subclass object to register its interest in +-- some incoming event(s). WaitFor instances are self-registering; merely +-- instantiating the object suffices. +-- * Any coroutine may call a given WaitFor object's wait() method. This blocks +-- the calling coroutine until a suitable event arrives. +-- * WaitFor's constructor accepts a float priority. Every incoming event +-- (other than those claimed by request() or generate()) is passed to each +-- extant WaitFor.filter() method in descending priority order. The first +-- such filter() to return nontrivial data claims that event. +-- * At that point, the blocked wait() call on that WaitFor object returns the +-- item returned by filter(). +-- * WaitFor contains a queue. Multiple arriving events claimed by that WaitFor +-- object's filter() method are added to the queue. Naturally, until the +-- queue is empty, calling wait() immediately returns the front entry. +-- +-- It's reasonable to instantiate a WaitFor subclass whose filter() method +-- unconditionally returns the incoming event, and whose priority places it +-- last in the list. This object will enqueue every unsolicited event left +-- unclaimed by other WaitFor subclass objects. +-- +-- It's not strictly necessary to associate a WaitFor object with exactly one +-- coroutine. You might have multiple "worker" coroutines drawing from the same +-- WaitFor object, useful if the work being done per event might itself involve +-- "blocking" operations. Or a given coroutine might sample a number of WaitFor +-- objects in round-robin fashion... etc. etc. Nonetheless, it's +-- straightforward to designate one coroutine for each WaitFor object. + +-- --------------------------------- WaitFor --------------------------------- +leap.WaitFor = { _id=0 } + +function leap.WaitFor.tostring(self) + -- Lua (sub)classes have no name; can't prefix with that + return self.name +end + +function leap.WaitFor:new(priority, name) + local obj = setmetatable({__tostring=leap.WaitFor.tostring}, self) + self.__index = self + + obj.priority = priority + if name then + obj.name = name + else + self._id += 1 + obj.name = 'WaitFor' .. self._id + end + obj._queue = ErrorQueue() + obj._registered = false + -- if no priority, then don't enable() - remember 0 is truthy + if priority then + obj:enable() + end + + return obj +end + +util.classctor(leap.WaitFor) + +-- Re-enable a disable()d WaitFor object. New WaitFor objects are +-- enable()d by default. +function leap.WaitFor:enable() + if not self._registered then + registerWaitFor(self) + self._registered = true + end +end + +-- Disable an enable()d WaitFor object. +function leap.WaitFor:disable() + if self._registered then + unregisterWaitFor(self) + self._registered = false + end +end + +-- Block the calling coroutine until a suitable unsolicited event (one +-- for which filter() returns the event) arrives. +function leap.WaitFor:wait() + dbg('%s about to wait', self.name) + local item = self._queue:Dequeue() + dbg('%s got %s', self.name, item) + return item +end + +-- Override filter() to examine the incoming event in whatever way +-- makes sense. +-- +-- Return nil to ignore this event. +-- +-- To claim the event, return the item you want placed in the queue. +-- Typically you'd write: +-- return data +-- or perhaps +-- return {pump=pump, data=data} +-- or some variation. +function leap.WaitFor:filter(pump, data) + error('You must override the WaitFor.filter() method') +end + +-- called by unsolicited() for each WaitFor in waitfors +function leap.WaitFor:handle(pump, data) + local item = self:filter(pump, data) + dbg('%s.filter() returned %s', self.name, item) + -- if this item doesn't pass the filter, we're not interested + if not item then + return false + end + -- okay, filter() claims this event + self:process(item) + return true +end + +-- called by WaitFor:handle() for an accepted event +function leap.WaitFor:process(item) + self._queue:Enqueue(item) +end + +-- called by cleanup() at end +function leap.WaitFor:close() + self:disable() + self._queue:close() +end + +-- called by leap.process() when get_event_next() raises an error +function leap.WaitFor:exception(message) + LL.print_warning(self.name .. ' error: ' .. message) + self._queue:Error(message) +end + +-- ------------------------------ WaitForReqid ------------------------------- +leap.WaitForReqid = leap.WaitFor() + +function leap.WaitForReqid:new(reqid) + -- priority is meaningless, since this object won't be added to the + -- priority-sorted waitfors list. Use the reqid as the debugging name + -- string. + local obj = leap.WaitFor(nil, 'WaitForReqid(' .. reqid .. ')') + setmetatable(obj, self) + self.__index = self + + obj.reqid = reqid + + return obj +end + +util.classctor(leap.WaitForReqid) + +function leap.WaitForReqid:filter(pump, data) + -- Because we expect to directly look up the WaitForReqid object of + -- interest based on the incoming ["reqid"] value, it's not necessary + -- to test the event again. Accept every such event. + return data +end + +function leap.WaitForReqid:close() + -- remove this entry from pending table + pending[self.reqid] = nil + self._queue:close() +end + +return leap diff --git a/indra/newview/scripts/lua/require/login.lua b/indra/newview/scripts/lua/require/login.lua new file mode 100644 index 0000000000..37c9093a21 --- /dev/null +++ b/indra/newview/scripts/lua/require/login.lua @@ -0,0 +1,42 @@ +local leap = require 'leap' +local startup = require 'startup' +local mapargs = require 'mapargs' + +local login = {} + +local function ensure_login_state(op) + -- no point trying to login until the viewer is ready + startup.wait('STATE_LOGIN_WAIT') + -- Once we've actually started login, LLPanelLogin is destroyed, and so is + -- its "LLPanelLogin" listener. At that point, + -- leap.request("LLPanelLogin", ...) will hang indefinitely because no one + -- is listening on that LLEventPump any more. Intercept that case and + -- produce a sensible error. + local state = startup.state() + if startup.before('STATE_LOGIN_WAIT', state) then + error(`Can't engage login operation {op} once we've reached state {state}`, 2) + end +end + +local function fullgrid(grid) + if string.find(grid, '.', 1, true) then + return grid + else + return `util.{grid}.secondlife.com` + end +end + +function login.login(...) + ensure_login_state('login') + local args = mapargs('username,grid,slurl', ...) + args.op = 'login' + args.grid = fullgrid(args.grid) + return leap.request('LLPanelLogin', args) +end + +function login.savedLogins(grid) + ensure_login_state('savedLogins') + return leap.request('LLPanelLogin', {op='savedLogins', grid=fullgrid(grid)})['logins'] +end + +return login diff --git a/indra/newview/scripts/lua/require/logout.lua b/indra/newview/scripts/lua/require/logout.lua new file mode 100644 index 0000000000..63dcd7f01f --- /dev/null +++ b/indra/newview/scripts/lua/require/logout.lua @@ -0,0 +1,7 @@ +local leap = require 'leap' + +local function logout() + leap.send('LLAppViewer', {op='userQuit'}); +end + +return logout diff --git a/indra/newview/scripts/lua/require/mapargs.lua b/indra/newview/scripts/lua/require/mapargs.lua new file mode 100644 index 0000000000..45f5a9c556 --- /dev/null +++ b/indra/newview/scripts/lua/require/mapargs.lua @@ -0,0 +1,73 @@ +-- Allow a calling function to be passed a mix of positional arguments with +-- keyword arguments. Reference them as fields of a table. +-- Don't use this for a function that can accept a single table argument. +-- mapargs() assumes that a single table argument means its caller was called +-- with f{table constructor} syntax, and maps that table to the specified names. +-- Usage: +-- function f(...) +-- local a = mapargs({'a1', 'a2', 'a3'}, ...) +-- ... a.a1 ... etc. +-- end +-- f(10, 20, 30) -- a.a1 == 10, a.a2 == 20, a.a3 == 30 +-- f{10, 20, 30} -- a.a1 == 10, a.a2 == 20, a.a3 == 30 +-- f{a3=300, a1=100} -- a.a1 == 100, a.a2 == nil, a.a3 == 300 +-- f{1, a3=3} -- a.a1 == 1, a.a2 == nil, a.a3 == 3 +-- f{a3=3, 1} -- a.a1 == 1, a.a2 == nil, a.a3 == 3 +local function mapargs(names, ...) + local args = table.pack(...) + local posargs = {} + local keyargs = {} + -- For a mixed table, no Lua operation will reliably tell you how many + -- array items it contains, if there are any holes. Track that by hand. + -- We must be able to handle f(1, nil, 3) calls. + local maxpos = 0 + + -- For convenience, allow passing 'names' as a string 'n0,n1,...' + if type(names) == 'string' then + names = string.split(names, ',') + end + + if not (args.n == 1 and type(args[1]) == 'table') then + -- If caller passes more than one argument, or if the first argument + -- is not a table, then it's classic positional function-call syntax: + -- f(first, second, etc.). In that case we need not bother teasing + -- apart positional from keyword arguments. + posargs = args + maxpos = args.n + else + -- Single table argument implies f{mixed} syntax. + -- Tease apart positional arguments from keyword arguments. + for k, v in pairs(args[1]) do + if type(k) == 'number' then + posargs[k] = v + maxpos = math.max(maxpos, k) + else + if table.find(names, k) == nil then + error('unknown keyword argument ' .. tostring(k)) + end + keyargs[k] = v + end + end + end + + -- keyargs already has keyword arguments in place, just fill in positionals + args = keyargs + -- Don't exceed the number of parameter names. Loop explicitly over every + -- index value instead of using ipairs() so we can support holes (nils) in + -- posargs. + for i = 1, math.min(#names, maxpos) do + if posargs[i] ~= nil then + -- As in Python, make it illegal to pass an argument both positionally + -- and by keyword. This implementation permits func(17, first=nil), a + -- corner case about which I don't particularly care. + if args[names[i]] ~= nil then + error(string.format('parameter %s passed both positionally and by keyword', + tostring(names[i]))) + end + args[names[i]] = posargs[i] + end + end + return args +end + +return mapargs diff --git a/indra/newview/scripts/lua/require/printf.lua b/indra/newview/scripts/lua/require/printf.lua new file mode 100644 index 0000000000..e84b2024df --- /dev/null +++ b/indra/newview/scripts/lua/require/printf.lua @@ -0,0 +1,19 @@ +-- printf(...) is short for print(string.format(...)) + +local inspect = require 'inspect' + +local function printf(format, ...) + -- string.format() only handles numbers and strings. + -- Convert anything else to string using the inspect module. + local args = {} + for _, arg in pairs(table.pack(...)) do + if type(arg) == 'number' or type(arg) == 'string' then + table.insert(args, arg) + else + table.insert(args, inspect(arg)) + end + end + print(string.format(format, table.unpack(args))) +end + +return printf diff --git a/indra/newview/scripts/lua/require/result_view.lua b/indra/newview/scripts/lua/require/result_view.lua new file mode 100644 index 0000000000..5301d7838c --- /dev/null +++ b/indra/newview/scripts/lua/require/result_view.lua @@ -0,0 +1,98 @@ +local leap = require 'leap' + +-- metatable for every result_view() table +local mt = { + __len = function(self) + return self.length + end, + __index = function(self, i) + -- right away, convert to 0-relative indexing + i -= 1 + -- can we find this index within the current slice? + local reli = i - self.start + if 0 <= reli and reli < #self.slice then + -- Lua 1-relative indexing + return self.slice[reli + 1] + end + -- is this index outside the overall result set? + if not (0 <= i and i < self.length) then + return nil + end + -- fetch a new slice starting at i, using provided fetch() + local start + self.slice, start = self.fetch(self.key, i) + -- It's possible that caller-provided fetch() function forgot + -- to return the adjusted start index of the new slice. In + -- Lua, 0 tests as true, so if fetch() returned (slice, 0), + -- we'll duly reset self.start to 0. Otherwise, assume the + -- requested index was not adjusted: that the returned slice + -- really does start at i. + self.start = start or i + -- Hopefully this slice contains the desired i. + -- Back to 1-relative indexing. + return self.slice[i - self.start + 1] + end, + -- We purposely avoid putting any array entries (int keys) into + -- our table so that access to any int key will always call our + -- __index() metamethod. Moreover, we want any table iteration to + -- call __index(table, i) however many times; we do NOT want it to + -- retrieve key, length, start, slice. + -- So turn 'for k, v in result' into 'for k, v in ipairs(result)'. + __iter = ipairs, + -- This result set provides read-only access. + -- We do not support pushing updates to individual items back to + -- C++; for the intended use cases, that makes no sense. + __newindex = function(self, i, value) + error("result_view is a read-only data structure", 2) + end +} + +-- result_view(key_length, fetch) returns a table which stores only a slice +-- of a result set plus some control values, yet presents read-only virtual +-- access to the entire result set. +-- key_length: {result set key, total result set length} +-- fetch: function(key, start) that returns (slice, adjusted start) +local result_view = setmetatable( + { + -- generic fetch() function + fetch = function(key, start) + local fetched = leap.request( + 'LLInventory', + {op='getSlice', result=key, index=start}) + return fetched.slice, fetched.start + end, + -- generic close() function accepting variadic result-set keys + close = function(...) + local keys = table.pack(...) + -- table.pack() produces a table with an array entry for every + -- parameter, PLUS an 'n' key with the count. Unfortunately that + -- 'n' key bollixes our conversion to LLSD, which requires either + -- all int keys (for an array) or all string keys (for a map). + keys.n = nil + leap.send('LLInventory', {op='closeResult', result=keys}) + end + }, + { + -- result_view(key_length, fetch) calls this + __call = function(class, key_length, fetch) + return setmetatable( + { + key=key_length[1], + length=key_length[2], + -- C++ result sets use 0-based indexing, so internally we do too + start=0, + -- start with a dummy array with length 0 + slice={}, + -- if caller didn't pass fetch() function, use generic + fetch=fetch or class.fetch, + -- returned view:close() will close result set with passed key + close=function(self) class.close(key_length[1]) end + }, + -- use our special metatable + mt + ) + end + } +) + +return result_view diff --git a/indra/newview/scripts/lua/require/startup.lua b/indra/newview/scripts/lua/require/startup.lua new file mode 100644 index 0000000000..c3040f94b8 --- /dev/null +++ b/indra/newview/scripts/lua/require/startup.lua @@ -0,0 +1,100 @@ +-- query, wait for or mandate a particular viewer startup state + +-- During startup, the viewer steps through a sequence of numbered (and named) +-- states. This can be used to detect when, for instance, the login screen is +-- displayed, or when the viewer has finished logging in and is fully +-- in-world. + +local fiber = require 'fiber' +local leap = require 'leap' +local inspect = require 'inspect' +local function dbg(...) end +-- local dbg = require 'printf' + +-- --------------------------------------------------------------------------- +local startup = {} + +-- Get the list of startup states from the viewer. +local bynum = leap.request('LLStartUp', {op='getStateTable'})['table'] + +local byname = setmetatable( + {}, + -- set metatable to throw an error if you look up invalid state name + {__index=function(t, k) + local v = rawget(t, k) + if v then + return v + end + error(string.format('startup module passed invalid state %q', k), 2) + end}) + +-- derive byname as a lookup table to find the 0-based index for a given name +for i, name in pairs(bynum) do + -- the viewer's states are 0-based, not 1-based like Lua indexes + byname[name] = i - 1 +end +-- dbg('startup states: %s', inspect(byname)) + +-- specialize a WaitFor to track the viewer's startup state +local startup_pump = 'StartupState' +local waitfor = leap.WaitFor(0, startup_pump) +function waitfor:filter(pump, data) + if pump == self.name then + return data + end +end + +function waitfor:process(data) + -- keep updating startup._state for interested parties + startup._state = data.str + dbg('startup updating state to %q', data.str) + -- now pass data along to base-class method to queue + leap.WaitFor.process(self, data) +end + +-- listen for StartupState events +leap.request(leap.cmdpump(), + {op='listen', source=startup_pump, listener='startup.lua', tweak=true}) +-- poke LLStartUp to make sure we get an event +leap.send('LLStartUp', {op='postStartupState'}) + +-- --------------------------------------------------------------------------- +-- wait for response from postStartupState +while not startup._state do + dbg('startup.state() waiting for first StartupState event') + waitfor:wait() +end + +-- return a list of all known startup states +function startup.list() + return bynum +end + +-- report whether state with string name 'left' is before string name 'right' +function startup.before(left, right) + return byname[left] < byname[right] +end + +-- report the viewer's current startup state +function startup.state() + return startup._state +end + +-- error if script is called before specified state string name +function startup.ensure(state) + if startup.before(startup.state(), state) then + -- tell error() to pretend this error was thrown by our caller + error('must not be called before startup state ' .. state, 2) + end +end + +-- block calling fiber until viewer has reached state with specified string name +function startup.wait(state) + dbg('startup.wait(%q)', state) + while startup.before(startup.state(), state) do + local item = waitfor:wait() + dbg('startup.wait(%q) sees %s', state, item) + end +end + +return startup diff --git a/indra/newview/scripts/lua/require/timers.lua b/indra/newview/scripts/lua/require/timers.lua new file mode 100644 index 0000000000..ab1615ffbf --- /dev/null +++ b/indra/newview/scripts/lua/require/timers.lua @@ -0,0 +1,122 @@ +-- Access to the viewer's time-delay facilities + +local leap = require 'leap' +local util = require 'util' + +local timers = {} + +local function dbg(...) end +-- local dbg = require 'printf' + +timers.Timer = {} + +-- delay: time in seconds until callback +-- callback: 'wait', or function to call when timer fires (self:tick if nil) +-- iterate: if non-nil, call callback repeatedly until it returns non-nil +-- (ignored if 'wait') +function timers.Timer:new(delay, callback, iterate) + local obj = setmetatable({}, self) + self.__index = self + + if callback == 'wait' then + dbg('scheduleAfter(%d):', delay) + sequence = leap.generate('Timers', {op='scheduleAfter', after=delay}) + -- ignore the immediate return + dbg('scheduleAfter(%d) -> %s', delay, + sequence.next()) + -- this call is where we wait for real + dbg('next():') + dbg('next() -> %s', + sequence.next()) + sequence.done() + return + end + + callback = callback or function() obj:tick() end + + local calls = 0 + if iterate then + -- With iterative timers, beware of running a timer callback which + -- performs async actions lasting longer than the timer interval. The + -- lengthy callback suspends, allowing leap to retrieve the next + -- event, which is a timer tick. leap calls a new instance of the + -- callback, even though the previous callback call is still + -- suspended... etc. 'in_callback' defends against that recursive + -- case. Rather than re-enter the suspended callback, drop the + -- too-soon timer event. (We could count the too-soon timer events and + -- iterate calling the callback, but it's a bathtub problem: the + -- callback could end up getting farther and farther behind.) + local in_callback = false + obj.id = leap.eventstream( + 'Timers', + {op='scheduleEvery', every=delay}, + function (event) + local reqid = event.reqid + calls += 1 + if calls == 1 then + dbg('timer(%s) first callback', reqid) + -- discard the first (immediate) response: don't call callback + return nil + else + if in_callback then + dbg('dropping timer(%s) callback %d', reqid, calls) + else + dbg('timer(%s) callback %d', reqid, calls) + in_callback = true + local ret = callback(event) + in_callback = false + return ret + end + end + end + ).reqid + else -- (not iterate) + obj.id = leap.eventstream( + 'Timers', + {op='scheduleAfter', after=delay}, + function (event) + calls += 1 + -- Arrange to return nil the first time, true the second. This + -- callback is called immediately with the response to + -- 'scheduleAfter', and if we immediately returned true, we'd + -- be done, and the subsequent timer event would be discarded. + if calls == 1 then + -- Caller doesn't expect an immediate callback. + return nil + else + callback(event) + -- Since caller doesn't want to iterate, the value + -- returned by the callback is irrelevant: just stop after + -- this one and only call. + return true + end + end + ).reqid + end + + return obj +end + +util.classctor(timers.Timer) + +function timers.Timer:tick() + error('Pass a callback to Timer:new(), or override Timer:tick()') +end + +function timers.Timer:cancel() + local ok = leap.request('Timers', {op='cancel', id=self.id}).ok + leap.cancelreq(self.id) + return ok +end + +function timers.Timer:isRunning() + return leap.request('Timers', {op='isRunning', id=self.id}).running +end + +-- returns (true, seconds left) for a live timer, else (false, 0) +function timers.Timer:timeUntilCall() + local result = leap.request('Timers', {op='timeUntilCall', id=self.id}) + return result.ok, result.remaining +end + +return timers diff --git a/indra/newview/scripts/lua/require/util.lua b/indra/newview/scripts/lua/require/util.lua new file mode 100644 index 0000000000..40737a159a --- /dev/null +++ b/indra/newview/scripts/lua/require/util.lua @@ -0,0 +1,114 @@ +-- utility functions, in alpha order + +local util = {} + +-- Allow MyClass(ctor args...) equivalent to MyClass:new(ctor args...) +-- Usage: +-- local MyClass = {} +-- function MyClass:new(...) +-- ... +-- end +-- ... +-- util.classctor(MyClass) +-- or if your constructor is named something other than MyClass:new(), e.g. +-- MyClass:construct(): +-- util.classctor(MyClass, MyClass.construct) +-- return MyClass +function util.classctor(class, ctor) + -- set class's __call metamethod to the specified constructor function + -- (class.new if not specified) + util.setmetamethods{class, __call=(ctor or class.new)} +end + +-- check if array-like table contains certain value +function util.contains(t, v) + return table.find(t, v) ~= nil +end + +-- reliable count of the number of entries in table t +-- (since #t is unreliable) +function util.count(t) + local count = 0 + for _ in pairs(t) do + count += 1 + end + return count +end + +-- cheap test whether table t is empty +function util.empty(t) + return not next(t) +end + +-- recursive table equality +function util.equal(t1, t2) + if not (type(t1) == 'table' and type(t2) == 'table') then + return t1 == t2 + end + -- both t1 and t2 are tables: get modifiable copy of t2 + local temp = table.clone(t2) + for k, v in pairs(t1) do + -- if any key in t1 doesn't have same value in t2, not equal + if not util.equal(v, temp[k]) then + return false + end + -- temp[k] == t1[k], delete temp[k] + temp[k] = nil + end + -- All keys in t1 have equal values in t2; t2 == t1 if there are no extra keys in t2 + return util.empty(temp) +end + +-- Find or create the metatable for a specified table (a new empty table if +-- omitted), and to that metatable assign the specified keys. +-- Setting multiple keys at once is more efficient than a function to set only +-- one at a time, e.g. setametamethod(). +-- t = util.setmetamethods{__index=readfunc, __len=lenfunc} +-- returns a new table with specified metamethods __index, __len +-- util.setmetamethods{t, __call=action} +-- finds or creates the metatable for existing table t and sets __call +-- util.setmetamethods{table=t, __call=action} +-- same as util.setmetamethods{t, __call=action} +function util.setmetamethods(specs) + -- first determine the target table + assert(not (specs.table and specs[1]), + "Pass setmetamethods table either as positional or table=, not both") + local t = specs.table or specs[1] or {} + -- remove both ways of specifying table, leaving only the metamethods + specs.table = nil + specs[1] = nil + local mt = getmetatable(t) + if not mt then + -- t doesn't already have a metatable: just set specs + setmetatable(t, specs) + else + -- t already has a metatable: copy specs into it + local key, value + for key, value in pairs(specs) do + mt[key] = value + end + end + -- having set or enriched t's metatable, return t + return t +end + +-- On the passed module (i.e. table), set an __index metamethod such that +-- referencing module.submodule lazily requires(path/submodule). +-- The loaded submodule is cached in the module table so it need not be passed +-- to require() again. +-- 'path', like any require() string, can be relative to LuaRequirePath. +-- Returns the enriched module, permitting e.g. +-- mymod = util.submoduledir({}, 'mymod') +function util.submoduledir(module, path) + return util.setmetamethods{ + module, + __index=function(t, key) + local mod = require(`{path}/{key}`) + -- cache the submodule + t[key] = mod + return mod + end + } +end + +return util diff --git a/indra/newview/scripts/lua/test_LLAppearance.lua b/indra/newview/scripts/lua/test_LLAppearance.lua new file mode 100644 index 0000000000..a97ec4e0ca --- /dev/null +++ b/indra/newview/scripts/lua/test_LLAppearance.lua @@ -0,0 +1,114 @@ +local LLAppearance = require 'LLAppearance' +local startup = require 'startup' +local inspect = require 'inspect' +local UI = require 'UI' + +local SHOW_OUTFITS = true +local SELECTED_OUTFIT_ID = {} +local DATA_MAP = {} + +local wearables_lbl = 'Show wearables' +local outfits_lbl = 'Show outfits' +local replace_cof_lbl = 'Replace COF' +local add_cof_lbl = 'Add to COF' +local outfits_title = 'Outfits' +local wear_lbl = 'Wear item' +local detach_lbl = 'Detach item' + +local flt = UI.Floater( + "luafloater_outfits_list.xml", + {outfits_list = {"double_click"}}) + +function get_selected_id() + return flt:request({action="get_selected_id", ctrl_name='outfits_list'}).value +end + +function populate_list() + if SHOW_OUTFITS then + DATA_MAP = LLAppearance.getOutfitsList() + else + DATA_MAP = LLAppearance.getOutfitItems(SELECTED_OUTFIT_ID) + end + + local action_data = {} + action_data.action = "add_list_element" + action_data.ctrl_name = "outfits_list" + local outfits = {} + for uuid, info in pairs(DATA_MAP) do + name = {} + if SHOW_OUTFITS then + name = info + else + name = info.name + end + table.insert(outfits, {value = uuid, columns={column = "outfit_name", value = name}}) + end + action_data.value = outfits + flt:post(action_data) +end + +function set_label(btn_name, value) + flt:post({action="set_label", ctrl_name=btn_name, value=value}) +end + +function set_enabled(btn_name, value) + flt:post({action="set_enabled", ctrl_name=btn_name, value=value}) +end + +function update_labels() + if SHOW_OUTFITS then + set_label('select_btn', wearables_lbl) + set_label('replace_btn', replace_cof_lbl) + set_label('add_btn', add_cof_lbl) + + set_enabled('select_btn', false) + flt:post({action="set_title", value=outfits_title}) + else + set_label('select_btn', outfits_lbl) + set_label('replace_btn', wear_lbl) + set_label('add_btn', detach_lbl) + + set_enabled('select_btn', true) + flt:post({action="set_title", value=DATA_MAP[SELECTED_OUTFIT_ID]}) + end + + set_enabled('replace_btn', false) + set_enabled('add_btn', false) +end + +function flt:post_build(event_data) + populate_list() +end + +function flt:commit_replace_btn(event_data) + if SHOW_OUTFITS then + LLAppearance.wearOutfit(get_selected_id(), 'replace') + else + LLAppearance.wearItems(get_selected_id(), false) + end +end + +function flt:commit_add_btn(event_data) + if SHOW_OUTFITS then + LLAppearance.wearOutfit(get_selected_id(), 'add') + else + LLAppearance.detachItems(get_selected_id()) + end +end + +function flt:commit_select_btn(event_data) + SHOW_OUTFITS = not SHOW_OUTFITS + SELECTED_OUTFIT_ID = get_selected_id() + update_labels() + self:post({action="clear_list", ctrl_name='outfits_list'}) + populate_list() +end + +function flt:commit_outfits_list(event_data) + set_enabled('replace_btn', true) + set_enabled('add_btn', true) + set_enabled('select_btn', true) +end + +startup.wait('STATE_STARTED') +flt:show() diff --git a/indra/newview/scripts/lua/test_LLChat.lua b/indra/newview/scripts/lua/test_LLChat.lua new file mode 100644 index 0000000000..3abaf28e42 --- /dev/null +++ b/indra/newview/scripts/lua/test_LLChat.lua @@ -0,0 +1,18 @@ +LLChat = require 'LLChat' + +function generateRandomWord(length) + local alphabet = "abcdefghijklmnopqrstuvwxyz" + local wordTable = {} + for i = 1, length do + local randomIndex = math.random(1, #alphabet) + table.insert(wordTable, alphabet:sub(randomIndex, randomIndex)) + end + return table.concat(wordTable) +end + +local msg = {'AI says:'} +math.randomseed(os.time()) +for i = 1, math.random(1, 10) do + table.insert(msg, generateRandomWord(math.random(1, 8))) +end +LLChat.sendNearby(table.concat(msg, ' ')) diff --git a/indra/newview/scripts/lua/test_LLChatListener.lua b/indra/newview/scripts/lua/test_LLChatListener.lua new file mode 100644 index 0000000000..0f269b54e6 --- /dev/null +++ b/indra/newview/scripts/lua/test_LLChatListener.lua @@ -0,0 +1,39 @@ +local LLListener = require 'LLListener' +local LLChat = require 'LLChat' +local UI = require 'UI' + +-- Chat listener script allows to use the following commands in Nearby chat: +-- open inventory -- open defined floater by name +-- close inventory -- close defined floater by name +-- closeall -- close all floaters +-- stop -- close the script +-- any other messages will be echoed. +function openOrEcho(message) + local open_floater_name = string.match(message, "^open%s+(%w+)") + local close_floater_name = string.match(message, "^close%s+(%w+)") + if open_floater_name then + UI.showFloater(open_floater_name) + elseif close_floater_name then + UI.hideFloater(close_floater_name) + elseif message == 'closeall' then + UI.closeAllFloaters() + else + LLChat.sendNearby('Echo: ' .. message) + end +end + +local listener = LLListener(LLChat.nearbyChatPump) + +function listener:handleMessages(event_data) + if string.find(event_data.message, '[LUA]') then + return true + elseif event_data.message == 'stop' then + LLChat.sendNearby('Closing echo script.') + return false + else + openOrEcho(event_data.message) + end + return true +end + +listener:start() diff --git a/indra/newview/scripts/lua/test_LLFloaterAbout.lua b/indra/newview/scripts/lua/test_LLFloaterAbout.lua new file mode 100644 index 0000000000..6bbf61982d --- /dev/null +++ b/indra/newview/scripts/lua/test_LLFloaterAbout.lua @@ -0,0 +1,6 @@ +-- test LLFloaterAbout + +LLFloaterAbout = require('LLFloaterAbout') +inspect = require('inspect') + +print(inspect(LLFloaterAbout.getInfo())) diff --git a/indra/newview/scripts/lua/test_LLGesture.lua b/indra/newview/scripts/lua/test_LLGesture.lua new file mode 100644 index 0000000000..1cce674565 --- /dev/null +++ b/indra/newview/scripts/lua/test_LLGesture.lua @@ -0,0 +1,26 @@ +-- exercise LLGesture API + +LLGesture = require 'LLGesture' +inspect = require 'inspect' + + +-- getActiveGestures() returns {<UUID>: {name, playing, trigger}} +gestures_uuid = LLGesture.getActiveGestures() +-- convert to {<name>: <uuid>} +gestures = {} +for uuid, info in pairs(gestures_uuid) do + gestures[info.name] = uuid +end +-- now run through the list +for name, uuid in pairs(gestures) do + if name == 'afk' then + -- afk has a long timeout, and isn't interesting to look at + continue + end + print(name) + LLGesture.startGesture(uuid) + repeat + LL.sleep(1) + until not LLGesture.isGesturePlaying(uuid) +end +print('Done.') diff --git a/indra/newview/scripts/lua/test_LLInventory.lua b/indra/newview/scripts/lua/test_LLInventory.lua new file mode 100644 index 0000000000..de57484bcd --- /dev/null +++ b/indra/newview/scripts/lua/test_LLInventory.lua @@ -0,0 +1,24 @@ +inspect = require 'inspect' +LLInventory = require 'LLInventory' + +-- Get 'My Landmarks' folder id (you can see all folder types via LLInventory.getFolderTypeNames()) +my_landmarks_id = LLInventory.getBasicFolderID('landmark') +-- Get 3 landmarks from the 'My Landmarks' folder (you can see all folder types via LLInventory.getAssetTypeNames()) +landmarks = LLInventory.collectDescendentsIf{folder_id=my_landmarks_id, type="landmark", limit=3} +for _, landmark in pairs(landmarks.items) do + print(landmark.name) +end + +-- Get 'Calling Cards' folder id +calling_cards_id = LLInventory.getBasicFolderID('callcard') +-- Get all items located directly in 'Calling Cards' folder +calling_cards = LLInventory.getDirectDescendents(calling_cards_id).items + +-- Print a random calling card name from 'Calling Cards' folder +-- (because getDirectDescendents().items is a Lua result set, selecting +-- a random entry only fetches one slice containing that entry) +math.randomseed(os.time()) +for i = 1, 5 do + pick = math.random(#calling_cards) + print(`Random calling card (#{pick} of {#calling_cards}): {calling_cards[pick].name}`) +end diff --git a/indra/newview/scripts/lua/test_animation.lua b/indra/newview/scripts/lua/test_animation.lua new file mode 100644 index 0000000000..37e7254a6c --- /dev/null +++ b/indra/newview/scripts/lua/test_animation.lua @@ -0,0 +1,32 @@ +LLInventory = require 'LLInventory' +LLAgent = require 'LLAgent' + +-- Get 'Animations' folder id (you can see all folder types via LLInventory.getFolderTypeNames()) +animations_id = LLInventory.getBasicFolderID('animatn') +-- Get animations from the 'Animation' folder (you can see all folder types via LLInventory.getAssetTypeNames()) +anims = LLInventory.collectDescendentsIf{folder_id=animations_id, type="animatn"}.items + +local anim_ids = {} +for key in pairs(anims) do + table.insert(anim_ids, key) +end + +if #anim_ids == 0 then + print("No animations found") +else + -- Start playing a random animation + math.randomseed(os.time()) + local random_id = anim_ids[math.random(#anim_ids)] + local anim_info = LLAgent.getAnimationInfo(random_id) + + print("Starting animation locally: " .. anims[random_id].name) + print("Loop: " .. anim_info.is_loop .. " Joints: " .. anim_info.num_joints .. " Duration " .. tonumber(string.format("%.2f", anim_info.duration))) + LLAgent.playAnimation{item_id=random_id} + + -- Stop animation after 3 sec if it's looped or longer than 3 sec + if anim_info.is_loop == 1 or anim_info.duration > 3 then + LL.sleep(3) + print("Stop animation.") + LLAgent.stopAnimation(random_id) + end +end diff --git a/indra/newview/scripts/lua/test_atexit.lua b/indra/newview/scripts/lua/test_atexit.lua new file mode 100644 index 0000000000..6fbc0f3eb1 --- /dev/null +++ b/indra/newview/scripts/lua/test_atexit.lua @@ -0,0 +1,3 @@ +LL.atexit(function() print('Third') end) +LL.atexit(function() print('Second') end) +LL.atexit(function() print('First') end) diff --git a/indra/newview/scripts/lua/test_autopilot.lua b/indra/newview/scripts/lua/test_autopilot.lua new file mode 100644 index 0000000000..09c85c140a --- /dev/null +++ b/indra/newview/scripts/lua/test_autopilot.lua @@ -0,0 +1,22 @@ +local LLAgent = require 'LLAgent' +local LLListener = require 'LLListener' + +local pos = LLAgent.getGlobalPosition() +pos[1]+=10 -- delta x +pos[2]+=5 -- delta y +LLAgent.requestStand() +LLAgent.startAutoPilot{target_global=pos,allow_flying=false,stop_distance=1} + +local listener = LLListener(LLAgent.autoPilotPump) + +function listener:handleMessages(event_data) + if event_data.success then + print('Destination is reached') + LLAgent.requestSit() + else + print('Failed to reach destination') + end + return false +end + +listener:start() diff --git a/indra/newview/scripts/lua/test_callables.lua b/indra/newview/scripts/lua/test_callables.lua new file mode 100644 index 0000000000..1bee062db8 --- /dev/null +++ b/indra/newview/scripts/lua/test_callables.lua @@ -0,0 +1,6 @@ +startup=require 'startup' +UI=require 'UI' +startup.wait('STATE_LOGIN_WAIT') +for _, cbl in pairs(UI.callables()) do + print(`{cbl.name} ({cbl.access})`) +end diff --git a/indra/newview/scripts/lua/test_camera_control.lua b/indra/newview/scripts/lua/test_camera_control.lua new file mode 100644 index 0000000000..7ac0986ee6 --- /dev/null +++ b/indra/newview/scripts/lua/test_camera_control.lua @@ -0,0 +1,49 @@ +local LLAgent = require 'LLAgent' +local startup = require 'startup' +local UI = require 'UI' + +local flt = UI.Floater('luafloater_camera_control.xml') + +function getValue(ctrl_name) + return flt:request({action="get_value", ctrl_name=ctrl_name}).value +end + +function setValue(ctrl_name, value) + flt:post({action="set_value", ctrl_name=ctrl_name, value=value}) +end + +function flt:commit_update_btn(event_data) + lock_focus = getValue('lock_focus_ctrl') + lock_camera = getValue('lock_camera_ctrl') + local camera_pos = {getValue('cam_x'), getValue('cam_y'), getValue('cam_z')} + local focus_pos = {getValue('focus_x'), getValue('focus_y'), getValue('focus_z')} + + LLAgent.setCamera{camera_pos=camera_pos, focus_pos=focus_pos, + focus_locked=lock_focus, camera_locked=lock_camera} + + self:post({action="add_text", ctrl_name="events_editor", + value = {'Updating FollowCam params', 'camera_pos:', camera_pos, 'focus_pos:', focus_pos, + 'lock_focus:', lock_focus, 'lock_camera:', lock_camera}}) +end + +function flt:commit_agent_cam_btn(event_data) + agent_pos = LLAgent.getRegionPosition() + setValue('cam_x', math.floor(agent_pos[1])) + setValue('cam_y', math.floor(agent_pos[2])) + setValue('cam_z', math.floor(agent_pos[3])) +end + +function flt:commit_agent_focus_btn(event_data) + agent_pos = LLAgent.getRegionPosition() + setValue('focus_x', math.floor(agent_pos[1])) + setValue('focus_y', math.floor(agent_pos[2])) + setValue('focus_z', math.floor(agent_pos[3])) +end + +function flt:commit_reset_btn(event_data) + LLAgent.removeCamParams() + LLAgent.setFollowCamActive(false) +end + +startup.wait('STATE_STARTED') +flt:show() diff --git a/indra/newview/scripts/lua/test_flycam.lua b/indra/newview/scripts/lua/test_flycam.lua new file mode 100644 index 0000000000..05c3c37b93 --- /dev/null +++ b/indra/newview/scripts/lua/test_flycam.lua @@ -0,0 +1,38 @@ +-- Make camera fly around the subject avatar for a few seconds. + +local LLAgent = require 'LLAgent' +local startup = require 'startup' +local timers = require 'timers' + +local height = 2.0 -- meters +local radius = 4.0 -- meters +local speed = 1.0 -- meters/second along circle +local start = os.clock() +local stop = os.clock() + 30 -- seconds + +local function cameraPos(t) + local agent = LLAgent.getRegionPosition() + local radians = speed * t + return { + agent[1] + radius * math.cos(radians), + agent[2] + radius * math.sin(radians), + agent[3] + height + } +end + +local function moveCamera() + if os.clock() < stop then + -- usual case + LLAgent.setCamera{ camera_pos=cameraPos(os.clock() - start), camera_locked=true } + return nil + else + -- last time + LLAgent.removeCamParams() + LLAgent.setFollowCamActive(false) + return true + end +end + +startup.wait('STATE_STARTED') +-- call moveCamera() repeatedly until it returns true +local timer = timers.Timer(0.1, moveCamera, true) diff --git a/indra/newview/scripts/lua/test_group_chat.lua b/indra/newview/scripts/lua/test_group_chat.lua new file mode 100644 index 0000000000..373411c26c --- /dev/null +++ b/indra/newview/scripts/lua/test_group_chat.lua @@ -0,0 +1,14 @@ +LLChat = require 'LLChat' +LLAgent = require 'LLAgent' +UI = require 'UI' + +local GROUPS = LLAgent.getGroups() + +-- Choose one of the groups randomly and send group message +math.randomseed(os.time()) +group_info = GROUPS[math.random(#GROUPS)] +LLChat.startGroupChat(group_info.id) +response = UI.popup:alertYesCancel('Started group chat with ' .. group_info.name .. ' group. Send greetings?') +if response == 'OK' then + LLChat.sendGroupIM('Greetings', group_info.id) +end diff --git a/indra/newview/scripts/lua/test_inv_resultset.lua b/indra/newview/scripts/lua/test_inv_resultset.lua new file mode 100644 index 0000000000..c31cfe3c67 --- /dev/null +++ b/indra/newview/scripts/lua/test_inv_resultset.lua @@ -0,0 +1,18 @@ +local LLInventory = require 'LLInventory' +local inspect = require 'inspect' + +print('basic folders:') +print(inspect(LLInventory.getFolderTypeNames())) + +local folder = LLInventory.getBasicFolderID('my_otfts') +print(`folder = {folder}`) +local result = LLInventory.getDirectDescendants(folder) +print(`type(result) = {type(result)}`) +print(#result.categories, 'categories:') +for i, cat in pairs(result.categories) do + print(`{i}: {cat.name}`) +end +print(#result.items, 'items') +for i, item in pairs(result.items) do + print(`{i}: {item.name}`) +end diff --git a/indra/newview/scripts/lua/test_login.lua b/indra/newview/scripts/lua/test_login.lua new file mode 100644 index 0000000000..54d3635a41 --- /dev/null +++ b/indra/newview/scripts/lua/test_login.lua @@ -0,0 +1,10 @@ +inspect = require 'inspect' +login = require 'login' + +local grid = 'agni' +print(inspect(login.savedLogins(grid))) + +print(inspect(login.login{ + username='Your Username', + grid=grid + })) diff --git a/indra/newview/scripts/lua/test_logout.lua b/indra/newview/scripts/lua/test_logout.lua new file mode 100644 index 0000000000..b1ac59e38c --- /dev/null +++ b/indra/newview/scripts/lua/test_logout.lua @@ -0,0 +1,3 @@ +logout = require 'logout' + +logout() diff --git a/indra/newview/scripts/lua/test_luafloater_demo.lua b/indra/newview/scripts/lua/test_luafloater_demo.lua new file mode 100644 index 0000000000..2158134511 --- /dev/null +++ b/indra/newview/scripts/lua/test_luafloater_demo.lua @@ -0,0 +1,39 @@ +local leap = require 'leap' +local startup = require 'startup' +local UI = require 'UI' + +local flt = UI.Floater( + 'luafloater_demo.xml', + {show_time_lbl = {"right_mouse_down", "double_click"}}) + +-- override base-class handleEvents() to report the event data in the floater's display field +function flt:handleEvents(event_data) + self:post({action="add_text", ctrl_name="events_editor", value = event_data}) + -- forward the call to base-class handleEvents() + return UI.Floater.handleEvents(self, event_data) +end + +function flt:commit_disable_ctrl(event_data) + self:post({action="set_enabled", ctrl_name="open_btn", value = (1 - event_data.value)}) +end + +function flt:commit_title_cmb(event_data) + self:post({action="set_title", value=event_data.value}) +end + +function flt:commit_open_btn(event_data) + floater_name = self:request({action="get_value", ctrl_name='openfloater_cmd'}).value + leap.send("LLFloaterReg", {name = floater_name, op = "showInstance"}) +end + +local function getCurrentTime() + local currentTime = os.date("*t") + return string.format("%02d:%02d:%02d", currentTime.hour, currentTime.min, currentTime.sec) +end + +function flt:double_click_show_time_lbl(event_data) + self:post({action="set_value", ctrl_name="time_lbl", value=getCurrentTime()}) +end + +startup.wait('STATE_LOGIN_WAIT') +flt:show() diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua new file mode 100644 index 0000000000..5f929c0d0c --- /dev/null +++ b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua @@ -0,0 +1,27 @@ +local LLGesture = require 'LLGesture' +local startup = require 'startup' +local UI = require 'UI' + +local flt = UI.Floater( + "luafloater_gesture_list.xml", + {gesture_list = {"double_click"}}) + +function flt:post_build(event_data) + local gestures_uuid = LLGesture.getActiveGestures() + local action_data = {} + action_data.action = "add_list_element" + action_data.ctrl_name = "gesture_list" + local gestures = {} + for uuid, info in pairs(gestures_uuid) do + table.insert(gestures, {value = uuid, columns={column = "gesture_name", value = info.name}}) + end + action_data.value = gestures + self:post(action_data) +end + +function flt:double_click_gesture_list(event_data) + LLGesture.startGesture(event_data.value) +end + +startup.wait('STATE_STARTED') +flt:show() diff --git a/indra/newview/scripts/lua/test_luafloater_speedometer.lua b/indra/newview/scripts/lua/test_luafloater_speedometer.lua new file mode 100644 index 0000000000..2cdd41fd7e --- /dev/null +++ b/indra/newview/scripts/lua/test_luafloater_speedometer.lua @@ -0,0 +1,31 @@ +local leap = require 'leap' +local startup = require 'startup' +local Timer = (require 'timers').Timer +local UI = require 'UI' +local popup = UI.popup +local max_speed = 0 +local flt = UI.Floater("luafloater_speedometer.xml") +startup.wait('STATE_STARTED') + +local timer + +function flt:floater_close(event_data) + if timer then + timer:cancel() + end + popup:tip(string.format("Registered max speed: %.2f m/s", max_speed)) +end + +local function idle(event_data) + local speed = leap.request('LLVOAvatar', {op='getSpeed'})['value'] + flt:post({action="set_value", ctrl_name="speed_lbl", value = string.format("%.2f", speed)}) + max_speed=math.max(max_speed, speed) +end + +msg = 'Are you sure you want to run this "speedometer" script?' +response = popup:alertYesCancel(msg) + +if response == 'OK' then + flt:show() + timer = Timer(1, idle, true) -- iterate +end diff --git a/indra/newview/scripts/lua/test_mapargs.lua b/indra/newview/scripts/lua/test_mapargs.lua new file mode 100644 index 0000000000..999a57acb4 --- /dev/null +++ b/indra/newview/scripts/lua/test_mapargs.lua @@ -0,0 +1,68 @@ +local mapargs = require 'mapargs' +local inspect = require 'inspect' + +function tabfunc(...) + local a = mapargs({'a1', 'a2', 'a3'}, ...) + print(inspect(a)) +end + +print('----------') +print('f(10, 20, 30)') +tabfunc(10, 20, 30) +print('f(10, nil, 30)') +tabfunc(10, nil, 30) +print('f{10, 20, 30}') +tabfunc{10, 20, 30} +print('f{10, nil, 30}') +tabfunc{10, nil, 30} +print('f{a3=300, a1=100}') +tabfunc{a3=300, a1=100} +print('f{1, a3=3}') +tabfunc{1, a3=3} +print('f{a3=3, 1}') +tabfunc{a3=3, 1} +print('----------') + +if false then + -- the code below was used to explore ideas that became mapargs() + mixed = { '[1]', nil, '[3]', abc='[abc]', '[3]', def='[def]' } + local function showtable(desc, t) + print(string.format('%s (len %s)\n%s', desc, #t, inspect(t))) + end + showtable('mixed', mixed) + + print('ipairs(mixed)') + for k, v in ipairs(mixed) do + print(string.format('[%s] = %s', k, tostring(v))) + end + + print('table.pack(mixed)') + print(inspect(table.pack(mixed))) + + local function nilarg(desc, a, b, c) + print(desc) + print('a = ' .. tostring(a)) + print('b = ' .. tostring(b)) + print('c = ' .. tostring(c)) + end + + nilarg('nilarg(1)', 1) + nilarg('nilarg(1, nil, 3)', 1, nil, 3) + + local function nilargs(desc, ...) + args = table.pack(...) + showtable(desc, args) + end + + nilargs('nilargs{a=1, b=2, c=3}', {a=1, b=2, c=3}) + nilargs('nilargs(1, 2, 3)', 1, 2, 3) + nilargs('nilargs(1, nil, 3)', 1, nil, 3) + nilargs('nilargs{1, 2, 3}', {1, 2, 3}) + nilargs('nilargs{1, nil, 3}', {1, nil, 3}) + + print('table.unpack({1, nil, 3})') + a, b, c = table.unpack({1, nil, 3}) + print('a = ' .. tostring(a)) + print('b = ' .. tostring(b)) + print('c = ' .. tostring(c)) +end diff --git a/indra/newview/scripts/lua/test_popup.lua b/indra/newview/scripts/lua/test_popup.lua new file mode 100644 index 0000000000..7a11895669 --- /dev/null +++ b/indra/newview/scripts/lua/test_popup.lua @@ -0,0 +1,10 @@ +UI = require 'UI' +popup = UI.popup +startup = require 'startup' + +startup.wait('STATE_STARTED') + +response = popup:alert('This just has a Close button') +response = popup:alertOK(string.format('You said "%s", is that OK?', response)) +response = popup:alertYesCancel(string.format('You said "%s"', response)) +popup:tip(string.format('You said "%s"', response)) diff --git a/indra/newview/scripts/lua/test_result_view.lua b/indra/newview/scripts/lua/test_result_view.lua new file mode 100644 index 0000000000..304633a472 --- /dev/null +++ b/indra/newview/scripts/lua/test_result_view.lua @@ -0,0 +1,55 @@ +-- Verify the functionality of result_view. +result_view = require 'result_view' + +print('alphabet') +alphabet = "abcdefghijklmnopqrstuvwxyz" +assert(#alphabet == 26) +alphabits = string.split(alphabet, '') + +print('function slice()') +function slice(t, index, count) + return table.move(t, index, index + count - 1, 1, {}) +end + +print('verify slice()') +-- verify that slice() does what we expect +assert(table.concat(slice(alphabits, 4, 3)) == "def") +assert(table.concat(slice(alphabits, 14, 3)) == "nop") +assert(table.concat(slice(alphabits, 25, 3)) == "yz") + +print('function fetch()') +function fetch(key, index) + -- fetch function is defined to be 0-relative: fix for Lua data + -- constrain view of alphabits to slices of at most 3 elements + return slice(alphabits, index+1, 3), index +end + +print('result_view()') +-- for test purposes, key is irrelevant, so just 'key' +view = result_view({'key', #alphabits}, fetch) + +print('function check_iter()') +function check_iter(...) + result = {} + for k, v in ... do + table.insert(result, v) + end + assert(table.concat(result) == alphabet) +end + +print('check_iter(pairs(view))') +check_iter(pairs(view)) +print('check_iter(ipairs(view))') +check_iter(ipairs(view)) +print('check_iter(view)') +check_iter(view) + +print('raw index access') +assert(view[5] == 'e') +assert(view[10] == 'j') +assert(view[15] == 'o') +assert(view[20] == 't') +assert(view[25] == 'y') + +print('Success!') + diff --git a/indra/newview/scripts/lua/test_setdtor.lua b/indra/newview/scripts/lua/test_setdtor.lua new file mode 100644 index 0000000000..ec5cd47e93 --- /dev/null +++ b/indra/newview/scripts/lua/test_setdtor.lua @@ -0,0 +1,91 @@ +inspect = require 'inspect' + +print('initial setdtor') +bye = LL.setdtor('initial setdtor', 'Goodbye world!', print) + +print('arithmetic') +n = LL.setdtor('arithmetic', 11, print) +print("n =", n) +print("n._target =", n._target) +print(pcall(function() n._target = 12 end)) +print("getmetatable(n) =", inspect(getmetatable(n))) +print("-n =", -n) +for i = 10, 12 do + -- Comparison metamethods are only called if both operands have the same + -- metamethod. + tempi = LL.setdtor('tempi', i, function(n) print('temp', i) end) + print(`n < {i}`, n < tempi) + print(`n <= {i}`, n <= tempi) + print(`n == {i}`, n == tempi) + print(`n ~= {i}`, n ~= tempi) + print(`n >= {i}`, n >= tempi) + print(`n > {i}`, n > tempi) +end +i = 2 +print(`n + {i} =`, n + i) +print(`{i} + n =`, i + n) +print(`n - {i} =`, n - i) +print(`{i} - n =`, i - n) +print(`n * {i} =`, n * i) +print(`{i} * n =`, i * n) +print(`n / {i} =`, n / i) +print(`{i} / n =`, i / n) +print(`n // {i} =`, n // i) +print(`{i} // n =`, i // n) +print(`n % {i} =`, n % i) +print(`{i} % n =`, i % n) +print(`n ^ {i} =`, n ^ i) +print(`{i} ^ n =`, i ^ n) + +print('string') +s = LL.setdtor('string', 'hello', print) +print('s =', s) +print('#s =', #s) +print('s .. " world" =', s .. " world") +print('"world " .. s =', "world " .. s) + +print('table') +t = LL.setdtor('table', {'[1]', '[2]', abc='.abc', def='.def'}, + function(t) print(inspect(t)) end) +print('t =', inspect(t)) +print('t._target =', inspect(t._target)) +print('#t =', #t) +print('next(t) =', next(t)) +print('next(t, 1) =', next(t, 1)) +print('t[2] =', t[2]) +print('t.def =', t.def) +t[1] = 'new [1]' +print('t[1] =', t[1]) +print('for k, v in pairs(t) do') +for k, v in pairs(t) do + print(`{k}: {v}`) +end +print('for k, v in ipairs(t) do') +for k, v in ipairs(t) do + print(`{k}: {v}`) +end +print('for k, v in t do') +for k, v in t do + print(`{k}: {v}`) +end +-- and now for something completely different +setmetatable( + t._target, + { + __iter = function(arg) + return next, {'alternate', '__iter'} + end + } +) +print('for k, v in t with __iter() metamethod do') +for k, v in t do + print(`{k}: {v}`) +end + +print('function') +f = LL.setdtor('function', function(a, b) return (a .. b) end, print) +print('f =', f) +print('f._target =', f._target) +print('f("Hello", " world") =', f("Hello", " world")) + +print('cleanup') diff --git a/indra/newview/scripts/lua/test_snapshot.lua b/indra/newview/scripts/lua/test_snapshot.lua new file mode 100644 index 0000000000..d7c878833b --- /dev/null +++ b/indra/newview/scripts/lua/test_snapshot.lua @@ -0,0 +1,15 @@ +local UI = require 'UI' + +PATH = 'E:\\' +-- 'png', 'jpeg' or 'bmp' +EXT = '.png' + +NAME_SIMPLE = 'Snapshot_simple' .. '_' .. os.date("%Y-%m-%d_%H-%M-%S") +UI.snapshot(PATH .. NAME_SIMPLE .. EXT) + +NAME_ARGS = 'Snapshot_args' .. '_' .. os.date("%Y-%m-%d_%H-%M-%S") + +-- 'COLOR' or 'DEPTH' +TYPE = 'COLOR' +UI.snapshot{PATH .. NAME_ARGS .. EXT, width = 700, height = 400, + type = TYPE, showui = false, showhud = false} diff --git a/indra/newview/scripts/lua/test_timers.lua b/indra/newview/scripts/lua/test_timers.lua new file mode 100644 index 0000000000..be5001aa16 --- /dev/null +++ b/indra/newview/scripts/lua/test_timers.lua @@ -0,0 +1,63 @@ +local timers = require 'timers' + +-- This t0 is constructed for 10 seconds, but its purpose is to exercise the +-- query and cancel methods. It would print "t0 fired at..." if it fired, but +-- it doesn't, so you don't see that message. Instead you see that isRunning() +-- is true, that timeUntilCall() is (true, close to 10), that cancel() returns +-- true. After that, isRunning() is false, timeUntilCall() returns (false, 0), +-- and a second cancel() returns false. +print('t0(10)') +start = os.clock() +t0 = timers.Timer(10, function() print('t0 fired at', os.clock() - start) end) +print('t0:isRunning(): ', t0:isRunning()) +print('t0:timeUntilCall(): ', t0:timeUntilCall()) +print('t0:cancel(): ', t0:cancel()) +print('t0:isRunning(): ', t0:isRunning()) +print('t0:timeUntilCall(): ', t0:timeUntilCall()) +print('t0:cancel(): ', t0:cancel()) + +-- t1 is supposed to fire after 5 seconds, but it doesn't wait, so you see the +-- t2 messages immediately after. +print('t1(5)') +start = os.clock() +t1 = timers.Timer(5, function() print('t1 fired at', os.clock() - start) end) + +-- t2 illustrates that instead of passing a callback to new(), you can +-- override the timer instance's tick() method. But t2 doesn't wait either, so +-- you see the Timer(5) message immediately. +print('t2(2)') +start = os.clock() +t2 = timers.Timer(2) +function t2:tick() + print('t2 fired at', os.clock() - start) +end + +-- This anonymous timer blocks the calling fiber for 5 seconds. Other fibers +-- are free to run during that time, so you see the t2 callback message and +-- then the t1 callback message before the Timer(5) completion message. +print('Timer(5) waiting') +start = os.clock() +timers.Timer(5, 'wait') +print(string.format('Timer(5) waited %f seconds', os.clock() - start)) + +-- This test demonstrates a repeating timer. It also shows that you can (but +-- need not) use a coroutine as the timer's callback function: unlike Python, +-- Lua doesn't disinguish between yield() and return. A coroutine wrapped with +-- coroutine.wrap() looks to Lua just like any other function that you can +-- call repeatedly and get a result each time. We use that to count the +-- callback calls and stop after a certain number. Of course that could also +-- be arranged in a plain function by incrementing a script-scope counter, but +-- it's worth knowing that a coroutine timer callback can be used to manage +-- more complex control flows. +start = os.clock() +timers.Timer( + 2, + coroutine.wrap(function() + for i = 1,5 do + print('repeat(2) timer fired at ', os.clock() - start) + coroutine.yield(nil) -- keep running + end + print('repeat(2) timer fired last at ', os.clock() - start) + return true -- stop + end), + true) -- iterate diff --git a/indra/newview/scripts/lua/test_toolbars.lua b/indra/newview/scripts/lua/test_toolbars.lua new file mode 100644 index 0000000000..7683fca8a3 --- /dev/null +++ b/indra/newview/scripts/lua/test_toolbars.lua @@ -0,0 +1,26 @@ +UI = require 'UI' + +local BUTTONS = UI.getToolbarBtnNames() +local TOOLBARS = {'left','right','bottom'} + +-- Clear the toolbars and then add the toolbar buttons to the random toolbar +response = UI.popup:alertYesCancel('Toolbars will be randomly reshuffled. Proceed?') +if response == 'OK' then + UI.clearAllToolbars() + math.randomseed(os.time()) + + -- add the buttons to the random toolbar + for i = 1, #BUTTONS do + UI.addToolbarBtn(BUTTONS[i], TOOLBARS[math.random(3)]) + end + + -- remove some of the added buttons from the toolbars + for i = 1, #BUTTONS do + if math.random(100) < 30 then + UI.removeToolbarBtn(BUTTONS[i]) + end + end + popup:tip('Toolbars were reshuffled') +else + popup:tip('Canceled') +end diff --git a/indra/newview/scripts/lua/test_top_menu.lua b/indra/newview/scripts/lua/test_top_menu.lua new file mode 100644 index 0000000000..f877cda5eb --- /dev/null +++ b/indra/newview/scripts/lua/test_top_menu.lua @@ -0,0 +1,52 @@ +UI = require 'UI' + +--Add new drop-down 'LUA Menu' to the Top menu. +local MENU_NAME = "lua_menu" +UI.addMenu{name=MENU_NAME,label="LUA Menu"} + +--Add two new menu items to the 'LUA Menu': 'Debug console' and 'Scripts' +UI.addMenuItem{name="lua_debug",label="Debug console", + param="lua_debug", + func="Floater.ToggleOrBringToFront", + parent_menu=MENU_NAME} + +UI.addMenuItem{name="lua_scripts",label="Scripts", + param="lua_scripts", + func="Floater.ToggleOrBringToFront", + parent_menu=MENU_NAME} + +--Add menu separator to the 'LUA Menu' under added menu items +UI.addMenuSeparator{parent_menu=MENU_NAME} + +--Add 'Demo scripts...' branch to the 'LUA Menu' +local DEMO_BRANCH = "demo_scripts" +UI.addMenuBranch{name=DEMO_BRANCH,label="Demo scripts...",parent_menu=MENU_NAME} + +--Add menu items to the 'Demo scripts...' branch, which will invoke specified script on click +UI.addMenuItem{name="speedometer",label="Speedometer", + param="test_luafloater_speedometer.lua", + func="Lua.RunScript", + parent_menu=DEMO_BRANCH} + +UI.addMenuItem{name="gesture_list",label="Gesture list", + param="test_luafloater_gesture_list.lua", + func="Lua.RunScript", + parent_menu=DEMO_BRANCH} + +--Add one more menu separator +UI.addMenuSeparator{parent_menu=MENU_NAME} + +--Add 'About...' branch to the 'LUA Menu' +local ABOUT_BRANCH = "about_branch" +UI.addMenuBranch{name=ABOUT_BRANCH,label="About...",parent_menu=MENU_NAME} + +--Add two new menu items to the 'About...' branch +UI.addMenuItem{name="lua_info",label="Lua...", + param="https://www.lua.org/about.html", + func="Advanced.ShowURL", + parent_menu=ABOUT_BRANCH} + +UI.addMenuItem{name="lua_info",label="Luau...", + param="https://luau-lang.org/", + func="Advanced.ShowURL", + parent_menu=ABOUT_BRANCH} diff --git a/indra/newview/scripts/lua/testmod.lua b/indra/newview/scripts/lua/testmod.lua new file mode 100644 index 0000000000..60f7f80db1 --- /dev/null +++ b/indra/newview/scripts/lua/testmod.lua @@ -0,0 +1,2 @@ +print('loaded scripts/lua/testmod.lua') +return function () return 'hello from scripts/lua/testmod.lua' end diff --git a/indra/newview/skins/default/xui/de/panel_snapshot_inventory.xml b/indra/newview/skins/default/xui/de/panel_snapshot_inventory.xml index 602424821f..09447cbbaf 100644 --- a/indra/newview/skins/default/xui/de/panel_snapshot_inventory.xml +++ b/indra/newview/skins/default/xui/de/panel_snapshot_inventory.xml @@ -7,7 +7,7 @@ <combo_box.item label="Klein (128x128)" name="Small(128x128)"/> <combo_box.item label="Mittel (256x256)" name="Medium(256x256)"/> <combo_box.item label="Groß (512x512)" name="Large(512x512)"/> - <combo_box.item label="Aktuelles Fenster (512x512)" name="CurrentWindow"/> + <combo_box.item label="Aktuelles Fenster" name="CurrentWindow"/> <combo_box.item label="Benutzerdefiniert" name="Custom"/> </combo_box> <spinner label="Breite x Höhe" name="inventory_snapshot_width"/> diff --git a/indra/newview/skins/default/xui/de/panel_snapshot_options.xml b/indra/newview/skins/default/xui/de/panel_snapshot_options.xml index dab20d63eb..2a51f10894 100644 --- a/indra/newview/skins/default/xui/de/panel_snapshot_options.xml +++ b/indra/newview/skins/default/xui/de/panel_snapshot_options.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="panel_snapshot_options"> <button label="Auf Datenträger speichern" name="save_to_computer_btn"/> - <button label="In Inventar speichern ([AMOUNT] L$)" name="save_to_inventory_btn"/> + <button label="In Inventar speichern" name="save_to_inventory_btn"/> <button label="Im Profil-Feed teilen" name="save_to_profile_btn"/> <button label="Auf Facebook teilen" name="send_to_facebook_btn"/> <button label="Auf Twitter teilen" name="send_to_twitter_btn"/> diff --git a/indra/newview/skins/default/xui/de/strings.xml b/indra/newview/skins/default/xui/de/strings.xml index a9e7626dc5..486d604e9f 100644 --- a/indra/newview/skins/default/xui/de/strings.xml +++ b/indra/newview/skins/default/xui/de/strings.xml @@ -31,7 +31,6 @@ Sichtweite: [DRAW_DISTANCE] m Bandbreite: [NET_BANDWITH] kbit/s LOD-Faktor: [LOD_FACTOR] Darstellungsqualität: [RENDER_QUALITY] -Erweitertes Beleuchtungsmodell: [GPU_SHADERS] Texturspeicher: [TEXTURE_MEMORY] MB</string> <string name="AboutOSXHiDPI">HiDPI-Anzeigemodus: [HIDPI]</string> <string name="AboutLibs">J2C-Decoderversion: [J2C_VERSION] diff --git a/indra/newview/skins/default/xui/en/floater_emoji_picker.xml b/indra/newview/skins/default/xui/en/floater_emoji_picker.xml index e4b8f13df7..f642ca93b7 100644 --- a/indra/newview/skins/default/xui/en/floater_emoji_picker.xml +++ b/indra/newview/skins/default/xui/en/floater_emoji_picker.xml @@ -13,7 +13,6 @@ chrome="true" height="350" width="304"> - <floater.string name="title_for_recently_used" value="Recently used"/> <floater.string name="title_for_frequently_used" value="Frequently used"/> <floater.string name="text_no_emoji_for_filter" value="No emoji found for '[FILTER]'"/> <scroll_container diff --git a/indra/newview/skins/default/xui/en/floater_fast_timers.xml b/indra/newview/skins/default/xui/en/floater_fast_timers.xml index 00411ba20b..ae907bcb5f 100644 --- a/indra/newview/skins/default/xui/en/floater_fast_timers.xml +++ b/indra/newview/skins/default/xui/en/floater_fast_timers.xml @@ -75,7 +75,7 @@ step_size="16" doc_pos="0" doc_size="3000" - page_size="0" + page_size="50" /> </layout_panel> <layout_panel name="timers_panel" diff --git a/indra/newview/skins/default/xui/en/floater_gltf_asset_editor.xml b/indra/newview/skins/default/xui/en/floater_gltf_asset_editor.xml index b17d0aa5b6..025d537804 100644 --- a/indra/newview/skins/default/xui/en/floater_gltf_asset_editor.xml +++ b/indra/newview/skins/default/xui/en/floater_gltf_asset_editor.xml @@ -11,10 +11,10 @@ name="gltf asset editor" title="[OBJECT_NAME]"> <floater.string name="floater_title" value="GLTF Scene Editor"/> - <floater.string name="scene_tittle" value="Scene"/> - <floater.string name="node_tittle" value="Node"/> - <floater.string name="mesh_tittle" value="Mesh"/> - <floater.string name="skin_tittle" value="Skin"/> + <floater.string name="scene_title" value="Scene"/> + <floater.string name="node_title" value="Node"/> + <floater.string name="mesh_title" value="Mesh"/> + <floater.string name="skin_title" value="Skin"/> <layout_stack name="main_layout" diff --git a/indra/newview/skins/default/xui/en/floater_lua_debug.xml b/indra/newview/skins/default/xui/en/floater_lua_debug.xml new file mode 100644 index 0000000000..15027f1647 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_lua_debug.xml @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + can_minimize="false" + can_resize="true" + can_close="true" + bevel_style="in" + height="220" + layout="topleft" + name="LUA debug" + save_rect="true" + title="LUA DEBUG" + single_instance="true" + width="535"> + <text + type="string" + length="1" + follows="left|top" + font="SansSerif" + height="30" + layout="topleft" + left="10" + left_delta="10" + name="editor_path_label" + top="10" + width="100"> + LUA string: + </text> + <line_editor + border_style="line" + border_thickness="1" + follows="left|top|right" + font="SansSerif" + height="20" + layout="topleft" + left="10" + max_length_bytes="300" + name="lua_cmd" + select_on_focus="true" + top_delta="30" + width="435" /> + <button + follows="right|top" + height="25" + label="Execute" + layout="topleft" + left_pad="5" + name="execute_btn" + top_delta="-2" + width="75" /> + + <text_editor + enabled="false" + left="10" + height="95" + layout="topleft" + name="result_text" + follows="all" + max_length="65536" + width="515" + top_delta="40" + word_wrap="true" /> + <text + type="string" + length="1" + follows="left|bottom" + font="SansSerif" + height="30" + layout="topleft" + left="10" + name="path_label" + top_pad="15" + width="100"> + File Path: + </text> + <line_editor + border_style="line" + enabled="false" + border_thickness="1" + follows="left|bottom|right" + font="SansSerif" + height="20" + layout="topleft" + left_delta="65" + max_length_bytes="300" + name="script_path" + select_on_focus="true" + top_delta="-2" + width="320" /> + <button + follows="right|bottom" + height="25" + label="Browse..." + label_selected="Browse..." + layout="topleft" + left_pad="5" + name="browse_btn" + top_delta="-2" + width="70" /> + <button + follows="right|bottom" + height="25" + label="Run" + label_selected="Run" + layout="topleft" + left_pad="5" + name="run_btn" + width="50" /> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_lua_scripts.xml b/indra/newview/skins/default/xui/en/floater_lua_scripts.xml new file mode 100644 index 0000000000..6859201650 --- /dev/null +++ b/indra/newview/skins/default/xui/en/floater_lua_scripts.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<floater + can_minimize="false" + can_resize="true" + can_close="true" + bevel_style="in" + height="220" + min_height="220" + layout="topleft" + name="LUA scripts" + save_rect="true" + title="LUA Scripts" + single_instance="true" + width="555" + min_width="555"> + <scroll_list + column_padding="0" + draw_stripes="true" + draw_heading="true" + height="200" + left="10" + follows="all" + layout="topleft" + sort_column="script_name" + name="scripts_list" + top_pad="10" + width="535"> + <scroll_list.columns + label="Name" + name="script_name" + width="180" /> + <scroll_list.columns + label="Path" + name="script_path"/> + </scroll_list> +</floater> diff --git a/indra/newview/skins/default/xui/en/floater_post_process.xml b/indra/newview/skins/default/xui/en/floater_post_process.xml deleted file mode 100644 index 37339f79c8..0000000000 --- a/indra/newview/skins/default/xui/en/floater_post_process.xml +++ /dev/null @@ -1,426 +0,0 @@ -<?xml version="1.0" encoding="utf-8" standalone="yes"?> -<floater - legacy_header_height="18" - height="400" - layout="topleft" - name="Post-Process Floater" - help_topic="post_process_floater" - title="POST-PROCESS SETTINGS" - width="400"> - <tab_container - follows="left|top" - height="400" - layout="topleft" - left="0" - name="Post-Process Tabs" - tab_position="top" - top="0" - width="400"> - <panel - border="true" - follows="left|top|right|bottom" - height="400" - label="Color Filter" - layout="topleft" - left="1" - mouse_opaque="false" - help_topic="post_process_color_filter_tab" - name="wmiColorFilterPanel" - top="0" - width="398"> - <check_box - control_name="wmiColorFilterToggle" - height="16" - label="Enable" - layout="topleft" - left="14" - name="wmiColorFilterToggle" - top="4" - width="200" /> - <text - type="string" - length="1" - follows="left|top|right" - font="SansSerif" - height="16" - layout="topleft" - left_delta="-4" - name="wmiColorFilterBrightnessText" - top_pad="4" - width="355"> - Brightness - </text> - <slider - control_name="wmiColorFilterBrightness" - decimal_digits="2" - follows="left" - height="10" - increment="0.01" - initial_value="1.0" - layout="topleft" - left_delta="4" - max_val="4" - name="wmiColorFilterBrightness" - top_pad="20" - width="200" /> - <text - type="string" - length="1" - follows="left|top|right" - font="SansSerif" - height="16" - layout="topleft" - left_delta="-4" - name="wmiColorFilterSaturationText" - top_pad="4" - width="355"> - Saturation - </text> - <slider - control_name="wmiColorFilterSaturation" - decimal_digits="2" - follows="left" - height="10" - increment="0.01" - initial_value="1.0" - layout="topleft" - left_delta="4" - max_val="2" - min_val="-1" - name="wmiColorFilterSaturation" - top_pad="20" - width="200" /> - <text - type="string" - length="1" - follows="left|top|right" - font="SansSerif" - height="16" - layout="topleft" - left_delta="-4" - name="wmiColorFilterContrastText" - top_pad="4" - width="355"> - Contrast - </text> - <slider - control_name="wmiColorFilterContrast" - decimal_digits="2" - follows="left" - height="10" - increment="0.01" - initial_value="1.0" - layout="topleft" - left_delta="4" - max_val="4" - name="wmiColorFilterContrast" - top_pad="20" - width="200" /> - <text - type="string" - length="1" - follows="left|top|right" - font="SansSerif" - height="16" - layout="topleft" - left_delta="-4" - name="wmiColorFilterBaseText" - top_pad="4" - width="355"> - Contrast Base Color - </text> - <slider - control_name="wmiColorFilterBaseR" - follows="left" - height="10" - increment="0.01" - initial_value="1.0" - label="R" - layout="topleft" - left_delta="4" - name="wmiColorFilterBaseR" - top_pad="20" - width="200" /> - <slider - control_name="wmiColorFilterBaseG" - follows="left" - height="10" - increment="0.01" - initial_value="1.0" - label="G" - layout="topleft" - left_delta="0" - name="wmiColorFilterBaseG" - top_pad="10" - width="200" /> - <slider - control_name="wmiColorFilterBaseB" - follows="left" - height="10" - increment="0.01" - initial_value="1.0" - label="B" - layout="topleft" - left_delta="0" - name="wmiColorFilterBaseB" - top_pad="10" - width="200" /> - <slider - control_name="wmiColorFilterBaseI" - follows="left" - height="10" - increment="0.01" - initial_value="0.5" - label="I" - layout="topleft" - left_delta="0" - name="wmiColorFilterBaseI" - top_pad="10" - width="200" /> - </panel> - <panel - border="true" - follows="left|top|right|bottom" - height="400" - label="Night Vision" - layout="topleft" - left_delta="0" - mouse_opaque="false" - help_topic="post_process_night_vision_tab" - name="wmiNightVisionPanel" - top_delta="-236" - width="398"> - <check_box - control_name="wmiNightVisionToggle" - height="16" - label="Enable" - layout="topleft" - left="14" - name="wmiNightVisionToggle" - top="4" - width="200" /> - <text - type="string" - length="1" - follows="left|top|right" - font="SansSerif" - height="16" - layout="topleft" - left_delta="-4" - name="wmiNightVisionBrightMultText" - top_pad="5" - width="355"> - Light Amplification Multiple - </text> - <slider - control_name="wmiNightVisionBrightMult" - follows="left" - height="10" - increment="0.01" - initial_value="3.0" - layout="topleft" - left_delta="4" - max_val="10" - min_val="1" - name="wmiNightVisionBrightMult" - top_pad="20" - width="200" /> - <text - type="string" - length="1" - follows="left|top|right" - font="SansSerif" - height="16" - layout="topleft" - left_delta="-4" - name="wmiNightVisionNoiseSizeText" - top_pad="4" - width="355"> - Noise Size - </text> - <slider - control_name="wmiNightVisionNoiseSize" - follows="left" - height="10" - initial_value="1" - layout="topleft" - left_delta="4" - max_val="100" - min_val="1" - name="wmiNightVisionNoiseSize" - top_pad="20" - width="200" /> - <text - type="string" - length="1" - follows="left|top|right" - font="SansSerif" - height="16" - layout="topleft" - left_delta="-4" - name="wmiNightVisionNoiseStrengthText" - top_pad="4" - width="355"> - Noise Strength - </text> - <slider - control_name="wmiNightVisionNoiseStrength" - follows="left" - height="10" - increment="0.01" - initial_value="0.3" - layout="topleft" - left_delta="4" - name="wmiNightVisionNoiseStrength" - top_pad="20" - width="200" /> - </panel> - <panel - border="true" - follows="left|top|right|bottom" - height="400" - label="Bloom" - layout="topleft" - left_delta="0" - help_topic="post_process_bloom_tab" - name="wmiBloomPanel" - top_delta="-236" - width="398"> - <check_box - control_name="wmiBloomToggle" - height="16" - label="Enable" - layout="topleft" - left="14" - name="wmiBloomToggle" - top="4" - width="200" /> - <text - type="string" - length="1" - follows="left|top|right" - font="SansSerif" - height="16" - layout="topleft" - left_delta="-4" - name="wmiBloomExtractText" - top_pad="5" - width="355"> - Luminosity Extraction - </text> - <slider - control_name="wmiBloomExtract" - follows="left" - height="10" - increment="0.01" - initial_value="0.9" - layout="topleft" - left_delta="4" - name="wmiBloomExtract" - top_pad="20" - width="200" /> - <text - type="string" - length="1" - follows="left|top|right" - font="SansSerif" - height="16" - layout="topleft" - left_delta="-4" - name="wmiBloomSizeText" - top_pad="4" - width="355"> - Bloom Size - </text> - <slider - control_name="wmiBloomSize" - follows="left" - height="10" - increment="0.01" - initial_value="3.0" - layout="topleft" - left_delta="4" - max_val="20" - name="wmiBloomSize" - top_pad="20" - width="200" /> - <text - type="string" - length="1" - follows="left|top|right" - font="SansSerif" - height="16" - layout="topleft" - left_delta="-4" - name="wmiBloomStrengthText" - top_pad="4" - width="355"> - Bloom Strength - </text> - <slider - control_name="wmiBloomStrength" - follows="left" - height="10" - increment="0.01" - initial_value="1.2" - layout="topleft" - left_delta="4" - max_val="10" - name="wmiBloomStrength" - top_pad="20" - width="200" /> - </panel> - <panel - border="true" - follows="left|top|right|bottom" - height="400" - label="Extras" - layout="topleft" - left_delta="0" - mouse_opaque="false" - help_topic="post_process_extras_tab" - name="Extras" - top_delta="-236" - width="398"> - <button - height="20" - label="LoadEffect" - label_selected="LoadEffect" - layout="topleft" - left="15" - name="PPLoadEffect" - top="13" - width="100" /> - <button - height="20" - label="SaveEffect" - label_selected="SaveEffect" - layout="topleft" - left_delta="0" - name="PPSaveEffect" - top_pad="7" - width="100" /> - <combo_box - height="18" - layout="topleft" - left_delta="120" - name="PPEffectsCombo" - top="15" - width="150" /> - <line_editor - border_style="line" - border_thickness="1" - follows="left|right|bottom" - font="SansSerif" - height="20" - label="Effect Name" - layout="topleft" - left_delta="0" - max_length_bytes="40" - name="PPEffectNameEditor" - tab_group="1" - top_pad="22" - width="150" /> - </panel> - </tab_container> -</floater> diff --git a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml index 1deb81175e..73ea0f548e 100644 --- a/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml +++ b/indra/newview/skins/default/xui/en/floater_preferences_graphics_advanced.xml @@ -222,7 +222,7 @@ height="16" increment="1" initial_value="12" - label="Max. # of non-impostors:" + label="Max. # animated avatars:" label_width="185" layout="topleft" left="30" @@ -943,6 +943,57 @@ width="260"> </slider> <!-- End of Sharpening Settings--> + <!-- Tone Mapping Settings --> + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="420" + name="TonemapTypeText" + text_readonly_color="LabelDisabledColor" + top_delta="25" + width="128"> + Tone Mapper: + </text> + <combo_box + control_name="RenderTonemapType" + height="18" + layout="topleft" + left_delta="130" + top_delta="0" + name="TonemapType" + width="150"> + <combo_box.item + label="Khronos Neutral" + name="0" + value="0"/> + <combo_box.item + label="ACES" + name="1" + value="1"/> + </combo_box> + <slider + control_name="RenderTonemapMix" + decimal_digits="1" + follows="left|top" + height="16" + increment="0.1" + initial_value="1" + label="Tone Mapping Mix:" + tool_tip="Mix between linear and tone mapped colors" + label_width="145" + layout="topleft" + left="420" + min_val="0.0" + max_val="1.0" + name="TonemapMix" + show_text="true" + top_delta="22" + width="260"> + </slider> + <!-- End of Tone Mapping Settings--> <!-- End of Advanced Settings block --> <view_border bevel_style="in" diff --git a/indra/newview/skins/default/xui/en/floater_settings_debug.xml b/indra/newview/skins/default/xui/en/floater_settings_debug.xml index a93be6a18d..0b8190df7e 100644 --- a/indra/newview/skins/default/xui/en/floater_settings_debug.xml +++ b/indra/newview/skins/default/xui/en/floater_settings_debug.xml @@ -43,6 +43,20 @@ label="Setting" name="setting" /> </scroll_list> + <button + follows="right|bottom" + layout="topleft" + image_hover_unselected="Toolbar_Middle_Over" + image_overlay="Icon_Copy" + image_selected="Toolbar_Middle_Selected" + image_unselected="Toolbar_Middle_Off" + name="copy_btn" + tool_tip="Copy to clipboard" + top_delta="8" + left_pad="10" + visible="false" + height="20" + width="20" /> <text type="string" length="1" @@ -51,12 +65,11 @@ layout="topleft" name="setting_name_txt" font="SansSerifSmallBold" - top_delta="8" - left_pad="10" + left_pad="4" visible="false" use_ellipses="true" text_color="White" - width="240"> + width="225"> Debug setting name </text> <text_editor @@ -67,6 +80,7 @@ name="comment_text" follows="left|top" width="240" + left="320" top_delta="20" word_wrap="true" /> <radio_group @@ -198,4 +212,15 @@ name="hide_default" width="330"> </check_box> + <text_editor + read_only="true" + visible="false" + height="115" + layout="topleft" + name="llsd_text" + follows="left|top" + width="240" + left="320" + top="180" + word_wrap="true" /> </floater> diff --git a/indra/newview/skins/default/xui/en/floater_world_map.xml b/indra/newview/skins/default/xui/en/floater_world_map.xml index b0b818cde5..5ab0177de6 100644 --- a/indra/newview/skins/default/xui/en/floater_world_map.xml +++ b/indra/newview/skins/default/xui/en/floater_world_map.xml @@ -37,7 +37,8 @@ top="16" left="0" right="-1" - bottom="-1"> + bottom="-1" + orientation="horizontal"> <layout_panel name="map_lp" width="385" diff --git a/indra/newview/skins/default/xui/en/menu_lua_scripts.xml b/indra/newview/skins/default/xui/en/menu_lua_scripts.xml new file mode 100644 index 0000000000..645fee405d --- /dev/null +++ b/indra/newview/skins/default/xui/en/menu_lua_scripts.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<context_menu + name="Scripts"> + <menu_item_call + label="Open Containing Folder" + layout="topleft" + name="open_folder"> + <menu_item_call.on_click + function="Script.OpenFolder" /> + </menu_item_call> + <menu_item_separator/> + <menu_item_call + label="Terminate script" + layout="topleft" + name="terminate"> + <menu_item_call.on_click + function="Script.Terminate" /> + </menu_item_call> +</context_menu> diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index bcd5a09d16..c825a811e6 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -465,24 +465,6 @@ <menu_item_check.on_click function="World.AlwaysRun" /> </menu_item_check> - <menu_item_check - label="Hear Media and Sound from Avatar" - name="Hear Media and Sound from Avatar"> - <menu_item_check.on_check - control="MediaSoundsEarLocation" /> - <menu_item_check.on_click - function="Agent.ToggleHearMediaSoundFromAvatar" /> - </menu_item_check> - <menu_item_check - label="Hear Voice from Avatar" - name="Hear Voice from Avatar"> - <menu_item_check.on_check - control="VoiceEarLocation" /> - <menu_item_check.on_click - function="Agent.ToggleHearVoiceFromAvatar" /> - <menu_item_call.on_enable - control="EnableVoiceChat" /> - </menu_item_check> <menu_item_separator/> <menu_item_check label="Gestures..." @@ -580,6 +562,25 @@ </menu_item_check> <menu_item_separator/> <menu_item_check + label="Hear Media and Sound from Avatar" + name="Hear Media and Sound from Avatar"> + <menu_item_check.on_check + control="MediaSoundsEarLocation" /> + <menu_item_check.on_click + function="Agent.ToggleHearMediaSoundFromAvatar" /> + </menu_item_check> + <menu_item_check + label="Hear Voice from Avatar" + name="Hear Voice from Avatar"> + <menu_item_check.on_check + control="VoiceEarLocation" /> + <menu_item_check.on_click + function="Agent.ToggleHearVoiceFromAvatar" /> + <menu_item_call.on_enable + control="EnableVoiceChat" /> + </menu_item_check> + <menu_item_separator/> + <menu_item_check label="Gestures..." name="Gestures" shortcut="control|G"> @@ -2653,6 +2654,37 @@ function="World.EnvPreset" parameter="scene monitor" /> </menu_item_check> <menu_item_separator/> + <menu_item_check + label="LUA Debug Console" + name="LUA Debug Console"> + <menu_item_check.on_check + function="Floater.Visible" + parameter="lua_debug" /> + <menu_item_check.on_click + function="Floater.Toggle" + parameter="lua_debug" /> + <menu_item_check.on_visible + function="CheckControl" + parameter="LuaFeature" /> + </menu_item_check> + <menu_item_check + label="LUA Scripts Info" + name="LUA Scripts"> + <menu_item_check.on_check + function="Floater.Visible" + parameter="lua_scripts" /> + <menu_item_check.on_click + function="Floater.Toggle" + parameter="lua_scripts" /> + <menu_item_check.on_visible + function="CheckControl" + parameter="LuaFeature" /> + </menu_item_check> + <menu_item_separator> + <menu_item_separator.on_visible + function="CheckControl" + parameter="LuaFeature"/> + </menu_item_separator> <menu_item_call label="Region Info to Debug Console" @@ -4287,16 +4319,6 @@ function="World.EnvPreset" <menu_item_separator/> - <menu_item_check - label="HTTP Textures" - name="HTTP Textures"> - <menu_item_check.on_check - function="CheckControl" - parameter="ImagePipelineUseHTTP" /> - <menu_item_check.on_click - function="ToggleControl" - parameter="ImagePipelineUseHTTP" /> - </menu_item_check> <menu_item_call label="Compress Images" name="Compress Images"> diff --git a/indra/newview/skins/default/xui/en/mime_types_linux.xml b/indra/newview/skins/default/xui/en/mime_types_linux.xml index d0ecd0a11c..87284f564b 100644 --- a/indra/newview/skins/default/xui/en/mime_types_linux.xml +++ b/indra/newview/skins/default/xui/en/mime_types_linux.xml @@ -7,7 +7,7 @@ none </defaultwidget> <defaultimpl> - media_plugin_webkit + media_plugin_cef </defaultimpl> <widgetset name="web"> <label name="web_label"> @@ -130,7 +130,7 @@ movie </widgettype> <impl> - media_plugin_libvlc + media_plugin_gstreamer </impl> </scheme> <mimetype name="blank"> @@ -141,7 +141,7 @@ none </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype name="none/none"> @@ -152,7 +152,7 @@ none </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype name="audio/*"> @@ -163,7 +163,7 @@ audio </widgettype> <impl> - media_plugin_libvlc + media_plugin_gstreamer </impl> </mimetype> <mimetype name="video/*"> @@ -174,7 +174,7 @@ movie </widgettype> <impl> - media_plugin_libvlc + media_plugin_gstreamer </impl> </mimetype> <mimetype name="image/*"> @@ -185,7 +185,7 @@ image </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype menu="1" name="video/vnd.secondlife.qt.legacy"> @@ -196,7 +196,7 @@ movie </widgettype> <impl> - media_plugin_libvlc + media_plugin_gstreamer </impl> </mimetype> <mimetype name="application/javascript"> @@ -207,7 +207,7 @@ web </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype name="application/ogg"> @@ -218,7 +218,7 @@ audio </widgettype> <impl> - media_plugin_libvlc + media_plugin_gstreamer </impl> </mimetype> <mimetype name="application/pdf"> @@ -229,7 +229,7 @@ image </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype name="application/postscript"> @@ -240,7 +240,7 @@ image </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype name="application/rtf"> @@ -251,7 +251,7 @@ image </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype name="application/smil"> @@ -262,7 +262,7 @@ movie </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype name="application/xhtml+xml"> @@ -273,7 +273,7 @@ web </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype name="application/x-director"> @@ -284,7 +284,7 @@ image </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype name="audio/mid"> @@ -295,7 +295,7 @@ audio </widgettype> <impl> - media_plugin_libvlc + media_plugin_gstreamer </impl> </mimetype> <mimetype name="audio/mpeg"> @@ -306,7 +306,7 @@ audio </widgettype> <impl> - media_plugin_libvlc + media_plugin_gstreamer </impl> </mimetype> <mimetype name="audio/x-aiff"> @@ -317,7 +317,7 @@ audio </widgettype> <impl> - media_plugin_libvlc + media_plugin_gstreamer </impl> </mimetype> <mimetype name="audio/x-wav"> @@ -328,7 +328,7 @@ audio </widgettype> <impl> - media_plugin_libvlc + media_plugin_gstreamer </impl> </mimetype> <mimetype menu="1" name="image/bmp"> @@ -339,7 +339,7 @@ image </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype menu="1" name="image/gif"> @@ -350,7 +350,7 @@ image </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype menu="1" name="image/jpeg"> @@ -361,7 +361,7 @@ image </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype menu="1" name="image/png"> @@ -372,7 +372,7 @@ image </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype name="image/svg+xml"> @@ -383,7 +383,7 @@ image </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype menu="1" name="image/tiff"> @@ -394,7 +394,7 @@ image </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype menu="1" name="text/html"> @@ -405,7 +405,7 @@ web </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype menu="1" name="text/plain"> @@ -416,7 +416,7 @@ text </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype name="text/xml"> @@ -427,7 +427,7 @@ text </widgettype> <impl> - media_plugin_webkit + media_plugin_cef </impl> </mimetype> <mimetype menu="1" name="video/mpeg"> @@ -438,7 +438,7 @@ movie </widgettype> <impl> - media_plugin_libvlc + media_plugin_gstreamer </impl> </mimetype> <mimetype name="video/mp4"> @@ -449,7 +449,7 @@ movie </widgettype> <impl> - media_plugin_libvlc + media_plugin_gstreamer </impl> </mimetype> <mimetype menu="1" name="video/quicktime"> @@ -460,7 +460,7 @@ movie </widgettype> <impl> - media_plugin_libvlc + media_plugin_gstreamer </impl> </mimetype> <mimetype name="video/x-ms-asf"> @@ -471,7 +471,7 @@ movie </widgettype> <impl> - media_plugin_libvlc + media_plugin_gstreamer </impl> </mimetype> <mimetype name="video/x-ms-wmv"> @@ -482,7 +482,7 @@ movie </widgettype> <impl> - media_plugin_libvlc + media_plugin_gstreamer </impl> </mimetype> <mimetype menu="1" name="video/x-msvideo"> @@ -493,7 +493,7 @@ movie </widgettype> <impl> - media_plugin_libvlc + media_plugin_gstreamer </impl> </mimetype> </mimetypes> diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 848d9aca7c..6c127ece53 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -7556,7 +7556,7 @@ Message from [NAME]: type="notify"> <unique/> This land has damage enabled. -You can be hurt here. If you die, you will be teleported to your home location. +You can be hurt here. If you die, you might be teleported to your home location or to the spawn point. </notification> <notification diff --git a/indra/newview/skins/default/xui/en/panel_group_roles.xml b/indra/newview/skins/default/xui/en/panel_group_roles.xml index 02e135a2c7..868d54401e 100644 --- a/indra/newview/skins/default/xui/en/panel_group_roles.xml +++ b/indra/newview/skins/default/xui/en/panel_group_roles.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel - height="750" + height="770" label="Members & Roles" layout="topleft" left="0" @@ -18,9 +18,9 @@ <panel.string name="help_text" /> <tab_container - border="false" + border="false" follows="left|top|right" - height="552" + height="770" halign="center" layout="topleft" left="0" @@ -29,16 +29,10 @@ tab_position="top" tab_height="22" tab_min_width="90" - top="0" - width="304"> + top="0"> <panel border="false" - follows="all" - height="303" label="MEMBERS" - layout="topleft" - left="0" - right="-1" help_topic="roles_members_tab" name="members_sub_tab" tool_tip="Members" @@ -65,16 +59,16 @@ clicking on their names. name="power_partial_icon" translate="false"> Checkbox_Off </panel.string> - <filter_editor - layout="topleft" - top="5" - left="5" - right="-5" - height="22" - search_button_visible="false" - follows="left|top|right" - label="Filter Members" - name="filter_input" /> + <filter_editor + layout="topleft" + top="5" + left="5" + right="-5" + height="22" + search_button_visible="false" + follows="left|top|right" + label="Filter Members" + name="filter_input" /> <name_list column_padding="2" draw_heading="true" @@ -90,11 +84,11 @@ clicking on their names. <name_list.columns label="Member" name="name" - relative_width="0.44" /> + relative_width="0.44" /> <name_list.columns label="Donation" name="donated" - relative_width="0.2" /> + relative_width="0.2" /> <name_list.columns label="Status" name="online" @@ -118,25 +112,21 @@ clicking on their names. left_pad="10" name="member_eject" width="100" /> - <button - height="23" - label="Ban Member(s)" - follows="top|left" - left_pad="10" - name="member_ban" - width="100" /> + <button + height="23" + label="Ban Member(s)" + follows="top|left" + left_pad="10" + name="member_ban" + width="100" /> </panel> <panel border="false" - height="303" label="ROLES" - layout="topleft" - left="0" - right="-1" help_topic="roles_roles_tab" name="roles_sub_tab" class="panel_group_roles_subtab"> - <!-- <button + <!--<button enabled="false" height="20" label="Show All" @@ -145,411 +135,393 @@ clicking on their names. right="-5" name="show_all_button" width="100" />--> - <panel.string - name="help_text"> - Roles have a title and an allowed list of Abilities - that Members can perform. Members can belong to - one or more Roles. A group can have up to 10 Roles, - including the Everyone and Owner Roles. - </panel.string> - <panel.string - name="cant_delete_role"> - The 'Everyone' and 'Owners' Roles are special and can't be deleted. - </panel.string> - <panel.string - name="power_folder_icon" translate="false"> - Inv_FolderClosed - </panel.string> - <panel.string - name="power_all_have_icon" translate="false"> - Checkbox_On - </panel.string> - <panel.string - name="power_partial_icon" translate="false"> - Checkbox_Off - </panel.string> - <filter_editor - layout="topleft" - top="5" - left="5" - right="-5" - height="22" - search_button_visible="false" - follows="left|top|right" - label="Filter Roles" - name="filter_input" /> - <scroll_list - column_padding="0" - draw_heading="true" - draw_stripes="false" - heading_height="23" - height="132" - layout="topleft" - search_column="1" - left="0" - follows="left|top|right" - right="-1" - name="role_list" - top_pad="2" - width="310"> - <scroll_list.columns - label="Role" - name="name" - relative_width="0.45" /> - <scroll_list.columns - label="Title" - name="title" - relative_width="0.45" /> - <scroll_list.columns - label="#" - name="members" - relative_width="0.15" /> - </scroll_list> - <button - follows="top|left" - height="23" - label="New Role" - layout="topleft" - left="0" - name="role_create" - width="100" /> - <button - follows="top|left" - height="23" - label="Copy Role" - layout="topleft" - left_pad="10" - name="role_copy" - width="100" /> - <button - height="23" - follows="top|left" - label="Delete Role" - layout="topleft" - left_pad="10" - name="role_delete" - width="100" /> - </panel> + <panel.string + name="help_text"> + Roles have a title and an allowed list of Abilities +that Members can perform. Members can belong to +one or more Roles. A group can have up to 10 Roles, +including the Everyone and Owner Roles. + </panel.string> + <panel.string + name="cant_delete_role"> + The 'Everyone' and 'Owners' Roles are special and can't be deleted. + </panel.string> + <panel.string + name="power_folder_icon" translate="false"> + Inv_FolderClosed + </panel.string> + <panel.string + name="power_all_have_icon" translate="false"> + Checkbox_On + </panel.string> + <panel.string + name="power_partial_icon" translate="false"> + Checkbox_Off + </panel.string> + <filter_editor + layout="topleft" + top="5" + left="5" + right="-5" + height="22" + search_button_visible="false" + follows="left|top|right" + label="Filter Roles" + name="filter_input" /> + <scroll_list + column_padding="0" + draw_heading="true" + draw_stripes="false" + heading_height="23" + height="138" + layout="topleft" + search_column="1" + left="0" + follows="left|top|right" + right="-1" + name="role_list" + top_pad="2"> + <scroll_list.columns + label="Role" + name="name" + relative_width="0.45" /> + <scroll_list.columns + label="Title" + name="title" + relative_width="0.45" /> + <scroll_list.columns + label="#" + name="members" + relative_width="0.15" /> + </scroll_list> + <button + follows="top|left" + height="23" + label="New Role" + layout="topleft" + left="0" + name="role_create" + width="100" /> + <button + follows="top|left" + height="23" + label="Copy Role" + layout="topleft" + left_pad="10" + name="role_copy" + width="100" /> + <button + height="23" + follows="top|left" + label="Delete Role" + layout="topleft" + left_pad="10" + name="role_delete" + width="100" /> + </panel> + <panel + border="false" + label="ABILITIES" + help_topic="roles_actions_tab" + name="actions_sub_tab" + class="panel_group_actions_subtab" + tool_tip="You can view an Ability's Description and which Roles and Members can execute the Ability."> + <panel.string + name="help_text"> + Abilities allow Members in Roles to do specific +things in this group. There's a broad variety of Abilities. + </panel.string> + <panel.string + name="power_folder_icon" translate="false"> + Inv_FolderClosed + </panel.string> + <panel.string + name="power_all_have_icon" translate="false"> + Checkbox_On + </panel.string> + <panel.string + name="power_partial_icon" translate="false"> + Checkbox_Off + </panel.string> + <filter_editor + layout="topleft" + top="5" + left="5" + right="-5" + height="22" + search_button_visible="false" + follows="left|top|right" + label="Filter Abilities" + name="filter_input" /> + <scroll_list + column_padding="0" + draw_stripes="true" + height="200" + follows="left|top|right" + layout="topleft" + left="0" + right="-1" + name="action_list" + search_column="2" + tool_tip="Select an Ability to view more details" + top_pad="5"> + <scroll_list.columns + label="" + name="icon" + width="2" /> + <scroll_list.columns + label="" + name="checkbox" + width="20" /> + <scroll_list.columns + label="" + name="action" /> + </scroll_list> + </panel> + <panel + border="false" + label="BANNED RESIDENTS" + help_topic="roles_banlist_tab" + name="banlist_sub_tab" + class="panel_group_banlist_subtab" + tool_tip="View the banned residents from this group."> + <panel.string + name="help_text"> + Any resident on the ban list will be unable to join the group. + </panel.string> + <panel.string + name="ban_count_template"> + Ban count: [COUNT]/[LIMIT] + </panel.string> + <name_list + column_padding="0" + draw_heading="true" + height="714" + follows="left|top|right" + layout="topleft" + left="0" + right="-1" + multi_select="true" + name="ban_list" + short_names="false" + top_pad="5"> + <name_list.columns + label="Resident" + name="name" + font.name="SANSSERIF_SMALL" + font.style="NORMAL" + relative_width="0.7" /> + <name_list.columns + label="Date Banned" + name="ban_date" + relative_width="0.3" /> + </name_list> + <button + follows="top|left" + height="23" + label="Ban Resident(s)" + layout="topleft" + left="3" + name="ban_create" + tool_tip="Ban residents from your group" + width="120" /> + <button + follows="top|left" + height="23" + label="Remove Ban(s)" + layout="topleft" + left_pad="5" + name="ban_delete" + tool_tip="Unban selected residents from your group" + width="120" /> + <button + follows="top|left" + height="23" + width="23" + image_overlay="Refresh_Off" + layout="topleft" + left_pad="5" + name="ban_refresh" + tool_tip="Refresh the ban list" /> + <text + type="string" + height="18" + left_pad="5" + follows="top|left" + layout="topleft" + name="ban_count" + width="100"> + </text> + </panel> + </tab_container> <panel - border="false" - height="303" - label="ABILITIES" + height="350" + background_visible="false" + bg_alpha_color="FloaterUnfocusBorderColor" layout="topleft" + follows="top|left|right" left="0" right="-1" - help_topic="roles_actions_tab" - name="actions_sub_tab" - class="panel_group_actions_subtab" - tool_tip="You can view an Ability's Description and which Roles and Members can execute the Ability." - width="310"> - <panel.string - name="help_text"> - Abilities allow Members in Roles to do specific - things in this group. There's a broad variety of Abilities. - </panel.string> - <panel.string - name="power_folder_icon" translate="false"> - Inv_FolderClosed - </panel.string> - <panel.string - name="power_all_have_icon" translate="false"> - Checkbox_On - </panel.string> - <panel.string - name="power_partial_icon" translate="false"> - Checkbox_Off - </panel.string> - <filter_editor - layout="topleft" - top="5" - left="5" - right="-5" - height="22" - search_button_visible="false" - follows="left|top|right" - label="Filter Abilities" - name="filter_input" /> - <scroll_list - column_padding="0" - draw_stripes="true" - height="200" - follows="left|top|right" - layout="topleft" - left="0" - right="-1" - name="action_list" - search_column="2" - tool_tip="Select an Ability to view more details" - top_pad="5" - width="300"> - <scroll_list.columns - label="" - name="icon" - width="2" /> - <scroll_list.columns - label="" - name="checkbox" - width="20" /> - <scroll_list.columns - label="" - name="action" /> - </scroll_list> + mouse_opaque="false" + name="members_footer" + top="325" + visible="false"> + <text + type="string" + height="16" + layout="topleft" + follows="left|top" + left="5" + top="8" + text_color="EmphasisColor" + name="static" + width="300"> + Assigned Roles + </text> + <scroll_list + draw_stripes="true" + follows="left|top|right" + height="150" + layout="topleft" + left="0" + right="-1" + name="member_assigned_roles" + top_pad="0"> + <scroll_list.columns + label="" + name="checkbox" + width="20" /> + <scroll_list.columns + label="" + name="role" + width="270" /> + </scroll_list> + <text + type="string" + height="16" + layout="topleft" + follows="left|top" + left="5" + top_pad="5" + text_color="EmphasisColor" + name="static2" + width="285"> + Allowed Abilities + </text> + <scroll_list + draw_stripes="true" + follows="left|top|right" + height="150" + layout="topleft" + left="0" + right="-1" + name="member_allowed_actions" + search_column="2" + tool_tip="For details of each allowed ability see the abilities tab" + top_pad="0"> + <scroll_list.columns + label="" + name="icon" + width="2" /> + <scroll_list.columns + label="" + name="checkbox" + width="20" /> + <scroll_list.columns + label="" + name="action" + width="270" /> + </scroll_list> </panel> <panel - border="false" - height="303" - label="BANNED RESIDENTS" + height="90" + background_visible="false" + bg_alpha_color="FloaterUnfocusBorderColor" layout="topleft" + follows="top|left|right" left="0" right="-1" - help_topic="roles_banlist_tab" - name="banlist_sub_tab" - class="panel_group_banlist_subtab" - tool_tip="View the banned residents from this group." - width="310"> - <panel.string - name="help_text"> - Any resident on the ban list will be unable to join the group. - </panel.string> - <panel.string - name="ban_count_template"> - Ban count: [COUNT]/[LIMIT] - </panel.string> - <name_list - column_padding="0" - draw_heading="true" - height="400" - follows="left|top|right" - layout="topleft" - left="0" - right="-1" - multi_select="true" - name="ban_list" - short_names="false" - top_pad="5"> - <name_list.columns - label="Resident" - name="name" - font.name="SANSSERIF_SMALL" - font.style="NORMAL" - relative_width="0.7" /> - <name_list.columns - label="Date Banned" - name="ban_date" - relative_width="0.3" /> - </name_list> - <button - follows="top|left" - height="23" - label="Ban Resident(s)" - layout="topleft" - left="3" - name="ban_create" - tool_tip="Ban residents from your group" - width="120" /> - <button - follows="top|left" - height="23" - label="Remove Ban(s)" - layout="topleft" - left_pad="5" - name="ban_delete" - tool_tip="Unban selected residents from your group" - width="120" /> - <button - follows="top|left" - height="23" - width="23" - image_overlay="Refresh_Off" - layout="topleft" - left_pad="5" - name="ban_refresh" - tool_tip="Refresh the ban list" - /> - <text - type="string" - height="18" - left_pad="5" - follows="top|left" - layout="topleft" - name="ban_count" - width="100"> - </text> + mouse_opaque="false" + name="members_header" + top_pad="3" + visible="false"> + <text_editor + bg_readonly_color="Transparent" + text_readonly_color="EmphasisColor" + font="SansSerifSmall" + type="string" + enabled="false" + halign="left" + layout="topleft" + top_pad="0" + follows="left|top|right" + left="0" + right="-1" + height="90" + max_length="512" + name="member_action_description" + word_wrap="true"> + This Ability is 'Eject Members from this Group'. Only an Owner can eject another Owner. + </text_editor> </panel> - </tab_container> - <panel - height="350" - background_visible="false" - bg_alpha_color="FloaterUnfocusBorderColor" - layout="topleft" - follows="top|left|right" - left="0" - right="-1" - width="313" - mouse_opaque="false" - name="members_footer" - top="325" - visible="false"> - <text - type="string" - height="16" - layout="topleft" - follows="left|top" - left="5" - top="8" - text_color="EmphasisColor" - name="static" - width="300"> - Assigned Roles - </text> - <scroll_list - draw_stripes="true" - follows="left|top|right" - height="150" + <panel + height="460" + background_visible="false" + bg_alpha_color="FloaterUnfocusBorderColor" layout="topleft" + follows="top|left|right" left="0" right="-1" - name="member_assigned_roles" - top_pad="0"> - <scroll_list.columns - label="" - name="checkbox" - width="20" /> - <scroll_list.columns - label="" - name="role" - width="270" /> - </scroll_list> - <text - type="string" - height="16" - layout="topleft" - follows="left|top" - left="5" - top_pad="5" - text_color="EmphasisColor" - name="static2" - width="285"> - Allowed Abilities - </text> - <scroll_list - draw_stripes="true" + mouse_opaque="false" + name="roles_footer" + top_delta="0" + top="215" + visible="false"> + <text + type="string" + height="16" + layout="topleft" + follows="left|top" + left="5" + top="5" + name="static" + width="300"> + Role Name + </text> + <line_editor + type="string" + height="20" + layout="topleft" + left="0" follows="left|top|right" - height="150" - layout="topleft" - left="0" - right="-1" - name="member_allowed_actions" - search_column="2" - tool_tip="For details of each allowed ability see the abilities tab" - top_pad="0"> - <scroll_list.columns - label="" - name="icon" - width="2" /> - <scroll_list.columns - label="" - name="checkbox" - width="20" /> - <scroll_list.columns - label="" - name="action" - width="270" /> - </scroll_list> - </panel> - <panel - height="90" - background_visible="false" - bg_alpha_color="FloaterUnfocusBorderColor" - layout="topleft" - follows="top|left|right" - left="0" - right="-1" - width="313" - mouse_opaque="false" - name="members_header" - top_pad="3" - visible="false"> - <text_editor - bg_readonly_color="Transparent" - text_readonly_color="EmphasisColor" - font="SansSerifSmall" - type="string" - enabled="false" - halign="left" - layout="topleft" - top_pad="0" - follows="left|top|right" - left="0" - right="-1" - height="90" - max_length="512" - name="member_action_description" - word_wrap="true"> - This Ability is 'Eject Members from this Group'. Only an Owner can eject another Owner. - </text_editor> - </panel> - <panel - height="460" - background_visible="false" - bg_alpha_color="FloaterUnfocusBorderColor" - layout="topleft" - follows="top|left|right" - left="0" - right="-1" - width="313" - mouse_opaque="false" - name="roles_footer" - top_delta="0" - top="209" - visible="false"> - <text - type="string" - height="16" - layout="topleft" - follows="left|top" - left="5" - top="5" - name="static" - width="300"> - Role Name - </text> - <line_editor - type="string" - height="20" - layout="topleft" - left="0" - follows="left|top|right" - right="-1" - max_length_bytes="20" - name="role_name" - top_pad="0" - width="300"> - </line_editor> - <text - type="string" - height="16" - layout="topleft" - follows="left|top" - left="5" - name="static3" - top_pad="5" - width="300"> - Role Title - </text> - <line_editor - type="string" - height="20" - layout="topleft" - left="0" - follows="left|top|right" - right="-1" - max_length_bytes="20" - name="role_title" - top_pad="0" - width="300"> - </line_editor> - <text + right="-1" + max_length_bytes="20" + name="role_name" + top_pad="0"> + </line_editor> + <text + type="string" + height="16" + layout="topleft" + follows="left|top" + left="5" + name="static3" + top_pad="5" + width="300"> + Role Title + </text> + <line_editor + type="string" + height="20" + layout="topleft" + left="0" + follows="left|top|right" + right="-1" + max_length_bytes="20" + name="role_title" + top_pad="0"> + </line_editor> + <text type="string" height="16" layout="topleft" @@ -558,192 +530,185 @@ clicking on their names. name="static2" top_pad="5" width="200"> - Description - </text> - <text_editor - type="string" - layout="topleft" - left="0" - follows="left|top|right" - right="-1" - max_length="295" - height="35" - name="role_description" - top_pad="0" - width="300" - word_wrap="true"> - </text_editor> - <text - type="string" - height="16" - layout="topleft" - follows="left|top" - left="5" - text_color="EmphasisColor" - name="static4" - top_pad="5" - width="300"> - Assigned Members - </text> - <name_list - draw_stripes="true" - height="128" - layout="topleft" - left="0" - follows="left|top|right" - right="-1" - name="role_assigned_members" - top_pad="0" - width="300" /> - <check_box - height="15" - label="Reveal members" - left="5" - layout="topleft" - name="role_visible_in_list" - tool_tip="Sets whether members of this role are visible in the General tab to people outside of the group." - top_pad="4" - width="300" /> - <text - type="string" - height="16" - layout="topleft" - follows="left|top" - left="5" - text_color="EmphasisColor" - name="static5" - top_pad="2" - width="300"> - Allowed Abilities - </text> - <scroll_list - draw_stripes="true" - height="140" - layout="topleft" - left="0" - follows="left|top|right" - right="-1" - name="role_allowed_actions" - search_column="2" - tool_tip="For details of each allowed ability see the abilities tab" - top_pad="0" - width="300"> - <scroll_list.columns - label="" - name="icon" - width="2" /> - <scroll_list.columns - label="" - name="checkbox" - width="20" /> - <scroll_list.columns - label="" - name="action" /> - </scroll_list> - </panel> - <panel - height="90" - background_visible="false" - bg_alpha_color="FloaterUnfocusBorderColor" - layout="topleft" - follows="top|left|right" - left="0" - right="-1" - width="313" - mouse_opaque="false" - name="roles_header" - top_pad="3" - visible="false"> - <text_editor - bg_readonly_color="Transparent" - text_readonly_color="EmphasisColor" - font="SansSerifSmall" - type="string" - enabled="false" - halign="left" - layout="topleft" - top_pad="0" - follows="left|top|right" - left="0" - right="-1" + Description + </text> + <text_editor + type="string" + layout="topleft" + left="0" + follows="left|top|right" + right="-1" + max_length="295" + height="35" + name="role_description" + top_pad="0" + word_wrap="true"> + </text_editor> + <text + type="string" + height="16" + layout="topleft" + follows="left|top" + left="5" + text_color="EmphasisColor" + name="static4" + top_pad="5" + width="300"> + Assigned Members + </text> + <name_list + draw_stripes="true" + height="128" + layout="topleft" + left="0" + follows="left|top|right" + right="-1" + name="role_assigned_members" + top_pad="0" /> + <check_box + height="15" + label="Reveal members" + left="5" + layout="topleft" + name="role_visible_in_list" + tool_tip="Sets whether members of this role are visible in the General tab to people outside of the group." + top_pad="4" + width="300" /> + <text + type="string" + height="16" + layout="topleft" + follows="left|top" + left="5" + text_color="EmphasisColor" + name="static5" + top_pad="2" + width="300"> + Allowed Abilities + </text> + <scroll_list + draw_stripes="true" + height="140" + layout="topleft" + left="0" + follows="left|top|right" + right="-1" + name="role_allowed_actions" + search_column="2" + tool_tip="For details of each allowed ability see the abilities tab" + top_pad="0"> + <scroll_list.columns + label="" + name="icon" + width="2" /> + <scroll_list.columns + label="" + name="checkbox" + width="20" /> + <scroll_list.columns + label="" + name="action" /> + </scroll_list> + </panel> + <panel height="90" - max_length="512" - name="role_action_description" - word_wrap="true"> - This Ability is 'Eject Members from this Group'. Only an Owner can eject another Owner. - </text_editor> - </panel> - <panel - height="424" - background_visible="false" - bg_alpha_color="FloaterUnfocusBorderColor" - layout="topleft" - follows="top|left|right" - left="0" - right="-1" - width="313" - mouse_opaque="false" - name="actions_footer" - top_delta="0" - top="255" - visible="false"> - <text_editor - bg_readonly_color="Transparent" - text_readonly_color="EmphasisColor" - font="SansSerifSmall" - type="string" - enabled="false" - halign="left" + background_visible="false" + bg_alpha_color="FloaterUnfocusBorderColor" layout="topleft" - follows="left|top|right" + follows="top|left|right" left="0" right="-1" - height="90" - max_length="512" - name="action_description" - top="0" - word_wrap="true"> - This Ability is 'Eject Members from this Group'. Only an Owner can eject another Owner. - </text_editor> - <text - type="string" - height="16" - layout="topleft" - follows="left|top" - left="5" - name="static2" - top_pad="1" - width="300"> - Roles with this ability - </text> - <scroll_list - height="172" - layout="topleft" - follows="left|top|right" - left="5" - right="-1" - name="action_roles" - top_pad="0" - width="300" /> - <text - type="string" - height="16" - layout="topleft" - follows="left|top" - left="5" - name="static3" - top_pad="5" - width="300"> - Members with this ability - </text> - <name_list - height="122" - follows="left|top|right" + mouse_opaque="false" + name="roles_header" + top_pad="3" + visible="false"> + <text_editor + bg_readonly_color="Transparent" + text_readonly_color="EmphasisColor" + font="SansSerifSmall" + type="string" + enabled="false" + halign="left" + layout="topleft" + top_pad="0" + follows="left|top|right" + left="0" + right="-1" + height="90" + max_length="512" + name="role_action_description" + word_wrap="true"> + This Ability is 'Eject Members from this Group'. Only an Owner can eject another Owner. + </text_editor> + </panel> + <panel + height="513" + background_visible="false" + bg_alpha_color="FloaterUnfocusBorderColor" layout="topleft" - left="5" + follows="top|left|right" + left="0" right="-1" - name="action_members" - top_pad="0" - width="300" /> - </panel> + mouse_opaque="false" + name="actions_footer" + top_delta="0" + top="255" + visible="false"> + <text_editor + bg_readonly_color="Transparent" + text_readonly_color="EmphasisColor" + font="SansSerifSmall" + type="string" + enabled="false" + halign="left" + layout="topleft" + follows="left|top|right" + left="0" + right="-1" + height="90" + max_length="512" + name="action_description" + top="0" + word_wrap="true"> + This Ability is 'Eject Members from this Group'. Only an Owner can eject another Owner. + </text_editor> + <text + type="string" + height="16" + layout="topleft" + follows="left|top" + left="5" + name="static2" + top_pad="1" + width="300"> + Roles with this ability + </text> + <scroll_list + height="216" + layout="topleft" + follows="left|top|right" + left="5" + right="-1" + name="action_roles" + top_pad="0" /> + <text + type="string" + height="16" + layout="topleft" + follows="left|top" + left="5" + name="static3" + top_pad="5" + width="300"> + Members with this ability + </text> + <name_list + height="167" + follows="left|top|right" + layout="topleft" + left="5" + right="-1" + name="action_members" + top_pad="0" /> + </panel> </panel> diff --git a/indra/newview/skins/default/xui/en/panel_login_first.xml b/indra/newview/skins/default/xui/en/panel_login_first.xml index c906e2f96c..d6ac71db94 100644 --- a/indra/newview/skins/default/xui/en/panel_login_first.xml +++ b/indra/newview/skins/default/xui/en/panel_login_first.xml @@ -179,7 +179,6 @@ control_name="RememberPassword" follows="left|top" font="SansSerifLarge" - text_color="EmphasisColor" height="24" left="262" bottom_delta="0" diff --git a/indra/newview/skins/default/xui/en/panel_preferences_graphics1.xml b/indra/newview/skins/default/xui/en/panel_preferences_graphics1.xml index 5d347397bf..cb3e0e4b6a 100644 --- a/indra/newview/skins/default/xui/en/panel_preferences_graphics1.xml +++ b/indra/newview/skins/default/xui/en/panel_preferences_graphics1.xml @@ -210,26 +210,60 @@ increment="8" initial_value="160" label="Draw distance:" - label_width="90" + label_width="187" layout="topleft" left="30" min_val="64" max_val="512" name="DrawDistance" top_delta="40" - width="330" /> + width="427" /> <text type="string" length="1" follows="left|top" height="12" layout="topleft" - left_delta="330" + left_delta="427" name="DrawDistanceMeterText2" top_delta="0" width="128"> m </text> + <slider + control_name="IndirectMaxNonImpostors" + name="IndirectMaxNonImpostors" + decimal_digits="0" + increment="1" + initial_value="12" + show_text="false" + min_val="1" + max_val="66" + label="Maximum number of animated avatars:" + follows="left|top" + layout="topleft" + height="16" + label_width="240" + left="30" + top_delta="40" + width="393"> + <slider.commit_callback + function="Pref.UpdateIndirectMaxNonImpostors" + parameter="IndirectNonImpostorsText" /> + </slider> + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + top_delta="0" + left_delta="397" + text_readonly_color="LabelDisabledColor" + name="IndirectMaxNonImpostorsText" + width="65"> + 0 + </text> <button height="23" diff --git a/indra/newview/skins/default/xui/en/panel_profile_pick.xml b/indra/newview/skins/default/xui/en/panel_profile_pick.xml index 024120931f..4f441b9b49 100644 --- a/indra/newview/skins/default/xui/en/panel_profile_pick.xml +++ b/indra/newview/skins/default/xui/en/panel_profile_pick.xml @@ -200,6 +200,26 @@ <layout_panel follows="all" + layout="bottomleft" + left_pad="2" + name="set_to_curr_location_btn_lp" + auto_resize="false" + width="100"> + <button + name="set_to_curr_location_btn" + label="Set Location" + tool_tip="Set to Current Location" + left="0" + top="0" + height="23" + width="100" + follows="left|top" + layout="topleft" + /> + </layout_panel> + + <layout_panel + follows="all" layout="topleft" name="util_resizer_right" auto_resize="true" diff --git a/indra/newview/skins/default/xui/en/panel_region_environment.xml b/indra/newview/skins/default/xui/en/panel_region_environment.xml index 6d23592948..6531233696 100644 --- a/indra/newview/skins/default/xui/en/panel_region_environment.xml +++ b/indra/newview/skins/default/xui/en/panel_region_environment.xml @@ -264,8 +264,7 @@ top_pad="1" halign="right" name="txt_alt1"> - Sky [INDEX] - [ALTITUDE]m + Sky [INDEX]
[ALTITUDE]m </text> <line_editor follows="left|top" @@ -310,8 +309,7 @@ top_pad="1" halign="right" name="txt_alt2"> - Sky [INDEX] - [ALTITUDE]m + Sky [INDEX]
[ALTITUDE]m </text> <line_editor follows="left|top" @@ -356,8 +354,7 @@ top_pad="1" halign="right" name="txt_alt3"> - Sky [INDEX] - [ALTITUDE]m + Sky [INDEX]
[ALTITUDE]m </text> <line_editor follows="left|top" diff --git a/indra/newview/skins/default/xui/en/panel_snapshot_inventory.xml b/indra/newview/skins/default/xui/en/panel_snapshot_inventory.xml index f8040b9a65..0cac1b410f 100644 --- a/indra/newview/skins/default/xui/en/panel_snapshot_inventory.xml +++ b/indra/newview/skins/default/xui/en/panel_snapshot_inventory.xml @@ -60,7 +60,7 @@ name="Large(512x512)" value="[i512,i512]" /> <combo_box.item - label="Current Window(512x512)" + label="Current Window" name="CurrentWindow" value="[i0,i0]" /> <combo_box.item @@ -119,6 +119,8 @@ type="string" word_wrap="true"> To save your image as a texture select one of the square formats. + +Upload cost: L$[UPLOAD_COST] </text> <button follows="right|bottom" diff --git a/indra/newview/skins/default/xui/en/panel_snapshot_options.xml b/indra/newview/skins/default/xui/en/panel_snapshot_options.xml index 3a7731d235..2fb02af61c 100644 --- a/indra/newview/skins/default/xui/en/panel_snapshot_options.xml +++ b/indra/newview/skins/default/xui/en/panel_snapshot_options.xml @@ -31,7 +31,7 @@ image_overlay_alignment="left" image_top_pad="-1" imgoverlay_label_space="10" - label="Save to Inventory (L$[AMOUNT])" + label="Save to Inventory" layout="topleft" left_delta="0" name="save_to_inventory_btn" diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index cebe1ff6c3..3ec4b7205b 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -535,7 +535,9 @@ http://secondlife.com/support for help fixing this problem. <string name="ChangeYourDefaultAnimations">Change your default animations</string> <string name="ForceSitAvatar">Force your avatar to sit</string> <string name="ChangeEnvSettings">Change your environment settings</string> - + <string name="ScriptBy" value="Script by "/> + <string name="ScriptStr" value="Script: "/> + <string name="NotConnected">Not Connected</string> <string name="AgentNameSubst">(You)</string> <!-- Substitution for agent name --> <string name="JoinAnExperience"/><!-- intentionally blank --> @@ -4028,6 +4030,10 @@ Please check http://status.secondlifegrid.net to see if there is a known problem <string name="DeleteItem">Delete selected item?</string> <string name="EmptyOutfitText">There are no items in this outfit</string> + <string name="OutfitNotFound" value="Couldn't find outfit "/> + <string name="OutfitNotAdded" value="Can't add to COF outfit "/> + <string name="OutfitNotReplaced" value="Can't replace COF with outfit "/> + <string name="SystemFolderNotWorn" value="Can't wear system folder "/> <!-- External editor status codes --> <string name="ExternalEditorNotSet">Select an editor by setting the environment variable LL_SCRIPT_EDITOR or the ExternalEditor setting. diff --git a/indra/newview/skins/default/xui/es/panel_snapshot_inventory.xml b/indra/newview/skins/default/xui/es/panel_snapshot_inventory.xml index b5cf57ade7..c9eea9a58e 100644 --- a/indra/newview/skins/default/xui/es/panel_snapshot_inventory.xml +++ b/indra/newview/skins/default/xui/es/panel_snapshot_inventory.xml @@ -7,7 +7,7 @@ Guardar una imagen en el inventario cuesta [UPLOAD_COST] L$. Para guardar una imagen como una textura, selecciona uno de los formatos cuadrados. </text> <combo_box label="Resolución" name="texture_size_combo"> - <combo_box.item label="Ventana actual (512 × 512)" name="CurrentWindow"/> + <combo_box.item label="Ventana actual" name="CurrentWindow"/> <combo_box.item label="Pequeña (128x128)" name="Small(128x128)"/> <combo_box.item label="Mediana (256x256)" name="Medium(256x256)"/> <combo_box.item label="Grande (512x512)" name="Large(512x512)"/> diff --git a/indra/newview/skins/default/xui/es/panel_snapshot_options.xml b/indra/newview/skins/default/xui/es/panel_snapshot_options.xml index 4eb9ecf28f..f3119c739e 100644 --- a/indra/newview/skins/default/xui/es/panel_snapshot_options.xml +++ b/indra/newview/skins/default/xui/es/panel_snapshot_options.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="panel_snapshot_options"> <button label="Guardar en disco" name="save_to_computer_btn"/> - <button label="Guardar en inventario (L$[AMOUNT])" name="save_to_inventory_btn"/> + <button label="Guardar en inventario" name="save_to_inventory_btn"/> <button label="Compartir en los comentarios de Mi perfil" name="save_to_profile_btn"/> <button label="Compartir en Facebook" name="send_to_facebook_btn"/> <button label="Compartir en Twitter" name="send_to_twitter_btn"/> diff --git a/indra/newview/skins/default/xui/es/strings.xml b/indra/newview/skins/default/xui/es/strings.xml index cd8e7687ae..9fcfc2daa5 100644 --- a/indra/newview/skins/default/xui/es/strings.xml +++ b/indra/newview/skins/default/xui/es/strings.xml @@ -29,7 +29,6 @@ Distancia de dibujo: [DRAW_DISTANCE]m Ancho de banda: [NET_BANDWITH]kbit/s Factor LOD: [LOD_FACTOR] Calidad de renderización: [RENDER_QUALITY] -Modelo de iluminación avanzado: [GPU_SHADERS] Memoria de textura: [TEXTURE_MEMORY]MB</string> <string name="AboutOSXHiDPI">Modo de visualización HiDPi: [HIDPI]</string> <string name="AboutLibs">Versión de descodificador J2C: [J2C_VERSION] diff --git a/indra/newview/skins/default/xui/fr/panel_snapshot_inventory.xml b/indra/newview/skins/default/xui/fr/panel_snapshot_inventory.xml index 3cf64583d2..a560ff8d5e 100644 --- a/indra/newview/skins/default/xui/fr/panel_snapshot_inventory.xml +++ b/indra/newview/skins/default/xui/fr/panel_snapshot_inventory.xml @@ -7,7 +7,7 @@ L'enregistrement d'une image dans l'inventaire coûte [UPLOAD_COST] L$. Pour enregistrer votre image sous forme de texture, sélectionnez un format carré. </text> <combo_box label="Résolution" name="texture_size_combo"> - <combo_box.item label="Fenêtre actuelle (512x512)" name="CurrentWindow"/> + <combo_box.item label="Fenêtre actuelle" name="CurrentWindow"/> <combo_box.item label="Petite (128 x 128)" name="Small(128x128)"/> <combo_box.item label="Moyenne (256 x 256)" name="Medium(256x256)"/> <combo_box.item label="Grande (512 x 512)" name="Large(512x512)"/> diff --git a/indra/newview/skins/default/xui/fr/panel_snapshot_options.xml b/indra/newview/skins/default/xui/fr/panel_snapshot_options.xml index bdedb9162f..52fa318f8e 100644 --- a/indra/newview/skins/default/xui/fr/panel_snapshot_options.xml +++ b/indra/newview/skins/default/xui/fr/panel_snapshot_options.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="panel_snapshot_options"> <button label="Enreg. sur le disque" name="save_to_computer_btn"/> - <button label="Enreg. dans l'inventaire ([AMOUNT] L$)" name="save_to_inventory_btn"/> + <button label="Enreg. dans l'inventaire" name="save_to_inventory_btn"/> <button label="Partager sur le flux de profil" name="save_to_profile_btn"/> <button label="Partager sur Facebook" name="send_to_facebook_btn"/> <button label="Partager sur Twitter" name="send_to_twitter_btn"/> diff --git a/indra/newview/skins/default/xui/fr/strings.xml b/indra/newview/skins/default/xui/fr/strings.xml index 0a3fbeb603..55f6209fe1 100644 --- a/indra/newview/skins/default/xui/fr/strings.xml +++ b/indra/newview/skins/default/xui/fr/strings.xml @@ -32,7 +32,6 @@ Distance de dessin : [DRAW_DISTANCE]m Bande passante : [NET_BANDWITH] kbit/s Facteur LOD (niveau de détail) : [LOD_FACTOR] Qualité de rendu : [RENDER_QUALITY] -Modèle d’éclairage avancé : [GPU_SHADERS] Mémoire textures : [TEXTURE_MEMORY] Mo</string> <string name="AboutOSXHiDPI">Mode d'affichage HiDPI : [HIDPI]</string> <string name="AboutLibs">J2C Decoder Version: [J2C_VERSION] diff --git a/indra/newview/skins/default/xui/it/panel_snapshot_inventory.xml b/indra/newview/skins/default/xui/it/panel_snapshot_inventory.xml index 75b5d64660..21b65e8e69 100644 --- a/indra/newview/skins/default/xui/it/panel_snapshot_inventory.xml +++ b/indra/newview/skins/default/xui/it/panel_snapshot_inventory.xml @@ -7,7 +7,7 @@ Salvare un'immagine nell'inventario costa L$[UPLOAD_COST]. Per salvare l'immagine come texture, selezionare uno dei formati quadrati. </text> <combo_box label="Risoluzione" name="texture_size_combo"> - <combo_box.item label="Finestra corrente (512x512)" name="CurrentWindow"/> + <combo_box.item label="Finestra corrente" name="CurrentWindow"/> <combo_box.item label="Piccola (128x128)" name="Small(128x128)"/> <combo_box.item label="Media (256x256)" name="Medium(256x256)"/> <combo_box.item label="Grande (512x512)" name="Large(512x512)"/> diff --git a/indra/newview/skins/default/xui/it/panel_snapshot_options.xml b/indra/newview/skins/default/xui/it/panel_snapshot_options.xml index 50fb3d39fa..7fce171326 100644 --- a/indra/newview/skins/default/xui/it/panel_snapshot_options.xml +++ b/indra/newview/skins/default/xui/it/panel_snapshot_options.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="panel_snapshot_options"> <button label="Salva sul disco" name="save_to_computer_btn"/> - <button label="Salva nell'inventario (L$[AMOUNT])" name="save_to_inventory_btn"/> + <button label="Salva nell'inventario" name="save_to_inventory_btn"/> <button label="Condividi sul feed del profilo" name="save_to_profile_btn"/> <button label="Condividi su Facebook" name="send_to_facebook_btn"/> <button label="Condividi su Twitter" name="send_to_twitter_btn"/> diff --git a/indra/newview/skins/default/xui/it/strings.xml b/indra/newview/skins/default/xui/it/strings.xml index 178bb90ca6..f77ab1062a 100644 --- a/indra/newview/skins/default/xui/it/strings.xml +++ b/indra/newview/skins/default/xui/it/strings.xml @@ -31,7 +31,6 @@ Distanza visualizzazione: [DRAW_DISTANCE]m Larghezza banda: [NET_BANDWITH]kbit/s Fattore livello di dettaglio: [LOD_FACTOR] Qualità di rendering: [RENDER_QUALITY] -Modello illuminazione avanzato: [GPU_SHADERS] Memoria texture: [TEXTURE_MEMORY]MB</string> <string name="AboutOSXHiDPI">Modalità display HiDPI: [HIDPI]</string> <string name="AboutLibs">J2C Versione decoder: [J2C_VERSION] diff --git a/indra/newview/skins/default/xui/ja/panel_snapshot_inventory.xml b/indra/newview/skins/default/xui/ja/panel_snapshot_inventory.xml index c55c11e928..04ecba4264 100644 --- a/indra/newview/skins/default/xui/ja/panel_snapshot_inventory.xml +++ b/indra/newview/skins/default/xui/ja/panel_snapshot_inventory.xml @@ -6,7 +6,7 @@ </text> <view_border name="hr"/> <combo_box label="解像度" name="texture_size_combo"> - <combo_box.item label="現在のウィンドウ (512✕512)" name="CurrentWindow"/> + <combo_box.item label="現在のウィンドウ" name="CurrentWindow"/> <combo_box.item label="小(128✕128)" name="Small(128x128)"/> <combo_box.item label="中(256✕256)" name="Medium(256x256)"/> <combo_box.item label="大(512✕512)" name="Large(512x512)"/> @@ -16,7 +16,7 @@ <spinner label="" name="inventory_snapshot_height"/> <check_box label="縦横比の固定" name="inventory_keep_aspect_check"/> <text name="hint_lbl"> - 画像をテクスチャとして保存する場合は、いずれかの正方形を選択してください。 + 画像をインベントリに保存するには L$[UPLOAD_COST] の費用がかかります。画像をテクスチャとして保存するには平方形式の 1 つを選択してください。 </text> <button label="キャンセル" name="cancel_btn"/> <button label="保存" name="save_btn"/> diff --git a/indra/newview/skins/default/xui/ja/panel_snapshot_options.xml b/indra/newview/skins/default/xui/ja/panel_snapshot_options.xml index 7a1aa280ec..a979e31c9a 100644 --- a/indra/newview/skins/default/xui/ja/panel_snapshot_options.xml +++ b/indra/newview/skins/default/xui/ja/panel_snapshot_options.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="panel_snapshot_options"> <button label="ディスクに保存" name="save_to_computer_btn"/> - <button label="インベントリに保存(L$ [AMOUNT])" name="save_to_inventory_btn"/> + <button label="インベントリに保存" name="save_to_inventory_btn"/> <button label="プロフィールフィードで共有する" name="save_to_profile_btn"/> <button label="メールで送信" name="save_to_email_btn"/> <text name="fee_hint_lbl"> diff --git a/indra/newview/skins/default/xui/pl/panel_snapshot_options.xml b/indra/newview/skins/default/xui/pl/panel_snapshot_options.xml index 016b9ca197..04c01940e1 100644 --- a/indra/newview/skins/default/xui/pl/panel_snapshot_options.xml +++ b/indra/newview/skins/default/xui/pl/panel_snapshot_options.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes" ?> <panel name="panel_snapshot_options"> <button label="Zapisz na dysku twardym" name="save_to_computer_btn" /> - <button label="Zapisz do Szafy ([AMOUNT]L$)" name="save_to_inventory_btn" /> + <button label="Zapisz do Szafy" name="save_to_inventory_btn" /> <button label="Wyślij na mój Kanał" name="save_to_profile_btn" /> <button label="Załaduj na Facebook" name="send_to_facebook_btn" /> <button label="Załaduj na Twitter" name="send_to_twitter_btn" /> diff --git a/indra/newview/skins/default/xui/pl/strings.xml b/indra/newview/skins/default/xui/pl/strings.xml index 26ec6cc9dc..8032443020 100644 --- a/indra/newview/skins/default/xui/pl/strings.xml +++ b/indra/newview/skins/default/xui/pl/strings.xml @@ -49,7 +49,6 @@ Pole widzenia (Draw Distance): [DRAW_DISTANCE]m Przepustowość (Bandwidth): [NET_BANDWITH]kbit/s Mnożnik poziomu detali (LOD Factor): [LOD_FACTOR] Jakość wyświetlania (Render quality): [RENDER_QUALITY] -Zaawansowane oświetlenie (Advanced Lighting Model): [GPU_SHADERS] Pamięć tekstur (Texture memory): [TEXTURE_MEMORY]MB Pamięć podręczna dysku (Disk cache): [DISK_CACHE_INFO] </string> diff --git a/indra/newview/skins/default/xui/pt/panel_snapshot_inventory.xml b/indra/newview/skins/default/xui/pt/panel_snapshot_inventory.xml index f3357026d5..28a5142baa 100644 --- a/indra/newview/skins/default/xui/pt/panel_snapshot_inventory.xml +++ b/indra/newview/skins/default/xui/pt/panel_snapshot_inventory.xml @@ -7,7 +7,7 @@ Salvar uma imagem em seu inventário custa L$[UPLOAD_COST]. Para salvar sua imagem como uma textura, selecione um dos formatos quadrados. </text> <combo_box label="Resolução" name="texture_size_combo"> - <combo_box.item label="Janela ativa (512x512)" name="CurrentWindow"/> + <combo_box.item label="Janela ativa" name="CurrentWindow"/> <combo_box.item label="Pequeno (128x128)" name="Small(128x128)"/> <combo_box.item label="Médio (256x256)" name="Medium(256x256)"/> <combo_box.item label="Grande (512x512)" name="Large(512x512)"/> diff --git a/indra/newview/skins/default/xui/pt/panel_snapshot_options.xml b/indra/newview/skins/default/xui/pt/panel_snapshot_options.xml index 067e5dbd76..f71bc7cd12 100644 --- a/indra/newview/skins/default/xui/pt/panel_snapshot_options.xml +++ b/indra/newview/skins/default/xui/pt/panel_snapshot_options.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="panel_snapshot_options"> <button label="Salvar no disco" name="save_to_computer_btn"/> - <button label="Salvar em inventário (L$[AMOUNT])" name="save_to_inventory_btn"/> + <button label="Salvar em inventário" name="save_to_inventory_btn"/> <button label="Compartilhar no feed do perfil" name="save_to_profile_btn"/> <button label="Compartilhar no Facebook" name="send_to_facebook_btn"/> <button label="Compartilhar no Twitter" name="send_to_twitter_btn"/> diff --git a/indra/newview/skins/default/xui/pt/strings.xml b/indra/newview/skins/default/xui/pt/strings.xml index 6db5da2e89..4ce1e6d2ec 100644 --- a/indra/newview/skins/default/xui/pt/strings.xml +++ b/indra/newview/skins/default/xui/pt/strings.xml @@ -29,7 +29,6 @@ Estabelecer a distância: [DRAW_DISTANCE]m Largura da banda: [NET_BANDWITH]kbit/s LOD fator: [LOD_FACTOR] Qualidade de renderização: [RENDER_QUALITY] -Modelo avançado de luzes: [GPU_SHADERS] Memória de textura: [TEXTURE_MEMORY]MB</string> <string name="AboutOSXHiDPI">HiDPI modo de exibição: [HIDPI]</string> <string name="AboutLibs">Versão do J2C Decoder: [J2C_VERSION] diff --git a/indra/newview/skins/default/xui/ru/panel_snapshot_inventory.xml b/indra/newview/skins/default/xui/ru/panel_snapshot_inventory.xml index f07e12e0ed..adc612dfd8 100644 --- a/indra/newview/skins/default/xui/ru/panel_snapshot_inventory.xml +++ b/indra/newview/skins/default/xui/ru/panel_snapshot_inventory.xml @@ -7,7 +7,7 @@ Сохранение изображения в инвентаре стоит L$[UPLOAD_COST]. Чтобы сохранить его как текстуру, выберите один из квадратных форматов. </text> <combo_box label="Размер" name="texture_size_combo"> - <combo_box.item label="Текущее окно (512x512)" name="CurrentWindow"/> + <combo_box.item label="Текущее окно" name="CurrentWindow"/> <combo_box.item label="Маленький (128x128)" name="Small(128x128)"/> <combo_box.item label="Средний (256x256)" name="Medium(256x256)"/> <combo_box.item label="Большой (512x512)" name="Large(512x512)"/> diff --git a/indra/newview/skins/default/xui/ru/panel_snapshot_options.xml b/indra/newview/skins/default/xui/ru/panel_snapshot_options.xml index 7ba03ee0c9..f7fda0b1dc 100644 --- a/indra/newview/skins/default/xui/ru/panel_snapshot_options.xml +++ b/indra/newview/skins/default/xui/ru/panel_snapshot_options.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="panel_snapshot_options"> <button label="Сохранить на диске" name="save_to_computer_btn"/> - <button label="Сохранить в инвентаре (L$[AMOUNT])" name="save_to_inventory_btn"/> + <button label="Сохранить в инвентаре" name="save_to_inventory_btn"/> <button label="Поделиться в профиле" name="save_to_profile_btn"/> <button label="Поделиться в Facebook" name="send_to_facebook_btn"/> <button label="Поделиться в Twitter" name="send_to_twitter_btn"/> diff --git a/indra/newview/skins/default/xui/ru/strings.xml b/indra/newview/skins/default/xui/ru/strings.xml index 61d836a2d1..0079309ba2 100644 --- a/indra/newview/skins/default/xui/ru/strings.xml +++ b/indra/newview/skins/default/xui/ru/strings.xml @@ -69,7 +69,6 @@ SLURL: <nolink>[SLURL]</nolink> Ширина канала: [NET_BANDWITH] кбит/с Коэффициент детализации: [LOD_FACTOR] Качество визуализации: [RENDER_QUALITY] -Расширенная модель освещения: [GPU_SHADERS] Память текстур: [TEXTURE_MEMORY] МБ </string> <string name="AboutOSXHiDPI"> diff --git a/indra/newview/skins/default/xui/tr/panel_snapshot_inventory.xml b/indra/newview/skins/default/xui/tr/panel_snapshot_inventory.xml index be5940c4b9..160cba8700 100644 --- a/indra/newview/skins/default/xui/tr/panel_snapshot_inventory.xml +++ b/indra/newview/skins/default/xui/tr/panel_snapshot_inventory.xml @@ -7,7 +7,7 @@ Bir görüntüyü envanterinize kaydetmenin maliyeti L$[UPLOAD_COST] olur. Görüntünüzü bir doku olarak kaydetmek için kare formatlardan birini seçin. </text> <combo_box label="Çözünürlük" name="texture_size_combo"> - <combo_box.item label="Mevcut Pencere(512x512)" name="CurrentWindow"/> + <combo_box.item label="Mevcut Pencere" name="CurrentWindow"/> <combo_box.item label="Küçük (128x128)" name="Small(128x128)"/> <combo_box.item label="Orta (256x256)" name="Medium(256x256)"/> <combo_box.item label="Büyük (512x512)" name="Large(512x512)"/> diff --git a/indra/newview/skins/default/xui/tr/panel_snapshot_options.xml b/indra/newview/skins/default/xui/tr/panel_snapshot_options.xml index 1b48bbeec2..a028710b98 100644 --- a/indra/newview/skins/default/xui/tr/panel_snapshot_options.xml +++ b/indra/newview/skins/default/xui/tr/panel_snapshot_options.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="panel_snapshot_options"> <button label="Diske Kaydet" name="save_to_computer_btn"/> - <button label="Envantere Kaydet (L$[AMOUNT])" name="save_to_inventory_btn"/> + <button label="Envantere Kaydet" name="save_to_inventory_btn"/> <button label="Profil Akışında Paylaş" name="save_to_profile_btn"/> <button label="Facebook'ta Paylaş" name="send_to_facebook_btn"/> <button label="Twitter'da Paylaş" name="send_to_twitter_btn"/> diff --git a/indra/newview/skins/default/xui/tr/strings.xml b/indra/newview/skins/default/xui/tr/strings.xml index e709a4c5d6..fa2fd3a802 100644 --- a/indra/newview/skins/default/xui/tr/strings.xml +++ b/indra/newview/skins/default/xui/tr/strings.xml @@ -69,7 +69,6 @@ UI Ölçeklendirme: [UI_SCALE] Bant genişliği: [NET_BANDWITH]kbit/s LOD faktörü: [LOD_FACTOR] İşleme kalitesi: [RENDER_QUALITY] -Gelişmiş Aydınlatma Modeli: [GPU_SHADERS] Doku belleği: [TEXTURE_MEMORY]MB </string> <string name="AboutOSXHiDPI"> diff --git a/indra/newview/skins/default/xui/zh/panel_snapshot_inventory.xml b/indra/newview/skins/default/xui/zh/panel_snapshot_inventory.xml index 094bf019b4..9c45c54a5e 100644 --- a/indra/newview/skins/default/xui/zh/panel_snapshot_inventory.xml +++ b/indra/newview/skins/default/xui/zh/panel_snapshot_inventory.xml @@ -7,7 +7,7 @@ 將圖像儲存到收納區的費用為 L$[UPLOAD_COST]。 若要將圖像存為材質,請選擇一個正方格式。 </text> <combo_box label="解析度" name="texture_size_combo"> - <combo_box.item label="目前視窗(512x512)" name="CurrentWindow"/> + <combo_box.item label="目前視窗" name="CurrentWindow"/> <combo_box.item label="小(128x128)" name="Small(128x128)"/> <combo_box.item label="中(256x256)" name="Medium(256x256)"/> <combo_box.item label="大(512x512)" name="Large(512x512)"/> diff --git a/indra/newview/skins/default/xui/zh/panel_snapshot_options.xml b/indra/newview/skins/default/xui/zh/panel_snapshot_options.xml index d7c65bb25e..d9536882ac 100644 --- a/indra/newview/skins/default/xui/zh/panel_snapshot_options.xml +++ b/indra/newview/skins/default/xui/zh/panel_snapshot_options.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8" standalone="yes"?> <panel name="panel_snapshot_options"> <button label="儲存到硬碟" name="save_to_computer_btn"/> - <button label="儲存到收納區(L$[AMOUNT])" name="save_to_inventory_btn"/> + <button label="儲存到收納區" name="save_to_inventory_btn"/> <button label="分享至檔案訊息發佈" name="save_to_profile_btn"/> <button label="分享到臉書" name="send_to_facebook_btn"/> <button label="分享到推特" name="send_to_twitter_btn"/> diff --git a/indra/newview/tests/cppfeatures_test.cpp b/indra/newview/tests/cppfeatures_test.cpp index f5ea3a522b..ca94dcfc95 100644 --- a/indra/newview/tests/cppfeatures_test.cpp +++ b/indra/newview/tests/cppfeatures_test.cpp @@ -283,7 +283,7 @@ void cpp_features_test_object_t::test<8>() ensure("init member inline 1", ii.mFoo==10); InitInlineWithConstructor iici; - ensure("init member inline 2", iici.mFoo=10); + ensure("init member inline 2", iici.mFoo==10); ensure("init member inline 3", iici.mBar==25); } diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp new file mode 100644 index 0000000000..f0a1b32eed --- /dev/null +++ b/indra/newview/tests/llluamanager_test.cpp @@ -0,0 +1,523 @@ +/** + * @file llluamanager_test.cpp + * @author Nat Goodspeed + * @date 2023-09-28 + * @brief Test for llluamanager. + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Copyright (c) 2023, Linden Research, Inc. + * $/LicenseInfo$ + */ + +// Precompiled header +//#include "llviewerprecompiledheaders.h" +// associated header +#include "../newview/llluamanager.h" +// STL headers +// std headers +#include <vector> +// external library headers +// other Linden headers +#include "../llcommon/tests/StringVec.h" +#include "../test/lltut.h" +#include "llapp.h" +#include "llcontrol.h" +#include "lldate.h" +#include "llevents.h" +#include "lleventcoro.h" +#include "llsdutil.h" +#include "lluri.h" +#include "lluuid.h" +#include "lua_function.h" +#include "lualistener.h" +#include "stringize.h" + +class LLTestApp : public LLApp +{ +public: + bool init() override { return true; } + bool cleanup() override { return true; } + bool frame() override { return true; } +}; + +LLControlGroup gSavedSettings("Global"); + +/***************************************************************************** +* TUT +*****************************************************************************/ +namespace tut +{ + struct llluamanager_data + { + llluamanager_data() + { + // Load gSavedSettings from source tree + // indra/newview/tests/llluamanager_test.cpp => + // indra/newview + auto newview{ fsyspath(__FILE__).parent_path().parent_path() }; + auto settings{ newview / "app_settings" / "settings.xml" }; + // true suppresses implicit declare; implicit declare requires + // that every variable in settings.xml has a Comment, which many don't. + gSavedSettings.loadFromFile(settings.u8string(), true); + // At test time, since we don't have the app bundle available, + // extend LuaRequirePath to include the require directory in the + // source tree. + auto require{ (newview / "scripts" / "lua" / "require").u8string() }; + auto paths{ gSavedSettings.getLLSD("LuaRequirePath") }; + bool found = false; + for (const auto& path : llsd::inArray(paths)) + { + if (path.asString() == require) + { + found = true; + break; + } + } + if (! found) + { + paths.append(require); + gSavedSettings.setLLSD("LuaRequirePath", paths); + } + } + // We need an LLApp instance because LLLUAmanager uses coroutines, + // which suspend, and when a coroutine suspends it checks LLApp state, + // and if it's not APP_STATUS_RUNNING the coroutine terminates. + LLTestApp mApp; + }; + typedef test_group<llluamanager_data> llluamanager_group; + typedef llluamanager_group::object object; + llluamanager_group llluamanagergrp("llluamanager"); + + static struct LuaExpr + { + std::string desc, expr; + LLSD expect; + } lua_expressions[] = { + { "nil", "nil", LLSD() }, + { "true", "true", true }, + { "false", "false", false }, + { "int", "17", 17 }, + { "real", "3.14", 3.14 }, + { "string", "'string'", "string" }, + // can't synthesize Lua userdata in Lua code: that can only be + // constructed by a C function + { "empty table", "{}", LLSD() }, + { "nested empty table", "{ 1, 2, 3, {}, 5 }", + llsd::array(1, 2, 3, LLSD(), 5) }, + { "nested non-empty table", "{ 1, 2, 3, {a=0, b=1}, 5 }", + llsd::array(1, 2, 3, llsd::map("a", 0, "b", 1), 5) }, + }; + + template<> template<> + void object::test<1>() + { + set_test_name("test Lua results"); + for (auto& luax : lua_expressions) + { + auto [count, result] = + LLLUAmanager::waitScriptLine("return " + luax.expr); + auto desc{ stringize("waitScriptLine(", luax.desc, "): ") }; + // if count < 0, report Lua error message + ensure_equals(desc + result.asString(), count, 1); + ensure_equals(desc + "result", result, luax.expect); + } + } + + void from_lua(const std::string& desc, std::string_view construct, const LLSD& expect) + { + LLSD fromlua; + LLStreamListener pump("testpump", + [&fromlua](const LLSD& data){ fromlua = data; }); + const std::string lua(stringize( + "data = ", construct, "\n" + "LL.post_on('testpump', data)\n" + )); + auto [count, result] = LLLUAmanager::waitScriptLine(lua); + // We woke up again ourselves because the coroutine running Lua has + // finished. But our Lua chunk didn't actually return anything, so we + // expect count to be 0 and result to be undefined. + ensure_equals(desc + ": " + result.asString(), count, 0); + ensure_equals(desc, fromlua, expect); + } + + template<> template<> + void object::test<2>() + { + set_test_name("LLSD from post_on()"); + for (auto& luax : lua_expressions) + { + from_lua(luax.desc, luax.expr, luax.expect); + } + } + + template<> template<> + void object::test<3>() + { + set_test_name("test post_on(), get_event_pumps(), get_event_next()"); + StringVec posts; + LLStreamListener pump("testpump", + [&posts](const LLSD& data) + { posts.push_back(data.asString()); }); + const std::string lua( + "-- test post_on,get_event_pumps,get_event_next\n" + "LL.post_on('testpump', 'entry')\n" + "LL.post_on('testpump', 'get_event_pumps()')\n" + "replypump, cmdpump = LL.get_event_pumps()\n" + "LL.post_on('testpump', replypump)\n" + "LL.post_on('testpump', 'get_event_next()')\n" + "pump, data = LL.get_event_next()\n" + "LL.post_on('testpump', data)\n" + "LL.post_on('testpump', 'exit')\n" + ); + // It's important to let the startScriptLine() coroutine run + // concurrently with ours until we've had a chance to post() our + // reply. + auto future = LLLUAmanager::startScriptLine(lua); + StringVec expected{ + "entry", + "get_event_pumps()", + "", + "get_event_next()", + "message", + "exit" + }; + expected[2] = posts.at(2); + LL_DEBUGS() << "Found pumpname '" << expected[2] << "'" << LL_ENDL; + LLEventPump& luapump{ LLEventPumps::instance().obtain(expected[2]) }; + LL_DEBUGS() << "Found pump '" << luapump.getName() << "', type '" + << LLError::Log::classname(luapump) + << "': post('" << expected[4] << "')" << LL_ENDL; + luapump.post(expected[4]); + auto [count, result] = future.get(); + ensure_equals("post_on(): " + result.asString(), count, 0); + ensure_equals("post_on() sequence", posts, expected); + } + + void round_trip(const std::string& desc, const LLSD& send, const LLSD& expect) + { + LLEventMailDrop testpump("testpump"); + const std::string lua( + "-- test LLSD round trip\n" + "replypump, cmdpump = LL.get_event_pumps()\n" + "LL.post_on('testpump', replypump)\n" + "pump, data = LL.get_event_next()\n" + "return data\n" + ); + auto future = LLLUAmanager::startScriptLine(lua); + // We woke up again ourselves because the coroutine running Lua has + // reached the get_event_next() call, which suspends the calling C++ + // coroutine (including the Lua code running on it) until we post + // something to that reply pump. + auto luapump{ llcoro::suspendUntilEventOn(testpump).asString() }; + LLEventPumps::instance().post(luapump, send); + // The C++ coroutine running the Lua script is now ready to run. Run + // it so it will echo the LLSD back to us. + auto [count, result] = future.get(); + ensure_equals(stringize("round_trip(", desc, "): ", result.asString()), count, 1); + ensure_equals(desc, result, expect); + } + + // Define an RTItem to be used for round-trip LLSD testing: what it is, + // what we send to Lua, what we expect to get back. They could be the + // same. + struct RTItem + { + RTItem(const std::string& name, const LLSD& send, const LLSD& expect): + mName(name), + mSend(send), + mExpect(expect) + {} + RTItem(const std::string& name, const LLSD& both): + mName(name), + mSend(both), + mExpect(both) + {} + + std::string mName; + LLSD mSend, mExpect; + }; + + template<> template<> + void object::test<4>() + { + set_test_name("LLSD round trip"); + LLSD::Binary binary{ 3, 1, 4, 1, 5, 9, 2, 6, 5 }; + const char* uuid{ "01234567-abcd-0123-4567-0123456789ab" }; + const char* date{ "2023-10-04T21:06:00Z" }; + const char* uri{ "https://secondlife.com/index.html" }; + std::vector<RTItem> items{ + RTItem("undefined", LLSD()), + RTItem("true", true), + RTItem("false", false), + RTItem("int", 17), + RTItem("real", 3.14), + RTItem("int real", 27.0, 27), + RTItem("string", "string"), + RTItem("binary", binary), + RTItem("empty array", LLSD::emptyArray(), LLSD()), + RTItem("empty map", LLSD::emptyMap(), LLSD()), + RTItem("UUID", LLUUID(uuid), uuid), + RTItem("date", LLDate(date), date), + RTItem("uri", LLURI(uri), uri) + }; + // scalars + for (const auto& item: items) + { + round_trip(item.mName, item.mSend, item.mExpect); + } + + // array + LLSD send_array{ LLSD::emptyArray() }, expect_array{ LLSD::emptyArray() }; + for (const auto& item: items) + { + send_array.append(item.mSend); + expect_array.append(item.mExpect); + } + // exercise the array tail trimming below + send_array.append(items[0].mSend); + expect_array.append(items[0].mExpect); + // Lua takes a table value of nil to mean: don't store this key. An + // LLSD array containing undefined entries (converted to nil) leaves + // "holes" in the Lua table. These will be converted back to undefined + // LLSD entries -- except at the end. Trailing undefined entries are + // simply omitted from the table -- so the table converts back to a + // shorter LLSD array. We've constructed send_array and expect_array + // according to 'items' above -- but truncate from expect_array any + // trailing entries whose mSend will map to Lua nil. + while (expect_array.size() > 0 && + send_array[expect_array.size() - 1].isUndefined()) + { + expect_array.erase(LLSD::Integer(expect_array.size() - 1)); + } + round_trip("array", send_array, expect_array); + + // map + LLSD send_map{ LLSD::emptyMap() }, expect_map{ LLSD::emptyMap() }; + for (const auto& item: items) + { + send_map[item.mName] = item.mSend; + // see comment in the expect_array truncation loop above -- + // Lua never stores table entries with nil values + if (item.mSend.isDefined()) + { + expect_map[item.mName] = item.mExpect; + } + } + round_trip("map", send_map, expect_map); + + // deeply nested map: exceed Lua's default stack space (20), + // i.e. verify that we have the right checkstack() calls + for (int i = 0; i < 20; ++i) + { + LLSD new_send_map{ send_map }, new_expect_map{ expect_map }; + new_send_map["nested map"] = send_map; + new_expect_map["nested map"] = expect_map; + send_map = new_send_map; + expect_map = new_expect_map; + } + round_trip("nested map", send_map, expect_map); + } + + template<> template<> + void object::test<5>() + { + set_test_name("leap.request() from main thread"); + const std::string lua( + "-- leap.request() from main thread\n" + "\n" + "leap = require 'leap'\n" + "\n" + "return {\n" + " a=leap.request('echo', {data='a'}).data,\n" + " b=leap.request('echo', {data='b'}).data\n" + "}\n" + ); + + LLStreamListener pump( + "echo", + [](const LLSD& data) + { + LL_DEBUGS("Lua") << "echo pump got: " << data << LL_ENDL; + sendReply(data, data); + }); + + auto [count, result] = LLLUAmanager::waitScriptLine(lua); + ensure_equals("Lua script didn't return item", count, 1); + ensure_equals("echo failed", result, llsd::map("a", "a", "b", "b")); + } + + template<> template<> + void object::test<6>() + { + set_test_name("interleave leap.request() responses"); + const std::string lua( + "-- interleave leap.request() responses\n" + "\n" + "fiber = require('fiber')\n" + "leap = require('leap')\n" + "local function debug(...) end\n" + "-- debug = require('printf')\n" + "\n" + "-- negative priority ensures catchall is always last\n" + "catchall = leap.WaitFor(-1, 'catchall')\n" + "function catchall:filter(pump, data)\n" + " debug('catchall:filter(%s, %s)', pump, data)\n" + " return data\n" + "end\n" + "\n" + "-- but first, catch events with 'special' key\n" + "catch_special = leap.WaitFor(2, 'catch_special')\n" + "function catch_special:filter(pump, data)\n" + " debug('catch_special:filter(%s, %s)', pump, data)\n" + " return if data['special'] ~= nil then data else nil\n" + "end\n" + "\n" + "function drain(waitfor)\n" + " debug('%s start', waitfor.name)\n" + " -- It seems as though we ought to be able to code this loop\n" + " -- over waitfor:wait() as:\n" + " -- for item in waitfor.wait, waitfor do\n" + " -- However, that seems to stitch a detour through C code into\n" + " -- the coroutine call stack, which prohibits coroutine.yield():\n" + " -- 'attempt to yield across metamethod/C-call boundary'\n" + " -- So we resort to two different calls to waitfor:wait().\n" + " local item = waitfor:wait()\n" + " while item do\n" + " debug('%s caught %s', waitfor.name, item)\n" + " item = waitfor:wait()\n" + " end\n" + " debug('%s done', waitfor.name)\n" + "end\n" + "\n" + "function requester(name)\n" + " debug('requester(%s) start', name)\n" + " local response = leap.request('testpump', {name=name})\n" + " debug('requester(%s) got %s', name, response)\n" + " -- verify that the correct response was dispatched to this coroutine\n" + " assert(response.name == name)\n" + "end\n" + "\n" + "-- fiber.print_all()\n" + "fiber.launch('catchall', drain, catchall)\n" + "fiber.launch('catch_special', drain, catch_special)\n" + "fiber.launch('requester(a)', requester, 'a')\n" + "fiber.launch('requester(b)', requester, 'b')\n" + // A script can normally count on an implicit fiber.run() call + // because fiber.lua calls LL.atexit(fiber.run). But atexit() + // functions are called by ~LuaState(), which (in the code below) + // won't be called until *after* we expect to interact with the + // various fibers. So make an explicit call for test purposes. + "fiber.run()\n" + ); + + LLSD requests; + LLStreamListener pump( + "testpump", + [&requests](const LLSD& data) + { + LL_DEBUGS("Lua") << "testpump got: " << data << LL_ENDL; + requests.append(data); + }); + + auto future = LLLUAmanager::startScriptLine(lua); + // LuaState::expr() periodically interrupts a running chunk to ensure + // the rest of our coroutines get cycles. Nonetheless, for this test + // we have to wait until both requester() coroutines have posted and + // are waiting for a reply. + for (unsigned count=0; count < 100; ++count) + { + if (requests.size() == 2) + break; + llcoro::suspend(); + } + ensure_equals("didn't get both requests", requests.size(), 2); + auto replyname{ requests[0]["reply"].asString() }; + auto& replypump{ LLEventPumps::instance().obtain(replyname) }; + // moreover, we expect they arrived in the order they were created + ensure_equals("a wasn't first", requests[0]["name"].asString(), "a"); + ensure_equals("b wasn't second", requests[1]["name"].asString(), "b"); + replypump.post(llsd::map("special", "K")); + // respond to requester(b) FIRST + replypump.post(requests[1]); + replypump.post(llsd::map("name", "not special")); + // now respond to requester(a) + replypump.post(requests[0]); + // tell leap we're done + replypump.post(LLSD()); + auto [count, result] = future.get(); + ensure_equals("leap.lua: " + result.asString(), count, 0); + } + + template<> template<> + void object::test<7>() + { + set_test_name("stop hanging Lua script"); + const std::string lua( + "-- hanging Lua script should terminate\n" + "\n" + "LL.get_event_next()\n" + ); + auto future = LLLUAmanager::startScriptLine(lua); + // Poke LLTestApp to send its preliminary shutdown message. + mApp.setQuitting(); + // but now we have to give the startScriptLine() coroutine a chance to run + auto [count, result] = future.get(); + ensure_equals("killed Lua script terminated normally", count, -1); + ensure_contains("unexpected killed Lua script error", + result.asString(), "viewer is stopping"); + } + + template<> template<> + void object::test<8>() + { + set_test_name("stop looping Lua script"); + const std::string desc("looping Lua script should terminate"); + const std::string lua( + "-- " + desc + "\n" + "\n" + "while true do\n" + " x = 1\n" + "end\n" + ); + auto [count, result] = LLLUAmanager::waitScriptLine(lua); + // We expect the above erroneous script has been forcibly terminated + // because it ran too long without doing any actual work. + ensure_equals(desc + " count: " + result.asString(), count, -1); + ensure_contains(desc + " result", result.asString(), "terminated"); + } + + template <typename T> + struct Visible + { + Visible(T name): name(name) + { + LL_INFOS() << "Visible<" << LLError::Log::classname<T>() << ">('" << name << "')" << LL_ENDL; + } + Visible(const Visible&) = delete; + Visible& operator=(const Visible&) = delete; + ~Visible() + { + LL_INFOS() << "~Visible<" << LLError::Log::classname<T>() << ">('" << name << "')" << LL_ENDL; + } + T name; + }; + + template<> template<> + void object::test<9>() + { + set_test_name("track distinct lua_emplace<T>() types"); + LuaState L; + lua_emplace<Visible<std::string>>(L, "std::string 0"); + int st0tag = lua_userdatatag(L, -1); + lua_emplace<Visible<const char*>>(L, "const char* 0"); + int cp0tag = lua_userdatatag(L, -1); + lua_emplace<Visible<std::string>>(L, "std::string 1"); + int st1tag = lua_userdatatag(L, -1); + lua_emplace<Visible<const char*>>(L, "const char* 1"); + int cp1tag = lua_userdatatag(L, -1); + lua_settop(L, 0); + ensure_equals("lua_emplace<std::string>() tags diverge", st0tag, st1tag); + ensure_equals("lua_emplace<const char*>() tags diverge", cp0tag, cp1tag); + ensure_not_equals("lua_emplace<>() tags collide", st0tag, cp0tag); + } +} // namespace tut diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index b2f9654eb3..daa7e58211 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -65,6 +65,8 @@ class ViewerManifest(LLManifest): self.path(src="../../scripts/messages/message_template.msg", dst="app_settings/message_template.msg") self.path(src="../../etc/message.xml", dst="app_settings/message.xml") + os.environ["XZ_DEFAULTS"] = "-T0" + if self.is_packaging_viewer(): with self.prefix(src_dst="app_settings"): self.exclude("logcontrol.xml") @@ -166,6 +168,8 @@ class ViewerManifest(LLManifest): self.path("*/*/*/*.js") self.path("*/*/*.html") + self.path('scripts/lua') + #build_data.json. Standard with exception handling is fine. If we can't open a new file for writing, we have worse problems #platform is computed above with other arg parsing build_data_dict = {"Type":"viewer","Version":'.'.join(self.args['version']), @@ -275,13 +279,13 @@ class ViewerManifest(LLManifest): # All lines up to and including the first blank line are the file header; skip them lines.reverse() # so that pop will pull from first to last line - while not re.match("\s*$", lines.pop()) : + while not re.match(r"\s*$", lines.pop()) : pass # do nothing # A line that starts with a non-whitespace character is a name; all others describe contributions, so collect the names names = [] for line in lines : - if re.match("\S", line) : + if re.match(r"\S", line) : names.append(line.rstrip()) # It's not fair to always put the same people at the head of the list random.shuffle(names) @@ -1189,6 +1193,9 @@ class LinuxManifest(ViewerManifest): super(LinuxManifest, self).construct() pkgdir = os.path.join(self.args['build'], os.pardir, 'packages') + if "package_dir" in self.args: + pkgdir = self.args['package_dir'] + relpkgdir = os.path.join(pkgdir, "lib", "release") debpkgdir = os.path.join(pkgdir, "lib", "debug") @@ -1207,49 +1214,125 @@ class LinuxManifest(ViewerManifest): with self.prefix(dst="bin"): self.path("secondlife-bin","do-not-directly-run-secondlife-bin") - self.path("../linux_crash_logger/linux-crash-logger","linux-crash-logger.bin") + #self.path("../linux_crash_logger/linux-crash-logger","linux-crash-logger.bin") self.path2basename("../llplugin/slplugin", "SLPlugin") #this copies over the python wrapper script, associated utilities and required libraries, see SL-321, SL-322 and SL-323 - with self.prefix(src="../viewer_components/manager", dst=""): - self.path("*.py") + #with self.prefix(src="../viewer_components/manager", dst=""): + # self.path("*.py") # recurses, packaged again self.path("res-sdl") + # We copy ll_icon.BMP in CMakeLists.txt to newview/res-sdl and this will let the above self.path step take care of copying + # the correct branded icon # Get the icons based on the channel type icon_path = self.icon_path() - print("DEBUG: icon_path '%s'" % icon_path) + #print("DEBUG: icon_path '%s'" % icon_path) with self.prefix(src=icon_path) : self.path("secondlife_256.png","secondlife_icon.png") with self.prefix(dst="res-sdl") : self.path("secondlife_256.BMP","ll_icon.BMP") - # plugins - with self.prefix(src="../media_plugins", dst="bin/llplugin"): - self.path("gstreamer010/libmedia_plugin_gstreamer010.so", - "libmedia_plugin_gstreamer.so") - self.path2basename("libvlc", "libmedia_plugin_libvlc.so") - - with self.prefix(src=os.path.join(pkgdir, 'lib', 'vlc', 'plugins'), dst="bin/llplugin/vlc/plugins"): - self.path( "plugins.dat" ) - self.path( "*/*.so" ) + with self.prefix(src=os.path.join(self.args['build'], os.pardir, "llwebrtc" ), dst="lib"): + self.path("libllwebrtc.so") - with self.prefix(src=os.path.join(pkgdir, 'lib' ), dst="lib"): - self.path( "libvlc*.so*" ) - - # llcommon - if not self.path("../llcommon/libllcommon.so", "lib/libllcommon.so"): - print("Skipping llcommon.so (assuming llcommon was linked statically)") + # plugins + with self.prefix(src=os.path.join(self.args['build'], os.pardir, 'media_plugins'), dst="bin/llplugin"): + self.path("gstreamer10/libmedia_plugin_gstreamer10.so", "libmedia_plugin_gstreamer.so") + + with self.prefix(src=os.path.join(self.args['build'], os.pardir, 'media_plugins'), dst="bin/llplugin"): + self.path("cef/libmedia_plugin_cef.so", "libmedia_plugin_cef.so" ) + with self.prefix(src=os.path.join(pkgdir, 'lib', 'release'), dst="lib"): + self.path( "libcef.so" ) + + self.path( "libEGL*" ) + self.path( "libvulkan*" ) + self.path( "libvk_swiftshader*" ) + self.path( "libGLESv2*" ) + self.path( "vk_swiftshader_icd.json") + + with self.prefix(src=os.path.join(pkgdir, 'bin', 'release'), dst="bin"): + self.path( "chrome-sandbox" ) + self.path( "dullahan_host" ) + self.path( "snapshot_blob.bin" ) + self.path( "v8_context_snapshot.bin" ) + with self.prefix(src=os.path.join(pkgdir, 'bin', 'release'), dst="lib"): + self.path( "snapshot_blob.bin" ) + self.path( "v8_context_snapshot.bin" ) + + with self.prefix(src=os.path.join(pkgdir, 'resources'), dst="lib"): + self.path( "chrome_100_percent.pak" ) + self.path( "chrome_200_percent.pak" ) + self.path( "resources.pak" ) + self.path( "icudtl.dat" ) + + with self.prefix(src=os.path.join(pkgdir, 'resources', 'locales'), dst=os.path.join('lib', 'locales')): + self.path("am.pak") + self.path("ar.pak") + self.path("bg.pak") + self.path("bn.pak") + self.path("ca.pak") + self.path("cs.pak") + self.path("da.pak") + self.path("de.pak") + self.path("el.pak") + self.path("en-GB.pak") + self.path("en-US.pak") + self.path("es-419.pak") + self.path("es.pak") + self.path("et.pak") + self.path("fa.pak") + self.path("fi.pak") + self.path("fil.pak") + self.path("fr.pak") + self.path("gu.pak") + self.path("he.pak") + self.path("hi.pak") + self.path("hr.pak") + self.path("hu.pak") + self.path("id.pak") + self.path("it.pak") + self.path("ja.pak") + self.path("kn.pak") + self.path("ko.pak") + self.path("lt.pak") + self.path("lv.pak") + self.path("ml.pak") + self.path("mr.pak") + self.path("ms.pak") + self.path("nb.pak") + self.path("nl.pak") + self.path("pl.pak") + self.path("pt-BR.pak") + self.path("pt-PT.pak") + self.path("ro.pak") + self.path("ru.pak") + self.path("sk.pak") + self.path("sl.pak") + self.path("sr.pak") + self.path("sv.pak") + self.path("sw.pak") + self.path("ta.pak") + self.path("te.pak") + self.path("th.pak") + self.path("tr.pak") + self.path("uk.pak") + self.path("vi.pak") + self.path("zh-CN.pak") + self.path("zh-TW.pak") self.path("featuretable_linux.txt") self.path("cube.dae") - with self.prefix(src=pkgdir): + with self.prefix(src=pkgdir, dst="bin"): self.path("ca-bundle.crt") def package_finish(self): installer_name = self.installer_base_name() + # When running as a GitHub Action job, RUNNER_TEMP is defined as the tmp dir + RUNNER_TEMP = os.getenv('RUNNER_TEMP') + self.strip_binaries() # Fix access permissions @@ -1264,84 +1347,77 @@ class LinuxManifest(ViewerManifest): # temporarily move directory tree so that it has the right # name in the tarfile realname = self.get_dst_prefix() - tempname = self.build_path_of(installer_name) - self.run_command(["mv", realname, tempname]) + versionedName = self.build_path_of(installer_name) + + tarName = versionedName + ".tar.xz" + + # If using a github runner we divert packaging a little. Considering this wil be a VM/docker image + # we can just pack the final installer into RUNNER_TEMP and not into the usual stop we'd pick when + # not building a GHA release + if RUNNER_TEMP: + tarName = os.path.join(RUNNER_TEMP, self.package_file) + + self.run_command(["mv", realname, versionedName]) + try: # only create tarball if it's a release build. if self.args['buildtype'].lower() == 'release': - # --numeric-owner hides the username of the builder for - # security etc. self.run_command(['tar', '-C', self.get_build_prefix(), '--numeric-owner', '-cJf', - tempname + '.tar.xz', installer_name]) + tarName, installer_name]) + self.set_github_output_path('viewer_app', tarName) else: print("Skipping %s.tar.xz for non-Release build (%s)" % \ (installer_name, self.args['buildtype'])) finally: - self.run_command(["mv", tempname, realname]) + self.run_command(["mv", versionedName, realname]) def strip_binaries(self): + doStrip = False if self.args['buildtype'].lower() == 'release' and self.is_packaging_viewer(): - print("* Going strip-crazy on the packaged binaries, since this is a RELEASE build") + doStrip = True + # In case of flatpak flatpak-build will call strip, disable doStrip here to get a flatpak symbol package. Increases flatpak size by about 1G + if "FLATPAK_DEST" in os.environ: + doStrip = True + + if doStrip: + print("* Going strip-crazy on the packaged binaries, since this is a Release build") # makes some small assumptions about our packaged dir structure self.run_command( ["find"] + [os.path.join(self.get_dst_prefix(), dir) for dir in ('bin', 'lib')] + ['-type', 'f', '!', '-name', '*.py', + '!', '-name', '*.pak', + '!', '-name', '*.bin', + '!', '-name', '*.dat', + '!', '-name', '*.crt', + '!', '-name', '*.dll', + '!', '-name', '*.lib', '!', '-name', 'update_install', '-exec', 'strip', '-S', '{}', ';']) -class Linux_i686_Manifest(LinuxManifest): - address_size = 32 +class Linux_x86_64_Manifest(LinuxManifest): + address_size = 64 def construct(self): - super(Linux_i686_Manifest, self).construct() + super(Linux_x86_64_Manifest, self).construct() pkgdir = os.path.join(self.args['build'], os.pardir, 'packages') + if "package_dir" in self.args: + pkgdir = self.args['package_dir'] + relpkgdir = os.path.join(pkgdir, "lib", "release") debpkgdir = os.path.join(pkgdir, "lib", "debug") with self.prefix(src=relpkgdir, dst="lib"): - self.path("libdb*.so") - self.path("libuuid.so*") - self.path("libSDL-1.2.so.*") - self.path("libdirectfb-1.*.so.*") - self.path("libfusion-1.*.so.*") - self.path("libdirect-1.*.so.*") - self.path("libopenjp2.so*") - self.path("libdirectfb-1.4.so.5") - self.path("libfusion-1.4.so.5") - self.path("libdirect-1.4.so.5*") + self.path("libapr-1.so*") + self.path("libaprutil-1.so*") + self.path_optional("libSDL*.so.*") + + self.path_optional("libjemalloc*.so") + 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 - # wildcards, though, this self.path() call specifically named - # libfontconfig.so.1.4.4 WITHOUT also copying the symlinks. When I - # (nat) changed the call to self.path("libfontconfig.so.*"), we - # ended up with the libfontconfig.so.1 symlink in the target - # directory as well. But guess what! At least on Ubuntu 10.04, - # certain viewer fonts look terrible with libfontconfig.so.1 - # present in the target directory. Removing that symlink suffices - # to improve them. I suspect that means we actually do better when - # the viewer fails to find our packaged libfontconfig.so*, falling - # back on the system one instead -- but diagnosing and fixing that - # is a bit out of scope for the present project. Meanwhile, this - # particular wildcard specification gets us exactly what the - # previous call did, without having to explicitly state the - # version number. - self.path("libfontconfig.so.*.*") - - # Include libfreetype.so. but have it work as libfontconfig does. - self.path("libfreetype.so.*.*") - - try: - self.path("libtcmalloc.so*") #formerly called google perf tools - pass - except: - print("tcmalloc files not found, skipping") - pass # Vivox runtimes with self.prefix(src=relpkgdir, dst="bin"): @@ -1353,17 +1429,6 @@ class Linux_i686_Manifest(LinuxManifest): self.path("libvivoxsdk.so") self.strip_binaries() - - -class Linux_x86_64_Manifest(LinuxManifest): - address_size = 64 - - def construct(self): - super(Linux_x86_64_Manifest, self).construct() - - # support file for valgrind debug tool - self.path("secondlife-i686.supp") - ################################################################ if __name__ == "__main__": diff --git a/indra/test/debug.h b/indra/test/debug.h index 1579bb9c86..ea9c634cc7 100644 --- a/indra/test/debug.h +++ b/indra/test/debug.h @@ -30,43 +30,56 @@ #define LL_DEBUG_H #include "print.h" +#include "stringize.h" +#include <exception> // std::uncaught_exceptions() /***************************************************************************** * Debugging stuff *****************************************************************************/ /** - * This class is intended to illuminate entry to a given block, exit from the - * same block and checkpoints along the way. It also provides a convenient - * place to turn std::cerr output on and off. - * - * If the environment variable LOGTEST is non-empty, each Debug instance will - * announce its construction and destruction, presumably at entry and exit to - * the block in which it's declared. Moreover, any arguments passed to its - * operator()() will be streamed to std::cerr, prefixed by the block - * description. + * Return true if the environment variable LOGTEST is non-empty. * * The variable LOGTEST is used because that's the environment variable * checked by test.cpp, our TUT main() program, to turn on LLError logging. It * is expected that Debug is solely for use in test programs. */ +inline +bool LOGTEST_enabled() +{ + auto LOGTEST{ getenv("LOGTEST") }; + // debug output enabled when LOGTEST is set AND non-empty + return LOGTEST && *LOGTEST; +} + +/** + * This class is intended to illuminate entry to a given block, exit from the + * same block and checkpoints along the way. It also provides a convenient + * place to turn std::cerr output on and off. + * + * If enabled, each Debug instance will announce its construction and + * destruction, presumably at entry and exit to the block in which it's + * declared. Moreover, any arguments passed to its operator()() will be + * streamed to std::cerr, prefixed by the block description. + */ class Debug { public: - Debug(const std::string& block): - mBlock(block), - mLOGTEST(getenv("LOGTEST")), - // debug output enabled when LOGTEST is set AND non-empty - mEnabled(mLOGTEST && *mLOGTEST) + template <typename... ARGS> + Debug(ARGS&&... args): + mBlock(stringize(std::forward<ARGS>(args)...)), + mEnabled(LOGTEST_enabled()) { (*this)("entry"); } // non-copyable Debug(const Debug&) = delete; + Debug& operator=(const Debug&) = delete; ~Debug() { - (*this)("exit"); + auto exceptional{ std::uncaught_exceptions()? "exceptional " : "" }; + (*this)(exceptional, "exit"); } template <typename... ARGS> @@ -80,7 +93,6 @@ public: private: const std::string mBlock; - const char* mLOGTEST; bool mEnabled; }; @@ -88,20 +100,19 @@ private: // of the Debug block. #define DEBUG Debug debug(LL_PRETTY_FUNCTION) -// These BEGIN/END macros are specifically for debugging output -- please -// don't assume you must use such for coroutines in general! They only help to -// make control flow (as well as exception exits) explicit. -#define BEGIN \ -{ \ - DEBUG; \ - try +/// If enabled, debug_expr(expression) gives you output concerning an inline +/// expression such as a class member initializer. +#define debug_expr(expr) debug_expr_(#expr, [&](){ return expr; }) -#define END \ - catch (...) \ - { \ - debug("*** exceptional "); \ - throw; \ - } \ +template <typename EXPR> +inline auto debug_expr_(const char* strexpr, EXPR&& lambda) +{ + if (! LOGTEST_enabled()) + return std::forward<EXPR>(lambda)(); + print("Before: ", strexpr); + auto result{ std::forward<EXPR>(lambda)() }; + print(strexpr, " -> ", result); + return result; } #endif /* ! defined(LL_DEBUG_H) */ diff --git a/indra/test/io.cpp b/indra/test/io.cpp index f77402065a..24e1a782d3 100644 --- a/indra/test/io.cpp +++ b/indra/test/io.cpp @@ -45,6 +45,7 @@ #include "llcommon.h" #include "lluuid.h" #include "llinstantmessage.h" +#include "stringize.h" namespace tut { @@ -1116,6 +1117,9 @@ namespace tut template<> template<> void fitness_test_object::test<5>() { + skip("Test is strongly timing dependent, " + "and on slow CI machines it fails way too often."); + const int retries = 100; // Set up the server LLPumpIO::chain_t chain; typedef LLCloneIOFactory<LLIOSleeper> sleeper_t; @@ -1129,9 +1133,12 @@ namespace tut chain.push_back(LLIOPipe::ptr_t(server)); mPump->addChain(chain, NEVER_CHAIN_EXPIRY_SECS); // We need to tickle the pump a little to set up the listen() - pump_loop(mPump, 0.1f); + for (int retry = 0; mPump->runningChains() < 1 && retry < retries; ++retry) + { + pump_loop(mPump, 0.1f); + } auto count = mPump->runningChains(); - ensure_equals("server chain onboard", count, 1); + ensure_equals("server chain 1 onboard", count, 1); LL_DEBUGS() << "** Server is up." << LL_ENDL; // Set up the client @@ -1140,9 +1147,12 @@ namespace tut bool connected = client->blockingConnect(server_host); ensure("Connected to server", connected); LL_DEBUGS() << "connected" << LL_ENDL; - pump_loop(mPump,0.1f); + for (int retry = 0; mPump->runningChains() < 2 && retry < retries; ++retry) + { + pump_loop(mPump,0.1f); + } count = mPump->runningChains(); - ensure_equals("server chain onboard", count, 2); + ensure_equals("server chain 2 onboard", count, 2); LL_DEBUGS() << "** Client is connected." << LL_ENDL; // We have connected, since the socket reader does not block, @@ -1156,20 +1166,32 @@ namespace tut chain.clear(); // pump for a bit and make sure all 3 chains are running - pump_loop(mPump,0.1f); + for (int retry = 0; mPump->runningChains() < 3 && retry < retries; ++retry) + { + pump_loop(mPump, 0.1f); + } count = mPump->runningChains(); - // ensure_equals("client chain onboard", count, 3); commented out because it fails frequently - appears to be timing sensitive + ensure_equals("client chain onboard", count, 3); LL_DEBUGS() << "** request should have been sent." << LL_ENDL; // pump for long enough the the client socket closes, and the // server socket should not be closed yet. - pump_loop(mPump,0.2f); + for (int retry = 0; mPump->runningChains() == 3 && retry < retries; ++retry) + { + pump_loop(mPump, 0.1f); + } + // We used to test for count == 2 here, but on a slow test machine it + // can happen that not just one but two chains close before we reach + // this point. count = mPump->runningChains(); - ensure_equals("client chain timed out ", count, 2); + ensure(stringize("client chain timed out: count ", count), count < 3); LL_DEBUGS() << "** client chain should be closed." << LL_ENDL; // At this point, the socket should be closed by the timeout - pump_loop(mPump,1.0f); + for (int retry = 0; mPump->runningChains() > 1 && retry < retries; ++retry) + { + pump_loop(mPump, 0.1f); + } count = mPump->runningChains(); ensure_equals("accepted socked close", count, 1); LL_DEBUGS() << "** Sleeper should have timed out.." << LL_ENDL; diff --git a/indra/test/llevents_tut.cpp b/indra/test/llevents_tut.cpp index bf5cd3f853..1f723c84b6 100644 --- a/indra/test/llevents_tut.cpp +++ b/indra/test/llevents_tut.cpp @@ -428,7 +428,7 @@ void events_object::test<9>() { set_test_name("listen(boost::bind(...TempListener...))"); // listen() can't do anything about a plain TempListener instance: - // it's not managed with shared_ptr, nor is it an LLEventTrackable subclass + // it's not managed with shared_ptr bool live = false; LLEventPump& heaptest(pumps.obtain("heaptest")); LLBoundListener connection; @@ -452,60 +452,4 @@ void events_object::test<9>() heaptest.stopListening("temp"); } -class TempTrackableListener: public TempListener, public LLEventTrackable -{ -public: - TempTrackableListener(const std::string& name, bool& liveFlag): - TempListener(name, liveFlag) - {} -}; - -template<> template<> -void events_object::test<10>() -{ - set_test_name("listen(boost::bind(...TempTrackableListener ref...))"); - bool live = false; - LLEventPump& heaptest(pumps.obtain("heaptest")); - LLBoundListener connection; - { - TempTrackableListener tempListener("temp", live); - ensure("TempTrackableListener constructed", live); - connection = heaptest.listen(tempListener.getName(), - boost::bind(&TempTrackableListener::call, - boost::ref(tempListener), _1)); - heaptest.post(1); - check_listener("received", tempListener, 1); - } // presumably this will make tempListener go away? - // verify that - ensure("TempTrackableListener destroyed", ! live); - ensure("implicit disconnect", ! connection.connected()); - // now just make sure we don't blow up trying to access a freed object! - heaptest.post(2); -} - -template<> template<> -void events_object::test<11>() -{ - set_test_name("listen(boost::bind(...TempTrackableListener pointer...))"); - bool live = false; - LLEventPump& heaptest(pumps.obtain("heaptest")); - LLBoundListener connection; - { - TempTrackableListener* newListener(new TempTrackableListener("temp", live)); - ensure("TempTrackableListener constructed", live); - connection = heaptest.listen(newListener->getName(), - boost::bind(&TempTrackableListener::call, - newListener, _1)); - heaptest.post(1); - check_listener("received", *newListener, 1); - // explicitly destroy newListener - delete newListener; - } - // verify that - ensure("TempTrackableListener destroyed", ! live); - ensure("implicit disconnect", ! connection.connected()); - // now just make sure we don't blow up trying to access a freed object! - heaptest.post(2); -} - } // namespace tut diff --git a/indra/test/lltut.h b/indra/test/lltut.h index e56b4e8d1c..fbf60444be 100644 --- a/indra/test/lltut.h +++ b/indra/test/lltut.h @@ -31,6 +31,8 @@ #include "is_approx_equal_fraction.h" // instead of llmath.h #include <cstring> +#include <string> +#include <vector> class LLDate; class LLSD; diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h index 8027f95728..96b19523ab 100644 --- a/indra/test/namedtempfile.h +++ b/indra/test/namedtempfile.h @@ -32,18 +32,18 @@ class NamedTempFile: public boost::noncopyable { LOG_CLASS(NamedTempFile); public: - NamedTempFile(const std::string_view& pfx, - const std::string_view& content, - const std::string_view& sfx=std::string_view("")) + NamedTempFile(std::string_view pfx, + std::string_view content, + std::string_view sfx=std::string_view("")) { createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx); } // Disambiguate when passing string literal -- unclear why a string // literal should be ambiguous wrt std::string_view and Streamer - NamedTempFile(const std::string_view& pfx, + NamedTempFile(std::string_view pfx, const char* content, - const std::string_view& sfx=std::string_view("")) + std::string_view sfx=std::string_view("")) { createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx); } @@ -53,9 +53,9 @@ public: // (boost::phoenix::placeholders::arg1 << "the value is " << 17 << '\n') typedef std::function<void(std::ostream&)> Streamer; - NamedTempFile(const std::string_view& pfx, + NamedTempFile(std::string_view pfx, const Streamer& func, - const std::string_view& sfx=std::string_view("")) + std::string_view sfx=std::string_view("")) { createFile(pfx, func, sfx); } @@ -94,8 +94,8 @@ public: return out; } - static boost::filesystem::path temp_path(const std::string_view& pfx="", - const std::string_view& sfx="") + static boost::filesystem::path temp_path(std::string_view pfx="", + std::string_view sfx="") { // This variable is set by GitHub actions and is the recommended place // to put temp files belonging to an actions job. @@ -114,9 +114,9 @@ public: } protected: - void createFile(const std::string_view& pfx, + void createFile(std::string_view pfx, const Streamer& func, - const std::string_view& sfx) + std::string_view sfx) { // Create file in a temporary place. mPath = temp_path(pfx, sfx); @@ -137,7 +137,7 @@ class NamedExtTempFile: public NamedTempFile { LOG_CLASS(NamedExtTempFile); public: - NamedExtTempFile(const std::string& ext, const std::string_view& content): + NamedExtTempFile(const std::string& ext, std::string_view content): NamedTempFile(remove_dot(ext), content, ensure_dot(ext)) {} diff --git a/indra/test/print.h b/indra/test/print.h index 7577698cc8..6906eae581 100644 --- a/indra/test/print.h +++ b/indra/test/print.h @@ -23,7 +23,9 @@ struct NONL_t {}; inline void print() { +#ifdef LL_TEST std::cerr << std::endl; +#endif } // print(NONL) is a no-op @@ -35,8 +37,10 @@ void print(NONL_t) template <typename T, typename... ARGS> void print(T&& first, ARGS&&... rest) { +#ifdef LL_TEST std::cerr << first; print(std::forward<ARGS>(rest)...); +#endif } #endif /* ! defined(LL_PRINT_H) */ diff --git a/indra/test/test.cpp b/indra/test/test.cpp index 172b6e3542..c002edd94c 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -35,13 +35,15 @@ */ #include "linden_common.h" -#include "llerrorcontrol.h" -#include "lltut.h" +#include "llexception.h" #include "chained_callback.h" -#include "stringize.h" -#include "namedtempfile.h" +#include "fsyspath.h" +#include "llerrorcontrol.h" #include "lltrace.h" #include "lltracethreadrecorder.h" +#include "lltut.h" +#include "namedtempfile.h" +#include "stringize.h" #include "apr_pools.h" #include "apr_getopt.h" @@ -164,10 +166,6 @@ public: LLTestCallback(bool verbose_mode, std::ostream *stream, std::shared_ptr<LLReplayLog> replayer) : mVerboseMode(verbose_mode), - mTotalTests(0), - mPassedTests(0), - mFailedTests(0), - mSkippedTests(0), // By default, capture a shared_ptr to std::cout, with a no-op "deleter" // so that destroying the shared_ptr makes no attempt to delete std::cout. mStream(std::shared_ptr<std::ostream>(&std::cout, [](std::ostream*){})), @@ -203,6 +201,8 @@ public: virtual void group_started(const std::string& name) { LL_INFOS("TestRunner")<<"Unit test group_started name=" << name << LL_ENDL; *mStream << "Unit test group_started name=" << name << std::endl; + mGroup = name; + mGroupTests = 0; super::group_started(name); } @@ -215,6 +215,7 @@ public: virtual void test_completed(const tut::test_result& tr) { ++mTotalTests; + ++mGroupTests; // If this test failed, dump requested log messages BEFORE stating the // test result. @@ -302,12 +303,15 @@ public: super::run_completed(); } + std::string mGroup; + int mGroupTests{ 0 }; + protected: - bool mVerboseMode; - int mTotalTests; - int mPassedTests; - int mFailedTests; - int mSkippedTests; + bool mVerboseMode{ false }; + int mTotalTests{ 0 }; + int mPassedTests{ 0 }; + int mFailedTests{ 0 }; + int mSkippedTests{ 0 }; std::shared_ptr<std::ostream> mStream; std::shared_ptr<LLReplayLog> mReplayer; }; @@ -522,6 +526,29 @@ int main(int argc, char **argv) // LOGTEST overrides default, but can be overridden by --debug. const char* LOGTEST = getenv("LOGTEST"); + // Sometimes we must rebuild much of the viewer before we get to the + // specific test we want to monitor, and some viewer integration tests are + // quite verbose. In addition to noticing plain LOGTEST= (for all tests), + // also notice LOGTEST_progname= (for a specific test). + // (Why doesn't MSVC notice fsyspath::operator std::string()? + // Why must we explicitly call fsyspath::string()?) + std::string basename(fsyspath(argv[0]).stem().string()); + // don't make user set LOGTEST_INTEGRATION_TEST_progname or (worse) + // LOGTEST_PROJECT_foo_TEST_bar -- only LOGTEST_progname or LOGTEST_bar + auto _TEST_ = basename.find("_TEST_"); + if (_TEST_ != std::string::npos) + { + basename.erase(0, _TEST_+6); + } + std::string LOGTEST_prog_key("LOGTEST_" + basename); + const char* LOGTEST_prog = getenv(LOGTEST_prog_key.c_str()); +// std::cout << LOGTEST_prog_key << "='" << (LOGTEST_prog? LOGTEST_prog : "") << "'" << std::endl; + if (LOGTEST_prog && *LOGTEST_prog) + { + LOGTEST = LOGTEST_prog; + std::cout << "LOGTEST='" << LOGTEST << "' from " << LOGTEST_prog_key << std::endl; + } + // values used for options parsing apr_status_t apr_err; const char* opt_arg = NULL; @@ -635,14 +662,47 @@ int main(int argc, char **argv) // a chained_callback subclass must be linked with previous mycallback->link(); - if(test_group.empty()) - { - tut::runner.get().run_tests(); - } - else - { - tut::runner.get().run_tests(test_group); - } + LL::seh::catcher( + // __try + [test_group] + { + if(test_group.empty()) + { + tut::runner.get().run_tests(); + } + else + { + tut::runner.get().run_tests(test_group); + } + }, + // __except + [mycallback](U32 code, const std::string& /*stacktrace*/) + { + static std::map<U32, const char*> codes = { + { 0xC0000005, "Access Violation" }, + { 0xC00000FD, "Stack Overflow" }, + // ... continue filling in as desired + }; + + auto found{ codes.find(code) }; + const char* name = ((found == codes.end())? "unknown" : found->second); + auto msg{ stringize("test threw ", std::hex, code, " (", name, ")") }; + + // Instead of bombing the whole test run, report this as a test + // failure. Arguably, catching structured exceptions should be + // hacked into TUT itself. + mycallback->test_completed(tut::test_result( + mycallback->mGroup, + mycallback->mGroupTests+1, // test within group + "unknown", // test name + tut::test_result::ex, // result: exception + // we don't have to throw this exception subclass to use it to + // populate the test_result struct + Windows_SEH_exception(msg))); + // we've left the TUT framework -- finish up by hand + mycallback->group_completed(mycallback->mGroup); + mycallback->run_completed(); + }); bool success = (mycallback->getFailedTests() == 0); diff --git a/indra/test/writestr.h b/indra/test/writestr.h new file mode 100755 index 0000000000..af8be5a3aa --- /dev/null +++ b/indra/test/writestr.h @@ -0,0 +1,39 @@ +/** + * @file writestr.h + * @author Nat Goodspeed + * @date 2024-05-21 + * @brief writestr() function for when iostream isn't set up + * + * $LicenseInfo:firstyear=2024&license=viewerlgpl$ + * Copyright (c) 2024, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_WRITESTR_H) +#define LL_WRITESTR_H + +#include "stringize.h" + +#ifndef LL_WINDOWS + +#include <unistd.h> + +#else // LL_WINDOWS + +#include <io.h> +inline +int write(int fd, const void* buffer, unsigned int count) +{ + return _write(fd, buffer, count); +} + +#endif // LL_WINDOWS + +template <typename... ARGS> +auto writestr(int fd, ARGS&&... args) +{ + std::string str{ stringize(std::forward<ARGS>(args)..., '\n') }; + return write(fd, str.data(), str.length()); +} + +#endif /* ! defined(LL_WRITESTR_H) */ diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp index feebecf4cb..bdabab70e0 100644 --- a/indra/viewer_components/login/lllogin.cpp +++ b/indra/viewer_components/login/lllogin.cpp @@ -46,7 +46,9 @@ class LLLogin::Impl { public: Impl(): - mPump("login", true) // Create the module's event pump with a tweaked (unique) name. + // Create the module's event pump, and do not tweak the name. Multiple + // parties depend on this LLEventPump having exactly the name "login". + mPump("login", false) { mValidAuthResponse["status"] = LLSD(); mValidAuthResponse["errorcode"] = LLSD(); diff --git a/indra/viewer_components/login/tests/lllogin_test.cpp b/indra/viewer_components/login/tests/lllogin_test.cpp index 8aea3b37aa..f051f8c67f 100644 --- a/indra/viewer_components/login/tests/lllogin_test.cpp +++ b/indra/viewer_components/login/tests/lllogin_test.cpp @@ -66,7 +66,7 @@ * Helper classes *****************************************************************************/ // This is a listener to receive results from lllogin. -class LoginListener: public LLEventTrackable +class LoginListener { std::string mName; LLSD mLastEvent; @@ -137,7 +137,7 @@ public: } }; -class LLXMLRPCListener: public LLEventTrackable +class LLXMLRPCListener { std::string mName; LLSD mEvent; diff --git a/scripts/packages-formatter.py b/scripts/packages-formatter.py index 4449111e46..5d31702e76 100755 --- a/scripts/packages-formatter.py +++ b/scripts/packages-formatter.py @@ -42,7 +42,7 @@ _autobuild_env=os.environ.copy() # Coerce stdout encoding to utf-8 as cygwin's will be detected as cp1252 otherwise. _autobuild_env["PYTHONIOENCODING"] = "utf-8" -pkg_line=re.compile('^([\w-]+):\s+(.*)$') +pkg_line=re.compile(r'^([\w-]+):\s+(.*)$') def autobuild(*args): """ diff --git a/scripts/perf/frame_profile b/scripts/perf/frame_profile new file mode 100755 index 0000000000..84eb1166d5 --- /dev/null +++ b/scripts/perf/frame_profile @@ -0,0 +1,43 @@ +#!/usr/bin/env bash + +exe="$1" + +if [[ -z "$exe" ]] +then + # this script lives in scripts/perf + base="$(dirname "$0")/../.." + case $OSTYPE in + darwin*) + # Don't assume a build type (e.g. RelWithDebInfo). Collect all of + # both, and pick the most recent build. + exe="$(ls -t "$base"/build-darwin-x86_64/newview/*/"Second Life"*.app/Contents/MacOS/"Second Life"* | head -1)" + ;; + + cygwin) + exe="$(ls -t "$base"/build-*/newview/*/secondlife-bin.exe | head -1)" + ;; + + linux-gnu) + exe="$(ls -t "$base"/build-linux-*/newview/packaged/secondlife | head -1)" + ;; + + *) + stderr "Unknown platform $OSTYPE" + exit 1 + ;; + esac +fi + +if [ -z "$exe" ] +then stderr "No viewer package build found" + exit 1 +fi + +# If a Mac user specified the .app bundle itself, dig in for the executable. +if [[ "$OSTYPE" == darwin* && -d "$exe" && "$exe" == *.app ]] +then + exe="$(ls "$exe/Contents/MacOS/Second Life "*)" +fi + +"$exe" --autologin --luafile 'frame_profile_quit.lua 228 232 26' \ + http://maps.secondlife.com/secondlife/Bug%20Island/220/224/27 diff --git a/scripts/perf/logsdir.py b/scripts/perf/logsdir.py new file mode 100644 index 0000000000..5ab45a28b6 --- /dev/null +++ b/scripts/perf/logsdir.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 +"""\ +@file logsdir.py +@author Nat Goodspeed +@date 2024-09-12 +@brief Locate the Second Life logs directory for the current user on the + current platform. + +$LicenseInfo:firstyear=2024&license=viewerlgpl$ +Copyright (c) 2024, Linden Research, Inc. +$/LicenseInfo$ +""" + +import os +from pathlib import Path +import platform + +class Error(Exception): + pass + +# logic used by SLVersionChecker +def logsdir(): + app = 'SecondLife' + system = platform.system() + if (system == 'Darwin'): + base_dir = os.path.join(os.path.expanduser('~'), + 'Library','Application Support',app) + elif (system == 'Linux'): + base_dir = os.path.join(os.path.expanduser('~'), + '.' + app.lower()) + elif (system == 'Windows'): + appdata = os.getenv('APPDATA') + base_dir = os.path.join(appdata, app) + else: + raise ValueError("Unsupported platform '%s'" % system) + + return os.path.join(base_dir, 'logs') + +def latest_file(dirpath, pattern): + files = Path(dirpath).glob(pattern) + sort = [(p.stat().st_mtime, p) for p in files if p.is_file()] + sort.sort(reverse=True) + try: + return sort[0][1] + except IndexError: + raise Error(f'No {pattern} files in {dirpath}') diff --git a/scripts/perf/profile_cmp.py b/scripts/perf/profile_cmp.py new file mode 100644 index 0000000000..34281b8d01 --- /dev/null +++ b/scripts/perf/profile_cmp.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +"""\ +@file profile_cmp.py +@author Nat Goodspeed +@date 2024-09-13 +@brief Compare a frame profile stats file with a similar baseline file. + +$LicenseInfo:firstyear=2024&license=viewerlgpl$ +Copyright (c) 2024, Linden Research, Inc. +$/LicenseInfo$ +""" + +from datetime import datetime +import json +from logsdir import Error, latest_file, logsdir +from pathlib import Path +import sys + +# variance that's ignorable +DEFAULT_EPSILON = 0.03 # 3% + +def compare(baseline, test, epsilon=DEFAULT_EPSILON): + if Path(baseline).samefile(test): + print(f'{baseline} same as\n{test}\nAnalysis moot.') + return + + with open(baseline) as inf: + bdata = json.load(inf) + with open(test) as inf: + tdata = json.load(inf) + print(f'baseline {baseline}\ntestfile {test}') + + for k, tv in tdata['context'].items(): + bv = bdata['context'].get(k) + if bv != tv: + print(f'baseline {k}={bv} vs.\ntestfile {k}={tv}') + + btime = bdata['context'].get('time') + ttime = tdata['context'].get('time') + if btime and ttime: + print('testfile newer by', + datetime.fromisoformat(ttime) - datetime.fromisoformat(btime)) + + # The following ignores totals and unused shaders, except to the extent + # that some shaders were used in the baseline but not in the recent test + # or vice-versa. While the viewer considers that a shader has been used if + # 'binds' is nonzero, we exclude any whose 'time' is zero to avoid zero + # division. + bshaders = {s['name']: s for s in bdata['shaders'] if s['time'] and s['samples']} + tshaders = {s['name']: s for s in tdata['shaders'] if s['time']} + + bothshaders = set(bshaders).intersection(tshaders) + deltas = [] + for shader in bothshaders: + bshader = bshaders[shader] + tshader = tshaders[shader] + bthruput = bshader['samples']/bshader['time'] + tthruput = tshader['samples']/tshader['time'] + delta = (tthruput - bthruput)/bthruput + if abs(delta) > epsilon: + deltas.append((delta, shader, bthruput, tthruput)) + + # ascending order of performance gain: put the most egregious performance + # hits at the top of the list + deltas.sort() + print(f'{len(deltas)} shaders showed nontrivial performance differences ' + '(millon samples/sec):') + namelen = max(len(s[1]) for s in deltas) if deltas else 0 + for delta, shader, bthruput, tthruput in deltas: + print(f' {shader.rjust(namelen)} {delta*100:6.1f}% ' + f'{bthruput/1000000:8.2f} -> {tthruput/1000000:8.2f}') + + tunused = set(bshaders).difference(tshaders) + print(f'{len(tunused)} baseline shaders not used in test:') + for s in tunused: + print(f' {s}') + bunused = set(tshaders).difference(bshaders) + print(f'{len(bunused)} shaders newly used in test:') + for s in bunused: + print(f' {s}') + +def main(*raw_args): + from argparse import ArgumentParser + parser = ArgumentParser(description=""" +%(prog)s compares a baseline JSON file from Develop -> Render Tests -> Frame +Profile to another such file from a more recent test. It identifies shaders +that have gained and lost in throughput. +""") + parser.add_argument('-e', '--epsilon', type=float, default=int(DEFAULT_EPSILON*100), + help="""percent variance considered ignorable (default %(default)s%%)""") + parser.add_argument('baseline', + help="""baseline profile filename to compare against""") + parser.add_argument('test', nargs='?', + help="""test profile filename to compare + (default is most recent)""") + args = parser.parse_args(raw_args) + compare(args.baseline, + args.test or latest_file(logsdir(), 'profile.*.json'), + epsilon=(args.epsilon / 100.)) + +if __name__ == "__main__": + try: + sys.exit(main(*sys.argv[1:])) + except (Error, OSError, json.JSONDecodeError) as err: + sys.exit(str(err)) diff --git a/scripts/perf/profile_csv.py b/scripts/perf/profile_csv.py new file mode 100644 index 0000000000..7a6b2b338e --- /dev/null +++ b/scripts/perf/profile_csv.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 +"""\ +@file profile_csv.py +@author Nat Goodspeed +@date 2024-09-12 +@brief Convert a JSON file from Develop -> Render Tests -> Frame Profile to CSV + +$LicenseInfo:firstyear=2024&license=viewerlgpl$ +Copyright (c) 2024, Linden Research, Inc. +$/LicenseInfo$ +""" + +import json +from logsdir import Error, latest_file, logsdir +import sys + +def convert(path, totals=True, unused=True, file=sys.stdout): + with open(path) as inf: + data = json.load(inf) + # print path to sys.stderr in case user is redirecting stdout + print(path, file=sys.stderr) + + print('"name", "file1", "file2", "time", "binds", "samples", "triangles"', file=file) + + if totals: + t = data['totals'] + print(f'"totals", "", "", {t["time"]}, {t["binds"]}, {t["samples"]}, {t["triangles"]}', + file=file) + + for sh in data['shaders']: + print(f'"{sh["name"]}", "{sh["files"][0]}", "{sh["files"][1]}", ' + f'{sh["time"]}, {sh["binds"]}, {sh["samples"]}, {sh["triangles"]}', file=file) + + if unused: + for u in data['unused']: + print(f'"{u}", "", "", 0, 0, 0, 0', file=file) + +def main(*raw_args): + from argparse import ArgumentParser + parser = ArgumentParser(description=""" +%(prog)s converts a JSON file from Develop -> Render Tests -> Frame Profile to +a more-or-less equivalent CSV file. It expands the totals stats and unused +shaders list to full shaders lines. +""") + parser.add_argument('-t', '--totals', action='store_false', default=True, + help="""omit totals from CSV file""") + parser.add_argument('-u', '--unused', action='store_false', default=True, + help="""omit unused shaders from CSV file""") + parser.add_argument('path', nargs='?', + help="""profile filename to convert (default is most recent)""") + + args = parser.parse_args(raw_args) + convert(args.path or latest_file(logsdir(), 'profile.*.json'), + totals=args.totals, unused=args.unused) + +if __name__ == "__main__": + try: + sys.exit(main(*sys.argv[1:])) + except (Error, OSError, json.JSONDecodeError) as err: + sys.exit(str(err)) diff --git a/scripts/perf/profile_pretty.py b/scripts/perf/profile_pretty.py new file mode 100644 index 0000000000..405b14b373 --- /dev/null +++ b/scripts/perf/profile_pretty.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +"""\ +@file profile_pretty.py +@author Nat Goodspeed +@date 2024-09-12 +@brief Pretty-print a JSON file from Develop -> Render Tests -> Frame Profile + +$LicenseInfo:firstyear=2024&license=viewerlgpl$ +Copyright (c) 2024, Linden Research, Inc. +$/LicenseInfo$ +""" + +import json +from logsdir import Error, latest_file, logsdir +import sys + +def pretty(path): + with open(path) as inf: + data = json.load(inf) + # print path to sys.stderr in case user is redirecting stdout + print(path, file=sys.stderr) + json.dump(data, sys.stdout, indent=4) + +def main(*raw_args): + from argparse import ArgumentParser + parser = ArgumentParser(description=""" +%(prog)s pretty-prints a JSON file from Develop -> Render Tests -> Frame Profile. +The file produced by the viewer is a single dense line of JSON. +""") + parser.add_argument('path', nargs='?', + help="""profile filename to pretty-print (default is most recent)""") + + args = parser.parse_args(raw_args) + pretty(args.path or latest_file(logsdir(), 'profile.*.json')) + +if __name__ == "__main__": + try: + sys.exit(main(*sys.argv[1:])) + except (Error, OSError, json.JSONDecodeError) as err: + sys.exit(str(err)) |