diff options
author | Monroe Williams <monroe@lindenlab.com> | 2009-08-27 19:00:18 +0000 |
---|---|---|
committer | Monroe Williams <monroe@lindenlab.com> | 2009-08-27 19:00:18 +0000 |
commit | 745845f79987e4b4ab7f5728746a0eda8898930f (patch) | |
tree | f10efd4a638a6a7eda92a960cdb97e5256ff736a | |
parent | 71344b233d5ae3d5262a492b636af04544952611 (diff) |
svn merge -r 129841:129910 svn+ssh://svn.lindenlab.com/svn/linden/branches/moss/pluginapi_05-merge@129910
svn merge -r 129913:131718 svn+ssh://svn.lindenlab.com/svn/linden/branches/pluginapi/pluginapi_05
Some branch shenannigans in the pluginapi_05 branch caused this to become a two-part merge.
224 files changed, 24287 insertions, 1492 deletions
diff --git a/etc/message.xml b/etc/message.xml index 4af5bd5296..dd1fda059e 100644 --- a/etc/message.xml +++ b/etc/message.xml @@ -377,7 +377,23 @@ <key>trusted-sender</key> <boolean>true</boolean> </map> + + <key>ParcelMediaURLFilter</key> + <map> + <key>flavor</key> + <string>llsd</string> + <key>trusted-sender</key> + <boolean>false</boolean> + </map> + <key>ParcelNavigateMedia</key> + <map> + <key>flavor</key> + <string>llsd</string> + <key>trusted-sender</key> + <boolean>false</boolean> + </map> + <key>ParcelObjectOwnersReply</key> <map> <key>flavor</key> diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index a5ad24815c..9418dbf271 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -43,7 +43,6 @@ add_subdirectory(${LIBS_OPEN_PREFIX}llimage) add_subdirectory(${LIBS_OPEN_PREFIX}llimagej2coj) add_subdirectory(${LIBS_OPEN_PREFIX}llinventory) add_subdirectory(${LIBS_OPEN_PREFIX}llmath) -add_subdirectory(${LIBS_OPEN_PREFIX}llmedia) add_subdirectory(${LIBS_OPEN_PREFIX}llmessage) add_subdirectory(${LIBS_OPEN_PREFIX}llprimitive) add_subdirectory(${LIBS_OPEN_PREFIX}llrender) @@ -64,9 +63,18 @@ endif (WINDOWS AND EXISTS ${LIBS_CLOSED_DIR}copy_win_scripts) add_custom_target(viewer) if (VIEWER) add_subdirectory(${LIBS_OPEN_PREFIX}llcrashlogger) + add_subdirectory(${LIBS_OPEN_PREFIX}llplugin) add_subdirectory(${LIBS_OPEN_PREFIX}llui) add_subdirectory(${LIBS_OPEN_PREFIX}llxuixml) + # viewer media plugins + add_subdirectory(${LIBS_OPEN_PREFIX}media_plugins) + + # llplugin testbed code (is this the right way to include it?) + if (NOT LINUX) + add_subdirectory(${VIEWER_PREFIX}test_apps/llplugintest) + endif (NOT LINUX) + if (LINUX) add_subdirectory(${VIEWER_PREFIX}linux_crash_logger) add_subdirectory(${VIEWER_PREFIX}linux_updater) diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index 658441dab1..3ce393b659 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -34,7 +34,7 @@ set(cmake_SOURCE_FILES FindXmlRpcEpi.cmake FMOD.cmake FreeType.cmake - GStreamer.cmake + GStreamer010Plugin.cmake GooglePerfTools.cmake JPEG.cmake LLAddBuildTest.cmake @@ -48,8 +48,8 @@ set(cmake_SOURCE_FILES LLInventory.cmake LLKDU.cmake LLMath.cmake - LLMedia.cmake LLMessage.cmake + LLPlugin.cmake LLPrimitive.cmake LLRender.cmake LLScene.cmake @@ -60,7 +60,6 @@ set(cmake_SOURCE_FILES LScript.cmake Linking.cmake MonoEmbed.cmake - Mozlib.cmake MySQL.cmake NDOF.cmake OPENAL.cmake @@ -70,7 +69,6 @@ set(cmake_SOURCE_FILES PNG.cmake Python.cmake Prebuilt.cmake - QuickTime.cmake RunBuildTest.cmake TemplateCheck.cmake Tut.cmake diff --git a/indra/cmake/GStreamer010Plugin.cmake b/indra/cmake/GStreamer010Plugin.cmake new file mode 100644 index 0000000000..0d334837d4 --- /dev/null +++ b/indra/cmake/GStreamer010Plugin.cmake @@ -0,0 +1,39 @@ +# -*- cmake -*- +include(Prebuilt) + +if (STANDALONE) + include(FindPkgConfig) + + pkg_check_modules(GSTREAMER010 REQUIRED gstreamer-0.10) + pkg_check_modules(GSTREAMER010_PLUGINS_BASE REQUIRED gstreamer-plugins-base-0.10) +elseif (LINUX) + use_prebuilt_binary(gstreamer) + # possible libxml should have its own .cmake file instead + use_prebuilt_binary(libxml) + set(GSTREAMER010_FOUND ON FORCE BOOL) + set(GSTREAMER010_PLUGINS_BASE_FOUND ON FORCE BOOL) + set(GSTREAMER010_INCLUDE_DIRS + ${LIBS_PREBUILT_DIR}/${LL_ARCH_DIR}/include/gstreamer-0.10 + ${LIBS_PREBUILT_DIR}/${LL_ARCH_DIR}/include/glib-2.0 + ${LIBS_PREBUILT_DIR}/${LL_ARCH_DIR}/include/libxml2 + ) + # We don't need to explicitly link against gstreamer itself, because + # LLMediaImplGStreamer probes for the system's copy at runtime. + set(GSTREAMER010_LIBRARIES + gobject-2.0 + gmodule-2.0 + dl + gthread-2.0 + rt + glib-2.0 + ) +endif (STANDALONE) + +if (GSTREAMER010_FOUND AND GSTREAMER010_PLUGINS_BASE_FOUND) + set(GSTREAMER010 ON CACHE BOOL "Build with GStreamer-0.10 streaming media support.") +endif (GSTREAMER010_FOUND AND GSTREAMER010_PLUGINS_BASE_FOUND) + +if (GSTREAMER010) + add_definitions(-DLL_GSTREAMER010_ENABLED=1) +endif (GSTREAMER010) + diff --git a/indra/cmake/Glui.cmake b/indra/cmake/Glui.cmake new file mode 100644 index 0000000000..f62a56856c --- /dev/null +++ b/indra/cmake/Glui.cmake @@ -0,0 +1,28 @@ +# -*- cmake -*- +include(Linking) +include(Prebuilt) + +if (STANDALONE) + set(GLUI OFF CACHE BOOL + "GLUI support for the llplugin/llmedia test apps.") +else (STANDALONE) + use_prebuilt_binary(glui) + set(GLUI ON CACHE BOOL + "GLUI support for the llplugin/llmedia test apps.") +endif (STANDALONE) + +if (LINUX) + set(GLUI ON CACHE BOOL + "llplugin media apps HACK for Linux.") +endif (LINUX) + +if (DARWIN OR LINUX) + set(GLUI_LIBRARY + glui) +endif (DARWIN OR LINUX) + +if (WINDOWS) + set(GLUI_LIBRARY + debug glui32.lib + optimized glui32.lib) +endif (WINDOWS) diff --git a/indra/cmake/Glut.cmake b/indra/cmake/Glut.cmake new file mode 100644 index 0000000000..314da30652 --- /dev/null +++ b/indra/cmake/Glut.cmake @@ -0,0 +1,19 @@ +# -*- cmake -*- +include(Linking) +include(Prebuilt) + +if (WINDOWS) + use_prebuilt_binary(freeglut) + set(GLUT_LIBRARY + debug freeglut_static.lib + optimized freeglut_static.lib) +endif (WINDOWS) + +if (LINUX) + FIND_LIBRARY(GLUT_LIBRARY glut) +endif (LINUX) + +if (DARWIN) + include(CMakeFindFrameworks) + find_library(GLUT_LIBRARY GLUT) +endif (DARWIN) diff --git a/indra/cmake/LLPlugin.cmake b/indra/cmake/LLPlugin.cmake new file mode 100644 index 0000000000..9722f16c3c --- /dev/null +++ b/indra/cmake/LLPlugin.cmake @@ -0,0 +1,8 @@ +# -*- cmake -*- + + +set(LLPLUGIN_INCLUDE_DIRS + ${LIBS_OPEN_DIR}/llplugin + ) + +set(LLPLUGIN_LIBRARIES llplugin) diff --git a/indra/cmake/MediaPluginBase.cmake b/indra/cmake/MediaPluginBase.cmake new file mode 100644 index 0000000000..2be035b641 --- /dev/null +++ b/indra/cmake/MediaPluginBase.cmake @@ -0,0 +1,8 @@ +# -*- cmake -*- + + +set(MEDIA_PLUGIN_BASE_INCLUDE_DIRS + ${LIBS_OPEN_DIR}/media_plugins/base/ + ) + +set(MEDIA_PLUGIN_BASE_LIBRARIES media_plugin_base) diff --git a/indra/cmake/PluginAPI.cmake b/indra/cmake/PluginAPI.cmake new file mode 100644 index 0000000000..d1649e8248 --- /dev/null +++ b/indra/cmake/PluginAPI.cmake @@ -0,0 +1,16 @@ +# -*- cmake -*- + +if (WINDOWS) + set(PLUGIN_API_WINDOWS_LIBRARIES + wsock32 + ws2_32 + psapi + netapi32 + advapi32 + user32 + ) +else (WINDOWS) + set(PLUGIN_API_WINDOWS_LIBRARIES "") +endif (WINDOWS) + + diff --git a/indra/cmake/QuickTimePlugin.cmake b/indra/cmake/QuickTimePlugin.cmake new file mode 100644 index 0000000000..8afd8f304c --- /dev/null +++ b/indra/cmake/QuickTimePlugin.cmake @@ -0,0 +1,46 @@ +# -*- cmake -*- + +if(INSTALL_PROPRIETARY) + include(Prebuilt) + use_prebuilt_binary(quicktime) +endif(INSTALL_PROPRIETARY) + +if (DARWIN) + include(CMakeFindFrameworks) + find_library(QUICKTIME_LIBRARY QuickTime) +elseif (WINDOWS) + set(QUICKTIME_SDK_DIR "$ENV{PROGRAMFILES}/QuickTime SDK" + CACHE PATH "Location of the QuickTime SDK.") + + find_library(DEBUG_QUICKTIME_LIBRARY qtmlclient + PATHS + ${ARCH_PREBUILT_DIRS_DEBUG} + "${QUICKTIME_SDK_DIR}\\libraries" + ) + + find_library(RELEASE_QUICKTIME_LIBRARY qtmlclient + PATHS + ${ARCH_PREBUILT_DIRS_RELEASE} + "${QUICKTIME_SDK_DIR}\\libraries" + ) + + if (DEBUG_QUICKTIME_LIBRARY AND RELEASE_QUICKTIME_LIBRARY) + set(QUICKTIME_LIBRARY + optimized ${RELEASE_QUICKTIME_LIBRARY} + debug ${DEBUG_QUICKTIME_LIBRARY} + ) + + endif (DEBUG_QUICKTIME_LIBRARY AND RELEASE_QUICKTIME_LIBRARY) + + include_directories( + ${LIBS_PREBUILT_DIR}/${LL_ARCH_DIR}/include/quicktime + "${QUICKTIME_SDK_DIR}\\CIncludes" + ) +endif (DARWIN) + +mark_as_advanced(QUICKTIME_LIBRARY) + +if (QUICKTIME_LIBRARY) + set(QUICKTIME ON CACHE BOOL "Build with QuickTime streaming media support.") +endif (QUICKTIME_LIBRARY) + diff --git a/indra/cmake/WebKitLibPlugin.cmake b/indra/cmake/WebKitLibPlugin.cmake new file mode 100644 index 0000000000..c84df2b839 --- /dev/null +++ b/indra/cmake/WebKitLibPlugin.cmake @@ -0,0 +1,62 @@ +# -*- cmake -*- +include(Linking) +include(Prebuilt) + +if (STANDALONE) + set(WEBKITLIBPLUGIN OFF CACHE BOOL + "WEBKITLIBPLUGIN support for the llplugin/llmedia test apps.") +else (STANDALONE) + use_prebuilt_binary(webkitlibplugin) + set(WEBKITLIBPLUGIN ON CACHE BOOL + "WEBKITLIBPLUGIN support for the llplugin/llmedia test apps.") +endif (STANDALONE) + +if (WINDOWS) + set(WEBKIT_PLUGIN_LIBRARIES + debug llwebkitlibd + debug QtWebKitd4 + debug QtOpenGLd4 + debug QtNetworkd4 + debug QtGuid4 + debug QtCored4 + debug qtmaind + optimized llwebkitlib + optimized QtWebKit4 + optimized QtOpenGL4 + optimized QtNetwork4 + optimized QtGui4 + optimized QtCore4 + optimized qtmain + ) +elseif (DARWIN) + set(WEBKIT_PLUGIN_LIBRARIES + optimized ${ARCH_PREBUILT_DIRS_RELEASE}/libllwebkitlib.dylib + debug ${ARCH_PREBUILT_DIRS_RELEASE}/libllwebkitlib.dylib + ) +elseif (LINUX) + set(WEBKIT_PLUGIN_LIBRARIES + llwebkitlib + + qgif +# qico + qjpeg +# qpng +# qtiff +# qsvg + +# QtSvg + QtWebKit + QtOpenGL + QtNetwork + QtGui + QtCore + + fontconfig + X11 + GL + +# sqlite3 +# Xi +# SM + ) +endif (WINDOWS) diff --git a/indra/develop.py b/indra/develop.py index 43fb354578..249b6519fc 100755 --- a/indra/develop.py +++ b/indra/develop.py @@ -384,16 +384,20 @@ class LinuxSetup(UnixSetup): if job_count is None: hosts, job_count = count_distcc_hosts() + hostname = socket.gethostname() if hosts == 1: - hostname = socket.gethostname() if hostname.startswith('station'): hosts, job_count = mk_distcc_hosts('station', 36, 2) os.environ['DISTCC_HOSTS'] = hosts if hostname.startswith('eniac'): hosts, job_count = mk_distcc_hosts('eniac', 71, 2) os.environ['DISTCC_HOSTS'] = hosts - if job_count > 12: - job_count = 12; + if hostname.startswith('build'): + max_jobs = 6 + else: + max_jobs = 12 + if job_count > max_jobs: + job_count = max_jobs; opts.extend(['-j', str(job_count)]) if targets: @@ -453,9 +457,7 @@ class DarwinSetup(UnixSetup): targets = ' '.join(['-target ' + repr(t) for t in targets]) else: targets = '' - # cmd = ('xcodebuild -parallelizeTargets ' # parallelizeTargets is suspected of non-deterministic build failures. + poppy 2009-06-05 - cmd = ('xcodebuild ' - '-configuration %s %s %s' % + cmd = ('xcodebuild -configuration %s %s %s' % (self.build_type, ' '.join(opts), targets)) for d in self.build_dirs(): try: diff --git a/indra/llaudio/CMakeLists.txt b/indra/llaudio/CMakeLists.txt index 235248ee73..80245fd569 100644 --- a/indra/llaudio/CMakeLists.txt +++ b/indra/llaudio/CMakeLists.txt @@ -4,15 +4,16 @@ project(llaudio) include(00-Common) include(Audio) +include(LLAudio) include(FMOD) include(OPENAL) include(LLCommon) include(LLMath) include(LLMessage) include(LLVFS) -include(LLMedia) include_directories( + ${LLAUDIO_INCLUDE_DIRS} ${FMOD_INCLUDE_DIR} ${LLCOMMON_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} @@ -24,42 +25,43 @@ include_directories( ${VORBIS_INCLUDE_DIRS} ${OPENAL_LIB_INCLUDE_DIRS} ${FREEAULT_LIB_INCLUDE_DIRS} - ${LLMEDIA_INCLUDE_DIRS} ) set(llaudio_SOURCE_FILES - audioengine.cpp - listener.cpp + llaudioengine.cpp + lllistener.cpp llaudiodecodemgr.cpp - vorbisdecode.cpp - vorbisencode.cpp + llvorbisdecode.cpp + llvorbisencode.cpp ) set(llaudio_HEADER_FILES CMakeLists.txt - audioengine.h - listener.h + llaudioengine.h + lllistener.h llaudiodecodemgr.h - vorbisdecode.h - vorbisencode.h - windgen.h + llvorbisdecode.h + llvorbisencode.h + llwindgen.h ) if (FMOD) list(APPEND llaudio_SOURCE_FILES - audioengine_fmod.cpp - listener_fmod.cpp + llaudioengine_fmod.cpp + lllistener_fmod.cpp + llstreamingaudio_fmod.cpp ) list(APPEND llaudio_HEADER_FILES - audioengine_fmod.h - listener_fmod.h + llaudioengine_fmod.h + lllistener_fmod.h + llstreamingaudio_fmod.h ) if (LINUX) if (${CXX_VERSION} MATCHES "4.[23]") - set_source_files_properties(audioengine_fmod.cpp + set_source_files_properties(llaudioengine_fmod.cpp COMPILE_FLAGS -Wno-error=write-strings) endif (${CXX_VERSION} MATCHES "4.[23]") endif (LINUX) @@ -67,13 +69,13 @@ endif (FMOD) if (OPENAL) list(APPEND llaudio_SOURCE_FILES - audioengine_openal.cpp - listener_openal.cpp + llaudioengine_openal.cpp + lllistener_openal.cpp ) list(APPEND llaudio_HEADER_FILES - audioengine_openal.h - listener_openal.h + llaudioengine_openal.h + lllistener_openal.h ) endif (OPENAL) diff --git a/indra/llaudio/llaudiodecodemgr.cpp b/indra/llaudio/llaudiodecodemgr.cpp index 6a494d1983..099c4eba40 100644 --- a/indra/llaudio/llaudiodecodemgr.cpp +++ b/indra/llaudio/llaudiodecodemgr.cpp @@ -33,8 +33,8 @@ #include "llaudiodecodemgr.h" -#include "vorbisdecode.h" -#include "audioengine.h" +#include "llvorbisdecode.h" +#include "llaudioengine.h" #include "lllfsthread.h" #include "llvfile.h" #include "llstring.h" diff --git a/indra/llaudio/llaudioengine.cpp b/indra/llaudio/llaudioengine.cpp new file mode 100644 index 0000000000..a28c94d00d --- /dev/null +++ b/indra/llaudio/llaudioengine.cpp @@ -0,0 +1,1751 @@ + /** + * @file audioengine.cpp + * @brief implementation of LLAudioEngine class abstracting the Open + * AL audio support + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + * + * Copyright (c) 2000-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llaudioengine.h" +#include "llstreamingaudio.h" + +#include "llerror.h" +#include "llmath.h" + +#include "sound_ids.h" // temporary hack for min/max distances + +#include "llvfs.h" +#include "lldir.h" +#include "llaudiodecodemgr.h" +#include "llassetstorage.h" + + +// necessary for grabbing sounds from sim (implemented in viewer) +extern void request_sound(const LLUUID &sound_guid); + +LLAudioEngine* gAudiop = NULL; + + +// +// LLAudioEngine implementation +// + + +LLAudioEngine::LLAudioEngine() +{ + setDefaults(); +} + + +LLAudioEngine::~LLAudioEngine() +{ +} + +LLStreamingAudioInterface* LLAudioEngine::getStreamingAudioImpl() +{ + return mStreamingAudioImpl; +} + +void LLAudioEngine::setStreamingAudioImpl(LLStreamingAudioInterface *impl) +{ + mStreamingAudioImpl = impl; +} + +void LLAudioEngine::setDefaults() +{ + mMaxWindGain = 1.f; + + mListenerp = NULL; + + mMuted = false; + mUserData = NULL; + + mLastStatus = 0; + + mNumChannels = 0; + mEnableWind = false; + + S32 i; + for (i = 0; i < MAX_CHANNELS; i++) + { + mChannels[i] = NULL; + } + for (i = 0; i < MAX_BUFFERS; i++) + { + mBuffers[i] = NULL; + } + + mMasterGain = 1.f; + mNextWindUpdate = 0.f; + + mStreamingAudioImpl = NULL; + + for (U32 i = 0; i < LLAudioEngine::AUDIO_TYPE_COUNT; i++) + mSecondaryGain[i] = 1.0f; +} + + +bool LLAudioEngine::init(const S32 num_channels, void* userdata) +{ + setDefaults(); + + mNumChannels = num_channels; + mUserData = userdata; + + allocateListener(); + + // Initialize the decode manager + gAudioDecodeMgrp = new LLAudioDecodeMgr; + + llinfos << "LLAudioEngine::init() AudioEngine successfully initialized" << llendl; + + return true; +} + + +void LLAudioEngine::shutdown() +{ + // Clean up decode manager + delete gAudioDecodeMgrp; + gAudioDecodeMgrp = NULL; + + // Clean up wind source + cleanupWind(); + + // Clean up audio sources + source_map::iterator iter_src; + for (iter_src = mAllSources.begin(); iter_src != mAllSources.end(); iter_src++) + { + delete iter_src->second; + } + + + // Clean up audio data + data_map::iterator iter_data; + for (iter_data = mAllData.begin(); iter_data != mAllData.end(); iter_data++) + { + delete iter_data->second; + } + + + // Clean up channels + S32 i; + for (i = 0; i < MAX_CHANNELS; i++) + { + delete mChannels[i]; + mChannels[i] = NULL; + } + + // Clean up buffers + for (i = 0; i < MAX_BUFFERS; i++) + { + delete mBuffers[i]; + mBuffers[i] = NULL; + } +} + + +// virtual +void LLAudioEngine::startInternetStream(const std::string& url) +{ + if (mStreamingAudioImpl) + mStreamingAudioImpl->start(url); +} + + +// virtual +void LLAudioEngine::stopInternetStream() +{ + if (mStreamingAudioImpl) + mStreamingAudioImpl->stop(); +} + +// virtual +void LLAudioEngine::pauseInternetStream(int pause) +{ + if (mStreamingAudioImpl) + mStreamingAudioImpl->pause(pause); +} + +// virtual +void LLAudioEngine::updateInternetStream() +{ + if (mStreamingAudioImpl) + mStreamingAudioImpl->update(); +} + +// virtual +int LLAudioEngine::isInternetStreamPlaying() +{ + if (mStreamingAudioImpl) + return mStreamingAudioImpl->isPlaying(); + + return 0; // Stopped +} + + +// virtual +void LLAudioEngine::setInternetStreamGain(F32 vol) +{ + if (mStreamingAudioImpl) + mStreamingAudioImpl->setGain(vol); +} + +// virtual +std::string LLAudioEngine::getInternetStreamURL() +{ + if (mStreamingAudioImpl) + return mStreamingAudioImpl->getURL(); + else return std::string(); +} + + +void LLAudioEngine::updateChannels() +{ + S32 i; + for (i = 0; i < MAX_CHANNELS; i++) + { + if (mChannels[i]) + { + mChannels[i]->updateBuffer(); + mChannels[i]->update3DPosition(); + mChannels[i]->updateLoop(); + } + } +} + +static const F32 default_max_decode_time = .002f; // 2 ms +void LLAudioEngine::idle(F32 max_decode_time) +{ + if (max_decode_time <= 0.f) + { + max_decode_time = default_max_decode_time; + } + + // "Update" all of our audio sources, clean up dead ones. + // Primarily does position updating, cleanup of unused audio sources. + // Also does regeneration of the current priority of each audio source. + + if (getMuted()) + { + setInternalGain(0.f); + } + else + { + setInternalGain(getMasterGain()); + } + + S32 i; + for (i = 0; i < MAX_BUFFERS; i++) + { + if (mBuffers[i]) + { + mBuffers[i]->mInUse = false; + } + } + + F32 max_priority = -1.f; + LLAudioSource *max_sourcep = NULL; // Maximum priority source without a channel + source_map::iterator iter; + for (iter = mAllSources.begin(); iter != mAllSources.end();) + { + LLAudioSource *sourcep = iter->second; + + // Update this source + sourcep->update(); + sourcep->updatePriority(); + + if (sourcep->isDone()) + { + // The source is done playing, clean it up. + delete sourcep; + mAllSources.erase(iter++); + continue; + } + + if (!sourcep->getChannel() && sourcep->getCurrentBuffer()) + { + // We could potentially play this sound if its priority is high enough. + if (sourcep->getPriority() > max_priority) + { + max_priority = sourcep->getPriority(); + max_sourcep = sourcep; + } + } + + // Move on to the next source + iter++; + } + + // Now, do priority-based organization of audio sources. + // All channels used, check priorities. + // Find channel with lowest priority + if (max_sourcep) + { + LLAudioChannel *channelp = getFreeChannel(max_priority); + if (channelp) + { + //llinfos << "Replacing source in channel due to priority!" << llendl; + max_sourcep->setChannel(channelp); + channelp->setSource(max_sourcep); + if (max_sourcep->isSyncSlave()) + { + // A sync slave, it doesn't start playing until it's synced up with the master. + // Flag this channel as waiting for sync, and return true. + channelp->setWaiting(true); + } + else + { + channelp->setWaiting(false); + channelp->play(); + } + } + } + + + // Do this BEFORE we update the channels + // Update the channels to sync up with any changes that the source made, + // such as changing what sound was playing. + updateChannels(); + + // Update queued sounds (switch to next queued data if the current has finished playing) + for (iter = mAllSources.begin(); iter != mAllSources.end(); ++iter) + { + // This is lame, instead of this I could actually iterate through all the sources + // attached to each channel, since only those with active channels + // can have anything interesting happen with their queue? (Maybe not true) + LLAudioSource *sourcep = iter->second; + if (!sourcep->mQueuedDatap) + { + // Nothing queued, so we don't care. + continue; + } + + LLAudioChannel *channelp = sourcep->getChannel(); + if (!channelp) + { + // This sound isn't playing, so we just process move the queue + sourcep->mCurrentDatap = sourcep->mQueuedDatap; + sourcep->mQueuedDatap = NULL; + + // Reset the timer so the source doesn't die. + sourcep->mAgeTimer.reset(); + // Make sure we have the buffer set up if we just decoded the data + if (sourcep->mCurrentDatap) + { + updateBufferForData(sourcep->mCurrentDatap); + } + + // Actually play the associated data. + sourcep->setupChannel(); + channelp = sourcep->getChannel(); + if (channelp) + { + channelp->updateBuffer(); + sourcep->getChannel()->play(); + } + continue; + } + else + { + // Check to see if the current sound is done playing, or looped. + if (!channelp->isPlaying()) + { + sourcep->mCurrentDatap = sourcep->mQueuedDatap; + sourcep->mQueuedDatap = NULL; + + // Reset the timer so the source doesn't die. + sourcep->mAgeTimer.reset(); + + // Make sure we have the buffer set up if we just decoded the data + if (sourcep->mCurrentDatap) + { + updateBufferForData(sourcep->mCurrentDatap); + } + + // Actually play the associated data. + sourcep->setupChannel(); + channelp->updateBuffer(); + sourcep->getChannel()->play(); + } + else if (sourcep->isLoop()) + { + // It's a loop, we need to check and see if we're done with it. + if (channelp->mLoopedThisFrame) + { + sourcep->mCurrentDatap = sourcep->mQueuedDatap; + sourcep->mQueuedDatap = NULL; + + // Actually, should do a time sync so if we're a loop master/slave + // we don't drift away. + sourcep->setupChannel(); + sourcep->getChannel()->play(); + } + } + } + } + + // Lame, update the channels AGAIN. + // Update the channels to sync up with any changes that the source made, + // such as changing what sound was playing. + updateChannels(); + + // Hack! For now, just use a global sync master; + LLAudioSource *sync_masterp = NULL; + LLAudioChannel *master_channelp = NULL; + F32 max_sm_priority = -1.f; + for (iter = mAllSources.begin(); iter != mAllSources.end(); ++iter) + { + LLAudioSource *sourcep = iter->second; + if (sourcep->isSyncMaster()) + { + if (sourcep->getPriority() > max_sm_priority) + { + sync_masterp = sourcep; + master_channelp = sync_masterp->getChannel(); + max_sm_priority = sourcep->getPriority(); + } + } + } + + if (master_channelp && master_channelp->mLoopedThisFrame) + { + // Synchronize loop slaves with their masters + // Update queued sounds (switch to next queued data if the current has finished playing) + for (iter = mAllSources.begin(); iter != mAllSources.end(); ++iter) + { + LLAudioSource *sourcep = iter->second; + + if (!sourcep->isSyncSlave()) + { + // Not a loop slave, we don't need to do anything + continue; + } + + LLAudioChannel *channelp = sourcep->getChannel(); + if (!channelp) + { + // Not playing, don't need to bother. + continue; + } + + if (!channelp->isPlaying()) + { + // Now we need to check if our loop master has just looped, and + // start playback if that's the case. + if (sync_masterp->getChannel()) + { + channelp->playSynced(master_channelp); + channelp->setWaiting(false); + } + } + } + } + + // Sync up everything that the audio engine needs done. + commitDeferredChanges(); + + // Flush unused buffers that are stale enough + for (i = 0; i < MAX_BUFFERS; i++) + { + if (mBuffers[i]) + { + if (!mBuffers[i]->mInUse && mBuffers[i]->mLastUseTimer.getElapsedTimeF32() > 30.f) + { + //llinfos << "Flushing unused buffer!" << llendl; + mBuffers[i]->mAudioDatap->mBufferp = NULL; + delete mBuffers[i]; + mBuffers[i] = NULL; + } + } + } + + + // Clear all of the looped flags for the channels + for (i = 0; i < MAX_CHANNELS; i++) + { + if (mChannels[i]) + { + mChannels[i]->mLoopedThisFrame = false; + } + } + + // Decode audio files + gAudioDecodeMgrp->processQueue(max_decode_time); + + // Call this every frame, just in case we somehow + // missed picking it up in all the places that can add + // or request new data. + startNextTransfer(); + + updateInternetStream(); +} + + + +bool LLAudioEngine::updateBufferForData(LLAudioData *adp, const LLUUID &audio_uuid) +{ + if (!adp) + { + return false; + } + + // Update the audio buffer first - load a sound if we have it. + // Note that this could potentially cause us to waste time updating buffers + // for sounds that actually aren't playing, although this should be mitigated + // by the fact that we limit the number of buffers, and we flush buffers based + // on priority. + if (!adp->getBuffer()) + { + if (adp->hasDecodedData()) + { + adp->load(); + } + else if (adp->hasLocalData()) + { + if (audio_uuid.notNull()) + { + gAudioDecodeMgrp->addDecodeRequest(audio_uuid); + } + } + else + { + return false; + } + } + return true; +} + + +void LLAudioEngine::enableWind(bool enable) +{ + if (enable && (!mEnableWind)) + { + initWind(); + mEnableWind = enable; + } + else if (mEnableWind && (!enable)) + { + mEnableWind = enable; + cleanupWind(); + } +} + + +LLAudioBuffer * LLAudioEngine::getFreeBuffer() +{ + S32 i; + for (i = 0; i < MAX_BUFFERS; i++) + { + if (!mBuffers[i]) + { + mBuffers[i] = createBuffer(); + return mBuffers[i]; + } + } + + + // Grab the oldest unused buffer + F32 max_age = -1.f; + S32 buffer_id = -1; + for (i = 0; i < MAX_BUFFERS; i++) + { + if (mBuffers[i]) + { + if (!mBuffers[i]->mInUse) + { + if (mBuffers[i]->mLastUseTimer.getElapsedTimeF32() > max_age) + { + max_age = mBuffers[i]->mLastUseTimer.getElapsedTimeF32(); + buffer_id = i; + } + } + } + } + + if (buffer_id >= 0) + { + llinfos << "Taking over unused buffer " << buffer_id << llendl; + //llinfos << "Flushing unused buffer!" << llendl; + mBuffers[buffer_id]->mAudioDatap->mBufferp = NULL; + delete mBuffers[buffer_id]; + mBuffers[buffer_id] = createBuffer(); + return mBuffers[buffer_id]; + } + return NULL; +} + + +LLAudioChannel * LLAudioEngine::getFreeChannel(const F32 priority) +{ + S32 i; + for (i = 0; i < mNumChannels; i++) + { + if (!mChannels[i]) + { + // No channel allocated here, use it. + mChannels[i] = createChannel(); + return mChannels[i]; + } + else + { + // Channel is allocated but not playing right now, use it. + if (!mChannels[i]->isPlaying() && !mChannels[i]->isWaiting()) + { + mChannels[i]->cleanup(); + if (mChannels[i]->getSource()) + { + mChannels[i]->getSource()->setChannel(NULL); + } + return mChannels[i]; + } + } + } + + // All channels used, check priorities. + // Find channel with lowest priority and see if we want to replace it. + F32 min_priority = 10000.f; + LLAudioChannel *min_channelp = NULL; + + for (i = 0; i < mNumChannels; i++) + { + LLAudioChannel *channelp = mChannels[i]; + LLAudioSource *sourcep = channelp->getSource(); + if (sourcep->getPriority() < min_priority) + { + min_channelp = channelp; + min_priority = sourcep->getPriority(); + } + } + + if (min_priority > priority || !min_channelp) + { + // All playing channels have higher priority, return. + return NULL; + } + + // Flush the minimum priority channel, and return it. + min_channelp->cleanup(); + min_channelp->getSource()->setChannel(NULL); + return min_channelp; +} + + +void LLAudioEngine::cleanupBuffer(LLAudioBuffer *bufferp) +{ + S32 i; + for (i = 0; i < MAX_BUFFERS; i++) + { + if (mBuffers[i] == bufferp) + { + delete mBuffers[i]; + mBuffers[i] = NULL; + } + } +} + + +bool LLAudioEngine::preloadSound(const LLUUID &uuid) +{ + gAudiop->getAudioData(uuid); // We don't care about the return value, this is just to make sure + // that we have an entry, which will mean that the audio engine knows about this + + if (gAudioDecodeMgrp->addDecodeRequest(uuid)) + { + // This means that we do have a local copy, and we're working on decoding it. + return true; + } + + // At some point we need to have the audio/asset system check the static VFS + // before it goes off and fetches stuff from the server. + //llwarns << "Used internal preload for non-local sound" << llendl; + return false; +} + + +bool LLAudioEngine::isWindEnabled() +{ + return mEnableWind; +} + + +void LLAudioEngine::setMuted(bool muted) +{ + mMuted = muted; + enableWind(!mMuted); +} + + +void LLAudioEngine::setMasterGain(const F32 gain) +{ + mMasterGain = gain; + setInternalGain(gain); +} + +F32 LLAudioEngine::getMasterGain() +{ + return mMasterGain; +} + +void LLAudioEngine::setSecondaryGain(S32 type, F32 gain) +{ + llassert(type < LLAudioEngine::AUDIO_TYPE_COUNT); + + mSecondaryGain[type] = gain; +} + +F32 LLAudioEngine::getSecondaryGain(S32 type) +{ + return mSecondaryGain[type]; +} + +F32 LLAudioEngine::getInternetStreamGain() +{ + if (mStreamingAudioImpl) + return mStreamingAudioImpl->getGain(); + else + return 1.0f; +} + +void LLAudioEngine::setMaxWindGain(F32 gain) +{ + mMaxWindGain = gain; +} + + +F64 LLAudioEngine::mapWindVecToGain(LLVector3 wind_vec) +{ + F64 gain = 0.0; + + gain = wind_vec.magVec(); + + if (gain) + { + if (gain > 20) + { + gain = 20; + } + gain = gain/20.0; + } + + return (gain); +} + + +F64 LLAudioEngine::mapWindVecToPitch(LLVector3 wind_vec) +{ + LLVector3 listen_right; + F64 theta; + + // Wind frame is in listener-relative coordinates + LLVector3 norm_wind = wind_vec; + norm_wind.normVec(); + listen_right.setVec(1.0,0.0,0.0); + + // measure angle between wind vec and listener right axis (on 0,PI) + theta = acos(norm_wind * listen_right); + + // put it on 0, 1 + theta /= F_PI; + + // put it on [0, 0.5, 0] + if (theta > 0.5) theta = 1.0-theta; + if (theta < 0) theta = 0; + + return (theta); +} + + +F64 LLAudioEngine::mapWindVecToPan(LLVector3 wind_vec) +{ + LLVector3 listen_right; + F64 theta; + + // Wind frame is in listener-relative coordinates + listen_right.setVec(1.0,0.0,0.0); + + LLVector3 norm_wind = wind_vec; + norm_wind.normVec(); + + // measure angle between wind vec and listener right axis (on 0,PI) + theta = acos(norm_wind * listen_right); + + // put it on 0, 1 + theta /= F_PI; + + return (theta); +} + + +void LLAudioEngine::triggerSound(const LLUUID &audio_uuid, const LLUUID& owner_id, const F32 gain, + const S32 type, const LLVector3d &pos_global) +{ + // Create a new source (since this can't be associated with an existing source. + //llinfos << "Localized: " << audio_uuid << llendl; + + if (mMuted) + { + return; + } + + LLUUID source_id; + source_id.generate(); + + LLAudioSource *asp = new LLAudioSource(source_id, owner_id, gain, type); + gAudiop->addAudioSource(asp); + if (pos_global.isExactlyZero()) + { + asp->setAmbient(true); + } + else + { + asp->setPositionGlobal(pos_global); + } + asp->updatePriority(); + asp->play(audio_uuid); +} + + +void LLAudioEngine::setListenerPos(LLVector3 aVec) +{ + mListenerp->setPosition(aVec); +} + + +LLVector3 LLAudioEngine::getListenerPos() +{ + if (mListenerp) + { + return(mListenerp->getPosition()); + } + else + { + return(LLVector3::zero); + } +} + + +void LLAudioEngine::setListenerVelocity(LLVector3 aVec) +{ + mListenerp->setVelocity(aVec); +} + + +void LLAudioEngine::translateListener(LLVector3 aVec) +{ + mListenerp->translate(aVec); +} + + +void LLAudioEngine::orientListener(LLVector3 up, LLVector3 at) +{ + mListenerp->orient(up, at); +} + + +void LLAudioEngine::setListener(LLVector3 pos, LLVector3 vel, LLVector3 up, LLVector3 at) +{ + mListenerp->set(pos,vel,up,at); +} + + +void LLAudioEngine::setDopplerFactor(F32 factor) +{ + if (mListenerp) + { + mListenerp->setDopplerFactor(factor); + } +} + + +F32 LLAudioEngine::getDopplerFactor() +{ + if (mListenerp) + { + return mListenerp->getDopplerFactor(); + } + else + { + return 0.f; + } +} + + +void LLAudioEngine::setRolloffFactor(F32 factor) +{ + if (mListenerp) + { + mListenerp->setRolloffFactor(factor); + } +} + + +F32 LLAudioEngine::getRolloffFactor() +{ + if (mListenerp) + { + return mListenerp->getRolloffFactor(); + } + else + { + return 0.f; + } +} + + +void LLAudioEngine::commitDeferredChanges() +{ + mListenerp->commitDeferredChanges(); +} + + +LLAudioSource * LLAudioEngine::findAudioSource(const LLUUID &source_id) +{ + source_map::iterator iter; + iter = mAllSources.find(source_id); + + if (iter == mAllSources.end()) + { + return NULL; + } + else + { + return iter->second; + } +} + + +LLAudioData * LLAudioEngine::getAudioData(const LLUUID &audio_uuid) +{ + data_map::iterator iter; + iter = mAllData.find(audio_uuid); + if (iter == mAllData.end()) + { + // Create the new audio data + LLAudioData *adp = new LLAudioData(audio_uuid); + mAllData[audio_uuid] = adp; + return adp; + } + else + { + return iter->second; + } +} + +void LLAudioEngine::addAudioSource(LLAudioSource *asp) +{ + mAllSources[asp->getID()] = asp; +} + + +void LLAudioEngine::cleanupAudioSource(LLAudioSource *asp) +{ + source_map::iterator iter; + iter = mAllSources.find(asp->getID()); + if (iter == mAllSources.end()) + { + llwarns << "Cleaning up unknown audio source!" << llendl; + return; + } + delete asp; + mAllSources.erase(iter); +} + + +bool LLAudioEngine::hasDecodedFile(const LLUUID &uuid) +{ + std::string uuid_str; + uuid.toString(uuid_str); + + std::string wav_path; + wav_path = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,uuid_str); + wav_path += ".dsf"; + + if (gDirUtilp->fileExists(wav_path)) + { + return true; + } + else + { + return false; + } +} + + +bool LLAudioEngine::hasLocalFile(const LLUUID &uuid) +{ + // See if it's in the VFS. + return gVFS->getExists(uuid, LLAssetType::AT_SOUND); +} + + +void LLAudioEngine::startNextTransfer() +{ + //llinfos << "LLAudioEngine::startNextTransfer()" << llendl; + if (mCurrentTransfer.notNull() || getMuted()) + { + //llinfos << "Transfer in progress, aborting" << llendl; + return; + } + + // Get the ID for the next asset that we want to transfer. + // Pick one in the following order: + LLUUID asset_id; + S32 i; + LLAudioSource *asp = NULL; + LLAudioData *adp = NULL; + data_map::iterator data_iter; + + // Check all channels for currently playing sounds. + F32 max_pri = -1.f; + for (i = 0; i < MAX_CHANNELS; i++) + { + if (!mChannels[i]) + { + continue; + } + + asp = mChannels[i]->getSource(); + if (!asp) + { + continue; + } + if (asp->getPriority() <= max_pri) + { + continue; + } + + if (asp->getPriority() <= max_pri) + { + continue; + } + + adp = asp->getCurrentData(); + if (!adp) + { + continue; + } + + if (!adp->hasLocalData() && adp->hasValidData()) + { + asset_id = adp->getID(); + max_pri = asp->getPriority(); + } + } + + // Check all channels for currently queued sounds. + if (asset_id.isNull()) + { + max_pri = -1.f; + for (i = 0; i < MAX_CHANNELS; i++) + { + if (!mChannels[i]) + { + continue; + } + + LLAudioSource *asp; + asp = mChannels[i]->getSource(); + if (!asp) + { + continue; + } + + if (asp->getPriority() <= max_pri) + { + continue; + } + + adp = asp->getQueuedData(); + if (!adp) + { + continue; + } + + if (!adp->hasLocalData() && adp->hasValidData()) + { + asset_id = adp->getID(); + max_pri = asp->getPriority(); + } + } + } + + // Check all live channels for other sounds (preloads). + if (asset_id.isNull()) + { + max_pri = -1.f; + for (i = 0; i < MAX_CHANNELS; i++) + { + if (!mChannels[i]) + { + continue; + } + + LLAudioSource *asp; + asp = mChannels[i]->getSource(); + if (!asp) + { + continue; + } + + if (asp->getPriority() <= max_pri) + { + continue; + } + + + for (data_iter = asp->mPreloadMap.begin(); data_iter != asp->mPreloadMap.end(); data_iter++) + { + LLAudioData *adp = data_iter->second; + if (!adp) + { + continue; + } + + if (!adp->hasLocalData() && adp->hasValidData()) + { + asset_id = adp->getID(); + max_pri = asp->getPriority(); + } + } + } + } + + // Check all sources + if (asset_id.isNull()) + { + max_pri = -1.f; + source_map::iterator source_iter; + for (source_iter = mAllSources.begin(); source_iter != mAllSources.end(); source_iter++) + { + asp = source_iter->second; + if (!asp) + { + continue; + } + + if (asp->getPriority() <= max_pri) + { + continue; + } + + adp = asp->getCurrentData(); + if (adp && !adp->hasLocalData() && adp->hasValidData()) + { + asset_id = adp->getID(); + max_pri = asp->getPriority(); + continue; + } + + adp = asp->getQueuedData(); + if (adp && !adp->hasLocalData() && adp->hasValidData()) + { + asset_id = adp->getID(); + max_pri = asp->getPriority(); + continue; + } + + for (data_iter = asp->mPreloadMap.begin(); data_iter != asp->mPreloadMap.end(); data_iter++) + { + LLAudioData *adp = data_iter->second; + if (!adp) + { + continue; + } + + if (!adp->hasLocalData() && adp->hasValidData()) + { + asset_id = adp->getID(); + max_pri = asp->getPriority(); + break; + } + } + } + } + + if (asset_id.notNull()) + { + llinfos << "Getting asset data for: " << asset_id << llendl; + gAudiop->mCurrentTransfer = asset_id; + gAudiop->mCurrentTransferTimer.reset(); + gAssetStorage->getAssetData(asset_id, LLAssetType::AT_SOUND, + assetCallback, NULL); + } + else + { + //llinfos << "No pending transfers?" << llendl; + } +} + + +// static +void LLAudioEngine::assetCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type, void *user_data, S32 result_code, LLExtStat ext_status) +{ + if (result_code) + { + llinfos << "Boom, error in audio file transfer: " << LLAssetStorage::getErrorString( result_code ) << " (" << result_code << ")" << llendl; + // Need to mark data as bad to avoid constant rerequests. + LLAudioData *adp = gAudiop->getAudioData(uuid); + if (adp) + { + adp->setHasValidData(false); + adp->setHasLocalData(false); + adp->setHasDecodedData(false); + } + } + else + { + LLAudioData *adp = gAudiop->getAudioData(uuid); + if (!adp) + { + // Should never happen + llwarns << "Got asset callback without audio data for " << uuid << llendl; + } + else + { + adp->setHasValidData(true); + adp->setHasLocalData(true); + gAudioDecodeMgrp->addDecodeRequest(uuid); + } + } + gAudiop->mCurrentTransfer = LLUUID::null; + gAudiop->startNextTransfer(); +} + + +// +// LLAudioSource implementation +// + + +LLAudioSource::LLAudioSource(const LLUUID& id, const LLUUID& owner_id, const F32 gain, const S32 type) +: mID(id), + mOwnerID(owner_id), + mPriority(0.f), + mGain(gain), + mType(type), + mAmbient(false), + mLoop(false), + mSyncMaster(false), + mSyncSlave(false), + mQueueSounds(false), + mPlayedOnce(false), + mChannelp(NULL), + mCurrentDatap(NULL), + mQueuedDatap(NULL) +{ +} + + +LLAudioSource::~LLAudioSource() +{ + if (mChannelp) + { + // Stop playback of this sound + mChannelp->setSource(NULL); + mChannelp = NULL; + } +} + + +void LLAudioSource::setChannel(LLAudioChannel *channelp) +{ + if (channelp == mChannelp) + { + return; + } + + mChannelp = channelp; +} + + +void LLAudioSource::update() +{ + if (!getCurrentBuffer()) + { + if (getCurrentData()) + { + // Hack - try and load the sound. Will do this as a callback + // on decode later. + if (getCurrentData()->load()) + { + play(getCurrentData()->getID()); + } + } + } +} + +void LLAudioSource::updatePriority() +{ + if (isAmbient()) + { + mPriority = 1.f; + } + else + { + // Priority is based on distance + LLVector3 dist_vec; + dist_vec.setVec(getPositionGlobal()); + dist_vec -= gAudiop->getListenerPos(); + F32 dist_squared = llmax(1.f, dist_vec.magVecSquared()); + + mPriority = mGain / dist_squared; + } +} + +bool LLAudioSource::setupChannel() +{ + LLAudioData *adp = getCurrentData(); + + if (!adp->getBuffer()) + { + // We're not ready to play back the sound yet, so don't try and allocate a channel for it. + //llwarns << "Aborting, no buffer" << llendl; + return false; + } + + + if (!mChannelp) + { + // Update the priority, in case we need to push out another channel. + updatePriority(); + + setChannel(gAudiop->getFreeChannel(getPriority())); + } + + if (!mChannelp) + { + // Ugh, we don't have any free channels. + // Now we have to reprioritize. + // For now, just don't play the sound. + //llwarns << "Aborting, no free channels" << llendl; + return false; + } + + mChannelp->setSource(this); + return true; +} + + +bool LLAudioSource::play(const LLUUID &audio_uuid) +{ + if (audio_uuid.isNull()) + { + if (getChannel()) + { + getChannel()->setSource(NULL); + setChannel(NULL); + addAudioData(NULL, true); + } + } + // Reset our age timeout if someone attempts to play the source. + mAgeTimer.reset(); + + LLAudioData *adp = gAudiop->getAudioData(audio_uuid); + + bool has_buffer = gAudiop->updateBufferForData(adp, audio_uuid); + + + addAudioData(adp); + + if (!has_buffer) + { + // Don't bother trying to set up a channel or anything, we don't have an audio buffer. + return false; + } + + if (!setupChannel()) + { + return false; + } + + if (isSyncSlave()) + { + // A sync slave, it doesn't start playing until it's synced up with the master. + // Flag this channel as waiting for sync, and return true. + getChannel()->setWaiting(true); + return true; + } + + getChannel()->play(); + return true; +} + + +bool LLAudioSource::isDone() +{ + const F32 MAX_AGE = 60.f; + const F32 MAX_UNPLAYED_AGE = 15.f; + + if (isLoop()) + { + // Looped sources never die on their own. + return false; + } + + + if (hasPendingPreloads()) + { + return false; + } + + if (mQueuedDatap) + { + // Don't kill this sound if we've got something queued up to play. + return false; + } + + F32 elapsed = mAgeTimer.getElapsedTimeF32(); + + // This is a single-play source + if (!mChannelp) + { + if ((elapsed > MAX_UNPLAYED_AGE) || mPlayedOnce) + { + // We don't have a channel assigned, and it's been + // over 5 seconds since we tried to play it. Don't bother. + //llinfos << "No channel assigned, source is done" << llendl; + return true; + } + else + { + return false; + } + } + + if (mChannelp->isPlaying()) + { + if (elapsed > MAX_AGE) + { + // Arbitarily cut off non-looped sounds when they're old. + return true; + } + else + { + // Sound is still playing and we haven't timed out, don't kill it. + return false; + } + } + + if ((elapsed > MAX_UNPLAYED_AGE) || mPlayedOnce) + { + // The sound isn't playing back after 5 seconds or we're already done playing it, kill it. + return true; + } + + return false; +} + + +void LLAudioSource::addAudioData(LLAudioData *adp, const bool set_current) +{ + // Only handle a single piece of audio data associated with a source right now, + // until I implement prefetch. + if (set_current) + { + if (!mCurrentDatap) + { + mCurrentDatap = adp; + if (mChannelp) + { + mChannelp->updateBuffer(); + mChannelp->play(); + } + + // Make sure the audio engine knows that we want to request this sound. + gAudiop->startNextTransfer(); + return; + } + else if (mQueueSounds) + { + // If we have current data, and we're queuing, put + // the object onto the queue. + if (mQueuedDatap) + { + // We only queue one sound at a time, and it's a FIFO. + // Don't put it onto the queue. + return; + } + + if (adp == mCurrentDatap && isLoop()) + { + // No point in queueing the same sound if + // we're looping. + return; + } + mQueuedDatap = adp; + + // Make sure the audio engine knows that we want to request this sound. + gAudiop->startNextTransfer(); + } + else + { + if (mCurrentDatap != adp) + { + // Right now, if we're currently playing this sound in a channel, we + // update the buffer that the channel's associated with + // and play it. This may not be the correct behavior. + mCurrentDatap = adp; + if (mChannelp) + { + mChannelp->updateBuffer(); + mChannelp->play(); + } + // Make sure the audio engine knows that we want to request this sound. + gAudiop->startNextTransfer(); + } + } + } + else + { + // Add it to the preload list. + mPreloadMap[adp->getID()] = adp; + gAudiop->startNextTransfer(); + } +} + + +bool LLAudioSource::hasPendingPreloads() const +{ + // Check to see if we've got any preloads on deck for this source + data_map::const_iterator iter; + for (iter = mPreloadMap.begin(); iter != mPreloadMap.end(); iter++) + { + LLAudioData *adp = iter->second; + // note: a bad UUID will forever be !hasDecodedData() + // but also !hasValidData(), hence the check for hasValidData() + if (!adp->hasDecodedData() && adp->hasValidData()) + { + // This source is still waiting for a preload + return true; + } + } + + return false; +} + + +LLAudioData * LLAudioSource::getCurrentData() +{ + return mCurrentDatap; +} + +LLAudioData * LLAudioSource::getQueuedData() +{ + return mQueuedDatap; +} + +LLAudioBuffer * LLAudioSource::getCurrentBuffer() +{ + if (!mCurrentDatap) + { + return NULL; + } + + return mCurrentDatap->getBuffer(); +} + + + + +// +// LLAudioChannel implementation +// + + +LLAudioChannel::LLAudioChannel() : + mCurrentSourcep(NULL), + mCurrentBufferp(NULL), + mLoopedThisFrame(false), + mWaiting(false), + mSecondaryGain(1.0f) +{ +} + + +LLAudioChannel::~LLAudioChannel() +{ + // Need to disconnect any sources which are using this channel. + //llinfos << "Cleaning up audio channel" << llendl; + if (mCurrentSourcep) + { + mCurrentSourcep->setChannel(NULL); + } + mCurrentBufferp = NULL; +} + + +void LLAudioChannel::setSource(LLAudioSource *sourcep) +{ + //llinfos << this << ": setSource(" << sourcep << ")" << llendl; + + if (!sourcep) + { + // Clearing the source for this channel, don't need to do anything. + //llinfos << "Clearing source for channel" << llendl; + cleanup(); + mCurrentSourcep = NULL; + mWaiting = false; + return; + } + + if (sourcep == mCurrentSourcep) + { + // Don't reallocate the channel, this will make FMOD goofy. + //llinfos << "Calling setSource with same source!" << llendl; + } + + mCurrentSourcep = sourcep; + + + updateBuffer(); + update3DPosition(); +} + + +bool LLAudioChannel::updateBuffer() +{ + if (!mCurrentSourcep) + { + // This channel isn't associated with any source, nothing + // to be updated + return false; + } + + // Initialize the channel's gain setting for this sound. + if(gAudiop) + { + setSecondaryGain(gAudiop->getSecondaryGain(mCurrentSourcep->getType())); + } + + LLAudioBuffer *bufferp = mCurrentSourcep->getCurrentBuffer(); + if (bufferp == mCurrentBufferp) + { + if (bufferp) + { + // The source hasn't changed what buffer it's playing + bufferp->mLastUseTimer.reset(); + bufferp->mInUse = true; + } + return false; + } + + // + // The source changed what buffer it's playing. We need to clean up + // the existing channel + // + cleanup(); + + mCurrentBufferp = bufferp; + if (bufferp) + { + bufferp->mLastUseTimer.reset(); + bufferp->mInUse = true; + } + + if (!mCurrentBufferp) + { + // There's no new buffer to be played, so we just abort. + return false; + } + + return true; +} + + + + +// +// LLAudioData implementation +// + + +LLAudioData::LLAudioData(const LLUUID &uuid) : + mID(uuid), + mBufferp(NULL), + mHasLocalData(false), + mHasDecodedData(false), + mHasValidData(true) +{ + if (uuid.isNull()) + { + // This is a null sound. + return; + } + + if (gAudiop && gAudiop->hasDecodedFile(uuid)) + { + // Already have a decoded version, don't need to decode it. + mHasLocalData = true; + mHasDecodedData = true; + } + else if (gAssetStorage && gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND)) + { + mHasLocalData = true; + } +} + + +bool LLAudioData::load() +{ + // For now, just assume we're going to use one buffer per audiodata. + if (mBufferp) + { + // We already have this sound in a buffer, don't do anything. + llinfos << "Already have a buffer for this sound, don't bother loading!" << llendl; + return true; + } + + mBufferp = gAudiop->getFreeBuffer(); + if (!mBufferp) + { + // No free buffers, abort. + llinfos << "Not able to allocate a new audio buffer, aborting." << llendl; + return false; + } + + std::string uuid_str; + std::string wav_path; + mID.toString(uuid_str); + wav_path= gDirUtilp->getExpandedFilename(LL_PATH_CACHE,uuid_str) + ".dsf"; + + if (!mBufferp->loadWAV(wav_path)) + { + // Hrm. Right now, let's unset the buffer, since it's empty. + gAudiop->cleanupBuffer(mBufferp); + mBufferp = NULL; + + return false; + } + mBufferp->mAudioDatap = this; + return true; +} + + diff --git a/indra/llaudio/llaudioengine.h b/indra/llaudio/llaudioengine.h new file mode 100644 index 0000000000..457fd93abe --- /dev/null +++ b/indra/llaudio/llaudioengine.h @@ -0,0 +1,452 @@ +/** + * @file audioengine.h + * @brief Definition of LLAudioEngine base class abstracting the audio support + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + * + * Copyright (c) 2000-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + + +#ifndef LL_AUDIOENGINE_H +#define LL_AUDIOENGINE_H + +#include <list> +#include <map> + +#include "v3math.h" +#include "v3dmath.h" +#include "lltimer.h" +#include "lluuid.h" +#include "llframetimer.h" +#include "llassettype.h" + +#include "lllistener.h" + +const F32 LL_WIND_UPDATE_INTERVAL = 0.1f; +const F32 LL_ROLLOFF_MULTIPLIER_UNDER_WATER = 5.f; // How much sounds are weaker under water +const F32 LL_WIND_UNDERWATER_CENTER_FREQ = 20.f; + +const F32 ATTACHED_OBJECT_TIMEOUT = 5.0f; +const F32 DEFAULT_MIN_DISTANCE = 2.0f; + +#define MAX_CHANNELS 30 +#define MAX_BUFFERS 40 // Some extra for preloading, maybe? + +// This define is intended to allow us to switch from os based wav +// file loading to vfs based wav file loading. The problem is that I +// am unconvinced that the LLWaveFile works for loading sounds from +// memory. So, until that is fixed up, changed, whatever, this remains +// undefined. +//#define USE_WAV_VFILE + +class LLVFS; + +class LLAudioSource; +class LLAudioData; +class LLAudioChannel; +class LLAudioChannelOpenAL; +class LLAudioBuffer; +class LLStreamingAudioInterface; + + +// +// LLAudioEngine definition +// + +class LLAudioEngine +{ + friend class LLAudioChannelOpenAL; // bleh. channel needs some listener methods. + +public: + enum LLAudioType + { + AUDIO_TYPE_NONE = 0, + AUDIO_TYPE_SFX = 1, + AUDIO_TYPE_UI = 2, + AUDIO_TYPE_AMBIENT = 3, + AUDIO_TYPE_COUNT = 4 // last + }; + + LLAudioEngine(); + virtual ~LLAudioEngine(); + + // initialization/startup/shutdown + virtual bool init(const S32 num_channels, void *userdata); + virtual std::string getDriverName(bool verbose) = 0; + virtual void shutdown(); + + // Used by the mechanics of the engine + //virtual void processQueue(const LLUUID &sound_guid); + virtual void setListener(LLVector3 pos,LLVector3 vel,LLVector3 up,LLVector3 at); + virtual void updateWind(LLVector3 direction, F32 camera_height_above_water) = 0; + virtual void idle(F32 max_decode_time = 0.f); + virtual void updateChannels(); + + // + // "End user" functionality + // + virtual bool isWindEnabled(); + virtual void enableWind(bool state_b); + + // Use these for temporarily muting the audio system. + // Does not change buffers, initialization, etc. but + // stops playing new sounds. + virtual void setMuted(bool muted); + virtual bool getMuted() const { return mMuted; } +#ifdef USE_PLUGIN_MEDIA + LLPluginClassMedia* initializeMedia(const std::string& media_type); +#endif + F32 getMasterGain(); + void setMasterGain(F32 gain); + + F32 getSecondaryGain(S32 type); + void setSecondaryGain(S32 type, F32 gain); + + F32 getInternetStreamGain(); + + virtual void setDopplerFactor(F32 factor); + virtual F32 getDopplerFactor(); + virtual void setRolloffFactor(F32 factor); + virtual F32 getRolloffFactor(); + virtual void setMaxWindGain(F32 gain); + + + // Methods actually related to setting up and removing sounds + // Owner ID is the owner of the object making the request + void triggerSound(const LLUUID &sound_id, const LLUUID& owner_id, const F32 gain, + const S32 type = LLAudioEngine::AUDIO_TYPE_NONE, + const LLVector3d &pos_global = LLVector3d::zero); + bool preloadSound(const LLUUID &id); + + void addAudioSource(LLAudioSource *asp); + void cleanupAudioSource(LLAudioSource *asp); + + LLAudioSource *findAudioSource(const LLUUID &source_id); + LLAudioData *getAudioData(const LLUUID &audio_uuid); + + // Internet stream implementation manipulation + LLStreamingAudioInterface *getStreamingAudioImpl(); + void setStreamingAudioImpl(LLStreamingAudioInterface *impl); + // Internet stream methods - these will call down into the *mStreamingAudioImpl if it exists + void startInternetStream(const std::string& url); + void stopInternetStream(); + void pauseInternetStream(int pause); + void updateInternetStream(); // expected to be called often + int isInternetStreamPlaying(); + // use a value from 0.0 to 1.0, inclusive + void setInternetStreamGain(F32 vol); + std::string getInternetStreamURL(); + + // For debugging usage + virtual LLVector3 getListenerPos(); + + LLAudioBuffer *getFreeBuffer(); // Get a free buffer, or flush an existing one if you have to. + LLAudioChannel *getFreeChannel(const F32 priority); // Get a free channel or flush an existing one if your priority is higher + void cleanupBuffer(LLAudioBuffer *bufferp); + + bool hasDecodedFile(const LLUUID &uuid); + bool hasLocalFile(const LLUUID &uuid); + + bool updateBufferForData(LLAudioData *adp, const LLUUID &audio_uuid = LLUUID::null); + + + // Asset callback when we're retrieved a sound from the asset server. + void startNextTransfer(); + static void assetCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type, void *user_data, S32 result_code, LLExtStat ext_status); + + friend class LLPipeline; // For debugging +public: + F32 mMaxWindGain; // Hack. Public to set before fade in? + +protected: + virtual LLAudioBuffer *createBuffer() = 0; + virtual LLAudioChannel *createChannel() = 0; + + virtual void initWind() = 0; + virtual void cleanupWind() = 0; + virtual void setInternalGain(F32 gain) = 0; + + void commitDeferredChanges(); + + virtual void allocateListener() = 0; + + + // listener methods + virtual void setListenerPos(LLVector3 vec); + virtual void setListenerVelocity(LLVector3 vec); + virtual void orientListener(LLVector3 up, LLVector3 at); + virtual void translateListener(LLVector3 vec); + + + F64 mapWindVecToGain(LLVector3 wind_vec); + F64 mapWindVecToPitch(LLVector3 wind_vec); + F64 mapWindVecToPan(LLVector3 wind_vec); + +protected: + LLListener *mListenerp; + + bool mMuted; + void* mUserData; + + S32 mLastStatus; + + S32 mNumChannels; + bool mEnableWind; + + LLUUID mCurrentTransfer; // Audio file currently being transferred by the system + LLFrameTimer mCurrentTransferTimer; + + // A list of all audio sources that are known to the viewer at this time. + // This is most likely a superset of the ones that we actually have audio + // data for, or are playing back. + typedef std::map<LLUUID, LLAudioSource *> source_map; + typedef std::map<LLUUID, LLAudioData *> data_map; + + source_map mAllSources; + data_map mAllData; + + LLAudioChannel *mChannels[MAX_CHANNELS]; + + // Buffers needs to change into a different data structure, as the number of buffers + // that we have active should be limited by RAM usage, not count. + LLAudioBuffer *mBuffers[MAX_BUFFERS]; + + F32 mMasterGain; + F32 mSecondaryGain[AUDIO_TYPE_COUNT]; + + F32 mNextWindUpdate; + + LLFrameTimer mWindUpdateTimer; + +private: + void setDefaults(); + LLStreamingAudioInterface *mStreamingAudioImpl; +}; + + + + +// +// Standard audio source. Can be derived from for special sources, such as those attached to objects. +// + + +class LLAudioSource +{ +public: + // owner_id is the id of the agent responsible for making this sound + // play, for example, the owner of the object currently playing it + LLAudioSource(const LLUUID &id, const LLUUID& owner_id, const F32 gain, const S32 type = LLAudioEngine::AUDIO_TYPE_NONE); + virtual ~LLAudioSource(); + + virtual void update(); // Update this audio source + void updatePriority(); + + void preload(const LLUUID &audio_id); // Only used for preloading UI sounds, now. + + void addAudioData(LLAudioData *adp, bool set_current = TRUE); + + void setAmbient(const bool ambient) { mAmbient = ambient; } + bool isAmbient() const { return mAmbient; } + + void setLoop(const bool loop) { mLoop = loop; } + bool isLoop() const { return mLoop; } + + void setSyncMaster(const bool master) { mSyncMaster = master; } + bool isSyncMaster() const { return mSyncMaster; } + + void setSyncSlave(const bool slave) { mSyncSlave = slave; } + bool isSyncSlave() const { return mSyncSlave; } + + void setQueueSounds(const bool queue) { mQueueSounds = queue; } + bool isQueueSounds() const { return mQueueSounds; } + + void setPlayedOnce(const bool played_once) { mPlayedOnce = played_once; } + + void setType(S32 type) { mType = type; } + S32 getType() { return mType; } + + void setPositionGlobal(const LLVector3d &position_global) { mPositionGlobal = position_global; } + LLVector3d getPositionGlobal() const { return mPositionGlobal; } + LLVector3 getVelocity() const { return mVelocity; } + F32 getPriority() const { return mPriority; } + + // Gain should always be clamped between 0 and 1. + F32 getGain() const { return mGain; } + virtual void setGain(const F32 gain) { mGain = llclamp(gain, 0.f, 1.f); } + + const LLUUID &getID() const { return mID; } + bool isDone(); + + LLAudioData *getCurrentData(); + LLAudioData *getQueuedData(); + LLAudioBuffer *getCurrentBuffer(); + + bool setupChannel(); + bool play(const LLUUID &audio_id); // Start the audio source playing + + bool hasPendingPreloads() const; // Has preloads that haven't been done yet + + friend class LLAudioEngine; + friend class LLAudioChannel; +protected: + void setChannel(LLAudioChannel *channelp); + LLAudioChannel *getChannel() const { return mChannelp; } + +protected: + LLUUID mID; // The ID of the source is that of the object if it's attached to an object. + LLUUID mOwnerID; // owner of the object playing the sound + F32 mPriority; + F32 mGain; + bool mAmbient; + bool mLoop; + bool mSyncMaster; + bool mSyncSlave; + bool mQueueSounds; + bool mPlayedOnce; + S32 mType; + LLVector3d mPositionGlobal; + LLVector3 mVelocity; + + //LLAudioSource *mSyncMasterp; // If we're a slave, the source that we're synced to. + LLAudioChannel *mChannelp; // If we're currently playing back, this is the channel that we're assigned to. + LLAudioData *mCurrentDatap; + LLAudioData *mQueuedDatap; + + typedef std::map<LLUUID, LLAudioData *> data_map; + data_map mPreloadMap; + + LLFrameTimer mAgeTimer; +}; + + + + +// +// Generic metadata about a particular piece of audio data. +// The actual data is handled by the derived LLAudioBuffer classes which are +// derived for each audio engine. +// + + +class LLAudioData +{ +public: + LLAudioData(const LLUUID &uuid); + bool load(); + + LLUUID getID() const { return mID; } + LLAudioBuffer *getBuffer() const { return mBufferp; } + + bool hasLocalData() const { return mHasLocalData; } + bool hasDecodedData() const { return mHasDecodedData; } + bool hasValidData() const { return mHasValidData; } + + void setHasLocalData(const bool hld) { mHasLocalData = hld; } + void setHasDecodedData(const bool hdd) { mHasDecodedData = hdd; } + void setHasValidData(const bool hvd) { mHasValidData = hvd; } + + friend class LLAudioEngine; // Severe laziness, bad. + +protected: + LLUUID mID; + LLAudioBuffer *mBufferp; // If this data is being used by the audio system, a pointer to the buffer will be set here. + bool mHasLocalData; + bool mHasDecodedData; + bool mHasValidData; +}; + + +// +// Base class for an audio channel, i.e. a channel which is capable of playing back a sound. +// Management of channels is done generically, methods for actually manipulating the channel +// are derived for each audio engine. +// + + +class LLAudioChannel +{ +public: + LLAudioChannel(); + virtual ~LLAudioChannel(); + + virtual void setSource(LLAudioSource *sourcep); + LLAudioSource *getSource() const { return mCurrentSourcep; } + + void setSecondaryGain(F32 gain) { mSecondaryGain = gain; } + F32 getSecondaryGain() { return mSecondaryGain; } + + friend class LLAudioEngine; + friend class LLAudioSource; +protected: + virtual void play() = 0; + virtual void playSynced(LLAudioChannel *channelp) = 0; + virtual void cleanup() = 0; + virtual bool isPlaying() = 0; + void setWaiting(const bool waiting) { mWaiting = waiting; } + bool isWaiting() const { return mWaiting; } + + virtual bool updateBuffer(); // Check to see if the buffer associated with the source changed, and update if necessary. + virtual void update3DPosition() = 0; + virtual void updateLoop() = 0; // Update your loop/completion status, for use by queueing/syncing. +protected: + LLAudioSource *mCurrentSourcep; + LLAudioBuffer *mCurrentBufferp; + bool mLoopedThisFrame; + bool mWaiting; // Waiting for sync. + F32 mSecondaryGain; +}; + + + + +// Basically an interface class to the engine-specific implementation +// of audio data that's ready for playback. +// Will likely get more complex as we decide to do stuff like real streaming audio. + + +class LLAudioBuffer +{ +public: + virtual ~LLAudioBuffer() {}; + virtual bool loadWAV(const std::string& filename) = 0; + virtual U32 getLength() = 0; + + friend class LLAudioEngine; + friend class LLAudioChannel; + friend class LLAudioData; +protected: + bool mInUse; + LLAudioData *mAudioDatap; + LLFrameTimer mLastUseTimer; +}; + + + +extern LLAudioEngine* gAudiop; + +#endif diff --git a/indra/llaudio/llaudioengine_fmod.cpp b/indra/llaudio/llaudioengine_fmod.cpp new file mode 100644 index 0000000000..7b12b62d53 --- /dev/null +++ b/indra/llaudio/llaudioengine_fmod.cpp @@ -0,0 +1,766 @@ +/** + * @file audioengine_fmod.cpp + * @brief Implementation of LLAudioEngine class abstracting the audio support as a FMOD 3D implementation + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llstreamingaudio.h" +#include "llstreamingaudio_fmod.h" + +#include "llaudioengine_fmod.h" +#include "lllistener_fmod.h" + +#include "llerror.h" +#include "llmath.h" +#include "llrand.h" + +#include "fmod.h" +#include "fmod_errors.h" +#include "lldir.h" +#include "llapr.h" + +#include "sound_ids.h" + + +extern "C" { + void * F_CALLBACKAPI windCallback(void *originalbuffer, void *newbuffer, int length, void* userdata); +} + +FSOUND_DSPUNIT *gWindDSP = NULL; + + +LLAudioEngine_FMOD::LLAudioEngine_FMOD() +{ + mInited = false; + mWindGen = NULL; +} + + +LLAudioEngine_FMOD::~LLAudioEngine_FMOD() +{ +} + + +bool LLAudioEngine_FMOD::init(const S32 num_channels, void* userdata) +{ + LLAudioEngine::init(num_channels, userdata); + + // Reserve one extra channel for the http stream. + if (!FSOUND_SetMinHardwareChannels(num_channels + 1)) + { + LL_WARNS("AppInit") << "FMOD::init[0](), error: " << FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL; + } + + LL_DEBUGS("AppInit") << "LLAudioEngine_FMOD::init() initializing FMOD" << LL_ENDL; + + F32 version = FSOUND_GetVersion(); + if (version < FMOD_VERSION) + { + LL_WARNS("AppInit") << "Error : You are using the wrong FMOD version (" << version + << ")! You should be using FMOD " << FMOD_VERSION << LL_ENDL; + //return false; + } + + U32 fmod_flags = 0x0; + +#if LL_WINDOWS + // Windows needs to know which window is frontmost. + // This must be called before FSOUND_Init() per the FMOD docs. + // This could be used to let FMOD handle muting when we lose focus, + // but we don't actually want to do that because we want to distinguish + // between minimized and not-focused states. + if (!FSOUND_SetHWND(userdata)) + { + LL_WARNS("AppInit") << "Error setting FMOD window: " + << FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL; + return false; + } + // Play audio when we don't have focus. + // (For example, IM client on top of us.) + // This means we also try to play audio when minimized, + // so we manually handle muting in that case. JC + fmod_flags |= FSOUND_INIT_GLOBALFOCUS; +#endif + +#if LL_LINUX + // initialize the FMOD engine + + // This is a hack to use only FMOD's basic FPU mixer + // when the LL_VALGRIND environmental variable is set, + // otherwise valgrind will fall over on FMOD's MMX detection + if (getenv("LL_VALGRIND")) /*Flawfinder: ignore*/ + { + LL_INFOS("AppInit") << "Pacifying valgrind in FMOD init." << LL_ENDL; + FSOUND_SetMixer(FSOUND_MIXER_QUALITY_FPU); + } + + // If we don't set an output method, Linux FMOD always + // decides on OSS and fails otherwise. So we'll manually + // try ESD, then OSS, then ALSA. + // Why this order? See SL-13250, but in short, OSS emulated + // on top of ALSA is ironically more reliable than raw ALSA. + // Ack, and ESD has more reliable failure modes - but has worse + // latency - than all of them, so wins for now. + bool audio_ok = false; + + if (!audio_ok) + if (NULL == getenv("LL_BAD_FMOD_ESD")) /*Flawfinder: ignore*/ + { + LL_DEBUGS("AppInit") << "Trying ESD audio output..." << LL_ENDL; + if(FSOUND_SetOutput(FSOUND_OUTPUT_ESD) && + FSOUND_Init(44100, num_channels, fmod_flags)) + { + LL_DEBUGS("AppInit") << "ESD audio output initialized OKAY" + << LL_ENDL; + audio_ok = true; + } else { + LL_WARNS("AppInit") << "ESD audio output FAILED to initialize: " + << FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL; + } + } else { + LL_DEBUGS("AppInit") << "ESD audio output SKIPPED" << LL_ENDL; + } + + if (!audio_ok) + if (NULL == getenv("LL_BAD_FMOD_OSS")) /*Flawfinder: ignore*/ + { + LL_DEBUGS("AppInit") << "Trying OSS audio output..." << LL_ENDL; + if(FSOUND_SetOutput(FSOUND_OUTPUT_OSS) && + FSOUND_Init(44100, num_channels, fmod_flags)) + { + LL_DEBUGS("AppInit") << "OSS audio output initialized OKAY" << LL_ENDL; + audio_ok = true; + } else { + LL_WARNS("AppInit") << "OSS audio output FAILED to initialize: " + << FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL; + } + } else { + LL_DEBUGS("AppInit") << "OSS audio output SKIPPED" << LL_ENDL; + } + + if (!audio_ok) + if (NULL == getenv("LL_BAD_FMOD_ALSA")) /*Flawfinder: ignore*/ + { + LL_DEBUGS("AppInit") << "Trying ALSA audio output..." << LL_ENDL; + if(FSOUND_SetOutput(FSOUND_OUTPUT_ALSA) && + FSOUND_Init(44100, num_channels, fmod_flags)) + { + LL_DEBUGS("AppInit") << "ALSA audio output initialized OKAY" << LL_ENDL; + audio_ok = true; + } else { + LL_WARNS("AppInit") << "ALSA audio output FAILED to initialize: " + << FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL; + } + } else { + LL_DEBUGS("AppInit") << "OSS audio output SKIPPED" << LL_ENDL; + } + + if (!audio_ok) + { + LL_WARNS("AppInit") << "Overall audio init failure." << LL_ENDL; + return false; + } + + // On Linux, FMOD causes a SIGPIPE for some netstream error + // conditions (an FMOD bug); ignore SIGPIPE so it doesn't crash us. + // NOW FIXED in FMOD 3.x since 2006-10-01. + //signal(SIGPIPE, SIG_IGN); + + // We're interested in logging which output method we + // ended up with, for QA purposes. + switch (FSOUND_GetOutput()) + { + case FSOUND_OUTPUT_NOSOUND: LL_DEBUGS("AppInit") << "Audio output: NoSound" << LL_ENDL; break; + case FSOUND_OUTPUT_OSS: LL_DEBUGS("AppInit") << "Audio output: OSS" << LL_ENDL; break; + case FSOUND_OUTPUT_ESD: LL_DEBUGS("AppInit") << "Audio output: ESD" << LL_ENDL; break; + case FSOUND_OUTPUT_ALSA: LL_DEBUGS("AppInit") << "Audio output: ALSA" << LL_ENDL; break; + default: LL_INFOS("AppInit") << "Audio output: Unknown!" << LL_ENDL; break; + }; + +#else // LL_LINUX + + // initialize the FMOD engine + if (!FSOUND_Init(44100, num_channels, fmod_flags)) + { + LL_WARNS("AppInit") << "Error initializing FMOD: " + << FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL; + return false; + } + +#endif + + // set up our favourite FMOD-native streaming audio implementation if none has already been added + if (!getStreamingAudioImpl()) // no existing implementation added + setStreamingAudioImpl(new LLStreamingAudio_FMOD()); + + LL_DEBUGS("AppInit") << "LLAudioEngine_FMOD::init() FMOD initialized correctly" << LL_ENDL; + + mInited = true; + + return true; +} + + +std::string LLAudioEngine_FMOD::getDriverName(bool verbose) +{ + if (verbose) + { + F32 version = FSOUND_GetVersion(); + return llformat("FMOD version %f", version); + } + else + { + return "FMOD"; + } +} + + +void LLAudioEngine_FMOD::allocateListener(void) +{ + mListenerp = (LLListener *) new LLListener_FMOD(); + if (!mListenerp) + { + llwarns << "Listener creation failed" << llendl; + } +} + + +void LLAudioEngine_FMOD::shutdown() +{ + if (gWindDSP) + { + FSOUND_DSP_SetActive(gWindDSP,false); + FSOUND_DSP_Free(gWindDSP); + } + + stopInternetStream(); + + LLAudioEngine::shutdown(); + + llinfos << "LLAudioEngine_FMOD::shutdown() closing FMOD" << llendl; + FSOUND_Close(); + llinfos << "LLAudioEngine_FMOD::shutdown() done closing FMOD" << llendl; + + delete mListenerp; + mListenerp = NULL; +} + + +LLAudioBuffer * LLAudioEngine_FMOD::createBuffer() +{ + return new LLAudioBufferFMOD(); +} + + +LLAudioChannel * LLAudioEngine_FMOD::createChannel() +{ + return new LLAudioChannelFMOD(); +} + + +void LLAudioEngine_FMOD::initWind() +{ + mWindGen = new LLWindGen<MIXBUFFERFORMAT>; + + if (!gWindDSP) + { + gWindDSP = FSOUND_DSP_Create(&windCallback, FSOUND_DSP_DEFAULTPRIORITY_CLEARUNIT + 20, mWindGen); + } + if (gWindDSP) + { + FSOUND_DSP_SetActive(gWindDSP, true); + } + mNextWindUpdate = 0.0; +} + + +void LLAudioEngine_FMOD::cleanupWind() +{ + if (gWindDSP) + { + FSOUND_DSP_SetActive(gWindDSP, false); + FSOUND_DSP_Free(gWindDSP); + gWindDSP = NULL; + } + + delete mWindGen; + mWindGen = NULL; +} + + +//----------------------------------------------------------------------- +void LLAudioEngine_FMOD::updateWind(LLVector3 wind_vec, F32 camera_height_above_water) +{ + LLVector3 wind_pos; + F64 pitch; + F64 center_freq; + + if (!mEnableWind) + { + return; + } + + if (mWindUpdateTimer.checkExpirationAndReset(LL_WIND_UPDATE_INTERVAL)) + { + + // wind comes in as Linden coordinate (+X = forward, +Y = left, +Z = up) + // need to convert this to the conventional orientation DS3D and OpenAL use + // where +X = right, +Y = up, +Z = backwards + + wind_vec.setVec(-wind_vec.mV[1], wind_vec.mV[2], -wind_vec.mV[0]); + + // cerr << "Wind update" << endl; + + pitch = 1.0 + mapWindVecToPitch(wind_vec); + center_freq = 80.0 * pow(pitch,2.5*(mapWindVecToGain(wind_vec)+1.0)); + + mWindGen->mTargetFreq = (F32)center_freq; + mWindGen->mTargetGain = (F32)mapWindVecToGain(wind_vec) * mMaxWindGain; + mWindGen->mTargetPanGainR = (F32)mapWindVecToPan(wind_vec); + } +} + +/* +//----------------------------------------------------------------------- +void LLAudioEngine_FMOD::setSourceMinDistance(U16 source_num, F64 distance) +{ + if (!mInited) + { + return; + } + if (mBuffer[source_num]) + { + mMinDistance[source_num] = (F32) distance; + if (!FSOUND_Sample_SetMinMaxDistance(mBuffer[source_num],mMinDistance[source_num], mMaxDistance[source_num])) + { + llwarns << "FMOD::setSourceMinDistance(" << source_num << "), error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl; + } + } +} + +//----------------------------------------------------------------------- +void LLAudioEngine_FMOD::setSourceMaxDistance(U16 source_num, F64 distance) +{ + if (!mInited) + { + return; + } + if (mBuffer[source_num]) + { + mMaxDistance[source_num] = (F32) distance; + if (!FSOUND_Sample_SetMinMaxDistance(mBuffer[source_num],mMinDistance[source_num], mMaxDistance[source_num])) + { + llwarns << "FMOD::setSourceMaxDistance(" << source_num << "), error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl; + } + } +} + +//----------------------------------------------------------------------- +void LLAudioEngine_FMOD::get3DParams(S32 source_num, S32 *volume, S32 *freq, S32 *inside, S32 *outside, LLVector3 *orient, S32 *out_volume, F32 *min_dist, F32 *max_dist) +{ + *volume = 0; + *freq = 0; + *inside = 0; + *outside = 0; + *orient = LLVector3::zero; + *out_volume = 0; + *min_dist = 0.f; + *max_dist = 0.f; +} + +*/ + + +//----------------------------------------------------------------------- +void LLAudioEngine_FMOD::setInternalGain(F32 gain) +{ + if (!mInited) + { + return; + } + + gain = llclamp( gain, 0.0f, 1.0f ); + FSOUND_SetSFXMasterVolume( llround( 255.0f * gain ) ); + + LLStreamingAudioInterface *saimpl = getStreamingAudioImpl(); + if ( saimpl ) + { + // fmod likes its streaming audio channel gain re-asserted after + // master volume change. + saimpl->setGain(saimpl->getGain()); + } +} + +// +// LLAudioChannelFMOD implementation +// + +LLAudioChannelFMOD::LLAudioChannelFMOD() : LLAudioChannel(), mChannelID(0), mLastSamplePos(0) +{ +} + + +LLAudioChannelFMOD::~LLAudioChannelFMOD() +{ + cleanup(); +} + + +bool LLAudioChannelFMOD::updateBuffer() +{ + if (LLAudioChannel::updateBuffer()) + { + // Base class update returned true, which means that we need to actually + // set up the channel for a different buffer. + + LLAudioBufferFMOD *bufferp = (LLAudioBufferFMOD *)mCurrentSourcep->getCurrentBuffer(); + + // Grab the FMOD sample associated with the buffer + FSOUND_SAMPLE *samplep = bufferp->getSample(); + if (!samplep) + { + // This is bad, there should ALWAYS be a sample associated with a legit + // buffer. + llerrs << "No FMOD sample!" << llendl; + return false; + } + + + // Actually play the sound. Start it off paused so we can do all the necessary + // setup. + mChannelID = FSOUND_PlaySoundEx(FSOUND_FREE, samplep, FSOUND_DSP_GetSFXUnit(), true); + + //llinfos << "Setting up channel " << std::hex << mChannelID << std::dec << llendl; + } + + // If we have a source for the channel, we need to update its gain. + if (mCurrentSourcep) + { + // SJB: warnings can spam and hurt framerate, disabling + if (!FSOUND_SetVolume(mChannelID, llround(getSecondaryGain() * mCurrentSourcep->getGain() * 255.0f))) + { +// llwarns << "LLAudioChannelFMOD::updateBuffer error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl; + } + + if (!FSOUND_SetLoopMode(mChannelID, mCurrentSourcep->isLoop() ? FSOUND_LOOP_NORMAL : FSOUND_LOOP_OFF)) + { +// llwarns << "Channel " << mChannelID << "Source ID: " << mCurrentSourcep->getID() +// << " at " << mCurrentSourcep->getPositionGlobal() << llendl; +// llwarns << "LLAudioChannelFMOD::updateBuffer error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl; + } + } + + return true; +} + + +void LLAudioChannelFMOD::update3DPosition() +{ + if (!mChannelID) + { + // We're not actually a live channel (i.e., we're not playing back anything) + return; + } + + LLAudioBufferFMOD *bufferp = (LLAudioBufferFMOD *)mCurrentBufferp; + if (!bufferp) + { + // We don't have a buffer associated with us (should really have been picked up + // by the above if. + return; + } + + if (mCurrentSourcep->isAmbient()) + { + // Ambient sound, don't need to do any positional updates. + bufferp->set3DMode(false); + } + else + { + // Localized sound. Update the position and velocity of the sound. + bufferp->set3DMode(true); + + LLVector3 float_pos; + float_pos.setVec(mCurrentSourcep->getPositionGlobal()); + if (!FSOUND_3D_SetAttributes(mChannelID, float_pos.mV, mCurrentSourcep->getVelocity().mV)) + { + LL_DEBUGS("FMOD") << "LLAudioChannelFMOD::update3DPosition error: " << FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL; + } + } +} + + +void LLAudioChannelFMOD::updateLoop() +{ + if (!mChannelID) + { + // May want to clear up the loop/sample counters. + return; + } + + // + // Hack: We keep track of whether we looped or not by seeing when the + // sample position looks like it's going backwards. Not reliable; may + // yield false negatives. + // + U32 cur_pos = FSOUND_GetCurrentPosition(mChannelID); + if (cur_pos < (U32)mLastSamplePos) + { + mLoopedThisFrame = true; + } + mLastSamplePos = cur_pos; +} + + +void LLAudioChannelFMOD::cleanup() +{ + if (!mChannelID) + { + //llinfos << "Aborting cleanup with no channelID." << llendl; + return; + } + + //llinfos << "Cleaning up channel: " << mChannelID << llendl; + if (!FSOUND_StopSound(mChannelID)) + { + LL_DEBUGS("FMOD") << "LLAudioChannelFMOD::cleanup error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl; + } + + mCurrentBufferp = NULL; + mChannelID = 0; +} + + +void LLAudioChannelFMOD::play() +{ + if (!mChannelID) + { + llwarns << "Playing without a channelID, aborting" << llendl; + return; + } + + if (!FSOUND_SetPaused(mChannelID, false)) + { + llwarns << "LLAudioChannelFMOD::play error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl; + } + getSource()->setPlayedOnce(true); +} + + +void LLAudioChannelFMOD::playSynced(LLAudioChannel *channelp) +{ + LLAudioChannelFMOD *fmod_channelp = (LLAudioChannelFMOD*)channelp; + if (!(fmod_channelp->mChannelID && mChannelID)) + { + // Don't have channels allocated to both the master and the slave + return; + } + + U32 position = FSOUND_GetCurrentPosition(fmod_channelp->mChannelID) % mCurrentBufferp->getLength(); + // Try to match the position of our sync master + if (!FSOUND_SetCurrentPosition(mChannelID, position)) + { + llwarns << "LLAudioChannelFMOD::playSynced unable to set current position" << llendl; + } + + // Start us playing + play(); +} + + +bool LLAudioChannelFMOD::isPlaying() +{ + if (!mChannelID) + { + return false; + } + + return FSOUND_IsPlaying(mChannelID) && (!FSOUND_GetPaused(mChannelID)); +} + + + +// +// LLAudioBufferFMOD implementation +// + + +LLAudioBufferFMOD::LLAudioBufferFMOD() +{ + mSamplep = NULL; +} + + +LLAudioBufferFMOD::~LLAudioBufferFMOD() +{ + if (mSamplep) + { + // Clean up the associated FMOD sample if it exists. + FSOUND_Sample_Free(mSamplep); + mSamplep = NULL; + } +} + + +bool LLAudioBufferFMOD::loadWAV(const std::string& filename) +{ + // Try to open a wav file from disk. This will eventually go away, as we don't + // really want to block doing this. + if (filename.empty()) + { + // invalid filename, abort. + return false; + } + + if (!LLAPRFile::isExist(filename, NULL, LL_APR_RPB)) + { + // File not found, abort. + return false; + } + + if (mSamplep) + { + // If there's already something loaded in this buffer, clean it up. + FSOUND_Sample_Free(mSamplep); + mSamplep = NULL; + } + + // Load up the wav file into an fmod sample +#if LL_WINDOWS + // MikeS. - Loading the sound file manually and then handing it over to FMOD, + // since FMOD uses posix IO internally, + // which doesn't work with unicode file paths. + LLFILE* sound_file = LLFile::fopen(filename,"rb"); /* Flawfinder: ignore */ + if (sound_file) + { + fseek(sound_file,0,SEEK_END); + U32 file_length = ftell(sound_file); //Find the length of the file by seeking to the end and getting the offset + size_t read_count; + fseek(sound_file,0,SEEK_SET); //Seek back to the beginning + char* buffer = new char[file_length]; + llassert(buffer); + read_count = fread((void*)buffer,file_length,1,sound_file);//Load it.. + if(ferror(sound_file)==0 && (read_count == 1)){//No read error, and we got 1 chunk of our size... + unsigned int mode_flags = FSOUND_LOOP_NORMAL | FSOUND_LOADMEMORY; + //FSOUND_16BITS | FSOUND_MONO | FSOUND_LOADMEMORY | FSOUND_LOOP_NORMAL; + mSamplep = FSOUND_Sample_Load(FSOUND_UNMANAGED, buffer, mode_flags , 0, file_length); + } + delete[] buffer; + fclose(sound_file); + } +#else + mSamplep = FSOUND_Sample_Load(FSOUND_UNMANAGED, filename.c_str(), FSOUND_LOOP_NORMAL, 0, 0); +#endif + + if (!mSamplep) + { + // We failed to load the file for some reason. + llwarns << "Could not load data '" << filename << "': " + << FMOD_ErrorString(FSOUND_GetError()) << llendl; + + // + // If we EVER want to load wav files provided by end users, we need + // to rethink this! + // + // file is probably corrupt - remove it. + LLFile::remove(filename); + return false; + } + + // Everything went well, return true + return true; +} + + +U32 LLAudioBufferFMOD::getLength() +{ + if (!mSamplep) + { + return 0; + } + + return FSOUND_Sample_GetLength(mSamplep); +} + + +void LLAudioBufferFMOD::set3DMode(bool use3d) +{ + U16 current_mode = FSOUND_Sample_GetMode(mSamplep); + + if (use3d) + { + if (!FSOUND_Sample_SetMode(mSamplep, (current_mode & (~FSOUND_2D)))) + { + llwarns << "LLAudioBufferFMOD::set3DMode error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl; + } + } + else + { + if (!FSOUND_Sample_SetMode(mSamplep, current_mode | FSOUND_2D)) + { + llwarns << "LLAudioBufferFMOD::set3DMode error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl; + } + } +} + + +void * F_CALLBACKAPI windCallback(void *originalbuffer, void *newbuffer, int length, void* userdata) +{ + // originalbuffer = fmod's original mixbuffer. + // newbuffer = the buffer passed from the previous DSP unit. + // length = length in samples at this mix time. + // param = user parameter passed through in FSOUND_DSP_Create. + // + // modify the buffer in some fashion + + LLWindGen<LLAudioEngine_FMOD::MIXBUFFERFORMAT> *windgen = + (LLWindGen<LLAudioEngine_FMOD::MIXBUFFERFORMAT> *)userdata; + U8 stride; + +#if LL_DARWIN + stride = sizeof(LLAudioEngine_FMOD::MIXBUFFERFORMAT); +#else + int mixertype = FSOUND_GetMixer(); + if (mixertype == FSOUND_MIXER_BLENDMODE || + mixertype == FSOUND_MIXER_QUALITY_FPU) + { + stride = 4; + } + else + { + stride = 2; + } +#endif + + newbuffer = windgen->windGenerate((LLAudioEngine_FMOD::MIXBUFFERFORMAT *)newbuffer, length, stride); + + return newbuffer; +} diff --git a/indra/llaudio/llaudioengine_fmod.h b/indra/llaudio/llaudioengine_fmod.h new file mode 100644 index 0000000000..3968657cba --- /dev/null +++ b/indra/llaudio/llaudioengine_fmod.h @@ -0,0 +1,129 @@ +/** + * @file audioengine_fmod.h + * @brief Definition of LLAudioEngine class abstracting the audio + * support as a FMOD 3D implementation + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_AUDIOENGINE_FMOD_H +#define LL_AUDIOENGINE_FMOD_H + +#include "llaudioengine.h" +#include "lllistener_fmod.h" +#include "llwindgen.h" + +#include "fmod.h" + +class LLAudioStreamManagerFMOD; + +class LLAudioEngine_FMOD : public LLAudioEngine +{ +public: + LLAudioEngine_FMOD(); + virtual ~LLAudioEngine_FMOD(); + + // initialization/startup/shutdown + virtual bool init(const S32 num_channels, void *user_data); + virtual std::string getDriverName(bool verbose); + virtual void allocateListener(); + + virtual void shutdown(); + + /*virtual*/ void initWind(); + /*virtual*/ void cleanupWind(); + + /*virtual*/void updateWind(LLVector3 direction, F32 camera_height_above_water); + +#if LL_DARWIN + typedef S32 MIXBUFFERFORMAT; +#else + typedef S16 MIXBUFFERFORMAT; +#endif + +protected: + /*virtual*/ LLAudioBuffer *createBuffer(); // Get a free buffer, or flush an existing one if you have to. + /*virtual*/ LLAudioChannel *createChannel(); // Create a new audio channel. + + /*virtual*/ void setInternalGain(F32 gain); +protected: + static signed char F_CALLBACKAPI callbackMetaData(char* name, char* value, void* userdata); + + //F32 mMinDistance[MAX_BUFFERS]; + //F32 mMaxDistance[MAX_BUFFERS]; + + bool mInited; + + // On Windows, userdata is the HWND of the application window. + void* mUserData; + + LLWindGen<MIXBUFFERFORMAT> *mWindGen; +}; + + +class LLAudioChannelFMOD : public LLAudioChannel +{ +public: + LLAudioChannelFMOD(); + virtual ~LLAudioChannelFMOD(); + +protected: + /*virtual*/ void play(); + /*virtual*/ void playSynced(LLAudioChannel *channelp); + /*virtual*/ void cleanup(); + /*virtual*/ bool isPlaying(); + + /*virtual*/ bool updateBuffer(); + /*virtual*/ void update3DPosition(); + /*virtual*/ void updateLoop(); + +protected: + int mChannelID; + S32 mLastSamplePos; +}; + + +class LLAudioBufferFMOD : public LLAudioBuffer +{ +public: + LLAudioBufferFMOD(); + virtual ~LLAudioBufferFMOD(); + + /*virtual*/ bool loadWAV(const std::string& filename); + /*virtual*/ U32 getLength(); + friend class LLAudioChannelFMOD; + + void set3DMode(bool use3d); +protected: + FSOUND_SAMPLE *getSample() { return mSamplep; } +protected: + FSOUND_SAMPLE *mSamplep; +}; + + +#endif // LL_AUDIOENGINE_FMOD_H diff --git a/indra/llaudio/llaudioengine_openal.cpp b/indra/llaudio/llaudioengine_openal.cpp new file mode 100644 index 0000000000..a5982ccbd6 --- /dev/null +++ b/indra/llaudio/llaudioengine_openal.cpp @@ -0,0 +1,546 @@ +/** + * @file audioengine_openal.cpp + * @brief implementation of audio engine using OpenAL + * support as a OpenAL 3D implementation + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "lldir.h" + +#include "llaudioengine_openal.h" +#include "lllistener_openal.h" + + +LLAudioEngine_OpenAL::LLAudioEngine_OpenAL() + : + mWindGen(NULL), + mWindBuf(NULL), + mWindBufFreq(0), + mWindBufSamples(0), + mWindBufBytes(0), + mWindSource(AL_NONE), + mNumEmptyWindALBuffers(MAX_NUM_WIND_BUFFERS) +{ +} + +// virtual +LLAudioEngine_OpenAL::~LLAudioEngine_OpenAL() +{ +} + +// virtual +bool LLAudioEngine_OpenAL::init(const S32 num_channels, void* userdata) +{ + mWindGen = NULL; + LLAudioEngine::init(num_channels, userdata); + + if(!alutInit(NULL, NULL)) + { + llwarns << "LLAudioEngine_OpenAL::init() ALUT initialization failed: " << alutGetErrorString (alutGetError ()) << llendl; + return false; + } + + llinfos << "LLAudioEngine_OpenAL::init() OpenAL successfully initialized" << llendl; + + llinfos << "OpenAL version: " + << ll_safe_string(alGetString(AL_VERSION)) << llendl; + llinfos << "OpenAL vendor: " + << ll_safe_string(alGetString(AL_VENDOR)) << llendl; + llinfos << "OpenAL renderer: " + << ll_safe_string(alGetString(AL_RENDERER)) << llendl; + + ALint major = alutGetMajorVersion (); + ALint minor = alutGetMinorVersion (); + llinfos << "ALUT version: " << major << "." << minor << llendl; + + ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext()); + + alcGetIntegerv(device, ALC_MAJOR_VERSION, 1, &major); + alcGetIntegerv(device, ALC_MAJOR_VERSION, 1, &minor); + llinfos << "ALC version: " << major << "." << minor << llendl; + + llinfos << "ALC default device: " + << ll_safe_string(alcGetString(device, + ALC_DEFAULT_DEVICE_SPECIFIER)) + << llendl; + + return true; +} + +// virtual +std::string LLAudioEngine_OpenAL::getDriverName(bool verbose) +{ + ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext()); + std::ostringstream version; + + version << + "OpenAL"; + + if (verbose) + { + version << + ", version " << + ll_safe_string(alGetString(AL_VERSION)) << + " / " << + ll_safe_string(alGetString(AL_VENDOR)) << + " / " << + ll_safe_string(alGetString(AL_RENDERER)); + + if (device) + version << + ": " << + ll_safe_string(alcGetString(device, + ALC_DEFAULT_DEVICE_SPECIFIER)); + } + + return version.str(); +} + +// virtual +void LLAudioEngine_OpenAL::allocateListener() +{ + mListenerp = (LLListener *) new LLListener_OpenAL(); + if(!mListenerp) + { + llwarns << "LLAudioEngine_OpenAL::allocateListener() Listener creation failed" << llendl; + } +} + +// virtual +void LLAudioEngine_OpenAL::shutdown() +{ + llinfos << "About to LLAudioEngine::shutdown()" << llendl; + LLAudioEngine::shutdown(); + + llinfos << "About to alutExit()" << llendl; + if(!alutExit()) + { + llwarns << "Nuts." << llendl; + llwarns << "LLAudioEngine_OpenAL::shutdown() ALUT shutdown failed: " << alutGetErrorString (alutGetError ()) << llendl; + } + + llinfos << "LLAudioEngine_OpenAL::shutdown() OpenAL successfully shut down" << llendl; + + delete mListenerp; + mListenerp = NULL; +} + +LLAudioBuffer *LLAudioEngine_OpenAL::createBuffer() +{ + return new LLAudioBufferOpenAL(); +} + +LLAudioChannel *LLAudioEngine_OpenAL::createChannel() +{ + return new LLAudioChannelOpenAL(); +} + +void LLAudioEngine_OpenAL::setInternalGain(F32 gain) +{ + //llinfos << "LLAudioEngine_OpenAL::setInternalGain() Gain: " << gain << llendl; + alListenerf(AL_GAIN, gain); +} + +LLAudioChannelOpenAL::LLAudioChannelOpenAL() + : + mALSource(AL_NONE), + mLastSamplePos(0) +{ + alGenSources(1, &mALSource); +} + +LLAudioChannelOpenAL::~LLAudioChannelOpenAL() +{ + cleanup(); + alDeleteSources(1, &mALSource); +} + +void LLAudioChannelOpenAL::cleanup() +{ + alSourceStop(mALSource); + mCurrentBufferp = NULL; +} + +void LLAudioChannelOpenAL::play() +{ + if (mALSource == AL_NONE) + { + llwarns << "Playing without a mALSource, aborting" << llendl; + return; + } + + if(!isPlaying()) + { + alSourcePlay(mALSource); + getSource()->setPlayedOnce(true); + } +} + +void LLAudioChannelOpenAL::playSynced(LLAudioChannel *channelp) +{ + if (channelp) + { + LLAudioChannelOpenAL *masterchannelp = + (LLAudioChannelOpenAL*)channelp; + if (mALSource != AL_NONE && + masterchannelp->mALSource != AL_NONE) + { + // we have channels allocated to master and slave + ALfloat master_offset; + alGetSourcef(masterchannelp->mALSource, AL_SEC_OFFSET, + &master_offset); + + llinfos << "Syncing with master at " << master_offset + << "sec" << llendl; + // *TODO: detect when this fails, maybe use AL_SAMPLE_ + alSourcef(mALSource, AL_SEC_OFFSET, master_offset); + } + } + play(); +} + +bool LLAudioChannelOpenAL::isPlaying() +{ + if (mALSource != AL_NONE) + { + ALint state; + alGetSourcei(mALSource, AL_SOURCE_STATE, &state); + if(state == AL_PLAYING) + { + return true; + } + } + + return false; +} + +bool LLAudioChannelOpenAL::updateBuffer() +{ + if (LLAudioChannel::updateBuffer()) + { + // Base class update returned true, which means that we need to actually + // set up the source for a different buffer. + LLAudioBufferOpenAL *bufferp = (LLAudioBufferOpenAL *)mCurrentSourcep->getCurrentBuffer(); + ALuint buffer = bufferp->getBuffer(); + alSourcei(mALSource, AL_BUFFER, buffer); + mLastSamplePos = 0; + } + + if (mCurrentSourcep) + { + alSourcef(mALSource, AL_GAIN, + mCurrentSourcep->getGain() * getSecondaryGain()); + alSourcei(mALSource, AL_LOOPING, + mCurrentSourcep->isLoop() ? AL_TRUE : AL_FALSE); + alSourcef(mALSource, AL_ROLLOFF_FACTOR, + gAudiop->mListenerp->getRolloffFactor()); + } + + return true; +} + + +void LLAudioChannelOpenAL::updateLoop() +{ + if (mALSource == AL_NONE) + { + return; + } + + // Hack: We keep track of whether we looped or not by seeing when the + // sample position looks like it's going backwards. Not reliable; may + // yield false negatives. + // + ALint cur_pos; + alGetSourcei(mALSource, AL_SAMPLE_OFFSET, &cur_pos); + if (cur_pos < mLastSamplePos) + { + mLoopedThisFrame = true; + } + mLastSamplePos = cur_pos; +} + + +void LLAudioChannelOpenAL::update3DPosition() +{ + if(!mCurrentSourcep) + { + return; + } + if (mCurrentSourcep->isAmbient()) + { + alSource3f(mALSource, AL_POSITION, 0.0, 0.0, 0.0); + alSource3f(mALSource, AL_VELOCITY, 0.0, 0.0, 0.0); + alSourcei (mALSource, AL_SOURCE_RELATIVE, AL_TRUE); + } else { + LLVector3 float_pos; + float_pos.setVec(mCurrentSourcep->getPositionGlobal()); + alSourcefv(mALSource, AL_POSITION, float_pos.mV); + alSourcefv(mALSource, AL_VELOCITY, mCurrentSourcep->getVelocity().mV); + alSourcei (mALSource, AL_SOURCE_RELATIVE, AL_FALSE); + } + + alSourcef(mALSource, AL_GAIN, mCurrentSourcep->getGain() * getSecondaryGain()); +} + +LLAudioBufferOpenAL::LLAudioBufferOpenAL() +{ + mALBuffer = AL_NONE; +} + +LLAudioBufferOpenAL::~LLAudioBufferOpenAL() +{ + cleanup(); +} + +void LLAudioBufferOpenAL::cleanup() +{ + if(mALBuffer != AL_NONE) + { + alDeleteBuffers(1, &mALBuffer); + mALBuffer = AL_NONE; + } +} + +bool LLAudioBufferOpenAL::loadWAV(const std::string& filename) +{ + cleanup(); + mALBuffer = alutCreateBufferFromFile(filename.c_str()); + if(mALBuffer == AL_NONE) + { + ALenum error = alutGetError(); + if (gDirUtilp->fileExists(filename)) + { + llwarns << + "LLAudioBufferOpenAL::loadWAV() Error loading " + << filename + << " " << alutGetErrorString(error) << llendl; + } + else + { + // It's common for the file to not actually exist. + lldebugs << + "LLAudioBufferOpenAL::loadWAV() Error loading " + << filename + << " " << alutGetErrorString(error) << llendl; + } + return false; + } + + return true; +} + +U32 LLAudioBufferOpenAL::getLength() +{ + if(mALBuffer == AL_NONE) + { + return 0; + } + ALint length; + alGetBufferi(mALBuffer, AL_SIZE, &length); + return length / 2; // convert size in bytes to size in (16-bit) samples +} + +// ------------ + +void LLAudioEngine_OpenAL::initWind() +{ + ALenum error; + llinfos << "LLAudioEngine_OpenAL::initWind() start" << llendl; + + mNumEmptyWindALBuffers = MAX_NUM_WIND_BUFFERS; + + alGetError(); /* clear error */ + + alGenSources(1,&mWindSource); + + if((error=alGetError()) != AL_NO_ERROR) + { + llwarns << "LLAudioEngine_OpenAL::initWind() Error creating wind sources: "<<error<<llendl; + } + + mWindGen = new LLWindGen<WIND_SAMPLE_T>; + + mWindBufFreq = mWindGen->getInputSamplingRate(); + mWindBufSamples = llceil(mWindBufFreq * WIND_BUFFER_SIZE_SEC); + mWindBufBytes = mWindBufSamples * 2 /*stereo*/ * sizeof(WIND_SAMPLE_T); + + mWindBuf = new WIND_SAMPLE_T [mWindBufSamples * 2 /*stereo*/]; + + if(mWindBuf==NULL) + { + llerrs << "LLAudioEngine_OpenAL::initWind() Error creating wind memory buffer" << llendl; + mEnableWind=false; + } + + llinfos << "LLAudioEngine_OpenAL::initWind() done" << llendl; +} + +void LLAudioEngine_OpenAL::cleanupWind() +{ + llinfos << "LLAudioEngine_OpenAL::cleanupWind()" << llendl; + + if (mWindSource != AL_NONE) + { + // detach and delete all outstanding buffers on the wind source + alSourceStop(mWindSource); + ALint processed; + alGetSourcei(mWindSource, AL_BUFFERS_PROCESSED, &processed); + while (processed--) + { + ALuint buffer = AL_NONE; + alSourceUnqueueBuffers(mWindSource, 1, &buffer); + alDeleteBuffers(1, &buffer); + } + + // delete the wind source itself + alDeleteSources(1, &mWindSource); + + mWindSource = AL_NONE; + } + + delete[] mWindBuf; + mWindBuf = NULL; + + delete mWindGen; + mWindGen = NULL; +} + +void LLAudioEngine_OpenAL::updateWind(LLVector3 wind_vec, F32 camera_altitude) +{ + LLVector3 wind_pos; + F64 pitch; + F64 center_freq; + ALenum error; + + if (!mEnableWind) + return; + + if(!mWindBuf) + return; + + if (mWindUpdateTimer.checkExpirationAndReset(LL_WIND_UPDATE_INTERVAL)) + { + + // wind comes in as Linden coordinate (+X = forward, +Y = left, +Z = up) + // need to convert this to the conventional orientation DS3D and OpenAL use + // where +X = right, +Y = up, +Z = backwards + + wind_vec.setVec(-wind_vec.mV[1], wind_vec.mV[2], -wind_vec.mV[0]); + + pitch = 1.0 + mapWindVecToPitch(wind_vec); + center_freq = 80.0 * pow(pitch,2.5*(mapWindVecToGain(wind_vec)+1.0)); + + mWindGen->mTargetFreq = (F32)center_freq; + mWindGen->mTargetGain = (F32)mapWindVecToGain(wind_vec) * mMaxWindGain; + mWindGen->mTargetPanGainR = (F32)mapWindVecToPan(wind_vec); + + alSourcei(mWindSource, AL_LOOPING, AL_FALSE); + alSource3f(mWindSource, AL_POSITION, 0.0, 0.0, 0.0); + alSource3f(mWindSource, AL_VELOCITY, 0.0, 0.0, 0.0); + alSourcef(mWindSource, AL_ROLLOFF_FACTOR, 0.0); + alSourcei(mWindSource, AL_SOURCE_RELATIVE, AL_TRUE); + } + + // ok lets make a wind buffer now + + ALint processed, queued, unprocessed; + alGetSourcei(mWindSource, AL_BUFFERS_PROCESSED, &processed); + alGetSourcei(mWindSource, AL_BUFFERS_QUEUED, &queued); + unprocessed = queued - processed; + + // ensure that there are always at least 3x as many filled buffers + // queued as we managed to empty since last time. + mNumEmptyWindALBuffers = llmin(mNumEmptyWindALBuffers + processed * 3 - unprocessed, MAX_NUM_WIND_BUFFERS-unprocessed); + mNumEmptyWindALBuffers = llmax(mNumEmptyWindALBuffers, 0); + + //llinfos << "mNumEmptyWindALBuffers: " << mNumEmptyWindALBuffers <<" (" << unprocessed << ":" << processed << ")" << llendl; + + while(processed--) // unqueue old buffers + { + ALuint buffer; + ALenum error; + alGetError(); /* clear error */ + alSourceUnqueueBuffers(mWindSource, 1, &buffer); + error = alGetError(); + if(error != AL_NO_ERROR) + { + llwarns << "LLAudioEngine_OpenAL::updateWind() error swapping (unqueuing) buffers" << llendl; + } + else + { + alDeleteBuffers(1, &buffer); + } + } + + unprocessed += mNumEmptyWindALBuffers; + while (mNumEmptyWindALBuffers > 0) // fill+queue new buffers + { + ALuint buffer; + alGetError(); /* clear error */ + alGenBuffers(1,&buffer); + if((error=alGetError()) != AL_NO_ERROR) + { + llwarns << "LLAudioEngine_OpenAL::initWind() Error creating wind buffer: " << error << llendl; + break; + } + + alBufferData(buffer, + AL_FORMAT_STEREO16, + mWindGen->windGenerate(mWindBuf, + mWindBufSamples, 2), + mWindBufBytes, + mWindBufFreq); + error = alGetError(); + if(error != AL_NO_ERROR) + { + llwarns << "LLAudioEngine_OpenAL::updateWind() error swapping (bufferdata) buffers" << llendl; + } + + alSourceQueueBuffers(mWindSource, 1, &buffer); + error = alGetError(); + if(error != AL_NO_ERROR) + { + llwarns << "LLAudioEngine_OpenAL::updateWind() error swapping (queuing) buffers" << llendl; + } + + --mNumEmptyWindALBuffers; + } + + ALint playing; + alGetSourcei(mWindSource, AL_SOURCE_STATE, &playing); + if(playing != AL_PLAYING) + { + alSourcePlay(mWindSource); + + lldebugs << "Wind had stopped - probably ran out of buffers - restarting: " << (unprocessed+mNumEmptyWindALBuffers) << " now queued." << llendl; + } +} + diff --git a/indra/llaudio/llaudioengine_openal.h b/indra/llaudio/llaudioengine_openal.h new file mode 100644 index 0000000000..5aca03e195 --- /dev/null +++ b/indra/llaudio/llaudioengine_openal.h @@ -0,0 +1,114 @@ +/** + * @file audioengine_openal.cpp + * @brief implementation of audio engine using OpenAL + * support as a OpenAL 3D implementation + * + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + + +#ifndef LL_AUDIOENGINE_OPENAL_H +#define LL_AUDIOENGINE_OPENAL_H + +#include "llaudioengine.h" +#include "lllistener_openal.h" +#include "llwindgen.h" + +class LLAudioEngine_OpenAL : public LLAudioEngine +{ + public: + LLAudioEngine_OpenAL(); + virtual ~LLAudioEngine_OpenAL(); + + virtual bool init(const S32 num_channels, void *user_data); + virtual std::string getDriverName(bool verbose); + virtual void allocateListener(); + + virtual void shutdown(); + + void setInternalGain(F32 gain); + + LLAudioBuffer* createBuffer(); + LLAudioChannel* createChannel(); + + /*virtual*/ void initWind(); + /*virtual*/ void cleanupWind(); + /*virtual*/ void updateWind(LLVector3 direction, F32 camera_altitude); + + private: + void * windDSP(void *newbuffer, int length); + typedef S16 WIND_SAMPLE_T; + LLWindGen<WIND_SAMPLE_T> *mWindGen; + S16 *mWindBuf; + U32 mWindBufFreq; + U32 mWindBufSamples; + U32 mWindBufBytes; + ALuint mWindSource; + int mNumEmptyWindALBuffers; + + static const int MAX_NUM_WIND_BUFFERS = 80; + static const float WIND_BUFFER_SIZE_SEC = 0.05f; // 1/20th sec +}; + +class LLAudioChannelOpenAL : public LLAudioChannel +{ + public: + LLAudioChannelOpenAL(); + virtual ~LLAudioChannelOpenAL(); + protected: + /*virtual*/ void play(); + /*virtual*/ void playSynced(LLAudioChannel *channelp); + /*virtual*/ void cleanup(); + /*virtual*/ bool isPlaying(); + + /*virtual*/ bool updateBuffer(); + /*virtual*/ void update3DPosition(); + /*virtual*/ void updateLoop(); + + ALuint mALSource; + ALint mLastSamplePos; +}; + +class LLAudioBufferOpenAL : public LLAudioBuffer{ + public: + LLAudioBufferOpenAL(); + virtual ~LLAudioBufferOpenAL(); + + bool loadWAV(const std::string& filename); + U32 getLength(); + + friend class LLAudioChannelOpenAL; + protected: + void cleanup(); + ALuint getBuffer() {return mALBuffer;} + + ALuint mALBuffer; +}; + +#endif diff --git a/indra/llaudio/lllistener.cpp b/indra/llaudio/lllistener.cpp new file mode 100644 index 0000000000..846c6bccb5 --- /dev/null +++ b/indra/llaudio/lllistener.cpp @@ -0,0 +1,142 @@ +/** + * @file listener.cpp + * @brief Implementation of LISTENER class abstracting the audio support + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + * + * Copyright (c) 2000-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "lllistener.h" + +#define DEFAULT_AT 0.0f,0.0f,-1.0f +#define DEFAULT_UP 0.0f,1.0f,0.0f + +//----------------------------------------------------------------------- +// constructor +//----------------------------------------------------------------------- +LLListener::LLListener() +{ + init(); +} + +//----------------------------------------------------------------------- +LLListener::~LLListener() +{ +} + +//----------------------------------------------------------------------- +void LLListener::init(void) +{ + mPosition.zeroVec(); + mListenAt.setVec(DEFAULT_AT); + mListenUp.setVec(DEFAULT_UP); + mVelocity.zeroVec(); +} + +//----------------------------------------------------------------------- +void LLListener::translate(LLVector3 offset) +{ + mPosition += offset; +} + +//----------------------------------------------------------------------- +void LLListener::setPosition(LLVector3 pos) +{ + mPosition = pos; +} + +//----------------------------------------------------------------------- +LLVector3 LLListener::getPosition(void) +{ + return(mPosition); +} + +//----------------------------------------------------------------------- +LLVector3 LLListener::getAt(void) +{ + return(mListenAt); +} + +//----------------------------------------------------------------------- +LLVector3 LLListener::getUp(void) +{ + return(mListenUp); +} + +//----------------------------------------------------------------------- +void LLListener::setVelocity(LLVector3 vel) +{ + mVelocity = vel; +} + +//----------------------------------------------------------------------- +void LLListener::orient(LLVector3 up, LLVector3 at) +{ + mListenUp = up; + mListenAt = at; +} + +//----------------------------------------------------------------------- +void LLListener::set(LLVector3 pos, LLVector3 vel, LLVector3 up, LLVector3 at) +{ + mPosition = pos; + mVelocity = vel; + + setPosition(pos); + setVelocity(vel); + orient(up,at); +} + +//----------------------------------------------------------------------- +void LLListener::setDopplerFactor(F32 factor) +{ +} + +//----------------------------------------------------------------------- +F32 LLListener::getDopplerFactor() +{ + return (1.f); +} + +//----------------------------------------------------------------------- +void LLListener::setRolloffFactor(F32 factor) +{ +} + +//----------------------------------------------------------------------- +F32 LLListener::getRolloffFactor() +{ + return (1.f); +} + +//----------------------------------------------------------------------- +void LLListener::commitDeferredChanges() +{ +} + diff --git a/indra/llaudio/lllistener.h b/indra/llaudio/lllistener.h new file mode 100644 index 0000000000..e94fbe853f --- /dev/null +++ b/indra/llaudio/lllistener.h @@ -0,0 +1,78 @@ +/** + * @file listener.h + * @brief Description of LISTENER base class abstracting the audio support. + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + * + * Copyright (c) 2000-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LISTENER_H +#define LL_LISTENER_H + +#include "v3math.h" + +class LLListener +{ + private: + protected: + LLVector3 mPosition; + LLVector3 mVelocity; + LLVector3 mListenAt; + LLVector3 mListenUp; + + public: + + private: + protected: + public: + LLListener(); + virtual ~LLListener(); + virtual void init(); + + virtual void set(LLVector3 pos, LLVector3 vel, LLVector3 up, LLVector3 at); + + virtual void setPosition(LLVector3 pos); + virtual void setVelocity(LLVector3 vel); + + virtual void orient(LLVector3 up, LLVector3 at); + virtual void translate(LLVector3 offset); + + virtual void setDopplerFactor(F32 factor); + virtual void setRolloffFactor(F32 factor); + + virtual LLVector3 getPosition(); + virtual LLVector3 getAt(); + virtual LLVector3 getUp(); + + virtual F32 getDopplerFactor(); + virtual F32 getRolloffFactor(); + + virtual void commitDeferredChanges(); +}; + +#endif + diff --git a/indra/llaudio/lllistener_ds3d.h b/indra/llaudio/lllistener_ds3d.h new file mode 100644 index 0000000000..1ff9c170c4 --- /dev/null +++ b/indra/llaudio/lllistener_ds3d.h @@ -0,0 +1,74 @@ +/** + * @file listener_ds3d.h + * @brief Description of LISTENER class abstracting the audio support + * as a DirectSound 3D implementation (windows only) + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + * + * Copyright (c) 2000-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LISTENER_DS3D_H +#define LL_LISTENER_DS3D_H + +#include "lllistener.h" + +#include <dmusici.h> +#include <dsound.h> +#include <ks.h> + +class LLListener_DS3D : public LLListener +{ + private: + protected: + IDirectSound3DListener8 *m3DListener; + public: + + private: + protected: + public: + LLListener_DS3D(); + virtual ~LLListener_DS3D(); + virtual void init(); + + virtual void setDS3DLPtr (IDirectSound3DListener8 *listener_p); + + virtual void translate(LLVector3 offset); + virtual void setPosition(LLVector3 pos); + virtual void setVelocity(LLVector3 vel); + virtual void orient(LLVector3 up, LLVector3 at); + + virtual void setDopplerFactor(F32 factor); + virtual F32 getDopplerFactor(); + virtual void setRolloffFactor(F32 factor); + virtual F32 getRolloffFactor(); + + virtual void commitDeferredChanges(); +}; + +#endif + + diff --git a/indra/llaudio/lllistener_fmod.cpp b/indra/llaudio/lllistener_fmod.cpp new file mode 100644 index 0000000000..57ad461b02 --- /dev/null +++ b/indra/llaudio/lllistener_fmod.cpp @@ -0,0 +1,131 @@ +/** + * @file listener_fmod.cpp + * @brief implementation of LISTENER class abstracting the audio + * support as a FMOD 3D implementation (windows only) + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llaudioengine.h" +#include "lllistener_fmod.h" +#include "fmod.h" + +//----------------------------------------------------------------------- +// constructor +//----------------------------------------------------------------------- +LLListener_FMOD::LLListener_FMOD() +{ + init(); +} + +//----------------------------------------------------------------------- +LLListener_FMOD::~LLListener_FMOD() +{ +} + +//----------------------------------------------------------------------- +void LLListener_FMOD::init(void) +{ + // do inherited + LLListener::init(); + mDopplerFactor = 1.0f; + mRolloffFactor = 1.0f; +} + +//----------------------------------------------------------------------- +void LLListener_FMOD::translate(LLVector3 offset) +{ + LLListener::translate(offset); + + FSOUND_3D_Listener_SetAttributes(mPosition.mV, NULL, mListenAt.mV[0],mListenAt.mV[1],mListenAt.mV[2], mListenUp.mV[0],mListenUp.mV[1],mListenUp.mV[2]); +} + +//----------------------------------------------------------------------- +void LLListener_FMOD::setPosition(LLVector3 pos) +{ + LLListener::setPosition(pos); + + FSOUND_3D_Listener_SetAttributes(pos.mV, NULL, mListenAt.mV[0],mListenAt.mV[1],mListenAt.mV[2], mListenUp.mV[0],mListenUp.mV[1],mListenUp.mV[2]); +} + +//----------------------------------------------------------------------- +void LLListener_FMOD::setVelocity(LLVector3 vel) +{ + LLListener::setVelocity(vel); + + FSOUND_3D_Listener_SetAttributes(NULL, vel.mV, mListenAt.mV[0],mListenAt.mV[1],mListenAt.mV[2], mListenUp.mV[0],mListenUp.mV[1],mListenUp.mV[2]); +} + +//----------------------------------------------------------------------- +void LLListener_FMOD::orient(LLVector3 up, LLVector3 at) +{ + LLListener::orient(up, at); + + // Welcome to the transition between right and left + // (coordinate systems, that is) + // Leaving the at vector alone results in a L/R reversal + // since DX is left-handed and we (LL, OpenGL, OpenAL) are right-handed + at = -at; + + FSOUND_3D_Listener_SetAttributes(NULL, NULL, at.mV[0],at.mV[1],at.mV[2], up.mV[0],up.mV[1],up.mV[2]); +} + +//----------------------------------------------------------------------- +void LLListener_FMOD::commitDeferredChanges() +{ + FSOUND_Update(); +} + + +void LLListener_FMOD::setRolloffFactor(F32 factor) +{ + mRolloffFactor = factor; + FSOUND_3D_SetRolloffFactor(factor); +} + + +F32 LLListener_FMOD::getRolloffFactor() +{ + return mRolloffFactor; +} + + +void LLListener_FMOD::setDopplerFactor(F32 factor) +{ + mDopplerFactor = factor; + FSOUND_3D_SetDopplerFactor(factor); +} + + +F32 LLListener_FMOD::getDopplerFactor() +{ + return mDopplerFactor; +} + + diff --git a/indra/llaudio/lllistener_fmod.h b/indra/llaudio/lllistener_fmod.h new file mode 100644 index 0000000000..5a48ec8b68 --- /dev/null +++ b/indra/llaudio/lllistener_fmod.h @@ -0,0 +1,64 @@ +/** + * @file listener_fmod.h + * @brief Description of LISTENER class abstracting the audio support + * as an FMOD 3D implementation (windows and Linux) + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LISTENER_FMOD_H +#define LL_LISTENER_FMOD_H + +#include "lllistener.h" + +class LLListener_FMOD : public LLListener +{ + public: + LLListener_FMOD(); + virtual ~LLListener_FMOD(); + virtual void init(); + + virtual void translate(LLVector3 offset); + virtual void setPosition(LLVector3 pos); + virtual void setVelocity(LLVector3 vel); + virtual void orient(LLVector3 up, LLVector3 at); + virtual void commitDeferredChanges(); + + virtual void setDopplerFactor(F32 factor); + virtual F32 getDopplerFactor(); + virtual void setRolloffFactor(F32 factor); + virtual F32 getRolloffFactor(); + + protected: + F32 mDopplerFactor; + F32 mRolloffFactor; +}; + +#endif + + diff --git a/indra/llaudio/lllistener_openal.cpp b/indra/llaudio/lllistener_openal.cpp new file mode 100644 index 0000000000..a96ebd5dba --- /dev/null +++ b/indra/llaudio/lllistener_openal.cpp @@ -0,0 +1,116 @@ +/** + * @file audioengine_openal.cpp + * @brief implementation of audio engine using OpenAL + * support as a OpenAL 3D implementation + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llaudioengine.h" + +#include "lllistener_openal.h" + +LLListener_OpenAL::LLListener_OpenAL() +{ + init(); +} + +LLListener_OpenAL::~LLListener_OpenAL() +{ +} + +void LLListener_OpenAL::translate(LLVector3 offset) +{ + //llinfos << "LLListener_OpenAL::translate() : " << offset << llendl; + LLListener::translate(offset); +} + +void LLListener_OpenAL::setPosition(LLVector3 pos) +{ + //llinfos << "LLListener_OpenAL::setPosition() : " << pos << llendl; + LLListener::setPosition(pos); +} + +void LLListener_OpenAL::setVelocity(LLVector3 vel) +{ + LLListener::setVelocity(vel); +} + +void LLListener_OpenAL::orient(LLVector3 up, LLVector3 at) +{ + //llinfos << "LLListener_OpenAL::orient() up: " << up << " at: " << at << llendl; + LLListener::orient(up, at); +} + +void LLListener_OpenAL::commitDeferredChanges() +{ + ALfloat orientation[6]; + orientation[0] = mListenAt.mV[0]; + orientation[1] = mListenAt.mV[1]; + orientation[2] = mListenAt.mV[2]; + orientation[3] = mListenUp.mV[0]; + orientation[4] = mListenUp.mV[1]; + orientation[5] = mListenUp.mV[2]; + + ALfloat velocity[3]; + velocity[0] = mVelocity.mV[0]; + velocity[1] = mVelocity.mV[1]; + velocity[2] = mVelocity.mV[2]; + + alListenerfv(AL_ORIENTATION, orientation); + alListenerfv(AL_POSITION, mPosition.mV); + alListenerfv(AL_VELOCITY, velocity); +} + +void LLListener_OpenAL::setDopplerFactor(F32 factor) +{ + //llinfos << "LLListener_OpenAL::setDopplerFactor() : " << factor << llendl; + alDopplerFactor(factor); +} + +F32 LLListener_OpenAL::getDopplerFactor() +{ + ALfloat factor; + factor = alGetFloat(AL_DOPPLER_FACTOR); + //llinfos << "LLListener_OpenAL::getDopplerFactor() : " << factor << llendl; + return factor; +} + + +void LLListener_OpenAL::setRolloffFactor(F32 factor) +{ + mRolloffFactor = factor; +} + +F32 LLListener_OpenAL::getRolloffFactor() +{ + return mRolloffFactor; +} + + diff --git a/indra/llaudio/lllistener_openal.h b/indra/llaudio/lllistener_openal.h new file mode 100644 index 0000000000..0dfeea5c90 --- /dev/null +++ b/indra/llaudio/lllistener_openal.h @@ -0,0 +1,64 @@ +/** + * @file listener_openal.h + * @brief Description of LISTENER class abstracting the audio support + * as an OpenAL implementation + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + * + * Copyright (c) 2000-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LISTENER_OPENAL_H +#define LL_LISTENER_OPENAL_H + +#include "lllistener.h" + +#include "AL/al.h" +#include "AL/alut.h" + +class LLListener_OpenAL : public LLListener +{ + public: + LLListener_OpenAL(); + virtual ~LLListener_OpenAL(); + + virtual void translate(LLVector3 offset); + virtual void setPosition(LLVector3 pos); + virtual void setVelocity(LLVector3 vel); + virtual void orient(LLVector3 up, LLVector3 at); + virtual void commitDeferredChanges(); + + virtual void setDopplerFactor(F32 factor); + virtual F32 getDopplerFactor(); + virtual void setRolloffFactor(F32 factor); + virtual F32 getRolloffFactor(); + + protected: + F32 mRolloffFactor; +}; + +#endif + diff --git a/indra/llaudio/llstreamingaudio.h b/indra/llaudio/llstreamingaudio.h new file mode 100644 index 0000000000..aa89e6a177 --- /dev/null +++ b/indra/llaudio/llstreamingaudio.h @@ -0,0 +1,56 @@ +/** + * @file streamingaudio.h + * @author Tofu Linden + * @brief Definition of LLStreamingAudioInterface base class abstracting the streaming audio interface + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_STREAMINGAUDIO_H +#define LL_STREAMINGAUDIO_H + +#include "stdtypes.h" // from llcommon + +// Entirely abstract. Based exactly on the historic API. +class LLStreamingAudioInterface +{ + public: + virtual ~LLStreamingAudioInterface() {} + + virtual void start(const std::string& url) = 0; + virtual void stop() = 0; + virtual void pause(int pause) = 0; + virtual void update() = 0; + virtual int isPlaying() = 0; + // use a value from 0.0 to 1.0, inclusive + virtual void setGain(F32 vol) = 0; + virtual F32 getGain() = 0; + virtual std::string getURL() = 0; +}; + +#endif // LL_STREAMINGAUDIO_H diff --git a/indra/llaudio/llstreamingaudio_fmod.cpp b/indra/llaudio/llstreamingaudio_fmod.cpp new file mode 100644 index 0000000000..a71a87203c --- /dev/null +++ b/indra/llaudio/llstreamingaudio_fmod.cpp @@ -0,0 +1,362 @@ +/** + * @file streamingaudio_fmod.cpp + * @brief LLStreamingAudio_FMOD implementation + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llmath.h" + +#include "fmod.h" +#include "fmod_errors.h" + +#include "llstreamingaudio_fmod.h" + + +class LLAudioStreamManagerFMOD +{ +public: + LLAudioStreamManagerFMOD(const std::string& url); + int startStream(); + bool stopStream(); // Returns true if the stream was successfully stopped. + bool ready(); + + const std::string& getURL() { return mInternetStreamURL; } + + int getOpenState(); +protected: + FSOUND_STREAM* mInternetStream; + bool mReady; + + std::string mInternetStreamURL; +}; + + + +//--------------------------------------------------------------------------- +// Internet Streaming +//--------------------------------------------------------------------------- +LLStreamingAudio_FMOD::LLStreamingAudio_FMOD() : + mCurrentInternetStreamp(NULL), + mFMODInternetStreamChannel(-1), + mGain(1.0f) +{ + // Number of milliseconds of audio to buffer for the audio card. + // Must be larger than the usual Second Life frame stutter time. + FSOUND_Stream_SetBufferSize(200); + + // Here's where we set the size of the network buffer and some buffering + // parameters. In this case we want a network buffer of 16k, we want it + // to prebuffer 40% of that when we first connect, and we want it + // to rebuffer 80% of that whenever we encounter a buffer underrun. + + // Leave the net buffer properties at the default. + //FSOUND_Stream_Net_SetBufferProperties(20000, 40, 80); +} + + +LLStreamingAudio_FMOD::~LLStreamingAudio_FMOD() +{ + // nothing interesting/safe to do. +} + + +void LLStreamingAudio_FMOD::start(const std::string& url) +{ + //if (!mInited) + //{ + // llwarns << "startInternetStream before audio initialized" << llendl; + // return; + //} + + // "stop" stream but don't clear url, etc. in case url == mInternetStreamURL + stop(); + + if (!url.empty()) + { + llinfos << "Starting internet stream: " << url << llendl; + mCurrentInternetStreamp = new LLAudioStreamManagerFMOD(url); + mURL = url; + } + else + { + llinfos << "Set internet stream to null" << llendl; + mURL.clear(); + } +} + + +void LLStreamingAudio_FMOD::update() +{ + // Kill dead internet streams, if possible + std::list<LLAudioStreamManagerFMOD *>::iterator iter; + for (iter = mDeadStreams.begin(); iter != mDeadStreams.end();) + { + LLAudioStreamManagerFMOD *streamp = *iter; + if (streamp->stopStream()) + { + llinfos << "Closed dead stream" << llendl; + delete streamp; + mDeadStreams.erase(iter++); + } + else + { + iter++; + } + } + + // Don't do anything if there are no streams playing + if (!mCurrentInternetStreamp) + { + return; + } + + int open_state = mCurrentInternetStreamp->getOpenState(); + + if (!open_state) + { + // Stream is live + + // start the stream if it's ready + if (mFMODInternetStreamChannel < 0) + { + mFMODInternetStreamChannel = mCurrentInternetStreamp->startStream(); + + if (mFMODInternetStreamChannel != -1) + { + // Reset volume to previously set volume + setGain(getGain()); + FSOUND_SetPaused(mFMODInternetStreamChannel, false); + } + } + } + + switch(open_state) + { + default: + case 0: + // success + break; + case -1: + // stream handle is invalid + llwarns << "InternetStream - invalid handle" << llendl; + stop(); + return; + case -2: + // opening + break; + case -3: + // failed to open, file not found, perhaps + llwarns << "InternetSteam - failed to open" << llendl; + stop(); + return; + case -4: + // connecting + break; + case -5: + // buffering + break; + } + +} + +void LLStreamingAudio_FMOD::stop() +{ + if (mFMODInternetStreamChannel != -1) + { + FSOUND_SetPaused(mFMODInternetStreamChannel, true); + FSOUND_SetPriority(mFMODInternetStreamChannel, 0); + mFMODInternetStreamChannel = -1; + } + + if (mCurrentInternetStreamp) + { + llinfos << "Stopping internet stream: " << mCurrentInternetStreamp->getURL() << llendl; + if (mCurrentInternetStreamp->stopStream()) + { + delete mCurrentInternetStreamp; + } + else + { + llwarns << "Pushing stream to dead list: " << mCurrentInternetStreamp->getURL() << llendl; + mDeadStreams.push_back(mCurrentInternetStreamp); + } + mCurrentInternetStreamp = NULL; + //mURL.clear(); + } +} + +void LLStreamingAudio_FMOD::pause(int pauseopt) +{ + if (pauseopt < 0) + { + pauseopt = mCurrentInternetStreamp ? 1 : 0; + } + + if (pauseopt) + { + if (mCurrentInternetStreamp) + { + stop(); + } + } + else + { + start(getURL()); + } +} + + +// A stream is "playing" if it has been requested to start. That +// doesn't necessarily mean audio is coming out of the speakers. +int LLStreamingAudio_FMOD::isPlaying() +{ + if (mCurrentInternetStreamp) + { + return 1; // Active and playing + } + else if (!mURL.empty()) + { + return 2; // "Paused" + } + else + { + return 0; + } +} + + +F32 LLStreamingAudio_FMOD::getGain() +{ + return mGain; +} + + +std::string LLStreamingAudio_FMOD::getURL() +{ + return mURL; +} + + +void LLStreamingAudio_FMOD::setGain(F32 vol) +{ + mGain = vol; + + if (mFMODInternetStreamChannel != -1) + { + vol = llclamp(vol, 0.f, 1.f); + int vol_int = llround(vol * 255.f); + FSOUND_SetVolumeAbsolute(mFMODInternetStreamChannel, vol_int); + } +} + + +/////////////////////////////////////////////////////// +// manager of possibly-multiple internet audio streams + +LLAudioStreamManagerFMOD::LLAudioStreamManagerFMOD(const std::string& url) : + mInternetStream(NULL), + mReady(false) +{ + mInternetStreamURL = url; + mInternetStream = FSOUND_Stream_Open(url.c_str(), FSOUND_NORMAL | FSOUND_NONBLOCKING, 0, 0); + if (!mInternetStream) + { + llwarns << "Couldn't open fmod stream, error " + << FMOD_ErrorString(FSOUND_GetError()) + << llendl; + mReady = false; + return; + } + + mReady = true; +} + +int LLAudioStreamManagerFMOD::startStream() +{ + // We need a live and opened stream before we try and play it. + if (!mInternetStream || getOpenState()) + { + llwarns << "No internet stream to start playing!" << llendl; + return -1; + } + + // Make sure the stream is set to 2D mode. + FSOUND_Stream_SetMode(mInternetStream, FSOUND_2D); + + return FSOUND_Stream_PlayEx(FSOUND_FREE, mInternetStream, NULL, true); +} + +bool LLAudioStreamManagerFMOD::stopStream() +{ + if (mInternetStream) + { + int read_percent = 0; + int status = 0; + int bitrate = 0; + unsigned int flags = 0x0; + FSOUND_Stream_Net_GetStatus(mInternetStream, &status, &read_percent, &bitrate, &flags); + + bool close = true; + switch (status) + { + case FSOUND_STREAM_NET_CONNECTING: + close = false; + break; + case FSOUND_STREAM_NET_NOTCONNECTED: + case FSOUND_STREAM_NET_BUFFERING: + case FSOUND_STREAM_NET_READY: + case FSOUND_STREAM_NET_ERROR: + default: + close = true; + } + + if (close) + { + FSOUND_Stream_Close(mInternetStream); + mInternetStream = NULL; + return true; + } + else + { + return false; + } + } + else + { + return true; + } +} + +int LLAudioStreamManagerFMOD::getOpenState() +{ + int open_state = FSOUND_Stream_GetOpenState(mInternetStream); + return open_state; +} diff --git a/indra/llaudio/llstreamingaudio_fmod.h b/indra/llaudio/llstreamingaudio_fmod.h new file mode 100644 index 0000000000..968ab53a0b --- /dev/null +++ b/indra/llaudio/llstreamingaudio_fmod.h @@ -0,0 +1,68 @@ +/** + * @file streamingaudio_fmod.h + * @author Tofu Linden + * @brief Definition of LLStreamingAudio_FMOD implementation + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_STREAMINGAUDIO_FMOD_H +#define LL_STREAMINGAUDIO_FMOD_H + +#include "stdtypes.h" // from llcommon + +#include "llstreamingaudio.h" + +class LLAudioStreamManagerFMOD; + +class LLStreamingAudio_FMOD : public LLStreamingAudioInterface +{ + public: + LLStreamingAudio_FMOD(); + /*virtual*/ ~LLStreamingAudio_FMOD(); + + /*virtual*/ void start(const std::string& url); + /*virtual*/ void stop(); + /*virtual*/ void pause(int pause); + /*virtual*/ void update(); + /*virtual*/ int isPlaying(); + /*virtual*/ void setGain(F32 vol); + /*virtual*/ F32 getGain(); + /*virtual*/ std::string getURL(); + +private: + LLAudioStreamManagerFMOD *mCurrentInternetStreamp; + int mFMODInternetStreamChannel; + std::list<LLAudioStreamManagerFMOD *> mDeadStreams; + + std::string mURL; + F32 mGain; +}; + + +#endif // LL_STREAMINGAUDIO_FMOD_H diff --git a/indra/llaudio/llvorbisencode.cpp b/indra/llaudio/llvorbisencode.cpp new file mode 100644 index 0000000000..8ee082a245 --- /dev/null +++ b/indra/llaudio/llvorbisencode.cpp @@ -0,0 +1,505 @@ +/** + * @file vorbisencode.cpp + * @brief Vorbis encoding routine routine for Indra. + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + * + * Copyright (c) 2000-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "vorbis/vorbisenc.h" + +#include "llvorbisencode.h" +#include "llerror.h" +#include "llrand.h" +#include "llmath.h" +#include "llapr.h" + +//#if LL_DARWIN +// MBW -- XXX -- Getting rid of SecondLifeVorbis for now -- no fmod means no name collisions. +#if 0 +#include "VorbisFramework.h" + +#define vorbis_analysis mac_vorbis_analysis +#define vorbis_analysis_headerout mac_vorbis_analysis_headerout +#define vorbis_analysis_init mac_vorbis_analysis_init +#define vorbis_encode_ctl mac_vorbis_encode_ctl +#define vorbis_encode_setup_init mac_vorbis_encode_setup_init +#define vorbis_encode_setup_managed mac_vorbis_encode_setup_managed + +#define vorbis_info_init mac_vorbis_info_init +#define vorbis_info_clear mac_vorbis_info_clear +#define vorbis_comment_init mac_vorbis_comment_init +#define vorbis_comment_clear mac_vorbis_comment_clear +#define vorbis_block_init mac_vorbis_block_init +#define vorbis_block_clear mac_vorbis_block_clear +#define vorbis_dsp_clear mac_vorbis_dsp_clear +#define vorbis_analysis_buffer mac_vorbis_analysis_buffer +#define vorbis_analysis_wrote mac_vorbis_analysis_wrote +#define vorbis_analysis_blockout mac_vorbis_analysis_blockout + +#define ogg_stream_packetin mac_ogg_stream_packetin +#define ogg_stream_init mac_ogg_stream_init +#define ogg_stream_flush mac_ogg_stream_flush +#define ogg_stream_pageout mac_ogg_stream_pageout +#define ogg_page_eos mac_ogg_page_eos +#define ogg_stream_clear mac_ogg_stream_clear + +#endif + +S32 check_for_invalid_wav_formats(const std::string& in_fname, std::string& error_msg) +{ + U16 num_channels = 0; + U32 sample_rate = 0; + U32 bits_per_sample = 0; + U32 physical_file_size = 0; + U32 chunk_length = 0; + U32 raw_data_length = 0; + U32 bytes_per_sec = 0; + BOOL uncompressed_pcm = FALSE; + + unsigned char wav_header[44]; /*Flawfinder: ignore*/ + + error_msg.clear(); + + //******************************** + LLAPRFile infile ; + infile.open(in_fname,LL_APR_RB); + //******************************** + if (!infile.getFileHandle()) + { + error_msg = "CannotUploadSoundFile"; + return(LLVORBISENC_SOURCE_OPEN_ERR); + } + + infile.read(wav_header, 44); + physical_file_size = infile.seek(APR_END,0); + + if (strncmp((char *)&(wav_header[0]),"RIFF",4)) + { + error_msg = "SoundFileNotRIFF"; + return(LLVORBISENC_WAV_FORMAT_ERR); + } + + if (strncmp((char *)&(wav_header[8]),"WAVE",4)) + { + error_msg = "SoundFileNotRIFF"; + return(LLVORBISENC_WAV_FORMAT_ERR); + } + + // parse the chunks + + U32 file_pos = 12; // start at the first chunk (usually fmt but not always) + + while ((file_pos + 8)< physical_file_size) + { + infile.seek(APR_SET,file_pos); + infile.read(wav_header, 44); + + chunk_length = ((U32) wav_header[7] << 24) + + ((U32) wav_header[6] << 16) + + ((U32) wav_header[5] << 8) + + wav_header[4]; + +// llinfos << "chunk found: '" << wav_header[0] << wav_header[1] << wav_header[2] << wav_header[3] << "'" << llendl; + + if (!(strncmp((char *)&(wav_header[0]),"fmt ",4))) + { + if ((wav_header[8] == 0x01) && (wav_header[9] == 0x00)) + { + uncompressed_pcm = TRUE; + } + num_channels = ((U16) wav_header[11] << 8) + wav_header[10]; + sample_rate = ((U32) wav_header[15] << 24) + + ((U32) wav_header[14] << 16) + + ((U32) wav_header[13] << 8) + + wav_header[12]; + bits_per_sample = ((U16) wav_header[23] << 8) + wav_header[22]; + bytes_per_sec = ((U32) wav_header[19] << 24) + + ((U32) wav_header[18] << 16) + + ((U32) wav_header[17] << 8) + + wav_header[16]; + } + else if (!(strncmp((char *)&(wav_header[0]),"data",4))) + { + raw_data_length = chunk_length; + } + file_pos += (chunk_length + 8); + chunk_length = 0; + } + //**************** + infile.close(); + //**************** + + if (!uncompressed_pcm) + { + error_msg = "SoundFileNotPCM"; + return(LLVORBISENC_PCM_FORMAT_ERR); + } + + if ((num_channels < 1) || (num_channels > 2)) + { + error_msg = "SoundFileInvalidChannelCount"; + return(LLVORBISENC_MULTICHANNEL_ERR); + } + + if (sample_rate != 44100) + { + error_msg = "SoundFileInvalidSampleRate"; + return(LLVORBISENC_UNSUPPORTED_SAMPLE_RATE); + } + + if ((bits_per_sample != 16) && (bits_per_sample != 8)) + { + error_msg = "SoundFileInvalidWordSize"; + return(LLVORBISENC_UNSUPPORTED_WORD_SIZE); + } + + if (!raw_data_length) + { + error_msg = "SoundFileInvalidHeader"; + return(LLVORBISENC_CLIP_TOO_LONG); + } + + F32 clip_length = (F32)raw_data_length/(F32)bytes_per_sec; + + if (clip_length > 10.0f) + { + error_msg = "SoundFileInvalidTooLong"; + return(LLVORBISENC_CLIP_TOO_LONG); + } + + return(LLVORBISENC_NOERR); +} + +S32 encode_vorbis_file(const std::string& in_fname, const std::string& out_fname) +{ +#define READ_BUFFER 1024 + unsigned char readbuffer[READ_BUFFER*4+44]; /* out of the data segment, not the stack */ /*Flawfinder: ignore*/ + + ogg_stream_state os; /* take physical pages, weld into a logical stream of packets */ + ogg_page og; /* one Ogg bitstream page. Vorbis packets are inside */ + ogg_packet op; /* one raw packet of data for decode */ + + vorbis_info vi; /* struct that stores all the static vorbis bitstream settings */ + vorbis_comment vc; /* struct that stores all the user comments */ + + vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */ + vorbis_block vb; /* local working space for packet->PCM decode */ + + int eos=0; + int result; + + U16 num_channels = 0; + U32 sample_rate = 0; + U32 bits_per_sample = 0; + + S32 format_error = 0; + std::string error_msg; + if ((format_error = check_for_invalid_wav_formats(in_fname, error_msg))) + { + llwarns << error_msg << ": " << in_fname << llendl; + return(format_error); + } + +#if 1 + unsigned char wav_header[44]; /*Flawfinder: ignore*/ + + S32 data_left = 0; + + LLAPRFile infile ; + infile.open(in_fname,LL_APR_RB); + if (!infile.getFileHandle()) + { + llwarns << "Couldn't open temporary ogg file for writing: " << in_fname + << llendl; + return(LLVORBISENC_SOURCE_OPEN_ERR); + } + + LLAPRFile outfile ; + outfile.open(out_fname,LL_APR_WPB); + if (!outfile.getFileHandle()) + { + llwarns << "Couldn't open upload sound file for reading: " << in_fname + << llendl; + return(LLVORBISENC_DEST_OPEN_ERR); + } + + // parse the chunks + U32 chunk_length = 0; + U32 file_pos = 12; // start at the first chunk (usually fmt but not always) + + while (infile.eof() != APR_EOF) + { + infile.seek(APR_SET,file_pos); + infile.read(wav_header, 44); + + chunk_length = ((U32) wav_header[7] << 24) + + ((U32) wav_header[6] << 16) + + ((U32) wav_header[5] << 8) + + wav_header[4]; + +// llinfos << "chunk found: '" << wav_header[0] << wav_header[1] << wav_header[2] << wav_header[3] << "'" << llendl; + + if (!(strncmp((char *)&(wav_header[0]),"fmt ",4))) + { + num_channels = ((U16) wav_header[11] << 8) + wav_header[10]; + sample_rate = ((U32) wav_header[15] << 24) + + ((U32) wav_header[14] << 16) + + ((U32) wav_header[13] << 8) + + wav_header[12]; + bits_per_sample = ((U16) wav_header[23] << 8) + wav_header[22]; + } + else if (!(strncmp((char *)&(wav_header[0]),"data",4))) + { + infile.seek(APR_SET,file_pos+8); + // leave the file pointer at the beginning of the data chunk data + data_left = chunk_length; + break; + } + file_pos += (chunk_length + 8); + chunk_length = 0; + } + + + /********** Encode setup ************/ + + /* choose an encoding mode */ + /* (mode 0: 44kHz stereo uncoupled, roughly 128kbps VBR) */ + vorbis_info_init(&vi); + + // always encode to mono + + // SL-52913 & SL-53779 determined this quality level to be our 'good + // enough' general-purpose quality level with a nice low bitrate. + // Equivalent to oggenc -q0.5 + F32 quality = 0.05f; +// quality = (bitrate==128000 ? 0.4f : 0.1); + +// if (vorbis_encode_init(&vi, /* num_channels */ 1 ,sample_rate, -1, bitrate, -1)) + if (vorbis_encode_init_vbr(&vi, /* num_channels */ 1 ,sample_rate, quality)) +// if (vorbis_encode_setup_managed(&vi,1,sample_rate,-1,bitrate,-1) || +// vorbis_encode_ctl(&vi,OV_ECTL_RATEMANAGE_AVG,NULL) || +// vorbis_encode_setup_init(&vi)) + { + llwarns << "unable to initialize vorbis codec at quality " << quality << llendl; + // llwarns << "unable to initialize vorbis codec at bitrate " << bitrate << llendl; + return(LLVORBISENC_DEST_OPEN_ERR); + } + + /* add a comment */ + vorbis_comment_init(&vc); +// vorbis_comment_add(&vc,"Linden"); + + /* set up the analysis state and auxiliary encoding storage */ + vorbis_analysis_init(&vd,&vi); + vorbis_block_init(&vd,&vb); + + /* set up our packet->stream encoder */ + /* pick a random serial number; that way we can more likely build + chained streams just by concatenation */ + ogg_stream_init(&os, ll_rand()); + + /* Vorbis streams begin with three headers; the initial header (with + most of the codec setup parameters) which is mandated by the Ogg + bitstream spec. The second header holds any comment fields. The + third header holds the bitstream codebook. We merely need to + make the headers, then pass them to libvorbis one at a time; + libvorbis handles the additional Ogg bitstream constraints */ + + { + ogg_packet header; + ogg_packet header_comm; + ogg_packet header_code; + + vorbis_analysis_headerout(&vd,&vc,&header,&header_comm,&header_code); + ogg_stream_packetin(&os,&header); /* automatically placed in its own + page */ + ogg_stream_packetin(&os,&header_comm); + ogg_stream_packetin(&os,&header_code); + + /* We don't have to write out here, but doing so makes streaming + * much easier, so we do, flushing ALL pages. This ensures the actual + * audio data will start on a new page + */ + while(!eos){ + int result=ogg_stream_flush(&os,&og); + if(result==0)break; + outfile.write(og.header, og.header_len); + outfile.write(og.body, og.body_len); + } + + } + + + while(!eos) + { + long bytes_per_sample = bits_per_sample/8; + + long bytes=(long)infile.read(readbuffer,llclamp((S32)(READ_BUFFER*num_channels*bytes_per_sample),0,data_left)); /* stereo hardwired here */ + + if (bytes==0) + { + /* end of file. this can be done implicitly in the mainline, + but it's easier to see here in non-clever fashion. + Tell the library we're at end of stream so that it can handle + the last frame and mark end of stream in the output properly */ + + vorbis_analysis_wrote(&vd,0); +// eos = 1; + + } + else + { + long i; + long samples; + int temp; + + data_left -= bytes; + /* data to encode */ + + /* expose the buffer to submit data */ + float **buffer=vorbis_analysis_buffer(&vd,READ_BUFFER); + + i = 0; + samples = bytes / (num_channels * bytes_per_sample); + + if (num_channels == 2) + { + if (bytes_per_sample == 2) + { + /* uninterleave samples */ + for(i=0; i<samples ;i++) + { + temp = ((signed char *)readbuffer)[i*4+1]; /*Flawfinder: ignore*/ + temp += ((signed char *)readbuffer)[i*4+3]; /*Flawfinder: ignore*/ + temp <<= 8; + temp += readbuffer[i*4]; + temp += readbuffer[i*4+2]; + + buffer[0][i] = ((float)temp) / 65536.f; + } + } + else // presume it's 1 byte per which is unsigned (F#@%ing wav "standard") + { + /* uninterleave samples */ + for(i=0; i<samples ;i++) + { + temp = readbuffer[i*2+0]; + temp += readbuffer[i*2+1]; + temp -= 256; + buffer[0][i] = ((float)temp) / 256.f; + } + } + } + else if (num_channels == 1) + { + if (bytes_per_sample == 2) + { + for(i=0; i < samples ;i++) + { + temp = ((signed char*)readbuffer)[i*2+1]; + temp <<= 8; + temp += readbuffer[i*2]; + buffer[0][i] = ((float)temp) / 32768.f; + } + } + else // presume it's 1 byte per which is unsigned (F#@%ing wav "standard") + { + for(i=0; i < samples ;i++) + { + temp = readbuffer[i]; + temp -= 128; + buffer[0][i] = ((float)temp) / 128.f; + } + } + } + + /* tell the library how much we actually submitted */ + vorbis_analysis_wrote(&vd,i); + } + + /* vorbis does some data preanalysis, then divvies up blocks for + more involved (potentially parallel) processing. Get a single + block for encoding now */ + while(vorbis_analysis_blockout(&vd,&vb)==1) + { + + /* analysis */ + /* Do the main analysis, creating a packet */ + vorbis_analysis(&vb, NULL); + vorbis_bitrate_addblock(&vb); + + while(vorbis_bitrate_flushpacket(&vd, &op)) + { + + /* weld the packet into the bitstream */ + ogg_stream_packetin(&os,&op); + + /* write out pages (if any) */ + while(!eos) + { + result = ogg_stream_pageout(&os,&og); + + if(result==0) + break; + + outfile.write(og.header, og.header_len); + outfile.write(og.body, og.body_len); + + /* this could be set above, but for illustrative purposes, I do + it here (to show that vorbis does know where the stream ends) */ + + if(ogg_page_eos(&og)) + eos=1; + + } + } + } + } + + + + /* clean up and exit. vorbis_info_clear() must be called last */ + + ogg_stream_clear(&os); + vorbis_block_clear(&vb); + vorbis_dsp_clear(&vd); + vorbis_comment_clear(&vc); + vorbis_info_clear(&vi); + + /* ogg_page and ogg_packet structs always point to storage in + libvorbis. They're never freed or manipulated directly */ + +// fprintf(stderr,"Vorbis encoding: Done.\n"); + llinfos << "Vorbis encoding: Done." << llendl; + +#endif + return(LLVORBISENC_NOERR); + +} diff --git a/indra/llaudio/llvorbisencode.h b/indra/llaudio/llvorbisencode.h new file mode 100644 index 0000000000..ff5ce3a053 --- /dev/null +++ b/indra/llaudio/llvorbisencode.h @@ -0,0 +1,53 @@ +/** + * @file vorbisencode.h + * @brief Vorbis encoding routine routine for Indra. + * + * $LicenseInfo:firstyear=2000&license=viewergpl$ + * + * Copyright (c) 2000-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_VORBISENCODE_H +#define LL_VORBISENCODE_H + +const S32 LLVORBISENC_NOERR = 0; // no error +const S32 LLVORBISENC_SOURCE_OPEN_ERR = 1; // error opening source +const S32 LLVORBISENC_DEST_OPEN_ERR = 2; // error opening destination +const S32 LLVORBISENC_WAV_FORMAT_ERR = 3; // not a WAV +const S32 LLVORBISENC_PCM_FORMAT_ERR = 4; // not a PCM +const S32 LLVORBISENC_MONO_ERR = 5; // can't do mono +const S32 LLVORBISENC_STEREO_ERR = 6; // can't do stereo +const S32 LLVORBISENC_MULTICHANNEL_ERR = 7; // can't do stereo +const S32 LLVORBISENC_UNSUPPORTED_SAMPLE_RATE = 8; // unsupported sample rate +const S32 LLVORBISENC_UNSUPPORTED_WORD_SIZE = 9; // unsupported word size +const S32 LLVORBISENC_CLIP_TOO_LONG = 10; // source file is too long + + +S32 check_for_invalid_wav_formats(const std::string& in_fname, std::string& error_msg); +S32 encode_vorbis_file(const std::string& in_fname, const std::string& out_fname); + +#endif + diff --git a/indra/llaudio/llwindgen.h b/indra/llaudio/llwindgen.h new file mode 100644 index 0000000000..847bfa6e9d --- /dev/null +++ b/indra/llaudio/llwindgen.h @@ -0,0 +1,136 @@ +/** + * @file windgen.h + * @brief Templated wind noise generation + * + * $LicenseInfo:firstyear=2002&license=viewergpl$ + * + * Copyright (c) 2002-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ +#ifndef WINDGEN_H +#define WINDGEN_H + +#include "llcommon.h" +#include "llrand.h" + +template <class MIXBUFFERFORMAT_T> +class LLWindGen +{ +public: + LLWindGen() : + mTargetGain(0.f), + mTargetFreq(100.f), + mTargetPanGainR(0.5f), + mbuf0(0.0), + mbuf1(0.0), + mbuf2(0.0), + mbuf3(0.0), + mbuf4(0.0), + mbuf5(0.0), + mY0(0.0), + mY1(0.0), + mCurrentGain(0.f), + mCurrentFreq(100.f), + mCurrentPanGainR(0.5f) {}; + + static const U32 getInputSamplingRate() {return mInputSamplingRate;} + + // newbuffer = the buffer passed from the previous DSP unit. + // numsamples = length in samples-per-channel at this mix time. + // stride = number of bytes between start of each sample. + // NOTE: generates L/R interleaved stereo + MIXBUFFERFORMAT_T* windGenerate(MIXBUFFERFORMAT_T *newbuffer, int numsamples, int stride) + { + U8 *cursamplep = (U8*)newbuffer; + + double bandwidth = 50.0F; + double a0,b1,b2; + + // calculate resonant filter coeffs + b2 = exp(-(F_TWO_PI) * (bandwidth / mInputSamplingRate)); + + while (numsamples--) + { + mCurrentFreq = (float)((0.999 * mCurrentFreq) + (0.001 * mTargetFreq)); + mCurrentGain = (float)((0.999 * mCurrentGain) + (0.001 * mTargetGain)); + mCurrentPanGainR = (float)((0.999 * mCurrentPanGainR) + (0.001 * mTargetPanGainR)); + b1 = (-4.0 * b2) / (1.0 + b2) * cos(F_TWO_PI * (mCurrentFreq / mInputSamplingRate)); + a0 = (1.0 - b2) * sqrt(1.0 - (b1 * b1) / (4.0 * b2)); + double nextSample; + + // start with white noise + nextSample = ll_frand(2.0f) - 1.0f; + + // apply pinking filter + mbuf0 = 0.997f * mbuf0 + 0.0126502f * nextSample; + mbuf1 = 0.985f * mbuf1 + 0.0139083f * nextSample; + mbuf2 = 0.950f * mbuf2 + 0.0205439f * nextSample; + mbuf3 = 0.850f * mbuf3 + 0.0387225f * nextSample; + mbuf4 = 0.620f * mbuf4 + 0.0465932f * nextSample; + mbuf5 = 0.250f * mbuf5 + 0.1093477f * nextSample; + + nextSample = mbuf0 + mbuf1 + mbuf2 + mbuf3 + mbuf4 + mbuf5; + + // do a resonant filter on the noise + nextSample = (double)( a0 * nextSample - b1 * mY0 - b2 * mY1 ); + mY1 = mY0; + mY0 = nextSample; + + nextSample *= mCurrentGain; + + MIXBUFFERFORMAT_T sample; + + sample = llfloor(((F32)nextSample*32768.f*(1.0f - mCurrentPanGainR))+0.5f); + *(MIXBUFFERFORMAT_T*)cursamplep = llclamp(sample, (MIXBUFFERFORMAT_T)-32768, (MIXBUFFERFORMAT_T)32767); + cursamplep += stride; + + sample = llfloor(((F32)nextSample*32768.f*mCurrentPanGainR)+0.5f); + *(MIXBUFFERFORMAT_T*)cursamplep = llclamp(sample, (MIXBUFFERFORMAT_T)-32768, (MIXBUFFERFORMAT_T)32767); + cursamplep += stride; + } + + return newbuffer; + } + + F32 mTargetGain; + F32 mTargetFreq; + F32 mTargetPanGainR; + +private: + static const U32 mInputSamplingRate = 44100; + F64 mbuf0; + F64 mbuf1; + F64 mbuf2; + F64 mbuf3; + F64 mbuf4; + F64 mbuf5; + F64 mY0; + F64 mY1; + F32 mCurrentGain; + F32 mCurrentFreq; + F32 mCurrentPanGainR; +}; + +#endif diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index bf46c2c038..4b24a7ad2b 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -53,6 +53,7 @@ set(llcommon_SOURCE_FILES llmetrics.cpp llmortician.cpp llptrto.cpp + llprocesslauncher.cpp llprocessor.cpp llqueuedthread.cpp llrand.cpp @@ -159,6 +160,7 @@ set(llcommon_HEADER_FILES llpointer.h llpreprocessor.h llpriqueuemap.h + llprocesslauncher.h llprocessor.h llptrskiplist.h llptrskipmap.h diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h index c9424f8a5e..a14bcd2ae2 100644 --- a/indra/llcommon/llerrorcontrol.h +++ b/indra/llcommon/llerrorcontrol.h @@ -73,6 +73,7 @@ namespace LLError void setFunctionLevel(const std::string& function_name, LLError::ELevel); void setClassLevel(const std::string& class_name, LLError::ELevel); void setFileLevel(const std::string& file_name, LLError::ELevel); + void setTagLevel(const std::string& file_name, LLError::ELevel); void configure(const LLSD&); // the LLSD can configure all of the settings diff --git a/indra/llcommon/llprocesslauncher.cpp b/indra/llcommon/llprocesslauncher.cpp new file mode 100644 index 0000000000..f0315e92eb --- /dev/null +++ b/indra/llcommon/llprocesslauncher.cpp @@ -0,0 +1,344 @@ +/** + * @file llprocesslauncher.cpp + * @brief Utility class for launching, terminating, and tracking the state of processes. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llprocesslauncher.h" + +#include <iostream> +#if LL_DARWIN || LL_LINUX +// not required or present on Win32 +#include <sys/wait.h> +#endif + +LLProcessLauncher::LLProcessLauncher() +{ +#if LL_WINDOWS + mProcessHandle = 0; +#else + mProcessID = 0; +#endif +} + +LLProcessLauncher::~LLProcessLauncher() +{ + kill(); +} + +void LLProcessLauncher::setExecutable(const std::string &executable) +{ + mExecutable = executable; +} + +void LLProcessLauncher::setWorkingDirectory(const std::string &dir) +{ + mWorkingDir = dir; +} + +void LLProcessLauncher::clearArguments() +{ + mLaunchArguments.clear(); +} + +void LLProcessLauncher::addArgument(const std::string &arg) +{ + mLaunchArguments.push_back(arg); +} + +void LLProcessLauncher::addArgument(const char *arg) +{ + mLaunchArguments.push_back(std::string(arg)); +} + +#if LL_WINDOWS + +int LLProcessLauncher::launch(void) +{ + // If there was already a process associated with this object, kill it. + kill(); + orphan(); + + int result = 0; + + PROCESS_INFORMATION pinfo; + STARTUPINFOA sinfo; + memset(&sinfo, 0, sizeof(sinfo)); + + std::string args = mExecutable; + for(int i = 0; i < (int)mLaunchArguments.size(); i++) + { + args += " "; + args += mLaunchArguments[i]; + } + + // So retarded. Windows requires that the second parameter to CreateProcessA be a writable (non-const) string... + char *args2 = new char[args.size() + 1]; + strcpy(args2, args.c_str()); + + if( ! CreateProcessA( NULL, args2, NULL, NULL, FALSE, 0, NULL, NULL, &sinfo, &pinfo ) ) + { + // TODO: do better than returning the OS-specific error code on failure... + result = GetLastError(); + if(result == 0) + { + // Make absolutely certain we return a non-zero value on failure. + result = -1; + } + } + else + { + // foo = pinfo.dwProcessId; // get your pid here if you want to use it later on + // CloseHandle(pinfo.hProcess); // stops leaks - nothing else + mProcessHandle = pinfo.hProcess; + CloseHandle(pinfo.hThread); // stops leaks - nothing else + } + + delete[] args2; + + return result; +} + +bool LLProcessLauncher::isRunning(void) +{ + if(mProcessHandle != 0) + { + DWORD waitresult = WaitForSingleObject(mProcessHandle, 0); + if(waitresult == WAIT_OBJECT_0) + { + // the process has completed. + mProcessHandle = 0; + } + } + + return (mProcessHandle != 0); +} +bool LLProcessLauncher::kill(void) +{ + bool result = true; + + if(mProcessHandle != 0) + { + TerminateProcess(mProcessHandle,0); + + if(isRunning()) + { + result = false; + } + } + + return result; +} + +void LLProcessLauncher::orphan(void) +{ + // Forget about the process + mProcessHandle = 0; +} + +// static +void LLProcessLauncher::reap(void) +{ + // No actions necessary on Windows. +} + +#else // Mac and linux + +#include <signal.h> +#include <fcntl.h> +#include <errno.h> + +static std::list<pid_t> sZombies; + +// Attempt to reap a process ID -- returns true if the process has exited and been reaped, false otherwise. +static bool reap_pid(pid_t pid) +{ + bool result = false; + + pid_t wait_result = ::waitpid(pid, NULL, WNOHANG); + if(wait_result == pid) + { + result = true; + } + else if(wait_result == -1) + { + if(errno == ECHILD) + { + // No such process -- this may mean we're ignoring SIGCHILD. + result = true; + } + } + + return result; +} + +int LLProcessLauncher::launch(void) +{ + // If there was already a process associated with this object, kill it. + kill(); + orphan(); + + int result = 0; + int current_wd = -1; + + // create an argv vector for the child process + const char ** fake_argv = new const char *[mLaunchArguments.size() + 2]; // 1 for the executable path, 1 for the NULL terminator + + int i = 0; + + // add the executable path + fake_argv[i++] = mExecutable.c_str(); + + // and any arguments + for(int j=0; j < mLaunchArguments.size(); j++) + fake_argv[i++] = mLaunchArguments[j].c_str(); + + // terminate with a null pointer + fake_argv[i] = NULL; + + if(!mWorkingDir.empty()) + { + // save the current working directory + current_wd = ::open(".", O_RDONLY); + + // and change to the one the child will be executed in + if (::chdir(mWorkingDir.c_str())) + { + // chdir failed + } + } + + // flush all buffers before the child inherits them + ::fflush(NULL); + + pid_t id = vfork(); + if(id == 0) + { + // child process + + ::execv(mExecutable.c_str(), (char * const *)fake_argv); + + // If we reach this point, the exec failed. + // Use _exit() instead of exit() per the vfork man page. + _exit(0); + } + + // parent process + + if(current_wd >= 0) + { + // restore the previous working directory + if (::fchdir(current_wd)) + { + // chdir failed + } + ::close(current_wd); + } + + delete[] fake_argv; + + mProcessID = id; + + // At this point, the child process will have been created (since that's how vfork works -- the child borrowed our execution context until it forked) + // If the process doesn't exist at this point, the exec failed. + if(!isRunning()) + { + result = -1; + } + + return result; +} + +bool LLProcessLauncher::isRunning(void) +{ + if(mProcessID != 0) + { + // Check whether the process has exited, and reap it if it has. + if(reap_pid(mProcessID)) + { + // the process has exited. + mProcessID = 0; + } + } + + return (mProcessID != 0); +} + +bool LLProcessLauncher::kill(void) +{ + bool result = true; + + if(mProcessID != 0) + { + // Try to kill the process. We'll do approximately the same thing whether the kill returns an error or not, so we ignore the result. + (void)::kill(mProcessID, SIGTERM); + + // This will have the side-effect of reaping the zombie if the process has exited. + if(isRunning()) + { + result = false; + } + } + + return result; +} + +void LLProcessLauncher::orphan(void) +{ + // Disassociate the process from this object + if(mProcessID != 0) + { + // We may still need to reap the process's zombie eventually + sZombies.push_back(mProcessID); + + mProcessID = 0; + } +} + +// static +void LLProcessLauncher::reap(void) +{ + // Attempt to real all saved process ID's. + + std::list<pid_t>::iterator iter = sZombies.begin(); + while(iter != sZombies.end()) + { + if(reap_pid(*iter)) + { + iter = sZombies.erase(iter); + } + else + { + iter++; + } + } +} + +#endif diff --git a/indra/llcommon/llprocesslauncher.h b/indra/llcommon/llprocesslauncher.h new file mode 100644 index 0000000000..a1b8e22691 --- /dev/null +++ b/indra/llcommon/llprocesslauncher.h @@ -0,0 +1,85 @@ +/** + * @file llprocesslauncher.h + * @brief Utility class for launching, terminating, and tracking the state of processes. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPROCESSLAUNCHER_H +#define LL_LLPROCESSLAUNCHER_H + +#if LL_WINDOWS +#include <windows.h> +#endif + + +/* + LLProcessLauncher handles launching external processes with specified command line arguments. + It also keeps track of whether the process is still running, and can kill it if required. +*/ + +class LLProcessLauncher +{ + LOG_CLASS(LLProcessLauncher); +public: + LLProcessLauncher(); + virtual ~LLProcessLauncher(); + + void setExecutable(const std::string &executable); + void setWorkingDirectory(const std::string &dir); + + void clearArguments(); + void addArgument(const std::string &arg); + void addArgument(const char *arg); + + int launch(void); + bool isRunning(void); + + // Attempt to kill the process -- returns true if the process is no longer running when it returns. + // Note that even if this returns false, the process may exit some time after it's called. + bool kill(void); + + // Use this if you want the external process to continue execution after the LLProcessLauncher instance controlling it is deleted. + // Normally, the destructor will attempt to kill the process and wait for termination. + // This should only be used if the viewer is about to exit -- otherwise, the child process will become a zombie after it exits. + void orphan(void); + + // This needs to be called periodically on Mac/Linux to clean up zombie processes. + static void reap(void); +private: + std::string mExecutable; + std::string mWorkingDir; + std::vector<std::string> mLaunchArguments; + +#if LL_WINDOWS + HANDLE mProcessHandle; +#else + pid_t mProcessID; +#endif +}; + +#endif // LL_LLPROCESSLAUNCHER_H diff --git a/indra/llinventory/llparcel.cpp b/indra/llinventory/llparcel.cpp index e115c0ef81..e48690908e 100644 --- a/indra/llinventory/llparcel.cpp +++ b/indra/llinventory/llparcel.cpp @@ -199,6 +199,12 @@ void LLParcel::init(const LLUUID &owner_id, mObscureMusic = 1; mMediaWidth = 0; mMediaHeight = 0; + setMediaCurrentURL(LLStringUtil::null); + mMediaURLFilterEnable = FALSE; + mMediaURLFilterList = LLSD::emptyArray(); + mMediaAllowNavigate = TRUE; + mMediaURLTimeout = 0.0f; + mMediaPreventCameraZoom = FALSE; mGroupID.setNull(); @@ -314,6 +320,56 @@ void LLParcel::setMediaHeight(S32 height) { mMediaHeight = height; } + +void LLParcel::setMediaCurrentURL(const std::string& url) +{ + mMediaCurrentURL = url; + // The escaping here must match the escaping in the database + // abstraction layer if it's ever added. + // This should really filter the url in some way. Other than + // simply requiring non-printable. + LLStringFn::replace_nonprintable_in_ascii(mMediaCurrentURL, LL_UNKNOWN_CHAR); + +} + +void LLParcel::setMediaURLResetTimer(F32 time) +{ + mMediaResetTimer.start(); + mMediaResetTimer.setTimerExpirySec(time); +} + +void LLParcel::setMediaURLFilterList(LLSD list) +{ + // sanity check LLSD + // must be array of strings + if (!list.isArray()) + { + return; + } + + for (S32 i = 0; i < list.size(); i++) + { + if (!list[i].isString()) + return; + } + + // can't be too big + const S32 MAX_SIZE = 50; + if (list.size() > MAX_SIZE) + { + LLSD new_list = LLSD::emptyArray(); + + for (S32 i = 0; i < llmin(list.size(), MAX_SIZE); i++) + { + new_list.append(list[i]); + } + + list = new_list; + } + + mMediaURLFilterList = list; +} + // virtual void LLParcel::setLocalID(S32 local_id) { @@ -568,6 +624,34 @@ BOOL LLParcel::importAccessEntry(std::istream& input_stream, LLAccessEntry* entr return input_stream.good(); } +BOOL LLParcel::importMediaURLFilter(std::istream& input_stream, std::string& url) +{ + skip_to_end_of_next_keyword("{", input_stream); + + while(input_stream.good()) + { + skip_comments_and_emptyspace(input_stream); + std::string line, keyword, value; + get_line(line, input_stream, MAX_STRING); + get_keyword_and_value(keyword, value, line); + + if ("}" == keyword) + { + break; + } + else if ("url" == keyword) + { + url = value; + } + else + { + llwarns << "Unknown keyword in parcel media url filter section: <" + << keyword << ">" << llendl; + } + } + return input_stream.good(); +} + // Assumes we are in a block "ParcelData" void LLParcel::packMessage(LLMessageSystem* msg) { @@ -593,6 +677,7 @@ void LLParcel::packMessage(LLMessageSystem* msg) // Assumes we are in a block "ParcelData" void LLParcel::packMessage(LLSD& msg) { + // used in the viewer, the sim uses it's own packer msg["local_id"] = getLocalID(); msg["parcel_flags"] = ll_sd_from_U32(getParcelFlags()); msg["sale_price"] = getSalePrice(); @@ -606,9 +691,15 @@ void LLParcel::packMessage(LLSD& msg) msg["media_height"] = getMediaHeight(); msg["auto_scale"] = getMediaAutoScale(); msg["media_loop"] = getMediaLoop(); + msg["media_current_url"] = getMediaCurrentURL(); msg["obscure_media"] = getObscureMedia(); msg["obscure_music"] = getObscureMusic(); msg["media_id"] = getMediaID(); + msg["media_allow_navigate"] = getMediaAllowNavigate(); + msg["media_prevent_camera_zoom"] = getMediaPreventCameraZoom(); + msg["media_url_timeout"] = getMediaURLTimeout(); + msg["media_url_filter_enable"] = getMediaURLFilterEnable(); + msg["media_url_filter_list"] = getMediaURLFilterList(); msg["group_id"] = getGroupID(); msg["pass_price"] = mPassPrice; msg["pass_hours"] = mPassHours; @@ -678,6 +769,21 @@ void LLParcel::unpackMessage(LLMessageSystem* msg) mObscureMedia = true; mObscureMusic = true; } + + if(msg->getNumberOfBlocks("MediaLinkSharing") > 0) + { + msg->getString("MediaLinkSharing", "MediaCurrentURL", buffer); + setMediaCurrentURL(buffer); + msg->getU8 ( "MediaLinkSharing", "MediaAllowNavigate", mMediaAllowNavigate ); + msg->getU8 ( "MediaLinkSharing", "MediaURLFilterEnable", mMediaURLFilterEnable ); + msg->getU8 ( "MediaLinkSharing", "MediaPreventCameraZoom", mMediaPreventCameraZoom ); + msg->getF32( "MediaLinkSharing", "MediaURLTimeout", mMediaURLTimeout); + } + else + { + setMediaCurrentURL(LLStringUtil::null); + } + } void LLParcel::packAccessEntries(LLMessageSystem* msg, @@ -994,6 +1100,20 @@ BOOL LLParcel::isSaleTimerExpired(const U64& time) return expired; } +BOOL LLParcel::isMediaResetTimerExpired(const U64& time) +{ + if (mMediaResetTimer.getStarted() == FALSE) + { + return FALSE; + } + BOOL expired = mMediaResetTimer.checkExpirationAndReset(0.0); + if (expired) + { + mMediaResetTimer.stop(); + } + return expired; +} + void LLParcel::startSale(const LLUUID& buyer_id, BOOL is_buyer_group) { @@ -1117,6 +1237,12 @@ void LLParcel::clearParcel() mObscureMusic = 1; mMediaWidth = 0; mMediaHeight = 0; + setMediaCurrentURL(LLStringUtil::null); + setMediaURLFilterList(LLSD::emptyArray()); + setMediaURLFilterEnable(FALSE); + setMediaAllowNavigate(TRUE); + setMediaPreventCameraZoom(FALSE); + setMediaURLTimeout(0.0f); setMusicURL(LLStringUtil::null); setInEscrow(FALSE); setAuthorizedBuyerID(LLUUID::null); diff --git a/indra/llinventory/llparcel.h b/indra/llinventory/llparcel.h index 71baac8d89..aa8391230c 100644 --- a/indra/llinventory/llparcel.h +++ b/indra/llinventory/llparcel.h @@ -248,6 +248,14 @@ public: void setObscureMusic( U8 flagIn ) { mObscureMusic = flagIn; } void setMediaWidth(S32 width); void setMediaHeight(S32 height); + void setMediaCurrentURL(const std::string& url); + void setMediaURLFilterEnable(U8 enable) { mMediaURLFilterEnable = enable; } + void setMediaURLFilterList(LLSD list); + void setMediaAllowNavigate(U8 enable) { mMediaAllowNavigate = enable; } + void setMediaURLTimeout(F32 timeout) { mMediaURLTimeout = timeout; } + void setMediaPreventCameraZoom(U8 enable) { mMediaPreventCameraZoom = enable; } + + void setMediaURLResetTimer(F32 time); virtual void setLocalID(S32 local_id); // blow away all the extra crap lurking in parcels, including urls, access lists, etc @@ -300,7 +308,8 @@ public: // BOOL importStream(std::istream& input_stream); BOOL importAccessEntry(std::istream& input_stream, LLAccessEntry* entry); -// BOOL exportStream(std::ostream& output_stream); + BOOL importMediaURLFilter(std::istream& input_stream, std::string& url); + // BOOL exportStream(std::ostream& output_stream); void packMessage(LLMessageSystem* msg); void packMessage(LLSD& msg); @@ -342,8 +351,15 @@ public: S32 getMediaHeight() const { return mMediaHeight; } U8 getMediaAutoScale() const { return mMediaAutoScale; } U8 getMediaLoop() const { return mMediaLoop; } + const std::string& getMediaCurrentURL() const { return mMediaCurrentURL; } U8 getObscureMedia() const { return mObscureMedia; } U8 getObscureMusic() const { return mObscureMusic; } + U8 getMediaURLFilterEnable() const { return mMediaURLFilterEnable; } + LLSD getMediaURLFilterList() const { return mMediaURLFilterList; } + U8 getMediaAllowNavigate() const { return mMediaAllowNavigate; } + F32 getMediaURLTimeout() const { return mMediaURLTimeout; } + U8 getMediaPreventCameraZoom() const { return mMediaPreventCameraZoom; } + S32 getLocalID() const { return mLocalID; } const LLUUID& getOwnerID() const { return mOwnerID; } const LLUUID& getGroupID() const { return mGroupID; } @@ -414,6 +430,10 @@ public: void completeSale(U32& type, U8& flags, LLUUID& to_id); void clearSale(); + + BOOL isMediaResetTimerExpired(const U64& time); + + // more accessors U32 getParcelFlags() const { return mParcelFlags; } @@ -592,6 +612,8 @@ protected: LLVector3 mUserLookAt; ELandingType mLandingType; LLTimer mSaleTimerExpires; + LLTimer mMediaResetTimer; + S32 mGraceExtension; // This value is non-zero if there is an auction associated with @@ -619,9 +641,15 @@ protected: S32 mMediaHeight; U8 mMediaAutoScale; U8 mMediaLoop; + std::string mMediaCurrentURL; U8 mObscureMedia; U8 mObscureMusic; LLUUID mMediaID; + U8 mMediaURLFilterEnable; + LLSD mMediaURLFilterList; + U8 mMediaAllowNavigate; + U8 mMediaPreventCameraZoom; + F32 mMediaURLTimeout; S32 mPassPrice; F32 mPassHours; LLVector3 mAABBMin; @@ -651,6 +679,7 @@ public: std::map<LLUUID,LLAccessEntry> mBanList; std::map<LLUUID,LLAccessEntry> mTempBanList; std::map<LLUUID,LLAccessEntry> mTempAccessList; + }; diff --git a/indra/llmath/llvolume.cpp b/indra/llmath/llvolume.cpp index 1250f539f5..a0357a32cc 100644 --- a/indra/llmath/llvolume.cpp +++ b/indra/llmath/llvolume.cpp @@ -3370,7 +3370,8 @@ void LLVolume::generateSilhouetteVertices(std::vector<LLVector3> &vertices, std::vector<S32> &segments, const LLVector3& obj_cam_vec, const LLMatrix4& mat, - const LLMatrix3& norm_mat) + const LLMatrix3& norm_mat, + S32 face_mask) { LLMemType m1(LLMemType::MTYPE_VOLUME); @@ -3378,12 +3379,17 @@ void LLVolume::generateSilhouetteVertices(std::vector<LLVector3> &vertices, normals.clear(); segments.clear(); + S32 cur_index = 0; //for each face for (face_list_t::iterator iter = mVolumeFaces.begin(); iter != mVolumeFaces.end(); ++iter) { const LLVolumeFace& face = *iter; + if (!(face_mask & (0x1 << cur_index++))) + { + continue; + } if (face.mTypeMask & (LLVolumeFace::CAP_MASK)) { } diff --git a/indra/llmath/llvolume.h b/indra/llmath/llvolume.h index af46da05d8..871b334452 100644 --- a/indra/llmath/llvolume.h +++ b/indra/llmath/llvolume.h @@ -902,9 +902,13 @@ public: // returns number of triangle indeces required for path/profile mesh S32 getNumTriangleIndices() const; - void generateSilhouetteVertices(std::vector<LLVector3> &vertices, std::vector<LLVector3> &normals, std::vector<S32> &segments, const LLVector3& view_vec, - const LLMatrix4& mat, - const LLMatrix3& norm_mat); + void generateSilhouetteVertices(std::vector<LLVector3> &vertices, + std::vector<LLVector3> &normals, + std::vector<S32> &segments, + const LLVector3& view_vec, + const LLMatrix4& mat, + const LLMatrix3& norm_mat, + S32 face_index); //get the face index of the face that intersects with the given line segment at the point //closest to start. Moves end to the point of intersection. Returns -1 if no intersection. diff --git a/indra/llplugin/CMakeLists.txt b/indra/llplugin/CMakeLists.txt new file mode 100644 index 0000000000..6706775d4f --- /dev/null +++ b/indra/llplugin/CMakeLists.txt @@ -0,0 +1,55 @@ +# -*- cmake -*- + +project(llplugin) + +include(00-Common) +include(LLCommon) +include(LLImage) +include(LLMath) +include(LLMessage) +include(LLRender) +include(LLXML) +include(LLWindow) + +include_directories( + ${LLCOMMON_INCLUDE_DIRS} + ${LLIMAGE_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} + ${LLMESSAGE_INCLUDE_DIRS} + ${LLRENDER_INCLUDE_DIRS} + ${LLXML_INCLUDE_DIRS} + ${LLWINDOW_INCLUDE_DIRS} + ) + +set(llplugin_SOURCE_FILES + llpluginclassmedia.cpp + llplugininstance.cpp + llpluginmessage.cpp + llpluginmessagepipe.cpp + llpluginprocesschild.cpp + llpluginprocessparent.cpp + llpluginsharedmemory.cpp + ) + +set(llplugin_HEADER_FILES + CMakeLists.txt + + llpluginclassmedia.h + llpluginclassmediaowner.h + llplugininstance.h + llpluginmessage.h + llpluginmessageclasses.h + llpluginmessagepipe.h + llpluginprocesschild.h + llpluginprocessparent.h + llpluginsharedmemory.h + ) + +set_source_files_properties(${llplugin_HEADER_FILES} + PROPERTIES HEADER_FILE_ONLY TRUE) + +list(APPEND llplugin_SOURCE_FILES ${llplugin_HEADER_FILES}) + +add_library (llplugin ${llplugin_SOURCE_FILES}) + +add_subdirectory(slplugin) diff --git a/indra/llplugin/llpluginclassmedia.cpp b/indra/llplugin/llpluginclassmedia.cpp new file mode 100644 index 0000000000..54f153d182 --- /dev/null +++ b/indra/llplugin/llpluginclassmedia.cpp @@ -0,0 +1,1042 @@ +/** + * @file llpluginclassmedia.cpp + * @brief LLPluginClassMedia handles a plugin which knows about the "media" message class. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "indra_constants.h" + +#include "llpluginclassmedia.h" +#include "llpluginmessageclasses.h" + +static int LOW_PRIORITY_TEXTURE_SIZE_DEFAULT = 256; + +static int nextPowerOf2( int value ) +{ + int next_power_of_2 = 1; + while ( next_power_of_2 < value ) + { + next_power_of_2 <<= 1; + } + + return next_power_of_2; +} + +LLPluginClassMedia::LLPluginClassMedia(LLPluginClassMediaOwner *owner) +{ + mOwner = owner; + mPlugin = NULL; + reset(); +} + + +LLPluginClassMedia::~LLPluginClassMedia() +{ + reset(); +} + +bool LLPluginClassMedia::init(const std::string &launcher_filename, const std::string &plugin_filename) +{ + LL_DEBUGS("Plugin") << "launcher: " << launcher_filename << LL_ENDL; + LL_DEBUGS("Plugin") << "plugin: " << plugin_filename << LL_ENDL; + + mPlugin = new LLPluginProcessParent(this); + mPlugin->setSleepTime(mSleepTime); + mPlugin->init(launcher_filename, plugin_filename); + + return true; +} + + +void LLPluginClassMedia::reset() +{ + if(mPlugin != NULL) + { + delete mPlugin; + mPlugin = NULL; + } + + mTextureParamsReceived = false; + mRequestedTextureDepth = 0; + mRequestedTextureInternalFormat = 0; + mRequestedTextureFormat = 0; + mRequestedTextureType = 0; + mRequestedTextureSwapBytes = false; + mRequestedTextureCoordsOpenGL = false; + mTextureSharedMemorySize = 0; + mTextureSharedMemoryName.clear(); + mDefaultMediaWidth = 0; + mDefaultMediaHeight = 0; + mNaturalMediaWidth = 0; + mNaturalMediaHeight = 0; + mSetMediaWidth = -1; + mSetMediaHeight = -1; + mRequestedMediaWidth = 0; + mRequestedMediaHeight = 0; + mTextureWidth = 0; + mTextureHeight = 0; + mMediaWidth = 0; + mMediaHeight = 0; + mDirtyRect = LLRect::null; + mAutoScaleMedia = false; + mRequestedVolume = 1.0f; + mPriority = PRIORITY_NORMAL; + mLowPrioritySizeLimit = LOW_PRIORITY_TEXTURE_SIZE_DEFAULT; + mAllowDownsample = false; + mPadding = 0; + mStatus = LLPluginClassMediaOwner::MEDIA_NONE; + mSleepTime = 1.0f / 100.0f; + mCanCut = false; + mCanCopy = false; + mCanPaste = false; + mMediaName.clear(); + mMediaDescription.clear(); + + // media_browser class + mNavigateURI.clear(); + mNavigateResultCode = -1; + mNavigateResultString.clear(); + mHistoryBackAvailable = false; + mHistoryForwardAvailable = false; + mStatusText.clear(); + mProgressPercent = 0; + + // media_time class + mCurrentTime = 0.0f; + mDuration = 0.0f; + mCurrentRate = 0.0f; +} + +void LLPluginClassMedia::idle(void) +{ + if(mPlugin) + { + mPlugin->idle(); + } + + if((mMediaWidth == -1) || (!mTextureParamsReceived) || (mPlugin == NULL)) + { + // Can't process a size change at this time + } + else if((mRequestedMediaWidth != mMediaWidth) || (mRequestedMediaHeight != mMediaHeight)) + { + // Calculate the correct size for the media texture + mRequestedTextureHeight = mRequestedMediaHeight; + if(mPadding < 0) + { + // negative values indicate the plugin wants a power of 2 + mRequestedTextureWidth = nextPowerOf2(mRequestedMediaWidth); + } + else + { + mRequestedTextureWidth = mRequestedMediaWidth; + + if(mPadding > 1) + { + // Pad up to a multiple of the specified number of bytes per row + int rowbytes = mRequestedTextureWidth * mRequestedTextureDepth; + int pad = rowbytes % mPadding; + if(pad != 0) + { + rowbytes += mPadding - pad; + } + + if(rowbytes % mRequestedTextureDepth == 0) + { + mRequestedTextureWidth = rowbytes / mRequestedTextureDepth; + } + else + { + LL_WARNS("Plugin") << "Unable to pad texture width, padding size " << mPadding << "is not a multiple of pixel size " << mRequestedTextureDepth << LL_ENDL; + } + } + } + + + // Size change has been requested but not initiated yet. + size_t newsize = mRequestedTextureWidth * mRequestedTextureHeight * mRequestedTextureDepth; + + // Add an extra line for padding, just in case. + newsize += mRequestedTextureWidth * mRequestedTextureDepth; + + if(newsize != mTextureSharedMemorySize) + { + if(!mTextureSharedMemoryName.empty()) + { + // Tell the plugin to remove the old memory segment + mPlugin->removeSharedMemory(mTextureSharedMemoryName); + mTextureSharedMemoryName.clear(); + } + + mTextureSharedMemorySize = newsize; + mTextureSharedMemoryName = mPlugin->addSharedMemory(mTextureSharedMemorySize); + if(!mTextureSharedMemoryName.empty()) + { + void *addr = mPlugin->getSharedMemoryAddress(mTextureSharedMemoryName); + + // clear texture memory to avoid random screen visual fuzz from uninitialized texture data + memset( addr, 0x00, newsize ); + + // We could do this to force an update, but textureValid() will still be returning false until the first roundtrip to the plugin, + // so it may not be worthwhile. + // mDirtyRect.setOriginAndSize(0, 0, mRequestedMediaWidth, mRequestedMediaHeight); + } + } + + // This is our local indicator that a change is in progress. + mTextureWidth = -1; + mTextureHeight = -1; + mMediaWidth = -1; + mMediaHeight = -1; + + // This invalidates any existing dirty rect. + resetDirty(); + + // Send a size change message to the plugin + { + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change"); + message.setValue("name", mTextureSharedMemoryName); + message.setValueS32("width", mRequestedMediaWidth); + message.setValueS32("height", mRequestedMediaHeight); + message.setValueS32("texture_width", mRequestedTextureWidth); + message.setValueS32("texture_height", mRequestedTextureHeight); + mPlugin->sendMessage(message); // DO NOT just use sendMessage() here -- we want this to jump ahead of the queue. + + LL_DEBUGS("Plugin") << "Sending size_change" << LL_ENDL; + } + } + + if(mPlugin && mPlugin->isRunning()) + { + // Send queued messages + while(!mSendQueue.empty()) + { + LLPluginMessage message = mSendQueue.front(); + mSendQueue.pop(); + mPlugin->sendMessage(message); + } + } +} + +int LLPluginClassMedia::getTextureWidth() const +{ + return nextPowerOf2(mTextureWidth); +} + +int LLPluginClassMedia::getTextureHeight() const +{ + return nextPowerOf2(mTextureHeight); +} + +unsigned char* LLPluginClassMedia::getBitsData() +{ + unsigned char *result = NULL; + if((mPlugin != NULL) && !mTextureSharedMemoryName.empty()) + { + result = (unsigned char*)mPlugin->getSharedMemoryAddress(mTextureSharedMemoryName); + } + return result; +} + +void LLPluginClassMedia::setSize(int width, int height) +{ + mSetMediaWidth = width; + mSetMediaHeight = height; + + setSizeInternal(); +} + +void LLPluginClassMedia::setSizeInternal(void) +{ + if((mSetMediaWidth > 0) && (mSetMediaHeight > 0)) + { + mRequestedMediaWidth = mSetMediaWidth; + mRequestedMediaHeight = mSetMediaHeight; + } + else + { + mRequestedMediaWidth = mDefaultMediaWidth; + mRequestedMediaHeight = mDefaultMediaHeight; + } + + if(mAllowDownsample) + { + switch(mPriority) + { + case PRIORITY_LOW: + // Reduce maximum texture dimension to (or below) mLowPrioritySizeLimit + while((mRequestedMediaWidth > mLowPrioritySizeLimit) || (mRequestedMediaHeight > mLowPrioritySizeLimit)) + { + mRequestedMediaWidth /= 2; + mRequestedMediaHeight /= 2; + } + break; + + default: + // Don't adjust texture size + break; + } + } + + if(mAutoScaleMedia) + { + mRequestedMediaWidth = nextPowerOf2(mRequestedMediaWidth); + mRequestedMediaHeight = nextPowerOf2(mRequestedMediaHeight); + } +} + +void LLPluginClassMedia::setAutoScale(bool auto_scale) +{ + if(auto_scale != mAutoScaleMedia) + { + mAutoScaleMedia = auto_scale; + setSizeInternal(); + } +} + +bool LLPluginClassMedia::textureValid(void) +{ + if( + !mTextureParamsReceived || + mTextureWidth <= 0 || + mTextureHeight <= 0 || + mMediaWidth <= 0 || + mMediaHeight <= 0 || + mRequestedMediaWidth != mMediaWidth || + mRequestedMediaHeight != mMediaHeight || + getBitsData() == NULL + ) + return false; + + return true; +} + +bool LLPluginClassMedia::getDirty(LLRect *dirty_rect) +{ + bool result = !mDirtyRect.isEmpty(); + + if(dirty_rect != NULL) + { + *dirty_rect = mDirtyRect; + } + + return result; +} + +void LLPluginClassMedia::resetDirty(void) +{ + mDirtyRect = LLRect::null; +} + +std::string LLPluginClassMedia::translateModifiers(MASK modifiers) +{ + std::string result; + + + if(modifiers & MASK_CONTROL) + { + result += "control|"; + } + + if(modifiers & MASK_ALT) + { + result += "alt|"; + } + + if(modifiers & MASK_SHIFT) + { + result += "shift|"; + } + + // TODO: should I deal with platform differences here or in callers? + // TODO: how do we deal with the Mac "command" key? +/* + if(modifiers & MASK_SOMETHING) + { + result += "meta|"; + } +*/ + return result; +} + +void LLPluginClassMedia::mouseEvent(EMouseEventType type, int x, int y, MASK modifiers) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "mouse_event"); + std::string temp; + switch(type) + { + case MOUSE_EVENT_DOWN: temp = "down"; break; + case MOUSE_EVENT_UP: temp = "up"; break; + case MOUSE_EVENT_MOVE: temp = "move"; break; + case MOUSE_EVENT_DOUBLE_CLICK: temp = "double_click"; break; + } + message.setValue("event", temp); + + message.setValueS32("x", x); + + // Incoming coordinates are OpenGL-style ((0,0) = lower left), so flip them here if the plugin has requested it. + if(!mRequestedTextureCoordsOpenGL) + { + // TODO: Should I use mMediaHeight or mRequestedMediaHeight here? + y = mMediaHeight - y; + } + message.setValueS32("y", y); + + message.setValue("modifiers", translateModifiers(modifiers)); + + sendMessage(message); +} + +bool LLPluginClassMedia::keyEvent(EKeyEventType type, int key_code, MASK modifiers) +{ + bool result = true; + + // FIXME: + // HACK: we don't have an easy way to tell if the plugin is going to handle a particular keycode. + // For now, return false for the ones the webkit plugin won't handle properly. + + switch(key_code) + { + case KEY_BACKSPACE: + case KEY_TAB: + case KEY_RETURN: + case KEY_PAD_RETURN: + case KEY_SHIFT: + case KEY_CONTROL: + case KEY_ALT: + case KEY_CAPSLOCK: + case KEY_ESCAPE: + case KEY_PAGE_UP: + case KEY_PAGE_DOWN: + case KEY_END: + case KEY_HOME: + case KEY_LEFT: + case KEY_UP: + case KEY_RIGHT: + case KEY_DOWN: + case KEY_INSERT: + case KEY_DELETE: + // These will be handled + break; + + default: + // regular ASCII characters will also be handled + if(key_code >= KEY_SPECIAL) + { + // Other "special" codes will not work properly. + result = false; + } + break; + } + + if(result) + { + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "key_event"); + std::string temp; + switch(type) + { + case KEY_EVENT_DOWN: temp = "down"; break; + case KEY_EVENT_UP: temp = "up"; break; + case KEY_EVENT_REPEAT: temp = "repeat"; break; + } + message.setValue("event", temp); + + message.setValueS32("key", key_code); + + message.setValue("modifiers", translateModifiers(modifiers)); + + sendMessage(message); + } + + return result; +} + +void LLPluginClassMedia::scrollEvent(int x, int y, MASK modifiers) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "scroll_event"); + + message.setValueS32("x", x); + message.setValueS32("y", y); + message.setValue("modifiers", translateModifiers(modifiers)); + + sendMessage(message); +} + +bool LLPluginClassMedia::textInput(const std::string &text) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "text_event"); + + message.setValue("text", text); + + sendMessage(message); + + return true; +} + +void LLPluginClassMedia::loadURI(const std::string &uri) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "load_uri"); + + message.setValue("uri", uri); + + sendMessage(message); +} + +void LLPluginClassMedia::setPriority(EPriority priority) +{ + if(mPriority != priority) + { + mPriority = priority; + + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "set_priority"); + + std::string priority_string; + switch(priority) + { + case PRIORITY_STOPPED: + priority_string = "stopped"; + mSleepTime = 1.0f; + break; + case PRIORITY_HIDDEN: + priority_string = "hidden"; + mSleepTime = 1.0f; + break; + case PRIORITY_LOW: + priority_string = "low"; + mSleepTime = 1.0f / 50.0f; + break; + case PRIORITY_NORMAL: + priority_string = "normal"; + mSleepTime = 1.0f / 100.0f; + break; + case PRIORITY_HIGH: + priority_string = "high"; + mSleepTime = 1.0f / 100.0f; + break; + } + + message.setValue("priority", priority_string); + + sendMessage(message); + + if(mPlugin) + { + mPlugin->setSleepTime(mSleepTime); + } + + // This may affect the calculated size, so recalculate it here. + setSizeInternal(); + } +} + +void LLPluginClassMedia::setLowPrioritySizeLimit(int size) +{ + if(mLowPrioritySizeLimit != size) + { + mLowPrioritySizeLimit = size; + + // This may affect the calculated size, so recalculate it here. + setSizeInternal(); + } +} + + +void LLPluginClassMedia::cut() +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_cut"); + sendMessage(message); +} + +void LLPluginClassMedia::copy() +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_copy"); + sendMessage(message); +} + +void LLPluginClassMedia::paste() +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_paste"); + sendMessage(message); +} + +/* virtual */ +void LLPluginClassMedia::receivePluginMessage(const LLPluginMessage &message) +{ + std::string message_class = message.getClass(); + + if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA) + { + std::string message_name = message.getName(); + if(message_name == "texture_params") + { + mRequestedTextureDepth = message.getValueS32("depth"); + mRequestedTextureInternalFormat = message.getValueU32("internalformat"); + mRequestedTextureFormat = message.getValueU32("format"); + mRequestedTextureType = message.getValueU32("type"); + mRequestedTextureSwapBytes = message.getValueBoolean("swap_bytes"); + mRequestedTextureCoordsOpenGL = message.getValueBoolean("coords_opengl"); + + // These two are optional, and will default to 0 if they're not specified. + mDefaultMediaWidth = message.getValueS32("default_width"); + mDefaultMediaHeight = message.getValueS32("default_height"); + + mAllowDownsample = message.getValueBoolean("allow_downsample"); + mPadding = message.getValueS32("padding"); + + setSizeInternal(); + + mTextureParamsReceived = true; + } + else if(message_name == "updated") + { + if(message.hasValue("left")) + { + LLRect newDirtyRect; + newDirtyRect.mLeft = message.getValueS32("left"); + newDirtyRect.mTop = message.getValueS32("top"); + newDirtyRect.mRight = message.getValueS32("right"); + newDirtyRect.mBottom = message.getValueS32("bottom"); + + // The plugin is likely to have top and bottom switched, due to vertical flip and OpenGL coordinate confusion. + // If they're backwards, swap them. + if(newDirtyRect.mTop < newDirtyRect.mBottom) + { + S32 temp = newDirtyRect.mTop; + newDirtyRect.mTop = newDirtyRect.mBottom; + newDirtyRect.mBottom = temp; + } + + if(mDirtyRect.isEmpty()) + { + mDirtyRect = newDirtyRect; + } + else + { + mDirtyRect.unionWith(newDirtyRect); + } + + LL_DEBUGS("Plugin") << "adjusted incoming rect is: (" + << newDirtyRect.mLeft << ", " + << newDirtyRect.mTop << ", " + << newDirtyRect.mRight << ", " + << newDirtyRect.mBottom << "), new dirty rect is: (" + << mDirtyRect.mLeft << ", " + << mDirtyRect.mTop << ", " + << mDirtyRect.mRight << ", " + << mDirtyRect.mBottom << ")" + << LL_ENDL; + + mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CONTENT_UPDATED); + } + + + bool time_duration_updated = false; + + if(message.hasValue("current_time")) + { + mCurrentTime = message.getValueReal("current_time"); + time_duration_updated = true; + } + if(message.hasValue("duration")) + { + mDuration = message.getValueReal("duration"); + time_duration_updated = true; + } + + if(message.hasValue("current_rate")) + { + mCurrentRate = message.getValueReal("current_rate"); + } + + if(time_duration_updated) + { + mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_TIME_DURATION_UPDATED); + } + + } + else if(message_name == "media_status") + { + std::string status = message.getValue("status"); + + LL_DEBUGS("Plugin") << "Status changed to: " << status << LL_ENDL; + + if(status == "loading") + { + mStatus = LLPluginClassMediaOwner::MEDIA_LOADING; + } + else if(status == "loaded") + { + mStatus = LLPluginClassMediaOwner::MEDIA_LOADED; + } + else if(status == "error") + { + mStatus = LLPluginClassMediaOwner::MEDIA_ERROR; + } + else if(status == "playing") + { + mStatus = LLPluginClassMediaOwner::MEDIA_PLAYING; + } + else if(status == "paused") + { + mStatus = LLPluginClassMediaOwner::MEDIA_PAUSED; + } + else + { + // empty string or any unknown string + mStatus = LLPluginClassMediaOwner::MEDIA_NONE; + } + } + else if(message_name == "size_change_request") + { + S32 width = message.getValueS32("width"); + S32 height = message.getValueS32("height"); + std::string name = message.getValue("name"); + + // TODO: check that name matches? + mNaturalMediaWidth = width; + mNaturalMediaHeight = height; + + setSize(width, height); + } + else if(message_name == "size_change_response") + { + std::string name = message.getValue("name"); + + // TODO: check that name matches? + + mTextureWidth = message.getValueS32("texture_width"); + mTextureHeight = message.getValueS32("texture_height"); + mMediaWidth = message.getValueS32("width"); + mMediaHeight = message.getValueS32("height"); + + // This invalidates any existing dirty rect. + resetDirty(); + + // TODO: should we verify that the plugin sent back the right values? + // Two size changes in a row may cause them to not match, due to queueing, etc. + + mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_SIZE_CHANGED); + } + else if(message_name == "cursor_changed") + { + mCursorName = message.getValue("name"); + + mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CURSOR_CHANGED); + } + else if(message_name == "edit_state") + { + if(message.hasValue("cut")) + { + mCanCut = message.getValueBoolean("cut"); + } + if(message.hasValue("copy")) + { + mCanCopy = message.getValueBoolean("copy"); + } + if(message.hasValue("paste")) + { + mCanPaste = message.getValueBoolean("paste"); + } + } + else + { + LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL; + } + } + else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER) + { + std::string message_name = message.getName(); + if(message_name == "navigate_begin") + { + mNavigateURI = message.getValue("uri"); + mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_NAVIGATE_BEGIN); + } + else if(message_name == "navigate_complete") + { + mNavigateURI = message.getValue("uri"); + mNavigateResultCode = message.getValueS32("result_code"); + mNavigateResultString = message.getValue("result_string"); + mHistoryBackAvailable = message.getValueBoolean("history_back_available"); + mHistoryForwardAvailable = message.getValueBoolean("history_forward_available"); + + mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_NAVIGATE_COMPLETE); + } + else if(message_name == "progress") + { + mProgressPercent = message.getValueS32("percent"); + mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PROGRESS_UPDATED); + } + else if(message_name == "status_text") + { + mStatusText = message.getValue("status"); + mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_STATUS_TEXT_CHANGED); + } + else if(message_name == "location_changed") + { + mLocation = message.getValue("uri"); + mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_LOCATION_CHANGED); + } + else if(message_name == "click_href") + { + mClickURL = message.getValue("uri"); + mClickTarget = message.getValue("target"); + mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_HREF); + } + else if(message_name == "click_nofollow") + { + mClickURL = message.getValue("uri"); + mClickTarget.clear(); + mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_NOFOLLOW); + } + else + { + LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL; + } + } + else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME) + { + std::string message_name = message.getName(); + + // This class hasn't defined any incoming messages yet. +// if(message_name == "message_name") +// { +// } +// else + { + LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL; + } + } + +} + +/* virtual */ +void LLPluginClassMedia::pluginDied() +{ + mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PLUGIN_FAILED); +} + +void LLPluginClassMedia::mediaEvent(LLPluginClassMediaOwner::EMediaEvent event) +{ + if(mOwner) + { + mOwner->handleMediaEvent(this, event); + } +} + +void LLPluginClassMedia::sendMessage(const LLPluginMessage &message) +{ + if(mPlugin && mPlugin->isRunning()) + { + mPlugin->sendMessage(message); + } + else + { + // The plugin isn't set up yet -- queue this message to be sent after initialization. + mSendQueue.push(message); + } +} + +//////////////////////////////////////////////////////////// +// MARK: media_browser class functions +bool LLPluginClassMedia::pluginSupportsMediaBrowser(void) +{ + std::string version = mPlugin->getMessageClassVersion(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER); + return !version.empty(); +} + +void LLPluginClassMedia::focus(bool focused) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "focus"); + + message.setValueBoolean("focused", focused); + + sendMessage(message); +} + +void LLPluginClassMedia::clear_cache() +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "clear_cache"); + sendMessage(message); +} + +void LLPluginClassMedia::clear_cookies() +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "clear_cookies"); + sendMessage(message); +} + +void LLPluginClassMedia::enable_cookies(bool enable) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "enable_cookies"); + sendMessage(message); +} + +void LLPluginClassMedia::proxy_setup(bool enable, const std::string &host, int port) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "proxy_setup"); + + message.setValueBoolean("enable", enable); + message.setValue("host", host); + message.setValueS32("port", port); + + sendMessage(message); +} + +void LLPluginClassMedia::browse_stop() +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_stop"); + sendMessage(message); +} + +void LLPluginClassMedia::browse_reload(bool ignore_cache) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_reload"); + + message.setValueBoolean("ignore_cache", ignore_cache); + + sendMessage(message); +} + +void LLPluginClassMedia::browse_forward() +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_forward"); + sendMessage(message); +} + +void LLPluginClassMedia::browse_back() +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_back"); + sendMessage(message); +} + +void LLPluginClassMedia::set_status_redirect(int code, const std::string &url) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "set_status_redirect"); + + message.setValueS32("code", code); + message.setValue("url", url); + + sendMessage(message); +} + +void LLPluginClassMedia::setBrowserUserAgent(const std::string& user_agent) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "set_user_agent"); + + message.setValue("user_agent", user_agent); + + sendMessage(message); +} + +void LLPluginClassMedia::crashPlugin() +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "crash"); + + sendMessage(message); +} + +void LLPluginClassMedia::hangPlugin() +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "hang"); + + sendMessage(message); +} + + +//////////////////////////////////////////////////////////// +// MARK: media_time class functions +bool LLPluginClassMedia::pluginSupportsMediaTime(void) +{ + std::string version = mPlugin->getMessageClassVersion(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME); + return !version.empty(); +} + +void LLPluginClassMedia::stop() +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "stop"); + sendMessage(message); +} + +void LLPluginClassMedia::start(float rate) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "start"); + + message.setValueReal("rate", rate); + + sendMessage(message); +} + +void LLPluginClassMedia::pause() +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "pause"); + sendMessage(message); +} + +void LLPluginClassMedia::seek(float time) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "seek"); + + message.setValueReal("time", time); + + sendMessage(message); +} + +void LLPluginClassMedia::setLoop(bool loop) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "set_loop"); + + message.setValueBoolean("loop", loop); + + sendMessage(message); +} + +void LLPluginClassMedia::setVolume(float volume) +{ + if(volume != mRequestedVolume) + { + mRequestedVolume = volume; + + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "set_volume"); + + message.setValueReal("volume", volume); + + sendMessage(message); + } +} + +void LLPluginClassMedia::initializeUrlHistory(const LLSD& url_history) +{ + // Send URL history to plugin + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "init_history"); + message.setValueLLSD("history", url_history); + sendMessage(message); + + LL_DEBUGS("Plugin") << "Sending history" << LL_ENDL; +} + diff --git a/indra/llplugin/llpluginclassmedia.h b/indra/llplugin/llpluginclassmedia.h new file mode 100644 index 0000000000..7a8586fe2f --- /dev/null +++ b/indra/llplugin/llpluginclassmedia.h @@ -0,0 +1,333 @@ +/** + * @file llpluginclassmedia.h + * @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINCLASSMEDIA_H +#define LL_LLPLUGINCLASSMEDIA_H + +#include "llgl.h" +#include "llpluginprocessparent.h" +#include "llrect.h" +#include "llpluginclassmediaowner.h" +#include <queue> + + +class LLPluginClassMedia : public LLPluginProcessParentOwner +{ + LOG_CLASS(LLPluginClassMedia); +public: + LLPluginClassMedia(LLPluginClassMediaOwner *owner); + virtual ~LLPluginClassMedia(); + + // local initialization, called by the media manager when creating a source + virtual bool init(const std::string &launcher_filename, const std::string &plugin_filename); + + // undoes everything init() didm called by the media manager when destroying a source + virtual void reset(); + + void idle(void); + + // All of these may return 0 or an actual valid value. + // Callers need to check the return for 0, and not use the values in that case. + int getWidth() const { return (mMediaWidth > 0) ? mMediaWidth : 0; }; + int getHeight() const { return (mMediaHeight > 0) ? mMediaHeight : 0; }; + int getNaturalWidth() const { return mNaturalMediaWidth; }; + int getNaturalHeight() const { return mNaturalMediaHeight; }; + int getSetWidth() const { return mSetMediaWidth; }; + int getSetHeight() const { return mSetMediaHeight; }; + int getBitsWidth() const { return (mTextureWidth > 0) ? mTextureWidth : 0; }; + int getBitsHeight() const { return (mTextureHeight > 0) ? mTextureHeight : 0; }; + int getTextureWidth() const; + int getTextureHeight() const; + + // This may return NULL. Callers need to check for and handle this case. + unsigned char* getBitsData(); + + // gets the format details of the texture data + // These may return 0 if they haven't been set up yet. The caller needs to detect this case. + int getTextureDepth() const { return mRequestedTextureDepth; }; + int getTextureFormatInternal() const { return mRequestedTextureInternalFormat; }; + int getTextureFormatPrimary() const { return mRequestedTextureFormat; }; + int getTextureFormatType() const { return mRequestedTextureType; }; + bool getTextureFormatSwapBytes() const { return mRequestedTextureSwapBytes; }; + bool getTextureCoordsOpenGL() const { return mRequestedTextureCoordsOpenGL; }; + + void setSize(int width, int height); + void setAutoScale(bool auto_scale); + + // Returns true if all of the texture parameters (depth, format, size, and texture size) are set up and consistent. + // This will initially be false, and will also be false for some time after setSize while the resize is processed. + // Note that if this returns true, it is safe to use all the get() functions above without checking for invalid return values + // until you call idle() again. + bool textureValid(void); + + bool getDirty(LLRect *dirty_rect = NULL); + void resetDirty(void); + + typedef enum + { + MOUSE_EVENT_DOWN, + MOUSE_EVENT_UP, + MOUSE_EVENT_MOVE, + MOUSE_EVENT_DOUBLE_CLICK + }EMouseEventType; + + void mouseEvent(EMouseEventType type, int x, int y, MASK modifiers); + + typedef enum + { + KEY_EVENT_DOWN, + KEY_EVENT_UP, + KEY_EVENT_REPEAT + }EKeyEventType; + + bool keyEvent(EKeyEventType type, int key_code, MASK modifiers); + + void scrollEvent(int x, int y, MASK modifiers); + + // Text may be unicode (utf8 encoded) + bool textInput(const std::string &text); + + void loadURI(const std::string &uri); + + // "Loading" means uninitialized or any state prior to fully running (processing commands) + bool isPluginLoading(void) { return mPlugin?mPlugin->isLoading():false; }; + + // "Running" means the steady state -- i.e. processing messages + bool isPluginRunning(void) { return mPlugin?mPlugin->isRunning():false; }; + + // "Exited" means any regular or error state after "Running" (plugin may have crashed or exited normally) + bool isPluginExited(void) { return mPlugin?mPlugin->isDone():false; }; + + std::string getPluginVersion() { return mPlugin?mPlugin->getPluginVersion():std::string(""); }; + + bool getDisableTimeout() { return mPlugin?mPlugin->getDisableTimeout():false; }; + void setDisableTimeout(bool disable) { if(mPlugin) mPlugin->setDisableTimeout(disable); }; + + // Inherited from LLPluginProcessParentOwner + /* virtual */ void receivePluginMessage(const LLPluginMessage &message); + /* virtual */ void pluginDied(); + + + typedef enum + { + PRIORITY_STOPPED, // media is not playing, shouldn't need to update at all. + PRIORITY_HIDDEN, // media is not being displayed or is out of view, don't need to do graphic updates, but may still update audio, playhead, etc. + PRIORITY_LOW, // media is in the far distance, may be rendered at reduced size + PRIORITY_NORMAL, // normal (default) priority + PRIORITY_HIGH // media has user focus and/or is taking up most of the screen + }EPriority; + + void setPriority(EPriority priority); + void setLowPrioritySizeLimit(int size); + + // Valid after a MEDIA_EVENT_CURSOR_CHANGED event + std::string getCursorName() const { return mCursorName; }; + + LLPluginClassMediaOwner::EMediaStatus getStatus() const { return mStatus; } + + void cut(); + bool canCut() const { return mCanCut; }; + + void copy(); + bool canCopy() const { return mCanCopy; }; + + void paste(); + bool canPaste() const { return mCanPaste; }; + + /////////////////////////////////// + // media browser class functions + bool pluginSupportsMediaBrowser(void); + + void focus(bool focused); + void clear_cache(); + void clear_cookies(); + void enable_cookies(bool enable); + void proxy_setup(bool enable, const std::string &host = LLStringUtil::null, int port = 0); + void browse_stop(); + void browse_reload(bool ignore_cache = false); + void browse_forward(); + void browse_back(); + void set_status_redirect(int code, const std::string &url); + void setBrowserUserAgent(const std::string& user_agent); + + // This is valid after MEDIA_EVENT_NAVIGATE_BEGIN or MEDIA_EVENT_NAVIGATE_COMPLETE + std::string getNavigateURI() const { return mNavigateURI; }; + + // These are valid after MEDIA_EVENT_NAVIGATE_COMPLETE + S32 getNavigateResultCode() const { return mNavigateResultCode; }; + std::string getNavigateResultString() const { return mNavigateResultString; }; + bool getHistoryBackAvailable() const { return mHistoryBackAvailable; }; + bool getHistoryForwardAvailable() const { return mHistoryForwardAvailable; }; + + // This is valid after MEDIA_EVENT_PROGRESS_UPDATED + int getProgressPercent() const { return mProgressPercent; }; + + // This is valid after MEDIA_EVENT_STATUS_TEXT_CHANGED + std::string getStatusText() const { return mStatusText; }; + + // This is valid after MEDIA_EVENT_LOCATION_CHANGED + std::string getLocation() const { return mLocation; }; + + // This is valid after MEDIA_EVENT_CLICK_LINK_HREF or MEDIA_EVENT_CLICK_LINK_NOFOLLOW + std::string getClickURL() const { return mClickURL; }; + + // This is valid after MEDIA_EVENT_CLICK_LINK_HREF + std::string getClickTarget() const { return mClickTarget; }; + + std::string getMediaName() const { return mMediaName; }; + std::string getMediaDescription() const { return mMediaDescription; }; + + // Crash the plugin. If you use this outside of a testbed, you will be punished. + void crashPlugin(); + + // Hang the plugin. If you use this outside of a testbed, you will be punished. + void hangPlugin(); + + /////////////////////////////////// + // media time class functions + bool pluginSupportsMediaTime(void); + void stop(); + void start(float rate = 0.0f); + void pause(); + void seek(float time); + void setLoop(bool loop); + void setVolume(float volume); + + F64 getCurrentTime(void) const { return mCurrentTime; }; + F64 getDuration(void) const { return mDuration; }; + F64 getCurrentPlayRate(void) { return mCurrentRate; }; + + // Initialize the URL history of the plugin by sending + // "init_history" message + void initializeUrlHistory(const LLSD& url_history); + +protected: + LLPluginClassMediaOwner *mOwner; + + // Notify this object's owner that an event has occurred. + void mediaEvent(LLPluginClassMediaOwner::EMediaEvent event); + + void sendMessage(const LLPluginMessage &message); // Send message internally, either queueing or sending directly. + std::queue<LLPluginMessage> mSendQueue; // Used to queue messages while the plugin initializes. + + void setSizeInternal(void); + + bool mTextureParamsReceived; // the mRequestedTexture* fields are only valid when this is true + S32 mRequestedTextureDepth; + LLGLenum mRequestedTextureInternalFormat; + LLGLenum mRequestedTextureFormat; + LLGLenum mRequestedTextureType; + bool mRequestedTextureSwapBytes; + bool mRequestedTextureCoordsOpenGL; + + std::string mTextureSharedMemoryName; + size_t mTextureSharedMemorySize; + + // True to scale requested media up to the full size of the texture (i.e. next power of two) + bool mAutoScaleMedia; + + // default media size for the plugin, from the texture_params message. + int mDefaultMediaWidth; + int mDefaultMediaHeight; + + // Size that has been requested by the plugin itself + int mNaturalMediaWidth; + int mNaturalMediaHeight; + + // Size that has been requested with setSize() + int mSetMediaWidth; + int mSetMediaHeight; + + // Actual media size being set (may be affected by auto-scale) + int mRequestedMediaWidth; + int mRequestedMediaHeight; + + // Texture size calculated from actual media size + int mRequestedTextureWidth; + int mRequestedTextureHeight; + + // Size that the plugin has acknowledged + int mTextureWidth; + int mTextureHeight; + int mMediaWidth; + int mMediaHeight; + + float mRequestedVolume; + + // Priority of this media stream + EPriority mPriority; + int mLowPrioritySizeLimit; + + bool mAllowDownsample; + int mPadding; + + + LLPluginProcessParent *mPlugin; + + LLRect mDirtyRect; + + std::string translateModifiers(MASK modifiers); + + std::string mCursorName; + + LLPluginClassMediaOwner::EMediaStatus mStatus; + + F64 mSleepTime; + + bool mCanCut; + bool mCanCopy; + bool mCanPaste; + + std::string mMediaName; + std::string mMediaDescription; + + ///////////////////////////////////////// + // media_browser class + std::string mNavigateURI; + S32 mNavigateResultCode; + std::string mNavigateResultString; + bool mHistoryBackAvailable; + bool mHistoryForwardAvailable; + std::string mStatusText; + int mProgressPercent; + std::string mLocation; + std::string mClickURL; + std::string mClickTarget; + + ///////////////////////////////////////// + // media_time class + F64 mCurrentTime; + F64 mDuration; + F64 mCurrentRate; + +}; + +#endif // LL_LLPLUGINCLASSMEDIA_H diff --git a/indra/llplugin/llpluginclassmediaowner.h b/indra/llplugin/llpluginclassmediaowner.h new file mode 100644 index 0000000000..3ae176cbeb --- /dev/null +++ b/indra/llplugin/llpluginclassmediaowner.h @@ -0,0 +1,79 @@ +/** + * @file llpluginclassmediaowner.h + * @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINCLASSMEDIAOWNER_H +#define LL_LLPLUGINCLASSMEDIAOWNER_H + +#include "llgl.h" +#include "llpluginprocessparent.h" +#include "llrect.h" +#include <queue> + +class LLPluginClassMedia; + +class LLPluginClassMediaOwner +{ +public: + typedef enum + { + MEDIA_EVENT_CONTENT_UPDATED, // contents/dirty rect have updated + MEDIA_EVENT_TIME_DURATION_UPDATED, // current time and/or duration have updated + MEDIA_EVENT_SIZE_CHANGED, // media size has changed + MEDIA_EVENT_CURSOR_CHANGED, // plugin has requested a cursor change + + MEDIA_EVENT_NAVIGATE_BEGIN, // browser has begun navigation + MEDIA_EVENT_NAVIGATE_COMPLETE, // browser has finished navigation + MEDIA_EVENT_PROGRESS_UPDATED, // browser has updated loading progress + MEDIA_EVENT_STATUS_TEXT_CHANGED, // browser has updated the status text + MEDIA_EVENT_LOCATION_CHANGED, // browser location (URL) has changed (maybe due to internal navagation/frames/etc) + MEDIA_EVENT_CLICK_LINK_HREF, // I'm not entirely sure what the semantics of these two are + MEDIA_EVENT_CLICK_LINK_NOFOLLOW, + + MEDIA_EVENT_PLUGIN_FAILED // The plugin failed to launch or died unexpectedly + + } EMediaEvent; + + typedef enum + { + MEDIA_NONE, // Uninitialized -- no useful state + MEDIA_LOADING, // loading or navigating + MEDIA_LOADED, // navigation/preroll complete + MEDIA_ERROR, // navigation/preroll failed + MEDIA_PLAYING, // playing (only for time-based media) + MEDIA_PAUSED, // paused (only for time-based media) + + } EMediaStatus; + + virtual ~LLPluginClassMediaOwner() {}; + virtual void handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent /*event*/) {}; +}; + +#endif // LL_LLPLUGINCLASSMEDIAOWNER_H diff --git a/indra/llplugin/llplugininstance.cpp b/indra/llplugin/llplugininstance.cpp new file mode 100644 index 0000000000..58fb792d0d --- /dev/null +++ b/indra/llplugin/llplugininstance.cpp @@ -0,0 +1,140 @@ +/** + * @file llplugininstance.cpp + * @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llplugininstance.h" + +#include "llapr.h" + +//virtual +LLPluginInstanceMessageListener::~LLPluginInstanceMessageListener() +{ +} + +const char *LLPluginInstance::PLUGIN_INIT_FUNCTION_NAME = "LLPluginInitEntryPoint"; + +LLPluginInstance::LLPluginInstance(LLPluginInstanceMessageListener *owner) : + mDSOHandle(NULL), + mPluginUserData(NULL), + mPluginSendMessageFunction(NULL) +{ + mOwner = owner; +} + +LLPluginInstance::~LLPluginInstance() +{ + if(mDSOHandle != NULL) + { + apr_dso_unload(mDSOHandle); + mDSOHandle = NULL; + } +} + +int LLPluginInstance::load(std::string &plugin_file) +{ + pluginInitFunction init_function = NULL; + + int result = apr_dso_load(&mDSOHandle, + plugin_file.c_str(), + gAPRPoolp); + if(result != APR_SUCCESS) + { + char buf[1024]; + apr_dso_error(mDSOHandle, buf, sizeof(buf)); + + LL_WARNS("Plugin") << "apr_dso_load of " << plugin_file << " failed with error " << result << " , additional info string: " << buf << LL_ENDL; + + } + + if(result == APR_SUCCESS) + { + result = apr_dso_sym((apr_dso_handle_sym_t*)&init_function, + mDSOHandle, + PLUGIN_INIT_FUNCTION_NAME); + + if(result != APR_SUCCESS) + { + LL_WARNS("Plugin") << "apr_dso_sym failed with error " << result << LL_ENDL; + } + } + + if(result == APR_SUCCESS) + { + result = init_function(staticReceiveMessage, (void*)this, &mPluginSendMessageFunction, &mPluginUserData); + + if(result != APR_SUCCESS) + { + LL_WARNS("Plugin") << "call to init function failed with error " << result << LL_ENDL; + } + } + + return (int)result; +} + +void LLPluginInstance::sendMessage(const std::string &message) +{ + if(mPluginSendMessageFunction) + { + LL_DEBUGS("Plugin") << "sending message to plugin: \"" << message << "\"" << LL_ENDL; + mPluginSendMessageFunction(message.c_str(), &mPluginUserData); + } + else + { + LL_WARNS("Plugin") << "dropping message: \"" << message << "\"" << LL_ENDL; + } +} + +void LLPluginInstance::idle(void) +{ +} + +// static +void LLPluginInstance::staticReceiveMessage(const char *message_string, void **user_data) +{ + // TODO: validate that the user_data argument is still a valid LLPluginInstance pointer + // we could also use a key that's looked up in a map (instead of a direct pointer) for safety, but that's probably overkill + LLPluginInstance *self = (LLPluginInstance*)*user_data; + self->receiveMessage(message_string); +} + +void LLPluginInstance::receiveMessage(const char *message_string) +{ + if(mOwner) + { + LL_DEBUGS("Plugin") << "processing incoming message: \"" << message_string << "\"" << LL_ENDL; + mOwner->receivePluginMessage(message_string); + } + else + { + LL_WARNS("Plugin") << "dropping incoming message: \"" << message_string << "\"" << LL_ENDL; + } +} diff --git a/indra/llplugin/llplugininstance.h b/indra/llplugin/llplugininstance.h new file mode 100644 index 0000000000..ba569df10c --- /dev/null +++ b/indra/llplugin/llplugininstance.h @@ -0,0 +1,88 @@ +/** + * @file llplugininstance.h + * @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGININSTANCE_H +#define LL_LLPLUGININSTANCE_H + +#include "llstring.h" +#include "llapr.h" + +#include "apr_dso.h" + +class LLPluginInstanceMessageListener +{ +public: + virtual ~LLPluginInstanceMessageListener(); + virtual void receivePluginMessage(const std::string &message) = 0; +}; + +class LLPluginInstance +{ + LOG_CLASS(LLPluginInstance); +public: + LLPluginInstance(LLPluginInstanceMessageListener *owner); + virtual ~LLPluginInstance(); + + // Load a plugin dll/dylib/so + // Returns 0 if successful, APR error code or error code returned from the plugin's init function on failure. + int load(std::string &plugin_file); + + // Sends a message to the plugin. + void sendMessage(const std::string &message); + + // send_count is the maximum number of message to process from the send queue. If negative, it will drain the queue completely. + // The receive queue is always drained completely. + // Returns the total number of messages processed from both queues. + void idle(void); + + // this is the signature of the "send a message" function. + // message_string is a null-terminated C string + // user_data is the opaque reference that the callee supplied during setup. + typedef void (*sendMessageFunction) (const char *message_string, void **user_data); + + // signature of the plugin init function + typedef int (*pluginInitFunction) (sendMessageFunction host_send_func, void *host_user_data, sendMessageFunction *plugin_send_func, void **plugin_user_data); + + static const char *PLUGIN_INIT_FUNCTION_NAME; + +private: + static void staticReceiveMessage(const char *message_string, void **user_data); + void receiveMessage(const char *message_string); + + apr_dso_handle_t *mDSOHandle; + + void *mPluginUserData; + sendMessageFunction mPluginSendMessageFunction; + + LLPluginInstanceMessageListener *mOwner; +}; + +#endif // LL_LLPLUGININSTANCE_H diff --git a/indra/llplugin/llpluginmessage.cpp b/indra/llplugin/llpluginmessage.cpp new file mode 100644 index 0000000000..bfabc5b7ca --- /dev/null +++ b/indra/llplugin/llpluginmessage.cpp @@ -0,0 +1,249 @@ +/** + * @file llpluginmessage.cpp + * @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llpluginmessage.h" +#include "llsdserialize.h" + +LLPluginMessage::LLPluginMessage() +{ +} + +LLPluginMessage::LLPluginMessage(const std::string &message_class, const std::string &message_name) +{ + setMessage(message_class, message_name); +} + + +LLPluginMessage::~LLPluginMessage() +{ +} + +void LLPluginMessage::clear() +{ + mMessage = LLSD::emptyMap(); + mMessage["params"] = LLSD::emptyMap(); +} + +void LLPluginMessage::setMessage(const std::string &message_class, const std::string &message_name) +{ + clear(); + mMessage["class"] = message_class; + mMessage["name"] = message_name; +} + +void LLPluginMessage::setValue(const std::string &key, const std::string &value) +{ + mMessage["params"][key] = value; +} + +void LLPluginMessage::setValueLLSD(const std::string &key, const LLSD &value) +{ + mMessage["params"][key] = value; +} + +void LLPluginMessage::setValueS32(const std::string &key, S32 value) +{ + mMessage["params"][key] = value; +} + +void LLPluginMessage::setValueU32(const std::string &key, U32 value) +{ + std::stringstream temp; + temp << "0x" << std::hex << value; + setValue(key, temp.str()); +} + +void LLPluginMessage::setValueBoolean(const std::string &key, bool value) +{ + mMessage["params"][key] = value; +} + +void LLPluginMessage::setValueReal(const std::string &key, F64 value) +{ + mMessage["params"][key] = value; +} + +std::string LLPluginMessage::getClass(void) const +{ + return mMessage["class"]; +} + +std::string LLPluginMessage::getName(void) const +{ + return mMessage["name"]; +} + +bool LLPluginMessage::hasValue(const std::string &key) const +{ + bool result = false; + + if(mMessage["params"].has(key)) + { + result = true; + } + + return result; +} + +std::string LLPluginMessage::getValue(const std::string &key) const +{ + std::string result; + + if(mMessage["params"].has(key)) + { + result = mMessage["params"][key].asString(); + } + + return result; +} + +LLSD LLPluginMessage::getValueLLSD(const std::string &key) const +{ + LLSD result; + + if(mMessage["params"].has(key)) + { + result = mMessage["params"][key]; + } + + return result; +} + +S32 LLPluginMessage::getValueS32(const std::string &key) const +{ + S32 result = 0; + + if(mMessage["params"].has(key)) + { + result = mMessage["params"][key].asInteger(); + } + + return result; +} + +U32 LLPluginMessage::getValueU32(const std::string &key) const +{ + U32 result = 0; + + if(mMessage["params"].has(key)) + { + std::string value = mMessage["params"][key].asString(); + + result = (U32)strtoul(value.c_str(), NULL, 16); + } + + return result; +} + +bool LLPluginMessage::getValueBoolean(const std::string &key) const +{ + bool result = false; + + if(mMessage["params"].has(key)) + { + result = mMessage["params"][key].asBoolean(); + } + + return result; +} + +F64 LLPluginMessage::getValueReal(const std::string &key) const +{ + F64 result = 0.0f; + + if(mMessage["params"].has(key)) + { + result = mMessage["params"][key].asReal(); + } + + return result; +} + +std::string LLPluginMessage::generate(void) const +{ + std::ostringstream result; + + // Pretty XML may be slightly easier to deal with while debugging... +// LLSDSerialize::toXML(mMessage, result); + LLSDSerialize::toPrettyXML(mMessage, result); + + return result.str(); +} + + +int LLPluginMessage::parse(const std::string &message) +{ + // clear any previous state + clear(); + + std::istringstream input(message); + + S32 parse_result = LLSDSerialize::fromXML(mMessage, input); + + return (int)parse_result; +} + + +LLPluginMessageListener::~LLPluginMessageListener() +{ + // TODO: should listeners have a way to ensure they're removed from dispatcher lists when deleted? +} + + +LLPluginMessageDispatcher::~LLPluginMessageDispatcher() +{ + +} + +void LLPluginMessageDispatcher::addPluginMessageListener(LLPluginMessageListener *listener) +{ + mListeners.insert(listener); +} + +void LLPluginMessageDispatcher::removePluginMessageListener(LLPluginMessageListener *listener) +{ + mListeners.erase(listener); +} + +void LLPluginMessageDispatcher::dispatchPluginMessage(const LLPluginMessage &message) +{ + for (listener_set_t::iterator it = mListeners.begin(); + it != mListeners.end(); + ) + { + LLPluginMessageListener* listener = *it; + listener->receivePluginMessage(message); + // In case something deleted an entry. + it = mListeners.upper_bound(listener); + } +} diff --git a/indra/llplugin/llpluginmessage.h b/indra/llplugin/llpluginmessage.h new file mode 100644 index 0000000000..a17ec4bb98 --- /dev/null +++ b/indra/llplugin/llpluginmessage.h @@ -0,0 +1,123 @@ +/** + * @file llpluginmessage.h + * @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINMESSAGE_H +#define LL_LLPLUGINMESSAGE_H + +#include "llsd.h" + + +class LLPluginMessage +{ + LOG_CLASS(LLPluginMessage); +public: + LLPluginMessage(); + LLPluginMessage(const std::string &message_class, const std::string &message_name); + ~LLPluginMessage(); + + // reset all internal state + void clear(void); + + // Sets the message class and name + // Also has the side-effect of clearing any key/value pairs in the message. + void setMessage(const std::string &message_class, const std::string &message_name); + + // Sets a key/value pair in the message + void setValue(const std::string &key, const std::string &value); + void setValueLLSD(const std::string &key, const LLSD &value); + void setValueS32(const std::string &key, S32 value); + void setValueU32(const std::string &key, U32 value); + void setValueBoolean(const std::string &key, bool value); + void setValueReal(const std::string &key, F64 value); + + std::string getClass(void) const; + std::string getName(void) const; + + // Returns true if the specified key exists in this message (useful for optional parameters) + bool hasValue(const std::string &key) const; + + // get the value of a particular key as a string. If the key doesn't exist in the message, an empty string will be returned. + std::string getValue(const std::string &key) const; + + // get the value of a particular key as LLSD. If the key doesn't exist in the message, a null LLSD will be returned. + LLSD getValueLLSD(const std::string &key) const; + + // get the value of a key as a S32. If the value wasn't set as a S32, behavior is undefined. + S32 getValueS32(const std::string &key) const; + + // get the value of a key as a U32. Since there isn't an LLSD type for this, we use a hexadecimal string instead. + U32 getValueU32(const std::string &key) const; + + // get the value of a key as a Boolean. + bool getValueBoolean(const std::string &key) const; + + // get the value of a key as a float. + F64 getValueReal(const std::string &key) const; + + // Flatten the message into a string + std::string generate(void) const; + + // Parse an incoming message into component parts + // (this clears out all existing state before starting the parse) + // Returns -1 on failure, otherwise returns the number of key/value pairs in the message. + int parse(const std::string &message); + + +private: + + LLSD mMessage; + +}; + +class LLPluginMessageListener +{ +public: + virtual ~LLPluginMessageListener(); + virtual void receivePluginMessage(const LLPluginMessage &message) = 0; + +}; + +class LLPluginMessageDispatcher +{ +public: + virtual ~LLPluginMessageDispatcher(); + + void addPluginMessageListener(LLPluginMessageListener *); + void removePluginMessageListener(LLPluginMessageListener *); +protected: + void dispatchPluginMessage(const LLPluginMessage &message); + + typedef std::set<LLPluginMessageListener*> listener_set_t; + listener_set_t mListeners; +}; + + +#endif // LL_LLPLUGINMESSAGE_H diff --git a/indra/llplugin/llpluginmessageclasses.h b/indra/llplugin/llpluginmessageclasses.h new file mode 100644 index 0000000000..927fcf2eb2 --- /dev/null +++ b/indra/llplugin/llpluginmessageclasses.h @@ -0,0 +1,56 @@ +/** + * @file llpluginmessageclasses.h + * @brief This file defines the versions of existing message classes for LLPluginMessage. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINMESSAGECLASSES_H +#define LL_LLPLUGINMESSAGECLASSES_H + +// Version strings for each plugin message class. +// Backwards-compatible changes (i.e. changes which only add new messges) should increment the minor version (i.e. "1.0" -> "1.1"). +// Non-backwards-compatible changes (which delete messages or change their semantics) should increment the major version (i.e. "1.1" -> "2.0"). +// Plugins will supply the set of message classes they understand, with version numbers, as part of their init_response message. +// The contents and semantics of the base:init message must NEVER change in a non-backwards-compatible way, as a special case. + +#define LLPLUGIN_MESSAGE_CLASS_INTERNAL "internal" +#define LLPLUGIN_MESSAGE_CLASS_INTERNAL_VERSION "1.0" + +#define LLPLUGIN_MESSAGE_CLASS_BASE "base" +#define LLPLUGIN_MESSAGE_CLASS_BASE_VERSION "1.0" + +#define LLPLUGIN_MESSAGE_CLASS_MEDIA "media" +#define LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION "1.0" + +#define LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER "media_browser" +#define LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER_VERSION "1.0" + +#define LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME "media_time" +#define LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME_VERSION "1.0" + +#endif // LL_LLPLUGINMESSAGECLASSES_H diff --git a/indra/llplugin/llpluginmessagepipe.cpp b/indra/llplugin/llpluginmessagepipe.cpp new file mode 100644 index 0000000000..31ea138912 --- /dev/null +++ b/indra/llplugin/llpluginmessagepipe.cpp @@ -0,0 +1,315 @@ +/** + * @file llpluginmessagepipe.cpp + * @brief Classes that implement connections from the plugin system to pipes/pumps. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llpluginmessagepipe.h" +#include "llbufferstream.h" + +#include "llapr.h" + +static const char MESSAGE_DELIMITER = '\0'; + +LLPluginMessagePipeOwner::LLPluginMessagePipeOwner() : + mMessagePipe(NULL), + mSocketError(APR_SUCCESS) +{ +} + +// virtual +LLPluginMessagePipeOwner::~LLPluginMessagePipeOwner() +{ + killMessagePipe(); +} + +// virtual +apr_status_t LLPluginMessagePipeOwner::socketError(apr_status_t error) +{ + mSocketError = error; + return error; +}; + +//virtual +void LLPluginMessagePipeOwner::setMessagePipe(LLPluginMessagePipe *read_pipe) +{ + // Save a reference to this pipe + mMessagePipe = read_pipe; +} + +bool LLPluginMessagePipeOwner::canSendMessage(void) +{ + return (mMessagePipe != NULL); +} + +bool LLPluginMessagePipeOwner::writeMessageRaw(const std::string &message) +{ + bool result = true; + if(mMessagePipe != NULL) + { + result = mMessagePipe->addMessage(message); + } + else + { + LL_WARNS("Plugin") << "dropping message: " << message << LL_ENDL; + result = false; + } + + return result; +} + +void LLPluginMessagePipeOwner::killMessagePipe(void) +{ + if(mMessagePipe != NULL) + { + delete mMessagePipe; + mMessagePipe = NULL; + } +} + +LLPluginMessagePipe::LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket) +{ + mOwner = owner; + mOwner->setMessagePipe(this); + mSocket = socket; +} + +LLPluginMessagePipe::~LLPluginMessagePipe() +{ + if(mOwner != NULL) + { + mOwner->setMessagePipe(NULL); + } +} + +bool LLPluginMessagePipe::addMessage(const std::string &message) +{ + // queue the message for later output + mOutput += message; + mOutput += MESSAGE_DELIMITER; // message separator + + return true; +} + +void LLPluginMessagePipe::clearOwner(void) +{ + // The owner is done with this pipe. The next call to process_impl should send any remaining data and exit. + mOwner = NULL; +} + +void LLPluginMessagePipe::setSocketTimeout(apr_interval_time_t timeout_usec) +{ + // We never want to sleep forever, so force negative timeouts to become non-blocking. + + // according to this page: http://dev.ariel-networks.com/apr/apr-tutorial/html/apr-tutorial-13.html + // blocking/non-blocking with apr sockets is somewhat non-portable. + + if(timeout_usec <= 0) + { + // Make the socket non-blocking + apr_socket_opt_set(mSocket->getSocket(), APR_SO_NONBLOCK, 1); + apr_socket_timeout_set(mSocket->getSocket(), 0); + } + else + { + // Make the socket blocking-with-timeout + apr_socket_opt_set(mSocket->getSocket(), APR_SO_NONBLOCK, 1); + apr_socket_timeout_set(mSocket->getSocket(), timeout_usec); + } +} + +bool LLPluginMessagePipe::pump(F64 timeout) +{ + bool result = true; + + if(mSocket) + { + apr_status_t status; + apr_size_t size; + + if(!mOutput.empty()) + { + // write any outgoing messages + size = (apr_size_t)mOutput.size(); + + setSocketTimeout(0); + +// LL_INFOS("Plugin") << "before apr_socket_send, size = " << size << LL_ENDL; + + status = apr_socket_send( + mSocket->getSocket(), + (const char*)mOutput.data(), + &size); + +// LL_INFOS("Plugin") << "after apr_socket_send, size = " << size << LL_ENDL; + + if(status == APR_SUCCESS) + { + // success + mOutput = mOutput.substr(size); + } + else if(APR_STATUS_IS_EAGAIN(status)) + { + // Socket buffer is full... + // remove the written part from the buffer and try again later. + mOutput = mOutput.substr(size); + } + else + { + // some other error + // Treat this as fatal. + ll_apr_warn_status(status); + + if(mOwner) + { + mOwner->socketError(status); + } + result = false; + } + } + + // FIXME: For some reason, the apr timeout stuff isn't working properly on windows. + // Until such time as we figure out why, don't try to use the socket timeout -- just sleep here instead. +#if LL_WINDOWS + if(result) + { + if(timeout != 0.0f) + { + ms_sleep((int)(timeout * 1000.0f)); + timeout = 0.0f; + } + } +#endif + + // Check for incoming messages + if(result) + { + char input_buf[1024]; + apr_size_t request_size; + + // Start out by reading one byte, so that any data received will wake us up. + request_size = 1; + + // and use the timeout so we'll sleep if no data is available. + setSocketTimeout((apr_interval_time_t)(timeout * 1000000)); + + while(1) + { + size = request_size; + +// LL_INFOS("Plugin") << "before apr_socket_recv, size = " << size << LL_ENDL; + + status = apr_socket_recv( + mSocket->getSocket(), + input_buf, + &size); + +// LL_INFOS("Plugin") << "after apr_socket_recv, size = " << size << LL_ENDL; + + if(size > 0) + mInput.append(input_buf, size); + + if(status == APR_SUCCESS) + { +// llinfos << "success, read " << size << llendl; + + if(size != request_size) + { + // This was a short read, so we're done. + break; + } + } + else if(APR_STATUS_IS_TIMEUP(status)) + { +// llinfos << "TIMEUP, read " << size << llendl; + + // Timeout was hit. Since the initial read is 1 byte, this should never be a partial read. + break; + } + else if(APR_STATUS_IS_EAGAIN(status)) + { +// llinfos << "EAGAIN, read " << size << llendl; + + // We've been doing partial reads, and we're done now. + break; + } + else + { + // some other error + // Treat this as fatal. + ll_apr_warn_status(status); + + if(mOwner) + { + mOwner->socketError(status); + } + result = false; + break; + } + + // Second and subsequent reads should not use the timeout + setSocketTimeout(0); + // and should try to fill the input buffer + request_size = sizeof(input_buf); + } + + processInput(); + } + } + + if(!result) + { + // If we got an error, we're done. + LL_INFOS("Plugin") << "Error from socket, cleaning up." << LL_ENDL; + delete this; + } + + return result; +} + +void LLPluginMessagePipe::processInput(void) +{ + // Look for input delimiter(s) in the input buffer. + int start = 0; + int delim; + while((delim = mInput.find(MESSAGE_DELIMITER, start)) != std::string::npos) + { + // Let the owner process this message + mOwner->receiveMessageRaw(mInput.substr(start, delim - start)); + + start = delim + 1; + } + + // Remove delivered messages from the input buffer. + if(start != 0) + mInput = mInput.substr(start); + +} + diff --git a/indra/llplugin/llpluginmessagepipe.h b/indra/llplugin/llpluginmessagepipe.h new file mode 100644 index 0000000000..4eb6575bd4 --- /dev/null +++ b/indra/llplugin/llpluginmessagepipe.h @@ -0,0 +1,91 @@ +/** + * @file llpluginmessagepipe.h + * @brief Classes that implement connections from the plugin system to pipes/pumps. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINMESSAGEPIPE_H +#define LL_LLPLUGINMESSAGEPIPE_H + +#include "lliosocket.h" + +class LLPluginMessagePipe; + +// Inherit from this to be able to receive messages from the LLPluginMessagePipe +class LLPluginMessagePipeOwner +{ + LOG_CLASS(LLPluginMessagePipeOwner); +public: + LLPluginMessagePipeOwner(); + virtual ~LLPluginMessagePipeOwner(); + // called with incoming messages + virtual void receiveMessageRaw(const std::string &message) = 0; + // called when the socket has an error + virtual apr_status_t socketError(apr_status_t error); + + // called from LLPluginMessagePipe to manage the connection with LLPluginMessagePipeOwner -- do not use! + virtual void setMessagePipe(LLPluginMessagePipe *message_pipe) ; + +protected: + // returns false if writeMessageRaw() would drop the message + bool canSendMessage(void); + // call this to send a message over the pipe + bool writeMessageRaw(const std::string &message); + // call this to close the pipe + void killMessagePipe(void); + + LLPluginMessagePipe *mMessagePipe; + apr_status_t mSocketError; +}; + +class LLPluginMessagePipe +{ + LOG_CLASS(LLPluginMessagePipe); +public: + LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket); + virtual ~LLPluginMessagePipe(); + + bool addMessage(const std::string &message); + void clearOwner(void); + + bool pump(F64 timeout = 0.0f); + +protected: + void processInput(void); + + // used internally by pump() + void setSocketTimeout(apr_interval_time_t timeout_usec); + + std::string mInput; + std::string mOutput; + + LLPluginMessagePipeOwner *mOwner; + LLSocket::ptr_t mSocket; +}; + +#endif // LL_LLPLUGINMESSAGE_H diff --git a/indra/llplugin/llpluginprocesschild.cpp b/indra/llplugin/llpluginprocesschild.cpp new file mode 100644 index 0000000000..293dea6fe1 --- /dev/null +++ b/indra/llplugin/llpluginprocesschild.cpp @@ -0,0 +1,466 @@ +/** + * @file llpluginprocesschild.cpp + * @brief LLPluginProcessChild handles the child side of the external-process plugin API. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llpluginprocesschild.h" +#include "llplugininstance.h" +#include "llpluginmessagepipe.h" +#include "llpluginmessageclasses.h" + +static const F32 HEARTBEAT_SECONDS = 1.0f; + +LLPluginProcessChild::LLPluginProcessChild() +{ + mInstance = NULL; + mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); + mSleepTime = 1.0f / 100.0f; // default: send idle messages at 100Hz +} + +LLPluginProcessChild::~LLPluginProcessChild() +{ + if(mInstance != NULL) + { + sendMessageToPlugin(LLPluginMessage("base", "cleanup")); + delete mInstance; + mInstance = NULL; + } +} + +void LLPluginProcessChild::killSockets(void) +{ + killMessagePipe(); + mSocket.reset(); +} + +void LLPluginProcessChild::init(U32 launcher_port) +{ + mLauncherHost = LLHost("127.0.0.1", launcher_port); + setState(STATE_INITIALIZED); +} + +void LLPluginProcessChild::idle(void) +{ + bool idle_again; + do + { + if(mSocketError != APR_SUCCESS) + { + LL_INFOS("Plugin") << "message pipe is in error state, moving to STATE_ERROR"<< LL_ENDL; + setState(STATE_ERROR); + } + + if((mState > STATE_INITIALIZED) && (mMessagePipe == NULL)) + { + // The pipe has been closed -- we're done. + // TODO: This could be slightly more subtle, but I'm not sure it needs to be. + LL_INFOS("Plugin") << "message pipe went away, moving to STATE_ERROR"<< LL_ENDL; + setState(STATE_ERROR); + } + + // If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState(). + // USE THIS CAREFULLY, since it can starve other code. Specifically make sure there's no way to get into a closed cycle and never return. + // When in doubt, don't do it. + idle_again = false; + + if(mInstance != NULL) + { + // Provide some time to the plugin + mInstance->idle(); + } + + switch(mState) + { + case STATE_UNINITIALIZED: + break; + + case STATE_INITIALIZED: + if(mSocket->blockingConnect(mLauncherHost)) + { + // This automatically sets mMessagePipe + new LLPluginMessagePipe(this, mSocket); + + setState(STATE_CONNECTED); + } + else + { + // connect failed + setState(STATE_ERROR); + } + break; + + case STATE_CONNECTED: + sendMessageToParent(LLPluginMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "hello")); + setState(STATE_PLUGIN_LOADING); + break; + + case STATE_PLUGIN_LOADING: + if(!mPluginFile.empty()) + { + mInstance = new LLPluginInstance(this); + if(mInstance->load(mPluginFile) == 0) + { + mHeartbeat.start(); + mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS); + setState(STATE_PLUGIN_LOADED); + } + else + { + setState(STATE_ERROR); + } + } + break; + + case STATE_PLUGIN_LOADED: + setState(STATE_PLUGIN_INITIALIZING); + sendMessageToPlugin(LLPluginMessage("base", "init")); + break; + + case STATE_PLUGIN_INITIALIZING: + // waiting for init_response... + break; + + case STATE_RUNNING: + if(mInstance != NULL) + { + // Provide some time to the plugin + LLPluginMessage message("base", "idle"); + message.setValueReal("time", mSleepTime); + sendMessageToPlugin(message); + + mInstance->idle(); + + if(mHeartbeat.checkExpirationAndReset(HEARTBEAT_SECONDS)) + { + // This just proves that we're not stuck down inside the plugin code. + sendMessageToParent(LLPluginMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "heartbeat")); + } + } + // receivePluginMessage will transition to STATE_UNLOADING + break; + + case STATE_UNLOADING: + if(mInstance != NULL) + { + sendMessageToPlugin(LLPluginMessage("base", "cleanup")); + delete mInstance; + mInstance = NULL; + } + setState(STATE_UNLOADED); + break; + + case STATE_UNLOADED: + killSockets(); + setState(STATE_DONE); + break; + + case STATE_ERROR: + // Close the socket to the launcher + killSockets(); + // TODO: Where do we go from here? Just exit()? + setState(STATE_DONE); + break; + + case STATE_DONE: + // just sit here. + break; + } + + } while (idle_again); +} + +void LLPluginProcessChild::sleep(F64 seconds) +{ + if(mMessagePipe) + { + mMessagePipe->pump(seconds); + } + else + { + ms_sleep((int)(seconds * 1000.0f)); + } +} + +void LLPluginProcessChild::pump(void) +{ + if(mMessagePipe) + { + mMessagePipe->pump(0.0f); + } + else + { + // Should we warn here? + } +} + + +bool LLPluginProcessChild::isRunning(void) +{ + bool result = false; + + if(mState == STATE_RUNNING) + result = true; + + return result; +} + +bool LLPluginProcessChild::isDone(void) +{ + bool result = false; + + switch(mState) + { + case STATE_DONE: + result = true; + break; + default: + break; + } + + return result; +} + +void LLPluginProcessChild::sendMessageToPlugin(const LLPluginMessage &message) +{ + std::string buffer = message.generate(); + + LL_DEBUGS("Plugin") << "Sending to plugin: " << buffer << LL_ENDL; + + mInstance->sendMessage(buffer); +} + +void LLPluginProcessChild::sendMessageToParent(const LLPluginMessage &message) +{ + std::string buffer = message.generate(); + + LL_DEBUGS("Plugin") << "Sending to parent: " << buffer << LL_ENDL; + + writeMessageRaw(buffer); +} + +void LLPluginProcessChild::receiveMessageRaw(const std::string &message) +{ + // Incoming message from the TCP Socket + + LL_DEBUGS("Plugin") << "Received from parent: " << message << LL_ENDL; + + bool passMessage = true; + + // FIXME: how should we handle queueing here? + + { + // Decode this message + LLPluginMessage parsed; + parsed.parse(message); + + std::string message_class = parsed.getClass(); + if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL) + { + passMessage = false; + + std::string message_name = parsed.getName(); + if(message_name == "load_plugin") + { + mPluginFile = parsed.getValue("file"); + } + else if(message_name == "shm_add") + { + std::string name = parsed.getValue("name"); + size_t size = (size_t)parsed.getValueS32("size"); + + sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); + if(iter != mSharedMemoryRegions.end()) + { + // Need to remove the old region first + LL_WARNS("Plugin") << "Adding a duplicate shared memory segment!" << LL_ENDL; + } + else + { + // This is a new region + LLPluginSharedMemory *region = new LLPluginSharedMemory; + if(region->attach(name, size)) + { + mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region)); + + std::stringstream addr; + addr << region->getMappedAddress(); + + // Send the add notification to the plugin + LLPluginMessage message("base", "shm_added"); + message.setValue("name", name); + message.setValueS32("size", (S32)size); + // shm address is split into 2x32bit values because LLSD doesn't serialize 64bit values and we need to support 64-bit addressing. + void * address = region->getMappedAddress(); + U32 address_lo = (U32)address; + U32 address_hi = (U32)(U64(address) / (U64(1)<<31)); + message.setValueU32("address", address_lo); + message.setValueU32("address_1", address_hi); + sendMessageToPlugin(message); + + // and send the response to the parent + message.setMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add_response"); + message.setValue("name", name); + sendMessageToParent(message); + } + else + { + LL_WARNS("Plugin") << "Couldn't create a shared memory segment!" << LL_ENDL; + } + } + + } + else if(message_name == "shm_remove") + { + std::string name = parsed.getValue("name"); + sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); + if(iter != mSharedMemoryRegions.end()) + { + // forward the remove request to the plugin -- its response will trigger us to detach the segment. + LLPluginMessage message("base", "shm_remove"); + message.setValue("name", name); + sendMessageToPlugin(message); + } + else + { + LL_WARNS("Plugin") << "shm_remove for unknown memory segment!" << LL_ENDL; + } + } + else if(message_name == "sleep_time") + { + mSleepTime = parsed.getValueReal("time"); + } + else if(message_name == "crash") + { + // Crash the plugin + LL_ERRS("Plugin") << "Plugin crash requested." << LL_ENDL; + } + else if(message_name == "hang") + { + // Hang the plugin + LL_WARNS("Plugin") << "Plugin hang requested." << LL_ENDL; + while(1) + { + // wheeeeeeeee...... + } + } + else + { + LL_WARNS("Plugin") << "Unknown internal message from parent: " << message_name << LL_ENDL; + } + } + } + + if(passMessage && mInstance != NULL) + { + mInstance->sendMessage(message); + } +} + +/* virtual */ +void LLPluginProcessChild::receivePluginMessage(const std::string &message) +{ + LL_DEBUGS("Plugin") << "Received from plugin: " << message << LL_ENDL; + + // Incoming message from the plugin instance + bool passMessage = true; + + // FIXME: how should we handle queueing here? + + // Intercept certain base messages (responses to ones sent by this class) + { + // Decode this message + LLPluginMessage parsed; + parsed.parse(message); + std::string message_class = parsed.getClass(); + if(message_class == "base") + { + std::string message_name = parsed.getName(); + if(message_name == "init_response") + { + // The plugin has finished initializing. + setState(STATE_RUNNING); + + // Don't pass this message up to the parent + passMessage = false; + + LLPluginMessage new_message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin_response"); + LLSD versions = parsed.getValueLLSD("versions"); + new_message.setValueLLSD("versions", versions); + + if(parsed.hasValue("plugin_version")) + { + std::string plugin_version = parsed.getValue("plugin_version"); + new_message.setValueLLSD("plugin_version", plugin_version); + } + + // Let the parent know it's loaded and initialized. + sendMessageToParent(new_message); + } + else if(message_name == "shm_remove_response") + { + // Don't pass this message up to the parent + passMessage = false; + + std::string name = parsed.getValue("name"); + sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); + if(iter != mSharedMemoryRegions.end()) + { + // detach the shared memory region + iter->second->detach(); + + // and remove it from our map + mSharedMemoryRegions.erase(iter); + + // Finally, send the response to the parent. + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove_response"); + message.setValue("name", name); + sendMessageToParent(message); + } + else + { + LL_WARNS("Plugin") << "shm_remove_response for unknown memory segment!" << LL_ENDL; + } + } + } + } + + if(passMessage) + { + writeMessageRaw(message); + } +} + + +void LLPluginProcessChild::setState(EState state) +{ + LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL; + mState = state; +}; diff --git a/indra/llplugin/llpluginprocesschild.h b/indra/llplugin/llpluginprocesschild.h new file mode 100644 index 0000000000..f92905e8bd --- /dev/null +++ b/indra/llplugin/llpluginprocesschild.h @@ -0,0 +1,108 @@ +/** + * @file llpluginprocesschild.h + * @brief LLPluginProcessChild handles the child side of the external-process plugin API. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINPROCESSCHILD_H +#define LL_LLPLUGINPROCESSCHILD_H + +#include "llpluginmessage.h" +#include "llpluginmessagepipe.h" +#include "llplugininstance.h" +#include "llhost.h" +#include "llpluginsharedmemory.h" + +class LLPluginInstance; + +class LLPluginProcessChild: public LLPluginMessagePipeOwner, public LLPluginInstanceMessageListener +{ + LOG_CLASS(LLPluginProcessChild); +public: + LLPluginProcessChild(); + ~LLPluginProcessChild(); + + void init(U32 launcher_port); + void idle(void); + void sleep(F64 seconds); + void pump(); + + // returns true if the plugin is in the steady state (processing messages) + bool isRunning(void); + + // returns true if the plugin is unloaded or we're in an unrecoverable error state. + bool isDone(void); + + void killSockets(void); + + F64 getSleepTime(void) const { return mSleepTime; }; + + void sendMessageToPlugin(const LLPluginMessage &message); + void sendMessageToParent(const LLPluginMessage &message); + + // Inherited from LLPluginMessagePipeOwner + /* virtual */ void receiveMessageRaw(const std::string &message); + + // Inherited from LLPluginInstanceMessageListener + /* virtual */ void receivePluginMessage(const std::string &message); + +private: + + enum EState + { + STATE_UNINITIALIZED, + STATE_INITIALIZED, // init() has been called + STATE_CONNECTED, // connected back to launcher + STATE_PLUGIN_LOADING, // plugin library needs to be loaded + STATE_PLUGIN_LOADED, // plugin library has been loaded + STATE_PLUGIN_INITIALIZING, // plugin is processing init message + STATE_RUNNING, // steady state (processing messages) + STATE_UNLOADING, // plugin has sent shutdown_response and needs to be unloaded + STATE_UNLOADED, // plugin has been unloaded + STATE_ERROR, // generic bailout state + STATE_DONE // state machine will sit in this state after either error or normal termination. + }; + EState mState; + void setState(EState state); + + LLHost mLauncherHost; + LLSocket::ptr_t mSocket; + + std::string mPluginFile; + + LLPluginInstance *mInstance; + + typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType; + sharedMemoryRegionsType mSharedMemoryRegions; + + LLTimer mHeartbeat; + F64 mSleepTime; + +}; + +#endif // LL_LLPLUGINPROCESSCHILD_H diff --git a/indra/llplugin/llpluginprocessparent.cpp b/indra/llplugin/llpluginprocessparent.cpp new file mode 100644 index 0000000000..f18a117dde --- /dev/null +++ b/indra/llplugin/llpluginprocessparent.cpp @@ -0,0 +1,658 @@ +/** + * @file llpluginprocessparent.cpp + * @brief LLPluginProcessParent handles the parent side of the external-process plugin API. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llpluginprocessparent.h" +#include "llpluginmessagepipe.h" +#include "llpluginmessageclasses.h" + +#include "llapr.h" + +// If we don't receive a heartbeat in this many seconds, we declare the plugin locked up. +static const F32 PLUGIN_LOCKED_UP_SECONDS = 10.0f; + +// Somewhat longer timeout for initial launch. +static const F32 PLUGIN_LAUNCH_SECONDS = 20.0f; + +//virtual +LLPluginProcessParentOwner::~LLPluginProcessParentOwner() +{ + +} + +LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner) +{ + mOwner = owner; + mBoundPort = 0; + mState = STATE_UNINITIALIZED; + mDisableTimeout = false; +} + +LLPluginProcessParent::~LLPluginProcessParent() +{ + LL_DEBUGS("Plugin") << "destructor" << LL_ENDL; + + // Destroy any remaining shared memory regions + sharedMemoryRegionsType::iterator iter; + while((iter = mSharedMemoryRegions.begin()) != mSharedMemoryRegions.end()) + { + // destroy the shared memory region + iter->second->destroy(); + + // and remove it from our map + mSharedMemoryRegions.erase(iter); + } + + mProcess.kill(); + killSockets(); +} + +void LLPluginProcessParent::killSockets(void) +{ + killMessagePipe(); + mListenSocket.reset(); + mSocket.reset(); +} + +void LLPluginProcessParent::init(const std::string &launcher_filename, const std::string &plugin_filename) +{ + mProcess.setExecutable(launcher_filename); + mPluginFile = plugin_filename; + + setState(STATE_INITIALIZED); +} + +bool LLPluginProcessParent::accept() +{ + bool result = false; + + apr_status_t status = APR_EGENERAL; + apr_socket_t *new_socket = NULL; + + status = apr_socket_accept( + &new_socket, + mListenSocket->getSocket(), + gAPRPoolp); + + + if(status == APR_SUCCESS) + { +// llinfos << "SUCCESS" << llendl; + // Success. Create a message pipe on the new socket + + // we MUST create a new pool for the LLSocket, since it will take ownership of it and delete it in its destructor! + apr_pool_t* new_pool = NULL; + status = apr_pool_create(&new_pool, gAPRPoolp); + + mSocket = LLSocket::create(new_socket, new_pool); + new LLPluginMessagePipe(this, mSocket); + + result = true; + } + else if(APR_STATUS_IS_EAGAIN(status)) + { +// llinfos << "EAGAIN" << llendl; + + // No incoming connections. This is not an error. + status = APR_SUCCESS; + } + else + { +// llinfos << "Error:" << llendl; + ll_apr_warn_status(status); + + // Some other error. + setState(STATE_ERROR); + } + + return result; +} + +void LLPluginProcessParent::idle(void) +{ + bool idle_again; + + do + { + // Give time to network processing + if(mMessagePipe) + { + if(!mMessagePipe->pump()) + { +// LL_WARNS("Plugin") << "Message pipe hit an error state" << LL_ENDL; + setState(STATE_ERROR); + } + } + + if((mSocketError != APR_SUCCESS) && (mState < STATE_ERROR)) + { + // The socket is in an error state -- the plugin is gone. + LL_WARNS("Plugin") << "Socket hit an error state (" << mSocketError << ")" << LL_ENDL; + setState(STATE_ERROR); + } + + // If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState(). + // USE THIS CAREFULLY, since it can starve other code. Specifically make sure there's no way to get into a closed cycle and never return. + // When in doubt, don't do it. + idle_again = false; + switch(mState) + { + case STATE_UNINITIALIZED: + break; + + case STATE_INITIALIZED: + { + + apr_status_t status = APR_SUCCESS; + apr_sockaddr_t* addr = NULL; + mListenSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); + mBoundPort = 0; + + // This code is based on parts of LLSocket::create() in lliosocket.cpp. + + status = apr_sockaddr_info_get( + &addr, + "127.0.0.1", + APR_INET, + 0, // port 0 = ephemeral ("find me a port") + 0, + gAPRPoolp); + + if(ll_apr_warn_status(status)) + { + killSockets(); + setState(STATE_ERROR); + break; + } + + // This allows us to reuse the address on quick down/up. This is unlikely to create problems. + ll_apr_warn_status(apr_socket_opt_set(mListenSocket->getSocket(), APR_SO_REUSEADDR, 1)); + + status = apr_socket_bind(mListenSocket->getSocket(), addr); + if(ll_apr_warn_status(status)) + { + killSockets(); + setState(STATE_ERROR); + break; + } + + // Get the actual port the socket was bound to + { + apr_sockaddr_t* bound_addr = NULL; + if(ll_apr_warn_status(apr_socket_addr_get(&bound_addr, APR_LOCAL, mListenSocket->getSocket()))) + { + killSockets(); + setState(STATE_ERROR); + break; + } + mBoundPort = bound_addr->port; + + if(mBoundPort == 0) + { + LL_WARNS("Plugin") << "Bound port number unknown, bailing out." << LL_ENDL; + + killSockets(); + setState(STATE_ERROR); + break; + } + } + + LL_DEBUGS("Plugin") << "Bound tcp socket to port: " << addr->port << LL_ENDL; + + // Make the listen socket non-blocking + status = apr_socket_opt_set(mListenSocket->getSocket(), APR_SO_NONBLOCK, 1); + if(ll_apr_warn_status(status)) + { + killSockets(); + setState(STATE_ERROR); + break; + } + + apr_socket_timeout_set(mListenSocket->getSocket(), 0); + if(ll_apr_warn_status(status)) + { + killSockets(); + setState(STATE_ERROR); + break; + } + + // If it's a stream based socket, we need to tell the OS + // to keep a queue of incoming connections for ACCEPT. + status = apr_socket_listen( + mListenSocket->getSocket(), + 10); // FIXME: Magic number for queue size + + if(ll_apr_warn_status(status)) + { + killSockets(); + setState(STATE_ERROR); + break; + } + + // If we got here, we're listening. + setState(STATE_LISTENING); + } + break; + + case STATE_LISTENING: + { + // Launch the plugin process. + + // Only argument to the launcher is the port number we're listening on + std::stringstream stream; + stream << mBoundPort; + mProcess.addArgument(stream.str()); + if(mProcess.launch() != 0) + { + setState(STATE_ERROR); + } + else + { + // This will allow us to time out if the process never starts. + mHeartbeat.start(); + mHeartbeat.setTimerExpirySec(PLUGIN_LAUNCH_SECONDS); + setState(STATE_LAUNCHED); + } + } + break; + + case STATE_LAUNCHED: + // waiting for the plugin to connect + if(pluginLockedUpOrQuit()) + { + setState(STATE_ERROR); + } + else + { + // Check for the incoming connection. + if(accept()) + { + // Stop listening on the server port + mListenSocket.reset(); + setState(STATE_CONNECTED); + } + } + break; + + case STATE_CONNECTED: + // waiting for hello message from the plugin + + if(pluginLockedUpOrQuit()) + { + setState(STATE_ERROR); + } + break; + + case STATE_HELLO: + LL_DEBUGS("Plugin") << "received hello message" << llendl; + + // Send the message to load the plugin + { + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin"); + message.setValue("file", mPluginFile); + sendMessage(message); + } + + setState(STATE_LOADING); + break; + + case STATE_LOADING: + // The load_plugin_response message will kick us from here into STATE_RUNNING + if(pluginLockedUpOrQuit()) + { + setState(STATE_ERROR); + } + break; + + case STATE_RUNNING: + if(pluginLockedUpOrQuit()) + { + setState(STATE_ERROR); + } + break; + + case STATE_EXITING: + if(!mProcess.isRunning()) + { + setState(STATE_CLEANUP); + } + else if(pluginLockedUp()) + { + LL_WARNS("Plugin") << "timeout in exiting state, bailing out" << llendl; + setState(STATE_ERROR); + } + break; + + case STATE_ERROR: + if(mOwner != NULL) + { + mOwner->pluginDied(); + } + setState(STATE_CLEANUP); + break; + + case STATE_CLEANUP: + mProcess.kill(); + killSockets(); + setState(STATE_DONE); + break; + + + case STATE_DONE: + // just sit here. + break; + + } + + } while (idle_again); +} + +bool LLPluginProcessParent::isLoading(void) +{ + bool result = false; + + if(mState <= STATE_LOADING) + result = true; + + return result; +} + +bool LLPluginProcessParent::isRunning(void) +{ + bool result = false; + + if(mState == STATE_RUNNING) + result = true; + + return result; +} + +bool LLPluginProcessParent::isDone(void) +{ + bool result = false; + + if(mState == STATE_DONE) + result = true; + + return result; +} + +void LLPluginProcessParent::setSleepTime(F64 sleep_time, bool force_send) +{ + if(force_send || (sleep_time != mSleepTime)) + { + // Cache the time locally + mSleepTime = sleep_time; + + if(canSendMessage()) + { + // and send to the plugin. + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "sleep_time"); + message.setValueReal("time", mSleepTime); + sendMessage(message); + } + else + { + // Too early to send -- the load_plugin_response message will trigger us to send mSleepTime later. + } + } +} + +void LLPluginProcessParent::sendMessage(const LLPluginMessage &message) +{ + + std::string buffer = message.generate(); + LL_DEBUGS("Plugin") << "Sending: " << buffer << LL_ENDL; + writeMessageRaw(buffer); +} + + +void LLPluginProcessParent::receiveMessageRaw(const std::string &message) +{ + LL_DEBUGS("Plugin") << "Received: " << message << LL_ENDL; + + // FIXME: should this go into a queue instead? + + LLPluginMessage parsed; + if(parsed.parse(message) != -1) + { + receiveMessage(parsed); + } +} + +void LLPluginProcessParent::receiveMessage(const LLPluginMessage &message) +{ + std::string message_class = message.getClass(); + if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL) + { + // internal messages should be handled here + std::string message_name = message.getName(); + if(message_name == "hello") + { + if(mState == STATE_CONNECTED) + { + // Plugin host has launched. Tell it which plugin to load. + setState(STATE_HELLO); + } + else + { + LL_WARNS("Plugin") << "received hello message in wrong state -- bailing out" << LL_ENDL; + setState(STATE_ERROR); + } + + } + else if(message_name == "load_plugin_response") + { + if(mState == STATE_LOADING) + { + // Plugin has been loaded. + + // Check which message classes/versions the plugin supports. + // TODO: check against current versions + // TODO: kill plugin on major mismatches? + mMessageClassVersions = message.getValueLLSD("versions"); + LLSD::map_iterator iter; + for(iter = mMessageClassVersions.beginMap(); iter != mMessageClassVersions.endMap(); iter++) + { + LL_INFOS("Plugin") << "message class: " << iter->first << " -> version: " << iter->second.asString() << LL_ENDL; + } + + mPluginVersionString = message.getValue("plugin_version"); + + // Send initial sleep time + setSleepTime(mSleepTime, true); + + setState(STATE_RUNNING); + } + else + { + LL_WARNS("Plugin") << "received load_plugin_response message in wrong state -- bailing out" << LL_ENDL; + setState(STATE_ERROR); + } + } + else if(message_name == "heartbeat") + { + // this resets our timer. + mHeartbeat.setTimerExpirySec(PLUGIN_LOCKED_UP_SECONDS); + } + else if(message_name == "shm_add_response") + { + // Nothing to do here. + } + else if(message_name == "shm_remove_response") + { + std::string name = message.getValue("name"); + sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); + + if(iter != mSharedMemoryRegions.end()) + { + // destroy the shared memory region + iter->second->destroy(); + + // and remove it from our map + mSharedMemoryRegions.erase(iter); + } + } + else + { + LL_WARNS("Plugin") << "Unknown internal message from child: " << message_name << LL_ENDL; + } + } + else + { + if(mOwner != NULL) + { + mOwner->receivePluginMessage(message); + } + } +} + +std::string LLPluginProcessParent::addSharedMemory(size_t size) +{ + std::string name; + + LLPluginSharedMemory *region = new LLPluginSharedMemory; + + // This is a new region + if(region->create(size)) + { + name = region->getName(); + + mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region)); + + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add"); + message.setValue("name", name); + message.setValueS32("size", (S32)size); + sendMessage(message); + } + else + { + LL_WARNS("Plugin") << "Couldn't create a shared memory segment!" << LL_ENDL; + + // Don't leak + delete region; + } + + return name; +} + +void LLPluginProcessParent::removeSharedMemory(const std::string &name) +{ + sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); + + if(iter != mSharedMemoryRegions.end()) + { + // This segment exists. Send the message to the child to unmap it. The response will cause the parent to unmap our end. + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove"); + message.setValue("name", name); + sendMessage(message); + } + else + { + LL_WARNS("Plugin") << "Request to remove an unknown shared memory segment." << LL_ENDL; + } +} +size_t LLPluginProcessParent::getSharedMemorySize(const std::string &name) +{ + size_t result = 0; + + sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); + if(iter != mSharedMemoryRegions.end()) + { + result = iter->second->getSize(); + } + + return result; +} +void *LLPluginProcessParent::getSharedMemoryAddress(const std::string &name) +{ + void *result = NULL; + + sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); + if(iter != mSharedMemoryRegions.end()) + { + result = iter->second->getMappedAddress(); + } + + return result; +} + +std::string LLPluginProcessParent::getMessageClassVersion(const std::string &message_class) +{ + std::string result; + + if(mMessageClassVersions.has(message_class)) + { + result = mMessageClassVersions[message_class].asString(); + } + + return result; +} + +std::string LLPluginProcessParent::getPluginVersion(void) +{ + return mPluginVersionString; +} + +void LLPluginProcessParent::setState(EState state) +{ + LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL; + mState = state; +}; + +bool LLPluginProcessParent::pluginLockedUpOrQuit() +{ + bool result = false; + + if(!mDisableTimeout) + { + if(!mProcess.isRunning()) + { + LL_WARNS("Plugin") << "child exited" << llendl; + result = true; + } + else if(pluginLockedUp()) + { + LL_WARNS("Plugin") << "timeout" << llendl; + result = true; + } + } + + return result; +} + +bool LLPluginProcessParent::pluginLockedUp() +{ + // If the timer has expired, the plugin has locked up. + return mHeartbeat.hasExpired(); +} + diff --git a/indra/llplugin/llpluginprocessparent.h b/indra/llplugin/llpluginprocessparent.h new file mode 100644 index 0000000000..545eb85c9a --- /dev/null +++ b/indra/llplugin/llpluginprocessparent.h @@ -0,0 +1,147 @@ +/** + * @file llpluginprocessparent.h + * @brief LLPluginProcessParent handles the parent side of the external-process plugin API. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINPROCESSPARENT_H +#define LL_LLPLUGINPROCESSPARENT_H + +#include "llprocesslauncher.h" +#include "llpluginmessage.h" +#include "llpluginmessagepipe.h" +#include "llpluginsharedmemory.h" + +#include "lliosocket.h" + +class LLPluginProcessParentOwner +{ +public: + virtual ~LLPluginProcessParentOwner(); + virtual void receivePluginMessage(const LLPluginMessage &message) = 0; + // This will only be called when the plugin has died unexpectedly + virtual void pluginDied() {}; +}; + +class LLPluginProcessParent : public LLPluginMessagePipeOwner +{ + LOG_CLASS(LLPluginProcessParent); +public: + LLPluginProcessParent(LLPluginProcessParentOwner *owner); + ~LLPluginProcessParent(); + + void init(const std::string &launcher_filename, const std::string &plugin_filename); + void idle(void); + + // returns true if the plugin is on its way to steady state + bool isLoading(void); + + // returns true if the plugin is in the steady state (processing messages) + bool isRunning(void); + + // returns true if the process has exited or we've had a fatal error + bool isDone(void); + + void killSockets(void); + + void setSleepTime(F64 sleep_time, bool force_send = false); + F64 getSleepTime(void) const { return mSleepTime; }; + + void sendMessage(const LLPluginMessage &message); + + void receiveMessage(const LLPluginMessage &message); + + // Inherited from LLPluginMessagePipeOwner + void receiveMessageRaw(const std::string &message); + + // This adds a memory segment shared with the client, generating a name for the segment. The name generated is guaranteed to be unique on the host. + // The caller must call removeSharedMemory first (and wait until getSharedMemorySize returns 0 for the indicated name) before re-adding a segment with the same name. + std::string addSharedMemory(size_t size); + // Negotiates for the removal of a shared memory segment. It is the caller's responsibility to ensure that nothing touches the memory + // after this has been called, since the segment will be unmapped shortly thereafter. + void removeSharedMemory(const std::string &name); + size_t getSharedMemorySize(const std::string &name); + void *getSharedMemoryAddress(const std::string &name); + + // Returns the version string the plugin indicated for the message class, or an empty string if that class wasn't in the list. + std::string getMessageClassVersion(const std::string &message_class); + + std::string getPluginVersion(void); + + bool getDisableTimeout() { return mDisableTimeout; }; + void setDisableTimeout(bool disable) { mDisableTimeout = disable; }; + +private: + + enum EState + { + STATE_UNINITIALIZED, + STATE_INITIALIZED, // init() has been called + STATE_LISTENING, // listening for incoming connection + STATE_LAUNCHED, // process has been launched + STATE_CONNECTED, // process has connected + STATE_HELLO, // first message from the plugin process has been received + STATE_LOADING, // process has been asked to load the plugin + STATE_RUNNING, // + STATE_ERROR, // generic bailout state + STATE_CLEANUP, // clean everything up + STATE_EXITING, // Tried to kill process, waiting for it to exit + STATE_DONE // + + }; + EState mState; + void setState(EState state); + + bool pluginLockedUp(); + bool pluginLockedUpOrQuit(); + + bool accept(); + + LLSocket::ptr_t mListenSocket; + LLSocket::ptr_t mSocket; + U32 mBoundPort; + + LLProcessLauncher mProcess; + + std::string mPluginFile; + + LLPluginProcessParentOwner *mOwner; + + typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType; + sharedMemoryRegionsType mSharedMemoryRegions; + + LLSD mMessageClassVersions; + std::string mPluginVersionString; + + LLTimer mHeartbeat; + F64 mSleepTime; + + bool mDisableTimeout; +}; + +#endif // LL_LLPLUGINPROCESSPARENT_H diff --git a/indra/llplugin/llpluginsharedmemory.cpp b/indra/llplugin/llpluginsharedmemory.cpp new file mode 100644 index 0000000000..ce8b8e3e09 --- /dev/null +++ b/indra/llplugin/llpluginsharedmemory.cpp @@ -0,0 +1,495 @@ +/** + * @file llpluginsharedmemory.cpp + * @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llpluginsharedmemory.h" + +// on Mac and Linux, we use the native shm_open/mmap interface by using +// #define USE_SHM_OPEN_SHARED_MEMORY 1 +// in the appropriate sections below. + +// For Windows, use: +// #define USE_WIN32_SHARED_MEMORY 1 + +// If we ever want to fall back to the apr implementation for a platform, use: +// #define USE_APR_SHARED_MEMORY 1 + +#if LL_WINDOWS +// #define USE_APR_SHARED_MEMORY 1 + #define USE_WIN32_SHARED_MEMORY 1 +#elif LL_DARWIN + #define USE_SHM_OPEN_SHARED_MEMORY 1 +#elif LL_LINUX + #define USE_SHM_OPEN_SHARED_MEMORY 1 +#endif + + +// FIXME: This path thing is evil and unacceptable. +#if LL_WINDOWS + #define APR_SHARED_MEMORY_PREFIX_STRING "C:\\LLPlugin_" + // Apparnently using the "Global\\" prefix here only works from administrative accounts under Vista. + // Other options I've seen referenced are "Local\\" and "Session\\". + #define WIN32_SHARED_MEMORY_PREFIX_STRING "Local\\LL_" +#else + // mac and linux + #define APR_SHARED_MEMORY_PREFIX_STRING "/tmp/LLPlugin_" + #define SHM_OPEN_SHARED_MEMORY_PREFIX_STRING "/LL" +#endif + +#if USE_APR_SHARED_MEMORY + #include "llapr.h" + #include "apr_shm.h" +#elif USE_SHM_OPEN_SHARED_MEMORY + #include <sys/fcntl.h> + #include <sys/mman.h> + #include <errno.h> +#elif USE_WIN32_SHARED_MEMORY +#include <windows.h> +#endif // USE_APR_SHARED_MEMORY + + +int LLPluginSharedMemory::sSegmentNumber = 0; + +std::string LLPluginSharedMemory::createName(void) +{ + std::stringstream newname; + +#if LL_WINDOWS + newname << GetCurrentProcessId(); +#else // LL_WINDOWS + newname << getpid(); +#endif // LL_WINDOWS + + newname << "_" << sSegmentNumber++; + + return newname.str(); +} + +class LLPluginSharedMemoryPlatformImpl +{ +public: + LLPluginSharedMemoryPlatformImpl(); + ~LLPluginSharedMemoryPlatformImpl(); + +#if USE_APR_SHARED_MEMORY + apr_shm_t* mAprSharedMemory; +#elif USE_SHM_OPEN_SHARED_MEMORY + int mSharedMemoryFD; +#elif USE_WIN32_SHARED_MEMORY + HANDLE mMapFile; +#endif + +}; + +LLPluginSharedMemory::LLPluginSharedMemory() +{ + mSize = 0; + mMappedAddress = NULL; + mNeedsDestroy = false; + + mImpl = new LLPluginSharedMemoryPlatformImpl; +} + +LLPluginSharedMemory::~LLPluginSharedMemory() +{ + if(mNeedsDestroy) + destroy(); + else + detach(); + + unlink(); + + delete mImpl; +} + +#if USE_APR_SHARED_MEMORY +// MARK: apr implementation + +LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl() +{ + mAprSharedMemory = NULL; +} + +LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl() +{ + +} + +bool LLPluginSharedMemory::map(void) +{ + mMappedAddress = apr_shm_baseaddr_get(mImpl->mAprSharedMemory); + if(mMappedAddress == NULL) + { + return false; + } + + return true; +} + +bool LLPluginSharedMemory::unmap(void) +{ + // This is a no-op under apr. + return true; +} + +bool LLPluginSharedMemory::close(void) +{ + // This is a no-op under apr. + return true; +} + +bool LLPluginSharedMemory::unlink(void) +{ + // This is a no-op under apr. + return true; +} + + +bool LLPluginSharedMemory::create(size_t size) +{ + mName = APR_SHARED_MEMORY_PREFIX_STRING; + mName += createName(); + mSize = size; + + apr_status_t status = apr_shm_create( &(mImpl->mAprSharedMemory), mSize, mName.c_str(), gAPRPoolp ); + + if(ll_apr_warn_status(status)) + { + return false; + } + + mNeedsDestroy = true; + + return map(); +} + +bool LLPluginSharedMemory::destroy(void) +{ + if(mImpl->mAprSharedMemory) + { + apr_status_t status = apr_shm_destroy(mImpl->mAprSharedMemory); + if(ll_apr_warn_status(status)) + { + // TODO: Is this a fatal error? I think not... + } + mImpl->mAprSharedMemory = NULL; + } + + return true; +} + +bool LLPluginSharedMemory::attach(const std::string &name, size_t size) +{ + mName = name; + mSize = size; + + apr_status_t status = apr_shm_attach( &(mImpl->mAprSharedMemory), mName.c_str(), gAPRPoolp ); + + if(ll_apr_warn_status(status)) + { + return false; + } + + return map(); +} + + +bool LLPluginSharedMemory::detach(void) +{ + if(mImpl->mAprSharedMemory) + { + apr_status_t status = apr_shm_detach(mImpl->mAprSharedMemory); + if(ll_apr_warn_status(status)) + { + // TODO: Is this a fatal error? I think not... + } + mImpl->mAprSharedMemory = NULL; + } + + return true; +} + + +#elif USE_SHM_OPEN_SHARED_MEMORY +// MARK: shm_open/mmap implementation + +LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl() +{ + mSharedMemoryFD = -1; +} + +LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl() +{ +} + +bool LLPluginSharedMemory::map(void) +{ + mMappedAddress = ::mmap(NULL, mSize, PROT_READ | PROT_WRITE, MAP_SHARED, mImpl->mSharedMemoryFD, 0); + if(mMappedAddress == NULL) + { + return false; + } + + LL_DEBUGS("Plugin") << "memory mapped at " << mMappedAddress << LL_ENDL; + + return true; +} + +bool LLPluginSharedMemory::unmap(void) +{ + if(mMappedAddress != NULL) + { + LL_DEBUGS("Plugin") << "calling munmap(" << mMappedAddress << ", " << mSize << ")" << LL_ENDL; + if(::munmap(mMappedAddress, mSize) == -1) + { + // TODO: Is this a fatal error? I think not... + } + + mMappedAddress = NULL; + } + + return true; +} + +bool LLPluginSharedMemory::close(void) +{ + if(mImpl->mSharedMemoryFD != -1) + { + LL_DEBUGS("Plugin") << "calling close(" << mImpl->mSharedMemoryFD << ")" << LL_ENDL; + if(::close(mImpl->mSharedMemoryFD) == -1) + { + // TODO: Is this a fatal error? I think not... + } + + mImpl->mSharedMemoryFD = -1; + } + return true; +} + +bool LLPluginSharedMemory::unlink(void) +{ + if(!mName.empty()) + { + if(::shm_unlink(mName.c_str()) == -1) + { + return false; + } + } + + return true; +} + + +bool LLPluginSharedMemory::create(size_t size) +{ + mName = SHM_OPEN_SHARED_MEMORY_PREFIX_STRING; + mName += createName(); + mSize = size; + + // Preemptive unlink, just in case something didn't get cleaned up. + unlink(); + + mImpl->mSharedMemoryFD = ::shm_open(mName.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + if(mImpl->mSharedMemoryFD == -1) + { + return false; + } + + mNeedsDestroy = true; + + if(::ftruncate(mImpl->mSharedMemoryFD, mSize) == -1) + { + return false; + } + + + return map(); +} + +bool LLPluginSharedMemory::destroy(void) +{ + unmap(); + close(); + + return true; +} + + +bool LLPluginSharedMemory::attach(const std::string &name, size_t size) +{ + mName = name; + mSize = size; + + mImpl->mSharedMemoryFD = ::shm_open(mName.c_str(), O_RDWR, S_IRUSR | S_IWUSR); + if(mImpl->mSharedMemoryFD == -1) + { + return false; + } + + // unlink here so the segment will be cleaned up automatically after the last close. + unlink(); + + return map(); +} + +bool LLPluginSharedMemory::detach(void) +{ + unmap(); + close(); + return true; +} + +#elif USE_WIN32_SHARED_MEMORY +// MARK: Win32 CreateFileMapping-based implementation + +// Reference: http://msdn.microsoft.com/en-us/library/aa366551(VS.85).aspx + +LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl() +{ + mMapFile = NULL; +} + +LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl() +{ + +} + +bool LLPluginSharedMemory::map(void) +{ + mMappedAddress = MapViewOfFile( + mImpl->mMapFile, // handle to map object + FILE_MAP_ALL_ACCESS, // read/write permission + 0, + 0, + mSize); + + if(mMappedAddress == NULL) + { + LL_WARNS("Plugin") << "MapViewOfFile failed: " << GetLastError() << LL_ENDL; + return false; + } + + LL_DEBUGS("Plugin") << "memory mapped at " << mMappedAddress << LL_ENDL; + + return true; +} + +bool LLPluginSharedMemory::unmap(void) +{ + if(mMappedAddress != NULL) + { + UnmapViewOfFile(mMappedAddress); + mMappedAddress = NULL; + } + + return true; +} + +bool LLPluginSharedMemory::close(void) +{ + if(mImpl->mMapFile != NULL) + { + CloseHandle(mImpl->mMapFile); + mImpl->mMapFile = NULL; + } + + return true; +} + +bool LLPluginSharedMemory::unlink(void) +{ + // This is a no-op on Windows. + return true; +} + + +bool LLPluginSharedMemory::create(size_t size) +{ + mName = WIN32_SHARED_MEMORY_PREFIX_STRING; + mName += createName(); + mSize = size; + + mImpl->mMapFile = CreateFileMappingA( + INVALID_HANDLE_VALUE, // use paging file + NULL, // default security + PAGE_READWRITE, // read/write access + 0, // max. object size + mSize, // buffer size + mName.c_str()); // name of mapping object + + if(mImpl->mMapFile == NULL) + { + LL_WARNS("Plugin") << "CreateFileMapping failed: " << GetLastError() << LL_ENDL; + return false; + } + + mNeedsDestroy = true; + + return map(); +} + +bool LLPluginSharedMemory::destroy(void) +{ + unmap(); + close(); + return true; +} + +bool LLPluginSharedMemory::attach(const std::string &name, size_t size) +{ + mName = name; + mSize = size; + + mImpl->mMapFile = OpenFileMappingA( + FILE_MAP_ALL_ACCESS, // read/write access + FALSE, // do not inherit the name + mName.c_str()); // name of mapping object + + if(mImpl->mMapFile == NULL) + { + LL_WARNS("Plugin") << "OpenFileMapping failed: " << GetLastError() << LL_ENDL; + return false; + } + + return map(); +} + +bool LLPluginSharedMemory::detach(void) +{ + unmap(); + close(); + return true; +} + + + +#endif diff --git a/indra/llplugin/llpluginsharedmemory.h b/indra/llplugin/llpluginsharedmemory.h new file mode 100644 index 0000000000..a4613b9a54 --- /dev/null +++ b/indra/llplugin/llpluginsharedmemory.h @@ -0,0 +1,79 @@ +/** + * @file llpluginsharedmemory.h + * @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINSHAREDMEMORY_H +#define LL_LLPLUGINSHAREDMEMORY_H + +class LLPluginSharedMemoryPlatformImpl; + +class LLPluginSharedMemory +{ + LOG_CLASS(LLPluginSharedMemory); +public: + LLPluginSharedMemory(); + ~LLPluginSharedMemory(); + + // Parent will use create/destroy, child will use attach/detach. + // Message transactions will ensure child attaches after parent creates and detaches before parent destroys. + + // create() implicitly creates a name for the segment which is guaranteed to be unique on the host at the current time. + bool create(size_t size); + bool destroy(void); + + bool attach(const std::string &name, size_t size); + bool detach(void); + + bool isMapped(void) const { return (mMappedAddress != NULL); }; + void *getMappedAddress(void) const { return mMappedAddress; }; + size_t getSize(void) const { return mSize; }; + std::string getName() const { return mName; }; + +private: + bool map(void); + bool unmap(void); + bool close(void); + bool unlink(void); + + std::string mName; + size_t mSize; + void *mMappedAddress; + bool mNeedsDestroy; + + LLPluginSharedMemoryPlatformImpl *mImpl; + + static int sSegmentNumber; + static std::string createName(); + +}; + + + +#endif // LL_LLPLUGINSHAREDMEMORY_H diff --git a/indra/llplugin/slplugin/CMakeLists.txt b/indra/llplugin/slplugin/CMakeLists.txt new file mode 100644 index 0000000000..4a7d670c23 --- /dev/null +++ b/indra/llplugin/slplugin/CMakeLists.txt @@ -0,0 +1,55 @@ +project(SLPlugin) + +include(00-Common) +include(LLCommon) +include(LLPlugin) +include(Linking) +include(PluginAPI) +include(LLMessage) + +include_directories( + ${LLPLUGIN_INCLUDE_DIRS} + ${LLMESSAGE_INCLUDE_DIRS} + ${LLCOMMON_INCLUDE_DIRS} +) + +if (DARWIN) + include(CMakeFindFrameworks) + find_library(CARBON_LIBRARY Carbon) +endif (DARWIN) + + +### SLPlugin + +set(SLPlugin_SOURCE_FILES + slplugin.cpp + ) + +add_executable(SLPlugin + WIN32 + ${SLPlugin_SOURCE_FILES} +) + +target_link_libraries(SLPlugin + ${LLPLUGIN_LIBRARIES} + ${LLMESSAGE_LIBRARIES} + ${LLCOMMON_LIBRARIES} + ${PLUGIN_API_WINDOWS_LIBRARIES} +) + +add_dependencies(SLPlugin + ${LLPLUGIN_LIBRARIES} + ${LLMESSAGE_LIBRARIES} + ${LLCOMMON_LIBRARIES} +) + +if (DARWIN) + # Mac version needs to link against carbon, and also needs an embedded plist (to set LSBackgroundOnly) + target_link_libraries(SLPlugin ${CARBON_LIBRARY}) + set_target_properties( + SLPlugin + PROPERTIES + LINK_FLAGS "-Wl,-sectcreate,__TEXT,__info_plist,${CMAKE_CURRENT_SOURCE_DIR}/slplugin_info.plist" + ) +endif (DARWIN) + diff --git a/indra/llplugin/slplugin/slplugin.cpp b/indra/llplugin/slplugin/slplugin.cpp new file mode 100644 index 0000000000..005e427572 --- /dev/null +++ b/indra/llplugin/slplugin/slplugin.cpp @@ -0,0 +1,243 @@ +/** + * @file slplugin.cpp + * @brief Loader shell for plugins, intended to be launched by the plugin host application, which directly loads a plugin dynamic library. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + + +#include "linden_common.h" + +#include "llpluginprocesschild.h" +#include "llpluginmessage.h" +#include "llerrorcontrol.h" +#include "llapr.h" +#include "llstring.h" + +#if LL_DARWIN + #include <Carbon/Carbon.h> +#endif + +#if LL_DARWIN || LL_LINUX + #include <signal.h> +#endif + +/* + On Mac OS, since we call WaitNextEvent, this process will show up in the dock unless we set the LSBackgroundOnly flag in the Info.plist. + + Normally non-bundled binaries don't have an info.plist file, but it's possible to embed one in the binary by adding this to the linker flags: + + -sectcreate __TEXT __info_plist /path/to/slplugin_info.plist + + which means adding this to the gcc flags: + + -Wl,-sectcreate,__TEXT,__info_plist,/path/to/slplugin_info.plist + +*/ + +#if LL_DARWIN || LL_LINUX +// Signal handlers to make crashes not show an OS dialog... +static void crash_handler(int sig) +{ + // Just exit cleanly. + // TODO: add our own crash reporting + _exit(1); +} +#endif + +#if LL_WINDOWS +#include <windows.h> +//////////////////////////////////////////////////////////////////////////////// +// Our exception handler - will probably just exit and the host application +// will miss the heartbeat and log the error in the usual fashion. +LONG WINAPI myWin32ExceptionHandler( struct _EXCEPTION_POINTERS* exception_infop ) +{ + //std::cerr << "This plugin (" << __FILE__ << ") - "; + //std::cerr << "intercepted an unhandled exception and will exit immediately." << std::endl; + + // TODO: replace exception handler before we exit? + return EXCEPTION_EXECUTE_HANDLER; +} + +//////////////////////////////////////////////////////////////////////////////// +// Hook our exception handler and replace the system one +void initExceptionHandler() +{ + LPTOP_LEVEL_EXCEPTION_FILTER prev_filter; + + // save old exception handler in case we need to restore it at the end + prev_filter = SetUnhandledExceptionFilter( myWin32ExceptionHandler ); +} + +bool checkExceptionHandler() +{ + bool ok = true; + LPTOP_LEVEL_EXCEPTION_FILTER prev_filter; + prev_filter = SetUnhandledExceptionFilter(myWin32ExceptionHandler); + + if (prev_filter != myWin32ExceptionHandler) + { + LL_WARNS("AppInit") << "Our exception handler (" << (void *)myWin32ExceptionHandler << ") replaced with " << prev_filter << "!" << LL_ENDL; + ok = false; + } + + if (prev_filter == NULL) + { + ok = FALSE; + if (myWin32ExceptionHandler == NULL) + { + LL_WARNS("AppInit") << "Exception handler uninitialized." << LL_ENDL; + } + else + { + LL_WARNS("AppInit") << "Our exception handler (" << (void *)myWin32ExceptionHandler << ") replaced with NULL!" << LL_ENDL; + } + } + + return ok; +} +#endif + +// If this application on Windows platform is a console application, a console is always +// created which is bad. Making it a Windows "application" via CMake settings but not +// adding any code to explicitly create windows does the right thing. +#if LL_WINDOWS +int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) +#else +int main(int argc, char **argv) +#endif +{ + ll_init_apr(); + + // Set up llerror logging + { + LLError::initForApplication("."); + LLError::setDefaultLevel(LLError::LEVEL_INFO); +// LLError::setTagLevel("Plugin", LLError::LEVEL_DEBUG); +// LLError::logToFile("slplugin.log"); + } + +#if LL_WINDOWS + if( strlen( lpCmdLine ) == 0 ) + { + LL_ERRS("slplugin") << "usage: " << "SLPlugin" << " launcher_port" << LL_ENDL; + }; + + U32 port = 0; + if(!LLStringUtil::convertToU32(lpCmdLine, port)) + { + LL_ERRS("slplugin") << "port number must be numeric" << LL_ENDL; + }; + + // Insert our exception handler into the system so this plugin doesn't + // display a crash message if something bad happens. The host app will + // see the missing heartbeat and log appropriately. + initExceptionHandler(); +#elif LL_DARWIN || LL_LINUX + if(argc < 2) + { + LL_ERRS("slplugin") << "usage: " << argv[0] << " launcher_port" << LL_ENDL; + } + + U32 port = 0; + if(!LLStringUtil::convertToU32(argv[1], port)) + { + LL_ERRS("slplugin") << "port number must be numeric" << LL_ENDL; + } + + // Catch signals that most kinds of crashes will generate, and exit cleanly so the system crash dialog isn't shown. + signal(SIGILL, &crash_handler); // illegal instruction +# if LL_DARWIN + signal(SIGEMT, &crash_handler); // emulate instruction executed +# endif // LL_DARWIN + signal(SIGFPE, &crash_handler); // floating-point exception + signal(SIGBUS, &crash_handler); // bus error + signal(SIGSEGV, &crash_handler); // segmentation violation + signal(SIGSYS, &crash_handler); // non-existent system call invoked +#endif + + LLPluginProcessChild *plugin = new LLPluginProcessChild(); + + plugin->init(port); + + LLTimer timer; + timer.start(); + +#if LL_WINDOWS + checkExceptionHandler(); +#endif + + while(!plugin->isDone()) + { + timer.reset(); + plugin->idle(); +#if LL_DARWIN + { + // Some plugins (webkit at least) will want an event loop. This qualifies. + EventRecord evt; + WaitNextEvent(0, &evt, 0, NULL); + } +#endif + F64 elapsed = timer.getElapsedTimeF64(); + F64 remaining = plugin->getSleepTime() - elapsed; + + if(remaining <= 0.0f) + { + // We've already used our full allotment. +// LL_INFOS("slplugin") << "elapsed = " << elapsed * 1000.0f << " ms, remaining = " << remaining * 1000.0f << " ms, not sleeping" << LL_ENDL; + + // Still need to service the network... + plugin->pump(); + } + else + { + +// LL_INFOS("slplugin") << "elapsed = " << elapsed * 1000.0f << " ms, remaining = " << remaining * 1000.0f << " ms, sleeping for " << remaining * 1000.0f << " ms" << LL_ENDL; +// timer.reset(); + + // This also services the network as needed. + plugin->sleep(remaining); + +// LL_INFOS("slplugin") << "slept for "<< timer.getElapsedTimeF64() * 1000.0f << " ms" << LL_ENDL; + } + +#if LL_WINDOWS + // More agressive checking of interfering exception handlers. + // Doesn't appear to be required so far - even for plugins + // that do crash with a single call to the intercept + // exception handler such as QuickTime. + //checkExceptionHandler(); +#endif + } + + delete plugin; + + ll_cleanup_apr(); + + return 0; +} + diff --git a/indra/llplugin/slplugin/slplugin_info.plist b/indra/llplugin/slplugin/slplugin_info.plist new file mode 100644 index 0000000000..b1daf87424 --- /dev/null +++ b/indra/llplugin/slplugin/slplugin_info.plist @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>English</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>LSBackgroundOnly</key> + <true/> +</dict> +</plist> diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index 3669b49e51..9d2cd4867a 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -728,7 +728,7 @@ void LLImageGL::setImage(const U8* data_in, BOOL data_hasmips) llpushcallstacks ; } -BOOL LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height) +BOOL LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update) { llpushcallstacks ; if (!width || !height) @@ -746,7 +746,8 @@ BOOL LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S3 return FALSE; } - if (x_pos == 0 && y_pos == 0 && width == getWidth() && height == getHeight() && data_width == width && data_height == height) + // HACK: allow the caller to explicitly force the fast path (i.e. using glTexSubImage2D here instead of calling setImage) even when updating the full texture. + if (!force_fast_update && x_pos == 0 && y_pos == 0 && width == getWidth() && height == getHeight() && data_width == width && data_height == height) { setImage(datap, FALSE); } @@ -823,9 +824,9 @@ BOOL LLImageGL::setSubImage(const U8* datap, S32 data_width, S32 data_height, S3 return TRUE; } -BOOL LLImageGL::setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height) +BOOL LLImageGL::setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update) { - return setSubImage(imageraw->getData(), imageraw->getWidth(), imageraw->getHeight(), x_pos, y_pos, width, height); + return setSubImage(imageraw->getData(), imageraw->getWidth(), imageraw->getHeight(), x_pos, y_pos, width, height, force_fast_update); } // Copy sub image from frame buffer diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h index 5f32b23356..987a1dc538 100644 --- a/indra/llrender/llimagegl.h +++ b/indra/llrender/llimagegl.h @@ -106,8 +106,8 @@ public: BOOL createGLTexture(S32 discard_level, const U8* data, BOOL data_hasmips = FALSE, S32 usename = 0); void setImage(const LLImageRaw* imageraw); void setImage(const U8* data_in, BOOL data_hasmips = FALSE); - BOOL setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height); - BOOL setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height); + BOOL setSubImage(const LLImageRaw* imageraw, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update = FALSE); + BOOL setSubImage(const U8* datap, S32 data_width, S32 data_height, S32 x_pos, S32 y_pos, S32 width, S32 height, BOOL force_fast_update = FALSE); BOOL setSubImageFromFrameBuffer(S32 fb_x, S32 fb_y, S32 x_pos, S32 y_pos, S32 width, S32 height); BOOL setDiscardLevel(S32 discard_level); // Read back a raw image for this discard level, if it exists diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 7f71ff6a53..8b0fcc68c4 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -3,7 +3,6 @@ project(llui) include(00-Common) -include(LLAudio) include(LLCommon) include(LLImage) include(LLMath) @@ -15,7 +14,6 @@ include(LLXML) include(LLXUIXML) include_directories( - ${LLAUDIO_INCLUDE_DIRS} ${LLCOMMON_INCLUDE_DIRS} ${LLIMAGE_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} diff --git a/indra/llui/llbutton.cpp b/indra/llui/llbutton.cpp index b13b250c75..98e8c9a988 100644 --- a/indra/llui/llbutton.cpp +++ b/indra/llui/llbutton.cpp @@ -937,6 +937,16 @@ void LLButton::setColor(const LLColor4& color) setImageColor(color); } +void LLButton::setAlpha(F32 alpha) +{ + LLColor4 temp = mImageColor.get(); + temp.setAlpha(alpha); + mImageColor.set(temp); + + temp = mDisabledImageColor.get(); + temp.setAlpha(alpha * 0.5f); + mDisabledImageColor.set(temp); +} void LLButton::setImageDisabled(LLPointer<LLUIImage> image) { diff --git a/indra/llui/llbutton.h b/indra/llui/llbutton.h index 249882013a..e51cd443fa 100644 --- a/indra/llui/llbutton.h +++ b/indra/llui/llbutton.h @@ -190,7 +190,8 @@ public: void setImageColor(const std::string& color_control); void setImageColor(const LLColor4& c); - virtual void setColor(const LLColor4& c); + /*virtual*/ void setColor(const LLColor4& c); + /*virtual*/ void setAlpha(F32 alpha); void setImages(const std::string &image_name, const std::string &selected_name); diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 2679143bbc..ca3829e1bd 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -1595,9 +1595,9 @@ void LLFloater::draw() { if (hasFocus() && getDefaultButton()->getEnabled()) { - LLUICtrl* focus_ctrl = gFocusMgr.getKeyboardFocus(); + LLFocusableElement* focus_ctrl = gFocusMgr.getKeyboardFocus(); // is this button a direct descendent and not a nested widget (e.g. checkbox)? - BOOL focus_is_child_button = dynamic_cast<LLButton*>(focus_ctrl) != NULL && focus_ctrl->getParent() == this; + BOOL focus_is_child_button = dynamic_cast<LLButton*>(focus_ctrl) != NULL && dynamic_cast<LLButton*>(focus_ctrl)->getParent() == this; // only enable default button when current focus is not a button getDefaultButton()->setBorderEnabled(!focus_is_child_button); } @@ -1617,7 +1617,7 @@ void LLFloater::draw() else { // draw children - LLView* focused_child = gFocusMgr.getKeyboardFocus(); + LLView* focused_child = dynamic_cast<LLView*>(gFocusMgr.getKeyboardFocus()); BOOL focused_child_visible = FALSE; if (focused_child && focused_child->getParent() == this) { diff --git a/indra/llui/llfocusmgr.cpp b/indra/llui/llfocusmgr.cpp index b8142216fc..60ddbc6cb3 100644 --- a/indra/llui/llfocusmgr.cpp +++ b/indra/llui/llfocusmgr.cpp @@ -38,6 +38,77 @@ const F32 FOCUS_FADE_TIME = 0.3f; +// NOTE: the LLFocusableElement implementation has been moved here from lluictrl.cpp. + +LLFocusableElement::LLFocusableElement() +: mFocusLostCallback(NULL), + mFocusReceivedCallback(NULL), + mFocusChangedCallback(NULL), + mTopLostCallback(NULL), + mFocusCallbackUserData(NULL) +{ +} + +// virtual +BOOL LLFocusableElement::handleKey(KEY key, MASK mask, BOOL called_from_parent) +{ + return FALSE; +} + +// virtual +BOOL LLFocusableElement::handleUnicodeChar(llwchar uni_char, BOOL called_from_parent) +{ + return FALSE; +} + +// virtual +LLFocusableElement::~LLFocusableElement() +{ +} + +void LLFocusableElement::onFocusReceived() +{ + if( mFocusReceivedCallback ) + { + mFocusReceivedCallback( this, mFocusCallbackUserData ); + } + if( mFocusChangedCallback ) + { + mFocusChangedCallback( this, mFocusCallbackUserData ); + } +} + +void LLFocusableElement::onFocusLost() +{ + if( mFocusLostCallback ) + { + mFocusLostCallback( this, mFocusCallbackUserData ); + } + + if( mFocusChangedCallback ) + { + mFocusChangedCallback( this, mFocusCallbackUserData ); + } +} + +void LLFocusableElement::onTopLost() +{ + if (mTopLostCallback) + { + mTopLostCallback(this, mFocusCallbackUserData); + } +} + +BOOL LLFocusableElement::hasFocus() const +{ + return gFocusMgr.getKeyboardFocus() == this; +} + +void LLFocusableElement::setFocus(BOOL b) +{ +} + + LLFocusMgr gFocusMgr; LLFocusMgr::LLFocusMgr() @@ -86,7 +157,7 @@ void LLFocusMgr::releaseFocusIfNeeded( const LLView* view ) } -void LLFocusMgr::setKeyboardFocus(LLUICtrl* new_focus, BOOL lock, BOOL keystrokes_only) +void LLFocusMgr::setKeyboardFocus(LLFocusableElement* new_focus, BOOL lock, BOOL keystrokes_only) { // notes if keyboard focus is changed again (by onFocusLost/onFocusReceived) // making the rest of our processing unnecessary since it will already be @@ -96,7 +167,9 @@ void LLFocusMgr::setKeyboardFocus(LLUICtrl* new_focus, BOOL lock, BOOL keystroke if (mLockedView && (new_focus == NULL || - (new_focus != mLockedView && !new_focus->hasAncestor(mLockedView)))) + (new_focus != mLockedView + && dynamic_cast<LLView*>(new_focus) + && !dynamic_cast<LLView*>(new_focus)->hasAncestor(mLockedView)))) { // don't allow focus to go to anything that is not the locked focus // or one of its descendants @@ -115,7 +188,7 @@ void LLFocusMgr::setKeyboardFocus(LLUICtrl* new_focus, BOOL lock, BOOL keystroke view_handle_list_t new_focus_list; // walk up the tree to root and add all views to the new_focus_list - for (LLView* ctrl = mKeyboardFocus; ctrl && ctrl != LLUI::getRootView(); ctrl = ctrl->getParent()) + for (LLView* ctrl = dynamic_cast<LLView*>(mKeyboardFocus); ctrl && ctrl != LLUI::getRootView(); ctrl = ctrl->getParent()) { if (ctrl) { @@ -167,7 +240,8 @@ void LLFocusMgr::setKeyboardFocus(LLUICtrl* new_focus, BOOL lock, BOOL keystroke } #ifdef _DEBUG - mKeyboardFocusName = new_focus ? new_focus->getName() : std::string("none"); + LLUICtrl* focus_ctrl = dynamic_cast<LLUICtrl*>(new_focus); + mKeyboardFocusName = focus_ctrl ? focus_ctrl->getName() : std::string("none"); #endif // If we've got a default keyboard focus, and the caller is @@ -177,8 +251,8 @@ void LLFocusMgr::setKeyboardFocus(LLUICtrl* new_focus, BOOL lock, BOOL keystroke mDefaultKeyboardFocus->setFocus(TRUE); } - LLView* focus_subtree = mKeyboardFocus; - LLView* viewp = mKeyboardFocus; + LLView* focus_subtree = dynamic_cast<LLView*>(mKeyboardFocus); + LLView* viewp = dynamic_cast<LLView*>(mKeyboardFocus); // find root-most focus root while(viewp) { @@ -192,7 +266,8 @@ void LLFocusMgr::setKeyboardFocus(LLUICtrl* new_focus, BOOL lock, BOOL keystroke if (focus_subtree) { - mFocusHistory[focus_subtree->getHandle()] = mKeyboardFocus ? mKeyboardFocus->getHandle() : LLHandle<LLView>(); + LLView* focused_view = dynamic_cast<LLView*>(mKeyboardFocus); + mFocusHistory[focus_subtree->getHandle()] = focused_view ? focused_view->getHandle() : LLHandle<LLView>(); } } @@ -208,7 +283,7 @@ void LLFocusMgr::setKeyboardFocus(LLUICtrl* new_focus, BOOL lock, BOOL keystroke // Returns TRUE is parent or any descedent of parent has keyboard focus. BOOL LLFocusMgr::childHasKeyboardFocus(const LLView* parent ) const { - LLView* focus_view = mKeyboardFocus; + LLView* focus_view = dynamic_cast<LLView*>(mKeyboardFocus); while( focus_view ) { if( focus_view == parent ) @@ -238,7 +313,7 @@ BOOL LLFocusMgr::childHasMouseCapture( const LLView* parent ) const return FALSE; } -void LLFocusMgr::removeKeyboardFocusWithoutCallback( const LLView* focus ) +void LLFocusMgr::removeKeyboardFocusWithoutCallback( const LLFocusableElement* focus ) { // should be ok to unlock here, as you have to know the locked view // in order to unlock it @@ -356,7 +431,7 @@ void LLFocusMgr::removeTopCtrlWithoutCallback( const LLUICtrl* top_view ) void LLFocusMgr::lockFocus() { - mLockedView = mKeyboardFocus; + mLockedView = dynamic_cast<LLUICtrl*>(mKeyboardFocus); } void LLFocusMgr::unlockFocus() diff --git a/indra/llui/llfocusmgr.h b/indra/llui/llfocusmgr.h index fbe0d22084..d0adadd6d3 100644 --- a/indra/llui/llfocusmgr.h +++ b/indra/llui/llfocusmgr.h @@ -37,10 +37,44 @@ #include "llstring.h" #include "llframetimer.h" -#include "llview.h" +#include "llui.h" class LLUICtrl; class LLMouseHandler; +class LLView; + +// NOTE: the LLFocusableElement class declaration has been moved here from lluictrl.h. +class LLFocusableElement +{ + friend class LLFocusMgr; // allow access to focus change handlers +public: + LLFocusableElement(); + virtual ~LLFocusableElement(); + + virtual void setFocus( BOOL b ); + virtual BOOL hasFocus() const; + + typedef boost::function<void(LLFocusableElement*, void*)> focus_callback_t; + void setFocusLostCallback(focus_callback_t cb, void* user_data = NULL) { mFocusLostCallback = cb; mFocusCallbackUserData = user_data; } + void setFocusReceivedCallback(focus_callback_t cb, void* user_data = NULL) { mFocusReceivedCallback = cb; mFocusCallbackUserData = user_data; } + void setFocusChangedCallback(focus_callback_t cb, void* user_data = NULL ) { mFocusChangedCallback = cb; mFocusCallbackUserData = user_data; } + void setTopLostCallback(focus_callback_t cb, void* user_data = NULL ) { mTopLostCallback = cb; mFocusCallbackUserData = user_data; } + + // These were brought up the hierarchy from LLView so that we don't have to use dynamic_cast when dealing with keyboard focus. + virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); + virtual BOOL handleUnicodeChar(llwchar uni_char, BOOL called_from_parent); + +protected: + virtual void onFocusReceived(); + virtual void onFocusLost(); + virtual void onTopLost(); // called when registered as top ctrl and user clicks elsewhere + focus_callback_t mFocusLostCallback; + focus_callback_t mFocusReceivedCallback; + focus_callback_t mFocusChangedCallback; + focus_callback_t mTopLostCallback; + void* mFocusCallbackUserData; +}; + class LLFocusMgr { @@ -55,11 +89,11 @@ public: BOOL childHasMouseCapture( const LLView* parent ) const; // Keyboard Focus - void setKeyboardFocus(LLUICtrl* new_focus, BOOL lock = FALSE, BOOL keystrokes_only = FALSE); // new_focus = NULL to release the focus. - LLUICtrl* getKeyboardFocus() const { return mKeyboardFocus; } - LLUICtrl* getLastKeyboardFocus() const { return mLastKeyboardFocus; } + void setKeyboardFocus(LLFocusableElement* new_focus, BOOL lock = FALSE, BOOL keystrokes_only = FALSE); // new_focus = NULL to release the focus. + LLFocusableElement* getKeyboardFocus() const { return mKeyboardFocus; } + LLFocusableElement* getLastKeyboardFocus() const { return mLastKeyboardFocus; } BOOL childHasKeyboardFocus( const LLView* parent ) const; - void removeKeyboardFocusWithoutCallback( const LLView* focus ); + void removeKeyboardFocusWithoutCallback( const LLFocusableElement* focus ); BOOL getKeystrokesOnly() { return mKeystrokesOnly; } void setKeystrokesOnly(BOOL keystrokes_only) { mKeystrokesOnly = keystrokes_only; } @@ -74,8 +108,8 @@ public: // If setKeyboardFocus(NULL) is called, and there is a non-NULL default // keyboard focus view, focus goes there. JC - void setDefaultKeyboardFocus(LLUICtrl* default_focus) { mDefaultKeyboardFocus = default_focus; } - LLUICtrl* getDefaultKeyboardFocus() const { return mDefaultKeyboardFocus; } + void setDefaultKeyboardFocus(LLFocusableElement* default_focus) { mDefaultKeyboardFocus = default_focus; } + LLFocusableElement* getDefaultKeyboardFocus() const { return mDefaultKeyboardFocus; } // Top View @@ -97,9 +131,9 @@ private: LLMouseHandler* mMouseCaptor; // Mouse events are premptively routed to this object // Keyboard Focus - LLUICtrl* mKeyboardFocus; // Keyboard events are preemptively routed to this object - LLUICtrl* mLastKeyboardFocus; // who last had focus - LLUICtrl* mDefaultKeyboardFocus; + LLFocusableElement* mKeyboardFocus; // Keyboard events are preemptively routed to this object + LLFocusableElement* mLastKeyboardFocus; // who last had focus + LLFocusableElement* mDefaultKeyboardFocus; BOOL mKeystrokesOnly; // caching list of keyboard focus ancestors for calling onFocusReceived and onFocusLost diff --git a/indra/llui/lliconctrl.cpp b/indra/llui/lliconctrl.cpp index 5c6ea663f3..673c742e7a 100644 --- a/indra/llui/lliconctrl.cpp +++ b/indra/llui/lliconctrl.cpp @@ -80,6 +80,14 @@ void LLIconCtrl::draw() LLUICtrl::draw(); } +// virtual +void LLIconCtrl::setAlpha(F32 alpha) +{ + LLColor4 temp = mColor.get(); + temp.setAlpha(alpha); + mColor.set(temp); +} + // virtual // value might be a string or a UUID void LLIconCtrl::setValue(const LLSD& value ) diff --git a/indra/llui/lliconctrl.h b/indra/llui/lliconctrl.h index ff25b0d53e..aceb70b9d5 100644 --- a/indra/llui/lliconctrl.h +++ b/indra/llui/lliconctrl.h @@ -71,6 +71,8 @@ public: std::string getImageName() const; + /*virtual*/ void setAlpha(F32 alpha); + void setColor(const LLColor4& color) { mColor = color; } private: diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 6926415c6d..5435b9ffbf 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -37,7 +37,6 @@ #include "lllineeditor.h" #include "lltexteditor.h" -#include "audioengine.h" #include "llmath.h" #include "llfontgl.h" #include "llgl.h" diff --git a/indra/llui/llmultisliderctrl.cpp b/indra/llui/llmultisliderctrl.cpp index 1523d5d527..01a3b5fdc7 100644 --- a/indra/llui/llmultisliderctrl.cpp +++ b/indra/llui/llmultisliderctrl.cpp @@ -34,9 +34,6 @@ #include "llmultisliderctrl.h" -#include "audioengine.h" -#include "sound_ids.h" - #include "llmath.h" #include "llfontgl.h" #include "llgl.h" diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp index 667a3e10c4..0b987bfcb5 100644 --- a/indra/llui/llpanel.cpp +++ b/indra/llui/llpanel.cpp @@ -190,14 +190,19 @@ void LLPanel::draw() LLView::draw(); } +/*virtual*/ +void LLPanel::setAlpha(F32 alpha) +{ + mBgColorOpaque.setAlpha(alpha); +} + void LLPanel::updateDefaultBtn() { if( mDefaultBtn) { if (gFocusMgr.childHasKeyboardFocus( this ) && mDefaultBtn->getEnabled()) { - LLUICtrl* focus_ctrl = gFocusMgr.getKeyboardFocus(); - LLButton* buttonp = dynamic_cast<LLButton*>(focus_ctrl); + LLButton* buttonp = dynamic_cast<LLButton*>(gFocusMgr.getKeyboardFocus()); BOOL focus_is_child_button = buttonp && buttonp->getCommitOnReturn(); // only enable default button when current focus is not a return-capturing button mDefaultBtn->setBorderEnabled(!focus_is_child_button); @@ -245,7 +250,7 @@ BOOL LLPanel::handleKeyHere( KEY key, MASK mask ) { BOOL handled = FALSE; - LLUICtrl* cur_focus = gFocusMgr.getKeyboardFocus(); + LLUICtrl* cur_focus = dynamic_cast<LLUICtrl*>(gFocusMgr.getKeyboardFocus()); // handle user hitting ESC to defocus if (key == KEY_ESCAPE) @@ -696,6 +701,14 @@ void LLPanel::childSetColor(const std::string& id, const LLColor4& color) child->setColor(color); } } +void LLPanel::childSetAlpha(const std::string& id, F32 alpha) +{ + LLUICtrl* child = getChild<LLUICtrl>(id, true); + if (child) + { + child->setAlpha(alpha); + } +} LLCtrlSelectionInterface* LLPanel::childGetSelectionInterface(const std::string& id) const { diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h index 3ee11483c3..28cd4d2799 100644 --- a/indra/llui/llpanel.h +++ b/indra/llui/llpanel.h @@ -117,6 +117,8 @@ public: // From LLFocusableElement /*virtual*/ void setFocus( BOOL b ); + virtual void setAlpha(F32 alpha); + // New virtuals virtual void refresh(); // called in setFocus() @@ -145,6 +147,7 @@ public: void setCtrlsEnabled(BOOL b); + LLHandle<LLPanel> getHandle() const { return mPanelHandle; } const LLCallbackMap::map_t& getFactoryMap() const { return mFactoryMap; } @@ -186,6 +189,7 @@ public: void childSetValidate(const std::string& id, boost::function<bool (const LLSD& data)> cb ); void childSetColor(const std::string& id, const LLColor4& color); + void childSetAlpha(const std::string& id, F32 alpha); LLCtrlSelectionInterface* childGetSelectionInterface(const std::string& id) const; LLCtrlListInterface* childGetListInterface(const std::string& id) const; @@ -239,7 +243,7 @@ protected: CommitCallbackRegistry::ScopedRegistrar mCommitCallbackRegistrar; EnableCallbackRegistry::ScopedRegistrar mEnableCallbackRegistrar; - commit_signal_t mVisibleSignal; // Called when visibilit changes, passes new visibility as LLSD() + commit_signal_t mVisibleSignal; // Called when visibility changes, passes new visibility as LLSD() private: // Unified error reporting for the child* functions diff --git a/indra/llui/llsliderctrl.cpp b/indra/llui/llsliderctrl.cpp index 12953fc261..15584c8dc7 100644 --- a/indra/llui/llsliderctrl.cpp +++ b/indra/llui/llsliderctrl.cpp @@ -34,8 +34,6 @@ #include "llsliderctrl.h" -#include "audioengine.h" - #include "llmath.h" #include "llfontgl.h" #include "llgl.h" diff --git a/indra/llui/llspinctrl.cpp b/indra/llui/llspinctrl.cpp index 2d70432182..7b96446fa1 100644 --- a/indra/llui/llspinctrl.cpp +++ b/indra/llui/llspinctrl.cpp @@ -45,7 +45,6 @@ #include "lltextbox.h" #include "llkeyboard.h" #include "llmath.h" -#include "audioengine.h" #include "llcontrol.h" #include "llfocusmgr.h" #include "llresmgr.h" diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index a97899f270..921041d17f 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -46,7 +46,6 @@ #include "lltimer.h" #include "llmath.h" -#include "audioengine.h" #include "llclipboard.h" #include "llscrollbar.h" #include "llstl.h" @@ -60,6 +59,7 @@ #include "lltextparser.h" #include "llscrollcontainer.h" #include "llpanel.h" + #include <queue> #include "llcombobox.h" diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp index 7e4df892c4..1d62ed93f9 100644 --- a/indra/llui/llui.cpp +++ b/indra/llui/llui.cpp @@ -38,7 +38,6 @@ #include <map> // Linden library includes -#include "audioengine.h" #include "v2math.h" #include "v4color.h" #include "llrender.h" diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index 4a9fec3191..7ff942268d 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -54,61 +54,7 @@ LLUICtrl::Params::Params() addSynonym(initial_value, "initial_value"); } -LLFocusableElement::LLFocusableElement() -: mFocusLostCallback(NULL), - mFocusReceivedCallback(NULL), - mFocusChangedCallback(NULL), - mTopLostCallback(NULL), - mFocusCallbackUserData(NULL) -{ -} - -//virtual -LLFocusableElement::~LLFocusableElement() -{ -} - -void LLFocusableElement::onFocusReceived() -{ - if( mFocusReceivedCallback ) - { - mFocusReceivedCallback( this, mFocusCallbackUserData ); - } - if( mFocusChangedCallback ) - { - mFocusChangedCallback( this, mFocusCallbackUserData ); - } -} - -void LLFocusableElement::onFocusLost() -{ - if( mFocusLostCallback ) - { - mFocusLostCallback( this, mFocusCallbackUserData ); - } - - if( mFocusChangedCallback ) - { - mFocusChangedCallback( this, mFocusCallbackUserData ); - } -} - -void LLFocusableElement::onTopLost() -{ - if (mTopLostCallback) - { - mTopLostCallback(this, mFocusCallbackUserData); - } -} - -BOOL LLFocusableElement::hasFocus() const -{ - return FALSE; -} - -void LLFocusableElement::setFocus(BOOL b) -{ -} +// NOTE: the LLFocusableElement implementation has been moved from here to llfocusmgr.cpp. //static const LLUICtrl::Params& LLUICtrl::getDefaultParams() @@ -560,7 +506,7 @@ void LLUICtrl::onFocusReceived() // find first view in hierarchy above new focus that is a LLUICtrl LLView* viewp = getParent(); - LLUICtrl* last_focus = gFocusMgr.getLastKeyboardFocus(); + LLUICtrl* last_focus = dynamic_cast<LLUICtrl*>(gFocusMgr.getLastKeyboardFocus()); while (viewp && !viewp->isCtrl()) { @@ -886,6 +832,9 @@ BOOL LLUICtrl::getTentative() const void LLUICtrl::setColor(const LLColor4& color) { } +// virtual +void LLUICtrl::setAlpha(F32 alpha) +{ } namespace LLInitParam diff --git a/indra/llui/lluictrl.h b/indra/llui/lluictrl.h index a4f539af14..3e2e1f41a1 100644 --- a/indra/llui/lluictrl.h +++ b/indra/llui/lluictrl.h @@ -46,35 +46,10 @@ const BOOL TAKE_FOCUS_YES = TRUE; const BOOL TAKE_FOCUS_NO = FALSE; -class LLFocusableElement -{ - friend class LLFocusMgr; // allow access to focus change handlers -public: - LLFocusableElement(); - virtual ~LLFocusableElement(); - - virtual void setFocus( BOOL b ); - virtual BOOL hasFocus() const; - - typedef boost::function<void(LLFocusableElement*, void*)> focus_callback_t; - void setFocusLostCallback(focus_callback_t cb, void* user_data = NULL) { mFocusLostCallback = cb; mFocusCallbackUserData = user_data; } - void setFocusReceivedCallback(focus_callback_t cb, void* user_data = NULL) { mFocusReceivedCallback = cb; mFocusCallbackUserData = user_data; } - void setFocusChangedCallback(focus_callback_t cb, void* user_data = NULL ) { mFocusChangedCallback = cb; mFocusCallbackUserData = user_data; } - void setTopLostCallback(focus_callback_t cb, void* user_data = NULL ) { mTopLostCallback = cb; mFocusCallbackUserData = user_data; } - -protected: - virtual void onFocusReceived(); - virtual void onFocusLost(); - virtual void onTopLost(); // called when registered as top ctrl and user clicks elsewhere - focus_callback_t mFocusLostCallback; - focus_callback_t mFocusReceivedCallback; - focus_callback_t mFocusChangedCallback; - focus_callback_t mTopLostCallback; - void* mFocusCallbackUserData; -}; +// NOTE: the LLFocusableElement class declaration has been moved from here to llfocusmgr.h. class LLUICtrl - : public LLView, public LLFocusableElement, public boost::signals2::trackable + : public LLView, public boost::signals2::trackable { public: @@ -237,6 +212,7 @@ public: virtual void onTabInto(); virtual void clear(); virtual void setColor(const LLColor4& color); + virtual void setAlpha(F32 alpha); BOOL focusNextItem(BOOL text_entry_only); BOOL focusPrevItem(BOOL text_entry_only); @@ -266,7 +242,7 @@ public: // *TODO: Deprecate; for backwards compatability only: boost::signals2::connection setCommitCallback( boost::function<void (LLUICtrl*,void*)> cb, void* data); boost::signals2::connection setValidateBeforeCommit( boost::function<bool (const LLSD& data)> cb ); - + LLUICtrl* findRootMostFocusRoot(); class LLTextInputFilter : public LLQueryFilter, public LLSingleton<LLTextInputFilter> diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index e2dc85d03f..4770807ac7 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -145,12 +145,6 @@ LLView::~LLView() // llassert_always(sDepth == 0); // avoid deleting views while drawing! It can subtly break list iterators - if( gFocusMgr.getKeyboardFocus() == this ) - { - //llwarns << "View holding keyboard focus deleted: " << getName() << ". Keyboard focus removed." << llendl; - gFocusMgr.removeKeyboardFocusWithoutCallback( this ); - } - if( hasMouseCapture() ) { //llwarns << "View holding mouse capture deleted: " << getName() << ". Mouse capture removed." << llendl; diff --git a/indra/llui/llview.h b/indra/llui/llview.h index 6247bf036c..ecc6bf47da 100644 --- a/indra/llui/llview.h +++ b/indra/llui/llview.h @@ -54,6 +54,7 @@ #include "llcursortypes.h" #include "lluictrlfactory.h" #include "lltreeiterators.h" +#include "llfocusmgr.h" #include <list> @@ -141,7 +142,7 @@ virtual BOOL handleUnicodeCharHere(llwchar uni_char); class LLViewWidgetRegistry : public LLChildRegistry<LLViewWidgetRegistry> {}; -class LLView : public LLMouseHandler, public LLMortician +class LLView : public LLMouseHandler, public LLMortician, public LLFocusableElement { public: struct Follows : public LLInitParam::Choice<Follows> @@ -413,8 +414,10 @@ public: virtual BOOL canSnapTo(const LLView* other_view); virtual void setSnappedTo(const LLView* snap_view); - virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); - virtual BOOL handleUnicodeChar(llwchar uni_char, BOOL called_from_parent); + // inherited from LLFocusableElement + /* virtual */ BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); + /* virtual */ BOOL handleUnicodeChar(llwchar uni_char, BOOL called_from_parent); + virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, EDragAndDropType cargo_type, void* cargo_data, @@ -432,8 +435,9 @@ public: BOOL getSaveToXML() const { return mSaveToXML; } void setSaveToXML(BOOL b) { mSaveToXML = b; } - virtual void onFocusLost(); - virtual void onFocusReceived(); + // inherited from LLFocusableElement + /* virtual */ void onFocusLost(); + /* virtual */ void onFocusReceived(); typedef enum e_hit_test_type { diff --git a/indra/llvfs/lldir.cpp b/indra/llvfs/lldir.cpp index 0c65047d4d..52ef1baabf 100644 --- a/indra/llvfs/lldir.cpp +++ b/indra/llvfs/lldir.cpp @@ -296,6 +296,10 @@ const std::string LLDir::getSkinBaseDir() const return mSkinBaseDir; } +const std::string &LLDir::getLLPluginDir() const +{ + return mLLPluginDir; +} std::string LLDir::getExpandedFilename(ELLPath location, const std::string& filename) const { diff --git a/indra/llvfs/lldir.h b/indra/llvfs/lldir.h index a1f9df007d..6c9fea6b6a 100644 --- a/indra/llvfs/lldir.h +++ b/indra/llvfs/lldir.h @@ -88,6 +88,10 @@ class LLDir const std::string findFile(const std::string& filename, const std::vector<std::string> filenames) const; const std::string findFile(const std::string& filename, const std::string& searchPath1 = "", const std::string& searchPath2 = "", const std::string& searchPath3 = "") const; + + virtual std::string getLLPluginLauncher() = 0; // full path and name for the plugin shell + virtual std::string getLLPluginFilename(std::string base_name) = 0; // full path and name to the plugin DSO for this base_name (i.e. 'FOO' -> '/bar/baz/libFOO.so') + const std::string &getExecutablePathAndName() const; // Full pathname of the executable const std::string &getAppName() const; // install directory under progams/ ie "SecondLife" const std::string &getExecutableDir() const; // Directory where the executable is located @@ -108,6 +112,7 @@ class LLDir const std::string &getUserSkinDir() const; // User-specified skin folder with user modifications. e.g. c:\documents and settings\username\application data\second life\skins\curskin const std::string &getDefaultSkinDir() const; // folder for default skin. e.g. c:\program files\second life\skins\default const std::string getSkinBaseDir() const; // folder that contains all installed skins (not user modifications). e.g. c:\program files\second life\skins + const std::string &getLLPluginDir() const; // Directory containing plugins and plugin shell // Expanded filename std::string getExpandedFilename(ELLPath location, const std::string &filename) const; @@ -165,6 +170,7 @@ protected: std::string mSkinDir; // Location for current skin info. std::string mDefaultSkinDir; // Location for default skin info. std::string mUserSkinDir; // Location for user-modified skin info. + std::string mLLPluginDir; // Location for plugins and plugin shell }; void dir_exists_or_crash(const std::string &dir_name); diff --git a/indra/llvfs/lldir_linux.cpp b/indra/llvfs/lldir_linux.cpp index d7a0dc8528..24efcb8ae8 100644 --- a/indra/llvfs/lldir_linux.cpp +++ b/indra/llvfs/lldir_linux.cpp @@ -129,6 +129,8 @@ LLDir_Linux::LLDir_Linux() } } + mLLPluginDir = mExecutableDir + mDirDelimiter + "llplugin"; + // *TODO: don't use /tmp, use $HOME/.secondlife/tmp or something. mTempDir = "/tmp"; } @@ -378,3 +380,15 @@ BOOL LLDir_Linux::fileExists(const std::string &filename) const } } + +/*virtual*/ std::string LLDir_Linux::getLLPluginLauncher() +{ + return gDirUtilp->getLLPluginDir() + gDirUtilp->getDirDelimiter() + + "SLPlugin"; +} + +/*virtual*/ std::string LLDir_Linux::getLLPluginFilename(std::string base_name) +{ + return gDirUtilp->getLLPluginDir() + gDirUtilp->getDirDelimiter() + + "lib" + base_name + ".so"; +} diff --git a/indra/llvfs/lldir_linux.h b/indra/llvfs/lldir_linux.h index a78a9854ff..1fec15c5c6 100644 --- a/indra/llvfs/lldir_linux.h +++ b/indra/llvfs/lldir_linux.h @@ -53,6 +53,9 @@ public: virtual void getRandomFileInDir(const std::string &dirname, const std::string &mask, std::string &fname); /*virtual*/ BOOL fileExists(const std::string &filename) const; + /*virtual*/ std::string getLLPluginLauncher(); + /*virtual*/ std::string getLLPluginFilename(std::string base_name); + private: DIR *mDirp; int mCurrentDirIndex; diff --git a/indra/llvfs/lldir_mac.cpp b/indra/llvfs/lldir_mac.cpp index 7f703464bc..9be787df11 100644 --- a/indra/llvfs/lldir_mac.cpp +++ b/indra/llvfs/lldir_mac.cpp @@ -143,14 +143,18 @@ LLDir_Mac::LLDir_Mac() // mAppRODataDir - CFURLRef resourcesURLRef = CFBundleCopyResourcesDirectoryURL(mainBundleRef); - CFURLRefToLLString(resourcesURLRef, mAppRODataDir, true); // *NOTE: When running in a dev tree, use the copy of // skins in indra/newview/ rather than in the application bundle. This // mirrors Windows dev environment behavior and allows direct checkin // of edited skins/xui files. JC + + // MBW -- This keeps the mac application from finding other things. + // If this is really for skins, it should JUST apply to skins. + CFURLRef resourcesURLRef = CFBundleCopyResourcesDirectoryURL(mainBundleRef); + CFURLRefToLLString(resourcesURLRef, mAppRODataDir, true); + U32 indra_pos = mExecutableDir.find("/indra"); if (indra_pos != std::string::npos) { @@ -211,6 +215,8 @@ LLDir_Mac::LLDir_Mac() } mWorkingDir = getCurPath(); + + mLLPluginDir = mAppRODataDir + mDirDelimiter + "llplugin"; CFRelease(executableURLRef); executableURLRef = NULL; @@ -416,4 +422,17 @@ BOOL LLDir_Mac::fileExists(const std::string &filename) const } +/*virtual*/ std::string LLDir_Mac::getLLPluginLauncher() +{ + return gDirUtilp->getLLPluginDir() + gDirUtilp->getDirDelimiter() + + "SLPlugin"; +} + +/*virtual*/ std::string LLDir_Mac::getLLPluginFilename(std::string base_name) +{ + return gDirUtilp->getLLPluginDir() + gDirUtilp->getDirDelimiter() + + base_name + ".dylib"; +} + + #endif // LL_DARWIN diff --git a/indra/llvfs/lldir_mac.h b/indra/llvfs/lldir_mac.h index 82ac94ed4c..834acc7262 100644 --- a/indra/llvfs/lldir_mac.h +++ b/indra/llvfs/lldir_mac.h @@ -53,6 +53,9 @@ public: virtual void getRandomFileInDir(const std::string &dirname, const std::string &ask, std::string &fname); virtual BOOL fileExists(const std::string &filename) const; + /*virtual*/ std::string getLLPluginLauncher(); + /*virtual*/ std::string getLLPluginFilename(std::string base_name); + private: int mCurrentDirIndex; int mCurrentDirCount; diff --git a/indra/llvfs/lldir_solaris.cpp b/indra/llvfs/lldir_solaris.cpp index ba71bc0eb4..a21f3ca0ab 100644 --- a/indra/llvfs/lldir_solaris.cpp +++ b/indra/llvfs/lldir_solaris.cpp @@ -161,6 +161,8 @@ LLDir_Solaris::LLDir_Solaris() } } + mLLPluginDir = mExecutableDir + mDirDelimiter + "llplugin"; + // *TODO: don't use /tmp, use $HOME/.secondlife/tmp or something. mTempDir = "/tmp"; } diff --git a/indra/llvfs/lldir_win32.cpp b/indra/llvfs/lldir_win32.cpp index da80a95922..872f2cf1c1 100644 --- a/indra/llvfs/lldir_win32.cpp +++ b/indra/llvfs/lldir_win32.cpp @@ -144,6 +144,8 @@ LLDir_Win32::LLDir_Win32() llwarns << "Couldn't create LL_PATH_CACHE dir " << mDefaultCacheDir << llendl; } } + + mLLPluginDir = mExecutableDir + mDirDelimiter + "llplugin"; } LLDir_Win32::~LLDir_Win32() @@ -393,6 +395,19 @@ BOOL LLDir_Win32::fileExists(const std::string &filename) const } +/*virtual*/ std::string LLDir_Win32::getLLPluginLauncher() +{ + return gDirUtilp->getLLPluginDir() + gDirUtilp->getDirDelimiter() + + "SLPlugin.exe"; +} + +/*virtual*/ std::string LLDir_Win32::getLLPluginFilename(std::string base_name) +{ + return gDirUtilp->getLLPluginDir() + gDirUtilp->getDirDelimiter() + + base_name + ".dll"; +} + + #if 0 // Utility function to get version number of a DLL diff --git a/indra/llvfs/lldir_win32.h b/indra/llvfs/lldir_win32.h index d2497901e4..c2acfa6bd4 100644 --- a/indra/llvfs/lldir_win32.h +++ b/indra/llvfs/lldir_win32.h @@ -50,6 +50,9 @@ public: /*virtual*/ void getRandomFileInDir(const std::string &dirname, const std::string &mask, std::string &fname); /*virtual*/ BOOL fileExists(const std::string &filename) const; + /*virtual*/ std::string getLLPluginLauncher(); + /*virtual*/ std::string getLLPluginFilename(std::string base_name); + private: BOOL LLDir_Win32::getNextFileInDir(const llutf16string &dirname, const std::string &mask, std::string &fname, BOOL wrap); diff --git a/indra/llwindow/CMakeLists.txt b/indra/llwindow/CMakeLists.txt index beaf5c3488..7b1cab696f 100644 --- a/indra/llwindow/CMakeLists.txt +++ b/indra/llwindow/CMakeLists.txt @@ -19,7 +19,6 @@ include(LLRender) include(LLVFS) include(LLWindow) include(LLXML) -include(Mozlib) include(UI) include_directories( diff --git a/indra/media_plugins/CMakeLists.txt b/indra/media_plugins/CMakeLists.txt new file mode 100644 index 0000000000..09f8acdb52 --- /dev/null +++ b/indra/media_plugins/CMakeLists.txt @@ -0,0 +1,16 @@ +# -*- cmake -*- + +add_subdirectory(base) + +add_subdirectory(webkit) + +add_subdirectory(gstreamer010) + +if (WINDOWS OR DARWIN) + add_subdirectory(quicktime) + add_subdirectory(awesomium) +endif (WINDOWS OR DARWIN) + +if (WINDOWS) + add_subdirectory(flash_activex) +endif(WINDOWS) diff --git a/indra/media_plugins/base/CMakeLists.txt b/indra/media_plugins/base/CMakeLists.txt new file mode 100644 index 0000000000..f8d2dabc6c --- /dev/null +++ b/indra/media_plugins/base/CMakeLists.txt @@ -0,0 +1,41 @@ +# -*- cmake -*- + +project(media_plugin_base) + +include(00-Common) +include(LLCommon) +include(LLImage) +include(LLPlugin) +include(LLMath) +include(LLRender) +include(LLWindow) +include(Linking) +include(PluginAPI) +include(FindOpenGL) + +include_directories( + ${LLPLUGIN_INCLUDE_DIRS} + ${LLCOMMON_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} + ${LLIMAGE_INCLUDE_DIRS} + ${LLRENDER_INCLUDE_DIRS} + ${LLWINDOW_INCLUDE_DIRS} +) + + +### media_plugin_base + +set(media_plugin_base_SOURCE_FILES + media_plugin_base.cpp +) + +set(media_plugin_base_HEADER_FILES + CMakeLists.txt + + media_plugin_base.h +) + +add_library(media_plugin_base + ${media_plugin_base_SOURCE_FILES} +) + diff --git a/indra/media_plugins/base/media_plugin_base.cpp b/indra/media_plugins/base/media_plugin_base.cpp new file mode 100644 index 0000000000..0b7092fad6 --- /dev/null +++ b/indra/media_plugins/base/media_plugin_base.cpp @@ -0,0 +1,154 @@ +/** + * @file media_plugin_base.cpp + * @brief Media plugin base class for LLMedia API plugin system + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "media_plugin_base.h" + + +// TODO: Make sure that the only symbol exported from this library is LLPluginInitEntryPoint +//////////////////////////////////////////////////////////////////////////////// +// + +MediaPluginBase::MediaPluginBase( + LLPluginInstance::sendMessageFunction host_send_func, + void *host_user_data ) +{ + mHostSendFunction = host_send_func; + mHostUserData = host_user_data; + mDeleteMe = false; + mPixels = 0; + mWidth = 0; + mHeight = 0; + mTextureWidth = 0; + mTextureHeight = 0; + mDepth = 0; + mStatus = STATUS_NONE; +} + +std::string MediaPluginBase::statusString() +{ + std::string result; + + switch(mStatus) + { + case STATUS_LOADING: result = "loading"; break; + case STATUS_LOADED: result = "loaded"; break; + case STATUS_ERROR: result = "error"; break; + case STATUS_PLAYING: result = "playing"; break; + case STATUS_PAUSED: result = "paused"; break; + default: + // keep the empty string + break; + } + + return result; +} + +void MediaPluginBase::setStatus(EStatus status) +{ + if(mStatus != status) + { + mStatus = status; + sendStatus(); + } +} + + +void MediaPluginBase::staticReceiveMessage(const char *message_string, void **user_data) +{ + MediaPluginBase *self = (MediaPluginBase*)*user_data; + + if(self != NULL) + { + self->receiveMessage(message_string); + + // If the plugin has processed the delete message, delete it. + if(self->mDeleteMe) + { + delete self; + *user_data = NULL; + } + } +} + +void MediaPluginBase::sendMessage(const LLPluginMessage &message) +{ + std::string output = message.generate(); + mHostSendFunction(output.c_str(), &mHostUserData); +} + +void MediaPluginBase::setDirty(int left, int top, int right, int bottom) +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "updated"); + + message.setValueS32("left", left); + message.setValueS32("top", top); + message.setValueS32("right", right); + message.setValueS32("bottom", bottom); + + sendMessage(message); +} + +void MediaPluginBase::sendStatus() +{ + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "media_status"); + + message.setValue("status", statusString()); + + sendMessage(message); +} + + +#if LL_WINDOWS +# define LLSYMEXPORT __declspec(dllexport) +#elif LL_LINUX +# define LLSYMEXPORT __attribute__ ((visibility("default"))) +#else +# define LLSYMEXPORT /**/ +#endif + +extern "C" +{ + LLSYMEXPORT int LLPluginInitEntryPoint(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data); +} + +LLSYMEXPORT int +LLPluginInitEntryPoint(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data) +{ + return init_media_plugin(host_send_func, host_user_data, plugin_send_func, plugin_user_data); +} + +#ifdef WIN32 +int WINAPI DllEntryPoint( HINSTANCE hInstance, unsigned long reason, void* params ) +{ + return 1; +} +#endif diff --git a/indra/media_plugins/base/media_plugin_base.exp b/indra/media_plugins/base/media_plugin_base.exp new file mode 100644 index 0000000000..d7a945a1c5 --- /dev/null +++ b/indra/media_plugins/base/media_plugin_base.exp @@ -0,0 +1 @@ +_LLPluginInitEntryPoint
\ No newline at end of file diff --git a/indra/media_plugins/base/media_plugin_base.h b/indra/media_plugins/base/media_plugin_base.h new file mode 100644 index 0000000000..8f600cb8d6 --- /dev/null +++ b/indra/media_plugins/base/media_plugin_base.h @@ -0,0 +1,111 @@ +/** + * @file media_plugin_base.h + * @brief Media plugin base class for LLMedia API plugin system + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llplugininstance.h" +#include "llpluginmessage.h" +#include "llpluginmessageclasses.h" + + +class MediaPluginBase +{ +public: + MediaPluginBase(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data); + virtual ~MediaPluginBase() {} + + virtual void receiveMessage(const char *message_string) = 0; + + static void staticReceiveMessage(const char *message_string, void **user_data); + +protected: + + typedef enum + { + STATUS_NONE, + STATUS_LOADING, + STATUS_LOADED, + STATUS_ERROR, + STATUS_PLAYING, + STATUS_PAUSED, + } EStatus; + + class SharedSegmentInfo + { + public: + void *mAddress; + size_t mSize; + }; + + void sendMessage(const LLPluginMessage &message); + void sendStatus(); + std::string statusString(); + void setStatus(EStatus status); + + // The quicktime plugin overrides this to add current time and duration to the message... + virtual void setDirty(int left, int top, int right, int bottom); + + typedef std::map<std::string, SharedSegmentInfo> SharedSegmentMap; + + + LLPluginInstance::sendMessageFunction mHostSendFunction; + void *mHostUserData; + bool mDeleteMe; + unsigned char* mPixels; + std::string mTextureSegmentName; + int mWidth; + int mHeight; + int mTextureWidth; + int mTextureHeight; + int mDepth; + EStatus mStatus; + SharedSegmentMap mSharedSegments; + +}; + +// The plugin must define this function to create its instance. +int init_media_plugin( + LLPluginInstance::sendMessageFunction host_send_func, + void *host_user_data, + LLPluginInstance::sendMessageFunction *plugin_send_func, + void **plugin_user_data); + +// It should look something like this: +/* +{ + MediaPluginFoo *self = new MediaPluginFoo(host_send_func, host_user_data); + *plugin_send_func = MediaPluginFoo::staticReceiveMessage; + *plugin_user_data = (void*)self; + + return 0; +} +*/ + diff --git a/indra/media_plugins/gstreamer010/CMakeLists.txt b/indra/media_plugins/gstreamer010/CMakeLists.txt new file mode 100644 index 0000000000..5c0ce3ee17 --- /dev/null +++ b/indra/media_plugins/gstreamer010/CMakeLists.txt @@ -0,0 +1,71 @@ +# -*- cmake -*- + +project(media_plugin_gstreamer010) + +include(00-Common) +include(LLCommon) +include(LLImage) +include(LLPlugin) +include(LLMath) +include(LLRender) +include(LLWindow) +include(Linking) +include(PluginAPI) +include(MediaPluginBase) +include(FindOpenGL) + +include(GStreamer010Plugin) + +include_directories( + ${LLPLUGIN_INCLUDE_DIRS} + ${MEDIA_PLUGIN_BASE_INCLUDE_DIRS} + ${LLCOMMON_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} + ${LLIMAGE_INCLUDE_DIRS} + ${LLRENDER_INCLUDE_DIRS} + ${LLWINDOW_INCLUDE_DIRS} + ${GSTREAMER010_INCLUDE_DIRS} + ${GSTREAMER010_PLUGINS_BASE_INCLUDE_DIRS} +) + +### media_plugin_gstreamer010 + +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 + ) + +if (${CXX_VERSION} MATCHES "4.[23]") + # Work around a bad interaction between broken gstreamer headers and + # g++ 4.3's increased strictness. + set_source_files_properties(llmediaimplgstreamervidplug.cpp PROPERTIES + COMPILE_FLAGS -Wno-error=write-strings) +endif (${CXX_VERSION} MATCHES "4.[23]") + +add_library(media_plugin_gstreamer010 + SHARED + ${media_plugin_gstreamer010_SOURCE_FILES} +) + +target_link_libraries(media_plugin_gstreamer010 + ${LLPLUGIN_LIBRARIES} + ${MEDIA_PLUGIN_BASE_LIBRARIES} + ${LLCOMMON_LIBRARIES} + ${PLUGIN_API_WINDOWS_LIBRARIES} + ${GSTREAMER010_LIBRARIES} +) + +add_dependencies(media_plugin_gstreamer010 + ${LLPLUGIN_LIBRARIES} + ${MEDIA_PLUGIN_BASE_LIBRARIES} + ${LLCOMMON_LIBRARIES} +) + + diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamer.h b/indra/media_plugins/gstreamer010/llmediaimplgstreamer.h new file mode 100644 index 0000000000..ef41736c18 --- /dev/null +++ b/indra/media_plugins/gstreamer010/llmediaimplgstreamer.h @@ -0,0 +1,57 @@ +/** + * @file llmediaimplgstreamer.h + * @author Tofu Linden + * @brief implementation that supports media playback via GStreamer. + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +// header guard +#ifndef llmediaimplgstreamer_h +#define llmediaimplgstreamer_h + +#if LL_GSTREAMER010_ENABLED + +extern "C" { +#include <stdio.h> +#include <gst/gst.h> + +#include "apr_pools.h" +#include "apr_dso.h" +} + + +extern "C" { +gboolean llmediaimplgstreamer_bus_callback (GstBus *bus, + GstMessage *message, + gpointer data); +} + +#endif // LL_GSTREAMER010_ENABLED + +#endif // llmediaimplgstreamer_h diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.cpp b/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.cpp new file mode 100644 index 0000000000..cc52232496 --- /dev/null +++ b/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.cpp @@ -0,0 +1,171 @@ +/** + * @file llmediaimplgstreamer_syms.cpp + * @brief dynamic GStreamer symbol-grabbing code + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#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 new file mode 100644 index 0000000000..ee7473d6d1 --- /dev/null +++ b/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms.h @@ -0,0 +1,78 @@ +/** + * @file llmediaimplgstreamer_syms.h + * @brief dynamic GStreamer symbol-grabbing code + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#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 new file mode 100644 index 0000000000..b33e59363d --- /dev/null +++ b/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms_raw.inc @@ -0,0 +1,51 @@ + +// 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 new file mode 100644 index 0000000000..14fbcb48b9 --- /dev/null +++ b/indra/media_plugins/gstreamer010/llmediaimplgstreamer_syms_rawv.inc @@ -0,0 +1,5 @@ + +// 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 new file mode 100644 index 0000000000..e31d4a3282 --- /dev/null +++ b/indra/media_plugins/gstreamer010/llmediaimplgstreamertriviallogging.h @@ -0,0 +1,53 @@ +/** + * @file llmediaimplgstreamertriviallogging.h + * @brief minimal logging utilities. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef __LLMEDIAIMPLGSTREAMERTRIVIALLOGGING_H__ +#define __LLMEDIAIMPLGSTREAMERTRIVIALLOGGING_H__ + +#include <cstdio> + +///////////////////////////////////////////////////////////////////////// +// Debug/Info/Warning macros. +#define MSGMODULEFOO "(media plugin)" +#define STDERRMSG(...) do{\ + 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 new file mode 100644 index 0000000000..d8ccfaa702 --- /dev/null +++ b/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.cpp @@ -0,0 +1,532 @@ +/** + * @file llmediaimplgstreamervidplug.h + * @brief Video-consuming static GStreamer plugin for gst-to-LLMediaImpl + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#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 ( + "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); + +#if 0 + fprintf(stderr, "\n\ntransferring a frame of %dx%d <- %p (%d)\n\n", + slvideo->width, slvideo->height, GST_BUFFER_DATA(buf), + slvideo->format); +#endif + 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) +{ + GstSLVideo *slvideo; + gboolean ret = TRUE; + + slvideo = 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) + { + 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); + } + } + } + + 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); + } + + GST_OBJECT_UNLOCK(slvideo); + + *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 = 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("\n\n\nPLUGIN INIT\n\n\n"); + + 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. + */ +void gst_slvideo_init_class (void) +{ +#define PACKAGE "packagehack" + // this macro quietly refers to PACKAGE internally + static GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "private-slvideoplugin", + "SL Video sink plugin", + plugin_init, "0.1", GST_LICENSE_UNKNOWN, + "Second Life", + "http://www.secondlife.com/"); +#undef PACKAGE + ll_gst_plugin_register_static (&gst_plugin_desc); + DEBUGMSG(stderr, "\n\n\nCLASS INIT\n\n\n"); +} + +#endif // LL_GSTREAMER010_ENABLED diff --git a/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.h b/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.h new file mode 100644 index 0000000000..f6d55b8758 --- /dev/null +++ b/indra/media_plugins/gstreamer010/llmediaimplgstreamervidplug.h @@ -0,0 +1,109 @@ +/** + * @file llmediaimplgstreamervidplug.h + * @brief Video-consuming static GStreamer plugin for gst-to-LLMediaImpl + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#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; + 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 new file mode 100644 index 0000000000..647db7a5bf --- /dev/null +++ b/indra/media_plugins/gstreamer010/media_plugin_gstreamer010.cpp @@ -0,0 +1,1204 @@ +/** + * @file media_plugin_gstreamer010.cpp + * @brief GStreamer-0.10 plugin for LLMedia API plugin system + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#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 ); + + bool sizeChanged(); + + static bool mDoneInit; + + guint mBusWatchID; + + float mVolume; + + int mDepth; + + // media natural size + int mNaturalWidth; + int mNaturalHeight; + int mNaturalRowbytes; + // previous media natural size so we can detect changes + int mPreviousNaturalWidth; + int mPreviousNaturalHeight; + // 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; + 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 ), + mNaturalRowbytes ( 4 ), + mTextureFormatPrimary ( GL_RGBA ), + mTextureFormatType ( GL_UNSIGNED_INT_8_8_8_8_REV ), + mSeekWanted(false), + mSeekDestination(0.0), + mPump ( NULL ), + mPlaybin ( 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 + { + 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 != mNaturalWidth || + mVideoSink->retained_frame_height != mNaturalHeight) + // *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); + + mNaturalRowbytes = neww * newd; + DEBUGMSG("video container resized to %dx%d", + neww, newh); + + mDepth = newd; + mNaturalWidth = neww; + mNaturalHeight = newh; + sizeChanged(); + return true; + } + + if (mPixels && + mNaturalHeight <= mHeight && + mNaturalWidth <= 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<mNaturalHeight; ++row) + { + memcpy(&mPixels + [destination_rowbytes * row], + &mVideoSink->retained_frame_data + [mNaturalRowbytes * row], + mNaturalRowbytes); + } + + GST_OBJECT_UNLOCK(mVideoSink); + DEBUGMSG("NEW FRAME REALLY TRULY CONSUMED, TELLING HOST"); + + setDirty(0,0,mNaturalWidth,mNaturalHeight); + } + 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? + llgst_element_set_state(mPlaybin, GST_STATE_PAUSED); + return true; +} + +bool +MediaPluginGStreamer010::stop() +{ + DEBUGMSG("stopping media..."); + // todo: error-check this? + llgst_element_set_state(mPlaybin, GST_STATE_READY); + return true; +} + +bool +MediaPluginGStreamer010::play(double rate) +{ + // NOTE: we don't actually support non-natural rate. + + DEBUGMSG("playing media... rate=%f", rate); + // todo: error-check this? + llgst_element_set_state(mPlaybin, GST_STATE_PLAYING); + return true; +} + +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 (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 (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); + } + + 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 (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; +} + + +bool +MediaPluginGStreamer010::sizeChanged() +{ + // the shared writing space has possibly changed size/location/whatever + + // Check to see whether the movie's natural size has updated + if (mNaturalWidth != mPreviousNaturalWidth || + mNaturalHeight != mPreviousNaturalHeight) + { + mPreviousNaturalWidth = mNaturalWidth; + mPreviousNaturalHeight = mNaturalHeight; + + 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' - size is %d x %d", mTextureSegmentName.c_str(), mNaturalWidth, mNaturalHeight); + sendMessage(message); + } + + return true; +} + + + +//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); + + // Plugin gets to decide the texture parameters to use. + message.setMessage(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params"); + // lame to have to decide this now, it depends on the movie. Oh well. + mDepth = 4; + + mNaturalWidth = 1; + mNaturalHeight = 1; + mPreviousNaturalWidth = 1; + mPreviousNaturalHeight = 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 == "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; + U64 address_lo = message_in.getValueU32("address"); + U64 address_hi = message_in.hasValue("address_1") ? message_in.getValueU32("address_1") : 0; + info.mAddress = (void*)((address_lo) | + (address_hi * (U64(1)<<31))); + 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 == "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 = 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/quicktime/CMakeLists.txt b/indra/media_plugins/quicktime/CMakeLists.txt new file mode 100644 index 0000000000..db11c9ae21 --- /dev/null +++ b/indra/media_plugins/quicktime/CMakeLists.txt @@ -0,0 +1,83 @@ +# -*- cmake -*- + +project(media_plugin_quicktime) + +include(00-Common) +include(LLCommon) +include(LLImage) +include(LLPlugin) +include(LLMath) +include(LLRender) +include(LLWindow) +include(Linking) +include(PluginAPI) +include(MediaPluginBase) +include(FindOpenGL) +include(QuickTimePlugin) + +include_directories( + ${LLPLUGIN_INCLUDE_DIRS} + ${MEDIA_PLUGIN_BASE_INCLUDE_DIRS} + ${LLCOMMON_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} + ${LLIMAGE_INCLUDE_DIRS} + ${LLRENDER_INCLUDE_DIRS} + ${LLWINDOW_INCLUDE_DIRS} +) + +if (DARWIN) + include(CMakeFindFrameworks) + find_library(CARBON_LIBRARY Carbon) +endif (DARWIN) + + +### media_plugin_quicktime + +set(media_plugin_quicktime_SOURCE_FILES + media_plugin_quicktime.cpp + ) + +add_library(media_plugin_quicktime + SHARED + ${media_plugin_quicktime_SOURCE_FILES} +) + +target_link_libraries(media_plugin_quicktime + ${LLPLUGIN_LIBRARIES} + ${MEDIA_PLUGIN_BASE_LIBRARIES} + ${LLCOMMON_LIBRARIES} + ${QUICKTIME_LIBRARY} + ${PLUGIN_API_WINDOWS_LIBRARIES} +) + +add_dependencies(media_plugin_quicktime + ${LLPLUGIN_LIBRARIES} + ${MEDIA_PLUGIN_BASE_LIBRARIES} + ${LLCOMMON_LIBRARIES} +) + +if (QUICKTIME) + + add_definitions(-DLL_QUICKTIME_ENABLED=1) + + if (DARWIN) + # Don't prepend 'lib' to the executable name, and don't embed a full path in the library's install name + set_target_properties( + media_plugin_quicktime + PROPERTIES + PREFIX "" + BUILD_WITH_INSTALL_RPATH 1 + INSTALL_NAME_DIR "@executable_path" + LINK_FLAGS "-exported_symbols_list ${CMAKE_CURRENT_SOURCE_DIR}/../base/media_plugin_base.exp" + ) + +# We use a bunch of deprecated system APIs. + set_source_files_properties( + media_plugin_quicktime.cpp PROPERTIES + COMPILE_FLAGS -Wno-deprecated-declarations + ) + find_library(CARBON_LIBRARY Carbon) + target_link_libraries(media_plugin_quicktime ${CARBON_LIBRARY}) + endif (DARWIN) +endif (QUICKTIME) + diff --git a/indra/media_plugins/quicktime/media_plugin_quicktime.cpp b/indra/media_plugins/quicktime/media_plugin_quicktime.cpp new file mode 100644 index 0000000000..e9be458960 --- /dev/null +++ b/indra/media_plugins/quicktime/media_plugin_quicktime.cpp @@ -0,0 +1,984 @@ +/** + * @file media_plugin_quicktime.cpp + * @brief QuickTime plugin for LLMedia API plugin system + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llgl.h" + +#include "llplugininstance.h" +#include "llpluginmessage.h" +#include "llpluginmessageclasses.h" +#include "media_plugin_base.h" + +#if LL_QUICKTIME_ENABLED + +#if defined(LL_DARWIN) + #include <QuickTime/QuickTime.h> +#elif defined(LL_WINDOWS) + #include "MacTypes.h" + #include "QTML.h" + #include "Movies.h" + #include "QDoffscreen.h" + #include "FixMath.h" +#endif + +// TODO: Make sure that the only symbol exported from this library is LLPluginInitEntryPoint +//////////////////////////////////////////////////////////////////////////////// +// +class MediaPluginQuickTime : public MediaPluginBase +{ +public: + MediaPluginQuickTime(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data); + ~MediaPluginQuickTime(); + + /* virtual */ void receiveMessage(const char *message_string); + +private: + + int mNaturalWidth; + int mNaturalHeight; + Movie mMovieHandle; + GWorldPtr mGWorldHandle; + ComponentInstance mMovieController; + int mCurVolume; + bool mMediaSizeChanging; + bool mIsLooping; + const int mMinWidth; + const int mMaxWidth; + const int mMinHeight; + const int mMaxHeight; + F64 mPlayRate; + + enum ECommand { + COMMAND_NONE, + COMMAND_STOP, + COMMAND_PLAY, + COMMAND_FAST_FORWARD, + COMMAND_FAST_REWIND, + COMMAND_PAUSE, + COMMAND_SEEK, + }; + ECommand mCommand; + + // Override this to add current time and duration to the message + /*virtual*/ void setDirty(int left, int top, int right, int bottom) + { + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "updated"); + + message.setValueS32("left", left); + message.setValueS32("top", top); + message.setValueS32("right", right); + message.setValueS32("bottom", bottom); + + if(mMovieHandle) + { + message.setValueReal("current_time", getCurrentTime()); + message.setValueReal("duration", getDuration()); + message.setValueReal("current_rate", Fix2X(GetMovieRate(mMovieHandle))); + } + + sendMessage(message); + } + + + static Rect rectFromSize(int width, int height) + { + Rect result; + + + result.left = 0; + result.top = 0; + result.right = width; + result.bottom = height; + + return result; + } + + Fixed getPlayRate(void) + { + Fixed result; + if(mPlayRate == 0.0f) + { + // Default to the movie's preferred rate + result = GetMoviePreferredRate(mMovieHandle); + if(result == 0) + { + // Don't return a 0 play rate, ever. + std::cerr << "Movie's preferred rate is 0, forcing to 1.0." << std::endl; + result = X2Fix(1.0f); + } + } + else + { + result = X2Fix(mPlayRate); + } + + return result; + } + + void load( const std::string url ) + { + if ( url.empty() ) + return; + + // Stop and unload any existing movie before starting another one. + unload(); + + setStatus(STATUS_LOADING); + + //In case std::string::c_str() makes a copy of the url data, + //make sure there is memory to hold it before allocating memory for handle. + //if fails, NewHandleClear(...) should return NULL. + const char* url_string = url.c_str() ; + Handle handle = NewHandleClear( ( Size )( url.length() + 1 ) ); + if ( NULL == handle || noErr != MemError() || NULL == *handle ) + { + setStatus(STATUS_ERROR); + return; + } + + BlockMove( url_string, *handle, ( Size )( url.length() + 1 ) ); + + OSErr err = NewMovieFromDataRef( &mMovieHandle, newMovieActive | newMovieDontInteractWithUser | newMovieAsyncOK | newMovieIdleImportOK, nil, handle, URLDataHandlerSubType ); + DisposeHandle( handle ); + if ( noErr != err ) + { + setStatus(STATUS_ERROR); + return; + }; + + // do pre-roll actions (typically fired for streaming movies but not always) + PrePrerollMovie( mMovieHandle, 0, getPlayRate(), moviePrePrerollCompleteCallback, ( void * )this ); + + Rect movie_rect = rectFromSize(mWidth, mHeight); + + // make a new movie controller + mMovieController = NewMovieController( mMovieHandle, &movie_rect, mcNotVisible | mcTopLeftMovie ); + + // movie controller + MCSetActionFilterWithRefCon( mMovieController, mcActionFilterCallBack, ( long )this ); + + SetMoviePlayHints( mMovieHandle, hintsAllowDynamicResize, hintsAllowDynamicResize ); + + // function that gets called when a frame is drawn + SetMovieDrawingCompleteProc( mMovieHandle, movieDrawingCallWhenChanged, movieDrawingCompleteCallback, ( long )this ); + + setStatus(STATUS_LOADED); + + sizeChanged(); + }; + + bool unload() + { + if ( mMovieHandle ) + { + StopMovie( mMovieHandle ); + if ( mMovieController ) + { + MCMovieChanged( mMovieController, mMovieHandle ); + }; + }; + + if ( mMovieController ) + { + MCSetActionFilterWithRefCon( mMovieController, NULL, (long)this ); + DisposeMovieController( mMovieController ); + mMovieController = NULL; + }; + + if ( mMovieHandle ) + { + SetMovieDrawingCompleteProc( mMovieHandle, movieDrawingCallWhenChanged, nil, ( long )this ); + DisposeMovie( mMovieHandle ); + mMovieHandle = NULL; + }; + + if ( mGWorldHandle ) + { + DisposeGWorld( mGWorldHandle ); + mGWorldHandle = NULL; + }; + + setStatus(STATUS_NONE); + + return true; + } + + bool navigateTo( const std::string url ) + { + unload(); + load( url ); + + return true; + }; + + bool sizeChanged() + { + if ( ! mMovieHandle ) + return false; + + // Check to see whether the movie's natural size has updated + { + int width, height; + getMovieNaturalSize(&width, &height); + if((width != 0) && (height != 0) && ((width != mNaturalWidth) || (height != mNaturalHeight))) + { + mNaturalWidth = width; + mNaturalHeight = height; + + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_request"); + message.setValue("name", mTextureSegmentName); + message.setValueS32("width", width); + message.setValueS32("height", height); + sendMessage(message); + //std::cerr << "<--- Sending size change request to application with name: " << mTextureSegmentName << " - size is " << width << " x " << height << std::endl; + } + } + + // sanitize destination size + Rect dest_rect = rectFromSize(mWidth, mHeight); + + // media depth won't change + int depth_bits = mDepth * 8; + long rowbytes = mDepth * mTextureWidth; + + GWorldPtr old_gworld_handle = mGWorldHandle; + + if(mPixels != NULL) + { + // We have pixels. Set up a GWorld pointing at the texture. + OSErr result = NewGWorldFromPtr( &mGWorldHandle, depth_bits, &dest_rect, NULL, NULL, 0, (Ptr)mPixels, rowbytes); + if ( noErr != result ) + { + // TODO: unrecoverable?? throw exception? return something? + return false; + } + } + else + { + // We don't have pixels. Create a fake GWorld we can point the movie at when it's not safe to render normally. + Rect tempRect = rectFromSize(1, 1); + OSErr result = NewGWorld( &mGWorldHandle, depth_bits, &tempRect, NULL, NULL, 0); + if ( noErr != result ) + { + // TODO: unrecoverable?? throw exception? return something? + return false; + } + } + + SetMovieGWorld( mMovieHandle, mGWorldHandle, GetGWorldDevice( mGWorldHandle ) ); + + // If the GWorld was already set up, delete it. + if(old_gworld_handle != NULL) + { + DisposeGWorld( old_gworld_handle ); + } + + // Set up the movie display matrix + { + // scale movie to fit rect and invert vertically to match opengl image format + MatrixRecord transform; + SetIdentityMatrix( &transform ); // transforms are additive so start from identify matrix + double scaleX = (double) mWidth / mNaturalWidth; + double scaleY = -1.0 * (double) mHeight / mNaturalHeight; + double centerX = mWidth / 2.0; + double centerY = mHeight / 2.0; + ScaleMatrix( &transform, X2Fix( scaleX ), X2Fix( scaleY ), X2Fix( centerX ), X2Fix( centerY ) ); + SetMovieMatrix( mMovieHandle, &transform ); + } + + // update movie controller + if ( mMovieController ) + { + MCSetControllerPort( mMovieController, mGWorldHandle ); + MCPositionController( mMovieController, &dest_rect, &dest_rect, + mcTopLeftMovie | mcPositionDontInvalidate ); + MCMovieChanged( mMovieController, mMovieHandle ); + } + + + // Emit event with size change so the calling app knows about it too + // TODO: + //LLMediaEvent event( this ); + //mEventEmitter.update( &LLMediaObserver::onMediaSizeChange, event ); + + return true; + } + + static Boolean mcActionFilterCallBack( MovieController mc, short action, void *params, long ref ) + { + Boolean result = false; + + MediaPluginQuickTime* self = ( MediaPluginQuickTime* )ref; + + switch( action ) + { + // handle window resizing + case mcActionControllerSizeChanged: + // Ensure that the movie draws correctly at the new size + self->sizeChanged(); + break; + + // Block any movie controller actions that open URLs. + case mcActionLinkToURL: + case mcActionGetNextURL: + case mcActionLinkToURLExtended: + // Prevent the movie controller from handling the message + result = true; + break; + + default: + break; + }; + + return result; + }; + + static OSErr movieDrawingCompleteCallback( Movie call_back_movie, long ref ) + { + MediaPluginQuickTime* self = ( MediaPluginQuickTime* )ref; + + // IMPORTANT: typically, a consumer who is observing this event will set a flag + // when this event is fired then render later. Be aware that the media stream + // can change during this period - dimensions, depth, format etc. + //LLMediaEvent event( self ); +// self->updateQuickTime(); + // TODO ^^^ + + if ( self->mWidth > 0 && self->mHeight > 0 ) + self->setDirty( 0, 0, self->mWidth, self->mHeight ); + + return noErr; + }; + + static void moviePrePrerollCompleteCallback( Movie movie, OSErr preroll_err, void *ref ) + { + //MediaPluginQuickTime* self = ( MediaPluginQuickTime* )ref; + + // TODO: + //LLMediaEvent event( self ); + //self->mEventEmitter.update( &LLMediaObserver::onMediaPreroll, event ); + }; + + + void rewind() + { + GoToBeginningOfMovie( mMovieHandle ); + MCMovieChanged( mMovieController, mMovieHandle ); + }; + + bool processState() + { + if ( mCommand == COMMAND_PLAY ) + { + if ( mStatus == STATUS_LOADED || mStatus == STATUS_PAUSED || mStatus == STATUS_PLAYING ) + { + long state = GetMovieLoadState( mMovieHandle ); + + if ( state >= kMovieLoadStatePlaythroughOK ) + { + // if the movie is at the end (generally because it reached it naturally) + // and we play is requested, jump back to the start of the movie. + // note: this is different from having loop flag set. + if ( IsMovieDone( mMovieHandle ) ) + { + Fixed rate = X2Fix( 0.0 ); + MCDoAction( mMovieController, mcActionPlay, (void*)rate ); + rewind(); + }; + + MCDoAction( mMovieController, mcActionPrerollAndPlay, (void*)getPlayRate() ); + MCDoAction( mMovieController, mcActionSetVolume, (void*)mCurVolume ); + setStatus(STATUS_PLAYING); + mCommand = COMMAND_NONE; + }; + }; + } + else + if ( mCommand == COMMAND_STOP ) + { + if ( mStatus == STATUS_PLAYING || mStatus == STATUS_PAUSED ) + { + if ( GetMovieLoadState( mMovieHandle ) >= kMovieLoadStatePlaythroughOK ) + { + Fixed rate = X2Fix( 0.0 ); + MCDoAction( mMovieController, mcActionPlay, (void*)rate ); + rewind(); + + setStatus(STATUS_LOADED); + mCommand = COMMAND_NONE; + }; + }; + } + else + if ( mCommand == COMMAND_PAUSE ) + { + if ( mStatus == STATUS_PLAYING ) + { + if ( GetMovieLoadState( mMovieHandle ) >= kMovieLoadStatePlaythroughOK ) + { + Fixed rate = X2Fix( 0.0 ); + MCDoAction( mMovieController, mcActionPlay, (void*)rate ); + setStatus(STATUS_PAUSED); + mCommand = COMMAND_NONE; + }; + }; + }; + + return true; + }; + + void play(F64 rate) + { + mPlayRate = rate; + mCommand = COMMAND_PLAY; + }; + + void stop() + { + mCommand = COMMAND_STOP; + }; + + void pause() + { + mCommand = COMMAND_PAUSE; + }; + + void getMovieNaturalSize(int *movie_width, int *movie_height) + { + Rect rect; + + GetMovieNaturalBoundsRect( mMovieHandle, &rect ); + + int width = ( rect.right - rect.left ); + int height = ( rect.bottom - rect.top ); + + // make sure width and height fall in valid range + if ( width < mMinWidth ) + width = mMinWidth; + + if ( width > mMaxWidth ) + width = mMaxWidth; + + if ( height < mMinHeight ) + height = mMinHeight; + + if ( height > mMaxHeight ) + height = mMaxHeight; + + // return the new rect + *movie_width = width; + *movie_height = height; + } + + void updateQuickTime(int milliseconds) + { + if ( ! mMovieHandle ) + return; + + if ( ! mMovieController ) + return; + + // service QuickTime + // Calling it this way doesn't have good behavior on Windows... +// MoviesTask( mMovieHandle, milliseconds ); + // This was the original, but I think using both MoviesTask and MCIdle is redundant. Trying with only MCIdle. +// MoviesTask( mMovieHandle, 0 ); + + MCIdle( mMovieController ); + + if ( ! mGWorldHandle ) + return; + + if ( mMediaSizeChanging ) + return; + + // update state machine + processState(); + + // special code for looping - need to rewind at the end of the movie + if ( mIsLooping ) + { + // QT call to see if we are at the end - can't do with controller + if ( IsMovieDone( mMovieHandle ) ) + { + // go back to start + rewind(); + + if ( mMovieController ) + { + // kick off new play + MCDoAction( mMovieController, mcActionPrerollAndPlay, (void*)getPlayRate() ); + + // set the volume + MCDoAction( mMovieController, mcActionSetVolume, (void*)mCurVolume ); + }; + }; + }; + }; + + int getDataWidth() const + { + if ( mGWorldHandle ) + { + int depth = mDepth; + + if (depth < 1) + depth = 1; + + // ALWAYS use the row bytes from the PixMap if we have a GWorld because + // sometimes it's not the same as mMediaDepth * mMediaWidth ! + PixMapHandle pix_map_handle = GetGWorldPixMap( mGWorldHandle ); + return QTGetPixMapHandleRowBytes( pix_map_handle ) / depth; + } + else + { + // TODO : return LLMediaImplCommon::getaDataWidth(); + return 0; + } + }; + + void seek( F64 time ) + { + if ( mMovieController ) + { + TimeRecord when; + when.scale = GetMovieTimeScale( mMovieHandle ); + when.base = 0; + + // 'time' is in (floating point) seconds. The timebase time will be in 'units', where + // there are 'scale' units per second. + SInt64 raw_time = ( SInt64 )( time * (double)( when.scale ) ); + + when.value.hi = ( SInt32 )( raw_time >> 32 ); + when.value.lo = ( SInt32 )( ( raw_time & 0x00000000FFFFFFFF ) ); + + MCDoAction( mMovieController, mcActionGoToTime, &when ); + }; + }; + + F64 getDuration() + { + TimeValue duration = GetMovieDuration( mMovieHandle ); + TimeValue scale = GetMovieTimeScale( mMovieHandle ); + + return (F64)duration / (F64)scale; + }; + + F64 getCurrentTime() + { + TimeValue curr_time = GetMovieTime( mMovieHandle, 0 ); + TimeValue scale = GetMovieTimeScale( mMovieHandle ); + + return (F64)curr_time / (F64)scale; + }; + + void setVolume( F64 volume ) + { + mCurVolume = (short)(volume * ( double ) 0x100 ); + + if ( mMovieController ) + { + MCDoAction( mMovieController, mcActionSetVolume, (void*)mCurVolume ); + }; + }; + + //////////////////////////////////////////////////////////////////////////////// + // + void update(int milliseconds = 0) + { + updateQuickTime(milliseconds); + }; + + //////////////////////////////////////////////////////////////////////////////// + // + void mouseDown( int x, int y ) + { + }; + + //////////////////////////////////////////////////////////////////////////////// + // + void mouseUp( int x, int y ) + { + }; + + //////////////////////////////////////////////////////////////////////////////// + // + void mouseMove( int x, int y ) + { + }; + + //////////////////////////////////////////////////////////////////////////////// + // + void keyPress( unsigned char key ) + { + }; + +}; + +MediaPluginQuickTime::MediaPluginQuickTime( + LLPluginInstance::sendMessageFunction host_send_func, + void *host_user_data ) : + MediaPluginBase(host_send_func, host_user_data), + mMinWidth( 0 ), + mMaxWidth( 2048 ), + mMinHeight( 0 ), + mMaxHeight( 2048 ) +{ +// std::cerr << "MediaPluginQuickTime constructor" << std::endl; + + mNaturalWidth = -1; + mNaturalHeight = -1; + mMovieHandle = 0; + mGWorldHandle = 0; + mMovieController = 0; + mCurVolume = 0x99; + mMediaSizeChanging = false; + mIsLooping = false; + mCommand = COMMAND_NONE; + mPlayRate = 0.0f; + mStatus = STATUS_NONE; +} + +MediaPluginQuickTime::~MediaPluginQuickTime() +{ +// std::cerr << "MediaPluginQuickTime destructor" << std::endl; + + ExitMovies(); + +#ifdef LL_WINDOWS + TerminateQTML(); +// std::cerr << "QuickTime closing down" << std::endl; +#endif +} + + +void MediaPluginQuickTime::receiveMessage(const char *message_string) +{ +// std::cerr << "MediaPluginQuickTime::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; + // Normally a plugin would only specify one of these two subclasses, but this is a demo... +// versions[LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER] = LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER_VERSION; + versions[LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME] = LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME_VERSION; + message.setValueLLSD("versions", versions); + + #ifdef LL_WINDOWS + if ( InitializeQTML( 0L ) != noErr ) + { + //TODO: If no QT on Windows, this fails - respond accordingly. + //return false; + } + else + { +// std::cerr << "QuickTime initialized" << std::endl; + }; + #endif + + EnterMovies(); + + std::string plugin_version = "QuickTime media plugin, QuickTime version "; + + long version = 0; + Gestalt( gestaltQuickTimeVersion, &version ); + std::ostringstream codec( "" ); + codec << std::hex << version << std::dec; + plugin_version += codec.str(); + message.setValue("plugin_version", plugin_version); + sendMessage(message); + + // Plugin gets to decide the texture parameters to use. + message.setMessage(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params"); + #if defined(LL_WINDOWS) + // Values for Windows + mDepth = 3; + message.setValueU32("format", GL_RGB); + message.setValueU32("type", GL_UNSIGNED_BYTE); + + // We really want to pad the texture width to a multiple of 32 bytes, but since we're using 3-byte pixels, it doesn't come out even. + // Padding to a multiple of 3*32 guarantees it'll divide out properly. + message.setValueU32("padding", 32 * 3); + #else + // Values for Mac + mDepth = 4; + message.setValueU32("format", GL_BGRA_EXT); + #ifdef __BIG_ENDIAN__ + message.setValueU32("type", GL_UNSIGNED_INT_8_8_8_8_REV ); + #else + message.setValueU32("type", GL_UNSIGNED_INT_8_8_8_8); + #endif + + // Pad texture width to a multiple of 32 bytes, to line up with cache lines. + message.setValueU32("padding", 32); + #endif + message.setValueS32("depth", mDepth); + message.setValueU32("internalformat", GL_RGB); + message.setValueBoolean("coords_opengl", true); // true == use OpenGL-style coordinates, false == (0,0) is upper left. + message.setValueBoolean("allow_downsample", true); + sendMessage(message); + } + else if(message_name == "idle") + { + // no response is necessary here. + F64 time = message_in.getValueReal("time"); + + // Convert time to milliseconds for update() + update((int)(time * 1000.0f)); + } + else if(message_name == "cleanup") + { + // TODO: clean up here + } + else if(message_name == "shm_added") + { + SharedSegmentInfo info; + U64 address_lo = message_in.getValueU32("address"); + U64 address_hi = message_in.hasValue("address_1") ? message_in.getValueU32("address_1") : 0; + info.mAddress = (void*)((address_lo) | + (address_hi * (U64(1)<<31))); + info.mSize = (size_t)message_in.getValueS32("size"); + std::string name = message_in.getValue("name"); + + +// std::cerr << "MediaPluginQuickTime::receiveMessage: shared memory added, name: " << name +// << ", size: " << info.mSize +// << ", address: " << info.mAddress +// << std::endl; + + mSharedSegments.insert(SharedSegmentMap::value_type(name, info)); + + } + else if(message_name == "shm_remove") + { + std::string name = message_in.getValue("name"); + +// std::cerr << "MediaPluginQuickTime::receiveMessage: shared memory remove, name = " << name << std::endl; + + 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 GWorld is no longer pointed at the shared segment. + sizeChanged(); + } + mSharedSegments.erase(iter); + } + else + { +// std::cerr << "MediaPluginQuickTime::receiveMessage: unknown shared memory region!" << std::endl; + } + + // Send the response so it can be cleaned up. + LLPluginMessage message("base", "shm_remove_response"); + message.setValue("name", name); + sendMessage(message); + } + else + { +// std::cerr << "MediaPluginQuickTime::receiveMessage: unknown base message: " << message_name << std::endl; + } + } + else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA) + { + 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::cerr << "---->Got size change instruction from application with name: " << name << " - size is " << width << " x " << height << std::endl; + + 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()) + { +// std::cerr << "%%% Got size change, new size is " << width << " by " << height << std::endl; +// std::cerr << "%%%% texture size is " << texture_width << " by " << texture_height << std::endl; + + mPixels = (unsigned char*)iter->second.mAddress; + mTextureSegmentName = name; + mWidth = width; + mHeight = height; + + mTextureWidth = texture_width; + mTextureHeight = texture_height; + + mMediaSizeChanging = false; + + sizeChanged(); + + update(); + }; + }; + } + else if(message_name == "load_uri") + { + std::string uri = message_in.getValue("uri"); + load( 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") + { + F64 rate = 0.0; + if(message_in.hasValue("rate")) + { + rate = message_in.getValueReal("rate"); + } + play(rate); + } + else if(message_name == "pause") + { + pause(); + } + else if(message_name == "seek") + { + F64 time = message_in.getValueReal("time"); + seek(time); + } + else if(message_name == "set_loop") + { + bool loop = message_in.getValueBoolean("loop"); + mIsLooping = loop; + } + else if(message_name == "set_volume") + { + F64 volume = message_in.getValueReal("volume"); + setVolume(volume); + } + } + else + { +// std::cerr << "MediaPluginQuickTime::receiveMessage: unknown message class: " << message_class << std::endl; + }; + }; +} + +int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data) +{ + MediaPluginQuickTime *self = new MediaPluginQuickTime(host_send_func, host_user_data); + *plugin_send_func = MediaPluginQuickTime::staticReceiveMessage; + *plugin_user_data = (void*)self; + + return 0; +} + +#else // LL_QUICKTIME_ENABLED + +// Stubbed-out class with constructor/destructor (necessary or windows linker +// will just think its dead code and optimize it all out) +class MediaPluginQuickTime : public MediaPluginBase +{ +public: + MediaPluginQuickTime(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data); + ~MediaPluginQuickTime(); + /* virtual */ void receiveMessage(const char *message_string); +}; + +MediaPluginQuickTime::MediaPluginQuickTime( + LLPluginInstance::sendMessageFunction host_send_func, + void *host_user_data ) : + MediaPluginBase(host_send_func, host_user_data) +{ + // no-op +} + +MediaPluginQuickTime::~MediaPluginQuickTime() +{ + // no-op +} + +void MediaPluginQuickTime::receiveMessage(const char *message_string) +{ + // no-op +} + +// We're building without quicktime 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_QUICKTIME_ENABLED diff --git a/indra/media_plugins/webkit/CMakeLists.txt b/indra/media_plugins/webkit/CMakeLists.txt new file mode 100644 index 0000000000..c048dd66c1 --- /dev/null +++ b/indra/media_plugins/webkit/CMakeLists.txt @@ -0,0 +1,74 @@ +# -*- cmake -*- + +project(media_plugin_webkit) + +include(00-Common) +include(LLCommon) +include(LLImage) +include(LLPlugin) +include(LLMath) +include(LLRender) +include(LLWindow) +include(Linking) +include(PluginAPI) +include(MediaPluginBase) +include(FindOpenGL) + +include(WebKitLibPlugin) + +include_directories( + ${LLPLUGIN_INCLUDE_DIRS} + ${MEDIA_PLUGIN_BASE_INCLUDE_DIRS} + ${LLCOMMON_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} + ${LLIMAGE_INCLUDE_DIRS} + ${LLRENDER_INCLUDE_DIRS} + ${LLWINDOW_INCLUDE_DIRS} +) + + +### media_plugin_webkit + +set(media_plugin_webkit_SOURCE_FILES + media_plugin_webkit.cpp + ) + +add_library(media_plugin_webkit + SHARED + ${media_plugin_webkit_SOURCE_FILES} +) + +target_link_libraries(media_plugin_webkit + ${LLPLUGIN_LIBRARIES} + ${MEDIA_PLUGIN_BASE_LIBRARIES} + ${LLCOMMON_LIBRARIES} + ${WEBKIT_PLUGIN_LIBRARIES} + ${PLUGIN_API_WINDOWS_LIBRARIES} +) + +add_dependencies(media_plugin_webkit + ${LLPLUGIN_LIBRARIES} + ${MEDIA_PLUGIN_BASE_LIBRARIES} + ${LLCOMMON_LIBRARIES} +) + +if (DARWIN) + # Don't prepend 'lib' to the executable name, and don't embed a full path in the library's install name + set_target_properties( + media_plugin_webkit + PROPERTIES + PREFIX "" + BUILD_WITH_INSTALL_RPATH 1 + INSTALL_NAME_DIR "@executable_path" + LINK_FLAGS "-exported_symbols_list ${CMAKE_CURRENT_SOURCE_DIR}/../base/media_plugin_base.exp" + ) + + # copy the webkit dylib to the build directory + add_custom_command( + TARGET media_plugin_webkit POST_BUILD +# OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/libllwebkitlib.dylib + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../libraries/universal-darwin/lib_release/libllwebkitlib.dylib ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/ + DEPENDS media_plugin_webkit ${CMAKE_SOURCE_DIR}/../libraries/universal-darwin/lib_release/libllwebkitlib.dylib + ) + +endif (DARWIN)
\ No newline at end of file diff --git a/indra/media_plugins/webkit/media_plugin_webkit.cpp b/indra/media_plugins/webkit/media_plugin_webkit.cpp new file mode 100644 index 0000000000..bd29eb5395 --- /dev/null +++ b/indra/media_plugins/webkit/media_plugin_webkit.cpp @@ -0,0 +1,787 @@ +/** + * @file media_plugin_webkit.cpp + * @brief Webkit plugin for LLMedia API plugin system + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "llwebkitlib.h" + +#include "linden_common.h" +#include "indra_constants.h" // for indra keyboard codes + +#include "llgl.h" + +#include "llplugininstance.h" +#include "llpluginmessage.h" +#include "llpluginmessageclasses.h" +#include "media_plugin_base.h" + +#if LL_WINDOWS +#include <direct.h> +#else +#include <unistd.h> +#include <stdlib.h> +#endif + +//////////////////////////////////////////////////////////////////////////////// +// +class MediaPluginWebKit : + public MediaPluginBase, + public LLEmbeddedBrowserWindowObserver +{ +public: + MediaPluginWebKit(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data); + ~MediaPluginWebKit(); + + /*virtual*/ void receiveMessage(const char *message_string); + +private: + + int mBrowserWindowId; + bool mBrowserInitialized; + bool mNeedsUpdate; + + bool mCanCut; + bool mCanCopy; + bool mCanPaste; + + //////////////////////////////////////////////////////////////////////////////// + // + void update(int milliseconds) + { + LLMozLib::getInstance()->pump( milliseconds ); + + checkEditState(); + + if ( mNeedsUpdate ) + { + const unsigned char* browser_pixels = LLMozLib::getInstance()->grabBrowserWindow( mBrowserWindowId ); + + unsigned int buffer_size = LLMozLib::getInstance()->getBrowserRowSpan( mBrowserWindowId ) * LLMozLib::getInstance()->getBrowserHeight( mBrowserWindowId ); + +// std::cerr << "webkit plugin: updating" << std::endl; + + // TODO: should get rid of this memcpy if possible + if ( mPixels && browser_pixels ) + { +// std::cerr << " memcopy of " << buffer_size << " bytes" << std::endl; + memcpy( mPixels, browser_pixels, buffer_size ); + } + + if ( mWidth > 0 && mHeight > 0 ) + { +// std::cerr << "Setting dirty, " << mWidth << " x " << mHeight << std::endl; + setDirty( 0, 0, mWidth, mHeight ); + } + + mNeedsUpdate = false; + }; + }; + + //////////////////////////////////////////////////////////////////////////////// + // + bool initBrowser() + { + // already initialized + if ( mBrowserInitialized ) + return true; + + // not enough information to initialize the browser yet. + if ( mWidth < 0 || mHeight < 0 || mDepth < 0 || + mTextureWidth < 0 || mTextureHeight < 0 ) + { + return false; + }; + + // set up directories + char cwd[ FILENAME_MAX ]; // I *think* this is defined on all platforms we use + if (NULL == getcwd( cwd, FILENAME_MAX - 1 )) + { + llwarns << "Couldn't get cwd - probably too long - failing to init." << llendl; + return false; + } + std::string application_dir = std::string( cwd ); + std::string component_dir = application_dir; + std::string profileDir = application_dir + "/" + "browser_profile"; // cross platform? + + // window handle - needed on Windows and must be app window. +#if LL_WINDOWS + char window_title[ MAX_PATH ]; + GetConsoleTitleA( window_title, MAX_PATH ); + void* native_window_handle = (void*)FindWindowA( NULL, window_title ); +#else + void* native_window_handle = 0; +#endif + + // main browser initialization + bool result = LLMozLib::getInstance()->init( application_dir, component_dir, profileDir, native_window_handle ); + if ( result ) + { + // create single browser window + mBrowserWindowId = LLMozLib::getInstance()->createBrowserWindow( mWidth, mHeight ); + + // Enable plugins + LLMozLib::getInstance()->enablePlugins(true); + + // tell LLMozLib about the size of the browser window + LLMozLib::getInstance()->setSize( mBrowserWindowId, mWidth, mHeight ); + + // observer events that LLMozLib emits + LLMozLib::getInstance()->addObserver( mBrowserWindowId, this ); + + // append details to agent string + LLMozLib::getInstance()->setBrowserAgentId( "LLPluginMedia Web Browser" ); + + // don't flip bitmap + LLMozLib::getInstance()->flipWindow( mBrowserWindowId, true ); + + // go to the "home page" + // Don't do this here -- it causes the dreaded "white flash" when loading a browser instance. +// LLMozLib::getInstance()->navigateTo( mBrowserWindowId, "about:blank" ); + + // set flag so we don't do this again + mBrowserInitialized = true; + + return true; + }; + + return false; + }; + + //////////////////////////////////////////////////////////////////////////////// + // virtual + void onCursorChanged(const EventType& event) + { + LLMozLib::ECursor moz_cursor = (LLMozLib::ECursor)event.getIntValue(); + std::string name; + + switch(moz_cursor) + { + case LLMozLib::C_ARROW: + name = "arrow"; + break; + case LLMozLib::C_IBEAM: + name = "ibeam"; + break; + case LLMozLib::C_SPLITV: + name = "splitv"; + break; + case LLMozLib::C_SPLITH: + name = "splith"; + break; + case LLMozLib::C_POINTINGHAND: + name = "hand"; + break; + + default: + llwarns << "Unknown cursor ID: " << (int)moz_cursor << llendl; + break; + } + + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "cursor_changed"); + message.setValue("name", name); + sendMessage(message); + } + + //////////////////////////////////////////////////////////////////////////////// + // virtual + void onPageChanged( const EventType& event ) + { + // flag that an update is required + mNeedsUpdate = true; + }; + + //////////////////////////////////////////////////////////////////////////////// + // virtual + void onNavigateBegin(const EventType& event) + { + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "navigate_begin"); + message.setValue("uri", event.getEventUri()); + sendMessage(message); + + setStatus(STATUS_LOADING); + } + + //////////////////////////////////////////////////////////////////////////////// + // virtual + void onNavigateComplete(const EventType& event) + { + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "navigate_complete"); + message.setValue("uri", event.getEventUri()); + message.setValueS32("result_code", event.getIntValue()); + message.setValue("result_string", event.getStringValue()); + message.setValueBoolean("history_back_available", LLMozLib::getInstance()->userActionIsEnabled( mBrowserWindowId, LLMozLib::UA_NAVIGATE_BACK)); + message.setValueBoolean("history_forward_available", LLMozLib::getInstance()->userActionIsEnabled( mBrowserWindowId, LLMozLib::UA_NAVIGATE_FORWARD)); + sendMessage(message); + + setStatus(STATUS_LOADED); + } + + //////////////////////////////////////////////////////////////////////////////// + // virtual + void onUpdateProgress(const EventType& event) + { + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "progress"); + message.setValueS32("percent", event.getIntValue()); + sendMessage(message); + } + + //////////////////////////////////////////////////////////////////////////////// + // virtual + void onStatusTextChange(const EventType& event) + { + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "status_text"); + message.setValue("status", event.getStringValue()); + sendMessage(message); + } + + //////////////////////////////////////////////////////////////////////////////// + // virtual + void onLocationChange(const EventType& event) + { + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "location_changed"); + message.setValue("uri", event.getEventUri()); + sendMessage(message); + } + + //////////////////////////////////////////////////////////////////////////////// + // virtual + void onClickLinkHref(const EventType& event) + { + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "click_href"); + message.setValue("uri", event.getStringValue()); + message.setValue("target", event.getStringValue2()); + sendMessage(message); + } + + //////////////////////////////////////////////////////////////////////////////// + // virtual + void onClickLinkNoFollow(const EventType& event) + { + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "click_nofollow"); + message.setValue("uri", event.getStringValue()); + sendMessage(message); + } + + //////////////////////////////////////////////////////////////////////////////// + // + void mouseDown( int x, int y ) + { + LLMozLib::getInstance()->mouseDown( mBrowserWindowId, x, y ); + }; + + //////////////////////////////////////////////////////////////////////////////// + // + void mouseUp( int x, int y ) + { + LLMozLib::getInstance()->mouseUp( mBrowserWindowId, x, y ); + LLMozLib::getInstance()->focusBrowser( mBrowserWindowId, true ); + checkEditState(); + }; + + //////////////////////////////////////////////////////////////////////////////// + // + void mouseMove( int x, int y ) + { + LLMozLib::getInstance()->mouseMove( mBrowserWindowId, x, y ); + }; + + //////////////////////////////////////////////////////////////////////////////// + // + void keyPress( int key ) + { + int moz_key; + + // The incoming values for 'key' will be the ones from indra_constants.h + // the outgoing values are the ones from llwebkitlib.h + + switch((KEY)key) + { + // This is the list that the qtwebkit-llmozlib implementation actually maps into Qt keys. +// case KEY_XXX: moz_key = LL_DOM_VK_CANCEL; break; +// case KEY_XXX: moz_key = LL_DOM_VK_HELP; break; + case KEY_BACKSPACE: moz_key = LL_DOM_VK_BACK_SPACE; break; + case KEY_TAB: moz_key = LL_DOM_VK_TAB; break; +// case KEY_XXX: moz_key = LL_DOM_VK_CLEAR; break; + case KEY_RETURN: moz_key = LL_DOM_VK_RETURN; break; + case KEY_PAD_RETURN: moz_key = LL_DOM_VK_ENTER; break; + case KEY_SHIFT: moz_key = LL_DOM_VK_SHIFT; break; + case KEY_CONTROL: moz_key = LL_DOM_VK_CONTROL; break; + case KEY_ALT: moz_key = LL_DOM_VK_ALT; break; +// case KEY_XXX: moz_key = LL_DOM_VK_PAUSE; break; + case KEY_CAPSLOCK: moz_key = LL_DOM_VK_CAPS_LOCK; break; + case KEY_ESCAPE: moz_key = LL_DOM_VK_ESCAPE; break; + case KEY_PAGE_UP: moz_key = LL_DOM_VK_PAGE_UP; break; + case KEY_PAGE_DOWN: moz_key = LL_DOM_VK_PAGE_DOWN; break; + case KEY_END: moz_key = LL_DOM_VK_END; break; + case KEY_HOME: moz_key = LL_DOM_VK_HOME; break; + case KEY_LEFT: moz_key = LL_DOM_VK_LEFT; break; + case KEY_UP: moz_key = LL_DOM_VK_UP; break; + case KEY_RIGHT: moz_key = LL_DOM_VK_RIGHT; break; + case KEY_DOWN: moz_key = LL_DOM_VK_DOWN; break; +// case KEY_XXX: moz_key = LL_DOM_VK_PRINTSCREEN; break; + case KEY_INSERT: moz_key = LL_DOM_VK_INSERT; break; + case KEY_DELETE: moz_key = LL_DOM_VK_DELETE; break; +// case KEY_XXX: moz_key = LL_DOM_VK_CONTEXT_MENU; break; + + default: + if(key < KEY_SPECIAL) + { + // Pass the incoming key through -- it should be regular ASCII, which should be correct for webkit. + moz_key = key; + } + else + { + // Don't pass through untranslated special keys -- they'll be all wrong. + moz_key = 0; + } + break; + } + +// std::cerr << "keypress, original code = 0x" << std::hex << key << ", converted code = 0x" << std::hex << moz_key << std::dec << std::endl; + + if(moz_key != 0) + { + LLMozLib::getInstance()->keyPress( mBrowserWindowId, moz_key ); + } + + checkEditState(); + }; + + //////////////////////////////////////////////////////////////////////////////// + // + void unicodeInput( const std::string &utf8str ) + { + LLWString wstr = utf8str_to_wstring(utf8str); + + unsigned int i; + for(i=0; i < wstr.size(); i++) + { +// std::cerr << "unicode input, code = 0x" << std::hex << (unsigned long)(wstr[i]) << std::dec << std::endl; + + LLMozLib::getInstance()->unicodeInput(mBrowserWindowId, wstr[i]); + } + + checkEditState(); + }; + + void checkEditState(void) + { + bool can_cut = LLMozLib::getInstance()->userActionIsEnabled( mBrowserWindowId, LLMozLib::UA_EDIT_CUT); + bool can_copy = LLMozLib::getInstance()->userActionIsEnabled( mBrowserWindowId, LLMozLib::UA_EDIT_COPY); + bool can_paste = LLMozLib::getInstance()->userActionIsEnabled( mBrowserWindowId, LLMozLib::UA_EDIT_PASTE); + + if((can_cut != mCanCut) || (can_copy != mCanCopy) || (can_paste != mCanPaste)) + { + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_state"); + + if(can_cut != mCanCut) + { + mCanCut = can_cut; + message.setValueBoolean("cut", can_cut); + } + + if(can_copy != mCanCopy) + { + mCanCopy = can_copy; + message.setValueBoolean("copy", can_copy); + } + + if(can_paste != mCanPaste) + { + mCanPaste = can_paste; + message.setValueBoolean("paste", can_paste); + } + + sendMessage(message); + + } + } + +}; + +MediaPluginWebKit::MediaPluginWebKit(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data) : + MediaPluginBase(host_send_func, host_user_data) +{ +// std::cerr << "MediaPluginWebKit constructor" << std::endl; + + mBrowserWindowId = 0; + mBrowserInitialized = false; + mNeedsUpdate = true; + mCanCut = false; + mCanCopy = false; + mCanPaste = false; +} + +MediaPluginWebKit::~MediaPluginWebKit() +{ + // unhook observer + LLMozLib::getInstance()->remObserver( mBrowserWindowId, this ); + + // clean up + LLMozLib::getInstance()->reset(); + +// std::cerr << "MediaPluginWebKit destructor" << std::endl; +} + +void MediaPluginWebKit::receiveMessage(const char *message_string) +{ +// std::cerr << "MediaPluginWebKit::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_BROWSER] = LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER_VERSION; + message.setValueLLSD("versions", versions); + + std::string plugin_version = "Webkit media plugin, Webkit version "; + plugin_version += LLMozLib::getInstance()->getVersion(); + message.setValue("plugin_version", plugin_version); + sendMessage(message); + + // Plugin gets to decide the texture parameters to use. + mDepth = 4; + + message.setMessage(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params"); + message.setValueS32("default_width", 800); + message.setValueS32("default_height", 600); + message.setValueS32("depth", mDepth); + message.setValueU32("internalformat", GL_RGBA); + message.setValueU32("format", GL_RGBA); + message.setValueU32("type", GL_UNSIGNED_BYTE); + message.setValueBoolean("coords_opengl", true); + sendMessage(message); + } + else if(message_name == "idle") + { + // no response is necessary here. + F64 time = message_in.getValueReal("time"); + + // Convert time to milliseconds for update() + update((int)(time * 1000.0f)); + } + else if(message_name == "cleanup") + { + // TODO: clean up here + } + else if(message_name == "shm_added") + { + SharedSegmentInfo info; + U64 address_lo = message_in.getValueU32("address"); + U64 address_hi = message_in.hasValue("address_1") ? message_in.getValueU32("address_1") : 0; + info.mAddress = (void*)((address_lo) | + (address_hi * (U64(1)<<31))); + info.mSize = (size_t)message_in.getValueS32("size"); + std::string name = message_in.getValue("name"); + + +// std::cerr << "MediaPluginWebKit::receiveMessage: shared memory added, name: " << name +// << ", size: " << info.mSize +// << ", address: " << info.mAddress +// << std::endl; + + mSharedSegments.insert(SharedSegmentMap::value_type(name, info)); + + } + else if(message_name == "shm_remove") + { + std::string name = message_in.getValue("name"); + +// std::cerr << "MediaPluginWebKit::receiveMessage: shared memory remove, name = " << name << std::endl; + + 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(); + } + mSharedSegments.erase(iter); + } + else + { +// std::cerr << "MediaPluginWebKit::receiveMessage: unknown shared memory region!" << std::endl; + } + + // Send the response so it can be cleaned up. + LLPluginMessage message("base", "shm_remove_response"); + message.setValue("name", name); + sendMessage(message); + } + else + { +// std::cerr << "MediaPluginWebKit::receiveMessage: unknown base message: " << message_name << std::endl; + } + } + else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA) + { + 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"); + + 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; + mWidth = width; + mHeight = height; + + // initialize (only gets called once) + initBrowser(); + + // size changed so tell the browser + LLMozLib::getInstance()->setSize( mBrowserWindowId, mWidth, mHeight ); + +// std::cerr << "webkit plugin: set size to " << mWidth << " x " << mHeight +// << ", rowspan is " << LLMozLib::getInstance()->getBrowserRowSpan(mBrowserWindowId) << std::endl; + + S32 real_width = LLMozLib::getInstance()->getBrowserRowSpan(mBrowserWindowId) / LLMozLib::getInstance()->getBrowserDepth(mBrowserWindowId); + + // The actual width the browser will be drawing to is probably smaller... let the host know by modifying texture_width in the response. + if(real_width <= texture_width) + { + texture_width = real_width; + } + else + { + // This won't work -- it'll be bigger than the allocated memory. This is a fatal error. +// std::cerr << "Fatal error: browser rowbytes greater than texture width" << std::endl; + mDeleteMe = true; + return; + } + + mTextureWidth = texture_width; + mTextureHeight = 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); + + } + else if(message_name == "load_uri") + { + std::string uri = message_in.getValue("uri"); + +// std::cout << "loading URI: " << uri << std::endl; + + if(!uri.empty()) + { + LLMozLib::getInstance()->navigateTo( mBrowserWindowId, uri ); + } + } + 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"); + // std::string modifiers = message.getValue("modifiers"); + + if(event == "down") + { + mouseDown(x, y); + //std::cout << "Mouse down at " << x << " x " << y << std::endl; + } + else if(event == "up") + { + mouseUp(x, y); + //std::cout << "Mouse up at " << x << " x " << y << std::endl; + } + else if(event == "move") + { + mouseMove(x, y); + //std::cout << ">>>>>>>>>>>>>>>>>>>> Mouse move at " << x << " x " << y << std::endl; + } + } + else if(message_name == "scroll_event") + { + // S32 x = message_in.getValueS32("x"); + S32 y = message_in.getValueS32("y"); + // std::string modifiers = message.getValue("modifiers"); + + // We currently ignore horizontal scrolling. + // The scroll values are roughly 1 per wheel click, so we need to magnify them by some factor. + // Arbitrarily, I choose 16. + y *= 16; + LLMozLib::getInstance()->scrollByLines(mBrowserWindowId, y); + } + else if(message_name == "key_event") + { + std::string event = message_in.getValue("event"); + + // act on "key down" or "key repeat" + if ( (event == "down") || (event == "repeat") ) + { + S32 key = message_in.getValueS32("key"); + keyPress( key ); + }; + } + else if(message_name == "text_event") + { + std::string text = message_in.getValue("text"); + + unicodeInput(text); + } + if(message_name == "edit_cut") + { + LLMozLib::getInstance()->userAction( mBrowserWindowId, LLMozLib::UA_EDIT_CUT ); + } + if(message_name == "edit_copy") + { + LLMozLib::getInstance()->userAction( mBrowserWindowId, LLMozLib::UA_EDIT_COPY ); + } + if(message_name == "edit_paste") + { + LLMozLib::getInstance()->userAction( mBrowserWindowId, LLMozLib::UA_EDIT_PASTE ); + } + else + { +// std::cerr << "MediaPluginWebKit::receiveMessage: unknown media message: " << message_string << std::endl; + }; + } + else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER) + { + if(message_name == "focus") + { + bool val = message_in.getValueBoolean("focused"); + LLMozLib::getInstance()->focusBrowser( mBrowserWindowId, val ); + } + else if(message_name == "clear_cache") + { + LLMozLib::getInstance()->clearCache(); + } + else if(message_name == "clear_cookies") + { + LLMozLib::getInstance()->clearAllCookies(); + } + else if(message_name == "enable_cookies") + { + bool val = message_in.getValueBoolean("enable"); + LLMozLib::getInstance()->enableCookies( val ); + } + else if(message_name == "proxy_setup") + { + bool val = message_in.getValueBoolean("enable"); + std::string host = message_in.getValue("host"); + int port = message_in.getValueS32("port"); + LLMozLib::getInstance()->enableProxy( val, host, port ); + } + else if(message_name == "browse_stop") + { + LLMozLib::getInstance()->userAction( mBrowserWindowId, LLMozLib::UA_NAVIGATE_STOP ); + } + else if(message_name == "browse_reload") + { + // foo = message_in.getValueBoolean("ignore_cache"); + LLMozLib::getInstance()->userAction( mBrowserWindowId, LLMozLib::UA_NAVIGATE_RELOAD ); + } + else if(message_name == "browse_forward") + { + LLMozLib::getInstance()->userAction( mBrowserWindowId, LLMozLib::UA_NAVIGATE_FORWARD ); + } + else if(message_name == "browse_back") + { + LLMozLib::getInstance()->userAction( mBrowserWindowId, LLMozLib::UA_NAVIGATE_BACK ); + } + else if(message_name == "set_status_redirect") + { + int code = message_in.getValueS32("code"); + std::string url = message_in.getValue("url"); + if ( 404 == code ) // browser lib only supports 404 right now + { + LLMozLib::getInstance()->set404RedirectUrl( mBrowserWindowId, url ); + }; + } + else if(message_name == "set_user_agent") + { + std::string user_agent = message_in.getValue("user_agent"); + LLMozLib::getInstance()->setBrowserAgentId( user_agent ); + } + else if(message_name == "init_history") + { + // Initialize browser history + LLSD history = message_in.getValueLLSD("history"); + // First, clear the URL history + LLMozLib::getInstance()->clearHistory(mBrowserWindowId); + // Then, add the history items in order + LLSD::array_iterator iter_history = history.beginArray(); + LLSD::array_iterator end_history = history.endArray(); + for(; iter_history != end_history; ++iter_history) + { + std::string url = (*iter_history).asString(); + if(! url.empty()) { + LLMozLib::getInstance()->prependHistoryUrl(mBrowserWindowId, url); + } + } + } + else + { +// std::cerr << "MediaPluginWebKit::receiveMessage: unknown media_browser message: " << message_string << std::endl; + }; + } + else + { +// std::cerr << "MediaPluginWebKit::receiveMessage: unknown message class: " << message_class << std::endl; + }; + } +} + +int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data) +{ + MediaPluginWebKit *self = new MediaPluginWebKit(host_send_func, host_user_data); + *plugin_send_func = MediaPluginWebKit::staticReceiveMessage; + *plugin_user_data = (void*)self; + + return 0; +} + diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 30904bd6c9..ae3fa9a324 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -18,8 +18,8 @@ include(LLImage) include(LLImageJ2COJ) include(LLInventory) include(LLMath) -include(LLMedia) include(LLMessage) +include(LLPlugin) include(LLPrimitive) include(LLRender) include(LLUI) @@ -29,7 +29,6 @@ include(LLXML) include(LLXUIXML) include(LScript) include(Linking) -include(Mozlib) include(NDOF) include(GooglePerfTools) include(TemplateCheck) @@ -51,8 +50,8 @@ include_directories( ${LLIMAGE_INCLUDE_DIRS} ${LLINVENTORY_INCLUDE_DIRS} ${LLMATH_INCLUDE_DIRS} - ${LLMEDIA_INCLUDE_DIRS} ${LLMESSAGE_INCLUDE_DIRS} + ${LLPLUGIN_INCLUDE_DIRS} ${LLPRIMITIVE_INCLUDE_DIRS} ${LLRENDER_INCLUDE_DIRS} ${LLUI_INCLUDE_DIRS} @@ -169,9 +168,8 @@ set(viewer_SOURCE_FILES llfloatergroups.cpp llfloaterhandler.cpp llfloaterhardwaresettings.cpp - llfloaterhtml.cpp llfloaterhtmlcurrency.cpp - llfloaterhtmlhelp.cpp + llfloatermediabrowser.cpp llfloaterhtmlsimple.cpp llfloaterhud.cpp llfloaterimagepreview.cpp @@ -309,12 +307,14 @@ set(viewer_SOURCE_FILES llpanelinventory.cpp llpanelimcontrolpanel.cpp llpanelland.cpp + llpanellandaudio.cpp llpanellandmarks.cpp llpanellandmedia.cpp llpanellogin.cpp llpanellookinfo.cpp llpanellooks.cpp llpanelmedia.cpp + llpanelmediahud.cpp llpanelmeprofile.cpp llpanelmovetip.cpp llpanelobject.cpp @@ -428,6 +428,8 @@ set(viewer_SOURCE_FILES llviewerkeyboard.cpp llviewerlayer.cpp llviewermedia.cpp + llviewermediafocus.cpp + llviewermedia_streamingaudio.cpp llviewermenu.cpp llviewermenufile.cpp llviewermessage.cpp @@ -479,7 +481,7 @@ set(viewer_SOURCE_FILES llwearabledictionary.cpp llwearablelist.cpp llweb.cpp - llwebbrowserctrl.cpp + llmediactrl.cpp llwind.cpp llwlanimator.cpp llwldaycycle.cpp @@ -620,9 +622,8 @@ set(viewer_HEADER_FILES llfloatergroups.h llfloaterhandler.h llfloaterhardwaresettings.h - llfloaterhtml.h llfloaterhtmlcurrency.h - llfloaterhtmlhelp.h + llfloatermediabrowser.h llfloaterhtmlsimple.h llfloaterhud.h llfloaterimagepreview.h @@ -758,12 +759,14 @@ set(viewer_HEADER_FILES llpanelinventory.h llpanelimcontrolpanel.h llpanelland.h + llpanellandaudio.h llpanellandmarks.h llpanellandmedia.h llpanellogin.h llpanellookinfo.h llpanellooks.h llpanelmedia.h + llpanelmediahud.h llpanelmeprofile.h llpanelmovetip.h llpanelobject.h @@ -879,6 +882,8 @@ set(viewer_HEADER_FILES llviewerkeyboard.h llviewerlayer.h llviewermedia.h + llviewermediaobserver.h + llviewermediafocus.h llviewermenu.h llviewermenufile.h llviewermessage.h @@ -932,7 +937,7 @@ set(viewer_HEADER_FILES llwearabledictionary.h llwearablelist.h llweb.h - llwebbrowserctrl.h + llmediactrl.h llwind.h llwindebug.h llwlanimator.h @@ -1132,6 +1137,9 @@ endif (WINDOWS) set(viewer_XUI_FILES skins/default/colors.xml skins/default/textures/textures.xml + + + ) file(GLOB DEFAULT_XUI_FILE_GLOB_LIST ${CMAKE_CURRENT_SOURCE_DIR}/skins/default/xui/en/*.xml) @@ -1329,6 +1337,9 @@ if (WINDOWS) --touch=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/touched.bat DEPENDS ${VIEWER_BINARY_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py ) + + add_dependencies(${VIEWER_BINARY_NAME} SLPlugin media_plugin_quicktime media_plugin_webkit media_plugin_flash_activex media_plugin_awesomium) + if (PACKAGE) add_custom_target(package ALL DEPENDS ${CMAKE_CFG_INTDIR}/touched.bat) add_dependencies(package windows-updater windows-crash-logger) @@ -1341,8 +1352,8 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${LLIMAGE_LIBRARIES} ${LLIMAGEJ2COJ_LIBRARIES} ${LLINVENTORY_LIBRARIES} - ${LLMEDIA_LIBRARIES} ${LLMESSAGE_LIBRARIES} + ${LLPLUGIN_LIBRARIES} ${LLPRIMITIVE_LIBRARIES} ${LLRENDER_LIBRARIES} ${FREETYPE_LIBRARIES} @@ -1362,11 +1373,9 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${OPENGL_LIBRARIES} ${FMODWRAPPER_LIBRARY} ${OPENGL_LIBRARIES} - ${MOZLIB_LIBRARIES} ${SDL_LIBRARY} ${SMARTHEAP_LIBRARY} ${UI_LIBRARIES} - ${QUICKTIME_LIBRARY} ${WINDOWS_LIBRARIES} ${XMLRPCEPI_LIBRARIES} ${ELFIO_LIBRARIES} @@ -1407,6 +1416,8 @@ if (LINUX) DEPENDS secondlife-stripped ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py ) + add_dependencies(${VIEWER_BINARY_NAME} SLPlugin media_plugin_gstreamer010 media_plugin_webkit) + if (NOT INSTALL) add_custom_target(package ALL DEPENDS ${product}.tar.bz2) add_dependencies(package linux-crash-logger-strip-target) @@ -1445,6 +1456,8 @@ if (DARWIN) DEPENDS ${VIEWER_BINARY_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py ) + add_dependencies(${VIEWER_BINARY_NAME} SLPlugin media_plugin_quicktime media_plugin_webkit media_plugin_awesomium) + if (PACKAGE) add_custom_target(package ALL DEPENDS ${VIEWER_BINARY_NAME}) add_dependencies(package mac-updater mac-crash-logger) @@ -1510,3 +1523,79 @@ set_source_files_properties( LL_ADD_PROJECT_UNIT_TESTS(${VIEWER_BINARY_NAME} "${viewer_TEST_SOURCE_FILES}") #ADD_VIEWER_BUILD_TEST(llmemoryview viewer) + + +# Don't do these for DARWIN or LINUX here -- they're taken care of by viewer_manifest.py +if (WINDOWS) + get_target_property(BUILT_SLPLUGIN SLPlugin LOCATION) + add_custom_command( + TARGET ${VIEWER_BINARY_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS + -E + copy_if_different + ${BUILT_SLPLUGIN} + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/llplugin + COMMENT "Copying SLPlugin executable to the runtime folder." + ) + + get_target_property(BUILT_WEBKIT_PLUGIN media_plugin_webkit LOCATION) + add_custom_command( + TARGET ${VIEWER_BINARY_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS + -E + copy_if_different + ${BUILT_WEBKIT_PLUGIN} + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/llplugin + COMMENT "Copying WebKit Plugin to the runtime folder." + ) + + get_target_property(BUILT_QUICKTIME_PLUGIN media_plugin_quicktime LOCATION) + add_custom_command( + TARGET ${VIEWER_BINARY_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS + -E + copy_if_different + ${BUILT_QUICKTIME_PLUGIN} + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/llplugin + COMMENT "Copying Quicktime Plugin to the runtime folder." + ) +endif (WINDOWS) + +if (WINDOWS) + get_target_property(BUILT_FLASH_ACTIVEX_PLUGIN media_plugin_flash_activex LOCATION) + add_custom_command( + TARGET ${VIEWER_BINARY_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS + -E + copy_if_different + ${BUILT_FLASH_ACTIVEX_PLUGIN} + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/llplugin + COMMENT "Copying Flash (ActiveX) Plugin to the runtime folder." + ) +endif (WINDOWS) + +if (WINDOWS) + get_target_property(BUILT_AWESOMIUM_PLUGIN media_plugin_awesomium LOCATION) + add_custom_command( + TARGET ${VIEWER_BINARY_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} + ARGS + -E + copy_if_different + ${BUILT_AWESOMIUM_PLUGIN} + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/llplugin + COMMENT "Copying Flash (Awesomium) Plugin to the runtime folder." + ) +endif (WINDOWS) + +if (DARWIN) +# Don't do this here -- it's taken care of by viewer_manifest.py +# add_custom_command(TARGET ${VIEWER_BINARY_NAME} POST_BUILD +# COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../libraries/universal-darwin/lib_release/libllwebkitlib.dylib ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/llplugin/ +# DEPENDS ${CMAKE_SOURCE_DIR}/../libraries/universal-darwin/lib_release/libllwebkitlib.dylib +# ) +endif (DARWIN) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 9835c5a4cf..4e64b8662c 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -166,17 +166,6 @@ <key>Value</key> <real>0.5</real> </map> - <key>AudioLevelDistance</key> - <map> - <key>Comment</key> - <string>Scale factor for audio engine (multiple of world scale, 2.0 = audio falls off twice as fast)</string> - <key>Persist</key> - <integer>1</integer> - <key>Type</key> - <string>F32</string> - <key>Value</key> - <real>1.0</real> - </map> <key>AudioLevelDoppler</key> <map> <key>Comment</key> @@ -1673,6 +1662,17 @@ <key>Value</key> <integer>0</integer> </map> + <key>DebugPluginDisableTimeout</key> + <map> + <key>Comment</key> + <string>Disable the code which watches for plugins that are crashed or hung</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> <key>DebugShowColor</key> <map> <key>Comment</key> @@ -4435,7 +4435,40 @@ <key>Value</key> <integer>0</integer> </map> - <key>MemoryLogFrequency</key> + <key>MediaControlFadeTime</key> + <map> + <key>Comment</key> + <string>Amount of time (in seconds) that the media control fades</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>1.5</real> + </map> + <key>MediaControlTimeout</key> + <map> + <key>Comment</key> + <string>Amount of time (in seconds) for media controls to fade with no mouse activity</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>F32</string> + <key>Value</key> + <real>3.0</real> + </map> + <key>MediaOnAPrimUI</key> + <map> + <key>Comment</key> + <string>Whether or not to show the "link sharing" UI</string> + <key>Persist</key> + <integer>1</integer> + <key>Type</key> + <string>Boolean</string> + <key>Value</key> + <integer>0</integer> + </map> + <key>MemoryLogFrequency</key> <map> <key>Comment</key> <string>Seconds between display of Memory in log (0 for never)</string> diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index ae30c1959c..214065f080 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -84,6 +84,7 @@ #include "llviewercamera.h" #include "llviewerdisplay.h" +#include "llviewermediafocus.h" #include "llviewerobjectlist.h" #include "llviewerparcelmgr.h" #include "llviewerstats.h" @@ -5993,6 +5994,7 @@ bool LLAgent::teleportCore(bool is_local) LLFloaterReg::hideInstance("about_land"); LLViewerParcelMgr::getInstance()->deselectLand(); + LLViewerMediaFocus::getInstance()->setFocusFace(false, NULL, 0, NULL); // Close all pie menus, deselect land, etc. // Don't change the camera until we know teleport succeeded. JC diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index fe9eae04a1..76c1d699f6 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -62,6 +62,8 @@ #include "llviewerwindow.h" #include "llviewerdisplay.h" #include "llviewermedia.h" +#include "llviewerparcelmedia.h" +#include "llviewermediafocus.h" #include "llviewermessage.h" #include "llviewerobjectlist.h" #include "llworldmap.h" @@ -109,7 +111,8 @@ #include "llassetstorage.h" #include "llpolymesh.h" #include "llcachename.h" -#include "audioengine.h" +#include "llaudioengine.h" +#include "llstreamingaudio.h" #include "llviewermenu.h" #include "llselectmgr.h" #include "lltrans.h" @@ -1217,6 +1220,14 @@ bool LLAppViewer::cleanup() if (gAudiop) { + // shut down the streaming audio sub-subsystem first, in case it relies on not outliving the general audio subsystem. + + LLStreamingAudioInterface *sai = gAudiop->getStreamingAudioImpl(); + delete sai; + gAudiop->setStreamingAudioImpl(NULL); + + // shut down the audio subsystem + bool want_longname = false; if (gAudiop->getDriverName(want_longname) == "FMOD") { @@ -1450,7 +1461,9 @@ bool LLAppViewer::cleanup() //Note: //LLViewerMedia::cleanupClass() has to be put before gTextureList.shutdown() //because some new image might be generated during cleaning up media. --bao + LLViewerMediaFocus::cleanupClass(); LLViewerMedia::cleanupClass(); + LLViewerParcelMedia::cleanupClass(); gTextureList.shutdown(); // shutdown again in case a callback added something LLUIImageList::getInstance()->cleanUp(); @@ -3586,6 +3599,9 @@ void LLAppViewer::idle() gAgent.updateCamera(); } + // update media focus + LLViewerMediaFocus::getInstance()->update(); + // objects and camera should be in sync, do LOD calculations now { LLFastTimer t(FTM_LOD_UPDATE); diff --git a/indra/newview/llappviewerlinux.cpp b/indra/newview/llappviewerlinux.cpp index cd35c28aa7..ed291c16a8 100644 --- a/indra/newview/llappviewerlinux.cpp +++ b/indra/newview/llappviewerlinux.cpp @@ -451,7 +451,7 @@ gboolean viewer_app_api_GoSLURL(ViewerAppAPI *obj, gchar *slurl, gboolean **succ llinfos << "Was asked to go to slurl: " << slurl << llendl; std::string url = slurl; - LLWebBrowserCtrl* web = NULL; + LLMediaCtrl* web = NULL; const bool trusted_browser = false; if (LLURLDispatcher::dispatch(url, web, trusted_browser)) { diff --git a/indra/newview/llappviewermacosx.cpp b/indra/newview/llappviewermacosx.cpp index ca005946c9..2b3939d92f 100644 --- a/indra/newview/llappviewermacosx.cpp +++ b/indra/newview/llappviewermacosx.cpp @@ -50,7 +50,7 @@ #include <Carbon/Carbon.h> #include "lldir.h" #include <signal.h> -class LLWebBrowserCtrl; // for LLURLDispatcher +class LLMediaCtrl; // for LLURLDispatcher namespace { @@ -476,7 +476,7 @@ OSErr AEGURLHandler(const AppleEvent *messagein, AppleEvent *reply, long refIn) url.replace(0, prefix.length(), "secondlife:///app/"); } - LLWebBrowserCtrl* web = NULL; + LLMediaCtrl* web = NULL; const bool trusted_browser = false; LLURLDispatcher::dispatch(url, web, trusted_browser); } diff --git a/indra/newview/llassetuploadresponders.cpp b/indra/newview/llassetuploadresponders.cpp index bb3b9087a1..827ae5855b 100644 --- a/indra/newview/llassetuploadresponders.cpp +++ b/indra/newview/llassetuploadresponders.cpp @@ -288,7 +288,8 @@ void LLNewAgentInventoryResponder::uploadComplete(const LLSD& content) LLFloaterInventory* view = LLFloaterInventory::getActiveInventory(); if(view) { - LLUICtrl* focus_ctrl = gFocusMgr.getKeyboardFocus(); + LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); + view->getPanel()->setSelection(content["new_inventory_item"].asUUID(), TAKE_FOCUS_NO); if((LLAssetType::AT_TEXTURE == asset_type || LLAssetType::AT_SOUND == asset_type) && LLFilePicker::instance().getFileCount() <= FILE_COUNT_DISPLAY_THRESHOLD) @@ -297,7 +298,7 @@ void LLNewAgentInventoryResponder::uploadComplete(const LLSD& content) } //LLFloaterInventory::dumpSelectionInformation((void*)view); // restore keyboard focus - gFocusMgr.setKeyboardFocus(focus_ctrl); + gFocusMgr.setKeyboardFocus(focus); } } else diff --git a/indra/newview/llaudiosourcevo.h b/indra/newview/llaudiosourcevo.h index e7bb2837a4..4b70f8bc20 100644 --- a/indra/newview/llaudiosourcevo.h +++ b/indra/newview/llaudiosourcevo.h @@ -34,7 +34,7 @@ #ifndef LL_LLAUDIOSOURCEVO_H #define LL_LLAUDIOSOURCEVO_H -#include "audioengine.h" +#include "llaudioengine.h" #include "llviewerobject.h" class LLViewerObject; diff --git a/indra/newview/llchatbar.cpp b/indra/newview/llchatbar.cpp index bea11ecfeb..3ee2c93961 100644 --- a/indra/newview/llchatbar.cpp +++ b/indra/newview/llchatbar.cpp @@ -678,7 +678,7 @@ public: // Your code here bool handle(const LLSD& tokens, const LLSD& query_map, - LLWebBrowserCtrl* web) + LLMediaCtrl* web) { if (tokens.size() < 2) return false; S32 channel = tokens[0].asInteger(); diff --git a/indra/newview/llcommandhandler.cpp b/indra/newview/llcommandhandler.cpp index 422c94ade5..a04182a910 100644 --- a/indra/newview/llcommandhandler.cpp +++ b/indra/newview/llcommandhandler.cpp @@ -55,7 +55,7 @@ public: bool dispatch(const std::string& cmd, const LLSD& params, const LLSD& query_map, - LLWebBrowserCtrl* web, + LLMediaCtrl* web, bool trusted_browser); private: @@ -84,7 +84,7 @@ void LLCommandHandlerRegistry::add(const char* cmd, bool require_trusted_browser bool LLCommandHandlerRegistry::dispatch(const std::string& cmd, const LLSD& params, const LLSD& query_map, - LLWebBrowserCtrl* web, + LLMediaCtrl* web, bool trusted_browser) { std::map<std::string, LLCommandHandlerInfo>::iterator it = mMap.find(cmd); @@ -126,7 +126,7 @@ LLCommandHandler::~LLCommandHandler() bool LLCommandDispatcher::dispatch(const std::string& cmd, const LLSD& params, const LLSD& query_map, - LLWebBrowserCtrl* web, + LLMediaCtrl* web, bool trusted_browser) { return LLCommandHandlerRegistry::instance().dispatch( diff --git a/indra/newview/llcommandhandler.h b/indra/newview/llcommandhandler.h index ab4c2cc488..5cb3ee73d4 100644 --- a/indra/newview/llcommandhandler.h +++ b/indra/newview/llcommandhandler.h @@ -47,7 +47,7 @@ public: // Your code here bool handle(const LLSD& tokens, const LLSD& query_map, - LLWebBrowserCtrl* web) + LLMediaCtrl* web) { if (tokens.size() < 1) return false; LLUUID id( tokens[0] ); @@ -60,7 +60,7 @@ LLFooHandler gFooHandler; */ -class LLWebBrowserCtrl; +class LLMediaCtrl; class LLCommandHandler { @@ -68,14 +68,14 @@ public: LLCommandHandler(const char* command, bool allow_from_untrusted_browser); // Automatically registers object to get called when // command is executed. All commands can be processed - // in links from LLWebBrowserCtrl, but some (like teleport) + // in links from LLMediaCtrl, but some (like teleport) // should not be allowed from outside the app. virtual ~LLCommandHandler(); virtual bool handle(const LLSD& params, const LLSD& query_map, - LLWebBrowserCtrl* web) = 0; + LLMediaCtrl* web) = 0; // For URL secondlife:///app/foo/bar/baz?cat=1&dog=2 // @params - array of "bar", "baz", possibly empty // @query_map - map of "cat" -> 1, "dog" -> 2, possibly empty @@ -91,7 +91,7 @@ public: static bool dispatch(const std::string& cmd, const LLSD& params, const LLSD& query_map, - LLWebBrowserCtrl* web, + LLMediaCtrl* web, bool trusted_browser); // Execute a command registered via the above mechanism, // passing string parameters. diff --git a/indra/newview/llfloaterabout.cpp b/indra/newview/llfloaterabout.cpp index 3c27d37ee9..56c5eaa70e 100644 --- a/indra/newview/llfloaterabout.cpp +++ b/indra/newview/llfloaterabout.cpp @@ -42,7 +42,7 @@ #include "llcurl.h" #include "llimagej2c.h" -#include "audioengine.h" +#include "llaudioengine.h" #include "llviewertexteditor.h" #include "llviewercontrol.h" @@ -58,7 +58,6 @@ #include "lltrans.h" #include "llappviewer.h" #include "llglheaders.h" -#include "llmediamanager.h" #include "llwindow.h" #include "llbutton.h" @@ -223,18 +222,10 @@ BOOL LLFloaterAbout::postBuild() support.append( gAudiop ? gAudiop->getDriverName(want_fullname) : getString("none") ); support.append("\n"); - LLMediaManager *mgr = LLMediaManager::getInstance(); - if (mgr) - { - LLMediaBase *media_source = mgr->createSourceFromMimeType("http", "text/html"); - if (media_source) - { - support.append(getString("LLMozLibVersion") + " "); - support.append(media_source->getVersion()); - support.append("\n"); - mgr->destroySource(media_source); - } - } + // TODO: Implement media plugin version query + + support.append(getString("LLQtWebkitVersion") + " "); + support.append("\n"); if (gPacketsIn > 0) { diff --git a/indra/newview/llfloaterchat.cpp b/indra/newview/llfloaterchat.cpp index 8c09ee7d48..0dee3a1e83 100644 --- a/indra/newview/llfloaterchat.cpp +++ b/indra/newview/llfloaterchat.cpp @@ -71,7 +71,7 @@ #include "llstylemap.h" // linden library includes -#include "audioengine.h" +#include "llaudioengine.h" #include "llchat.h" #include "llfontgl.h" #include "llrect.h" diff --git a/indra/newview/llfloaterhandler.cpp b/indra/newview/llfloaterhandler.cpp index f229d30488..e50a09ed86 100644 --- a/indra/newview/llfloaterhandler.cpp +++ b/indra/newview/llfloaterhandler.cpp @@ -31,7 +31,7 @@ #include "llfloaterhandler.h" #include "llfloater.h" -#include "llwebbrowserctrl.h" +#include "llmediactrl.h" // register with dispatch via global object LLFloaterHandler gFloaterHandler; @@ -54,7 +54,7 @@ LLFloater* get_parent_floater(LLView* view) } -bool LLFloaterHandler::handle(const LLSD ¶ms, const LLSD &query_map, LLWebBrowserCtrl *web) +bool LLFloaterHandler::handle(const LLSD ¶ms, const LLSD &query_map, LLMediaCtrl *web) { if (params.size() < 2) return false; LLFloater* floater = NULL; diff --git a/indra/newview/llfloaterhandler.h b/indra/newview/llfloaterhandler.h index b08f1f35b4..31ea80c12c 100644 --- a/indra/newview/llfloaterhandler.h +++ b/indra/newview/llfloaterhandler.h @@ -39,7 +39,7 @@ class LLFloaterHandler { public: LLFloaterHandler() : LLCommandHandler("floater", true) { } - bool handle(const LLSD& params, const LLSD& query_map, LLWebBrowserCtrl* web); + bool handle(const LLSD& params, const LLSD& query_map, LLMediaCtrl* web); }; #endif diff --git a/indra/newview/llfloaterhud.cpp b/indra/newview/llfloaterhud.cpp index 4dcf726c9a..047dc2fa92 100644 --- a/indra/newview/llfloaterhud.cpp +++ b/indra/newview/llfloaterhud.cpp @@ -36,7 +36,7 @@ // Viewer libs #include "llviewercontrol.h" -#include "llwebbrowserctrl.h" +#include "llmediactrl.h" #include "llalertdialog.h" // Linden libs @@ -76,7 +76,7 @@ LLFloaterHUD::LLFloaterHUD(const LLSD& key) BOOL LLFloaterHUD::postBuild() { - mWebBrowser = getChild<LLWebBrowserCtrl>("floater_hud_browser" ); + mWebBrowser = getChild<LLMediaCtrl>("floater_hud_browser" ); if (mWebBrowser) { // Open links in internal browser diff --git a/indra/newview/llfloaterhud.h b/indra/newview/llfloaterhud.h index 4772735afc..23ff82362a 100644 --- a/indra/newview/llfloaterhud.h +++ b/indra/newview/llfloaterhud.h @@ -35,7 +35,7 @@ #include "llfloater.h" -class LLWebBrowserCtrl; +class LLMediaCtrl; class LLFloaterHUD : public LLFloater { @@ -50,7 +50,7 @@ private: /*virtual*/ ~LLFloaterHUD(); private: - LLWebBrowserCtrl* mWebBrowser; ///< the actual web browser control + LLMediaCtrl* mWebBrowser; ///< the actual web browser control }; #endif // LL_LLFLOATERHUD_H diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp index 1746d6d435..4cd09faaaf 100644 --- a/indra/newview/llfloaterland.cpp +++ b/indra/newview/llfloaterland.cpp @@ -55,6 +55,7 @@ #include "lllineeditor.h" #include "llnamelistctrl.h" #include "llnotify.h" +#include "llpanellandaudio.h" #include "llpanellandmedia.h" #include "llradiogroup.h" #include "llscrolllistctrl.h" @@ -225,6 +226,7 @@ LLFloaterLand::LLFloaterLand(const LLSD& seed) mFactoryMap["land_covenant_panel"] = LLCallbackMap(createPanelLandCovenant, this); mFactoryMap["land_objects_panel"] = LLCallbackMap(createPanelLandObjects, this); mFactoryMap["land_options_panel"] = LLCallbackMap(createPanelLandOptions, this); + mFactoryMap["land_audio_panel"] = LLCallbackMap(createPanelLandAudio, this); mFactoryMap["land_media_panel"] = LLCallbackMap(createPanelLandMedia, this); mFactoryMap["land_access_panel"] = LLCallbackMap(createPanelLandAccess, this); @@ -265,6 +267,7 @@ void LLFloaterLand::refresh() mPanelGeneral->refresh(); mPanelObjects->refresh(); mPanelOptions->refresh(); + mPanelAudio->refresh(); mPanelMedia->refresh(); mPanelAccess->refresh(); } @@ -304,6 +307,14 @@ void* LLFloaterLand::createPanelLandOptions(void* data) } // static +void* LLFloaterLand::createPanelLandAudio(void* data) +{ + LLFloaterLand* self = (LLFloaterLand*)data; + self->mPanelAudio = new LLPanelLandAudio(self->mParcel); + return self->mPanelAudio; +} + +// static void* LLFloaterLand::createPanelLandMedia(void* data) { LLFloaterLand* self = (LLFloaterLand*)data; diff --git a/indra/newview/llfloaterland.h b/indra/newview/llfloaterland.h index 29d1c4dd84..749c395147 100644 --- a/indra/newview/llfloaterland.h +++ b/indra/newview/llfloaterland.h @@ -64,6 +64,7 @@ class LLParcelSelection; class LLPanelLandGeneral; class LLPanelLandObjects; class LLPanelLandOptions; +class LLPanelLandAudio; class LLPanelLandMedia; class LLPanelLandAccess; class LLPanelLandBan; @@ -99,6 +100,7 @@ protected: static void* createPanelLandCovenant(void* data); static void* createPanelLandObjects(void* data); static void* createPanelLandOptions(void* data); + static void* createPanelLandAudio(void* data); static void* createPanelLandMedia(void* data); static void* createPanelLandAccess(void* data); static void* createPanelLandBan(void* data); @@ -112,6 +114,7 @@ protected: LLPanelLandGeneral* mPanelGeneral; LLPanelLandObjects* mPanelObjects; LLPanelLandOptions* mPanelOptions; + LLPanelLandAudio* mPanelAudio; LLPanelLandMedia* mPanelMedia; LLPanelLandAccess* mPanelAccess; LLPanelLandCovenant* mPanelCovenant; diff --git a/indra/newview/llfloatermediabrowser.cpp b/indra/newview/llfloatermediabrowser.cpp new file mode 100644 index 0000000000..c580cdef8a --- /dev/null +++ b/indra/newview/llfloatermediabrowser.cpp @@ -0,0 +1,398 @@ +/** + * @file llfloaterhtmlhelp.cpp + * @brief HTML Help floater - uses embedded web browser control + * + * $LicenseInfo:firstyear=2006&license=viewergpl$ + * + * Copyright (c) 2006-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llfloatermediabrowser.h" +#include "llfloaterhtml.h" + +#include "llfloaterreg.h" +#include "llparcel.h" +#include "llpluginclassmedia.h" +#include "lluictrlfactory.h" +#include "llmediactrl.h" +#include "llviewerwindow.h" +#include "llviewercontrol.h" +#include "llviewerparcelmgr.h" +#include "llweb.h" +#include "llui.h" +#include "roles_constants.h" + +#include "llurlhistory.h" +#include "llmediactrl.h" +#include "llviewermedia.h" +#include "llviewerparcelmedia.h" +#include "llcombobox.h" + + +// TEMP +#include "llsdutil.h" + +LLFloaterMediaBrowser::LLFloaterMediaBrowser(const LLSD& key) + : LLFloater(key) +{ +// LLUICtrlFactory::getInstance()->buildFloater(this, "floater_media_browser.xml"); + +} + +void LLFloaterMediaBrowser::draw() +{ + childSetEnabled("go", !mAddressCombo->getValue().asString().empty()); + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if(parcel) + { + childSetVisible("parcel_owner_controls", LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_CHANGE_MEDIA)); + childSetEnabled("assign", !mAddressCombo->getValue().asString().empty()); + } + bool show_time_controls = false; + bool media_playing = false; + if(mBrowser) + { + LLPluginClassMedia* media_plugin = mBrowser->getMediaPlugin(); + if(media_plugin) + { + show_time_controls = media_plugin->pluginSupportsMediaTime(); + media_playing = media_plugin->getStatus() == LLPluginClassMediaOwner::MEDIA_PLAYING; + } + } + childSetVisible("rewind", show_time_controls); + childSetVisible("play", show_time_controls && ! media_playing); + childSetVisible("pause", show_time_controls && media_playing); + childSetVisible("stop", show_time_controls); + childSetVisible("seek", show_time_controls); + + childSetEnabled("play", ! media_playing); + childSetEnabled("stop", media_playing); + + childSetEnabled("back", mBrowser->canNavigateBack()); + childSetEnabled("forward", mBrowser->canNavigateForward()); + + LLFloater::draw(); +} + +BOOL LLFloaterMediaBrowser::postBuild() +{ + mBrowser = getChild<LLMediaCtrl>("browser"); + mBrowser->addObserver(this); + + mAddressCombo = getChild<LLComboBox>("address"); + mAddressCombo->setCommitCallback(onEnterAddress, this); + + childSetAction("back", onClickBack, this); + childSetAction("forward", onClickForward, this); + childSetAction("reload", onClickRefresh, this); + childSetAction("rewind", onClickRewind, this); + childSetAction("play", onClickPlay, this); + childSetAction("stop", onClickStop, this); + childSetAction("pause", onClickPlay, this); + childSetAction("seek", onClickSeek, this); + childSetAction("go", onClickGo, this); + childSetAction("close", onClickClose, this); + childSetAction("open_browser", onClickOpenWebBrowser, this); + childSetAction("assign", onClickAssign, this); + + buildURLHistory(); + return TRUE; +} + +void LLFloaterMediaBrowser::buildURLHistory() +{ + LLCtrlListInterface* url_list = childGetListInterface("address"); + if (url_list) + { + url_list->operateOnAll(LLCtrlListInterface::OP_DELETE); + } + + // Get all of the entries in the "browser" collection + LLSD browser_history = LLURLHistory::getURLHistory("browser"); + + LLSD::array_iterator iter_history = + browser_history.beginArray(); + LLSD::array_iterator end_history = + browser_history.endArray(); + for(; iter_history != end_history; ++iter_history) + { + std::string url = (*iter_history).asString(); + if(! url.empty()) + url_list->addSimpleElement(url); + } + + // initialize URL history in the plugin + mBrowser->getMediaPlugin()->initializeUrlHistory(browser_history); +} + +std::string LLFloaterMediaBrowser::getSupportURL() +{ + return getString("support_page_url"); +} +void LLFloaterMediaBrowser::onClose(bool app_quitting) +{ + //setVisible(FALSE); + destroy(); +} + +void LLFloaterMediaBrowser::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) +{ + if(event == MEDIA_EVENT_LOCATION_CHANGED) + { + setCurrentURL(self->getLocation()); + } + else if(event == MEDIA_EVENT_NAVIGATE_COMPLETE) + { + // This is the event these flags are sent with. + childSetEnabled("back", self->getHistoryBackAvailable()); + childSetEnabled("forward", self->getHistoryForwardAvailable()); + } +} +void LLFloaterMediaBrowser::setCurrentURL(const std::string& url) +{ + mCurrentURL = url; + + // redirects will navigate momentarily to about:blank, don't add to history + if (mCurrentURL != "about:blank") + { + mAddressCombo->remove(mCurrentURL); + mAddressCombo->add(mCurrentURL, ADD_SORTED); + mAddressCombo->selectByValue(mCurrentURL); + + // Serialize url history + LLURLHistory::removeURL("browser", mCurrentURL); + LLURLHistory::addURL("browser", mCurrentURL); + } + childSetEnabled("back", mBrowser->canNavigateBack()); + childSetEnabled("forward", mBrowser->canNavigateForward()); + childSetEnabled("reload", TRUE); +} + +void LLFloaterMediaBrowser::onOpen(const LLSD& media_url) +{ + LLFloater::onOpen(media_url); + openMedia(media_url.asString()); +} + +//static +void LLFloaterMediaBrowser::onEnterAddress(LLUICtrl* ctrl, void* user_data) +{ + LLFloaterMediaBrowser* self = (LLFloaterMediaBrowser*)user_data; + self->mBrowser->navigateTo(self->mAddressCombo->getValue().asString()); +} + +//static +void LLFloaterMediaBrowser::onClickRefresh(void* user_data) +{ + LLFloaterMediaBrowser* self = (LLFloaterMediaBrowser*)user_data; + + self->mAddressCombo->remove(0); + self->mBrowser->navigateTo(self->mCurrentURL); +} + +//static +void LLFloaterMediaBrowser::onClickForward(void* user_data) +{ + LLFloaterMediaBrowser* self = (LLFloaterMediaBrowser*)user_data; + + self->mBrowser->navigateForward(); +} + +//static +void LLFloaterMediaBrowser::onClickBack(void* user_data) +{ + LLFloaterMediaBrowser* self = (LLFloaterMediaBrowser*)user_data; + + self->mBrowser->navigateBack(); +} + +//static +void LLFloaterMediaBrowser::onClickGo(void* user_data) +{ + LLFloaterMediaBrowser* self = (LLFloaterMediaBrowser*)user_data; + + self->mBrowser->navigateTo(self->mAddressCombo->getValue().asString()); +} + +//static +void LLFloaterMediaBrowser::onClickClose(void* user_data) +{ + LLFloaterMediaBrowser* self = (LLFloaterMediaBrowser*)user_data; + + self->closeFloater(); +} + +//static +void LLFloaterMediaBrowser::onClickOpenWebBrowser(void* user_data) +{ + LLFloaterMediaBrowser* self = (LLFloaterMediaBrowser*)user_data; + + std::string url = self->mCurrentURL.empty() ? + self->mBrowser->getHomePageUrl() : + self->mCurrentURL; + LLWeb::loadURLExternal(url); +} + +void LLFloaterMediaBrowser::onClickAssign(void* user_data) +{ + LLFloaterMediaBrowser* self = (LLFloaterMediaBrowser*)user_data; + + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (!parcel) + { + return; + } + std::string media_url = self->mAddressCombo->getValue().asString(); + LLStringUtil::trim(media_url); + + if(parcel->getMediaType() != "text/html") + { + parcel->setMediaURL(media_url); + parcel->setMediaCurrentURL(media_url); + parcel->setMediaType(std::string("text/html")); + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel, true ); + LLViewerParcelMedia::sendMediaNavigateMessage(media_url); + LLViewerParcelMedia::stop(); + // LLViewerParcelMedia::update( parcel ); + } + LLViewerParcelMedia::sendMediaNavigateMessage(media_url); +} +//static +void LLFloaterMediaBrowser::onClickRewind(void* user_data) +{ + LLFloaterMediaBrowser* self = (LLFloaterMediaBrowser*)user_data; + + if(self->mBrowser->getMediaPlugin()) + self->mBrowser->getMediaPlugin()->start(-2.0f); +} +//static +void LLFloaterMediaBrowser::onClickPlay(void* user_data) +{ + LLFloaterMediaBrowser* self = (LLFloaterMediaBrowser*)user_data; + + LLPluginClassMedia* plugin = self->mBrowser->getMediaPlugin(); + if(plugin) + { + if(plugin->getStatus() == LLPluginClassMediaOwner::MEDIA_PLAYING) + { + plugin->pause(); + } + else + { + plugin->start(); + } + } +} +//static +void LLFloaterMediaBrowser::onClickStop(void* user_data) +{ + LLFloaterMediaBrowser* self = (LLFloaterMediaBrowser*)user_data; + + if(self->mBrowser->getMediaPlugin()) + self->mBrowser->getMediaPlugin()->stop(); +} +//static +void LLFloaterMediaBrowser::onClickSeek(void* user_data) +{ + LLFloaterMediaBrowser* self = (LLFloaterMediaBrowser*)user_data; + + if(self->mBrowser->getMediaPlugin()) + self->mBrowser->getMediaPlugin()->start(2.0f); +} +void LLFloaterMediaBrowser::openMedia(const std::string& media_url) +{ + mBrowser->setHomePageUrl(media_url); + mBrowser->navigateTo(media_url); + setCurrentURL(media_url); +} +//////////////////////////////////////////////////////////////////////////////// +// + +LLViewerHtmlHelp gViewerHtmlHelp; + + +//////////////////////////////////////////////////////////////////////////////// +// +LLViewerHtmlHelp::LLViewerHtmlHelp() +{ + + LLUI::setHtmlHelp(this); +} + +LLViewerHtmlHelp::~LLViewerHtmlHelp() +{ + + LLUI::setHtmlHelp(NULL); +} + +void LLViewerHtmlHelp::show() +{ + show(""); +} + +void LLViewerHtmlHelp::show(std::string url) +{ + LLFloaterMediaBrowser* floater_html = dynamic_cast<LLFloaterMediaBrowser*>(LLFloaterReg::getInstance("media_browser")); + floater_html->setVisible(FALSE); + + if (url.empty()) + { + url = floater_html->getSupportURL(); + } + + if (gSavedSettings.getBOOL("UseExternalBrowser")) + { + LLSD notificationData; + notificationData["url"] = url; + + LLNotifications::instance().add("ClickOpenF1Help", notificationData, LLSD(), onClickF1HelpLoadURL); + floater_html->closeFloater(); + } + else + { + // don't wait, just do it + floater_html->setVisible(TRUE); + floater_html->openMedia(url); + } +} + +// static +bool LLViewerHtmlHelp::onClickF1HelpLoadURL(const LLSD& notification, const LLSD& response) +{ + LLFloaterMediaBrowser* floater_html = dynamic_cast<LLFloaterMediaBrowser*>(LLFloaterReg::getInstance("media_browser")); + floater_html->setVisible(FALSE); + std::string url = floater_html->getSupportURL(); + S32 option = LLNotification::getSelectedOption(notification, response); + if (option == 0) + { + LLWeb::loadURL(url); + } + floater_html->closeFloater(); + return false; +} + diff --git a/indra/newview/llfloatermediabrowser.h b/indra/newview/llfloatermediabrowser.h new file mode 100644 index 0000000000..76e8b517a0 --- /dev/null +++ b/indra/newview/llfloatermediabrowser.h @@ -0,0 +1,99 @@ +/** + * @file llfloatermediabrowser.h + * @brief HTML Help floater - uses embedded web browser control + * + * $LicenseInfo:firstyear=2006&license=viewergpl$ + * + * Copyright (c) 2006-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLFLOATERMEDIABROWSER_H +#define LL_LLFLOATERMEDIABROWSER_H + +#include "llhtmlhelp.h" +#include "llfloater.h" +#include "llmediactrl.h" + +class LLViewerHtmlHelp : public LLHtmlHelp +{ +public: + LLViewerHtmlHelp(); + virtual ~LLViewerHtmlHelp(); + + /*virtual*/ void show(); + /*virtual*/ void show(std::string start_url); + void show(std::string start_url, std::string title); + + static bool onClickF1HelpLoadURL(const LLSD& notification, const LLSD& response); + +}; + +class LLComboBox; +class LLMediaCtrl; + +class LLFloaterMediaBrowser : + public LLFloater, + public LLViewerMediaObserver +{ +public: + LLFloaterMediaBrowser(const LLSD& key); + + /*virtual*/ BOOL postBuild(); + /*virtual*/ void onClose(bool app_quitting); + /*virtual*/ void draw(); + /*virtual*/ void onOpen(const LLSD& key); + + // inherited from LLViewerMediaObserver + /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); + + void openMedia(const std::string& media_url); + void buildURLHistory(); + std::string getSupportURL(); + void setCurrentURL(const std::string& url); + + static void onEnterAddress(LLUICtrl* ctrl, void* user_data); + static void onClickRefresh(void* user_data); + static void onClickBack(void* user_data); + static void onClickForward(void* user_data); + static void onClickGo(void* user_data); + static void onClickClose(void* user_data); + static void onClickOpenWebBrowser(void* user_data); + static void onClickAssign(void* user_data); + static void onClickRewind(void* user_data); + static void onClickPlay(void* user_data); + static void onClickStop(void* user_data); + static void onClickSeek(void* user_data); + +private: + LLMediaCtrl* mBrowser; + LLComboBox* mAddressCombo; + std::string mCurrentURL; +}; + +extern LLViewerHtmlHelp gViewerHtmlHelp; + +#endif // LL_LLFLOATERMEDIABROWSER_H + diff --git a/indra/newview/llfloaterparcel.cpp b/indra/newview/llfloaterparcel.cpp index af42ce4f4a..44270683a0 100644 --- a/indra/newview/llfloaterparcel.cpp +++ b/indra/newview/llfloaterparcel.cpp @@ -55,7 +55,7 @@ public: // requires trusted browser to trigger LLParcelHandler() : LLCommandHandler("parcel", true) { } bool handle(const LLSD& params, const LLSD& query_map, - LLWebBrowserCtrl* web) + LLMediaCtrl* web) { if (params.size() < 2) { diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 79f8f412fd..b395e1128c 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -99,6 +99,8 @@ #include "pipeline.h" #include "lluictrlfactory.h" #include "llboost.h" +#include "llviewermedia.h" +#include "llpluginclassmedia.h" //RN temporary includes for resolution switching @@ -166,9 +168,8 @@ void LLVoiceSetKeyDialog::onCancel(void* user_data) // if creating/destroying these is too slow, we'll need to create // a static member and update all our static callbacks -void free_web_media(LLMediaBase *media_source); void handleNameTagOptionChanged(const LLSD& newvalue); -LLMediaBase *get_web_media(); +viewer_media_t get_web_media(); bool callback_clear_browser_cache(const LLSD& notification, const LLSD& response); bool callback_skip_dialogs(const LLSD& notification, const LLSD& response, LLFloaterPreference* floater); @@ -177,42 +178,13 @@ bool callback_reset_dialogs(const LLSD& notification, const LLSD& response, LLFl bool extractWindowSizeFromString(const std::string& instr, U32 &width, U32 &height); void fractionFromDecimal(F32 decimal_val, S32& numerator, S32& denominator); -LLMediaBase *get_web_media() +viewer_media_t get_web_media() { - LLMediaBase *media_source; - LLMediaManager *mgr = LLMediaManager::getInstance(); - - if (!mgr) - { - llwarns << "cannot get media manager" << llendl; - return NULL; - } - - media_source = mgr->createSourceFromMimeType("http", "text/html" ); - if ( !media_source ) - { - llwarns << "media source create failed " << llendl; - return NULL; - } + viewer_media_t media_source = LLViewerMedia::newMediaImpl("", LLUUID::null, 0, 0, 0, 0, "text/html"); return media_source; } -void free_web_media(LLMediaBase *media_source) -{ - if (!media_source) - return; - - LLMediaManager *mgr = LLMediaManager::getInstance(); - if (!mgr) - { - llwarns << "cannot get media manager" << llendl; - return; - } - - mgr->destroySource(media_source); -} - bool callback_clear_browser_cache(const LLSD& notification, const LLSD& response) { @@ -220,10 +192,9 @@ bool callback_clear_browser_cache(const LLSD& notification, const LLSD& response if ( option == 0 ) // YES { // clean web - LLMediaBase *media_source = get_web_media(); - if (media_source) - media_source->clearCache(); - free_web_media(media_source); + viewer_media_t media_source = get_web_media(); + if (media_source && media_source->hasMedia()) + media_source->getMediaPlugin()->clear_cache(); // clean nav bar history LLNavigationBar::getInstance()->clearHistoryCache(); @@ -434,20 +405,18 @@ void LLFloaterPreference::apply() std::string cache_location = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, ""); childSetText("cache_location", cache_location); - LLMediaBase *media_source = get_web_media(); - if (media_source) + viewer_media_t media_source = get_web_media(); + if (media_source && media_source->hasMedia()) { - media_source->enableCookies(childGetValue("cookies_enabled")); + media_source->getMediaPlugin()->enable_cookies(childGetValue("cookies_enabled")); if(hasChild("web_proxy_enabled") &&hasChild("web_proxy_editor") && hasChild("web_proxy_port")) { bool proxy_enable = childGetValue("web_proxy_enabled"); std::string proxy_address = childGetValue("web_proxy_editor"); - int proxy_port = childGetValue("web_proxy_port"); - media_source->enableProxy(proxy_enable, proxy_address, proxy_port); + media_source->getMediaPlugin()->proxy_setup(proxy_enable, proxy_address, proxy_port); } } - free_web_media(media_source); // LLWString busy_response = utf8str_to_wstring(getChild<LLUICtrl>("busy_response")->getValue().asString()); // LLWStringUtil::replaceTabsWithSpaces(busy_response, 4); @@ -558,7 +527,7 @@ void LLFloaterPreference::onBtnOK() // commit any outstanding text entry if (hasFocus()) { - LLUICtrl* cur_focus = gFocusMgr.getKeyboardFocus(); + LLUICtrl* cur_focus = dynamic_cast<LLUICtrl*>(gFocusMgr.getKeyboardFocus()); if (cur_focus->acceptsTextInput()) { cur_focus->onCommit(); @@ -595,7 +564,7 @@ void LLFloaterPreference::onBtnApply( ) { if (hasFocus()) { - LLUICtrl* cur_focus = gFocusMgr.getKeyboardFocus(); + LLUICtrl* cur_focus = dynamic_cast<LLUICtrl*>(gFocusMgr.getKeyboardFocus()); if (cur_focus->acceptsTextInput()) { cur_focus->onCommit(); @@ -611,7 +580,7 @@ void LLFloaterPreference::onBtnCancel() { if (hasFocus()) { - LLUICtrl* cur_focus = gFocusMgr.getKeyboardFocus(); + LLUICtrl* cur_focus = dynamic_cast<LLUICtrl*>(gFocusMgr.getKeyboardFocus()); if (cur_focus->acceptsTextInput()) { cur_focus->onCommit(); diff --git a/indra/newview/llfloatertos.cpp b/indra/newview/llfloatertos.cpp index c20b3f34b2..1060a5e031 100644 --- a/indra/newview/llfloatertos.cpp +++ b/indra/newview/llfloatertos.cpp @@ -132,12 +132,10 @@ BOOL LLFloaterTOS::postBuild() LLUICtrl *editor = getChild<LLUICtrl>("tos_text"); editor->setVisible( FALSE ); - LLWebBrowserCtrl* web_browser = getChild<LLWebBrowserCtrl>("tos_html"); + LLMediaCtrl* web_browser = getChild<LLMediaCtrl>("tos_html"); if ( web_browser ) { - // start to observe it so we see navigate complete events - web_browser->addObserver( this ); - + web_browser->addObserver(this); gResponsePtr = LLIamHere::build( this ); LLHTTPClient::get( getString( "real_url" ), gResponsePtr ); } @@ -150,7 +148,7 @@ void LLFloaterTOS::setSiteIsAlive( bool alive ) // only do this for TOS pages if (hasChild("tos_html")) { - LLWebBrowserCtrl* web_browser = getChild<LLWebBrowserCtrl>("tos_html"); + LLMediaCtrl* web_browser = getChild<LLMediaCtrl>("tos_html"); // if the contents of the site was retrieved if ( alive ) { @@ -169,12 +167,6 @@ void LLFloaterTOS::setSiteIsAlive( bool alive ) LLFloaterTOS::~LLFloaterTOS() { - // stop obsaerving events - LLWebBrowserCtrl* web_browser = getChild<LLWebBrowserCtrl>("tos_html"); - if ( web_browser ) - { - web_browser->remObserver( this ); - }; // tell the responder we're not here anymore if ( gResponsePtr ) @@ -235,14 +227,17 @@ void LLFloaterTOS::onCancel( void* userdata ) } //virtual -void LLFloaterTOS::onNavigateComplete( const EventType& eventIn ) +void LLFloaterTOS::handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent event) { - // skip past the loading screen navigate complete - if ( ++mLoadCompleteCount == 2 ) + if(event == MEDIA_EVENT_NAVIGATE_COMPLETE) { - llinfos << "NAVIGATE COMPLETE" << llendl; - // enable Agree to TOS radio button now that page has loaded - LLCheckBoxCtrl * tos_agreement = getChild<LLCheckBoxCtrl>("agree_chk"); - tos_agreement->setEnabled( true ); - }; + // skip past the loading screen navigate complete + if ( ++mLoadCompleteCount == 2 ) + { + llinfos << "NAVIGATE COMPLETE" << llendl; + // enable Agree to TOS radio button now that page has loaded + LLCheckBoxCtrl * tos_agreement = getChild<LLCheckBoxCtrl>("agree_chk"); + tos_agreement->setEnabled( true ); + } + } } diff --git a/indra/newview/llfloatertos.h b/indra/newview/llfloatertos.h index a9436cbd63..49f982aa80 100644 --- a/indra/newview/llfloatertos.h +++ b/indra/newview/llfloatertos.h @@ -35,7 +35,7 @@ #include "llmodaldialog.h" #include "llassetstorage.h" -#include "llwebbrowserctrl.h" +#include "llmediactrl.h" class LLButton; class LLRadioGroup; @@ -45,7 +45,7 @@ class LLUUID; class LLFloaterTOS : public LLModalDialog, - public LLWebBrowserCtrlObserver + public LLViewerMediaObserver { public: LLFloaterTOS(const LLSD& message); @@ -61,7 +61,8 @@ public: void setSiteIsAlive( bool alive ); - virtual void onNavigateComplete( const EventType& eventIn ); + // inherited from LLViewerMediaObserver + /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); private: std::string mMessage; diff --git a/indra/newview/llfloaterurlentry.cpp b/indra/newview/llfloaterurlentry.cpp index 2ad41291f3..1e975cd447 100644 --- a/indra/newview/llfloaterurlentry.cpp +++ b/indra/newview/llfloaterurlentry.cpp @@ -227,7 +227,7 @@ void LLFloaterURLEntry::onBtnOK( void* userdata ) } // Discover the MIME type only for "http" scheme. - if(scheme == "http") + if(scheme == "http" || scheme == "https") { LLHTTPClient::getHeaderOnly( media_url, new LLMediaTypeResponder(self->getHandle())); diff --git a/indra/newview/llgroupactions.cpp b/indra/newview/llgroupactions.cpp index 24e9615f0d..10a17476d9 100644 --- a/indra/newview/llgroupactions.cpp +++ b/indra/newview/llgroupactions.cpp @@ -53,7 +53,7 @@ public: // requires trusted browser to trigger LLGroupHandler() : LLCommandHandler("group", true) { } bool handle(const LLSD& tokens, const LLSD& query_map, - LLWebBrowserCtrl* web) + LLMediaCtrl* web) { if (tokens.size() < 1) { diff --git a/indra/newview/llhudview.cpp b/indra/newview/llhudview.cpp index 0a6938f4f8..027cd2ab07 100644 --- a/indra/newview/llhudview.cpp +++ b/indra/newview/llhudview.cpp @@ -46,14 +46,18 @@ #include "lltracker.h" #include "llviewercamera.h" #include "llui.h" +#include "lluictrlfactory.h" LLHUDView *gHUDView = NULL; const S32 HUD_ARROW_SIZE = 32; -LLHUDView::LLHUDView() -: LLPanel() + + +LLHUDView::LLHUDView(const LLRect& r) { + LLUICtrlFactory::getInstance()->buildPanel(this, "panel_hud.xml"); + setShape(r, true); } LLHUDView::~LLHUDView() @@ -64,6 +68,7 @@ LLHUDView::~LLHUDView() void LLHUDView::draw() { LLTracker::drawHUDArrow(); + LLView::draw(); } @@ -89,4 +94,3 @@ BOOL LLHUDView::handleMouseDown(S32 x, S32 y, MASK mask) } return LLView::handleMouseDown(x, y, mask); } - diff --git a/indra/newview/llhudview.h b/indra/newview/llhudview.h index 7859e7f8be..05ff9c8596 100644 --- a/indra/newview/llhudview.h +++ b/indra/newview/llhudview.h @@ -42,7 +42,7 @@ class LLHUDView : public LLPanel { public: - LLHUDView(); + LLHUDView(const LLRect& rect); virtual ~LLHUDView(); virtual void draw(); diff --git a/indra/newview/llimpanel.cpp b/indra/newview/llimpanel.cpp index 8c24962897..9cf3e57e22 100644 --- a/indra/newview/llimpanel.cpp +++ b/indra/newview/llimpanel.cpp @@ -77,7 +77,6 @@ #include "lluictrlfactory.h" #include "llviewerwindow.h" #include "lllogchat.h" -#include "llfloaterhtml.h" #include "llweb.h" #include "llhttpclient.h" #include "llmutelist.h" diff --git a/indra/newview/llloginhandler.cpp b/indra/newview/llloginhandler.cpp index 554163c8e1..6f0b8a3c1e 100644 --- a/indra/newview/llloginhandler.cpp +++ b/indra/newview/llloginhandler.cpp @@ -164,7 +164,7 @@ void LLLoginHandler::parse(const LLSD& queryMap) bool LLLoginHandler::handle(const LLSD& tokens, const LLSD& query_map, - LLWebBrowserCtrl* web) + LLMediaCtrl* web) { parse(query_map); diff --git a/indra/newview/llloginhandler.h b/indra/newview/llloginhandler.h index c76d7e8274..0844b80c7c 100644 --- a/indra/newview/llloginhandler.h +++ b/indra/newview/llloginhandler.h @@ -40,7 +40,7 @@ class LLLoginHandler : public LLCommandHandler public: // allow from external browsers LLLoginHandler() : LLCommandHandler("login", false) { } - /*virtual*/ bool handle(const LLSD& tokens, const LLSD& query_map, LLWebBrowserCtrl* web); + /*virtual*/ bool handle(const LLSD& tokens, const LLSD& query_map, LLMediaCtrl* web); // Fill in our internal fields from a SLURL like // secondlife:///app/login?first=Bob&last=Dobbs diff --git a/indra/newview/llmediactrl.cpp b/indra/newview/llmediactrl.cpp new file mode 100644 index 0000000000..62b38f2b4a --- /dev/null +++ b/indra/newview/llmediactrl.cpp @@ -0,0 +1,937 @@ +/** + * @file LLMediaCtrl.cpp + * @brief Web browser UI control + * + * $LicenseInfo:firstyear=2006&license=viewergpl$ + * + * Copyright (c) 2006-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + + +#include "llmediactrl.h" + +// viewer includes +#include "llfloaterhtml.h" +#include "llfloaterworldmap.h" +#include "lluictrlfactory.h" +#include "llurldispatcher.h" +#include "llurlsimstring.h" +#include "llviewborder.h" +#include "llviewercontrol.h" +#include "llviewermedia.h" +#include "llviewerwindow.h" +#include "llnotifications.h" +#include "llweb.h" +#include "llrender.h" +#include "llpluginclassmedia.h" +#include "llslurl.h" +#include "lluictrlfactory.h" // LLDefaultChildRegistry + +// linden library includes +#include "llfocusmgr.h" + +extern BOOL gRestoreGL; + +static LLDefaultChildRegistry::Register<LLMediaCtrl> r("web_browser"); + +LLMediaCtrl::Params::Params() +: start_url("start_url"), + border_visible("border_visible", true), + ignore_ui_scale("ignore_ui_scale", true), + hide_loading("hide_loading", false), + caret_color("caret_color") +{} + +LLMediaCtrl::LLMediaCtrl( const Params& p) : + LLPanel( p ), + mTextureDepthBytes( 4 ), + mBorder(NULL), + mFrequentUpdates( true ), + mForceUpdate( false ), + mOpenLinksInExternalBrowser( false ), + mOpenLinksInInternalBrowser( false ), + mTrusted( false ), + mHomePageUrl( "" ), + mIgnoreUIScale( true ), + mAlwaysRefresh( false ), + mExternalUrl( "" ), + mMediaSource( 0 ), + mTakeFocusOnClick( true ), + mCurrentNavUrl( "" ), + mLastSetCursor( UI_CURSOR_ARROW ), + mStretchToFill( true ), + mMaintainAspectRatio ( true ), + mHideLoading (false) +{ + { + LLColor4 color = p.caret_color().get(); + setCaretColor( (unsigned int)color.mV[0], (unsigned int)color.mV[1], (unsigned int)color.mV[2] ); + } + + setIgnoreUIScale(p.ignore_ui_scale()); + + setHomePageUrl(p.start_url()); + + setBorderVisible(p.border_visible()); + + mHideLoading = p.hide_loading(); + + S32 screen_width = mIgnoreUIScale ? + llround((F32)getRect().getWidth() * LLUI::sGLScaleFactor.mV[VX]) : getRect().getWidth(); + S32 screen_height = mIgnoreUIScale ? + llround((F32)getRect().getHeight() * LLUI::sGLScaleFactor.mV[VY]) : getRect().getHeight(); + + mMediaTextureID.generate(); + mMediaSource = LLViewerMedia::newMediaImpl(mHomePageUrl, mMediaTextureID, screen_width, screen_height, false, false, "text/html"); + if ( !mMediaSource ) + { + llwarns << "media source create failed " << llendl; + // return; + } + + mMediaSource->setVisible( getVisible() ); + + mMediaSource->addObserver( this ); + + // FIXME: How do we create a bevel now? +// LLRect border_rect( 0, getRect().getHeight() + 2, getRect().getWidth() + 2, 0 ); +// mBorder = new LLViewBorder( std::string("web control border"), border_rect, LLViewBorder::BEVEL_IN ); +// addChild( mBorder ); +} + +//////////////////////////////////////////////////////////////////////////////// +// note: this is now a singleton and destruction happens via initClass() now +LLMediaCtrl::~LLMediaCtrl() +{ + + if (mMediaSource) + { + mMediaSource->remObserver( this ); + mMediaSource = NULL; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::setBorderVisible( BOOL border_visible ) +{ + if ( mBorder ) + { + mBorder->setVisible( border_visible ); + }; +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::setTakeFocusOnClick( bool take_focus ) +{ + mTakeFocusOnClick = take_focus; +} + +//////////////////////////////////////////////////////////////////////////////// +// set flag that forces the embedded browser to open links in the external system browser +void LLMediaCtrl::setOpenInExternalBrowser( bool valIn ) +{ + mOpenLinksInExternalBrowser = valIn; +}; + +//////////////////////////////////////////////////////////////////////////////// +// set flag that forces the embedded browser to open links in the internal browser floater +void LLMediaCtrl::setOpenInInternalBrowser( bool valIn ) +{ + mOpenLinksInInternalBrowser = valIn; +}; + +//////////////////////////////////////////////////////////////////////////////// +void LLMediaCtrl::setTrusted( bool valIn ) +{ + mTrusted = valIn; +} + +//////////////////////////////////////////////////////////////////////////////// +// +BOOL LLMediaCtrl::handleHover( S32 x, S32 y, MASK mask ) +{ + convertInputCoords(x, y); + + if (mMediaSource) + mMediaSource->mouseMove(x, y); + + gViewerWindow->setCursor(mLastSetCursor); + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// +// +BOOL LLMediaCtrl::handleScrollWheel( S32 x, S32 y, S32 clicks ) +{ + if (mMediaSource && mMediaSource->hasMedia()) + mMediaSource->getMediaPlugin()->scrollEvent(0, clicks, MASK_NONE); + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// +// +BOOL LLMediaCtrl::handleMouseUp( S32 x, S32 y, MASK mask ) +{ + convertInputCoords(x, y); + + if (mMediaSource) + { + mMediaSource->mouseUp(x, y); + + // *HACK: LLMediaImplLLMozLib automatically takes focus on mouseup, + // in addition to the onFocusReceived() call below. Undo this. JC + if (!mTakeFocusOnClick) + { + mMediaSource->focus(false); + gViewerWindow->focusClient(); + } + } + + gFocusMgr.setMouseCapture( NULL ); + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// +// +BOOL LLMediaCtrl::handleMouseDown( S32 x, S32 y, MASK mask ) +{ + convertInputCoords(x, y); + + if (mMediaSource) + mMediaSource->mouseDown(x, y); + + gFocusMgr.setMouseCapture( this ); + + if (mTakeFocusOnClick) + { + setFocus( TRUE ); + } + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// +// +BOOL LLMediaCtrl::handleDoubleClick( S32 x, S32 y, MASK mask ) +{ + convertInputCoords(x, y); + + if (mMediaSource) + mMediaSource->mouseLeftDoubleClick( x, y ); + + gFocusMgr.setMouseCapture( this ); + + if (mTakeFocusOnClick) + { + setFocus( TRUE ); + } + + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::onFocusReceived() +{ + if (mMediaSource) + { + mMediaSource->focus(true); + + // Set focus for edit menu items + LLEditMenuHandler::gEditMenuHandler = mMediaSource; + } + + LLPanel::onFocusReceived(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::onFocusLost() +{ + if (mMediaSource) + { + mMediaSource->focus(false); + + if( LLEditMenuHandler::gEditMenuHandler == mMediaSource ) + { + // Clear focus for edit menu items + LLEditMenuHandler::gEditMenuHandler = NULL; + } + } + + gViewerWindow->focusClient(); + + LLPanel::onFocusLost(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +BOOL LLMediaCtrl::postBuild () +{ + mVisibleSignal.connect(boost::bind(&LLMediaCtrl::onVisibilityChange, this, _2)); + return TRUE; +} + +//////////////////////////////////////////////////////////////////////////////// +// +BOOL LLMediaCtrl::handleKeyHere( KEY key, MASK mask ) +{ + BOOL result = FALSE; + + // FIXME: THIS IS SO WRONG. + // Menu keys should be handled by the menu system and not passed to UI elements, but this is how LLTextEditor and LLLineEditor do it... + + if (mMediaSource) + { + if( MASK_CONTROL & mask ) + { + if( 'C' == key ) + { + mMediaSource->copy(); + result = TRUE; + } + else + if( 'V' == key ) + { + mMediaSource->paste(); + result = TRUE; + } + else + if( 'X' == key ) + { + mMediaSource->cut(); + result = TRUE; + } + } + + if(!result) + { + result = mMediaSource->handleKeyHere(key, mask); + } + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::handleVisibilityChange ( BOOL new_visibility ) +{ + llinfos << "visibility changed to " << (new_visibility?"true":"false") << llendl; + if(mMediaSource) + { + mMediaSource->setVisible( new_visibility ); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +BOOL LLMediaCtrl::handleUnicodeCharHere(llwchar uni_char) +{ + BOOL result = FALSE; + + // only accept 'printable' characters, sigh... + if (uni_char >= 32 // discard 'control' characters + && uni_char != 127) // SDL thinks this is 'delete' - yuck. + { + if (mMediaSource) + result = mMediaSource->handleUnicodeCharHere(uni_char); + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::onVisibilityChange ( const LLSD& new_visibility ) +{ + // set state of frequent updates automatically if visibility changes + if ( new_visibility.asBoolean() ) + { + mFrequentUpdates = true; + } + else + { + mFrequentUpdates = false; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::reshape( S32 width, S32 height, BOOL called_from_parent ) +{ + S32 screen_width = mIgnoreUIScale ? llround((F32)width * LLUI::sGLScaleFactor.mV[VX]) : width; + S32 screen_height = mIgnoreUIScale ? llround((F32)height * LLUI::sGLScaleFactor.mV[VY]) : height; + +// llinfos << "reshape called with width = " << width << ", height = " << height << llendl; + + // when floater is minimized, these sizes are negative + if ( screen_height > 0 && screen_width > 0 ) + { + mMediaSource->setSize(screen_width, screen_height); + mForceUpdate = true; + } + + LLPanel::reshape( width, height, called_from_parent ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::navigateBack() +{ + if (mMediaSource && mMediaSource->hasMedia()) + { + mMediaSource->getMediaPlugin()->browse_back(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::navigateForward() +{ + if (mMediaSource && mMediaSource->hasMedia()) + { + mMediaSource->getMediaPlugin()->browse_forward(); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::canNavigateBack() +{ + if (mMediaSource) + return mMediaSource->canNavigateBack(); + else + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::canNavigateForward() +{ + if (mMediaSource) + return mMediaSource->canNavigateForward(); + else + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::set404RedirectUrl( std::string redirect_url ) +{ + if(mMediaSource && mMediaSource->hasMedia()) + mMediaSource->getMediaPlugin()->set_status_redirect( 404, redirect_url ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::clr404RedirectUrl() +{ + if(mMediaSource && mMediaSource->hasMedia()) + mMediaSource->getMediaPlugin()->set_status_redirect(404, ""); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::navigateTo( std::string url_in, std::string mime_type) +{ + // don't browse to anything that starts with secondlife:// or sl:// + const std::string protocol1 = "secondlife://"; + const std::string protocol2 = "sl://"; + if ((LLStringUtil::compareInsensitive(url_in.substr(0, protocol1.length()), protocol1) == 0) || + (LLStringUtil::compareInsensitive(url_in.substr(0, protocol2.length()), protocol2) == 0)) + { + // TODO: Print out/log this attempt? + // llinfos << "Rejecting attempt to load restricted website :" << urlIn << llendl; + return; + } + + if (mMediaSource) + { + mCurrentNavUrl = url_in; + mMediaSource->navigateTo(url_in, mime_type, mime_type.empty()); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::navigateToLocalPage( const std::string& subdir, const std::string& filename_in ) +{ + std::string language = LLUI::getLanguage(); + std::string delim = gDirUtilp->getDirDelimiter(); + std::string filename; + + filename += subdir; + filename += delim; + filename += filename_in; + + std::string expanded_filename = gDirUtilp->findSkinnedFilename("html", language, filename); + + if (! gDirUtilp->fileExists(expanded_filename)) + { + if (language != "en-us") + { + expanded_filename = gDirUtilp->findSkinnedFilename("html", "en-us", filename); + if (! gDirUtilp->fileExists(expanded_filename)) + { + llwarns << "File " << subdir << delim << filename_in << "not found" << llendl; + return; + } + } + else + { + llwarns << "File " << subdir << delim << filename_in << "not found" << llendl; + return; + } + } + if (mMediaSource) + { + mCurrentNavUrl = expanded_filename; + mMediaSource->navigateTo(expanded_filename, "text/html", false); + } + +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::navigateHome() +{ + if( mHomePageUrl.length() ) + { + if (mMediaSource) + mMediaSource->navigateTo(mHomePageUrl); + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::setHomePageUrl( const std::string urlIn ) +{ + mHomePageUrl = urlIn; +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaCtrl::setCaretColor(unsigned int red, unsigned int green, unsigned int blue) +{ + //NOOP + return false; +} +//////////////////////////////////////////////////////////////////////////////// +// +std::string LLMediaCtrl::getHomePageUrl() +{ + return mHomePageUrl; +} + +//////////////////////////////////////////////////////////////////////////////// +// +LLPluginClassMedia* LLMediaCtrl::getMediaPlugin() +{ + return mMediaSource.isNull() ? NULL : mMediaSource->getMediaPlugin(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::draw() +{ + LLPluginClassMedia* media_plugin = NULL; + + if(mMediaSource && mMediaSource->hasMedia()) + { + media_plugin = mMediaSource->getMediaPlugin(); + } + else + { + return; + } + + LLViewerMediaTexture* media_texture = LLViewerTextureManager::findMediaTexture(mMediaTextureID); + + if (!media_texture ) + { + return; + } + + if ( gRestoreGL == 1 ) + { + LLRect r = getRect(); + reshape( r.getWidth(), r.getHeight(), FALSE ); + return; + }; + + // NOTE: optimization needed here - probably only need to do this once + // unless tearoffs change the parent which they probably do. + const LLUICtrl* ptr = findRootMostFocusRoot(); + if ( ptr && ptr->hasFocus() ) + { + setFrequentUpdates( true ); + } + else + { + setFrequentUpdates( false ); + }; + + // alpha off for this + LLGLSUIDefault gls_ui; + LLGLDisable gls_alphaTest( GL_ALPHA_TEST ); + + gGL.pushMatrix(); + { + if (mIgnoreUIScale) + { + glLoadIdentity(); + // font system stores true screen origin, need to scale this by UI scale factor + // to get render origin for this view (with unit scale) + gGL.translatef(floorf(LLFontGL::sCurOrigin.mX * LLUI::sGLScaleFactor.mV[VX]), + floorf(LLFontGL::sCurOrigin.mY * LLUI::sGLScaleFactor.mV[VY]), + LLFontGL::sCurOrigin.mZ); + } + + // scale texture to fit the space using texture coords + gGL.getTexUnit(0)->bind(media_texture); + gGL.color4fv( LLColor4::white.mV ); + F32 max_u = ( F32 )media_plugin->getWidth() / ( F32 )media_plugin->getTextureWidth(); + F32 max_v = ( F32 )media_plugin->getHeight() / ( F32 )media_plugin->getTextureHeight(); + + LLRect r = getRect(); + S32 width, height; + S32 x_offset = 0; + S32 y_offset = 0; + + if(mStretchToFill) + { + if(mMaintainAspectRatio) + { + F32 media_aspect = (F32)(media_plugin->getWidth()) / (F32)(media_plugin->getHeight()); + F32 view_aspect = (F32)(r.getWidth()) / (F32)(r.getHeight()); + if(media_aspect > view_aspect) + { + // max width, adjusted height + width = r.getWidth(); + height = llmin(llmax(S32(width / media_aspect), 0), r.getHeight()); + } + else + { + // max height, adjusted width + height = r.getHeight(); + width = llmin(llmax(S32(height * media_aspect), 0), r.getWidth()); + } + } + else + { + width = r.getWidth(); + height = r.getHeight(); + } + } + else + { + width = llmin(media_plugin->getWidth(), r.getWidth()); + height = llmin(media_plugin->getHeight(), r.getHeight()); + } + + x_offset = (r.getWidth() - width) / 2; + y_offset = (r.getHeight() - height) / 2; + + // draw the browser + gGL.setSceneBlendType(LLRender::BT_REPLACE); + gGL.begin( LLRender::QUADS ); + if (! media_plugin->getTextureCoordsOpenGL()) + { + // render using web browser reported width and height, instead of trying to invert GL scale + gGL.texCoord2f( max_u, 0.f ); + gGL.vertex2i( x_offset + width, y_offset + height ); + + gGL.texCoord2f( 0.f, 0.f ); + gGL.vertex2i( x_offset, 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 ); + } + else + { + // render using web browser reported width and height, instead of trying to invert GL scale + gGL.texCoord2f( max_u, max_v ); + gGL.vertex2i( x_offset + width, y_offset + height ); + + gGL.texCoord2f( 0.f, max_v ); + gGL.vertex2i( x_offset, 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 ); + } + gGL.end(); + gGL.setSceneBlendType(LLRender::BT_ALPHA); + } + gGL.popMatrix(); + + // highlight if keyboard focus here. (TODO: this needs some work) + if ( mBorder && mBorder->getVisible() ) + mBorder->setKeyboardFocusHighlight( gFocusMgr.childHasKeyboardFocus( this ) ); + + + LLPanel::draw(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::convertInputCoords(S32& x, S32& y) +{ + bool coords_opengl = false; + + if(mMediaSource && mMediaSource->hasMedia()) + { + coords_opengl = mMediaSource->getMediaPlugin()->getTextureCoordsOpenGL(); + } + + x = mIgnoreUIScale ? llround((F32)x * LLUI::sGLScaleFactor.mV[VX]) : x; + if ( ! coords_opengl ) + { + y = mIgnoreUIScale ? llround((F32)(y) * LLUI::sGLScaleFactor.mV[VY]) : y; + } + else + { + y = mIgnoreUIScale ? llround((F32)(getRect().getHeight() - y) * LLUI::sGLScaleFactor.mV[VY]) : getRect().getHeight() - y; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// static +bool LLMediaCtrl::onClickLinkExternalTarget(const LLSD& notification, const LLSD& response ) +{ + S32 option = LLNotification::getSelectedOption(notification, response); + if ( 0 == option ) + { + // open in external browser because we don't support + // creation of our own secondary browser windows + LLWeb::loadURLExternal( notification["payload"]["external_url"].asString() ); + } + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// inherited from LLViewerMediaObserver +//virtual +void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) +{ + switch(event) + { + case MEDIA_EVENT_CONTENT_UPDATED: + { + // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CONTENT_UPDATED " << LL_ENDL; + }; + break; + + case MEDIA_EVENT_TIME_DURATION_UPDATED: + { + // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_TIME_DURATION_UPDATED, time is " << self->getCurrentTime() << " of " << self->getDuration() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_SIZE_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_SIZE_CHANGED " << LL_ENDL; + LLRect r = getRect(); + reshape( r.getWidth(), r.getHeight(), FALSE ); + }; + break; + + case MEDIA_EVENT_CURSOR_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CURSOR_CHANGED, new cursor is " << self->getCursorName() << LL_ENDL; + + std::string cursor = self->getCursorName(); + + if(cursor == "arrow") + mLastSetCursor = UI_CURSOR_ARROW; + else if(cursor == "ibeam") + mLastSetCursor = UI_CURSOR_IBEAM; + else if(cursor == "splith") + mLastSetCursor = UI_CURSOR_SIZEWE; + else if(cursor == "splitv") + mLastSetCursor = UI_CURSOR_SIZENS; + else if(cursor == "hand") + mLastSetCursor = UI_CURSOR_HAND; + else // for anything else, default to the arrow + mLastSetCursor = UI_CURSOR_ARROW; + }; + break; + + case MEDIA_EVENT_NAVIGATE_BEGIN: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_BEGIN, url is " << self->getNavigateURI() << LL_ENDL; + if(mMediaSource && mHideLoading) + { + mMediaSource->suspendUpdates(true); + } + }; + break; + + case MEDIA_EVENT_NAVIGATE_COMPLETE: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_COMPLETE, result string is: " << self->getNavigateResultString() << LL_ENDL; + if(mMediaSource && mHideLoading) + { + mMediaSource->suspendUpdates(false); + } + }; + break; + + case MEDIA_EVENT_PROGRESS_UPDATED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PROGRESS_UPDATED, loading at " << self->getProgressPercent() << "%" << LL_ENDL; + }; + break; + + case MEDIA_EVENT_STATUS_TEXT_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_STATUS_TEXT_CHANGED, new status text is: " << self->getStatusText() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_LOCATION_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_LOCATION_CHANGED, new uri is: " << self->getLocation() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_CLICK_LINK_HREF: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_HREF, target is \"" << self->getClickTarget() << "\", uri is " << self->getClickURL() << LL_ENDL; + onClickLinkHref(self); + }; + break; + + case MEDIA_EVENT_CLICK_LINK_NOFOLLOW: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_NOFOLLOW, uri is " << self->getClickURL() << LL_ENDL; + onClickLinkNoFollow(self); + }; + break; + + case MEDIA_EVENT_PLUGIN_FAILED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED" << LL_ENDL; + }; + break; + }; + + // chain all events to any potential observers of this object. + emitEvent(self, event); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::onClickLinkHref( LLPluginClassMedia* self ) +{ + // retrieve the event parameters + std::string target = self->getClickTarget(); + std::string url = self->getClickURL(); + + // if there is a value for the target + if ( !target.empty() ) + { + if ( target == "_external" ) + { + mExternalUrl = url; + LLSD payload; + payload["external_url"] = mExternalUrl; + LLNotifications::instance().add( "WebLaunchExternalTarget", LLSD(), payload, onClickLinkExternalTarget); + return; + } + } + + const std::string protocol1( "http://" ); + const std::string protocol2( "https://" ); + if( mOpenLinksInExternalBrowser ) + { + if ( !url.empty() ) + { + if ( LLStringUtil::compareInsensitive( url.substr( 0, protocol1.length() ), protocol1 ) == 0 || + LLStringUtil::compareInsensitive( url.substr( 0, protocol2.length() ), protocol2 ) == 0 ) + { + LLWeb::loadURLExternal( url ); + } + } + } + else + if( mOpenLinksInInternalBrowser ) + { + if ( !url.empty() ) + { + if ( LLStringUtil::compareInsensitive( url.substr( 0, protocol1.length() ), protocol1 ) == 0 || + LLStringUtil::compareInsensitive( url.substr( 0, protocol2.length() ), protocol2 ) == 0 ) + { + // If we spawn a new LLFloaterHTML, assume we want it to + // follow this LLMediaCtrl's trust for whether or + // not to open secondlife:///app/ links. JC. +// const bool open_links_externally = false; +// LLFloaterHtml::getInstance()->show( +// event_in.mStringPayload, +// "Second Life Browser", +// open_links_externally, +// mTrusted); + } + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaCtrl::onClickLinkNoFollow( LLPluginClassMedia* self ) +{ + std::string url = self->getClickURL(); + if (LLSLURL::isSLURLCommand(url) + && !mTrusted) + { + // block handling of this secondlife:///app/ URL + LLNotifications::instance().add("UnableToOpenCommandURL"); + return; + } + + LLURLDispatcher::dispatch(url, this, mTrusted); +} + +//////////////////////////////////////////////////////////////////////////////// +// +std::string LLMediaCtrl::getCurrentNavUrl() +{ + return mCurrentNavUrl; +} + diff --git a/indra/newview/llmediactrl.h b/indra/newview/llmediactrl.h new file mode 100644 index 0000000000..a19b3ad67b --- /dev/null +++ b/indra/newview/llmediactrl.h @@ -0,0 +1,182 @@ +/** + * @file llmediactrl.h + * @brief Web browser UI control + * + * $LicenseInfo:firstyear=2006&license=viewergpl$ + * + * Copyright (c) 2006-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLMediaCtrl_H +#define LL_LLMediaCtrl_H + +#include "llviewermedia.h" + +#include "lluictrl.h" +#include "llframetimer.h" +#include "lldynamictexture.h" + +class LLViewBorder; +class LLUICtrlFactory; + +//////////////////////////////////////////////////////////////////////////////// +// +class LLMediaCtrl : + public LLPanel, + public LLViewerMediaObserver, + public LLViewerMediaEventEmitter +{ + LOG_CLASS(LLMediaCtrl); + +public: + struct Params : public LLInitParam::Block<Params, LLPanel::Params> + { + Optional<std::string> start_url; + + Optional<bool> border_visible, + ignore_ui_scale, + hide_loading; + + Optional<LLUIColor> caret_color; + + Params(); + }; + +protected: + LLMediaCtrl(const Params&); + friend class LLUICtrlFactory; + +public: + virtual ~LLMediaCtrl(); + + void setBorderVisible( BOOL border_visible ); + + // For the tutorial window, we don't want to take focus on clicks, + // as the examples include how to move around with the arrow + // keys. Thus we keep focus on the app by setting this false. + // Defaults to true. + void setTakeFocusOnClick( bool take_focus ); + + // handle mouse related methods + virtual BOOL handleHover( S32 x, S32 y, MASK mask ); + virtual BOOL handleMouseUp( S32 x, S32 y, MASK mask ); + virtual BOOL handleMouseDown( S32 x, S32 y, MASK mask ); + virtual BOOL handleDoubleClick( S32 x, S32 y, MASK mask ); + virtual BOOL handleScrollWheel( S32 x, S32 y, S32 clicks ); + + // navigation + void navigateTo( std::string url_in, std::string mime_type = ""); + void navigateBack(); + void navigateHome(); + void navigateForward(); + void navigateToLocalPage( const std::string& subdir, const std::string& filename_in ); + bool canNavigateBack(); + bool canNavigateForward(); + void setOpenInExternalBrowser( bool valIn ); + void setOpenInInternalBrowser( bool valIn ); + std::string getCurrentNavUrl(); + + // By default, we do not handle "secondlife:///app/" SLURLs, because + // those can cause teleports, open windows, etc. We cannot be sure + // that each "click" is actually due to a user action, versus + // Javascript or some other mechanism. However, we need the search + // floater and login page to handle these URLs. Those are safe + // because we control the page content. See DEV-9530. JC. + void setTrusted( bool valIn ); + + void setHomePageUrl( const std::string urlIn ); + std::string getHomePageUrl(); + + // set/clear URL to visit when a 404 page is reached + void set404RedirectUrl( std::string redirect_url ); + void clr404RedirectUrl(); + + // accessor/mutator for flag that indicates if frequent updates to texture happen + bool getFrequentUpdates() { return mFrequentUpdates; }; + void setFrequentUpdates( bool frequentUpdatesIn ) { mFrequentUpdates = frequentUpdatesIn; }; + + void setIgnoreUIScale(bool ignore) { mIgnoreUIScale = ignore; } + bool getIgnoreUIScale() { return mIgnoreUIScale; } + + void setAlwaysRefresh(bool refresh) { mAlwaysRefresh = refresh; } + bool getAlwaysRefresh() { return mAlwaysRefresh; } + + void setForceUpdate(bool force_update) { mForceUpdate = force_update; } + bool getForceUpdate() { return mForceUpdate; } + + LLPluginClassMedia* getMediaPlugin(); + + bool setCaretColor( unsigned int red, unsigned int green, unsigned int blue ); + + + // over-rides + virtual BOOL handleKeyHere( KEY key, MASK mask); + virtual void handleVisibilityChange ( BOOL new_visibility ); + virtual BOOL handleUnicodeCharHere(llwchar uni_char); + virtual void reshape( S32 width, S32 height, BOOL called_from_parent = TRUE); + virtual void draw(); + virtual BOOL postBuild(); + + // focus overrides + void onFocusLost(); + void onFocusReceived(); + + // Incoming media event dispatcher + virtual void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); + + // handlers for individual events (could be done inside the switch in handleMediaEvent, they're just individual functions for clarity) + void onClickLinkHref( LLPluginClassMedia* self ); + void onClickLinkNoFollow( LLPluginClassMedia* self ); + + protected: + void convertInputCoords(S32& x, S32& y); + + private: + void onVisibilityChange ( const LLSD& new_visibility ); + static bool onClickLinkExternalTarget( const LLSD&, const LLSD& ); + + const S32 mTextureDepthBytes; + LLUUID mMediaTextureID; + LLViewBorder* mBorder; + bool mFrequentUpdates; + bool mForceUpdate; + bool mOpenLinksInExternalBrowser; + bool mOpenLinksInInternalBrowser; + bool mTrusted; + std::string mHomePageUrl; + std::string mExternalUrl; + std::string mCurrentNavUrl; + bool mIgnoreUIScale; + bool mAlwaysRefresh; + viewer_media_t mMediaSource; + bool mTakeFocusOnClick; + ECursorType mLastSetCursor; + bool mStretchToFill; + bool mMaintainAspectRatio; + bool mHideLoading; +}; + +#endif // LL_LLMediaCtrl_H diff --git a/indra/newview/llmimetypes.cpp b/indra/newview/llmimetypes.cpp index bfbc81aa66..525e89cdff 100644 --- a/indra/newview/llmimetypes.cpp +++ b/indra/newview/llmimetypes.cpp @@ -46,6 +46,8 @@ std::string sDefaultWidgetType; // Returned when we don't know what widget set to use std::string sDefaultImpl; // Returned when we don't know what impl to use +std::string sXMLFilename; + // Squirrel away XML filename so we know how to reset ///////////////////////////////////////////////////////////////////////////// @@ -146,6 +148,8 @@ bool LLMIMETypes::parseMIMETypes(const std::string& xml_filename) sWidgetMap[set_name] = info; } } + + sXMLFilename = xml_filename; return true; } @@ -267,3 +271,23 @@ bool LLMIMETypes::findAllowLooping(const std::string& mime_type) } return allow_looping; } + +// static +bool LLMIMETypes::isTypeHandled(const std::string& mime_type) +{ + mime_info_map_t::const_iterator it = sMap.find(mime_type); + if (it != sMap.end()) + { + return true; + } + return false; +} + +// static +void LLMIMETypes::reload(void*) +{ + sMap.clear(); + sWidgetMap.clear(); + (void)LLMIMETypes::parseMIMETypes(sXMLFilename); +} + diff --git a/indra/newview/llmimetypes.h b/indra/newview/llmimetypes.h index 7a50c29429..b217ce7a81 100644 --- a/indra/newview/llmimetypes.h +++ b/indra/newview/llmimetypes.h @@ -72,6 +72,12 @@ public: static bool findAllowLooping(const std::string& mime_type); // accessor for flag to enable/disable media looping checkbox + static bool isTypeHandled(const std::string& mime_type); + // determines if the specific mime type is handled by the media system + + static void reload(void*); + // re-loads the MIME types file from the file path last passed into parseMIMETypes + public: struct LLMIMEInfo { diff --git a/indra/newview/llnavigationbar.cpp b/indra/newview/llnavigationbar.cpp index 7cb0456e8a..46bbb382a5 100644 --- a/indra/newview/llnavigationbar.cpp +++ b/indra/newview/llnavigationbar.cpp @@ -41,6 +41,7 @@ #include "llagent.h" #include "llviewerregion.h" +#include "lllandmarkactions.h" #include "lllocationhistory.h" #include "lllocationinputctrl.h" #include "llteleporthistory.h" @@ -53,6 +54,7 @@ #include "llworldmap.h" #include "llappviewer.h" #include "llviewercontrol.h" +#include "llfloatermediabrowser.h" #include "llinventorymodel.h" #include "lllandmarkactions.h" diff --git a/indra/newview/llnearbychatbar.cpp b/indra/newview/llnearbychatbar.cpp index 0696d41732..d4a9be0355 100644 --- a/indra/newview/llnearbychatbar.cpp +++ b/indra/newview/llnearbychatbar.cpp @@ -602,7 +602,7 @@ public: // Your code here bool handle(const LLSD& tokens, const LLSD& query_map, - LLWebBrowserCtrl* web) + LLMediaCtrl* web) { if (tokens.size() < 2) return false; S32 channel = tokens[0].asInteger(); diff --git a/indra/newview/lloverlaybar.cpp b/indra/newview/lloverlaybar.cpp index 0eb96f992a..ea24638b6d 100644 --- a/indra/newview/lloverlaybar.cpp +++ b/indra/newview/lloverlaybar.cpp @@ -37,7 +37,7 @@ #include "lloverlaybar.h" -#include "audioengine.h" +#include "llaudioengine.h" #include "llrender.h" #include "llagent.h" #include "llbutton.h" @@ -60,7 +60,7 @@ #include "llvoiceclient.h" #include "llvoavatarself.h" #include "llvoiceremotectrl.h" -#include "llwebbrowserctrl.h" +#include "llmediactrl.h" #include "llselectmgr.h" // @@ -315,7 +315,7 @@ void LLOverlayBar::mediaStop(void*) { if (!gOverlayBar) { - return; + // return; } LLViewerParcelMedia::stop(); } @@ -324,15 +324,15 @@ void LLOverlayBar::toggleMediaPlay(void*) { if (!gOverlayBar) { - return; + // return; } - if (LLViewerMedia::isMediaPaused()) + if (LLViewerParcelMedia::getStatus() == LLViewerMediaImpl::MEDIA_PAUSED) { LLViewerParcelMedia::start(); } - else if(LLViewerMedia::isMediaPlaying()) + else if(LLViewerParcelMedia::getStatus() == LLViewerMediaImpl::MEDIA_PLAYING) { LLViewerParcelMedia::pause(); } diff --git a/indra/newview/llpanelclassified.cpp b/indra/newview/llpanelclassified.cpp index f58f993a2e..c77d089af7 100644 --- a/indra/newview/llpanelclassified.cpp +++ b/indra/newview/llpanelclassified.cpp @@ -142,7 +142,7 @@ public: const bool from_search = true; LLPanelClassified::sendClassifiedClickMessage(classified_id, "teleport", from_search); // Invoke teleport - LLWebBrowserCtrl* web = NULL; + LLMediaCtrl* web = NULL; const bool trusted_browser = true; return LLURLDispatcher::dispatch(url, web, trusted_browser); } diff --git a/indra/newview/llpanelface.cpp b/indra/newview/llpanelface.cpp index e9e71644b1..08a50d4b6e 100644 --- a/indra/newview/llpanelface.cpp +++ b/indra/newview/llpanelface.cpp @@ -62,6 +62,7 @@ #include "llviewerobject.h" #include "llviewerstats.h" #include "lluictrlfactory.h" +#include "llpluginclassmedia.h" // // Methods @@ -386,11 +387,6 @@ void LLPanelFace::getState() childSetEnabled("button align",FALSE); //mBtnAutoFix->setEnabled ( FALSE ); - if(LLViewerMedia::hasMedia()) - { - childSetEnabled("textbox autofix",editable); - childSetEnabled("button align",editable); - } //if ( LLMediaEngine::getInstance()->getMediaRenderer () ) // if ( LLMediaEngine::getInstance()->getMediaRenderer ()->isLoaded () ) // { @@ -447,7 +443,15 @@ void LLPanelFace::getState() } } } + + if(LLViewerMedia::textureHasMedia(id)) + { + childSetEnabled("textbox autofix",editable); + childSetEnabled("button align",editable); + } + } + LLAggregatePermissions texture_perms; if(texture_ctrl) @@ -919,14 +923,18 @@ struct LLPanelFaceSetMediaFunctor : public LLSelectedTEFunctor { virtual bool apply(LLViewerObject* object, S32 te) { + // TODO: the media impl pointer should actually be stored by the texture + viewer_media_t pMediaImpl = LLViewerMedia::getMediaImplFromTextureID(object->getTE ( te )->getID()); // only do this if it's a media texture - if ( object->getTE ( te )->getID() == LLViewerMedia::getMediaTextureID() ) + if ( pMediaImpl.notNull()) { - S32 media_width, media_height; - S32 texture_width, texture_height; - if ( LLViewerMedia::getMediaSize( &media_width, &media_height ) - && LLViewerMedia::getTextureSize( &texture_width, &texture_height ) ) + LLPluginClassMedia *media = pMediaImpl->getMediaPlugin(); + if(media) { + S32 media_width = media->getWidth(); + S32 media_height = media->getHeight(); + S32 texture_width = media->getTextureWidth(); + S32 texture_height = media->getTextureHeight(); F32 scale_s = (F32)media_width / (F32)texture_width; F32 scale_t = (F32)media_height / (F32)texture_height; diff --git a/indra/newview/llpanellandaudio.cpp b/indra/newview/llpanellandaudio.cpp new file mode 100644 index 0000000000..920fca66f2 --- /dev/null +++ b/indra/newview/llpanellandaudio.cpp @@ -0,0 +1,192 @@ +/** + * @file llpanellandaudio.cpp + * @brief Allows configuration of "media" for a land parcel, + * for example movies, web pages, and audio. + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llpanellandaudio.h" + +// viewer includes +#include "llmimetypes.h" +#include "llviewerparcelmgr.h" +#include "lluictrlfactory.h" + +// library includes +#include "llcheckboxctrl.h" +#include "llcombobox.h" +#include "llfloaterurlentry.h" +#include "llfocusmgr.h" +#include "lllineeditor.h" +#include "llparcel.h" +#include "lltextbox.h" +#include "llradiogroup.h" +#include "llspinctrl.h" +#include "llsdutil.h" +#include "lltexturectrl.h" +#include "roles_constants.h" +#include "llscrolllistctrl.h" + +// Values for the parcel voice settings radio group +enum +{ + kRadioVoiceChatEstate = 0, + kRadioVoiceChatPrivate = 1, + kRadioVoiceChatDisable = 2 +}; + +//--------------------------------------------------------------------------- +// LLPanelLandAudio +//--------------------------------------------------------------------------- + +LLPanelLandAudio::LLPanelLandAudio(LLParcelSelectionHandle& parcel) +: LLPanel(/*std::string("land_media_panel")*/), mParcel(parcel) +{ +} + + +// virtual +LLPanelLandAudio::~LLPanelLandAudio() +{ +} + + +BOOL LLPanelLandAudio::postBuild() +{ + mCheckSoundLocal = getChild<LLCheckBoxCtrl>("check sound local"); + childSetCommitCallback("check sound local", onCommitAny, this); + + mRadioVoiceChat = getChild<LLRadioGroup>("parcel_voice_channel"); + childSetCommitCallback("parcel_voice_channel", onCommitAny, this); + + mMusicURLEdit = getChild<LLLineEditor>("music_url"); + childSetCommitCallback("music_url", onCommitAny, this); + + mMusicUrlCheck = getChild<LLCheckBoxCtrl>("hide_music_url"); + childSetCommitCallback("hide_music_url", onCommitAny, this); + + return TRUE; +} + + +// public +void LLPanelLandAudio::refresh() +{ + LLParcel *parcel = mParcel->getParcel(); + + if (!parcel) + { + clearCtrls(); + } + else + { + // something selected, hooray! + + // Display options + BOOL can_change_media = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_CHANGE_MEDIA); + + mCheckSoundLocal->set( parcel->getSoundLocal() ); + mCheckSoundLocal->setEnabled( can_change_media ); + + mMusicUrlCheck->set( parcel->getObscureMusic() ); + mMusicUrlCheck->setEnabled( can_change_media ); + + if(parcel->getParcelFlagAllowVoice()) + { + if(parcel->getParcelFlagUseEstateVoiceChannel()) + mRadioVoiceChat->setSelectedIndex(kRadioVoiceChatEstate); + else + mRadioVoiceChat->setSelectedIndex(kRadioVoiceChatPrivate); + } + else + { + mRadioVoiceChat->setSelectedIndex(kRadioVoiceChatDisable); + } + + mRadioVoiceChat->setEnabled( can_change_media ); + + mMusicURLEdit->setText(parcel->getMusicURL()); + mMusicURLEdit->setEnabled( can_change_media ); + } +} +// static +void LLPanelLandAudio::onCommitAny(LLUICtrl*, void *userdata) +{ + LLPanelLandAudio *self = (LLPanelLandAudio *)userdata; + + LLParcel* parcel = self->mParcel->getParcel(); + if (!parcel) + { + return; + } + + // Extract data from UI + BOOL sound_local = self->mCheckSoundLocal->get(); + int voice_setting = self->mRadioVoiceChat->getSelectedIndex(); + std::string music_url = self->mMusicURLEdit->getText(); + U8 obscure_music = self->mMusicUrlCheck->get(); + + + BOOL voice_enabled; + BOOL voice_estate_chan; + + switch(voice_setting) + { + default: + case kRadioVoiceChatEstate: + voice_enabled = TRUE; + voice_estate_chan = TRUE; + break; + case kRadioVoiceChatPrivate: + voice_enabled = TRUE; + voice_estate_chan = FALSE; + break; + case kRadioVoiceChatDisable: + voice_enabled = FALSE; + voice_estate_chan = FALSE; + break; + } + + // Remove leading/trailing whitespace (common when copying/pasting) + LLStringUtil::trim(music_url); + + // Push data into current parcel + parcel->setParcelFlag(PF_ALLOW_VOICE_CHAT, voice_enabled); + parcel->setParcelFlag(PF_USE_ESTATE_VOICE_CHAN, voice_estate_chan); + parcel->setParcelFlag(PF_SOUND_LOCAL, sound_local); + parcel->setMusicURL(music_url); + parcel->setObscureMusic(obscure_music); + + // Send current parcel data upstream to server + LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); + + // Might have changed properties, so let's redraw! + self->refresh(); +} diff --git a/indra/newview/llpanellandaudio.h b/indra/newview/llpanellandaudio.h new file mode 100644 index 0000000000..de5da95fa4 --- /dev/null +++ b/indra/newview/llpanellandaudio.h @@ -0,0 +1,62 @@ +/** + * @file llpanellandaudio.h + * @brief Allows configuration of "audio" for a land parcel. + * + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2008, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LLPANELLANDAUDIO_H +#define LLPANELLANDAUDIO_H + +#include "lllineeditor.h" +#include "llpanel.h" +#include "llparcelselection.h" +#include "lluifwd.h" // widget pointer types + +class LLPanelLandAudio + : public LLPanel +{ +public: + LLPanelLandAudio(LLSafeHandle<LLParcelSelection>& parcelp); + /*virtual*/ ~LLPanelLandAudio(); + /*virtual*/ BOOL postBuild(); + void refresh(); + +private: + static void onCommitAny(LLUICtrl* ctrl, void *userdata); + +private: + LLCheckBoxCtrl* mCheckSoundLocal; + LLRadioGroup* mRadioVoiceChat; + LLLineEditor* mMusicURLEdit; + LLCheckBoxCtrl* mMusicUrlCheck; + + LLSafeHandle<LLParcelSelection>& mParcel; +}; + +#endif diff --git a/indra/newview/llpanellandmedia.cpp b/indra/newview/llpanellandmedia.cpp index d1ab3510cd..994bf7e3f9 100644 --- a/indra/newview/llpanellandmedia.cpp +++ b/indra/newview/llpanellandmedia.cpp @@ -39,6 +39,8 @@ #include "llmimetypes.h" #include "llviewerparcelmgr.h" #include "llviewerregion.h" +#include "llviewermedia.h" +#include "llviewerparcelmedia.h" #include "lluictrlfactory.h" // library includes @@ -54,6 +56,7 @@ #include "llsdutil.h" #include "lltexturectrl.h" #include "roles_constants.h" +#include "llscrolllistctrl.h" //--------------------------------------------------------------------------- // LLPanelLandMedia @@ -62,12 +65,6 @@ LLPanelLandMedia::LLPanelLandMedia(LLParcelSelectionHandle& parcel) : LLPanel(), mParcel(parcel), - mCheckSoundLocal(NULL), - mSoundHelpButton(NULL), - mCheckEnableVoiceChat(NULL), - mCheckEnableVoiceChatIsEstateDisabled(NULL), - mCheckEnableVoiceChatParcel(NULL), - mMusicURLEdit(NULL), mMediaURLEdit(NULL), mMediaDescEdit(NULL), mMediaTypeCombo(NULL), @@ -78,8 +75,7 @@ LLPanelLandMedia::LLPanelLandMedia(LLParcelSelectionHandle& parcel) mMediaTextureCtrl(NULL), mMediaAutoScaleCheck(NULL), mMediaLoopCheck(NULL), - mMediaUrlCheck(NULL), - mMusicUrlCheck(NULL) + mMediaUrlCheck(NULL) { } @@ -87,34 +83,10 @@ LLPanelLandMedia::LLPanelLandMedia(LLParcelSelectionHandle& parcel) // virtual LLPanelLandMedia::~LLPanelLandMedia() { - // close LLFloaterURLEntry? } - -// static -void LLPanelLandMedia::onClickSoundHelp(void*) -{ - LLNotifications::instance().add("ClickSoundHelpLand"); -} - - BOOL LLPanelLandMedia::postBuild() { - mCheckSoundLocal = getChild<LLCheckBoxCtrl>("check sound local"); - childSetCommitCallback("check sound local", onCommitAny, this); - - mSoundHelpButton = getChild<LLButton>("?"); - mSoundHelpButton->setClickedCallback(onClickSoundHelp, this); - - mCheckEnableVoiceChat = getChild<LLCheckBoxCtrl>("parcel_enable_voice_channel"); - childSetCommitCallback("parcel_enable_voice_channel", onCommitAny, this); - mCheckEnableVoiceChatIsEstateDisabled = getChild<LLCheckBoxCtrl>("parcel_enable_voice_channel_is_estate_disabled"); - childSetCommitCallback("parcel_enable_voice_channel_is_estate_disabled", onCommitAny, this); - mCheckEnableVoiceChatParcel = getChild<LLCheckBoxCtrl>("parcel_enable_voice_channel_parcel"); - childSetCommitCallback("parcel_enable_voice_channel_parcel", onCommitAny, this); - - mMusicURLEdit = getChild<LLLineEditor>("music_url"); - childSetCommitCallback("music_url", onCommitAny, this); mMediaTextureCtrl = getChild<LLTextureCtrl>("media texture"); mMediaTextureCtrl->setCommitCallback( onCommitAny, this ); @@ -126,16 +98,13 @@ BOOL LLPanelLandMedia::postBuild() childSetCommitCallback("media_auto_scale", onCommitAny, this); mMediaLoopCheck = getChild<LLCheckBoxCtrl>("media_loop"); - childSetCommitCallback("media_loop", onCommitAny, this); + childSetCommitCallback("media_loop", onCommitAny, this ); mMediaUrlCheck = getChild<LLCheckBoxCtrl>("hide_media_url"); - childSetCommitCallback("hide_media_url", onCommitAny, this); - - mMusicUrlCheck = getChild<LLCheckBoxCtrl>("hide_music_url"); - childSetCommitCallback("hide_music_url", onCommitAny, this); + childSetCommitCallback("hide_media_url", onCommitAny, this ); mMediaURLEdit = getChild<LLLineEditor>("media_url"); - childSetCommitCallback("media_url", onCommitAny, this); + childSetCommitCallback("media_url", onCommitAny, this ); mMediaDescEdit = getChild<LLLineEditor>("url_description"); childSetCommitCallback("url_description", onCommitAny, this); @@ -153,6 +122,9 @@ BOOL LLPanelLandMedia::postBuild() mSetURLButton = getChild<LLButton>("set_media_url"); childSetAction("set_media_url", onSetBtn, this); + mResetURLButton = getChild<LLButton>("reset_media_url"); + childSetAction("reset_media_url", onResetBtn, this); + return TRUE; } @@ -173,46 +145,11 @@ void LLPanelLandMedia::refresh() // Display options BOOL can_change_media = LLViewerParcelMgr::isParcelModifiableByAgent(parcel, GP_LAND_CHANGE_MEDIA); - mCheckSoundLocal->set( parcel->getSoundLocal() ); - mCheckSoundLocal->setEnabled( can_change_media ); - - LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); - if (!region) - { - // never seen this happen, but log it - llwarns << "Couldn't get selected region." << llendl; - } - - if (region && region->isVoiceEnabled()) // estate-wide voice-disable overrides all - { - bool allow_voice = parcel->getParcelFlagAllowVoice(); - - mCheckEnableVoiceChatIsEstateDisabled->setVisible(false); - - mCheckEnableVoiceChat->setVisible(true); - mCheckEnableVoiceChat->setEnabled( can_change_media ); - mCheckEnableVoiceChat->set(allow_voice); - - mCheckEnableVoiceChatParcel->setEnabled( can_change_media && allow_voice ); - } - else // disabled at region level - { - mCheckEnableVoiceChatIsEstateDisabled->setVisible(true); // always disabled - mCheckEnableVoiceChat->setVisible(false); - mCheckEnableVoiceChat->setEnabled(false); - mCheckEnableVoiceChat->set(false); - - mCheckEnableVoiceChatParcel->setEnabled(false); - } - - mCheckEnableVoiceChatParcel->set(!parcel->getParcelFlagUseEstateVoiceChannel()); - - mMusicURLEdit->setText(parcel->getMusicURL()); - mMusicURLEdit->setEnabled( can_change_media ); - mMediaURLEdit->setText(parcel->getMediaURL()); mMediaURLEdit->setEnabled( FALSE ); + childSetText("current_url", parcel->getMediaCurrentURL()); + mMediaDescEdit->setText(parcel->getMediaDesc()); mMediaDescEdit->setEnabled( can_change_media ); @@ -228,15 +165,11 @@ void LLPanelLandMedia::refresh() mMediaUrlCheck->set( parcel->getObscureMedia() ); mMediaUrlCheck->setEnabled( can_change_media ); - mMusicUrlCheck->set( parcel->getObscureMusic() ); - mMusicUrlCheck->setEnabled( can_change_media ); - // don't display urls if you're not able to change it // much requested change in forums so people can't 'steal' urls // NOTE: bug#2009 means this is still vunerable - however, bug // should be closed since this bug opens up major security issues elsewhere. bool obscure_media = ! can_change_media && parcel->getObscureMedia(); - bool obscure_music = ! can_change_media && parcel->getObscureMusic(); // Special code to disable asterixes for html type if(mime_type == "text/html") @@ -246,7 +179,6 @@ void LLPanelLandMedia::refresh() mMediaUrlCheck->setEnabled( false ); } - mMusicURLEdit->setDrawAsterixes( obscure_music ); mMediaURLEdit->setDrawAsterixes( obscure_media ); mMediaAutoScaleCheck->set( parcel->getMediaAutoScale () ); @@ -260,7 +192,7 @@ void LLPanelLandMedia::refresh() else mMediaLoopCheck->set( false ); mMediaLoopCheck->setEnabled ( can_change_media && allow_looping ); - + // disallow media size change for mime types that don't allow it bool allow_resize = LLMIMETypes::findAllowResize( mime_type ); if ( allow_resize ) @@ -283,22 +215,7 @@ void LLPanelLandMedia::refresh() mMediaTextureCtrl->setEnabled( can_change_media ); mSetURLButton->setEnabled( can_change_media ); - - #if 0 - // there is a media url and a media texture selected - if ( ( ! ( std::string ( parcel->getMediaURL() ).empty () ) ) && ( ! ( parcel->getMediaID ().isNull () ) ) ) - { - // turn on transport controls if allowed for this parcel - mMediaStopButton->setEnabled ( editable ); - mMediaStartButton->setEnabled ( editable ); - } - else - { - // no media url or no media texture - mMediaStopButton->setEnabled ( FALSE ); - mMediaStartButton->setEnabled ( FALSE ); - }; - #endif + mResetURLButton->setEnabled( can_change_media ); LLFloaterURLEntry* floater_url_entry = (LLFloaterURLEntry*)mURLEntryFloater.get(); if (floater_url_entry) @@ -346,12 +263,19 @@ void LLPanelLandMedia::setMediaType(const std::string& mime_type) void LLPanelLandMedia::setMediaURL(const std::string& media_url) { mMediaURLEdit->setText(media_url); + LLParcel *parcel = mParcel->getParcel(); + if(parcel) + parcel->setMediaCurrentURL(media_url); + // LLViewerMedia::navigateHome(); + + mMediaURLEdit->onCommit(); + // LLViewerParcelMedia::sendMediaNavigateMessage(media_url); + childSetText("current_url", media_url); } - std::string LLPanelLandMedia::getMediaURL() { - return mMediaURLEdit->getText(); + return mMediaURLEdit->getText(); } // static @@ -380,33 +304,23 @@ void LLPanelLandMedia::onCommitAny(LLUICtrl*, void *userdata) } // Extract data from UI - BOOL sound_local = self->mCheckSoundLocal->get(); - std::string music_url = self->mMusicURLEdit->getText(); std::string media_url = self->mMediaURLEdit->getText(); std::string media_desc = self->mMediaDescEdit->getText(); std::string mime_type = self->childGetText("mime_type"); U8 media_auto_scale = self->mMediaAutoScaleCheck->get(); U8 media_loop = self->mMediaLoopCheck->get(); U8 obscure_media = self->mMediaUrlCheck->get(); - U8 obscure_music = self->mMusicUrlCheck->get(); S32 media_width = (S32)self->mMediaWidthCtrl->get(); S32 media_height = (S32)self->mMediaHeightCtrl->get(); LLUUID media_id = self->mMediaTextureCtrl->getImageAssetID(); - BOOL voice_enabled = self->mCheckEnableVoiceChat->get(); - BOOL voice_estate_chan = ! self->mCheckEnableVoiceChatParcel->get(); self->childSetText("mime_type", mime_type); // Remove leading/trailing whitespace (common when copying/pasting) - LLStringUtil::trim(music_url); LLStringUtil::trim(media_url); // Push data into current parcel - parcel->setParcelFlag(PF_ALLOW_VOICE_CHAT, voice_enabled); - parcel->setParcelFlag(PF_USE_ESTATE_VOICE_CHAN, voice_estate_chan); - parcel->setParcelFlag(PF_SOUND_LOCAL, sound_local); - parcel->setMusicURL(music_url); parcel->setMediaURL(media_url); parcel->setMediaType(mime_type); parcel->setMediaDesc(media_desc); @@ -416,7 +330,6 @@ void LLPanelLandMedia::onCommitAny(LLUICtrl*, void *userdata) parcel->setMediaAutoScale ( media_auto_scale ); parcel->setMediaLoop ( media_loop ); parcel->setObscureMedia( obscure_media ); - parcel->setObscureMusic( obscure_music ); // Send current parcel data upstream to server LLViewerParcelMgr::getInstance()->sendParcelPropertiesUpdate( parcel ); @@ -435,3 +348,16 @@ void LLPanelLandMedia::onSetBtn(void *userdata) parent_floater->addDependentFloater(self->mURLEntryFloater.get()); } } + +// static +void LLPanelLandMedia::onResetBtn(void *userdata) +{ + LLPanelLandMedia *self = (LLPanelLandMedia *)userdata; + LLParcel* parcel = self->mParcel->getParcel(); + // LLViewerMedia::navigateHome(); + self->refresh(); + self->childSetText("current_url", parcel->getMediaURL()); + // LLViewerParcelMedia::sendMediaNavigateMessage(parcel->getMediaURL()); + +} + diff --git a/indra/newview/llpanellandmedia.h b/indra/newview/llpanellandmedia.h index 6a53dd42a5..5ad1f9758d 100644 --- a/indra/newview/llpanellandmedia.h +++ b/indra/newview/llpanellandmedia.h @@ -56,29 +56,34 @@ private: static void onCommitAny(LLUICtrl* ctrl, void *userdata); static void onCommitType(LLUICtrl* ctrl, void *userdata); static void onSetBtn(void* userdata); - static void onClickSoundHelp(void*); - + static void onResetBtn(void* userdata); + private: - LLCheckBoxCtrl* mCheckSoundLocal; - LLButton* mSoundHelpButton; - LLCheckBoxCtrl* mCheckEnableVoiceChat; - LLCheckBoxCtrl* mCheckEnableVoiceChatIsEstateDisabled; - LLCheckBoxCtrl* mCheckEnableVoiceChatParcel; - LLLineEditor* mMusicURLEdit; LLLineEditor* mMediaURLEdit; LLLineEditor* mMediaDescEdit; LLComboBox* mMediaTypeCombo; LLButton* mSetURLButton; + LLButton* mResetURLButton; + LLSpinCtrl* mMediaResetCtrl; LLSpinCtrl* mMediaHeightCtrl; LLSpinCtrl* mMediaWidthCtrl; + LLTextBox* mMediaResetCtrlLabel; LLTextBox* mMediaSizeCtrlLabel; LLTextureCtrl* mMediaTextureCtrl; LLCheckBoxCtrl* mMediaAutoScaleCheck; LLCheckBoxCtrl* mMediaLoopCheck; LLCheckBoxCtrl* mMediaUrlCheck; - LLCheckBoxCtrl* mMusicUrlCheck; LLHandle<LLFloater> mURLEntryFloater; + LLCheckBoxCtrl* mMediaNavigateAllowCheck; + LLCheckBoxCtrl* mMediaURLFilterCheck; + LLLineEditor* mMediaURLFilterDomainEdit; + LLButton* mMediaURLFilterAddButton; + LLButton* mMediaURLFilterRemoveButton; + LLScrollListCtrl* mURLFilterList; + LLRadioGroup* mRadioNavigateControl; + + LLSafeHandle<LLParcelSelection>& mParcel; }; diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index 7007bfc9d5..167446d380 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -66,11 +66,10 @@ #include "lluictrlfactory.h" #include "llhttpclient.h" #include "llweb.h" -#include "llwebbrowserctrl.h" -#include "llfloaterhtml.h" +#include "llmediactrl.h" #include "llrootview.h" -#include "llfloaterhtmlhelp.h" +#include "llfloatermediabrowser.h" #include "llfloatertos.h" #include "lltrans.h" #include "llglheaders.h" @@ -89,7 +88,7 @@ class LLLoginRefreshHandler : public LLCommandHandler public: // don't allow from external browsers LLLoginRefreshHandler() : LLCommandHandler("login_refresh", true) { } - bool handle(const LLSD& tokens, const LLSD& query_map, LLWebBrowserCtrl* web) + bool handle(const LLSD& tokens, const LLSD& query_map, LLMediaCtrl* web) { if (LLStartUp::getStartupState() < STATE_LOGIN_CLEANUP) { @@ -257,16 +256,15 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, #endif // get the web browser control - LLWebBrowserCtrl* web_browser = getChild<LLWebBrowserCtrl>("login_html"); + LLMediaCtrl* web_browser = getChild<LLMediaCtrl>("login_html"); + web_browser->addObserver(this); + // Need to handle login secondlife:///app/ URLs web_browser->setTrusted( true ); - // observe browser events - web_browser->addObserver( this ); - // don't make it a tab stop until SL-27594 is fixed web_browser->setTabStop(FALSE); - web_browser->navigateToLocalPage( "loading", "loading.html" ); + // web_browser->navigateToLocalPage( "loading", "loading.html" ); // make links open in external browser web_browser->setOpenInExternalBrowser( true ); @@ -300,7 +298,7 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, void LLPanelLogin::setSiteIsAlive( bool alive ) { - LLWebBrowserCtrl* web_browser = getChild<LLWebBrowserCtrl>("login_html"); + LLMediaCtrl* web_browser = getChild<LLMediaCtrl>("login_html"); // if the contents of the site was retrieved if ( alive ) { @@ -680,7 +678,7 @@ void LLPanelLogin::setAlwaysRefresh(bool refresh) { if (LLStartUp::getStartupState() >= STATE_LOGIN_CLEANUP) return; - LLWebBrowserCtrl* web_browser = sInstance->getChild<LLWebBrowserCtrl>("login_html"); + LLMediaCtrl* web_browser = sInstance->getChild<LLMediaCtrl>("login_html"); if (web_browser) { @@ -820,25 +818,28 @@ void LLPanelLogin::loadLoginPage() #endif #endif - LLWebBrowserCtrl* web_browser = sInstance->getChild<LLWebBrowserCtrl>("login_html"); + LLMediaCtrl* web_browser = sInstance->getChild<LLMediaCtrl>("login_html"); // navigate to the "real" page - web_browser->navigateTo( oStr.str() ); + web_browser->navigateTo( oStr.str(), "text/html" ); } -void LLPanelLogin::onNavigateComplete( const EventType& eventIn ) +void LLPanelLogin::handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent event) { - LLWebBrowserCtrl* web_browser = sInstance->getChild<LLWebBrowserCtrl>("login_html"); - if (web_browser) + if(event == MEDIA_EVENT_NAVIGATE_COMPLETE) { - // *HACK HACK HACK HACK! - /* Stuff a Tab key into the browser now so that the first field will - ** get the focus! The embedded javascript on the page that properly - ** sets the initial focus in a real web browser is not working inside - ** the viewer, so this is an UGLY HACK WORKAROUND for now. - */ - // Commented out as it's not reliable - //web_browser->handleKey(KEY_TAB, MASK_NONE, false); + LLMediaCtrl* web_browser = sInstance->getChild<LLMediaCtrl>("login_html"); + if (web_browser) + { + // *HACK HACK HACK HACK! + /* Stuff a Tab key into the browser now so that the first field will + ** get the focus! The embedded javascript on the page that properly + ** sets the initial focus in a real web browser is not working inside + ** the viewer, so this is an UGLY HACK WORKAROUND for now. + */ + // Commented out as it's not reliable + //web_browser->handleKey(KEY_TAB, MASK_NONE, false); + } } } diff --git a/indra/newview/llpanellogin.h b/indra/newview/llpanellogin.h index 83a31eecda..ffcf6a9b70 100644 --- a/indra/newview/llpanellogin.h +++ b/indra/newview/llpanellogin.h @@ -35,7 +35,7 @@ #include "llpanel.h" #include "llpointer.h" // LLPointer<> -#include "llwebbrowserctrl.h" // LLWebBrowserCtrlObserver +#include "llmediactrl.h" // LLMediaCtrlObserver class LLLineEditor; class LLUIImage; @@ -43,7 +43,7 @@ class LLUIImage; class LLPanelLogin: public LLPanel, - public LLWebBrowserCtrlObserver + public LLViewerMediaObserver { LOG_CLASS(LLPanelLogin); public: @@ -82,13 +82,15 @@ public: static void setAlwaysRefresh(bool refresh); static void mungePassword(LLUICtrl* caller, void* user_data); + // inherited from LLViewerMediaObserver + /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); + private: static void onClickConnect(void*); static void onClickNewAccount(void*); // static bool newAccountAlertCallback(const LLSD& notification, const LLSD& response); static void onClickQuit(void*); static void onClickVersion(void*); - virtual void onNavigateComplete( const EventType& eventIn ); static void onClickForgotPassword(void*); static void onPassKey(LLLineEditor* caller, void* user_data); static void onSelectServer(LLUICtrl*, void*); diff --git a/indra/newview/llpanelpermissions.cpp b/indra/newview/llpanelpermissions.cpp index d515b03ea9..42f9906409 100644 --- a/indra/newview/llpanelpermissions.cpp +++ b/indra/newview/llpanelpermissions.cpp @@ -244,7 +244,7 @@ void LLPanelPermissions::refresh() BOOL is_perm_modify = (LLSelectMgr::getInstance()->getSelection()->getFirstRootNode() && LLSelectMgr::getInstance()->selectGetRootsModify()) || LLSelectMgr::getInstance()->selectGetModify(); - const LLView* keyboard_focus_view = gFocusMgr.getKeyboardFocus(); + const LLFocusableElement* keyboard_focus_view = gFocusMgr.getKeyboardFocus(); S32 string_index = 0; std::string MODIFY_INFO_STRINGS[] = { diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index 2895a68683..6d3d307526 100644 --- a/indra/newview/llpanelprofile.cpp +++ b/indra/newview/llpanelprofile.cpp @@ -49,7 +49,7 @@ public: LLAgentHandler() : LLCommandHandler("agent", true) { } bool handle(const LLSD& params, const LLSD& query_map, - LLWebBrowserCtrl* web) + LLMediaCtrl* web) { if (params.size() < 2) return false; LLUUID agent_id; diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp index 49b9bbadfb..6c73638475 100644 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -83,7 +83,7 @@ #include "llviewertexteditor.h" #include "llviewerwindow.h" #include "lluictrlfactory.h" -#include "llwebbrowserctrl.h" +#include "llmediactrl.h" #include "lluictrlfactory.h" #include "llviewercontrol.h" @@ -446,7 +446,7 @@ void LLScriptEdCore::updateDynamicHelp(BOOL immediate) // update back and forward buttons LLButton* fwd_button = help_floater->getChild<LLButton>("fwd_btn"); LLButton* back_button = help_floater->getChild<LLButton>("back_btn"); - LLWebBrowserCtrl* browser = help_floater->getChild<LLWebBrowserCtrl>("lsl_guide_html"); + LLMediaCtrl* browser = help_floater->getChild<LLMediaCtrl>("lsl_guide_html"); back_button->setEnabled(browser->canNavigateBack()); fwd_button->setEnabled(browser->canNavigateForward()); @@ -505,7 +505,7 @@ void LLScriptEdCore::setHelpPage(const std::string& help_string) LLFloater* help_floater = mLiveHelpHandle.get(); if (!help_floater) return; - LLWebBrowserCtrl* web_browser = help_floater->getChild<LLWebBrowserCtrl>("lsl_guide_html"); + LLMediaCtrl* web_browser = help_floater->getChild<LLMediaCtrl>("lsl_guide_html"); if (!web_browser) return; LLComboBox* history_combo = help_floater->getChild<LLComboBox>("history_combo"); @@ -646,7 +646,7 @@ void LLScriptEdCore::onBtnDynamicHelp() live_help_floater->childSetAction("back_btn", onClickBack, this); live_help_floater->childSetAction("fwd_btn", onClickForward, this); - LLWebBrowserCtrl* browser = live_help_floater->getChild<LLWebBrowserCtrl>("lsl_guide_html"); + LLMediaCtrl* browser = live_help_floater->getChild<LLMediaCtrl>("lsl_guide_html"); browser->setAlwaysRefresh(TRUE); LLComboBox* help_combo = live_help_floater->getChild<LLComboBox>("history_combo"); @@ -675,7 +675,7 @@ void LLScriptEdCore::onClickBack(void* userdata) LLFloater* live_help_floater = corep->mLiveHelpHandle.get(); if (live_help_floater) { - LLWebBrowserCtrl* browserp = live_help_floater->getChild<LLWebBrowserCtrl>("lsl_guide_html"); + LLMediaCtrl* browserp = live_help_floater->getChild<LLMediaCtrl>("lsl_guide_html"); if (browserp) { browserp->navigateBack(); @@ -690,7 +690,7 @@ void LLScriptEdCore::onClickForward(void* userdata) LLFloater* live_help_floater = corep->mLiveHelpHandle.get(); if (live_help_floater) { - LLWebBrowserCtrl* browserp = live_help_floater->getChild<LLWebBrowserCtrl>("lsl_guide_html"); + LLMediaCtrl* browserp = live_help_floater->getChild<LLMediaCtrl>("lsl_guide_html"); if (browserp) { browserp->navigateForward(); @@ -732,7 +732,7 @@ void LLScriptEdCore::onHelpComboCommit(LLUICtrl* ctrl, void* userdata) corep->addHelpItemToHistory(help_string); - LLWebBrowserCtrl* web_browser = live_help_floater->getChild<LLWebBrowserCtrl>("lsl_guide_html"); + LLMediaCtrl* web_browser = live_help_floater->getChild<LLMediaCtrl>("lsl_guide_html"); LLUIString url_string = gSavedSettings.getString("LSLHelpURL"); url_string.setArg("[LSL_STRING]", help_string); web_browser->navigateTo(url_string); diff --git a/indra/newview/llpreviewsound.cpp b/indra/newview/llpreviewsound.cpp index 84f7562297..0f2b94ebd4 100644 --- a/indra/newview/llpreviewsound.cpp +++ b/indra/newview/llpreviewsound.cpp @@ -32,7 +32,7 @@ #include "llviewerprecompiledheaders.h" -#include "audioengine.h" +#include "llaudioengine.h" #include "llagent.h" // gAgent #include "llbutton.h" #include "llinventory.h" diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index 5984af5df4..32fe996125 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -60,6 +60,7 @@ #include "llfloaterreg.h" #include "llfloatertools.h" #include "llframetimer.h" +#include "llfocusmgr.h" #include "llhudeffecttrail.h" #include "llhudmanager.h" #include "llinventorymodel.h" @@ -75,6 +76,8 @@ #include "llviewercamera.h" #include "llviewercontrol.h" #include "llviewertexturelist.h" +#include "llviewermedia.h" +#include "llviewermediafocus.h" #include "llviewermenu.h" #include "llviewerobject.h" #include "llviewerobjectlist.h" @@ -774,7 +777,7 @@ void LLSelectMgr::addAsIndividual(LLViewerObject *objectp, S32 face, BOOL undoab } -LLObjectSelectionHandle LLSelectMgr::setHoverObject(LLViewerObject *objectp) +LLObjectSelectionHandle LLSelectMgr::setHoverObject(LLViewerObject *objectp, S32 face) { // Always blitz hover list when setting mHoverObjects->deleteAllNodes(); @@ -806,6 +809,7 @@ LLObjectSelectionHandle LLSelectMgr::setHoverObject(LLViewerObject *objectp) { LLViewerObject* cur_objectp = *iter; LLSelectNode* nodep = new LLSelectNode(cur_objectp, FALSE); + nodep->selectTE(face, TRUE); mHoverObjects->addNodeAtEnd(nodep); } @@ -830,8 +834,8 @@ void LLSelectMgr::highlightObjectOnly(LLViewerObject* objectp) return; } - if ((gSavedSettings.getBOOL("SelectOwnedOnly") && !objectp->permYouOwner()) || - (gSavedSettings.getBOOL("SelectMovableOnly") && !objectp->permMove())) + if ((gSavedSettings.getBOOL("SelectOwnedOnly") && !objectp->permYouOwner()) + || (gSavedSettings.getBOOL("SelectMovableOnly") && !objectp->permMove())) { // only select my own objects return; @@ -4565,54 +4569,7 @@ void LLSelectMgr::updateSilhouettes() std::vector<LLViewerObject*> changed_objects; - if (mSelectedObjects->getNumNodes()) - { - //gGLSPipelineSelection.set(); - - //mSilhouetteImagep->bindTexture(); - //glAlphaFunc(GL_GREATER, sHighlightAlphaTest); - - for (S32 pass = 0; pass < 2; pass++) - { - for (LLObjectSelection::iterator iter = mSelectedObjects->begin(); - iter != mSelectedObjects->end(); iter++) - { - LLSelectNode* node = *iter; - LLViewerObject* objectp = node->getObject(); - if (!objectp) - continue; - // do roots first, then children so that root flags are cleared ASAP - BOOL roots_only = (pass == 0); - BOOL is_root = (objectp->isRootEdit()); - if (roots_only != is_root || objectp->mDrawable.isNull()) - { - continue; - } - - if (!node->mSilhouetteExists - || objectp->isChanged(LLXform::SILHOUETTE) - || (objectp->getParent() && objectp->getParent()->isChanged(LLXform::SILHOUETTE))) - { - if (num_sils_genned++ < MAX_SILS_PER_FRAME)// && objectp->mDrawable->isVisible()) - { - generateSilhouette(node, LLViewerCamera::getInstance()->getOrigin()); - changed_objects.push_back(objectp); - } - else if (objectp->isAttachment()) - { - //RN: hack for orthogonal projection of HUD attachments - LLViewerJointAttachment* attachment_pt = (LLViewerJointAttachment*)objectp->getRootEdit()->mDrawable->getParent(); - if (attachment_pt && attachment_pt->getIsHUDAttachment()) - { - LLVector3 camera_pos = LLVector3(-10000.f, 0.f, 0.f); - generateSilhouette(node, camera_pos); - } - } - } - } - } - } - + updateSelectionSilhouette(mSelectedObjects, num_sils_genned, changed_objects); if (mRectSelectedObjects.size() > 0) { //gGLSPipelineSelection.set(); @@ -4806,6 +4763,56 @@ void LLSelectMgr::updateSilhouettes() //gGL.setAlphaRejectSettings(LLRender::CF_DEFAULT); } +void LLSelectMgr::updateSelectionSilhouette(LLObjectSelectionHandle object_handle, S32& num_sils_genned, std::vector<LLViewerObject*>& changed_objects) +{ + if (object_handle->getNumNodes()) + { + //gGLSPipelineSelection.set(); + + //mSilhouetteImagep->bindTexture(); + //glAlphaFunc(GL_GREATER, sHighlightAlphaTest); + + for (S32 pass = 0; pass < 2; pass++) + { + for (LLObjectSelection::iterator iter = object_handle->begin(); + iter != object_handle->end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* objectp = node->getObject(); + if (!objectp) + continue; + // do roots first, then children so that root flags are cleared ASAP + BOOL roots_only = (pass == 0); + BOOL is_root = (objectp->isRootEdit()); + if (roots_only != is_root || objectp->mDrawable.isNull()) + { + continue; + } + + if (!node->mSilhouetteExists + || objectp->isChanged(LLXform::SILHOUETTE) + || (objectp->getParent() && objectp->getParent()->isChanged(LLXform::SILHOUETTE))) + { + if (num_sils_genned++ < MAX_SILS_PER_FRAME)// && objectp->mDrawable->isVisible()) + { + generateSilhouette(node, LLViewerCamera::getInstance()->getOrigin()); + changed_objects.push_back(objectp); + } + else if (objectp->isAttachment()) + { + //RN: hack for orthogonal projection of HUD attachments + LLViewerJointAttachment* attachment_pt = (LLViewerJointAttachment*)objectp->getRootEdit()->mDrawable->getParent(); + if (attachment_pt && attachment_pt->getIsHUDAttachment()) + { + LLVector3 camera_pos = LLVector3(-10000.f, 0.f, 0.f); + generateSilhouette(node, camera_pos); + } + } + } + } + } + } +} void LLSelectMgr::renderSilhouettes(BOOL for_hud) { if (!mRenderSilhouettes) @@ -4848,6 +4855,7 @@ void LLSelectMgr::renderSilhouettes(BOOL for_hud) { inspect_item_id = inspect_instance->getSelectedUUID(); } + LLUUID focus_item_id = LLViewerMediaFocus::getInstance()->getSelectedUUID(); for (S32 pass = 0; pass < 2; pass++) { for (LLObjectSelection::iterator iter = mSelectedObjects->begin(); @@ -4861,7 +4869,11 @@ void LLSelectMgr::renderSilhouettes(BOOL for_hud) { continue; } - if(objectp->getID() == inspect_item_id) + if (objectp->getID() == focus_item_id) + { + node->renderOneSilhouette(gFocusMgr.getFocusColor()); + } + else if(objectp->getID() == inspect_item_id) { node->renderOneSilhouette(sHighlightInspectColor); } @@ -4943,6 +4955,16 @@ void LLSelectMgr::generateSilhouette(LLSelectNode* nodep, const LLVector3& view_ // Utility classes // LLSelectNode::LLSelectNode(LLViewerObject* object, BOOL glow) +: mObject(object), + mIndividualSelection(FALSE), + mTransient(FALSE), + mValid(FALSE), + mPermissions(new LLPermissions()), + mInventorySerial(0), + mSilhouetteExists(FALSE), + mDuplicated(FALSE), + mTESelectMask(0), + mLastTESelected(0) { mObject = object; selectAllTEs(FALSE); @@ -4964,11 +4986,7 @@ LLSelectNode::LLSelectNode(LLViewerObject* object, BOOL glow) LLSelectNode::LLSelectNode(const LLSelectNode& nodep) { - S32 i; - for (i = 0; i < SELECT_MAX_TES; i++) - { - mTESelected[i] = nodep.mTESelected[i]; - } + mTESelectMask = nodep.mTESelectMask; mLastTESelected = nodep.mLastTESelected; mIndividualSelection = nodep.mIndividualSelection; @@ -5021,10 +5039,7 @@ LLSelectNode::~LLSelectNode() void LLSelectNode::selectAllTEs(BOOL b) { - for (S32 i = 0; i < SELECT_MAX_TES; i++) - { - mTESelected[i] = b; - } + mTESelectMask = b ? 0xFFFFFFFF : 0x0; mLastTESelected = 0; } @@ -5034,7 +5049,7 @@ void LLSelectNode::selectTE(S32 te_index, BOOL selected) { return; } - mTESelected[te_index] = selected; + mTESelectMask |= 0x1 << te_index; mLastTESelected = te_index; } @@ -5044,7 +5059,7 @@ BOOL LLSelectNode::isTESelected(S32 te_index) { return FALSE; } - return mTESelected[te_index]; + return (mTESelectMask & (0x1 << te_index)) != 0; } S32 LLSelectNode::getLastSelectedTE() diff --git a/indra/newview/llselectmgr.h b/indra/newview/llselectmgr.h index cfc2b702fc..c41a86e355 100644 --- a/indra/newview/llselectmgr.h +++ b/indra/newview/llselectmgr.h @@ -139,6 +139,7 @@ public: void selectTE(S32 te_index, BOOL selected); BOOL isTESelected(S32 te_index); S32 getLastSelectedTE(); + S32 getTESelectMask() { return mTESelectMask; } void renderOneSilhouette(const LLColor4 &color); void setTransient(BOOL transient) { mTransient = transient; } BOOL isTransient() { return mTransient; } @@ -191,7 +192,7 @@ public: protected: LLPointer<LLViewerObject> mObject; - BOOL mTESelected[SELECT_MAX_TES]; + S32 mTESelectMask; S32 mLastTESelected; }; @@ -409,7 +410,7 @@ public: // converts all objects currently highlighted to a selection, and returns it LLObjectSelectionHandle selectHighlightedObjects(); - LLObjectSelectionHandle setHoverObject(LLViewerObject *objectp); + LLObjectSelectionHandle setHoverObject(LLViewerObject *objectp, S32 face = -1); void highlightObjectOnly(LLViewerObject *objectp); void highlightObjectAndFamily(LLViewerObject *objectp); @@ -649,6 +650,7 @@ private: ESelectType getSelectTypeForObject(LLViewerObject* object); void addAsFamily(std::vector<LLViewerObject*>& objects, BOOL add_to_end = FALSE); void generateSilhouette(LLSelectNode *nodep, const LLVector3& view_point); + void updateSelectionSilhouette(LLObjectSelectionHandle object_handle, S32& num_sils_genned, std::vector<LLViewerObject*>& changed_objects); // Send one message to each region containing an object on selection list. void sendListToRegions( const std::string& message_name, void (*pack_header)(void *user_data), @@ -656,6 +658,7 @@ private: void *user_data, ESendType send_type); + static void packAgentID( void *); static void packAgentAndSessionID(void* user_data); static void packAgentAndGroupID(void* user_data); diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 2975053289..920ec0c65d 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -40,20 +40,15 @@ # include <sys/stat.h> // mkdir() #endif -#if LL_LINUX && LL_GTK -extern "C" { -# include "glib.h" // g_spawn_command_line_async() -} -#endif - -#include "audioengine.h" +#include "llviewermedia_streamingaudio.h" +#include "llaudioengine.h" #ifdef LL_FMOD -# include "audioengine_fmod.h" +# include "llaudioengine_fmod.h" #endif #ifdef LL_OPENAL -#include "audioengine_openal.h" +#include "llaudioengine_openal.h" #endif #include "llares.h" @@ -193,10 +188,6 @@ extern "C" { #include "llwearable.h" #include "llinventorybridge.h" -#if LL_LIBXUL_ENABLED -#include "llmozlib.h" -#endif // LL_LIBXUL_ENABLED - #if LL_WINDOWS #include "llwindebug.h" #include "lldxhardware.h" @@ -724,6 +715,16 @@ bool idle_startup() delete gAudiop; gAudiop = NULL; } + + if (gAudiop) + { + // if the audio engine hasn't set up its own preferred handler for streaming audio then set up the generic streaming audio implementation which uses media plugins + if (NULL == gAudiop->getStreamingAudioImpl()) + { + LL_INFOS("AppInit") << "Using media plugins to render streaming audio" << LL_ENDL; + gAudiop->setStreamingAudioImpl(new LLStreamingAudio_MediaPlugins()); + } + } } } @@ -809,10 +810,7 @@ bool idle_startup() std::string msg = LLTrans::getString("LoginInitializingBrowser"); set_startup_status(0.03f, msg.c_str(), gAgent.mMOTD.c_str()); display_startup(); -#if !defined(LL_WINDOWS) || !defined(LL_DEBUG) - // This generates an error in debug mode on Windows - LLViewerMedia::initBrowser(); -#endif + // LLViewerMedia::initBrowser(); LLStartUp::setStartupState( STATE_LOGIN_SHOW ); return FALSE; } @@ -3486,7 +3484,7 @@ void LLStartUp::multimediaInit() set_startup_status(0.42f, msg.c_str(), gAgent.mMOTD.c_str()); display_startup(); - LLViewerMedia::initClass(); + // LLViewerMedia::initClass(); LLViewerParcelMedia::initClass(); } @@ -3505,7 +3503,7 @@ bool LLStartUp::dispatchURL() // ok, if we've gotten this far and have a startup URL if (!sSLURLCommand.empty()) { - LLWebBrowserCtrl* web = NULL; + LLMediaCtrl* web = NULL; const bool trusted_browser = false; LLURLDispatcher::dispatch(sSLURLCommand, web, trusted_browser); } @@ -3523,7 +3521,7 @@ bool LLStartUp::dispatchURL() || (dy*dy > SLOP*SLOP) ) { std::string url = LLURLSimString::getURL(); - LLWebBrowserCtrl* web = NULL; + LLMediaCtrl* web = NULL; const bool trusted_browser = false; LLURLDispatcher::dispatch(url, web, trusted_browser); } diff --git a/indra/newview/llstatusbar.cpp b/indra/newview/llstatusbar.cpp index b3a820080f..9cabcf4680 100644 --- a/indra/newview/llstatusbar.cpp +++ b/indra/newview/llstatusbar.cpp @@ -625,7 +625,7 @@ class LLBalanceHandler : public LLCommandHandler public: // Requires "trusted" browser/URL source LLBalanceHandler() : LLCommandHandler("balance", true) { } - bool handle(const LLSD& tokens, const LLSD& query_map, LLWebBrowserCtrl* web) + bool handle(const LLSD& tokens, const LLSD& query_map, LLMediaCtrl* web) { if (tokens.size() == 1 && tokens[0].asString() == "request") diff --git a/indra/newview/lltoolcomp.cpp b/indra/newview/lltoolcomp.cpp index cb964082b8..fff18df442 100644 --- a/indra/newview/lltoolcomp.cpp +++ b/indra/newview/lltoolcomp.cpp @@ -765,10 +765,6 @@ void LLToolCompGun::onMouseCaptureLost() return; } mCur->onMouseCaptureLost(); - - // JC - I don't know if this is necessary. Maybe we could lose capture - // if someone ALT-Tab's out when in mouselook. - setCurrentTool( (LLTool*) mGun ); } void LLToolCompGun::handleSelect() diff --git a/indra/newview/lltoolcomp.h b/indra/newview/lltoolcomp.h index b24ba25d5a..81ed0ba8e4 100644 --- a/indra/newview/lltoolcomp.h +++ b/indra/newview/lltoolcomp.h @@ -229,6 +229,7 @@ public: virtual void onMouseCaptureLost(); virtual void handleSelect(); virtual void handleDeselect(); + virtual LLTool* getOverrideTool(MASK mask) { return NULL; } protected: LLToolGun* mGun; diff --git a/indra/newview/lltoolgun.cpp b/indra/newview/lltoolgun.cpp index b70cff3869..8accf6babf 100644 --- a/indra/newview/lltoolgun.cpp +++ b/indra/newview/lltoolgun.cpp @@ -52,7 +52,8 @@ #include "llwindow.h" // setMouseClipping() LLToolGun::LLToolGun( LLToolComposite* composite ) -: LLTool( std::string("gun"), composite ) +: LLTool( std::string("gun"), composite ), + mIsSelected(FALSE) { } @@ -61,6 +62,7 @@ void LLToolGun::handleSelect() gViewerWindow->hideCursor(); gViewerWindow->moveCursorToCenter(); gViewerWindow->mWindow->setMouseClipping(TRUE); + mIsSelected = TRUE; } void LLToolGun::handleDeselect() @@ -68,6 +70,7 @@ void LLToolGun::handleDeselect() gViewerWindow->moveCursorToCenter(); gViewerWindow->showCursor(); gViewerWindow->mWindow->setMouseClipping(FALSE); + mIsSelected = FALSE; } BOOL LLToolGun::handleMouseDown(S32 x, S32 y, MASK mask) @@ -80,7 +83,7 @@ BOOL LLToolGun::handleMouseDown(S32 x, S32 y, MASK mask) BOOL LLToolGun::handleHover(S32 x, S32 y, MASK mask) { - if( gAgent.cameraMouselook() ) + if( gAgent.cameraMouselook() && mIsSelected ) { const F32 NOMINAL_MOUSE_SENSITIVITY = 0.0025f; diff --git a/indra/newview/lltoolgun.h b/indra/newview/lltoolgun.h index 4e945d796c..4644e686b7 100644 --- a/indra/newview/lltoolgun.h +++ b/indra/newview/lltoolgun.h @@ -52,6 +52,8 @@ public: virtual LLTool* getOverrideTool(MASK mask) { return NULL; } virtual BOOL clipMouseWhenDown() { return FALSE; } +private: + BOOL mIsSelected; }; #endif diff --git a/indra/newview/lltoolmgr.cpp b/indra/newview/lltoolmgr.cpp index cf3d15a12a..d52e0b4b80 100644 --- a/indra/newview/lltoolmgr.cpp +++ b/indra/newview/lltoolmgr.cpp @@ -357,22 +357,20 @@ void LLToolMgr::clearTransientTool() } -// The "gun tool", used for handling mouselook, captures the mouse and -// locks it within the window. When the app loses focus we need to -// release this locking. void LLToolMgr::onAppFocusLost() { - mSavedTool = mBaseTool; - mBaseTool = gToolNull; + if (mSelectedTool) + { + mSelectedTool->handleDeselect(); + } updateToolStatus(); } void LLToolMgr::onAppFocusGained() { - if (mSavedTool) + if (mSelectedTool) { - mBaseTool = mSavedTool; - mSavedTool = NULL; + mSelectedTool->handleSelect(); } updateToolStatus(); } diff --git a/indra/newview/lltoolmorph.cpp b/indra/newview/lltoolmorph.cpp index ae3f2f55de..d7d7b5f44b 100644 --- a/indra/newview/lltoolmorph.cpp +++ b/indra/newview/lltoolmorph.cpp @@ -37,7 +37,7 @@ #include "llrender.h" // Library includes -#include "audioengine.h" +#include "llaudioengine.h" #include "llviewercontrol.h" #include "llfontgl.h" #include "sound_ids.h" diff --git a/indra/newview/lltoolpie.cpp b/indra/newview/lltoolpie.cpp index fab336f17d..235d4acf9d 100644 --- a/indra/newview/lltoolpie.cpp +++ b/indra/newview/lltoolpie.cpp @@ -36,11 +36,11 @@ #include "indra_constants.h" #include "llclickaction.h" -#include "llmediabase.h" // for status codes #include "llparcel.h" #include "llagent.h" #include "llviewercontrol.h" +#include "llfocusmgr.h" #include "llfirstuse.h" #include "llfloaterland.h" #include "llfloaterreg.h" @@ -48,7 +48,6 @@ #include "llhoverview.h" #include "llhudeffecttrail.h" #include "llhudmanager.h" -#include "llmediamanager.h" #include "llmenugl.h" #include "llmutelist.h" #include "llselectmgr.h" @@ -65,6 +64,7 @@ #include "llviewerwindow.h" #include "llviewermedia.h" #include "llvoavatarself.h" +#include "llviewermediafocus.h" #include "llworld.h" #include "llui.h" #include "llweb.h" @@ -73,13 +73,15 @@ extern void handle_buy(void*); extern BOOL gDebugClicks; +static bool handle_media_click(const LLPickInfo& info); +static bool handle_media_hover(const LLPickInfo& info); static void handle_click_action_play(); static void handle_click_action_open_media(LLPointer<LLViewerObject> objectp); static ECursorType cursor_from_parcel_media(U8 click_action); LLToolPie::LLToolPie() -: LLTool(std::string("Select")), +: LLTool(std::string("Pie")), mGrabMouseButtonDown( FALSE ), mMouseOutsideSlop( FALSE ), mClickAction(0) @@ -117,6 +119,11 @@ BOOL LLToolPie::handleRightMouseUp(S32 x, S32 y, MASK mask) return LLTool::handleRightMouseUp(x, y, mask); } +BOOL LLToolPie::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + return LLViewerMediaFocus::getInstance()->handleScrollWheel(x, y, clicks); +} + // static void LLToolPie::rightMouseCallback(const LLPickInfo& pick_info) { @@ -150,6 +157,7 @@ BOOL LLToolPie::pickLeftMouseDownCallback() } } + gFocusMgr.setKeyboardFocus(NULL); return LLTool::handleMouseDown(x, y, mask); } @@ -167,9 +175,11 @@ BOOL LLToolPie::pickLeftMouseDownCallback() parent = object->getRootEdit(); } + BOOL touchable = (object && object->flagHandleTouch()) || (parent && parent->flagHandleTouch()); + // If it's a left-click, and we have a special action, do it. if (useClickAction(mask, object, parent)) { @@ -192,6 +202,8 @@ BOOL LLToolPie::pickLeftMouseDownCallback() if ((gAgent.getAvatarObject() != NULL) && (!gAgent.getAvatarObject()->isSitting())) // agent not already sitting { handle_sit_or_stand(); + // put focus in world when sitting on an object + gFocusMgr.setKeyboardFocus(NULL); return TRUE; } // else nothing (fall through to touch) @@ -244,6 +256,14 @@ BOOL LLToolPie::pickLeftMouseDownCallback() } } + if (handle_media_click(mPick)) + { + return FALSE; + } + + // put focus back "in world" + gFocusMgr.setKeyboardFocus(NULL); + // Switch to grab tool if physical or triggerable if (object && !object->isAvatar() && @@ -461,13 +481,13 @@ BOOL LLToolPie::handleHover(S32 x, S32 y, MASK mask) mMouseOutsideSlop = TRUE; } */ - + + // FIXME: This was in the pluginapi branch, but I don't think it's correct. +// gViewerWindow->getWindow()->setCursor(UI_CURSOR_ARROW); + LLViewerObject *object = NULL; LLViewerObject *parent = NULL; - if (gHoverView) - { - object = gViewerWindow->getHoverPick().getObject(); - } + object = gViewerWindow->getHoverPick().getObject(); if (object) { @@ -480,6 +500,11 @@ BOOL LLToolPie::handleHover(S32 x, S32 y, MASK mask) gViewerWindow->setCursor(cursor); lldebugst(LLERR_USER_INPUT) << "hover handled by LLToolPie (inactive)" << llendl; } + else if (handle_media_hover(gViewerWindow->getHoverPick())) + { + // cursor set by media object + lldebugst(LLERR_USER_INPUT) << "hover handled by LLToolPie (inactive)" << llendl; + } else if ((object && !object->isAvatar() && object->usePhysics()) || (parent && !parent->isAvatar() && parent->usePhysics())) { @@ -496,6 +521,15 @@ BOOL LLToolPie::handleHover(S32 x, S32 y, MASK mask) { gViewerWindow->setCursor(UI_CURSOR_ARROW); lldebugst(LLERR_USER_INPUT) << "hover handled by LLToolPie (inactive)" << llendl; + + if(!object) + { + // We need to clear media hover flag + if (LLViewerMediaFocus::getInstance()->getMouseOverFlag()) + { + LLViewerMediaFocus::getInstance()->setMouseOverFlag(false); + } + } } return TRUE; @@ -644,14 +678,14 @@ static void handle_click_action_play() LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); if (!parcel) return; - LLMediaBase::EStatus status = LLViewerParcelMedia::getStatus(); + LLViewerMediaImpl::EMediaStatus status = LLViewerParcelMedia::getStatus(); switch(status) { - case LLMediaBase::STATUS_STARTED: + case LLViewerMediaImpl::MEDIA_PLAYING: LLViewerParcelMedia::pause(); break; - case LLMediaBase::STATUS_PAUSED: + case LLViewerMediaImpl::MEDIA_PAUSED: LLViewerParcelMedia::start(); break; @@ -661,6 +695,111 @@ static void handle_click_action_play() } } +static bool handle_media_click(const LLPickInfo& pick) +{ + //FIXME: how do we handle object in different parcel than us? + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + LLPointer<LLViewerObject> objectp = pick.getObject(); + + + if (!parcel || + objectp.isNull() || + pick.mObjectFace < 0 || + pick.mObjectFace >= objectp->getNumTEs()) + { + LLSelectMgr::getInstance()->deselect(); + LLViewerMediaFocus::getInstance()->clearFocus(); + + return false; + } + + + + // HACK: This is directly referencing an impl name. BAD! + // This can be removed when we have a truly generic media browser that only + // builds an impl based on the type of url it is passed. + + // is media playing on this face? + const LLTextureEntry* tep = objectp->getTE(pick.mObjectFace); + + viewer_media_t media_impl = LLViewerMedia::getMediaImplFromTextureID(tep->getID()); + if (tep + && media_impl.notNull() + && media_impl->hasMedia() + && gSavedSettings.getBOOL("MediaOnAPrimUI")) + { + LLObjectSelectionHandle selection = LLViewerMediaFocus::getInstance()->getSelection(); + if (! selection->contains(pick.getObject(), pick.mObjectFace)) + { + LLViewerMediaFocus::getInstance()->setFocusFace(TRUE, pick.getObject(), pick.mObjectFace, media_impl); + } + else + { + media_impl->mouseDown(pick.mXYCoords.mX, pick.mXYCoords.mY); + media_impl->mouseCapture(); // the mouse-up will happen when capture is lost + } + + return true; + } + + LLSelectMgr::getInstance()->deselect(); + LLViewerMediaFocus::getInstance()->clearFocus(); + + return false; +} + +static bool handle_media_hover(const LLPickInfo& pick) +{ + //FIXME: how do we handle object in different parcel than us? + LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (!parcel) return false; + + LLPointer<LLViewerObject> objectp = pick.getObject(); + + // Early out cases. Must clear mouse over media focus flag + // did not hit an object or did not hit a valid face + if ( objectp.isNull() || + pick.mObjectFace < 0 || + pick.mObjectFace >= objectp->getNumTEs() ) + { + LLViewerMediaFocus::getInstance()->setMouseOverFlag(false); + return false; + } + + + // HACK: This is directly referencing an impl name. BAD! + // This can be removed when we have a truly generic media browser that only + // builds an impl based on the type of url it is passed. + + // is media playing on this face? + const LLTextureEntry* tep = objectp->getTE(pick.mObjectFace); + viewer_media_t media_impl = LLViewerMedia::getMediaImplFromTextureID(tep->getID()); + if (tep + && media_impl.notNull() + && media_impl->hasMedia() + && gSavedSettings.getBOOL("MediaOnAPrimUI")) + { + if(LLViewerMediaFocus::getInstance()->getFocus()) + { + media_impl->mouseMove(pick.mXYCoords.mX, pick.mXYCoords.mY); + } + + // Set mouse over flag if unset + if (! LLViewerMediaFocus::getInstance()->getMouseOverFlag()) + { + LLSelectMgr::getInstance()->setHoverObject(objectp, pick.mObjectFace); + LLViewerMediaFocus::getInstance()->setMouseOverFlag(true, media_impl); + LLViewerMediaFocus::getInstance()->setPickInfo(pick); + } + + return true; + } + LLViewerMediaFocus::getInstance()->setMouseOverFlag(false); + + return false; +} + + static void handle_click_action_open_media(LLPointer<LLViewerObject> objectp) { //FIXME: how do we handle object in different parcel than us? @@ -675,7 +814,7 @@ static void handle_click_action_open_media(LLPointer<LLViewerObject> objectp) if( face < 0 || face >= objectp->getNumTEs() ) return; // is media playing on this face? - if (!LLViewerMedia::isActiveMediaTexture(objectp->getTE(face)->getID())) + if (LLViewerMedia::getMediaImplFromTextureID(objectp->getTE(face)->getID()) != NULL) { handle_click_action_play(); return; @@ -685,18 +824,7 @@ static void handle_click_action_open_media(LLPointer<LLViewerObject> objectp) std::string media_type = std::string ( parcel->getMediaType() ); LLStringUtil::trim(media_url); - // Get the scheme, see if that is handled as well. - LLURI uri(media_url); - std::string media_scheme = uri.scheme() != "" ? uri.scheme() : "http"; - - // HACK: This is directly referencing an impl name. BAD! - // This can be removed when we have a truly generic media browser that only - // builds an impl based on the type of url it is passed. - - if( LLMediaManager::getInstance()->supportsMediaType( "LLMediaImplLLMozLib", media_scheme, media_type ) ) - { - LLWeb::loadURL(media_url); - } + LLWeb::loadURL(media_url); } static ECursorType cursor_from_parcel_media(U8 click_action) @@ -714,19 +842,12 @@ static ECursorType cursor_from_parcel_media(U8 click_action) std::string media_type = std::string ( parcel->getMediaType() ); LLStringUtil::trim(media_url); - // Get the scheme, see if that is handled as well. - LLURI uri(media_url); - std::string media_scheme = uri.scheme() != "" ? uri.scheme() : "http"; - - if( LLMediaManager::getInstance()->supportsMediaType( "LLMediaImplLLMozLib", media_scheme, media_type ) ) - { - open_cursor = UI_CURSOR_TOOLMEDIAOPEN; - } + open_cursor = UI_CURSOR_TOOLMEDIAOPEN; - LLMediaBase::EStatus status = LLViewerParcelMedia::getStatus(); + LLViewerMediaImpl::EMediaStatus status = LLViewerParcelMedia::getStatus(); switch(status) { - case LLMediaBase::STATUS_STARTED: + case LLViewerMediaImpl::MEDIA_PLAYING: return click_action == CLICK_ACTION_PLAY ? UI_CURSOR_TOOLPAUSE : open_cursor; default: return UI_CURSOR_TOOLPLAY; diff --git a/indra/newview/lltoolpie.h b/indra/newview/lltoolpie.h index d2437e813d..a55e435282 100644 --- a/indra/newview/lltoolpie.h +++ b/indra/newview/lltoolpie.h @@ -51,6 +51,7 @@ public: virtual BOOL handleRightMouseUp(S32 x, S32 y, MASK mask); virtual BOOL handleHover(S32 x, S32 y, MASK mask); virtual BOOL handleDoubleClick(S32 x, S32 y, MASK mask); + virtual BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); virtual void render(); diff --git a/indra/newview/lltoolplacer.cpp b/indra/newview/lltoolplacer.cpp index 607818305e..de68dd6153 100644 --- a/indra/newview/lltoolplacer.cpp +++ b/indra/newview/lltoolplacer.cpp @@ -56,7 +56,7 @@ #include "llvolumemessage.h" #include "llhudmanager.h" #include "llagent.h" -#include "audioengine.h" +#include "llaudioengine.h" #include "llhudeffecttrail.h" #include "llviewerobjectlist.h" #include "llviewercamera.h" diff --git a/indra/newview/llurldispatcher.cpp b/indra/newview/llurldispatcher.cpp index 9fb6d1c874..cb68045310 100644 --- a/indra/newview/llurldispatcher.cpp +++ b/indra/newview/llurldispatcher.cpp @@ -37,8 +37,7 @@ #include "llagent.h" // teleportViaLocation() #include "llcommandhandler.h" #include "llfloaterdirectory.h" -#include "llfloaterhtml.h" -#include "llfloaterhtmlhelp.h" +#include "llfloatermediabrowser.h" #include "llfloaterreg.h" #include "llfloaterurldisplay.h" #include "llfloaterworldmap.h" @@ -57,7 +56,7 @@ class LLURLDispatcherImpl { public: static bool dispatch(const std::string& url, - LLWebBrowserCtrl* web, + LLMediaCtrl* web, bool trusted_browser); // returns true if handled or explicitly blocked. @@ -66,7 +65,7 @@ public: private: static bool dispatchCore(const std::string& url, bool right_mouse, - LLWebBrowserCtrl* web, + LLMediaCtrl* web, bool trusted_browser); // handles both left and right click @@ -76,7 +75,7 @@ private: static bool dispatchApp(const std::string& url, bool right_mouse, - LLWebBrowserCtrl* web, + LLMediaCtrl* web, bool trusted_browser); // Handles secondlife:///app/agent/<agent_id>/about and similar // by showing panel in Search floater. @@ -102,7 +101,7 @@ private: // static bool LLURLDispatcherImpl::dispatchCore(const std::string& url, bool right_mouse, - LLWebBrowserCtrl* web, + LLMediaCtrl* web, bool trusted_browser) { if (url.empty()) return false; @@ -122,7 +121,7 @@ bool LLURLDispatcherImpl::dispatchCore(const std::string& url, // static bool LLURLDispatcherImpl::dispatch(const std::string& url, - LLWebBrowserCtrl* web, + LLMediaCtrl* web, bool trusted_browser) { llinfos << "url: " << url << llendl; @@ -135,7 +134,7 @@ bool LLURLDispatcherImpl::dispatchRightClick(const std::string& url) { llinfos << "url: " << url << llendl; const bool right_click = true; - LLWebBrowserCtrl* web = NULL; + LLMediaCtrl* web = NULL; const bool trusted_browser = false; return dispatchCore(url, right_click, web, trusted_browser); } @@ -156,7 +155,7 @@ bool LLURLDispatcherImpl::dispatchHelp(const std::string& url, bool right_mouse) // static bool LLURLDispatcherImpl::dispatchApp(const std::string& url, bool right_mouse, - LLWebBrowserCtrl* web, + LLMediaCtrl* web, bool trusted_browser) { if (!LLSLURL::isSLURL(url)) @@ -327,7 +326,7 @@ public: LLTeleportHandler() : LLCommandHandler("teleport", true) { } bool handle(const LLSD& tokens, const LLSD& query_map, - LLWebBrowserCtrl* web) + LLMediaCtrl* web) { // construct a "normal" SLURL, resolve the region to // a global position, and teleport to it @@ -355,7 +354,7 @@ LLTeleportHandler gTeleportHandler; // static bool LLURLDispatcher::dispatch(const std::string& url, - LLWebBrowserCtrl* web, + LLMediaCtrl* web, bool trusted_browser) { return LLURLDispatcherImpl::dispatch(url, web, trusted_browser); @@ -377,6 +376,6 @@ bool LLURLDispatcher::dispatchFromTextEditor(const std::string& url) // click on it. // *TODO: Make this trust model more refined. JC const bool trusted_browser = true; - LLWebBrowserCtrl* web = NULL; + LLMediaCtrl* web = NULL; return LLURLDispatcherImpl::dispatch(url, web, trusted_browser); } diff --git a/indra/newview/llurldispatcher.h b/indra/newview/llurldispatcher.h index 15e3ec73d8..ff8a351253 100644 --- a/indra/newview/llurldispatcher.h +++ b/indra/newview/llurldispatcher.h @@ -32,14 +32,14 @@ #ifndef LLURLDISPATCHER_H #define LLURLDISPATCHER_H -class LLWebBrowserCtrl; +class LLMediaCtrl; class LLURLDispatcher { public: static bool dispatch(const std::string& url, - LLWebBrowserCtrl* web, + LLMediaCtrl* web, bool trusted_browser); // At startup time and on clicks in internal web browsers, // teleport, open map, or run requested command. @@ -48,7 +48,7 @@ public: // secondlife:///app/agent/3d6181b0-6a4b-97ef-18d8-722652995cf1/show // sl://app/foo/bar // @param web - // Pointer to LLWebBrowserCtrl sending URL, can be NULL + // Pointer to LLMediaCtrl sending URL, can be NULL // @param trusted_browser // True if coming inside the app AND from a brower instance // that navigates to trusted (Linden Lab) pages. diff --git a/indra/newview/llvieweraudio.cpp b/indra/newview/llvieweraudio.cpp index a1e55c1137..49506db173 100644 --- a/indra/newview/llvieweraudio.cpp +++ b/indra/newview/llvieweraudio.cpp @@ -32,7 +32,7 @@ #include "llviewerprecompiledheaders.h" -#include "audioengine.h" +#include "llaudioengine.h" #include "llagent.h" #include "llappviewer.h" #include "llvieweraudio.h" @@ -128,7 +128,6 @@ void audio_update_volume(bool force_update) gAudiop->setMasterGain ( master_volume ); gAudiop->setDopplerFactor(gSavedSettings.getF32("AudioLevelDoppler")); - gAudiop->setDistanceFactor(gSavedSettings.getF32("AudioLevelDistance")); gAudiop->setRolloffFactor(gSavedSettings.getF32("AudioLevelRolloff")); #ifdef kAUDIO_ENABLE_WIND gAudiop->enableWind(!mute_audio); diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index 4640745e8c..2c1707e49f 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -39,7 +39,7 @@ #include "llwindow.h" // getGamma() // For Listeners -#include "audioengine.h" +#include "llaudioengine.h" #include "llagent.h" #include "llconsole.h" #include "lldrawpoolterrain.h" @@ -523,7 +523,6 @@ void settings_setup_listeners() gSavedSettings.getControl("AudioLevelMusic")->getSignal()->connect(boost::bind(&handleAudioVolumeChanged, _2)); gSavedSettings.getControl("AudioLevelMedia")->getSignal()->connect(boost::bind(&handleAudioVolumeChanged, _2)); gSavedSettings.getControl("AudioLevelVoice")->getSignal()->connect(boost::bind(&handleAudioVolumeChanged, _2)); - gSavedSettings.getControl("AudioLevelDistance")->getSignal()->connect(boost::bind(&handleAudioVolumeChanged, _2)); gSavedSettings.getControl("AudioLevelDoppler")->getSignal()->connect(boost::bind(&handleAudioVolumeChanged, _2)); gSavedSettings.getControl("AudioLevelRolloff")->getSignal()->connect(boost::bind(&handleAudioVolumeChanged, _2)); gSavedSettings.getControl("AudioStreamingMusic")->getSignal()->connect(boost::bind(&handleAudioStreamMusicChanged, _2)); diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index c1915d2ead..0a59ba8a50 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -66,7 +66,7 @@ #include "llfloatergroups.h" #include "llfloaterhardwaresettings.h" #include "llfloaterhtmlcurrency.h" -#include "llfloaterhtmlhelp.h" +#include "llfloatermediabrowser.h" #include "llfloaterhud.h" #include "llfloaterimagepreview.h" #include "llimpanel.h" diff --git a/indra/newview/llviewergesture.cpp b/indra/newview/llviewergesture.cpp index 4155a87e14..93b126b67d 100644 --- a/indra/newview/llviewergesture.cpp +++ b/indra/newview/llviewergesture.cpp @@ -34,7 +34,7 @@ #include "llviewergesture.h" -#include "audioengine.h" +#include "llaudioengine.h" #include "lldir.h" #include "llviewerinventory.h" #include "sound_ids.h" // for testing diff --git a/indra/newview/llviewerkeyboard.cpp b/indra/newview/llviewerkeyboard.cpp index 6bb302727d..fc2f00a2ea 100644 --- a/indra/newview/llviewerkeyboard.cpp +++ b/indra/newview/llviewerkeyboard.cpp @@ -539,7 +539,7 @@ void start_chat( EKeystate s ) void start_gesture( EKeystate s ) { if (KEYSTATE_UP == s && - !(gFocusMgr.getKeyboardFocus() && gFocusMgr.getKeyboardFocus()->acceptsTextInput())) + !(gFocusMgr.getKeyboardFocus() && dynamic_cast<LLUICtrl*>(gFocusMgr.getKeyboardFocus())->acceptsTextInput())) { if (LLNearbyChatBar::getInstance()->getCurrentChat().empty()) { diff --git a/indra/newview/llviewermedia.cpp b/indra/newview/llviewermedia.cpp index abaac42874..a06d913fd6 100644 --- a/indra/newview/llviewermedia.cpp +++ b/indra/newview/llviewermedia.cpp @@ -33,609 +33,1118 @@ #include "llviewerprecompiledheaders.h" #include "llviewermedia.h" - -#include "audioengine.h" - -#include "llparcel.h" - +#include "llviewermediafocus.h" +#include "llhoverview.h" #include "llmimetypes.h" #include "llviewercontrol.h" #include "llviewertexture.h" #include "llviewerparcelmedia.h" #include "llviewerparcelmgr.h" -#include "llviewerparcelmedia.h" -#include "llviewerparcelmgr.h" #include "llviewerwindow.h" #include "llversionviewer.h" #include "llviewertexturelist.h" +#include "llpluginclassmedia.h" #include "llevent.h" // LLSimpleListener -#include "llmediamanager.h" #include "lluuid.h" #include <boost/bind.hpp> // for SkinFolder listener #include <boost/signals2.hpp> +// Move this to its own file. -// Implementation functions not exported into header file -class LLViewerMediaImpl - : public LLMediaObserver +LLViewerMediaEventEmitter::~LLViewerMediaEventEmitter() { -public: - LLViewerMediaImpl() - : mMediaSource( NULL ), - mMovieImageID(), - mMovieImageHasMips(false) - { } - - void destroyMediaSource(); - - void play(const std::string& media_url, - const std::string& mime_type, - const LLUUID& placeholder_texture_id, - S32 media_width, S32 media_height, U8 media_auto_scale, - U8 media_loop); - - void stop(); - void pause(); - void start(); - void seek(F32 time); - void setVolume(F32 volume); - LLMediaBase::EStatus getStatus(); - - /*virtual*/ void onMediaSizeChange(const EventType& event_in); - /*virtual*/ void onMediaContentsChange(const EventType& event_in); - - void restoreMovieImage(); - void updateImagesMediaStreams(); - LLUUID getMediaTextureID(); - - // Internally set our desired browser user agent string, including - // the Second Life version and skin name. Used because we can - // switch skins without restarting the app. - static void updateBrowserUserAgent(); - - // Callback for when the SkinCurrent control is changed to - // switch the user agent string to indicate the new skin. - static bool handleSkinCurrentChanged(const LLSD& newvalue); + observerListType::iterator iter = mObservers.begin(); -public: + while( iter != mObservers.end() ) + { + LLViewerMediaObserver *self = *iter; + iter++; + remObserver(self); + } +} - // a single media url with some data and an impl. - LLMediaBase* mMediaSource; - LLUUID mMovieImageID; - bool mMovieImageHasMips; - std::string mMediaURL; - std::string mMimeType; +/////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaEventEmitter::addObserver( LLViewerMediaObserver* observer ) +{ + if ( ! observer ) + return false; -private: - void initializePlaceholderImage(LLViewerMediaTexture *placeholder_image, LLMediaBase *media_source); -}; + if ( std::find( mObservers.begin(), mObservers.end(), observer ) != mObservers.end() ) + return false; -static LLViewerMediaImpl sViewerMediaImpl; -////////////////////////////////////////////////////////////////////////////////////////// + mObservers.push_back( observer ); + observer->mEmitters.push_back( this ); -void LLViewerMediaImpl::destroyMediaSource() + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// +bool LLViewerMediaEventEmitter::remObserver( LLViewerMediaObserver* observer ) +{ + if ( ! observer ) + return false; + + mObservers.remove( observer ); + observer->mEmitters.remove(this); + + return true; +} + +/////////////////////////////////////////////////////////////////////////////// +// +void LLViewerMediaEventEmitter::emitEvent( LLPluginClassMedia* media, LLPluginClassMediaOwner::EMediaEvent event ) { - LLMediaManager* mgr = LLMediaManager::getInstance(); - if ( mMediaSource ) + observerListType::iterator iter = mObservers.begin(); + + while( iter != mObservers.end() ) { - mMediaSource->remObserver(this); - mgr->destroySource( mMediaSource ); + LLViewerMediaObserver *self = *iter; + ++iter; + self->handleMediaEvent( media, event ); + } +} - // Restore the texture - restoreMovieImage(); +// Move this to its own file. +LLViewerMediaObserver::~LLViewerMediaObserver() +{ + std::list<LLViewerMediaEventEmitter *>::iterator iter = mEmitters.begin(); + while( iter != mEmitters.end() ) + { + LLViewerMediaEventEmitter *self = *iter; + iter++; + self->remObserver( this ); } - mMediaSource = NULL; } -void LLViewerMediaImpl::play(const std::string& media_url, - const std::string& mime_type, - const LLUUID& placeholder_texture_id, - S32 media_width, S32 media_height, U8 media_auto_scale, - U8 media_loop) + +// Move this to its own file. +// helper class that tries to download a URL from a web site and calls a method +// on the Panel Land Media and to discover the MIME type +class LLMimeDiscoveryResponder : public LLHTTPClient::Responder { - // first stop any previously playing media - stop(); +LOG_CLASS(LLMimeDiscoveryResponder); +public: + LLMimeDiscoveryResponder( viewer_media_t media_impl) + : mMediaImpl(media_impl), + mInitialized(false) + {} + + - // Save this first, as init/load below may fire events - mMovieImageID = placeholder_texture_id; + virtual void completedHeader(U32 status, const std::string& reason, const LLSD& content) + { + std::string media_type = content["content-type"].asString(); + std::string::size_type idx1 = media_type.find_first_of(";"); + std::string mime_type = media_type.substr(0, idx1); + completeAny(status, mime_type); + } - // If the mime_type passed in is different than the cached one, and - // Auto-discovery is turned OFF, replace the cached mime_type with the new one. - if(mime_type != mMimeType && - ! gSavedSettings.getBOOL("AutoMimeDiscovery")) + virtual void error( U32 status, const std::string& reason ) { - mMimeType = mime_type; + // completeAny(status, "none/none"); } - LLURI url(media_url); - std::string scheme = url.scheme() != "" ? url.scheme() : "http"; - LLMediaManager* mgr = LLMediaManager::getInstance(); - mMediaSource = mgr->createSourceFromMimeType(scheme, mMimeType ); - if ( !mMediaSource ) + void completeAny(U32 status, const std::string& mime_type) { - if (mMimeType != "none/none") + if(!mInitialized && ! mime_type.empty()) { - llwarns << "media source create failed " << media_url - << " type " << mMimeType - << llendl; + if (mMediaImpl->initializeMedia(mime_type)) + { + mInitialized = true; + mMediaImpl->play(); + } } - return; } - // Store the URL and Mime Type - mMediaURL = media_url; + public: + viewer_media_t mMediaImpl; + bool mInitialized; +}; +typedef std::vector<LLViewerMediaImpl*> impl_list; +static impl_list sViewerMediaImplList; + +////////////////////////////////////////////////////////////////////////////////////////// +// LLViewerMedia - if ((media_width != 0) && (media_height != 0)) +////////////////////////////////////////////////////////////////////////////////////////// +// static +viewer_media_t LLViewerMedia::newMediaImpl(const std::string& media_url, + const LLUUID& texture_id, + S32 media_width, S32 media_height, U8 media_auto_scale, + U8 media_loop, + std::string mime_type) +{ + LLViewerMediaImpl* media_impl = getMediaImplFromTextureID(texture_id); + if(media_impl == NULL || texture_id.isNull()) { - mMediaSource->setRequestedMediaSize(media_width, media_height); + // Create the media impl + media_impl = new LLViewerMediaImpl(media_url, texture_id, media_width, media_height, media_auto_scale, media_loop, mime_type); + sViewerMediaImplList.push_back(media_impl); } - - mMediaSource->setLooping(media_loop); - mMediaSource->setAutoScaled(media_auto_scale); - mMediaSource->addObserver( this ); - mMediaSource->navigateTo( media_url ); - mMediaSource->addCommand(LLMediaBase::COMMAND_START); + else + { + media_impl->stop(); + media_impl->mTextureId = texture_id; + media_impl->mMediaURL = media_url; + media_impl->mMediaWidth = media_width; + media_impl->mMediaHeight = media_height; + media_impl->mMediaAutoScale = media_auto_scale; + media_impl->mMediaLoop = media_loop; + if(! media_url.empty()) + media_impl->navigateTo(media_url, mime_type, true); + } + return media_impl; } -void LLViewerMediaImpl::stop() +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::removeMedia(LLViewerMediaImpl* media) { - destroyMediaSource(); + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) + { + if(media == *iter) + { + sViewerMediaImplList.erase(iter); + return; + } + } } -void LLViewerMediaImpl::pause() +////////////////////////////////////////////////////////////////////////////////////////// +// static +LLViewerMediaImpl* LLViewerMedia::getMediaImplFromTextureID(const LLUUID& texture_id) { - if(mMediaSource) + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) { - mMediaSource->addCommand(LLMediaBase::COMMAND_PAUSE); + LLViewerMediaImpl* media_impl = *iter; + if(media_impl->getMediaTextureID() == texture_id) + { + return media_impl; + } } + return NULL; } -void LLViewerMediaImpl::start() +////////////////////////////////////////////////////////////////////////////////////////// +// static +std::string LLViewerMedia::getCurrentUserAgent() { - if(mMediaSource) + // Don't use user-visible string to avoid + // punctuation and strange characters. + std::string skin_name = gSavedSettings.getString("SkinCurrent"); + + // Just in case we need to check browser differences in A/B test + // builds. + std::string channel = gSavedSettings.getString("VersionChannelName"); + + // append our magic version number string to the browser user agent id + // See the HTTP 1.0 and 1.1 specifications for allowed formats: + // http://www.ietf.org/rfc/rfc1945.txt section 10.15 + // http://www.ietf.org/rfc/rfc2068.txt section 3.8 + // This was also helpful: + // http://www.mozilla.org/build/revised-user-agent-strings.html + std::ostringstream codec; + codec << "SecondLife/"; + codec << LL_VERSION_MAJOR << "." << LL_VERSION_MINOR << "." << LL_VERSION_PATCH << "." << LL_VERSION_BUILD; + codec << " (" << channel << "; " << skin_name << " skin)"; + llinfos << codec.str() << llendl; + + return codec.str(); +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::updateBrowserUserAgent() +{ + std::string user_agent = getCurrentUserAgent(); + + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) { - mMediaSource->addCommand(LLMediaBase::COMMAND_START); + LLViewerMediaImpl* pimpl = *iter; + if(pimpl->mMediaSource && pimpl->mMediaSource->pluginSupportsMediaBrowser()) + { + pimpl->mMediaSource->setBrowserUserAgent(user_agent); + } } + } -void LLViewerMediaImpl::seek(F32 time) +////////////////////////////////////////////////////////////////////////////////////////// +// static +bool LLViewerMedia::handleSkinCurrentChanged(const LLSD& /*newvalue*/) { - if(mMediaSource) + // gSavedSettings is already updated when this function is called. + updateBrowserUserAgent(); + return true; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +bool LLViewerMedia::textureHasMedia(const LLUUID& texture_id) +{ + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) { - mMediaSource->seek(time); + LLViewerMediaImpl* pimpl = *iter; + if(pimpl->getMediaTextureID() == texture_id) + { + return true; + } } + return false; } -void LLViewerMediaImpl::setVolume(F32 volume) +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::setVolume(F32 volume) { - if(mMediaSource) + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) { - mMediaSource->setVolume( volume); + LLViewerMediaImpl* pimpl = *iter; + pimpl->setVolume(volume); } } -LLMediaBase::EStatus LLViewerMediaImpl::getStatus() +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::updateMedia() { - if (mMediaSource) + impl_list::iterator iter = sViewerMediaImplList.begin(); + impl_list::iterator end = sViewerMediaImplList.end(); + + for(; iter != end; iter++) { - return mMediaSource->getStatus(); + LLViewerMediaImpl* pimpl = *iter; + pimpl->update(); } - else +} + +////////////////////////////////////////////////////////////////////////////////////////// +// static +void LLViewerMedia::cleanupClass() +{ + // This is no longer necessary, since the list is no longer smart pointers. +#if 0 + while(!sViewerMediaImplList.empty()) { - return LLMediaBase::STATUS_UNKNOWN; + sViewerMediaImplList.pop_back(); } +#endif +} + +////////////////////////////////////////////////////////////////////////////////////////// +// LLViewerMediaImpl +////////////////////////////////////////////////////////////////////////////////////////// +LLViewerMediaImpl::LLViewerMediaImpl(const std::string& media_url, + const LLUUID& texture_id, + S32 media_width, + S32 media_height, + U8 media_auto_scale, + U8 media_loop, + const std::string& mime_type) +: + mMediaSource( NULL ), + mMovieImageHasMips(false), + mTextureId(texture_id), + mMediaWidth(media_width), + mMediaHeight(media_height), + mMediaAutoScale(media_auto_scale), + mMediaLoop(media_loop), + mMediaURL(media_url), + mMimeType(mime_type), + mNeedsNewTexture(true), + mSuspendUpdates(false), + mVisible(true) +{ + createMediaSource(); } ////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::restoreMovieImage() +LLViewerMediaImpl::~LLViewerMediaImpl() { - // IF the media image hasn't changed, do nothing - if (mMovieImageID.isNull()) + if( gEditMenuHandler == this ) { - return; + gEditMenuHandler = NULL; } + + destroyMediaSource(); + LLViewerMedia::removeMedia(this); +} - //restore the movie image to the old one - LLViewerMediaTexture* media = LLViewerTextureManager::findMediaTexture( mMovieImageID ) ; - if (media) +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::initializeMedia(const std::string& mime_type) +{ + if((mMediaSource == NULL) || (mMimeType != mime_type)) { - if(media->getOldTexture())//set back to the old texture if it exists + if(! initializePlugin(mime_type)) { - media->switchToTexture(media->getOldTexture()) ; - media->setPlaying(FALSE) ; + LL_WARNS("Plugin") << "plugin intialization failed for mime type: " << mime_type << LL_ENDL; + LLSD args; + args["MIME_TYPE"] = mime_type; + LLNotifications::instance().add("NoPlugin", args); + + return false; } - media->reinit(mMovieImageHasMips); } - mMovieImageID.setNull(); -} + // play(); + return (mMediaSource != NULL); +} ////////////////////////////////////////////////////////////////////////////////////////// -void LLViewerMediaImpl::updateImagesMediaStreams() +void LLViewerMediaImpl::createMediaSource() { - LLMediaManager::updateClass(); + if(! mMediaURL.empty()) + { + navigateTo(mMediaURL, mMimeType, true); + } + else if(! mMimeType.empty()) + { + initializeMedia(mMimeType); + } + } -void LLViewerMediaImpl::initializePlaceholderImage(LLViewerMediaTexture *placeholder_image, LLMediaBase *media_source) +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::destroyMediaSource() { - int media_width = media_source->getMediaWidth(); - int media_height = media_source->getMediaHeight(); - //int media_rowspan = media_source->getMediaRowSpan(); - - // if width & height are invalid, don't bother doing anything - if ( media_width < 1 || media_height < 1 ) + mNeedsNewTexture = true; + if(! mMediaSource) + { return; + } + // Restore the texture + updateMovieImage(LLUUID::null, false); + delete mMediaSource; + mMediaSource = NULL; +} - llinfos << "initializing media placeholder" << llendl; - llinfos << "movie image id " << mMovieImageID << llendl; +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::setMediaType(const std::string& media_type) +{ + mMimeType = media_type; +} - int texture_width = LLMediaManager::textureWidthFromMediaWidth( media_width ); - int texture_height = LLMediaManager::textureHeightFromMediaHeight( media_height ); - int texture_depth = media_source->getMediaDepth(); +////////////////////////////////////////////////////////////////////////////////////////// +/*static*/ +LLPluginClassMedia* LLViewerMediaImpl::newSourceFromMediaType(std::string media_type, LLPluginClassMediaOwner *owner /* may be NULL */, S32 default_width, S32 default_height) +{ + std::string plugin_basename = LLMIMETypes::implType(media_type); - // MEDIAOPT: check to see if size actually changed before doing work - placeholder_image->destroyGLTexture(); - // MEDIAOPT: apparently just calling setUseMipMaps(FALSE) doesn't work? - placeholder_image->reinit(FALSE); // probably not needed + if(plugin_basename.empty()) + { + LL_WARNS("Media") << "Couldn't find plugin for media type " << media_type << LL_ENDL; + } + else + { + std::string plugins_path = gDirUtilp->getLLPluginDir(); + plugins_path += gDirUtilp->getDirDelimiter(); + + std::string launcher_name = gDirUtilp->getLLPluginLauncher(); + std::string plugin_name = gDirUtilp->getLLPluginFilename(plugin_basename); + + // See if the plugin executable exists + llstat s; + if(LLFile::stat(launcher_name, &s)) + { + LL_WARNS("Media") << "Couldn't find launcher at " << launcher_name << LL_ENDL; + } + else if(LLFile::stat(plugin_name, &s)) + { + LL_WARNS("Media") << "Couldn't find plugin at " << plugin_name << LL_ENDL; + } + else + { + LLPluginClassMedia* media_source = new LLPluginClassMedia(owner); + media_source->setSize(default_width, default_height); + if (media_source->init(launcher_name, plugin_name)) + { + return media_source; + } + else + { + LL_WARNS("Media") << "Failed to init plugin. Destroying." << LL_ENDL; + delete media_source; + } + } + } + + return NULL; +} - // MEDIAOPT: seems insane that we actually have to make an imageraw then - // immediately discard it - LLPointer<LLImageRaw> raw = new LLImageRaw(texture_width, texture_height, texture_depth); - raw->clear(0x0f, 0x0f, 0x0f, 0xff); - int discard_level = 0; +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::initializePlugin(const std::string& media_type) +{ + if(mMediaSource) + { + // Save the previous media source's last set size before destroying it. + mMediaWidth = mMediaSource->getSetWidth(); + mMediaHeight = mMediaSource->getSetHeight(); + } + + // Always delete the old media impl first. + destroyMediaSource(); + + // and unconditionally set the mime type + mMimeType = media_type; - // ask media source for correct GL image format constants - placeholder_image->setExplicitFormat(media_source->getTextureFormatInternal(), - media_source->getTextureFormatPrimary(), - media_source->getTextureFormatType()); + LLPluginClassMedia* media_source = newSourceFromMediaType(media_type, this, mMediaWidth, mMediaHeight); + + if (media_source) + { + media_source->setDisableTimeout(gSavedSettings.getBOOL("DebugPluginDisableTimeout")); + media_source->setLoop(mMediaLoop); + media_source->setAutoScale(mMediaAutoScale); + media_source->setBrowserUserAgent(LLViewerMedia::getCurrentUserAgent()); + + mMediaSource = media_source; + return true; + } - placeholder_image->createGLTexture(discard_level, raw); + return false; +} - // placeholder_image->setExplicitFormat() - placeholder_image->setUseMipMaps(FALSE); +void LLViewerMediaImpl::setSize(int width, int height) +{ + mMediaWidth = width; + mMediaHeight = height; + if(mMediaSource) + { + mMediaSource->setSize(width, height); + } } -// virtual -void LLViewerMediaImpl::onMediaContentsChange(const EventType& event_in) +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::play() { - LLMediaBase* media_source = event_in.getSubject(); - LLViewerMediaTexture* placeholder_image = LLViewerTextureManager::getMediaTexture( mMovieImageID ) ; - if (placeholder_image && placeholder_image->hasValidGLTexture()) + // first stop any previously playing media + // stop(); + + // mMediaSource->addObserver( this ); + if(mMediaSource == NULL) { - if (placeholder_image->getUseMipMaps()) + if(!initializePlugin(mMimeType)) { - // bad image! NO MIPMAPS! - initializePlaceholderImage(placeholder_image, media_source); + // Plugin failed initialization... should assert or something + return; } + } + + // updateMovieImage(mTextureId, true); - U8* data = media_source->getMediaData(); - S32 x_pos = 0; - S32 y_pos = 0; - S32 width = media_source->getMediaWidth(); - S32 height = media_source->getMediaHeight(); - S32 data_width = media_source->getMediaDataWidth(); - S32 data_height = media_source->getMediaDataHeight(); - placeholder_image->setSubImage(data, data_width, data_height, - x_pos, y_pos, width, height); + mMediaSource->loadURI( mMediaURL ); + if(/*mMediaSource->pluginSupportsMediaTime()*/ true) + { + start(); } } - -// virtual -void LLViewerMediaImpl::onMediaSizeChange(const EventType& event_in) +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::stop() { - LLMediaBase* media_source = event_in.getSubject(); - LLViewerMediaTexture* placeholder_image = LLViewerTextureManager::getMediaTexture( mMovieImageID ) ; - if (placeholder_image) + if(mMediaSource) { - initializePlaceholderImage(placeholder_image, media_source); + mMediaSource->stop(); + // destroyMediaSource(); } - else +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::pause() +{ + if(mMediaSource) { - llinfos << "no placeholder image" << llendl; + mMediaSource->pause(); } } -LLUUID LLViewerMediaImpl::getMediaTextureID() +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::start() { - return mMovieImageID; + if(mMediaSource) + { + mMediaSource->start(); + } } -// static -void LLViewerMediaImpl::updateBrowserUserAgent() +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::seek(F32 time) { - // Don't use user-visible string to avoid - // punctuation and strange characters. - std::string skin_name = gSavedSettings.getString("SkinCurrent"); - - // Just in case we need to check browser differences in A/B test - // builds. - std::string channel = gSavedSettings.getString("VersionChannelName"); - - // append our magic version number string to the browser user agent id - // See the HTTP 1.0 and 1.1 specifications for allowed formats: - // http://www.ietf.org/rfc/rfc1945.txt section 10.15 - // http://www.ietf.org/rfc/rfc2068.txt section 3.8 - // This was also helpful: - // http://www.mozilla.org/build/revised-user-agent-strings.html - std::ostringstream codec; - codec << "SecondLife/"; - codec << LL_VERSION_MAJOR << "." << LL_VERSION_MINOR << "." << LL_VERSION_PATCH << "." << LL_VERSION_BUILD; - codec << " (" << channel << "; " << skin_name << " skin)"; - llinfos << codec.str() << llendl; - LLMediaManager::setBrowserUserAgent( codec.str() ); + if(mMediaSource) + { + mMediaSource->seek(time); + } } -// static -bool LLViewerMediaImpl::handleSkinCurrentChanged(const LLSD& /*newvalue*/) +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::setVolume(F32 volume) { - // gSavedSettings is already updated when this function is called. - updateBrowserUserAgent(); - return true; + if(mMediaSource) + { + mMediaSource->setVolume(volume); + } } ////////////////////////////////////////////////////////////////////////////////////////// -// Wrapper class +void LLViewerMediaImpl::focus(bool focus) +{ + if (mMediaSource) + { + // call focus just for the hell of it, even though this apopears to be a nop + mMediaSource->focus(focus); + if (focus) + { + // spoof a mouse click to *actually* pass focus + // Don't do this anymore -- it actually clicks through now. +// mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_DOWN, 1, 1, 0); +// mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_UP, 1, 1, 0); + } + } +} + ////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::mouseDown(S32 x, S32 y) +{ + scaleMouse(&x, &y); + mLastMouseX = x; + mLastMouseY = y; + if (mMediaSource) + { + mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_DOWN, x, y, 0); + } +} -S32 LLViewerMedia::mMusicState = LLViewerMedia::STOPPED; ////////////////////////////////////////////////////////////////////////////////////////// -// The viewer takes a long time to load the start screen. Part of the problem -// is media initialization -- in particular, QuickTime loads many DLLs and -// hits the disk heavily. So we initialize only the browser component before -// the login screen, then do the rest later when we have a progress bar. JC -// static -void LLViewerMedia::initBrowser() +void LLViewerMediaImpl::mouseUp(S32 x, S32 y) { - LLMediaManagerData* init_data = new LLMediaManagerData; - buildMediaManagerData( init_data ); - LLMediaManager::initBrowser( init_data ); - delete init_data; + scaleMouse(&x, &y); + mLastMouseX = x; + mLastMouseY = y; + if (mMediaSource) + { + mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_UP, x, y, 0); + } +} - // We use a custom user agent with viewer version and skin name. - LLViewerMediaImpl::updateBrowserUserAgent(); +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::mouseMove(S32 x, S32 y) +{ + scaleMouse(&x, &y); + mLastMouseX = x; + mLastMouseY = y; + if (mMediaSource) + { + mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_MOVE, x, y, 0); + } } ////////////////////////////////////////////////////////////////////////////////////////// -// static -void LLViewerMedia::initClass() +void LLViewerMediaImpl::mouseLeftDoubleClick(S32 x, S32 y) { - // *TODO: This looks like a memory leak to me. JC - LLMediaManagerData* init_data = new LLMediaManagerData; - buildMediaManagerData( init_data ); - LLMediaManager::initClass( init_data ); - delete init_data; + scaleMouse(&x, &y); + mLastMouseX = x; + mLastMouseY = y; + if (mMediaSource) + { + mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_DOUBLE_CLICK, x, y, 0); + } +} - LLMediaManager* mm = LLMediaManager::getInstance(); - LLMIMETypes::mime_info_map_t::const_iterator it; - for (it = LLMIMETypes::sMap.begin(); it != LLMIMETypes::sMap.end(); ++it) +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::onMouseCaptureLost() +{ + if (mMediaSource) { - const std::string& mime_type = it->first; - const LLMIMETypes::LLMIMEInfo& info = it->second; - mm->addMimeTypeImplNameMap( mime_type, info.mImpl ); + mMediaSource->mouseEvent(LLPluginClassMedia::MOUSE_EVENT_UP, mLastMouseX, mLastMouseY, 0); } } ////////////////////////////////////////////////////////////////////////////////////////// -// static -void LLViewerMedia::buildMediaManagerData( LLMediaManagerData* init_data ) -{ -// std::string executable_dir = std::string( arg0 ).substr( 0, std::string( arg0 ).find_last_of("\\/") ); -// std::string component_dir = std::string( executable_dir ).substr( 0, std::string( executable_dir ).find_last_of("\\/") ); -// component_dir = std::string( component_dir ).substr( 0, std::string( component_dir ).find_last_of("\\/") ); -// component_dir = std::string( component_dir ).substr( 0, std::string( component_dir ).find_last_of("\\/") ); -// component_dir += "\\newview\\app_settings\\mozilla"; - - -#if LL_DARWIN - // For Mac OS, we store both the shared libraries and the runtime files (chrome/, plugins/, etc) in - // Second Life.app/Contents/MacOS/. This matches the way Firefox is distributed on the Mac. - std::string component_dir(gDirUtilp->getExecutableDir()); -#elif LL_WINDOWS - std::string component_dir( gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "" ) ); - component_dir += gDirUtilp->getDirDelimiter(); - #ifdef LL_DEBUG - component_dir += "mozilla_debug"; - #else // LL_DEBUG - component_dir += "mozilla"; - #endif // LL_DEBUG -#elif LL_LINUX - std::string component_dir( gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "" ) ); - component_dir += gDirUtilp->getDirDelimiter(); - component_dir += "mozilla-runtime-linux-i686"; -#elif LL_SOLARIS - std::string component_dir( gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "" ) ); - component_dir += gDirUtilp->getDirDelimiter(); - #ifdef __sparc - component_dir += "mozilla-solaris-sparc"; - #else - component_dir += "mozilla-solaris-i686"; - #endif -#else - std::string component_dir( gDirUtilp->getExpandedFilename( LL_PATH_APP_SETTINGS, "" ) ); - component_dir += gDirUtilp->getDirDelimiter(); - component_dir += "mozilla"; -#endif +BOOL LLViewerMediaImpl::handleMouseUp(S32 x, S32 y, MASK mask) +{ + // NOTE: this is called when the mouse is released when we have capture. + // Due to the way mouse coordinates are mapped to the object, we can't use the x and y coordinates that come in with the event. + + if(hasMouseCapture()) + { + // Release the mouse -- this will also send a mouseup to the media + gFocusMgr.setMouseCapture( FALSE ); + } + + return TRUE; +} +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::navigateHome() +{ + if(mMediaSource) + { + mMediaSource->loadURI( mHomeURL ); + } +} - std::string application_dir = gDirUtilp->getExecutableDir(); +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::navigateTo(const std::string& url, const std::string& mime_type, bool rediscover_type) +{ + if(rediscover_type) + { - init_data->setBrowserApplicationDir( application_dir ); - std::string profile_dir = gDirUtilp->getExpandedFilename( LL_PATH_MOZILLA_PROFILE, "" ); - init_data->setBrowserProfileDir( profile_dir ); - init_data->setBrowserComponentDir( component_dir ); - std::string profile_name("Second Life"); - init_data->setBrowserProfileName( profile_name ); - init_data->setBrowserParentWindow( gViewerWindow->getMediaWindow() ); + LLURI uri(url); + std::string scheme = uri.scheme(); - // Users can change skins while client is running, so make sure - // we pick up on changes. - gSavedSettings.getControl("SkinCurrent")->getSignal()->connect( - boost::bind( LLViewerMediaImpl::handleSkinCurrentChanged, _2 ) ); + if(scheme.empty() || "http" == scheme || "https" == scheme) + { + LLHTTPClient::getHeaderOnly( url, new LLMimeDiscoveryResponder(this)); + } + else if("data" == scheme || "file" == scheme || "about" == scheme) + { + // FIXME: figure out how to really discover the type for these schemes + // We use "data" internally for a text/html url for loading the login screen + if(initializeMedia("text/html")) + { + mMediaSource->loadURI( url ); + } + } + else + { + // This catches 'rtsp://' urls + if(initializeMedia(scheme)) + { + mMediaSource->loadURI( url ); + } + } + } + else if (mMediaSource) + { + mMediaSource->loadURI( url ); + } + else if(initializeMedia(mime_type) && mMediaSource) + { + mMediaSource->loadURI( url ); + } + else + { + LL_WARNS("Media") << "Couldn't navigate to: " << url << " as there is no media type for: " << mime_type << LL_ENDL; + return; + } + mMediaURL = url; } ////////////////////////////////////////////////////////////////////////////////////////// -// static -void LLViewerMedia::cleanupClass() +void LLViewerMediaImpl::navigateStop() { - stop() ; - LLMediaManager::cleanupClass(); -} + if(mMediaSource) + { + mMediaSource->browse_stop(); + } -// static -void LLViewerMedia::play(const std::string& media_url, - const std::string& mime_type, - const LLUUID& placeholder_texture_id, - S32 media_width, S32 media_height, U8 media_auto_scale, - U8 media_loop) -{ - sViewerMediaImpl.play(media_url, mime_type, placeholder_texture_id, - media_width, media_height, media_auto_scale, media_loop); } -// static -void LLViewerMedia::stop() +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::handleKeyHere(KEY key, MASK mask) { - sViewerMediaImpl.stop(); + bool result = false; + + if (mMediaSource) + { + result = mMediaSource->keyEvent(LLPluginClassMedia::KEY_EVENT_DOWN ,key, mask); + } + + return result; } -// static -void LLViewerMedia::pause() +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::handleUnicodeCharHere(llwchar uni_char) { - sViewerMediaImpl.pause(); + bool result = false; + + if (mMediaSource) + { + mMediaSource->textInput(wstring_to_utf8str(LLWString(1, uni_char))); + } + + return result; } -// static -void LLViewerMedia::start() +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::canNavigateForward() { - sViewerMediaImpl.start(); + BOOL result = FALSE; + if (mMediaSource) + { + result = mMediaSource->getHistoryForwardAvailable(); + } + return result; } -// static -void LLViewerMedia::seek(F32 time) +////////////////////////////////////////////////////////////////////////////////////////// +bool LLViewerMediaImpl::canNavigateBack() { - sViewerMediaImpl.seek(time); + BOOL result = FALSE; + if (mMediaSource) + { + result = mMediaSource->getHistoryBackAvailable(); + } + return result; } -// static -void LLViewerMedia::setVolume(F32 volume) + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::updateMovieImage(const LLUUID& uuid, BOOL active) { - sViewerMediaImpl.setVolume(volume); + // IF the media image hasn't changed, do nothing + if (mTextureId == uuid) + { + return; + } + // If we have changed media uuid, restore the old one + if (!mTextureId.isNull()) + { + LLViewerMediaTexture* old_image = LLViewerTextureManager::findMediaTexture( mTextureId ); + if (old_image) + { + old_image->setPlaying(FALSE); + LLViewerTexture* original_texture = old_image->getOldTexture(); + if(original_texture) + { + old_image->switchToTexture(original_texture); + } + } + } + // If the movie is playing, set the new media image + if (active && !uuid.isNull()) + { + LLViewerMediaTexture* viewerImage = LLViewerTextureManager::findMediaTexture( uuid ); + if( viewerImage ) + { + mTextureId = uuid; + + // Can't use mipmaps for movies because they don't update the full image + mMovieImageHasMips = viewerImage->getUseMipMaps(); + viewerImage->reinit(FALSE); + // FIXME +// viewerImage->mIsMediaTexture = TRUE; + } + } } -// static -LLMediaBase::EStatus LLViewerMedia::getStatus() +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::update() { - return sViewerMediaImpl.getStatus(); + if(mMediaSource == NULL) + { + return; + } + + mMediaSource->idle(); + + if(mMediaSource->isPluginExited()) + { + destroyMediaSource(); + return; + } + + if(!mMediaSource->textureValid()) + { + return; + } + + if(mSuspendUpdates || !mVisible) + { + return; + } + + LLViewerMediaTexture* placeholder_image = updatePlaceholderImage(); + + if(placeholder_image) + { + LLRect dirty_rect; + if(mMediaSource->getDirty(&dirty_rect)) + { + // Constrain the dirty rect to be inside the texture + S32 x_pos = llmax(dirty_rect.mLeft, 0); + S32 y_pos = llmax(dirty_rect.mBottom, 0); + S32 width = llmin(dirty_rect.mRight, placeholder_image->getWidth()) - x_pos; + S32 height = llmin(dirty_rect.mTop, placeholder_image->getHeight()) - y_pos; + + if(width > 0 && height > 0) + { + + U8* data = mMediaSource->getBitsData(); + + // Offset the pixels pointer to match x_pos and y_pos + data += ( x_pos * mMediaSource->getTextureDepth() * mMediaSource->getBitsWidth() ); + data += ( y_pos * mMediaSource->getTextureDepth() ); + + placeholder_image->setSubImage( + data, + mMediaSource->getBitsWidth(), + mMediaSource->getBitsHeight(), + x_pos, + y_pos, + width, + height); + + } + + mMediaSource->resetDirty(); + } + } } + ////////////////////////////////////////////////////////////////////////////////////////// -// static -LLUUID LLViewerMedia::getMediaTextureID() +void LLViewerMediaImpl::updateImagesMediaStreams() { - return sViewerMediaImpl.getMediaTextureID(); } + ////////////////////////////////////////////////////////////////////////////////////////// -// static -bool LLViewerMedia::getMediaSize(S32 *media_width, S32 *media_height) +LLViewerMediaTexture* LLViewerMediaImpl::updatePlaceholderImage() { - // make sure we're valid - - if ( sViewerMediaImpl.mMediaSource != NULL ) + if(mTextureId.isNull()) { - *media_width = sViewerMediaImpl.mMediaSource->getMediaWidth(); - *media_height = sViewerMediaImpl.mMediaSource->getMediaHeight(); - return true; + // The code that created this instance will read from the plugin's bits. + return NULL; } - return false; + + LLViewerMediaTexture* placeholder_image = LLViewerTextureManager::getMediaTexture( mTextureId ); + + if (mNeedsNewTexture + || placeholder_image->getUseMipMaps() +// || ! placeholder_image->getType() == LLViewerTexture::MEDIA_TEXTURE + || placeholder_image->getWidth() != mMediaSource->getTextureWidth() + || placeholder_image->getHeight() != mMediaSource->getTextureHeight()) + { + llinfos << "initializing media placeholder" << llendl; + llinfos << "movie image id " << mTextureId << llendl; + + int texture_width = mMediaSource->getTextureWidth(); + int texture_height = mMediaSource->getTextureHeight(); + int texture_depth = mMediaSource->getTextureDepth(); + + // MEDIAOPT: check to see if size actually changed before doing work + placeholder_image->destroyGLTexture(); + // MEDIAOPT: apparently just calling setUseMipMaps(FALSE) doesn't work? + placeholder_image->reinit(FALSE); // probably not needed + + // MEDIAOPT: seems insane that we actually have to make an imageraw then + // immediately discard it + LLPointer<LLImageRaw> raw = new LLImageRaw(texture_width, texture_height, texture_depth); + raw->clear(0x0f, 0x0f, 0x0f, 0xff); + int discard_level = 0; + + // ask media source for correct GL image format constants + placeholder_image->setExplicitFormat(mMediaSource->getTextureFormatInternal(), + mMediaSource->getTextureFormatPrimary(), + mMediaSource->getTextureFormatType(), + mMediaSource->getTextureFormatSwapBytes()); + + placeholder_image->createGLTexture(discard_level, raw); + + // placeholder_image->setExplicitFormat() + placeholder_image->setUseMipMaps(FALSE); + + // MEDIAOPT: set this dynamically on play/stop + // FIXME +// placeholder_image->mIsMediaTexture = true; + mNeedsNewTexture = false; + } + + return placeholder_image; } + ////////////////////////////////////////////////////////////////////////////////////////// -// static -bool LLViewerMedia::getTextureSize(S32 *texture_width, S32 *texture_height) +LLUUID LLViewerMediaImpl::getMediaTextureID() { - if ( sViewerMediaImpl.mMediaSource != NULL ) + return mTextureId; +} + +////////////////////////////////////////////////////////////////////////////////////////// +void LLViewerMediaImpl::setVisible(bool visible) +{ + mVisible = visible; + + if(mVisible) { - S32 media_width = sViewerMediaImpl.mMediaSource->getMediaWidth(); - S32 media_height = sViewerMediaImpl.mMediaSource->getMediaHeight(); - *texture_width = LLMediaManager::textureWidthFromMediaWidth( media_width ); - *texture_height = LLMediaManager::textureHeightFromMediaHeight( media_height ); - return true; + if(mMediaSource && mMediaSource->isPluginExited()) + { + destroyMediaSource(); + } + + if(!mMediaSource) + { + createMediaSource(); + } + } + + if(mMediaSource) + { + mMediaSource->setPriority(mVisible?LLPluginClassMedia::PRIORITY_NORMAL:LLPluginClassMedia::PRIORITY_HIDDEN); } - return false; } - ////////////////////////////////////////////////////////////////////////////////////////// -// static -void LLViewerMedia::updateImagesMediaStreams() +void LLViewerMediaImpl::mouseCapture() { - sViewerMediaImpl.updateImagesMediaStreams(); + gFocusMgr.setMouseCapture(this); } + ////////////////////////////////////////////////////////////////////////////////////////// -// static -bool LLViewerMedia::isMediaPlaying() +void LLViewerMediaImpl::scaleMouse(S32 *mouse_x, S32 *mouse_y) { - LLMediaBase::EStatus status = sViewerMediaImpl.getStatus(); - return (status == LLMediaBase::STATUS_STARTED ); +#if 0 + S32 media_width, media_height; + S32 texture_width, texture_height; + getMediaSize( &media_width, &media_height ); + getTextureSize( &texture_width, &texture_height ); + S32 y_delta = texture_height - media_height; + + *mouse_y -= y_delta; +#endif } + ////////////////////////////////////////////////////////////////////////////////////////// -// static -bool LLViewerMedia::isMediaPaused() +bool LLViewerMediaImpl::isMediaPlaying() { - LLMediaBase::EStatus status = sViewerMediaImpl.getStatus(); - return (status == LLMediaBase::STATUS_PAUSED); + bool result = false; + + if(mMediaSource) + { + EMediaStatus status = mMediaSource->getStatus(); + if(status == MEDIA_PLAYING || status == MEDIA_LOADING) + result = true; + } + + return result; } ////////////////////////////////////////////////////////////////////////////////////////// -// static -bool LLViewerMedia::hasMedia() +bool LLViewerMediaImpl::isMediaPaused() { - return sViewerMediaImpl.mMediaSource != NULL; + bool result = false; + + if(mMediaSource) + { + if(mMediaSource->getStatus() == MEDIA_PAUSED) + result = true; + } + + return result; } ////////////////////////////////////////////////////////////////////////////////////////// -//static -bool LLViewerMedia::isActiveMediaTexture(const LLUUID& id) +// +bool LLViewerMediaImpl::hasMedia() { - return (id.notNull() - && id == getMediaTextureID() - && isMediaPlaying()); + return mMediaSource != NULL; } ////////////////////////////////////////////////////////////////////////////////////////// -//static -bool LLViewerMedia::isMusicPlaying() +void LLViewerMediaImpl::handleMediaEvent(LLPluginClassMedia* self, LLPluginClassMediaOwner::EMediaEvent event) { - return mMusicState == PLAYING; + switch(event) + { + case MEDIA_EVENT_PLUGIN_FAILED: + { + LLSD args; + args["PLUGIN"] = LLMIMETypes::implType(mMimeType); + LLNotifications::instance().add("MediaPluginFailed", args); + } + break; + default: + break; + } + // Just chain the event to observers. + emitEvent(self, event); } -////////////////////////////////////////////////////////////////////////////////////////// -// static -std::string LLViewerMedia::getMediaURL() +//////////////////////////////////////////////////////////////////////////////// +// virtual +void +LLViewerMediaImpl::cut() { - return sViewerMediaImpl.mMediaURL; + if (mMediaSource) + mMediaSource->cut(); } -////////////////////////////////////////////////////////////////////////////////////////// -// static -std::string LLViewerMedia::getMimeType() + +//////////////////////////////////////////////////////////////////////////////// +// virtual +BOOL +LLViewerMediaImpl::canCut() const { - return sViewerMediaImpl.mMimeType; + if (mMediaSource) + return mMediaSource->canCut(); + else + return FALSE; } -////////////////////////////////////////////////////////////////////////////////////////// -// static -void LLViewerMedia::setMimeType(std::string mime_type) + +//////////////////////////////////////////////////////////////////////////////// +// virtual +void +LLViewerMediaImpl::copy() +{ + if (mMediaSource) + mMediaSource->copy(); +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +BOOL +LLViewerMediaImpl::canCopy() const { - sViewerMediaImpl.mMimeType = mime_type; + if (mMediaSource) + return mMediaSource->canCopy(); + else + return FALSE; +} + +//////////////////////////////////////////////////////////////////////////////// +// virtual +void +LLViewerMediaImpl::paste() +{ + if (mMediaSource) + mMediaSource->paste(); } +//////////////////////////////////////////////////////////////////////////////// +// virtual +BOOL +LLViewerMediaImpl::canPaste() const +{ + if (mMediaSource) + return mMediaSource->canPaste(); + else + return FALSE; +} + + ////////////////////////////////////////////////////////////////////////////////////////// //static void LLViewerMedia::toggleMusicPlay(void*) { +// FIXME: This probably doesn't belong here +#if 0 if (mMusicState != PLAYING) { mMusicState = PLAYING; // desired state @@ -656,12 +1165,15 @@ void LLViewerMedia::toggleMusicPlay(void*) gAudiop->stopInternetStream(); } } +#endif } ////////////////////////////////////////////////////////////////////////////////////////// //static void LLViewerMedia::toggleMediaPlay(void*) { +// FIXME: This probably doesn't belong here +#if 0 if (LLViewerMedia::isMediaPaused()) { LLViewerParcelMedia::start(); @@ -678,11 +1190,24 @@ void LLViewerMedia::toggleMediaPlay(void*) LLViewerParcelMedia::play(parcel); } } +#endif } ////////////////////////////////////////////////////////////////////////////////////////// //static void LLViewerMedia::mediaStop(void*) { +// FIXME: This probably doesn't belong here +#if 0 LLViewerParcelMedia::stop(); +#endif +} + +////////////////////////////////////////////////////////////////////////////////////////// +//static +bool LLViewerMedia::isMusicPlaying() +{ +// FIXME: This probably doesn't belong here +// FIXME: make this work + return false; } diff --git a/indra/newview/llviewermedia.h b/indra/newview/llviewermedia.h index b3fb2c9031..68a49662e7 100644 --- a/indra/newview/llviewermedia.h +++ b/indra/newview/llviewermedia.h @@ -33,58 +33,196 @@ #ifndef LLVIEWERMEDIA_H #define LLVIEWERMEDIA_H -#include "llmediabase.h" // for status codes +#include "llfocusmgr.h" +#include "lleditmenuhandler.h" -class LLMediaManagerData; +#include "llpanel.h" +#include "llpluginclassmediaowner.h" + +#include "llviewermediaobserver.h" + +class LLViewerMediaImpl; class LLUUID; +class LLViewerMediaTexture; + +typedef LLPointer<LLViewerMediaImpl> viewer_media_t; +/////////////////////////////////////////////////////////////////////////////// +// +class LLViewerMediaEventEmitter +{ +public: + virtual ~LLViewerMediaEventEmitter(); + + bool addObserver( LLViewerMediaObserver* subject ); + bool remObserver( LLViewerMediaObserver* subject ); + void emitEvent(LLPluginClassMedia* self, LLPluginClassMediaOwner::EMediaEvent event); + +private: + typedef std::list< LLViewerMediaObserver* > observerListType; + observerListType mObservers; +}; class LLViewerMedia { + LOG_CLASS(LLViewerMedia); public: // Special case early init for just web browser component // so we can show login screen. See .cpp file for details. JC - static void initBrowser(); - static void initClass(); - static void cleanupClass(); + static viewer_media_t newMediaImpl(const std::string& media_url, + const LLUUID& texture_id, + S32 media_width, + S32 media_height, + U8 media_auto_scale, + U8 media_loop, + std::string mime_type = "none/none"); - static void play(const std::string& media_url, - const std::string& mime_type, - const LLUUID& placeholder_texture_id, - S32 media_width, S32 media_height, U8 media_auto_scale, - U8 media_loop); - static void stop(); - static void pause(); - static void start(); - static void seek(F32 time); + static void removeMedia(LLViewerMediaImpl* media); + static LLViewerMediaImpl* getMediaImplFromTextureID(const LLUUID& texture_id); + static std::string getCurrentUserAgent(); + static void updateBrowserUserAgent(); + static bool handleSkinCurrentChanged(const LLSD& /*newvalue*/); + static bool textureHasMedia(const LLUUID& texture_id); static void setVolume(F32 volume); - static LLMediaBase::EStatus getStatus(); - - static LLUUID getMediaTextureID(); - static bool getMediaSize(S32 *media_width, S32 *media_height); - static bool getTextureSize(S32 *texture_width, S32 *texture_height); - static bool isMediaPlaying(); - static bool isMediaPaused(); - static bool hasMedia(); - static bool isActiveMediaTexture(const LLUUID& id); - static bool isMusicPlaying(); - static std::string getMediaURL(); - static std::string getMimeType(); - static void setMimeType(std::string mime_type); + static void updateMedia(); + static bool isMusicPlaying(); - static void updateImagesMediaStreams(); + static void cleanupClass(); static void toggleMusicPlay(void*); static void toggleMediaPlay(void*); static void mediaStop(void*); +}; + +// Implementation functions not exported into header file +class LLViewerMediaImpl + : public LLMouseHandler, public LLRefCount, public LLPluginClassMediaOwner, public LLViewerMediaEventEmitter, public LLEditMenuHandler +{ + LOG_CLASS(LLViewerMediaImpl); +public: + + LLViewerMediaImpl(const std::string& media_url, + const LLUUID& texture_id, + S32 media_width, + S32 media_height, + U8 media_auto_scale, + U8 media_loop, + const std::string& mime_type); + + ~LLViewerMediaImpl(); + void createMediaSource(); + void destroyMediaSource(); + void setMediaType(const std::string& media_type); + bool initializeMedia(const std::string& mime_type); + bool initializePlugin(const std::string& media_type); + LLPluginClassMedia* getMediaPlugin() { return mMediaSource; } + void setSize(int width, int height); + + void play(); + void stop(); + void pause(); + void start(); + void seek(F32 time); + void setVolume(F32 volume); + void focus(bool focus); + void mouseDown(S32 x, S32 y); + void mouseUp(S32 x, S32 y); + void mouseMove(S32 x, S32 y); + void mouseLeftDoubleClick(S32 x,S32 y ); + void mouseCapture(); + + void navigateHome(); + void navigateTo(const std::string& url, const std::string& mime_type = "", bool rediscover_type = false); + void navigateStop(); + bool handleKeyHere(KEY key, MASK mask); + bool handleUnicodeCharHere(llwchar uni_char); + bool canNavigateForward(); + bool canNavigateBack(); + std::string getMediaURL() { return mMediaURL; } + std::string getMediaHomeURL() { return mHomeURL; } + std::string getMimeType() { return mMimeType; } + void scaleMouse(S32 *mouse_x, S32 *mouse_y); + + void update(); + void updateMovieImage(const LLUUID& image_id, BOOL active); + void updateImagesMediaStreams(); + LLUUID getMediaTextureID(); + + void suspendUpdates(bool suspend) { mSuspendUpdates = suspend; }; + void setVisible(bool visible); + + bool isMediaPlaying(); + bool isMediaPaused(); + bool hasMedia(); + + // utility function to create a ready-to-use media instance from a desired media type. + static LLPluginClassMedia* newSourceFromMediaType(std::string media_type, LLPluginClassMediaOwner *owner /* may be NULL */, S32 default_width, S32 default_height); + + // Internally set our desired browser user agent string, including + // the Second Life version and skin name. Used because we can + // switch skins without restarting the app. + static void updateBrowserUserAgent(); + + // Callback for when the SkinCurrent control is changed to + // switch the user agent string to indicate the new skin. + static bool handleSkinCurrentChanged(const LLSD& newvalue); + + // need these to handle mouseup... + /*virtual*/ void onMouseCaptureLost(); + /*virtual*/ BOOL handleMouseUp(S32 x, S32 y, MASK mask); + + // Grr... the only thing I want as an LLMouseHandler are the onMouseCaptureLost and handleMouseUp calls. + // Sadly, these are all pure virtual, so I have to supply implementations here: + /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask) { return FALSE; }; + /*virtual*/ BOOL handleHover(S32 x, S32 y, MASK mask) { return FALSE; }; + /*virtual*/ BOOL handleScrollWheel(S32 x, S32 y, S32 clicks) { return FALSE; }; + /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask) { return FALSE; }; + /*virtual*/ BOOL handleRightMouseDown(S32 x, S32 y, MASK mask) { return FALSE; }; + /*virtual*/ BOOL handleRightMouseUp(S32 x, S32 y, MASK mask) { return FALSE; }; + /*virtual*/ BOOL handleToolTip(S32 x, S32 y, std::string& msg, LLRect* sticky_rect_screen) { return FALSE; }; + /*virtual*/ BOOL handleMiddleMouseDown(S32 x, S32 y, MASK mask) { return FALSE; }; + /*virtual*/ BOOL handleMiddleMouseUp(S32 x, S32 y, MASK mask) {return FALSE; }; + /*virtual*/ const std::string& getName() const { return LLStringUtil::null; }; + /*virtual*/ BOOL isView() const { return FALSE; }; + /*virtual*/ void screenPointToLocal(S32 screen_x, S32 screen_y, S32* local_x, S32* local_y) const {}; + /*virtual*/ void localPointToScreen(S32 local_x, S32 local_y, S32* screen_x, S32* screen_y) const {}; + /*virtual*/ BOOL hasMouseCapture() { return gFocusMgr.getMouseCapture() == this; }; + + // Inherited from LLPluginClassMediaOwner + /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, LLPluginClassMediaOwner::EMediaEvent); + + // LLEditMenuHandler overrides + /*virtual*/ void cut(); + /*virtual*/ BOOL canCut() const; + + /*virtual*/ void copy(); + /*virtual*/ BOOL canCopy() const; + + /*virtual*/ void paste(); + /*virtual*/ BOOL canPaste() const; + +public: + // a single media url with some data and an impl. + LLPluginClassMedia* mMediaSource; + LLUUID mTextureId; + bool mMovieImageHasMips; + std::string mMediaURL; + std::string mHomeURL; + std::string mMimeType; + S32 mLastMouseX; // save the last mouse coord we get, so when we lose capture we can simulate a mouseup at that point. + S32 mLastMouseY; + S32 mMediaWidth; + S32 mMediaHeight; + bool mMediaAutoScale; + bool mMediaLoop; + bool mNeedsNewTexture; + bool mSuspendUpdates; + bool mVisible; + - private: - // Fill in initialization data for LLMediaManager::initClass() - static void buildMediaManagerData( LLMediaManagerData* init_data ); - - enum { STOPPED=0, PLAYING=1, PAUSED=2 }; - static S32 mMusicState; +private: + LLViewerMediaTexture *updatePlaceholderImage(); }; #endif // LLVIEWERMEDIA_H diff --git a/indra/newview/llviewermedia_streamingaudio.cpp b/indra/newview/llviewermedia_streamingaudio.cpp new file mode 100644 index 0000000000..90cfb85821 --- /dev/null +++ b/indra/newview/llviewermedia_streamingaudio.cpp @@ -0,0 +1,167 @@ +/** + * @file llviewermedia_streamingaudio.h + * @author Tofu Linden, Sam Kolb + * @brief LLStreamingAudio_MediaPlugins implementation - an implementation of the streaming audio interface which is implemented as a client of the media plugins API. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ +#include "llviewerprecompiledheaders.h" +#include "linden_common.h" +#include "llpluginclassmedia.h" +#include "llviewermedia.h" + +#include "llviewermedia_streamingaudio.h" + +#include "llmimetypes.h" +#include "llvfs.h" +#include "lldir.h" + + +LLStreamingAudio_MediaPlugins::LLStreamingAudio_MediaPlugins() : + mMediaPlugin(NULL), + mGain(1.0) +{ + // nothing interesting to do? + // we will lazily create a media plugin at play-time, if none exists. +} + +LLStreamingAudio_MediaPlugins::~LLStreamingAudio_MediaPlugins() +{ + delete mMediaPlugin; + mMediaPlugin = NULL; +} + +void LLStreamingAudio_MediaPlugins::start(const std::string& url) +{ + if (!mMediaPlugin) // lazy-init the underlying media plugin + { + mMediaPlugin = initializeMedia("audio/mpeg"); // assumes that whatever media implementation supports mp3 also supports vorbis. + llinfos << "mMediaPlugin is now " << mMediaPlugin << llendl; + } + + if(!mMediaPlugin) + return; + + if (!url.empty()) { + llinfos << "Starting internet stream: " << url << llendl; + mURL = url; + mMediaPlugin->loadURI ( url ); + mMediaPlugin->start(); + llinfos << "Playing....." << llendl; + } else { + llinfos << "setting stream to NULL"<< llendl; + mURL.clear(); + mMediaPlugin->stop(); + } +} + +void LLStreamingAudio_MediaPlugins::stop() +{ + if(mMediaPlugin) + { + mMediaPlugin->stop(); + } + + mURL.clear(); +} + +void LLStreamingAudio_MediaPlugins::pause(int pause) +{ + if(!mMediaPlugin) + return; + + if(pause) + { + mMediaPlugin->pause(); + } + else + { + mMediaPlugin->start(); + } +} + +void LLStreamingAudio_MediaPlugins::update() +{ + if (mMediaPlugin) + mMediaPlugin->idle(); +} + +int LLStreamingAudio_MediaPlugins::isPlaying() +{ + if (!mMediaPlugin) + return 0; + + // *TODO: can probably do better than this + if (mMediaPlugin->isPluginRunning()) + { + return 1; // Active and playing + } + + if (mMediaPlugin->isPluginExited()) + { + return 0; // stopped + } + + return 2; // paused +} + +void LLStreamingAudio_MediaPlugins::setGain(F32 vol) +{ + mGain = vol; + + if(!mMediaPlugin) + return; + + vol = llclamp(vol, 0.f, 1.f); + mMediaPlugin->setVolume(vol); +} + +F32 LLStreamingAudio_MediaPlugins::getGain() +{ + return mGain; +} + +std::string LLStreamingAudio_MediaPlugins::getURL() +{ + return mURL; +} + +LLPluginClassMedia* LLStreamingAudio_MediaPlugins::initializeMedia(const std::string& media_type) +{ + LLPluginClassMediaOwner* owner = NULL; + S32 default_size = 1; // audio-only - be minimal, doesn't matter + LLPluginClassMedia* media_source = LLViewerMediaImpl::newSourceFromMediaType(media_type, owner, default_size, default_size); + + if (media_source) + { + media_source->setLoop(false); // audio streams are not expected to loop + } + + return media_source; +} + diff --git a/indra/newview/llviewermedia_streamingaudio.h b/indra/newview/llviewermedia_streamingaudio.h new file mode 100644 index 0000000000..270bab7625 --- /dev/null +++ b/indra/newview/llviewermedia_streamingaudio.h @@ -0,0 +1,69 @@ +/** + * @file llviewermedia_streamingaudio.h + * @author Tofu Linden + * @brief Definition of LLStreamingAudio_MediaPlugins implementation - an implementation of the streaming audio interface which is implemented as a client of the media plugins API. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_VIEWERMEDIA_STREAMINGAUDIO_H +#define LL_VIEWERMEDIA_STREAMINGAUDIO_H + + +#include "stdtypes.h" // from llcommon + +#include "llstreamingaudio.h" + +class LLPluginClassMedia; + +class LLStreamingAudio_MediaPlugins : public LLStreamingAudioInterface +{ + public: + LLStreamingAudio_MediaPlugins(); + /*virtual*/ ~LLStreamingAudio_MediaPlugins(); + + /*virtual*/ void start(const std::string& url); + /*virtual*/ void stop(); + /*virtual*/ void pause(int pause); + /*virtual*/ void update(); + /*virtual*/ int isPlaying(); + /*virtual*/ void setGain(F32 vol); + /*virtual*/ F32 getGain(); + /*virtual*/ std::string getURL(); + +private: + LLPluginClassMedia* initializeMedia(const std::string& media_type); + + LLPluginClassMedia *mMediaPlugin; + + std::string mURL; + F32 mGain; +}; + + +#endif //LL_VIEWERMEDIA_STREAMINGAUDIO_H diff --git a/indra/newview/llviewermediafocus.cpp b/indra/newview/llviewermediafocus.cpp new file mode 100644 index 0000000000..60eabd730f --- /dev/null +++ b/indra/newview/llviewermediafocus.cpp @@ -0,0 +1,355 @@ +/** + * @file llviewermediafocus.cpp + * @brief Governs focus on Media prims + * + * $LicenseInfo:firstyear=2003&license=viewergpl$ + * + * Copyright (c) 2003-2007, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" + +#include "llviewermediafocus.h" + +//LLViewerMediaFocus +#include "llviewerobjectlist.h" +#include "llpanelmediahud.h" +#include "llpluginclassmedia.h" +#include "llagent.h" +#include "lltoolpie.h" +#include "llviewercamera.h" +#include "llviewermedia.h" +#include "llhudview.h" +#include "lluictrlfactory.h" +#include "lldrawable.h" +#include "llparcel.h" +#include "llviewerparcelmgr.h" +#include "llweb.h" +// +// LLViewerMediaFocus +// + +LLViewerMediaFocus::LLViewerMediaFocus() +: mMouseOverFlag(false) +{ +} + +LLViewerMediaFocus::~LLViewerMediaFocus() +{ + // The destructor for LLSingletons happens at atexit() time, which is too late to do much. + // Clean up in cleanupClass() instead. +} + +void LLViewerMediaFocus::cleanupClass() +{ + LLViewerMediaFocus *self = LLViewerMediaFocus::getInstance(); + + if(self) + { + // mMediaHUD will have been deleted by this point -- don't try to delete it. + + /* Richard says: + all widgets are supposed to be destroyed at the same time + you shouldn't hold on to pointer to them outside of ui code + you can use the LLHandle approach + if you want to be type safe, you'll need to add a LLRootHandle to whatever derived class you are pointing to + look at llview::gethandle + its our version of a weak pointer + */ + if(self->mMediaHUD.get()) + { + self->mMediaHUD.get()->setMediaImpl(NULL); + } + self->mMediaImpl = NULL; + } + +} + + +void LLViewerMediaFocus::setFocusFace( BOOL b, LLPointer<LLViewerObject> objectp, S32 face, viewer_media_t media_impl ) +{ + LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + if (b && media_impl.notNull()) + { + mMediaImpl = media_impl; + LLSelectMgr::getInstance()->deselectAll(); + LLSelectMgr::getInstance()->selectObjectOnly(objectp, face); + + mFocus = LLSelectMgr::getInstance()->getSelection(); + if(mMediaHUD.get() && ! parcel->getMediaPreventCameraZoom()) + { + mMediaHUD.get()->resetZoomLevel(); + mMediaHUD.get()->nextZoomLevel(); + } + if (!mFocus->isEmpty()) + { + gFocusMgr.setKeyboardFocus(this); + } + mObjectID = objectp->getID(); + // LLViewerMedia::addObserver(this, mObjectID); + + + } + else + { + gFocusMgr.setKeyboardFocus(NULL); + mFocus = NULL; + if(! parcel->getMediaPreventCameraZoom()) + { + gAgent.setFocusOnAvatar(TRUE, ANIMATE); + } + // LLViewerMedia::remObserver(this, mObjectID); + + // Null out the media hud media pointer + if(mMediaHUD.get()) + { + mMediaHUD.get()->setMediaImpl(NULL); + } + + // and null out the media impl + mMediaImpl = NULL; + } + if(mMediaHUD.get()) + { + mMediaHUD.get()->setMediaFocus(b); + } +} +bool LLViewerMediaFocus::getFocus() +{ + if (gFocusMgr.getKeyboardFocus() == this) + { + return true; + } + return false; +} + +// This function selects an ideal viewing distance given a selection bounding box, normal, and padding value +void LLViewerMediaFocus::setCameraZoom(F32 padding_factor) +{ + LLPickInfo& pick = LLToolPie::getInstance()->getPick(); + + if(LLSelectMgr::getInstance()->getSelection()->isEmpty()) + { + pick = mPickInfo; + setFocusFace(true, pick.getObject(), pick.mObjectFace, mMediaImpl); + } + + if (!LLSelectMgr::getInstance()->getSelection()->isEmpty()) + { + gAgent.setFocusOnAvatar(FALSE, ANIMATE); + + LLBBox selection_bbox = LLSelectMgr::getInstance()->getBBoxOfSelection(); + F32 height; + F32 width; + F32 depth; + F32 angle_of_view; + F32 distance; + + // We need the aspect ratio, and the 3 components of the bbox as height, width, and depth. + F32 aspect_ratio = getBBoxAspectRatio(selection_bbox, pick.mNormal, &height, &width, &depth); + F32 camera_aspect = LLViewerCamera::getInstance()->getAspect(); + + // We will normally use the side of the volume aligned with the short side of the screen (i.e. the height for + // a screen in a landscape aspect ratio), however there is an edge case where the aspect ratio of the object is + // more extreme than the screen. In this case we invert the logic, using the longer component of both the object + // and the screen. + bool invert = (camera_aspect > 1.0f && aspect_ratio > camera_aspect) || + (camera_aspect < 1.0f && aspect_ratio < camera_aspect); + + // To calculate the optimum viewing distance we will need the angle of the shorter side of the view rectangle. + // In portrait mode this is the width, and in landscape it is the height. + // We then calculate the distance based on the corresponding side of the object bbox (width for portrait, height for landscape) + // We will add half the depth of the bounding box, as the distance projection uses the center point of the bbox. + if(camera_aspect < 1.0f || invert) + { + angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getView() * LLViewerCamera::getInstance()->getAspect()); + distance = width * 0.5 * padding_factor / tan(angle_of_view * 0.5f ); + } + else + { + angle_of_view = llmax(0.1f, LLViewerCamera::getInstance()->getView()); + distance = height * 0.5 * padding_factor / tan(angle_of_view * 0.5f ); + } + + distance += depth * 0.5; + + // Finally animate the camera to this new position and focal point + gAgent.setCameraPosAndFocusGlobal(LLSelectMgr::getInstance()->getSelectionCenterGlobal() + LLVector3d(pick.mNormal * distance), + LLSelectMgr::getInstance()->getSelectionCenterGlobal(), LLSelectMgr::getInstance()->getSelection()->getFirstObject()->mID ); + } +} +void LLViewerMediaFocus::onFocusReceived() +{ + if(mMediaImpl.notNull()) + mMediaImpl->focus(true); + + LLFocusableElement::onFocusReceived(); +} + +void LLViewerMediaFocus::onFocusLost() +{ + if(mMediaImpl.notNull()) + mMediaImpl->focus(false); + gViewerWindow->focusClient(); + mFocus = NULL; + LLFocusableElement::onFocusLost(); +} +void LLViewerMediaFocus::setMouseOverFlag(bool b, viewer_media_t media_impl) +{ + if (b && media_impl.notNull()) + { + if(! mMediaHUD.get()) + { + LLPanelMediaHUD* media_hud = new LLPanelMediaHUD(mMediaImpl); + mMediaHUD = media_hud->getHandle(); + gHUDView->addChild(media_hud); + } + mMediaHUD.get()->setMediaImpl(media_impl); + mMediaImpl = media_impl; + } + mMouseOverFlag = b; +} +LLUUID LLViewerMediaFocus::getSelectedUUID() +{ + LLViewerObject* object = mFocus->getFirstObject(); + return object ? object->getID() : LLUUID::null; +} +#if 0 // Must re-implement when the new media api event system is ready +void LLViewerMediaFocus::onNavigateComplete( const EventType& event_in ) +{ + if (hasFocus() && mLastURL != event_in.getStringValue()) + { + LLViewerMedia::focus(true, mObjectID); + // spoof mouse event to reassert focus + LLViewerMedia::mouseDown(1,1, mObjectID); + LLViewerMedia::mouseUp(1,1, mObjectID); + } + mLastURL = event_in.getStringValue(); +} +#endif +BOOL LLViewerMediaFocus::handleKey(KEY key, MASK mask, BOOL called_from_parent) +{ + if(mMediaImpl.notNull()) + mMediaImpl->handleKeyHere(key, mask); + return true; +} + +BOOL LLViewerMediaFocus::handleUnicodeChar(llwchar uni_char, BOOL called_from_parent) +{ + if(mMediaImpl.notNull()) + mMediaImpl->handleUnicodeCharHere(uni_char); + return true; +} +BOOL LLViewerMediaFocus::handleScrollWheel(S32 x, S32 y, S32 clicks) +{ + BOOL retval = FALSE; + if(mFocus.notNull() && mMediaImpl.notNull() && mMediaImpl->hasMedia()) + { + mMediaImpl->getMediaPlugin()->scrollEvent(x, y, clicks); + retval = TRUE; + } + return retval; +} + +void LLViewerMediaFocus::update() +{ + if (mMediaHUD.get()) + { + if(mFocus.notNull() || mMouseOverFlag || mMediaHUD.get()->isMouseOver()) + { + // mMediaHUD.get()->setVisible(true); + mMediaHUD.get()->updateShape(); + } + else + { + mMediaHUD.get()->setVisible(false); + } + } +} +// This function calculates the aspect ratio and the world aligned components of a selection bounding box. +F32 LLViewerMediaFocus::getBBoxAspectRatio(const LLBBox& bbox, const LLVector3& normal, F32* height, F32* width, F32* depth) +{ + // Convert the selection normal and an up vector to local coordinate space of the bbox + LLVector3 local_normal = bbox.agentToLocalBasis(normal); + LLVector3 z_vec = bbox.agentToLocalBasis(LLVector3(0.0f, 0.0f, 1.0f)); + + LLVector3 comp1(0.f,0.f,0.f); + LLVector3 comp2(0.f,0.f,0.f); + LLVector3 bbox_max = bbox.getExtentLocal(); + F32 dot1 = 0.f; + F32 dot2 = 0.f; + + // The largest component of the localized normal vector is the depth component + // meaning that the other two are the legs of the rectangle. + local_normal.abs(); + if(local_normal.mV[VX] > local_normal.mV[VY]) + { + if(local_normal.mV[VX] > local_normal.mV[VZ]) + { + // Use the y and z comps + comp1.mV[VY] = bbox_max.mV[VY]; + comp2.mV[VZ] = bbox_max.mV[VZ]; + *depth = bbox_max.mV[VX]; + } + else + { + // Use the x and y comps + comp1.mV[VY] = bbox_max.mV[VY]; + comp2.mV[VZ] = bbox_max.mV[VZ]; + *depth = bbox_max.mV[VZ]; + } + } + else if(local_normal.mV[VY] > local_normal.mV[VZ]) + { + // Use the x and z comps + comp1.mV[VX] = bbox_max.mV[VX]; + comp2.mV[VZ] = bbox_max.mV[VZ]; + *depth = bbox_max.mV[VY]; + } + else + { + // Use the x and y comps + comp1.mV[VY] = bbox_max.mV[VY]; + comp2.mV[VZ] = bbox_max.mV[VZ]; + *depth = bbox_max.mV[VX]; + } + + // The height is the vector closest to vertical in the bbox coordinate space (highest dot product value) + dot1 = comp1 * z_vec; + dot2 = comp2 * z_vec; + if(fabs(dot1) > fabs(dot2)) + { + *height = comp1.length(); + *width = comp2.length(); + } + else + { + *height = comp2.length(); + *width = comp1.length(); + } + + // Return the aspect ratio. + return *width / *height; +} diff --git a/indra/newview/llviewermediafocus.h b/indra/newview/llviewermediafocus.h new file mode 100644 index 0000000000..a078d24b6a --- /dev/null +++ b/indra/newview/llviewermediafocus.h @@ -0,0 +1,90 @@ +/** + * @file llpanelmsgs.h + * @brief Message popup preferences panel + * + * $LicenseInfo:firstyear=2003&license=viewergpl$ + * + * Copyright (c) 2003-2007, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_VIEWERMEDIAFOCUS_H +#define LL_VIEWERMEDIAFOCUS_H + +// includes for LLViewerMediaFocus +#include "llfocusmgr.h" +#include "llviewermedia.h" +#include "llviewerobject.h" +#include "llviewerwindow.h" +#include "llselectmgr.h" + +class LLViewerMediaImpl; +class LLPanelMediaHUD; + +class LLViewerMediaFocus : + public LLFocusableElement, + public LLSingleton<LLViewerMediaFocus> +{ +public: + LLViewerMediaFocus(); + ~LLViewerMediaFocus(); + + static void cleanupClass(); + + void setFocusFace(BOOL b, LLPointer<LLViewerObject> objectp, S32 face, viewer_media_t media_impl); + void clearFocus() { setFocusFace(false, NULL, 0, NULL); } + /*virtual*/ bool getFocus(); + /*virtual*/ // void onNavigateComplete( const EventType& event_in ); + + /*virtual*/ BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); + /*virtual*/ BOOL handleUnicodeChar(llwchar uni_char, BOOL called_from_parent); + BOOL handleScrollWheel(S32 x, S32 y, S32 clicks); + + LLUUID getSelectedUUID(); + LLObjectSelectionHandle getSelection() { return mFocus; } + + void update(); + + void setCameraZoom(F32 padding_factor); + void setMouseOverFlag(bool b, viewer_media_t media_impl = NULL); + bool getMouseOverFlag() { return mMouseOverFlag; } + void setPickInfo(LLPickInfo pick_info) { mPickInfo = pick_info; } + F32 getBBoxAspectRatio(const LLBBox& bbox, const LLVector3& normal, F32* height, F32* width, F32* depth); + +protected: + /*virtual*/ void onFocusReceived(); + /*virtual*/ void onFocusLost(); + +private: + LLObjectSelectionHandle mFocus; + std::string mLastURL; + bool mMouseOverFlag; + LLPickInfo mPickInfo; + LLHandle<LLPanelMediaHUD> mMediaHUD; + LLUUID mObjectID; + viewer_media_t mMediaImpl; +}; + + +#endif // LL_VIEWERMEDIAFOCUS_H diff --git a/indra/newview/llviewermediaobserver.h b/indra/newview/llviewermediaobserver.h new file mode 100644 index 0000000000..6667f982b6 --- /dev/null +++ b/indra/newview/llviewermediaobserver.h @@ -0,0 +1,71 @@ +/** + * @file llviewermediaobserver.h + * @brief Methods to override to catch events from LLViewerMedia class + * + * $LicenseInfo:firstyear=2007&license=viewergpl$ + * + * Copyright (c) 2007-2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LLVIEWERMEDIAOBSERVER_H +#define LLVIEWERMEDIAOBSERVER_H + +#include "llpluginclassmediaowner.h" + +class LLViewerMediaEventEmitter; + +class LLViewerMediaObserver : public LLPluginClassMediaOwner +{ +public: + virtual ~LLViewerMediaObserver(); + +private: + // Emitters will manage this list in addObserver/remObserver. + friend class LLViewerMediaEventEmitter; + std::list<LLViewerMediaEventEmitter *> mEmitters; +}; + + +#if 0 + // Classes that inherit from LLViewerMediaObserver should add this to their class declaration: + + // inherited from LLViewerMediaObserver + /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); + + /* and will probably need to add this to their cpp file: + + #include "llpluginclassmedia.h" + + */ + + // The list of events is in llpluginclassmediaowner.h + + +#endif + + +#endif // LLVIEWERMEDIAOBSERVER_H + diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 207b9592b8..8220fff6b5 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -40,7 +40,7 @@ #include <sstream> // linden library includes -#include "audioengine.h" +#include "llaudioengine.h" #include "llfloaterreg.h" #include "indra_constants.h" #include "llassetstorage.h" @@ -104,9 +104,8 @@ #include "llfloatergodtools.h" #include "llfloatergroupinvite.h" #include "llfloatergroups.h" -#include "llfloaterhtml.h" #include "llfloaterhtmlcurrency.h" -#include "llfloaterhtmlhelp.h" // gViewerHtmlHelp +#include "llfloatermediabrowser.h" // gViewerHtmlHelp #include "llfloaterhtmlsimple.h" #include "llfloaterhud.h" #include "llfloaterinspect.h" @@ -150,6 +149,7 @@ #include "llpanellogin.h" #include "llmenucommands.h" #include "llmenugl.h" +#include "llmimetypes.h" #include "llmorphview.h" #include "llmoveview.h" #include "llmutelist.h" @@ -5581,6 +5581,7 @@ class LLShowFloater : public view_listener_t } else if (floater_name == "help f1") { + llinfos << "Spawning HTML help window" << llendl; gViewerHtmlHelp.show(); } else if (floater_name == "complaint reporter") @@ -6885,7 +6886,7 @@ void handle_grab_texture(void* data) // user know that the image is now in inventory. if(view) { - LLUICtrl* focus_ctrl = gFocusMgr.getKeyboardFocus(); + LLFocusableElement* focus_ctrl = gFocusMgr.getKeyboardFocus(); view->getPanel()->setSelection(item_id, TAKE_FOCUS_NO); view->getPanel()->openSelected(); @@ -7084,13 +7085,7 @@ void handle_load_from_xml(void*) void handle_web_browser_test(void*) { - const bool open_links_externally = false; - const bool open_app_slurls = true; - LLFloaterHtml::getInstance()->show( - "http://secondlife.com/app/search/slurls.html", - "Web Browser Test", - open_links_externally, - open_app_slurls); + LLWeb::loadURL("http://secondlife.com/app/search/slurls.html"); } void handle_buy_currency_test(void*) diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp index 02e9528f7d..1cfeec5627 100644 --- a/indra/newview/llviewermenufile.cpp +++ b/indra/newview/llviewermenufile.cpp @@ -65,7 +65,7 @@ #include "llstring.h" #include "lltransactiontypes.h" #include "lluuid.h" -#include "vorbisencode.h" +#include "llvorbisencode.h" // system libraries #include <boost/tokenizer.hpp> diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 7303605915..4503228cf2 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -37,7 +37,7 @@ #include <deque> -#include "audioengine.h" +#include "llaudioengine.h" #include "indra_constants.h" #include "lscript_byteformat.h" #include "mean_collision_data.h" @@ -971,7 +971,7 @@ void open_offer(const std::vector<LLUUID>& items, const std::string& from_name) if (view->getPanel()) { - LLUICtrl* focus_ctrl = gFocusMgr.getKeyboardFocus(); + LLFocusableElement* focus_ctrl = gFocusMgr.getKeyboardFocus(); view->getPanel()->setSelection(item->getUUID(), TAKE_FOCUS_NO); gFocusMgr.setKeyboardFocus(focus_ctrl); } diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 99723f86a7..1594a68f36 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -34,7 +34,7 @@ #include "llviewerobject.h" -#include "audioengine.h" +#include "llaudioengine.h" #include "imageids.h" #include "indra_constants.h" #include "llmath.h" diff --git a/indra/newview/llviewerparcelmedia.cpp b/indra/newview/llviewerparcelmedia.cpp index 26e21133ac..cb233085e5 100644 --- a/indra/newview/llviewerparcelmedia.cpp +++ b/indra/newview/llviewerparcelmedia.cpp @@ -41,47 +41,22 @@ #include "llviewerparcelmgr.h" #include "lluuid.h" #include "message.h" +#include "llviewermediafocus.h" #include "llviewerparcelmediaautoplay.h" #include "llviewerwindow.h" #include "llfirstuse.h" +#include "llpluginclassmedia.h" // Static Variables S32 LLViewerParcelMedia::sMediaParcelLocalID = 0; LLUUID LLViewerParcelMedia::sMediaRegionID; +viewer_media_t LLViewerParcelMedia::sMediaImpl; + // Local functions bool callback_play_media(const LLSD& notification, const LLSD& response, LLParcel* parcel); -// Move this to its own file. -// helper class that tries to download a URL from a web site and calls a method -// on the Panel Land Media and to discover the MIME type -class LLMimeDiscoveryResponder : public LLHTTPClient::Responder -{ -public: - LLMimeDiscoveryResponder( ) - {} - - - - virtual void completedHeader(U32 status, const std::string& reason, const LLSD& content) - { - std::string media_type = content["content-type"].asString(); - std::string::size_type idx1 = media_type.find_first_of(";"); - std::string mime_type = media_type.substr(0, idx1); - completeAny(status, mime_type); - } - - virtual void error( U32 status, const std::string& reason ) - { - completeAny(status, "none/none"); - } - - void completeAny(U32 status, const std::string& mime_type) - { - LLViewerMedia::setMimeType(mime_type); - } -}; // static void LLViewerParcelMedia::initClass() @@ -92,6 +67,13 @@ void LLViewerParcelMedia::initClass() LLViewerParcelMediaAutoPlay::initClass(); } +//static +void LLViewerParcelMedia::cleanupClass() +{ + // This needs to be destroyed before global destructor time. + sMediaImpl = NULL; +} + ////////////////////////////////////////////////////////////////////////////////////////// // static void LLViewerParcelMedia::update(LLParcel* parcel) @@ -105,6 +87,7 @@ void LLViewerParcelMedia::update(LLParcel* parcel) { sMediaRegionID = LLUUID() ; stop() ; + LL_DEBUGS("Media") << "no agent region, bailing out." << LL_ENDL; return ; } @@ -115,64 +98,54 @@ void LLViewerParcelMedia::update(LLParcel* parcel) LLUUID regionid = gAgent.getRegion()->getRegionID(); if (parcelid != sMediaParcelLocalID || regionid != sMediaRegionID) { + LL_DEBUGS("Media") << "New parcel, parcel id = " << parcelid << ", region id = " << regionid << LL_ENDL; sMediaParcelLocalID = parcelid; sMediaRegionID = regionid; new_parcel = true; } std::string mediaUrl = std::string ( parcel->getMediaURL () ); + std::string mediaCurrentUrl = std::string( parcel->getMediaCurrentURL()); + + // First use warning + if( ! mediaUrl.empty() && gWarningSettings.getBOOL("FirstStreamingVideo") ) + { + LLNotifications::instance().add("ParcelCanPlayMedia", LLSD(), LLSD(), + boost::bind(callback_play_media, _1, _2, parcel)); + return; + + } + + // if we have a current (link sharing) url, use it instead + if (mediaCurrentUrl != "" && parcel->getMediaType() == "text/html") + { + mediaUrl = mediaCurrentUrl; + } + LLStringUtil::trim(mediaUrl); + + // If no parcel media is playing, nothing left to do + if(sMediaImpl.isNull()) - // has something changed? - if ( ( LLViewerMedia::getMediaURL() != mediaUrl ) - || ( LLViewerMedia::getMediaTextureID() != parcel->getMediaID () ) ) { - bool video_was_playing = FALSE; - bool same_media_id = LLViewerMedia::getMediaTextureID() == parcel->getMediaID (); + return; + } - if (LLViewerMedia::isMediaPlaying()) + // Media is playing...has something changed? + else if (( sMediaImpl->getMediaURL() != mediaUrl ) + || ( sMediaImpl->getMediaTextureID() != parcel->getMediaID() ) + || ( sMediaImpl->getMimeType() != parcel->getMediaType() )) + { + // Only play if the media types are the same. + if(sMediaImpl->getMimeType() == parcel->getMediaType()) { - video_was_playing = TRUE; + play(parcel); } - if ( !mediaUrl.empty() && same_media_id && ! new_parcel) - { - // Someone has "changed the channel", changing the URL of a video - // you were already watching. Automatically play provided the texture ID is the same - if (video_was_playing) - { - // Poke the mime type in before calling play. - // This is necessary because in this instance we are not waiting - // for the results of a header curl. In order to change the channel - // a mime type MUST be provided. - LLViewerMedia::setMimeType(parcel->getMediaType()); - play(parcel); - } - } else { stop(); } - - // Discover the MIME type - // Disabled for the time being. Get the mime type from the parcel. - if(gSavedSettings.getBOOL("AutoMimeDiscovery")) - { - LLHTTPClient::getHeaderOnly( mediaUrl, new LLMimeDiscoveryResponder()); - } - else - { - LLViewerMedia::setMimeType(parcel->getMediaType()); - } - - // First use warning - if( gWarningSettings.getBOOL("FirstStreamingVideo") ) - { - LLNotifications::instance().add("ParcelCanPlayMedia", LLSD(), LLSD(), - boost::bind(callback_play_media, _1, _2, parcel)); - - } - } } else @@ -183,7 +156,7 @@ void LLViewerParcelMedia::update(LLParcel* parcel) /* else { - // no audio player, do a first use dialog if their is media here + // no audio player, do a first use dialog if there is media here if (parcel) { std::string mediaUrl = std::string ( parcel->getMediaURL () ); @@ -212,15 +185,53 @@ void LLViewerParcelMedia::play(LLParcel* parcel) return; std::string media_url = parcel->getMediaURL(); + std::string media_current_url = parcel->getMediaCurrentURL(); std::string mime_type = parcel->getMediaType(); LLUUID placeholder_texture_id = parcel->getMediaID(); U8 media_auto_scale = parcel->getMediaAutoScale(); U8 media_loop = parcel->getMediaLoop(); S32 media_width = parcel->getMediaWidth(); S32 media_height = parcel->getMediaHeight(); - LLViewerMedia::play(media_url, mime_type, placeholder_texture_id, - media_width, media_height, media_auto_scale, - media_loop); + + if(sMediaImpl) + { + // If the url and mime type are the same, call play again + if(sMediaImpl->getMediaURL() == media_url + && sMediaImpl->getMimeType() == mime_type + && sMediaImpl->getMediaTextureID() == placeholder_texture_id) + { + LL_DEBUGS("Media") << "playing with existing url " << media_url << LL_ENDL; + + sMediaImpl->play(); + } + // Else if the texture id's are the same, navigate and rediscover type + // MBW -- This causes other state from the previous parcel (texture size, autoscale, and looping) to get re-used incorrectly. + // It's also not really necessary -- just creating a new instance is fine. +// else if(sMediaImpl->getMediaTextureID() == placeholder_texture_id) +// { +// sMediaImpl->navigateTo(media_url, mime_type, true); +// } + else + { + // Since the texture id is different, we need to generate a new impl + LL_DEBUGS("Media") << "new media impl with mime type " << mime_type << ", url " << media_url << LL_ENDL; + + // Delete the old one first so they don't fight over the texture. + sMediaImpl->stop(); + + sMediaImpl = LLViewerMedia::newMediaImpl(media_url, placeholder_texture_id, + media_width, media_height, media_auto_scale, + media_loop); + } + } + else + { + // There is no media impl, make a new one + sMediaImpl = LLViewerMedia::newMediaImpl(media_url, placeholder_texture_id, + media_width, media_height, media_auto_scale, + media_loop); + } + LLFirstUse::useMedia(); LLViewerParcelMediaAutoPlay::playStarted(); @@ -229,20 +240,38 @@ void LLViewerParcelMedia::play(LLParcel* parcel) // static void LLViewerParcelMedia::stop() { + if(sMediaImpl.isNull()) + { + return; + } + + // We need to remove the media HUD if it is up. + LLViewerMediaFocus::getInstance()->clearFocus(); - LLViewerMedia::stop(); + // This will kill the media instance. + sMediaImpl->stop(); + sMediaImpl = NULL; } // static void LLViewerParcelMedia::pause() { - LLViewerMedia::pause(); + if(sMediaImpl.isNull()) + { + return; + } + sMediaImpl->pause(); } // static void LLViewerParcelMedia::start() { - LLViewerMedia::start(); + if(sMediaImpl.isNull()) + { + return; + } + sMediaImpl->start(); + LLFirstUse::useMedia(); LLViewerParcelMediaAutoPlay::playStarted(); @@ -251,16 +280,41 @@ void LLViewerParcelMedia::start() // static void LLViewerParcelMedia::seek(F32 time) { - LLViewerMedia::seek(time); + if(sMediaImpl.isNull()) + { + return; + } + sMediaImpl->seek(time); } - // static -LLMediaBase::EStatus LLViewerParcelMedia::getStatus() +void LLViewerParcelMedia::focus(bool focus) { - return LLViewerMedia::getStatus(); + sMediaImpl->focus(focus); } +// static +LLPluginClassMediaOwner::EMediaStatus LLViewerParcelMedia::getStatus() +{ + LLPluginClassMediaOwner::EMediaStatus result = LLPluginClassMediaOwner::MEDIA_NONE; + + if(sMediaImpl.notNull() && sMediaImpl->hasMedia()) + { + result = sMediaImpl->getMediaPlugin()->getStatus(); + } + + return result; +} + +// static +std::string LLViewerParcelMedia::getMimeType() +{ + return sMediaImpl.notNull() ? sMediaImpl->getMimeType() : "none/none"; +} +viewer_media_t LLViewerParcelMedia::getParcelMedia() +{ + return sMediaImpl; +} ////////////////////////////////////////////////////////////////////////////////////////// // static void LLViewerParcelMedia::processParcelMediaCommandMessage( LLMessageSystem *msg, void ** ) @@ -298,7 +352,7 @@ void LLViewerParcelMedia::processParcelMediaCommandMessage( LLMessageSystem *msg if(( command == PARCEL_MEDIA_COMMAND_PLAY ) || ( command == PARCEL_MEDIA_COMMAND_LOOP )) { - if (LLViewerMedia::isMediaPaused()) + if (getStatus() == LLViewerMediaImpl::MEDIA_PAUSED) { start(); } @@ -318,7 +372,7 @@ void LLViewerParcelMedia::processParcelMediaCommandMessage( LLMessageSystem *msg if (flags & (1<<PARCEL_MEDIA_COMMAND_TIME)) { - if(! LLViewerMedia::hasMedia()) + if(sMediaImpl.isNull()) { LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); play(parcel); @@ -382,6 +436,107 @@ void LLViewerParcelMedia::processParcelMediaUpdate( LLMessageSystem *msg, void * } } } +// Static +///////////////////////////////////////////////////////////////////////////////////////// +void LLViewerParcelMedia::sendMediaNavigateMessage(const std::string& url) +{ + std::string region_url = gAgent.getRegion()->getCapability("ParcelNavigateMedia"); + if (!region_url.empty()) + { + // send navigate event to sim for link sharing + LLSD body; + body["agent-id"] = gAgent.getID(); + body["local-id"] = LLViewerParcelMgr::getInstance()->getAgentParcel()->getLocalID(); + body["url"] = url; + LLHTTPClient::post(region_url, body, new LLHTTPClient::Responder); + } + else + { + llwarns << "can't get ParcelNavigateMedia capability" << llendl; + } + +} + +///////////////////////////////////////////////////////////////////////////////////////// +// inherited from LLViewerMediaObserver +// virtual +void LLViewerParcelMedia::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) +{ + switch(event) + { + case MEDIA_EVENT_CONTENT_UPDATED: + { + // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CONTENT_UPDATED " << LL_ENDL; + }; + break; + + case MEDIA_EVENT_TIME_DURATION_UPDATED: + { + // LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_TIME_DURATION_UPDATED, time is " << self->getCurrentTime() << " of " << self->getDuration() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_SIZE_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_SIZE_CHANGED " << LL_ENDL; + }; + break; + + case MEDIA_EVENT_CURSOR_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CURSOR_CHANGED, new cursor is " << self->getCursorName() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_NAVIGATE_BEGIN: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_BEGIN " << LL_ENDL; + }; + break; + + case MEDIA_EVENT_NAVIGATE_COMPLETE: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_NAVIGATE_COMPLETE, result string is: " << self->getNavigateResultString() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_PROGRESS_UPDATED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PROGRESS_UPDATED, loading at " << self->getProgressPercent() << "%" << LL_ENDL; + }; + break; + + case MEDIA_EVENT_STATUS_TEXT_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_STATUS_TEXT_CHANGED, new status text is: " << self->getStatusText() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_LOCATION_CHANGED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_LOCATION_CHANGED, new uri is: " << self->getLocation() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_CLICK_LINK_HREF: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_HREF, target is \"" << self->getClickTarget() << "\", uri is " << self->getClickURL() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_CLICK_LINK_NOFOLLOW: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_CLICK_LINK_NOFOLLOW, uri is " << self->getClickURL() << LL_ENDL; + }; + break; + + case MEDIA_EVENT_PLUGIN_FAILED: + { + LL_DEBUGS("Media") << "Media event: MEDIA_EVENT_PLUGIN_FAILED" << LL_ENDL; + }; + break; + }; +} bool callback_play_media(const LLSD& notification, const LLSD& response, LLParcel* parcel) { @@ -401,3 +556,19 @@ bool callback_play_media(const LLSD& notification, const LLSD& response, LLParce return false; } +// TODO: observer +/* +void LLViewerParcelMediaNavigationObserver::onNavigateComplete( const EventType& event_in ) +{ + std::string url = event_in.getStringValue(); + + if (mCurrentURL != url && ! mFromMessage) + { + LLViewerParcelMedia::sendMediaNavigateMessage(url); + } + + mCurrentURL = url; + mFromMessage = false; + +} +*/ diff --git a/indra/newview/llviewerparcelmedia.h b/indra/newview/llviewerparcelmedia.h index 18988704d8..3f7f898356 100644 --- a/indra/newview/llviewerparcelmedia.h +++ b/indra/newview/llviewerparcelmedia.h @@ -33,18 +33,22 @@ #ifndef LLVIEWERPARCELMEDIA_H #define LLVIEWERPARCELMEDIA_H -#include "llmediabase.h" +#include "llviewermedia.h" class LLMessageSystem; class LLParcel; +class LLViewerParcelMediaNavigationObserver; + // This class understands land parcels, network traffic, LSL media // transport commands, and talks to the LLViewerMedia class to actually // do playback. It allows us to remove code from LLViewerParcelMgr. -class LLViewerParcelMedia +class LLViewerParcelMedia : public LLViewerMediaObserver { + LOG_CLASS(LLViewerParcelMedia); public: static void initClass(); + static void cleanupClass(); static void update(LLParcel* parcel); // called when the agent's parcel has a new URL, or the agent has @@ -60,17 +64,38 @@ class LLViewerParcelMedia static void start(); // restart after pause - no need for all the setup + static void focus(bool focus); + static void seek(F32 time); // jump to timecode time - static LLMediaBase::EStatus getStatus(); + static LLPluginClassMediaOwner::EMediaStatus getStatus(); + static std::string getMimeType(); + static viewer_media_t getParcelMedia(); static void processParcelMediaCommandMessage( LLMessageSystem *msg, void ** ); static void processParcelMediaUpdate( LLMessageSystem *msg, void ** ); + static void sendMediaNavigateMessage(const std::string& url); + + // inherited from LLViewerMediaObserver + virtual void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); public: static S32 sMediaParcelLocalID; static LLUUID sMediaRegionID; + // HACK: this will change with Media on a Prim + static viewer_media_t sMediaImpl; +}; + + +class LLViewerParcelMediaNavigationObserver +{ +public: + std::string mCurrentURL; + bool mFromMessage; + + // void onNavigateComplete( const EventType& event_in ); + }; #endif diff --git a/indra/newview/llviewerparcelmediaautoplay.cpp b/indra/newview/llviewerparcelmediaautoplay.cpp index 7ba2172c3f..1b79b47905 100644 --- a/indra/newview/llviewerparcelmediaautoplay.cpp +++ b/indra/newview/llviewerparcelmediaautoplay.cpp @@ -109,7 +109,7 @@ BOOL LLViewerParcelMediaAutoPlay::tick() if ((!mPlayed) && // if we've never played (mTimeInParcel > AUTOPLAY_TIME) && // and if we've been here for so many seconds (this_media_url.size() != 0) && // and if the parcel has media - (!LLViewerMedia::isMediaPlaying())) // and if the media is not already playing + (LLViewerParcelMedia::sMediaImpl.isNull())) // and if the media is not already playing { if (this_media_texture_id.notNull()) // and if the media texture is good { diff --git a/indra/newview/llviewerparcelmediaautoplay.h b/indra/newview/llviewerparcelmediaautoplay.h index cc2e70b1b9..16279e7f1f 100644 --- a/indra/newview/llviewerparcelmediaautoplay.h +++ b/indra/newview/llviewerparcelmediaautoplay.h @@ -33,7 +33,6 @@ #ifndef LLVIEWERPARCELMEDIAAUTOPLAY_H #define LLVIEWERPARCELMEDIAAUTOPLAY_H -#include "llmediabase.h" #include "lltimer.h" // timer to automatically play media diff --git a/indra/newview/llviewerparcelmgr.cpp b/indra/newview/llviewerparcelmgr.cpp index 9dafc4b9f5..ca3061e083 100644 --- a/indra/newview/llviewerparcelmgr.cpp +++ b/indra/newview/llviewerparcelmgr.cpp @@ -35,7 +35,7 @@ #include "llviewerparcelmgr.h" // Library includes -#include "audioengine.h" +#include "llaudioengine.h" #include "indra_constants.h" #include "llcachename.h" #include "llgl.h" @@ -1603,6 +1603,9 @@ void LLViewerParcelMgr::processParcelProperties(LLMessageSystem *msg, void **use // Request access list information for this land LLViewerParcelMgr::getInstance()->sendParcelAccessListRequest(AL_ACCESS | AL_BAN); + // Request the media url filter list for this land + LLViewerParcelMgr::getInstance()->requestParcelMediaURLFilter(); + // Request dwell for this land, if it's not public land. LLViewerParcelMgr::getInstance()->mSelectedDwell = 0.f; if (0 != local_id) @@ -1926,6 +1929,66 @@ void LLViewerParcelMgr::sendParcelAccessListUpdate(U32 which) } } +class LLParcelMediaURLFilterResponder : public LLHTTPClient::Responder +{ + virtual void result(const LLSD& content) + { + LLViewerParcelMgr::getInstance()->receiveParcelMediaURLFilter(content); + } +}; + +void LLViewerParcelMgr::requestParcelMediaURLFilter() +{ + if (!mSelected) + { + return; + } + + LLViewerRegion* region = LLWorld::getInstance()->getRegionFromPosGlobal( mWestSouth ); + if (!region) + { + return; + } + + LLParcel* parcel = mCurrentParcel; + if (!parcel) + { + llwarns << "no parcel" << llendl; + return; + } + + LLSD body; + body["local-id"] = parcel->getLocalID(); + body["list"] = parcel->getMediaURLFilterList(); + + std::string url = region->getCapability("ParcelMediaURLFilterList"); + if (!url.empty()) + { + LLHTTPClient::post(url, body, new LLParcelMediaURLFilterResponder); + } + else + { + llwarns << "can't get ParcelMediaURLFilterList cap" << llendl; + } +} + + +void LLViewerParcelMgr::receiveParcelMediaURLFilter(const LLSD &content) +{ + if (content.has("list")) + { + LLParcel* parcel = LLViewerParcelMgr::getInstance()->mCurrentParcel; + if (!parcel) return; + + if (content["local-id"].asInteger() == parcel->getLocalID()) + { + parcel->setMediaURLFilterList(content["list"]); + + LLViewerParcelMgr::getInstance()->notifyObservers(); + } + } +} + void LLViewerParcelMgr::deedLandToGroup() { diff --git a/indra/newview/llviewerparcelmgr.h b/indra/newview/llviewerparcelmgr.h index 917f28fd29..7373366cd7 100644 --- a/indra/newview/llviewerparcelmgr.h +++ b/indra/newview/llviewerparcelmgr.h @@ -206,6 +206,11 @@ public: // Takes an Access List flag, like AL_ACCESS or AL_BAN void sendParcelAccessListRequest(U32 flags); + // asks for the parcel's media url filter list + void requestParcelMediaURLFilter(); + // receive the response + void receiveParcelMediaURLFilter(const LLSD &content); + // Dwell is not part of the usual parcel update information because the // simulator doesn't actually know the per-parcel dwell. Ack! We have // to get it out of the database. diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index fd3dc16745..2c68a106c3 100644 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -1436,6 +1436,8 @@ void LLViewerRegion::setSeedCapability(const std::string& url) capabilityNames.append("MapLayerGod"); capabilityNames.append("NewFileAgentInventory"); capabilityNames.append("ParcelPropertiesUpdate"); + capabilityNames.append("ParcelMediaURLFilterList"); + capabilityNames.append("ParcelNavigateMedia"); capabilityNames.append("ParcelVoiceInfoRequest"); capabilityNames.append("ProductInfoRequest"); capabilityNames.append("ProvisionVoiceAccountRequest"); diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index 444d8e3164..994fbd8475 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -59,7 +59,6 @@ #include "llviewerregion.h" #include "llvoavatar.h" #include "llvoavatarself.h" -#include "llfloaterhtml.h" #include "llviewerwindow.h" // *TODO: remove, only used for width/height #include "llworld.h" #include "llfeaturemanager.h" diff --git a/indra/newview/llviewertexteditor.cpp b/indra/newview/llviewertexteditor.cpp index 7565c86696..7bbe2c89b4 100644 --- a/indra/newview/llviewertexteditor.cpp +++ b/indra/newview/llviewertexteditor.cpp @@ -34,7 +34,7 @@ #include "llfloaterreg.h" #include "llfocusmgr.h" -#include "audioengine.h" +#include "llaudioengine.h" #include "llagent.h" #include "llinventory.h" #include "llinventorybridge.h" diff --git a/indra/newview/llviewertexturelist.cpp b/indra/newview/llviewertexturelist.cpp index 1b926e2db6..dac2331ca3 100644 --- a/indra/newview/llviewertexturelist.cpp +++ b/indra/newview/llviewertexturelist.cpp @@ -583,7 +583,7 @@ void LLViewerTextureList::updateImages(F32 max_time) } if (!gNoRender && !gGLManager.mIsDisabled) { - LLViewerMedia::updateImagesMediaStreams(); + LLViewerMedia::updateMedia(); } updateImagesUpdateStats(); } diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 01756a0d82..36499fd191 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -56,7 +56,7 @@ // // linden library includes -#include "audioengine.h" // mute on minimize +#include "llaudioengine.h" // mute on minimize #include "indra_constants.h" #include "llassetstorage.h" #include "llerrorcontrol.h" @@ -165,6 +165,8 @@ #include "llviewertexturelist.h" #include "llviewerinventory.h" #include "llviewerkeyboard.h" +#include "llviewermedia.h" +#include "llviewermediafocus.h" #include "llviewermenu.h" #include "llviewermessage.h" #include "llviewerobjectlist.h" @@ -1155,7 +1157,7 @@ void LLViewerWindow::handleDataCopy(LLWindow *window, S32 data_type, void *data) case SLURL_MESSAGE_TYPE: // received URL std::string url = (const char*)data; - LLWebBrowserCtrl* web = NULL; + LLMediaCtrl* web = NULL; const bool trusted_browser = false; if (LLURLDispatcher::dispatch(url, web, trusted_browser)) { @@ -1654,6 +1656,19 @@ void LLViewerWindow::initWorldUI() //Notification Manager LLNotificationsUI::LLNotificationManager* notify_manager = LLNotificationsUI::LLNotificationManager::getInstance(); getRootView()->addChild(notify_manager); + + if ( gHUDView == NULL ) + { + LLRect hud_rect = full_window; + hud_rect.mBottom += 50; + if (gMenuBarView) + { + hud_rect.mTop -= gMenuBarView->getRect().getHeight(); + } + gHUDView = new LLHUDView(hud_rect); + // put behind everything else in the UI + getRootView()->addChildInBack(gHUDView); + } } // Destroy the UI @@ -2095,7 +2110,7 @@ BOOL LLViewerWindow::handleKey(KEY key, MASK mask) if (key < 0x80) { // Not a special key, so likely (we hope) to generate a character. Let it fall through to character handler first. - return gFocusMgr.childHasKeyboardFocus(mRootView); + return (gFocusMgr.getKeyboardFocus() != NULL); } } @@ -2178,7 +2193,7 @@ BOOL LLViewerWindow::handleKey(KEY key, MASK mask) } // Traverses up the hierarchy - LLUICtrl* keyboard_focus = gFocusMgr.getKeyboardFocus(); + LLFocusableElement* keyboard_focus = gFocusMgr.getKeyboardFocus(); if( keyboard_focus ) { LLLineEditor* chat_editor = LLBottomTray::instanceExists() ? LLBottomTray::getInstance()->getNearbyChatBar()->getChatBox() : NULL; @@ -2313,7 +2328,7 @@ BOOL LLViewerWindow::handleUnicodeChar(llwchar uni_char, MASK mask) } // Traverses up the hierarchy - LLView* keyboard_focus = gFocusMgr.getKeyboardFocus(); + LLFocusableElement* keyboard_focus = gFocusMgr.getKeyboardFocus(); if( keyboard_focus ) { if (keyboard_focus->handleUnicodeChar(uni_char, FALSE)) @@ -2803,6 +2818,12 @@ void LLViewerWindow::updatePicking(S32 x, S32 y, MASK mask) do_pick = FALSE; } + if(LLViewerMediaFocus::getInstance()->getFocus()) + { + // When in-world media is in focus, pick every frame so that browser mouse-overs, dragging scrollbars, etc. work properly. + do_pick = TRUE; + } + if (do_pick) { mouse_moved_since_pick = FALSE; @@ -2907,7 +2928,7 @@ void LLViewerWindow::updateMouseDelta() void LLViewerWindow::updateKeyboardFocus() { // clean up current focus - LLUICtrl* cur_focus = gFocusMgr.getKeyboardFocus(); + LLUICtrl* cur_focus = dynamic_cast<LLUICtrl*>(gFocusMgr.getKeyboardFocus()); if (cur_focus) { if (!cur_focus->isInVisibleChain() || !cur_focus->isInEnabledChain()) @@ -4277,7 +4298,6 @@ void LLViewerWindow::drawMouselookInstructions() LLFontGL::HCENTER, LLFontGL::TOP); } - S32 LLViewerWindow::getWindowHeight() const { return mVirtualWindowRect.getHeight(); @@ -4747,7 +4767,7 @@ BOOL LLViewerWindow::changeDisplaySettings(BOOL fullscreen, LLCoordScreen size, BOOL result_first_try = FALSE; BOOL result_second_try = FALSE; - LLUICtrl* keyboard_focus = gFocusMgr.getKeyboardFocus(); + LLFocusableElement* keyboard_focus = gFocusMgr.getKeyboardFocus(); send_agent_pause(); llinfos << "Stopping GL during changeDisplaySettings" << llendl; stopGL(); @@ -5122,12 +5142,8 @@ void LLPickInfo::updateXYCoords() LLPointer<LLViewerTexture> imagep = LLViewerTextureManager::getFetchedTexture(tep->getID()); if(mUVCoords.mV[VX] >= 0.f && mUVCoords.mV[VY] >= 0.f && imagep.notNull()) { - LLCoordGL coords; - - coords.mX = llround(mUVCoords.mV[VX] * (F32)imagep->getWidth()); - coords.mY = llround(mUVCoords.mV[VY] * (F32)imagep->getHeight()); - - gViewerWindow->getWindow()->convertCoords(coords, &mXYCoords); + mXYCoords.mX = llround(mUVCoords.mV[VX] * (F32)imagep->getWidth()); + mXYCoords.mY = llround((1.f - mUVCoords.mV[VY]) * (F32)imagep->getHeight()); } } } diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index e98e4f2e6a..98d8b27e09 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -37,7 +37,7 @@ #include <stdio.h> #include <ctype.h> -#include "audioengine.h" +#include "llaudioengine.h" #include "noise.h" #include "llagent.h" // Get state values from here diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index 5b4a649ee1..9777e1ec21 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -38,7 +38,7 @@ #include <stdio.h> #include <ctype.h> -#include "audioengine.h" +#include "llaudioengine.h" #include "noise.h" // TODO: Seraph - Remove unnecessary headers. These are copied from llvoavatar.h. diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 6a209ecabf..940accdd06 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -1759,7 +1759,7 @@ void LLVOVolume::generateSilhouette(LLSelectNode* nodep, const LLVector3& view_p trans_mat.translate(getRegion()->getOriginAgent()); } - volume->generateSilhouetteVertices(nodep->mSilhouetteVertices, nodep->mSilhouetteNormals, nodep->mSilhouetteSegments, view_vector, trans_mat, mRelativeXformInvTrans); + volume->generateSilhouetteVertices(nodep->mSilhouetteVertices, nodep->mSilhouetteNormals, nodep->mSilhouetteSegments, view_vector, trans_mat, mRelativeXformInvTrans, nodep->getTESelectMask()); nodep->mSilhouetteExists = TRUE; } diff --git a/indra/newview/llweb.cpp b/indra/newview/llweb.cpp index 57f57f75d7..300a5db7c3 100644 --- a/indra/newview/llweb.cpp +++ b/indra/newview/llweb.cpp @@ -40,7 +40,7 @@ #include "llviewerwindow.h" #include "llviewercontrol.h" -#include "llfloaterhtmlhelp.h" +#include "llfloatermediabrowser.h" #include "llfloaterreg.h" #include "llalertdialog.h" diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index bf00957212..b0c3e8d711 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -35,7 +35,7 @@ #include "pipeline.h" // library includes -#include "audioengine.h" // For MAX_BUFFERS for debugging. +#include "llaudioengine.h" // For MAX_BUFFERS for debugging. #include "imageids.h" #include "llerror.h" #include "llviewercontrol.h" diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index 29492ebbdf..0325e755eb 100644 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -530,5 +530,24 @@ <texture name="default_land_picture.j2c"/> <texture name="default_profile_picture.j2c"/> <texture name="locked_image.j2c"/> + + <texture name="media_btn_back.png"/> + <texture name="media_btn_done.png"/> + <texture name="media_btn_forward.png"/> + <texture name="media_btn_home.png"/> + <texture name="media_btn_newwindow.png"/> + <texture name="media_btn_optimalzoom.png"/> + <texture name="media_btn_reload.png"/> + <texture name="media_btn_scrolldown.png"/> + <texture name="media_btn_scrollleft.png"/> + <texture name="media_btn_scrollright.png"/> + <texture name="media_btn_scrollup.png"/> + <texture name="media_btn_stoploading.png"/> + <texture name="media_panel_divider.png"/> + + <texture name="media_floater_border_16.png" scale_top="12" scale_left="4" scale_bottom="4" scale_right="12"/> + + <texture name="media_panel_bg.png" preload="true" scale_left="9" scale_top="9" scale_right="9" scale_bottom="9" /> + <texture name="media_panel_hoverrectangle.png" preload="true" scale_left="9" scale_top="9" scale_right="9" scale_bottom="9" /> </textures> diff --git a/indra/newview/skins/default/xui/en/floater_about.xml b/indra/newview/skins/default/xui/en/floater_about.xml index 6eb55fa57f..73d789ce89 100644 --- a/indra/newview/skins/default/xui/en/floater_about.xml +++ b/indra/newview/skins/default/xui/en/floater_about.xml @@ -58,7 +58,11 @@ name="LLMozLibVersion"> LLMozLib Version: </floater.string> - <floater.string + <floater.string + name="LLQtWebkitVersion"> + Qt Webkit Version: 4.5.2 + </floater.string> + <floater.string name="PacketsLost"> Packets Lost: [LOST]/[IN] ([PCT]%) </floater.string> diff --git a/indra/newview/skins/default/xui/en/floater_about_land.xml b/indra/newview/skins/default/xui/en/floater_about_land.xml index 9165e8db98..63abb63bdf 100644 --- a/indra/newview/skins/default/xui/en/floater_about_land.xml +++ b/indra/newview/skins/default/xui/en/floater_about_land.xml @@ -1618,7 +1618,7 @@ Only large parcels can be listed in search. name="with media:" top="9" width="65"> - Media Type: + Type: </text> <combo_box height="18" @@ -1646,7 +1646,7 @@ Only large parcels can be listed in search. name="at URL:" top="29" width="65"> - Media URL: + Home URL: </text> <line_editor bottom_delta="0" @@ -1677,6 +1677,46 @@ Only large parcels can be listed in search. height="16" layout="topleft" left="10" + name="at URL:" + top="49" + width="65"> + Current URL: + </text> + <text + follows="left|top" + height="16" + layout="topleft" + left_pad="5" + name="current_url" + top_delta="0" + width="300" /> + <button + follows="left|top" + font="SansSerifSmall" + height="16" + label="Reset..." + label_selected="Reset..." + layout="topleft" + left_pad="6" + name="reset_media_url" + top_delta="0" + width="60" /> + <check_box + height="16" + label="Hide URL" + layout="topleft" + left="100" + name="hide_media_url" + tool_tip="Checking this option will hide the media url to any non-authorized viewers of this parcel information. Note this is not available for HTML types." + top="89" + width="200" /> + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" name="Description:" top="49" width="364"> @@ -1729,21 +1769,10 @@ Texture: name="replace_texture_help" top="85" width="270"> - (Objects using this texture will show the movie or -web page after you click the play arrow.) - </text> - <text - type="string" - length="1" - follows="left|top" - height="16" - layout="topleft" - left="10" - name="Options:" - top="140" - width="292"> - Media -Options: + Objects using this texture will show the movie or + web page after you click the play arrow. + + Select the thumbnail to choose a different texture. </text> <check_box height="16" @@ -1754,33 +1783,6 @@ Options: tool_tip="Checking this option will scale the content for this parcel automatically. It may be slightly slower and lower quality visually but no other texture scaling or alignment will be required." top_delta="0" width="200" /> - <check_box - height="16" - label="Loop Media" - layout="topleft" - left_delta="170" - name="media_loop" - tool_tip="Play media in a loop. When the media has finished playing, it will restart from the beginning." - top_delta="0" - width="200" /> - <check_box - height="16" - label="Hide Media URL" - layout="topleft" - left="80" - name="hide_media_url" - tool_tip="Checking this option will hide the media url to any non-authorized viewers of this parcel information. Note this is not available for HTML types." - top="160" - width="200" /> - <check_box - height="16" - label="Hide Music URL" - layout="topleft" - left_delta="170" - name="hide_music_url" - tool_tip="Checking this option will hide the music url to any non-authorized viewers of this parcel information" - top_delta="0" - width="200" /> <text type="string" length="1" @@ -1792,7 +1794,7 @@ Options: tool_tip="Size to render Web media, leave 0 for default." top="185" width="85"> - Media size: + Size: </text> <spinner decimal_digits="0" @@ -1843,6 +1845,38 @@ Options: height="16" layout="topleft" left="10" + name="Options:" + top="237" + width="292"> + Options: + </text> + <check_box + height="16" + label="Loop" + layout="topleft" + left_delta="70" + name="media_loop" + tool_tip="Play media in a loop. When the media has finished playing, it will restart from the beginning." + top_delta="0" + width="200" /> + </panel> + <panel + border="true" + follows="left|top|right|bottom" + height="363" + label="Audio" + layout="topleft" + left_delta="0" + name="land_audio_panel" + top_delta="1" + width="458"> + <text + type="string" + length="1" + follows="left|top" + height="16" + layout="topleft" + left="10" name="MusicURL:" top="225" width="364"> diff --git a/indra/newview/skins/default/xui/en/floater_media_browser.xml b/indra/newview/skins/default/xui/en/floater_media_browser.xml index a277294be1..afc72a78a9 100644 --- a/indra/newview/skins/default/xui/en/floater_media_browser.xml +++ b/indra/newview/skins/default/xui/en/floater_media_browser.xml @@ -9,71 +9,55 @@ save_rect="true" title="Media Browser" width="820"> - <!--TODO: ADD CLOSE X TO WINDOW--> - <!-- <button - follows="bottom|right" - height="20" - label="Close" - layout="topleft" - left_pad="80" - name="close" - top_delta="0" - width="70"> - <button.commit_callback - function="MediaBrowser.Close" /> - </button>--> + <floater.string + name="home_page_url"> + http://www.secondlife.com + </floater.string> + <floater.string + name="support_page_url"> + http://support.secondlife.com + </floater.string> <layout_stack + bottom="440" follows="left|right|top|bottom" layout="topleft" left="10" name="stack1" top="20" - width="800" - height="400" - > + width="800"> <layout_panel auto_resize="false" + height="20" layout="topleft" + left="0" name="nav_controls" - user_resize="false"> + top="400" + user_resize="false" + width="800"> <button - left="1" - follows="left|top" - font="SansSerifSmall" - height="20" - image_disabled="PushButton_Disabled" - image_disabled_selected="PushButton_Disabled" - image_overlay="Arrow_Left_Off" - image_selected="PushButton_Selected" - image_unselected="PushButton_Off" - hover_glow_amount="0.15" - layout="topleft" - name="back" - picture_style="true" - tool_tip="Go back to previous location" - width="20"> - <button.commit_callback - function="MediaBrowser.Back" /> - </button> + follows="left|top" + height="20" + label="Back" + layout="topleft" + left="0" + name="back" + top="0" + width="55"> + <button.commit_callback + function="MediaBrowser.Back" /> + </button> <button - follows="left|top" - font="SansSerifSmall" - height="20" - image_disabled="PushButton_Disabled" - image_disabled_selected="PushButton_Disabled" - image_overlay="Arrow_Right_Off" - image_selected="PushButton_Selected" - image_unselected="PushButton_Off" - hover_glow_amount="0.15" - layout="topleft" - left_pad="2" - name="forward" - picture_style="true" - tool_tip="Go forward one location" - width="20"> - <button.commit_callback - function="MediaBrowser.Forward" /> - </button> + follows="left|top" + height="20" + label="Forward" + layout="topleft" + left_pad="3" + name="forward" + top_delta="0" + width="68"> + <button.commit_callback + function="MediaBrowser.Forward" /> + </button> <button enabled="false" follows="left|top" @@ -82,26 +66,24 @@ layout="topleft" left_pad="2" name="reload" + top_delta="0" width="70"> - <button.commit_callback - function="MediaBrowser.Refresh" /> - </button> + <button.commit_callback + function="MediaBrowser.Refresh" /> + </button> <combo_box allow_text_entry="true" + follows="left|top|right" + height="20" + layout="topleft" + left_pad="5" + max_chars="255" name="address" - follows="left|right|top" - halign="right" - height="20" - label="Location" - layout="topleft" - left_pad="5" - max_chars="254" - mouse_opaque="false" - top_delta="0" - width="340"> - <combo_box.commit_callback - function="MediaBrowser.EnterAddress" /> - </combo_box> + top_delta="0" + width="540"> + <combo_box.commit_callback + function="MediaBrowser.EnterAddress" /> + </combo_box> <button enabled="false" follows="right|top" @@ -110,31 +92,81 @@ layout="topleft" left_pad="5" name="go" - width="50"> - <button.commit_callback - function="MediaBrowser.Go" /> - </button> + top_delta="0" + width="55"> + <button.commit_callback + function="MediaBrowser.Go" /> + </button> + </layout_panel> + <layout_panel + auto_resize="false" + height="20" + layout="topleft" + left_delta="0" + name="time_controls" + top_delta="0" + user_resize="false" + width="800"> <button - follows="right|top" + follows="left|top" height="20" - left_pad="20" - label="Open in My Browser" + label="rewind" layout="topleft" - name="open_browser" - width="145"> - <button.commit_callback - function="MediaBrowser.OpenWebBrowser" /> - </button> - <check_box - control_name="UseExternalBrowser" - follows="right|top" - label="Always" + left="0" + name="rewind" + top="0" + width="55" /> + <button + follows="left|top" + height="20" + image_selected="button_anim_play_selected.tga" + image_unselected="button_anim_play.tga" layout="topleft" - left_pad="5" - width="140" - name="open_always" /> - <!-- - <button + left_delta="55" + name="play" + picture_style="true" + top_delta="0" + width="55" /> + <button + follows="left|top" + height="20" + image_selected="button_anim_pause_selected.tga" + image_unselected="button_anim_pause.tga" + layout="topleft" + left_delta="0" + name="pause" + picture_style="true" + top_delta="0" + width="55" /> + <button + follows="left|top" + height="20" + label="stop" + layout="topleft" + left_pad="10" + name="stop" + top_delta="0" + width="55" /> + <button + follows="left|top" + height="20" + label="forward" + layout="topleft" + left_pad="20" + name="seek" + top_delta="0" + width="55" /> + </layout_panel> + <layout_panel + auto_resize="false" + height="20" + layout="topleft" + left_delta="0" + name="parcel_owner_controls" + top_delta="0" + user_resize="false" + width="540"> + <button enabled="false" follows="left|top" height="20" @@ -144,22 +176,60 @@ name="assign" top="0" width="200"> - <button.commit_callback - function="MediaBrowser.Assign" /> - </button> - </layout_panel> + <button.commit_callback + function="MediaBrowser.Assign" /> + </button> + </layout_panel> <layout_panel - auto_resize="false" + height="20" layout="topleft" - name="browser" - left="0" - top="0" - user_resize="false">--> - <web_browser + left_delta="0" + name="external_controls" + top_delta="0" + user_resize="false" + width="540"> + <web_browser + bottom="-10" follows="left|right|top|bottom" layout="topleft" left="0" - name="browser" /> + name="browser" + top="0" + width="540" /> + <button + follows="bottom|left" + height="20" + label="Open in My Web Browser" + layout="topleft" + left_delta="0" + name="open_browser" + top_pad="5" + width="185"> + <button.commit_callback + function="MediaBrowser.OpenWebBrowser" /> + </button> + <check_box + control_name="UseExternalBrowser" + follows="bottom|left" + height="20" + label="Always open in my web browser" + layout="topleft" + left_pad="5" + name="open_always" + top_delta="0" + width="200" /> + <button + follows="bottom|right" + height="20" + label="Close" + layout="topleft" + left_pad="80" + name="close" + top_delta="0" + width="70"> + <button.commit_callback + function="MediaBrowser.Close" /> + </button> </layout_panel> </layout_stack> </floater> diff --git a/indra/newview/skins/default/xui/en/mime_types.xml b/indra/newview/skins/default/xui/en/mime_types.xml index e3d102148a..804bee6cd1 100644 --- a/indra/newview/skins/default/xui/en/mime_types.xml +++ b/indra/newview/skins/default/xui/en/mime_types.xml @@ -7,7 +7,7 @@ none </defaultwidget> <defaultimpl> - LLMediaImplLLMozLib + media_plugin_webkit </defaultimpl> <widgetset name="web"> <label name="web_label"> @@ -55,27 +55,6 @@ true </allow_looping> </widgetset> - <widgetset name="none"> - <label name="none_label"> - No Content - </label> - <default_type> - none/none - </default_type> - <icon> - icn_media_web.tga - </icon> - <tooltip name="none_tooltip"> - No media here - </tooltip> - <playtip name="none_playtip" /> - <allow_resize> - false - </allow_resize> - <allow_looping> - false - </allow_looping> - </widgetset> <widgetset name="image"> <label name="image_label"> Image @@ -129,6 +108,9 @@ <widgettype> movie </widgettype> + <impl> + media_plugin_quicktime + </impl> </scheme> <mimetype name="blank"> <label name="blank_label"> @@ -138,7 +120,7 @@ none </widgettype> <impl> - LLMediaImplQuickTime + media_plugin_quicktime </impl> </mimetype> <mimetype name="none/none"> @@ -181,8 +163,8 @@ movie </widgettype> <impl> - LLMediaImplQuickTime - </impl> + media_plugin_quicktime + </impl> </mimetype> <mimetype name="application/javascript"> <label name="application/javascript_label"> @@ -255,6 +237,9 @@ <widgettype> image </widgettype> + <impl> + media_plugin_flash_activex + </impl> </mimetype> <mimetype name="audio/mid"> <label name="audio/mid_label"> @@ -263,6 +248,9 @@ <widgettype> audio </widgettype> + <impl> + media_plugin_quicktime + </impl> </mimetype> <mimetype name="audio/mpeg"> <label name="audio/mpeg_label"> @@ -271,6 +259,9 @@ <widgettype> audio </widgettype> + <impl> + media_plugin_quicktime + </impl> </mimetype> <mimetype name="audio/x-aiff"> <label name="audio/x-aiff_label"> @@ -279,6 +270,9 @@ <widgettype> audio </widgettype> + <impl> + media_plugin_quicktime + </impl> </mimetype> <mimetype name="audio/x-wav"> <label name="audio/x-wav_label"> @@ -287,6 +281,9 @@ <widgettype> audio </widgettype> + <impl> + media_plugin_quicktime + </impl> </mimetype> <mimetype menu="1" name="image/bmp"> <label name="image/bmp_label"> @@ -296,7 +293,7 @@ image </widgettype> <impl> - LLMediaImplLLMozLib + media_plugin_webkit </impl> </mimetype> <mimetype menu="1" name="image/gif"> @@ -307,7 +304,7 @@ image </widgettype> <impl> - LLMediaImplLLMozLib + media_plugin_webkit </impl> </mimetype> <mimetype menu="1" name="image/jpeg"> @@ -318,7 +315,7 @@ image </widgettype> <impl> - LLMediaImplLLMozLib + media_plugin_webkit </impl> </mimetype> <mimetype menu="1" name="image/png"> @@ -329,7 +326,7 @@ image </widgettype> <impl> - LLMediaImplLLMozLib + media_plugin_webkit </impl> </mimetype> <mimetype name="image/svg+xml"> @@ -340,7 +337,7 @@ image </widgettype> <impl> - LLMediaImplLLMozLib + media_plugin_webkit </impl> </mimetype> <mimetype menu="1" name="image/tiff"> @@ -351,7 +348,7 @@ image </widgettype> <impl> - LLMediaImplLLMozLib + media_plugin_webkit </impl> </mimetype> <mimetype menu="1" name="text/html"> @@ -362,8 +359,8 @@ web </widgettype> <impl> - LLMediaImplLLMozLib - </impl> + media_plugin_webkit + </impl> </mimetype> <mimetype menu="1" name="text/plain"> <label name="text/plain_label"> @@ -373,7 +370,7 @@ text </widgettype> <impl> - LLMediaImplLLMozLib + media_plugin_webkit </impl> </mimetype> <mimetype name="text/xml"> @@ -384,7 +381,7 @@ text </widgettype> <impl> - LLMediaImplLLMozLib + media_plugin_webkit </impl> </mimetype> <mimetype menu="1" name="video/mpeg"> @@ -395,8 +392,8 @@ movie </widgettype> <impl> - LLMediaImplQuickTime - </impl> + media_plugin_quicktime + </impl> </mimetype> <mimetype name="video/mp4"> <label name="video/mp4_label"> @@ -406,8 +403,8 @@ movie </widgettype> <impl> - LLMediaImplQuickTime - </impl> + media_plugin_quicktime + </impl> </mimetype> <mimetype menu="1" name="video/quicktime"> <label name="video/quicktime_label"> @@ -417,8 +414,8 @@ movie </widgettype> <impl> - LLMediaImplQuickTime - </impl> + media_plugin_quicktime + </impl> </mimetype> <mimetype name="video/x-ms-asf"> <label name="video/x-ms-asf_label"> @@ -428,8 +425,8 @@ movie </widgettype> <impl> - LLMediaImplQuickTime - </impl> + media_plugin_quicktime + </impl> </mimetype> <mimetype name="video/x-ms-wmv"> <label name="video/x-ms-wmv_label"> @@ -439,8 +436,8 @@ movie </widgettype> <impl> - LLMediaImplQuickTime - </impl> + media_plugin_quicktime + </impl> </mimetype> <mimetype menu="1" name="video/x-msvideo"> <label name="video/x-msvideo_label"> @@ -450,7 +447,7 @@ movie </widgettype> <impl> - LLMediaImplQuickTime - </impl> + media_plugin_quicktime + </impl> </mimetype> </mimetypes> diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 8b0741524d..216627879e 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -5451,7 +5451,25 @@ How odd. The string [STRING_NAME] is missing from strings.xml Apple's QuickTime software does not appear to be installed on your system. If you want to view streaming media on parcels that support it you should go to the QuickTime site (http://www.apple.com/quicktime) and install the QuickTime Player. </notification> + <notification + icon="notify.tga" + name="NoPlugin" + type="notify"> + No Media Plugin was found to handle the "[MIME_TYPE]" mime type. Media of this type will be unavailable. + </notification> + <notification + icon="alertmodal.tga" + name="MediaPluginFailed" + type="alertmodal"> + The following Media Plugin has failed: + [PLUGIN] +Please re-install the plugin or contact the vendor if you continue to experience problems. + <form name="form"> + <ignore name="ignore" + text="When a Media Plugin fails"/> + </form> + </notification> <notification icon="notify.tga" name="OwnedObjectsReturned" diff --git a/indra/newview/skins/default/xui/en/panel_hud.xml b/indra/newview/skins/default/xui/en/panel_hud.xml new file mode 100644 index 0000000000..3e9614a9b7 --- /dev/null +++ b/indra/newview/skins/default/xui/en/panel_hud.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8" standalone="yes" ?> +<panel + follows="left|right|top|bottom" + height="728" + layout="topleft" + left="0" + mouse_opaque="false" + name="hud" + top="778" + width="1024" /> diff --git a/indra/newview/skins/default/xui/en/panel_login.xml b/indra/newview/skins/default/xui/en/panel_login.xml index 5ee73c1242..41294259e3 100644 --- a/indra/newview/skins/default/xui/en/panel_login.xml +++ b/indra/newview/skins/default/xui/en/panel_login.xml @@ -26,6 +26,7 @@ layout="topleft" left="0" name="login_html" + hide_loading="true" right="-1" start_url="data:text/html,%3Chtml%3E%3Chead%3E%3C/head%3E%3Cbody bgcolor=%22#000000%22 text=%22ffffff%22%3E%3Ch1%3E%3Ctt%3Eloading...%3C/tt%3E%3C/h1%3E %3C/body%3E %3C/html%3E" top="1" /> diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 9c122deba0..d175a26531 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -193,7 +193,47 @@ class WindowsManifest(ViewerManifest): self.path("openjpeg.dll") self.end_prefix() - # Mozilla appears to force a dependency on these files so we need to ship it (CP) - updated to vc8 versions (nyx) + # Plugin host application + if self.prefix(src='../llplugin/slplugin/%s' % self.args['configuration'], dst="llplugin"): + self.path("slplugin.exe") + self.end_prefix() + + # Media plugins - Flash (ActiveX) + if self.prefix(src='../media_plugins/flash_activex/%s' % self.args['configuration'], dst="llplugin"): + self.path("media_plugin_flash_activex.dll") + self.end_prefix() + + # Media plugins - QuickTime + if self.prefix(src='../media_plugins/quicktime/%s' % self.args['configuration'], dst="llplugin"): + self.path("media_plugin_quicktime.dll") + self.end_prefix() + + # Media plugins - WebKit/Qt + if self.prefix(src='../media_plugins/webkit/%s' % self.args['configuration'], dst="llplugin"): + self.path("media_plugin_webkit.dll") + self.end_prefix() + + # For WebKit/Qt plugin runtimes + if self.prefix(src="../../libraries/i686-win32/lib/release", dst="llplugin"): + self.path("libeay32.dll") + self.path("qtcore4.dll") + self.path("qtgui4.dll") + self.path("qtnetwork4.dll") + self.path("qtopengl4.dll") + self.path("qtwebkit4.dll") + self.path("ssleay32.dll") + self.end_prefix() + + # For WebKit/Qt plugin runtimes (image format plugins) + if self.prefix(src="../../libraries/i686-win32/lib/release/imageformats", dst="llplugin/imageformats"): + self.path("qgif4.dll") + self.path("qico4.dll") + self.path("qjpeg4.dll") + self.path("qmng4.dll") + self.path("qsvg4.dll") + self.path("qtiff4.dll") + self.end_prefix() + # These need to be installed as a SxS assembly, currently a 'private' assembly. # See http://msdn.microsoft.com/en-us/library/ms235291(VS.80).aspx if self.prefix(src=self.args['configuration'], dst=""): @@ -210,34 +250,6 @@ class WindowsManifest(ViewerManifest): # The config file name needs to match the exe's name. self.path(src="%s/secondlife-bin.exe.config" % self.args['configuration'], dst=self.final_exe() + ".config") - # Mozilla runtime DLLs (CP) - if self.prefix(src="../../libraries/i686-win32/lib/release", dst=""): - self.path("freebl3.dll") - self.path("js3250.dll") - self.path("nspr4.dll") - self.path("nss3.dll") - self.path("nssckbi.dll") - self.path("plc4.dll") - self.path("plds4.dll") - self.path("smime3.dll") - self.path("softokn3.dll") - self.path("ssl3.dll") - self.path("xpcom.dll") - self.path("xul.dll") - self.end_prefix() - - # Mozilla runtime misc files (CP) - if self.prefix(src="app_settings/mozilla"): - self.path("chrome/*.*") - self.path("components/*.*") - self.path("greprefs/*.*") - self.path("plugins/*.*") - self.path("res/*.*") - self.path("res/*/*") - self.end_prefix() - - # Mozilla hack to get it to accept newer versions of msvc*80.dll than are listed in manifest - # necessary as llmozlib2-vc80.lib refers to an old version of msvc*80.dll - can be removed when new version of llmozlib is built - Nyx # Vivox runtimes if self.prefix(src="vivox-runtime/i686-win32", dst=""): self.path("SLVoice.exe") @@ -403,21 +415,11 @@ class DarwinManifest(ViewerManifest): self.path(self.args['configuration'] + "/Second Life.app", dst="") if self.prefix(src="", dst="Contents"): # everything goes in Contents - # Expand the tar file containing the assorted mozilla bits into - # <bundle>/Contents/MacOS/ - self.contents_of_tar(self.args['source']+'/mozilla-universal-darwin.tgz', 'MacOS') - self.path("Info-SecondLife.plist", dst="Info.plist") # copy additional libs in <bundle>/Contents/MacOS/ self.path("../../libraries/universal-darwin/lib_release/libndofdev.dylib", dst="MacOS/libndofdev.dylib") - # replace the default theme with our custom theme (so scrollbars work). - if self.prefix(src="mozilla-theme", dst="MacOS/chrome"): - self.path("classic.jar") - self.path("classic.manifest") - self.end_prefix("MacOS/chrome") - # most everything goes in the Resources directory if self.prefix(src="", dst="Resources"): super(DarwinManifest, self).construct() @@ -480,6 +482,18 @@ class DarwinManifest(ViewerManifest): self.path("../mac_crash_logger/" + self.args['configuration'] + "/mac-crash-logger.app", "mac-crash-logger.app") self.path("../mac_updater/" + self.args['configuration'] + "/mac-updater.app", "mac-updater.app") + # plugins + if self.prefix(src="", dst="llplugin"): + self.path("../llplugin/slplugin/" + self.args['configuration'] + "/SLPlugin", "SLPlugin") + self.path("../media_plugins/quicktime/" + self.args['configuration'] + "/media_plugin_quicktime.dylib", "media_plugin_quicktime.dylib") + self.path("../media_plugins/webkit/" + self.args['configuration'] + "/media_plugin_webkit.dylib", "media_plugin_webkit.dylib") + self.path("../../libraries/universal-darwin/lib_release/libllwebkitlib.dylib", "libllwebkitlib.dylib") + + self.path("../media_plugins/awesomium/" + self.args['configuration'] + "/media_plugin_awesomium.dylib", "media_plugin_awesomium.dylib") + self.path("../../libraries/universal-darwin/lib_release/Awesomium.framework", "Awesomium.framework") + + self.end_prefix("llplugin") + # command line arguments for connecting to the proper grid self.put_in_file(self.flags_list(), 'arguments.txt') @@ -522,7 +536,7 @@ class DarwinManifest(ViewerManifest): # make sure we don't have stale files laying about self.remove(sparsename, finalname) - self.run_command('hdiutil create "%(sparse)s" -volname "%(vol)s" -fs HFS+ -type SPARSE -megabytes 300 -layout SPUD' % { + self.run_command('hdiutil create "%(sparse)s" -volname "%(vol)s" -fs HFS+ -type SPARSE -megabytes 400 -layout SPUD' % { 'sparse':sparsename, 'vol':volname}) @@ -669,11 +683,16 @@ class Linux_i686Manifest(LinuxManifest): # recurse self.end_prefix("res-sdl") + # plugins + if self.prefix(src="", dst="bin/llplugin"): + self.path("../llplugin/slplugin/SLPlugin", "SLPlugin") + self.path("../media_plugins/webkit/libmedia_plugin_webkit.so", "libmedia_plugin_webkit.so") + self.path("../media_plugins/gstreamer010/libmedia_plugin_gstreamer010.so", "libmedia_plugin_quicktime.so") + self.end_prefix("bin/llplugin") + self.path("featuretable_linux.txt") #self.path("secondlife-i686.supp") - self.path("app_settings/mozilla-runtime-linux-i686") - if self.prefix("../../libraries/i686-linux/lib_release_client", dst="lib"): #self.path("libkdu_v42R.so", "libkdu.so") self.path("libfmod-3.75.so") diff --git a/indra/test_apps/llplugintest/CMakeLists.txt b/indra/test_apps/llplugintest/CMakeLists.txt new file mode 100644 index 0000000000..dd894087e6 --- /dev/null +++ b/indra/test_apps/llplugintest/CMakeLists.txt @@ -0,0 +1,364 @@ +# -*- cmake -*- + +project(llplugintest) + +include(00-Common) +include(FindOpenGL) +include(LLCommon) +include(LLPlugin) +include(Linking) +include(PluginAPI) +include(LLImage) +include(LLMath) +include(LLMessage) +include(LLRender) +include(LLWindow) +include(Glut) +include(Glui) + +include_directories( + ${LLPLUGIN_INCLUDE_DIRS} + ${LLCOMMON_INCLUDE_DIRS} + ${LLIMAGE_INCLUDE_DIRS} + ${LLMATH_INCLUDE_DIRS} + ${LLMESSAGE_INCLUDE_DIRS} + ${LLRENDER_INCLUDE_DIRS} + ${LLWINDOW_INCLUDE_DIRS} +) + +if (DARWIN) + include(CMakeFindFrameworks) + find_library(CARBON_LIBRARY Carbon) +endif (DARWIN) + +### demo_plugin + +#set(demo_plugin_SOURCE_FILES +# demo_plugin.cpp +# ) +# +#add_library(demo_plugin +# SHARED +# ${demo_plugin_SOURCE_FILES} +#) +# +#target_link_libraries(demo_plugin +# ${LLPLUGIN_LIBRARIES} +# ${LLCOMMON_LIBRARIES} +# ${PLUGIN_API_WINDOWS_LIBRARIES} +#) +# +#add_dependencies(demo_plugin +# ${LLPLUGIN_LIBRARIES} +# ${LLCOMMON_LIBRARIES} +#) +# +#if (DARWIN) +# # Don't prepend 'lib' to the executable name, and don't embed a full path in the library's install name +# set_target_properties( +# demo_plugin +# PROPERTIES +# PREFIX "" +# BUILD_WITH_INSTALL_RPATH 1 +# INSTALL_NAME_DIR "@executable_path" +# ) +#endif (DARWIN) + +### plugin_host + +#set(plugin_host_SOURCE_FILES +# plugin_host.cpp +# ) +# +#add_executable(plugin_host +# WIN32 +# ${plugin_host_SOURCE_FILES} +#) +# +#set_target_properties(plugin_host +# PROPERTIES +# WIN32_EXECUTABLE +# FALSE +#) +# +#target_link_libraries(plugin_host +# ${LLPLUGIN_LIBRARIES} +# ${LLCOMMON_LIBRARIES} +# ${PLUGIN_API_WINDOWS_LIBRARIES} +#) +# +#add_dependencies(plugin_host +# demo_plugin +# ${LLPLUGIN_LIBRARIES} +# ${LLCOMMON_LIBRARIES} +#) + +### plugin_process_launcher + +#set(plugin_process_launcher_SOURCE_FILES +# plugin_process_launcher.cpp +# ) +# +#add_executable(plugin_process_launcher +# WIN32 +# ${plugin_process_launcher_SOURCE_FILES} +#) +# +#set_target_properties(plugin_process_launcher +# PROPERTIES +# WIN32_EXECUTABLE +# FALSE +#) +# +#target_link_libraries(plugin_process_launcher +# ${LLPLUGIN_LIBRARIES} +# ${LLMESSAGE_LIBRARIES} +# ${LLCOMMON_LIBRARIES} +# ${PLUGIN_API_WINDOWS_LIBRARIES} +#) +# +#add_dependencies(plugin_process_launcher +# SLPlugin +# demo_plugin +# ${LLPLUGIN_LIBRARIES} +# ${LLMESSAGE_LIBRARIES} +# ${LLCOMMON_LIBRARIES} +#) + +### media_simple_test + +#set(media_simple_test_SOURCE_FILES +# media_simple_test.cpp +# ) +# +#add_executable(media_simple_test +# WIN32 +# ${media_simple_test_SOURCE_FILES} +#) +# +#add_dependencies(media_simple_test copy_win_libs) +# +#set_target_properties(media_simple_test +# PROPERTIES +# WIN32_EXECUTABLE +# FALSE +#) +# +#target_link_libraries(media_simple_test +# ${GLUT_LIBRARY} +# ${OPENGL_LIBRARIES} +# ${LLCOMMON_LIBRARIES} +#) + +### media_plugin_test + +#set(media_plugin_test_SOURCE_FILES +# media_plugin_test.cpp +# ) +# +#add_executable(media_plugin_test +# WIN32 +# ${media_plugin_test_SOURCE_FILES} +#) +# +#set_target_properties(media_plugin_test +# PROPERTIES +# WIN32_EXECUTABLE +# FALSE +#) +# +#target_link_libraries(media_plugin_test +# ${GLUT_LIBRARY} +# ${OPENGL_LIBRARIES} +# ${LLPLUGIN_LIBRARIES} +# ${LLMESSAGE_LIBRARIES} +# ${LLCOMMON_LIBRARIES} +# ${PLUGIN_API_WINDOWS_LIBRARIES} +#) +# +#add_dependencies(media_plugin_test +# copy_win_libs +# SLPlugin +# demo_media_plugin +# ${LLPLUGIN_LIBRARIES} +# ${LLMESSAGE_LIBRARIES} +# ${LLCOMMON_LIBRARIES} +#) + +### demo_media_plugin + +#set(demo_media_plugin_SOURCE_FILES +# demo_media_plugin.cpp +# ) +# +#add_library(demo_media_plugin +# SHARED +# ${demo_media_plugin_SOURCE_FILES} +#) +# +#target_link_libraries(demo_media_plugin +# ${LLPLUGIN_LIBRARIES} +# ${LLCOMMON_LIBRARIES} +# ${PLUGIN_API_WINDOWS_LIBRARIES} +#) +# +#add_dependencies(demo_media_plugin +# ${LLPLUGIN_LIBRARIES} +# ${LLCOMMON_LIBRARIES} +#) +# +#if (DARWIN) +# # Don't prepend 'lib' to the executable name, and don't embed a full path in the library's install name +# set_target_properties( +# demo_media_plugin +# PROPERTIES +# PREFIX "" +# BUILD_WITH_INSTALL_RPATH 1 +# INSTALL_NAME_DIR "@executable_path" +# ) +#endif (DARWIN) + +### demo_media_plugin_2 + +#set(demo_media_plugin_2_SOURCE_FILES +# demo_media_plugin_2.cpp +# ) +# +#add_library(demo_media_plugin_2 +# SHARED +# ${demo_media_plugin_2_SOURCE_FILES} +#) +# +#target_link_libraries(demo_media_plugin_2 +# ${LLPLUGIN_LIBRARIES} +# ${LLCOMMON_LIBRARIES} +# ${PLUGIN_API_WINDOWS_LIBRARIES} +#) +# +#add_dependencies(demo_media_plugin_2 +# ${LLPLUGIN_LIBRARIES} +# ${LLCOMMON_LIBRARIES} +#) +# +#if (DARWIN) +# # Don't prepend 'lib' to the executable name, and don't embed a full path in the library's install name +# set_target_properties( +# demo_media_plugin_2 +# PROPERTIES +# PREFIX "" +# BUILD_WITH_INSTALL_RPATH 1 +# INSTALL_NAME_DIR "@executable_path" +# ) +#endif (DARWIN) + +### llmediaplugintest + +set(llmediaplugintest_SOURCE_FILES + llmediaplugintest.cpp + llmediaplugintest.h + bookmarks.txt + ) + +add_executable(llmediaplugintest + WIN32 + ${llmediaplugintest_SOURCE_FILES} +) + +set_target_properties(llmediaplugintest + PROPERTIES + WIN32_EXECUTABLE + FALSE +) + +target_link_libraries(llmediaplugintest + ${GLUT_LIBRARY} + ${GLUI_LIBRARY} + ${OPENGL_LIBRARIES} + ${LLPLUGIN_LIBRARIES} + ${LLMESSAGE_LIBRARIES} + ${LLCOMMON_LIBRARIES} + ${PLUGIN_API_WINDOWS_LIBRARIES} +) + +add_dependencies(llmediaplugintest + copy_win_libs + SLPlugin + media_plugin_flash_activex + media_plugin_quicktime + media_plugin_webkit + media_plugin_awesomium + ${LLPLUGIN_LIBRARIES} + ${LLMESSAGE_LIBRARIES} + ${LLCOMMON_LIBRARIES} +) + +# turn off weird GLUI pragma +add_definitions(-DGLUI_NO_LIB_PRAGMA) + +if (DARWIN OR LINUX) + # glui.h contains code that triggers the "overloaded-virtual" warning in gcc. + set_source_files_properties(llmediaplugintest.cpp PROPERTIES COMPILE_FLAGS "-Wno-overloaded-virtual") +endif (DARWIN OR LINUX) + +# Gather build products of the various dependencies into the build directory for the testbed. + +get_target_property(BUILT_SLPLUGIN SLPlugin LOCATION) +add_custom_command(TARGET llmediaplugintest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BUILT_SLPLUGIN} ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/ + DEPENDS ${BUILT_SLPLUGIN} +) + +if (DARWIN OR WINDOWS) + get_target_property(BUILT_WEBKIT_PLUGIN media_plugin_webkit LOCATION) + add_custom_command(TARGET llmediaplugintest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BUILT_WEBKIT_PLUGIN} ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/ + DEPENDS ${BUILT_WEBKIT_PLUGIN} + ) + + get_target_property(BUILT_QUICKTIME_PLUGIN media_plugin_quicktime LOCATION) + add_custom_command(TARGET llmediaplugintest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BUILT_QUICKTIME_PLUGIN} ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/ + DEPENDS ${BUILT_QUICKTIME_PLUGIN} + ) + + get_target_property(BUILT_AWESOMIUM_PLUGIN media_plugin_awesomium LOCATION) + add_custom_command(TARGET llmediaplugintest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BUILT_AWESOMIUM_PLUGIN} ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/ + DEPENDS ${BUILT_AWESOMIUM_PLUGIN} + ) + + # copy over bookmarks file if llmediaplugintest gets built + get_target_property(BUILT_LLMEDIAPLUGINTEST llmediaplugintest LOCATION) + add_custom_command(TARGET llmediaplugintest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/bookmarks.txt ${CMAKE_CURRENT_BINARY_DIR}/ + DEPENDS ${BUILT_LLMEDIAPLUGINTEST} + ) + # also copy it to the build configuration directory, which is what the mac wants... + add_custom_command(TARGET llmediaplugintest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/bookmarks.txt ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/ + DEPENDS ${BUILT_LLMEDIAPLUGINTEST} + ) +endif (DARWIN OR WINDOWS) + +if (WINDOWS) + get_target_property(BUILT_FLASH_ACTIVEX_PLUGIN media_plugin_flash_activex LOCATION) + add_custom_command(TARGET llmediaplugintest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${BUILT_FLASH_ACTIVEX_PLUGIN} ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/ + DEPENDS ${BUILT_FLASH_ACTIVEX_PLUGIN} + ) + +endif (WINDOWS) + +if (DARWIN) + add_custom_command(TARGET llmediaplugintest POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../libraries/universal-darwin/lib_release/libllwebkitlib.dylib ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/ + DEPENDS ${CMAKE_SOURCE_DIR}/../libraries/universal-darwin/lib_release/libllwebkitlib.dylib + ) + + add_custom_command(TARGET llmediaplugintest POST_BUILD + COMMAND rsync -av --delete ${CMAKE_SOURCE_DIR}/../libraries/universal-darwin/lib_release/Awesomium.framework ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/ + DEPENDS ${CMAKE_SOURCE_DIR}/../libraries/universal-darwin/lib_release/Awesomium.framework + ) +endif (DARWIN) + + diff --git a/indra/test_apps/llplugintest/bookmarks.txt b/indra/test_apps/llplugintest/bookmarks.txt new file mode 100644 index 0000000000..3de56db532 --- /dev/null +++ b/indra/test_apps/llplugintest/bookmarks.txt @@ -0,0 +1,38 @@ +# format is description, url (don't put ',' chars in description :) +# if no ',' found, whole line is used for both description and url +(WK) Google Home Page,http://www.google.com +(WK) BBC News Home Page,http://news.bbc.co.uk +(WK) Second Life,http://secondlife.com +(WK) WebKit Home ,http://www.webkit.org +(WK) Yahoo News,http://news.yahoo.com +(WK) Canvas Paint (DHTML version of MS Paint),http://www.canvaspaint.org +(WK) DHTML Lemmings!,http://www.elizium.nu/scripts/lemmings/ +(WK) DHTML graphics demos,http://www.dhteumeuleu.com/ +(WK) Neat Javascript 3D,http://gyu.que.jp/jscloth/ +(QT) Local sample,file:///C|/Program Files/QuickTime/Sample.mov +(FLASH) Falling Bush,http://de.fishki.net/video/bush.swf +(FLASH) Bubble Shooter,http://www.wiicade.com/Data/72/game.swf +(FLASH) MAME,http://yvern.com/fMAME/testDecryptLoader.swf +(FLASH) Scribd doc,http://documents.scribd.com/ScribdViewer.swf?document_id=7470987&access_key=key-gemg40ut1qjepsgrx9y&page=&version=1&auto_size=true&viewMode= +(FLASH) GE Whiteboard,http://imagination3.com/pen_main.swf +(FLASH) DabbleBoard,http://www.dabbleboard.com/swf/db.swf +(FLASH) Countdown,http://itv.jaildog.com/sites/itv/ivbg/countdown.swf +(FLASH) Blockbusters,http://itv.jaildog.com/sites/itv/ivbg/blockbusters.swf +(FLASH) Clock Block,http://www.subliminalmessages.com/images/clockblock.swf +(FLASH) Devil Cat YouYube movie,http://www.youtube.com/swf/l.swf?swf=http%3A//s.ytimg.com/yt/swf/cps-vfl99456.swf&video_id=YRyK_1g4VWQ&rel=1&eurl=&iurl=http%3A//i2.ytimg.com/vi/YRyK_1g4VWQ/hqdefault.jpg&sk=70SiFZ9wC2NTIYkBXg_bK8xZB6OHmmfwC&fexp=900503%2C900037&fs=1&hl=en&autoplay=1&cr=US&avg_rating=4.56172839506&length_seconds=22&allow_ratings=1&allow_embed=1&title=devil%20cat +(QT) Movie - Watchmen Trailer,http://movies.apple.com/movies/wb/watchmen/watchmen-tlr2_480p.mov +(QT) Movie - Transformers - Revenge of the Fallen,http://movies.apple.com/movies/paramount/transformers2/transformersrevengeofthefallen-tlr1_h.320.mov +(QT) Movie - Terminator Salvation,http://movies.apple.com/movies/wb/terminatorsalvation/terminatorsalvation-tlr3_h.320.mov +(QT) Movie - Angels and Demons,http://movies.apple.com/movies/sony_pictures/angelsanddemons/angelsanddemons-video_h.320.mov +(QT) Movie - Sin City Trailer,http://movies.apple.com/movies/miramax/sin_city/sin_city_480.mov +(QT) Movie - The Incredibles Trailer,http://movies.apple.com/movies/disney/the_incredibles/the_incredibles-tlr_a480.mov +(QT) Movie - Streaming Apple Event,http://stream.qtv.apple.com/events/mar/0903lajkszg/m_090374535329zdwg_650_ref.mov +(QT) Movie - MPEG-4 from Amazon S3,http://s3.amazonaws.com/callum-linden/flashdemo/interactive_flash_demo.mp4 +(QT) Movie - Star Trek,http://movies.apple.com/movies/paramount/star_trek/startrek-tlr3_h.320.mov +(QT) Movie - Ice Age 3,http://movies.apple.com/movies/fox/ice_age_iii/iceage3-tlrd_h.320.mov +(QT) Movie - AstroBoy,http://movies.apple.com/movies/summit/astroboy/astroboy-tsr_h.320.mov +(QT) Movie - Ante Up,http://movies.apple.com/movies/independent/anteup/anteup_h.320.mov +(QT) Movie - Every Little Step,http://movies.apple.com/movies/sony/everylittlestep/everylittlestep-clip_h.320.mov +(QT) Movie - The Informers,http://movies.apple.com/movies/independent/theinformers/theinformers_h.320.mov +(QT) Animated GIF,http://upload.wikimedia.org/wikipedia/commons/4/44/Optical.greysquares.arp-animated.gif +(QT) Apple Text Descriptors,http://ubrowser.com/tmp/apple_text.txt diff --git a/indra/test_apps/llplugintest/llmediaplugintest.cpp b/indra/test_apps/llplugintest/llmediaplugintest.cpp new file mode 100644 index 0000000000..7a84ef83cb --- /dev/null +++ b/indra/test_apps/llplugintest/llmediaplugintest.cpp @@ -0,0 +1,2168 @@ +/** + * @file LLMediaPluginTest2.cpp + * @brief Primary test application for LLMedia (Separate Process) Plugin system + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "indra_constants.h" + +#include "llapr.h" +#include "llerrorcontrol.h" + +#include <math.h> +#include <iomanip> +#include <sstream> +#include <ctime> + +#include "llmediaplugintest.h" + +#if __APPLE__ + #include <GLUT/glut.h> +#else + #define FREEGLUT_STATIC + #include "GL/freeglut.h" + #define GLUI_FREEGLUT +#endif + +#if LL_WINDOWS +#pragma warning(disable: 4263) +#pragma warning(disable: 4264) +#endif +#include "glui.h" + + +LLMediaPluginTest* gApplication = 0; +static void gluiCallbackWrapper( int control_id ); + +//////////////////////////////////////////////////////////////////////////////// +// +static bool isTexture( GLuint texture ) +{ + bool result = false; + + // glIsTexture will sometimes return false for real textures... do this instead. + if(texture != 0) + result = true; + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +mediaPanel::mediaPanel() +{ + mMediaTextureHandle = 0; + mPickTextureHandle = 0; + mMediaSource = NULL; + mPickTexturePixels = NULL; +} + +//////////////////////////////////////////////////////////////////////////////// +// +mediaPanel::~mediaPanel() +{ + // delete OpenGL texture handles + if ( isTexture( mPickTextureHandle ) ) + { + std::cerr << "remMediaPanel: deleting pick texture " << mPickTextureHandle << std::endl; + glDeleteTextures( 1, &mPickTextureHandle ); + mPickTextureHandle = 0; + } + + if ( isTexture( mMediaTextureHandle ) ) + { + std::cerr << "remMediaPanel: deleting media texture " << mMediaTextureHandle << std::endl; + glDeleteTextures( 1, &mMediaTextureHandle ); + mMediaTextureHandle = 0; + } + + if(mPickTexturePixels) + { + delete mPickTexturePixels; + } + + if(mMediaSource) + { + delete mMediaSource; + } + +} + +//////////////////////////////////////////////////////////////////////////////// +// +LLMediaPluginTest::LLMediaPluginTest( int app_window, int window_width, int window_height ) : + mVersionMajor( 2 ), + mVersionMinor( 0 ), + mVersionPatch( 0 ), + mMaxPanels( 16 ), + mViewportAspect( 0 ), + mAppWindow( app_window ), + mCurMouseX( 0 ), + mCurMouseY( 0 ), + mFuzzyMedia( true ), + mSelectedPanel( 0 ), + mMediaBrowserControlEnableCookies( 0 ), + mMediaBrowserControlBackButton( 0 ), + mMediaBrowserControlForwardButton( 0 ), + mMediaTimeControlVolume( 100 ), + mMediaTimeControlSeekSeconds( 0 ), + mGluiMediaTimeControlWindowFlag( true ), + mGluiMediaBrowserControlWindowFlag( true ), + mMediaBrowserControlBackButtonFlag( true ), + mMediaBrowserControlForwardButtonFlag( true ), + mHomeWebUrl( "http://www.google.com/" ) +{ + // debugging spam + std::cout << std::endl << " GLUT version: " << "3.7.6" << std::endl; // no way to get real version from GLUT + std::cout << std::endl << " GLUI version: " << GLUI_Master.get_version() << std::endl; + std::cout << std::endl << "Media Plugin Test version: " << mVersionMajor << "." << mVersionMinor << "." << mVersionPatch << std::endl; + + // bookmark title + mBookmarks.push_back( std::pair< std::string, std::string >( "--- Bookmarks ---", "" ) ); + + // insert hardcoded URLs here as required for testing + //mBookmarks.push_back( std::pair< std::string, std::string >( "description", "url" ) ); + + // read bookmarks from file. + // note: uses command in ./CmakeLists.txt which copies bookmmarks file from source directory + // to app directory (WITHOUT build configuration dir) (this is cwd in Windows within MSVC) + // For example, test_apps\llplugintest and not test_apps\llplugintest\Release + // This may need to be changed for Mac/Linux builds. + // See https://jira.lindenlab.com/browse/DEV-31350 for large list of media URLs from AGNI + const std::string bookmarks_filename( "bookmarks.txt" ); + std::ifstream file_handle( bookmarks_filename.c_str() ); + if ( file_handle.is_open() ) + { + std::cout << "Reading bookmarks for test" << std::endl; + while( ! file_handle.eof() ) + { + std::string line; + std::getline( file_handle, line ); + if ( file_handle.eof() ) + break; + + if ( line.substr( 0, 1 ) != "#" ) + { + size_t comma_pos = line.find_first_of( ',' ); + if ( comma_pos != std::string::npos ) + { + std::string description = line.substr( 0, comma_pos ); + std::string url = line.substr( comma_pos + 1 ); +#if LL_DARWIN || LL_LINUX + // Don't load flash movies on mac or linux yet. + if ( url.find( ".swf" ) != std::string::npos ) + { + continue; + } +#endif + mBookmarks.push_back( std::pair< std::string, std::string >( description, url ) ); + } + else + { + mBookmarks.push_back( std::pair< std::string, std::string >( line, line ) ); + }; + }; + }; + std::cout << "Read " << mBookmarks.size() << " bookmarks" << std::endl; + } + else + { + std::cout << "Unable to read bookmarks from file: " << bookmarks_filename << std::endl; + }; + + // initialize linden lab APR module + ll_init_apr(); + + // Set up llerror logging + { + LLError::initForApplication("."); + LLError::setDefaultLevel(LLError::LEVEL_INFO); +// LLError::setTagLevel("Plugin", LLError::LEVEL_DEBUG); + } + + // lots of randomness in this app + srand( ( unsigned int )time( 0 ) ); + + // build GUI + makeChrome(); + + // OpenGL initialilzation + glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); + glClearDepth( 1.0f ); + glEnable( GL_DEPTH_TEST ); + glEnable( GL_COLOR_MATERIAL ); + glColorMaterial( GL_FRONT, GL_AMBIENT_AND_DIFFUSE ); + glDepthFunc( GL_LEQUAL ); + glEnable( GL_TEXTURE_2D ); + glDisable( GL_BLEND ); + glColor3f( 1.0f, 1.0f, 1.0f ); + glPixelStorei( GL_UNPACK_ALIGNMENT, 1 ); + glPixelStorei( GL_UNPACK_ROW_LENGTH, 0 ); + + // start with a sane view + resetView(); + + // initial media panel + const int num_initial_panels = 4; + for( int i = 0; i < num_initial_panels; ++i ) + { + //addMediaPanel( mBookmarks[ rand() % ( mBookmarks.size() - 1 ) + 1 ].second ); + }; + + // always add a Web panel for testing + addMediaPanel( "http://www.google.com" ); + //addMediaPanel( "http://www.wiicade.com/Data/72/game.swf" ); + //addMediaPanel( "http://movies.apple.com/movies/wb/watchmen/watchmen-tlr2_480p.mov" ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +LLMediaPluginTest::~LLMediaPluginTest() +{ + // delete all media panels + for( int i = 0; i < (int)mMediaPanels.size(); ++i ) + { + remMediaPanel( mMediaPanels[ i ] ); + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::reshape( int width, int height ) +{ + // update viewport (the active window inside the chrome) + int viewport_x, viewport_y; + int viewport_height, viewport_width; + GLUI_Master.get_viewport_area( &viewport_x, &viewport_y, &viewport_width, &viewport_height ); + mViewportAspect = (float)( viewport_width ) / (float)( viewport_height ); + glViewport( viewport_x, viewport_y, viewport_width, viewport_height ); + + // save these as we'll need them later + mWindowWidth = width; + mWindowHeight = height; + + // adjust size of URL bar so it doesn't get clipped + mUrlEdit->set_w( mWindowWidth - 360 ); + + // GLUI requires this + if ( glutGetWindow() != mAppWindow ) + glutSetWindow( mAppWindow ); + + // trigger re-display + glutPostRedisplay(); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::bindTexture(GLuint texture, GLint row_length, GLint alignment) +{ + glEnable( GL_TEXTURE_2D ); + +// std::cerr << "binding texture " << texture << std::endl; + + glBindTexture( GL_TEXTURE_2D, texture ); + glPixelStorei( GL_UNPACK_ROW_LENGTH, row_length ); + glPixelStorei( GL_UNPACK_ALIGNMENT, alignment ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +bool LLMediaPluginTest::checkGLError(char *name) +{ + bool result = false; + GLenum error = glGetError(); + + if(error != GL_NO_ERROR) + { + // For some reason, glGenTextures is returning GL_INVALID_VALUE... + std::cout << name << " ERROR 0x" << std::hex << error << std::dec << std::endl; + result = true; + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::drawGeometry( int panel ) +{ + // texture coordinates for each panel + GLfloat non_opengl_texture_coords[ 8 ] = { 0.0f, 1.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f }; + GLfloat opengl_texture_coords[ 8 ] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; + + GLfloat *texture_coords = mMediaPanels[ panel ]->mAppTextureCoordsOpenGL?opengl_texture_coords:non_opengl_texture_coords; + + // base coordinates for each panel + GLfloat base_vertex_pos[ 8 ] = { 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f }; + + // calculate posiitons + const int num_panels = (int)mMediaPanels.size(); + const int num_rows = (int)sqrt( (float)num_panels ); + const int num_cols = num_panels / num_rows; + const int panel_x = ( panel / num_rows ); + const int panel_y = ( panel % num_rows ); + + const float spacing = 0.1f; + const GLfloat offset_x = num_cols * ( 1.0 + spacing ) / 2; + const GLfloat offset_y = num_rows * ( 1.0 + spacing ) / 2; + + // Adjust for media aspect ratios + { + float aspect = 1.0f; + + if(mMediaPanels[ panel ]->mMediaHeight != 0) + { + aspect = (float)mMediaPanels[ panel ]->mMediaWidth / (float)mMediaPanels[ panel ]->mMediaHeight; + } + + if(aspect > 1.0f) + { + // media is wider than it is high -- adjust the top and bottom in + for( int corner = 0; corner < 4; ++corner ) + { + float temp = base_vertex_pos[corner * 2 + 1]; + + if(temp < 0.5f) + temp += 0.5 - (0.5f / aspect); + else + temp -= 0.5 - (0.5f / aspect); + + base_vertex_pos[corner * 2 + 1] = temp; + } + } + else if(aspect < 1.0f) + { + // media is higher than it is wide -- adjust the left and right sides in + for( int corner = 0; corner < 4; ++corner ) + { + float temp = base_vertex_pos[corner * 2]; + + if(temp < 0.5f) + temp += 0.5f - (0.5f * aspect); + else + temp -= 0.5f - (0.5f * aspect); + + base_vertex_pos[corner * 2] = temp; + } + } + } + + glBegin( GL_QUADS ); + for( int corner = 0; corner < 4; ++corner ) + { + glTexCoord2f( texture_coords[ corner * 2 ], texture_coords[ corner * 2 + 1 ] ); + GLfloat x = base_vertex_pos[ corner * 2 ] + panel_x * ( 1.0 + spacing ) - offset_x + spacing / 2.0f; + GLfloat y = base_vertex_pos[ corner * 2 + 1 ] + panel_y * ( 1.0 + spacing ) - offset_y + spacing / 2.0f; + + glVertex3f( x, y, 0.0f ); + }; + glEnd(); +} + +////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::startPanelHighlight( float red, float green, float blue, float line_width ) +{ + glPushAttrib( GL_ALL_ATTRIB_BITS ); + glEnable( GL_POLYGON_OFFSET_FILL ); + glPolygonOffset( -2.5f, -2.5f ); + glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); + glLineWidth( line_width ); + glColor3f( red, green, blue ); + glDisable( GL_TEXTURE_2D ); +} + +////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::endPanelHighlight() +{ + glPopAttrib(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::draw( int draw_type ) +{ + for( int panel = 0; panel < (int)mMediaPanels.size(); ++panel ) + { + // drawing pick texture + if ( draw_type == DrawTypePickTexture ) + { + // only bother with pick if we have something to render + // Actually, we need to pick even if we're not ready to render. + // Otherwise you can't select and remove a panel which has gone bad. +// if ( mMediaPanels[ panel ]->mReadyToRender ) + { + glMatrixMode( GL_TEXTURE ); + glPushMatrix(); + + // pick texture is a power of 2 so no need to scale + glLoadIdentity(); + + // bind to media texture + glLoadIdentity(); + bindTexture( mMediaPanels[ panel ]->mPickTextureHandle ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + + // draw geometry using pick texture + drawGeometry( panel ); + + glMatrixMode( GL_TEXTURE ); + glPopMatrix(); + }; + } + else + if ( draw_type == DrawTypeMediaTexture ) + { + bool texture_valid = false; + bool plugin_exited = false; + + if(mMediaPanels[ panel ]->mMediaSource) + { + texture_valid = mMediaPanels[ panel ]->mMediaSource->textureValid(); + plugin_exited = mMediaPanels[ panel ]->mMediaSource->isPluginExited(); + } + + // save texture matrix (changes for each panel) + glMatrixMode( GL_TEXTURE ); + glPushMatrix(); + + // only process texture if the media is ready to draw + // (we still want to draw the geometry) + if ( mMediaPanels[ panel ]->mReadyToRender && texture_valid ) + { + // bind to media texture + bindTexture( mMediaPanels[ panel ]->mMediaTextureHandle ); + + if ( mFuzzyMedia ) + { + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + } + else + { + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + } + + // scale to fit panel + glScalef( mMediaPanels[ panel ]->mTextureScaleX, + mMediaPanels[ panel ]->mTextureScaleY, + 1.0f ); + }; + + float intensity = plugin_exited?0.25f:1.0f; + + // highlight the selected panel + if ( mSelectedPanel && ( mMediaPanels[ panel ]->mId == mSelectedPanel->mId ) ) + { + startPanelHighlight( intensity, intensity, 0.0f, 5.0f ); + drawGeometry( panel ); + endPanelHighlight(); + } + else + // this panel not able to render yet since it + // doesn't have enough information + if ( !mMediaPanels[ panel ]->mReadyToRender ) + { + startPanelHighlight( intensity, 0.0f, 0.0f, 2.0f ); + drawGeometry( panel ); + endPanelHighlight(); + } + else + // just display a border around the media + { + startPanelHighlight( 0.0f, intensity, 0.0f, 2.0f ); + drawGeometry( panel ); + endPanelHighlight(); + }; + + if ( mMediaPanels[ panel ]->mReadyToRender && texture_valid ) + { + // draw visual geometry + drawGeometry( panel ); + } + + // restore texture matrix (changes for each panel) + glMatrixMode( GL_TEXTURE ); + glPopMatrix(); + }; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::display() +{ + // GLUI requires this + if ( glutGetWindow() != mAppWindow ) + glutSetWindow( mAppWindow ); + + // start with a clean slate + glClearColor( 0.0f, 0.0f, 0.0f, 1.0f ); + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + + // set up OpenGL view + glMatrixMode( GL_PROJECTION ); + glLoadIdentity(); + glFrustum( -mViewportAspect * 0.04f, mViewportAspect * 0.04f, -0.04f, 0.04f, 0.1f, 50.0f ); + glMatrixMode( GL_MODELVIEW ); + glLoadIdentity(); + glTranslatef( 0.0, 0.0, 0.0f ); + glTranslatef( mViewPos[ 0 ], mViewPos[ 1 ], -mViewPos[ 2 ] ); + glMultMatrixf( mViewRotation ); + + // draw pick texture + draw( DrawTypePickTexture ); + + // read colors and get coordinate values + glReadPixels( mCurMouseX, mCurMouseY, 1, 1, GL_RGB, GL_UNSIGNED_BYTE, mPixelReadColor ); + + // clear the pick render (otherwise it may depth-fight with the textures rendered later) + glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); + + // draw visible geometry + draw( DrawTypeMediaTexture ); + + glutSwapBuffers(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::idle() +{ +// checkGLError("LLMediaPluginTest::idle"); + + // GLUI requires this + if ( glutGetWindow() != mAppWindow ) + glutSetWindow( mAppWindow ); + + // random creation/destruction of panels enabled? + const time_t panel_timeout_time = 5; + if ( mRandomPanelCount ) + { + // time for a change + static time_t last_panel_time = 0; + if ( time( NULL ) - last_panel_time > panel_timeout_time ) + { + if ( rand() % 2 == 0 ) + { + if ( mMediaPanels.size() < 16 ) + { + std::cout << "Randomly adding new panel" << std::endl; + addMediaPanel( mBookmarks[ rand() % ( mBookmarks.size() - 1 ) + 1 ].second ); + }; + } + else + { + if ( mMediaPanels.size() > 0 ) + { + std::cout << "Deleting selected panel" << std::endl; + remMediaPanel( mSelectedPanel ); + }; + }; + time( &last_panel_time ); + }; + }; + + // random selection of bookmarks enabled? + const time_t bookmark_timeout_time = 5; + if ( mRandomBookmarks ) + { + // time for a change + static time_t last_bookmark_time = 0; + if ( time( NULL ) - last_bookmark_time > bookmark_timeout_time ) + { + // go to a different random bookmark on each panel + for( int panel = 0; panel < (int)mMediaPanels.size(); ++panel ) + { + std::string uri = mBookmarks[ rand() % ( mBookmarks.size() - 1 ) + 1 ].second; + + std::cout << "Random: navigating to : " << uri << std::endl; + + std::string mime_type = mimeTypeFromUrl( uri ); + + if ( mime_type != mMediaPanels[ panel ]->mMimeType ) + { + replaceMediaPanel( mMediaPanels[ panel ], uri ); + } + else + { + mMediaPanels[ panel ]->mMediaSource->loadURI( uri ); + mMediaPanels[ panel ]->mMediaSource->start(); + }; + }; + + time( &last_bookmark_time ); + }; + }; + + // update UI + if ( mSelectedPanel ) + { + // set volume based on slider if we have time media +// if ( mGluiMediaTimeControlWindowFlag ) +// { +// mSelectedPanel->mMediaSource->setVolume( (float)mMediaTimeControlVolume / 100.0f ); +// }; + + // NOTE: it is absurd that we need cache the state of GLUI controls + // but enabling/disabling controls drags framerate from 500+ + // down to 15. Not a problem for plugin system - only this test + // enable/disable time based UI controls based on type of plugin + if ( mSelectedPanel->mMediaSource->pluginSupportsMediaTime() ) + { + if ( ! mGluiMediaTimeControlWindowFlag ) + { + mGluiMediaTimeControlWindow->enable(); + mGluiMediaTimeControlWindowFlag = true; + }; + } + else + { + if ( mGluiMediaTimeControlWindowFlag ) + { + mGluiMediaTimeControlWindow->disable(); + mGluiMediaTimeControlWindowFlag = false; + }; + }; + + // enable/disable browser based UI controls based on type of plugin + if ( mSelectedPanel->mMediaSource->pluginSupportsMediaBrowser() ) + { + if ( ! mGluiMediaBrowserControlWindowFlag ) + { + mGluiMediaBrowserControlWindow->enable(); + mGluiMediaBrowserControlWindowFlag = true; + }; + } + else + { + if ( mGluiMediaBrowserControlWindowFlag ) + { + mGluiMediaBrowserControlWindow->disable(); + mGluiMediaBrowserControlWindowFlag = false; + }; + }; + + // enable/disable browser back button depending on browser history + if ( mSelectedPanel->mMediaSource->getHistoryBackAvailable() ) + { + if ( ! mMediaBrowserControlBackButtonFlag ) + { + mMediaBrowserControlBackButton->enable(); + mMediaBrowserControlBackButtonFlag = true; + }; + } + else + { + if ( mMediaBrowserControlBackButtonFlag ) + { + mMediaBrowserControlBackButton->disable(); + mMediaBrowserControlBackButtonFlag = false; + }; + }; + + // enable/disable browser forward button depending on browser history + if ( mSelectedPanel->mMediaSource->getHistoryForwardAvailable() ) + { + if ( ! mMediaBrowserControlForwardButtonFlag ) + { + mMediaBrowserControlForwardButton->enable(); + mMediaBrowserControlForwardButtonFlag = true; + }; + } + else + { + if ( mMediaBrowserControlForwardButtonFlag ) + { + mMediaBrowserControlForwardButton->disable(); + mMediaBrowserControlForwardButtonFlag = false; + }; + }; + + // NOTE: This is *very* slow and not worth optimising + updateStatusBar(); + }; + + // update all the panels + for( int panel_index = 0; panel_index < (int)mMediaPanels.size(); ++panel_index ) + { + mediaPanel *panel = mMediaPanels[ panel_index ]; + + // call plugins idle function so it can potentially update itself + panel->mMediaSource->idle(); + + // update each media panel + updateMediaPanel( panel ); + + LLRect dirty_rect; + if ( ! panel->mMediaSource->textureValid() ) + { + //std::cout << "texture invalid, skipping update..." << std::endl; + } + else + if ( panel && + ( panel->mMediaWidth != panel->mMediaSource->getWidth() || + panel->mMediaHeight != panel->mMediaSource->getHeight() ) ) + { + //std::cout << "Resize in progress, skipping update..." << std::endl; + } + else + if ( panel->mMediaSource->getDirty( &dirty_rect ) ) + { + const unsigned char* pixels = panel->mMediaSource->getBitsData(); + if ( pixels && isTexture(panel->mMediaTextureHandle)) + { + int x_offset = dirty_rect.mLeft; + int y_offset = dirty_rect.mBottom; + int width = dirty_rect.mRight - dirty_rect.mLeft; + int height = dirty_rect.mTop - dirty_rect.mBottom; + + if((dirty_rect.mRight <= panel->mTextureWidth) && (dirty_rect.mTop <= panel->mTextureHeight)) + { + // Offset the pixels pointer properly + pixels += ( y_offset * panel->mMediaSource->getTextureDepth() * panel->mMediaSource->getBitsWidth() ); + pixels += ( x_offset * panel->mMediaSource->getTextureDepth() ); + + // set up texture + bindTexture( panel->mMediaTextureHandle, panel->mMediaSource->getBitsWidth() ); + if ( mFuzzyMedia ) + { + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); + } + else + { + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); + glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); + }; + + checkGLError("glTexParameteri"); + + if(panel->mMediaSource->getTextureFormatSwapBytes()) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 1); + checkGLError("glPixelStorei"); + } + + // draw portion that changes into texture + glTexSubImage2D( GL_TEXTURE_2D, 0, + x_offset, + y_offset, + width, + height, + panel->mMediaSource->getTextureFormatPrimary(), + panel->mMediaSource->getTextureFormatType(), + pixels ); + + if(checkGLError("glTexSubImage2D")) + { + std::cerr << " panel ID=" << panel->mId << std::endl; + std::cerr << " texture size = " << panel->mTextureWidth << " x " << panel->mTextureHeight << std::endl; + std::cerr << " media size = " << panel->mMediaWidth << " x " << panel->mMediaHeight << std::endl; + std::cerr << " dirty rect = " << dirty_rect.mLeft << ", " << dirty_rect.mBottom << ", " << dirty_rect.mRight << ", " << dirty_rect.mTop << std::endl; + std::cerr << " texture width = " << panel->mMediaSource->getBitsWidth() << std::endl; + std::cerr << " format primary = 0x" << std::hex << panel->mMediaSource->getTextureFormatPrimary() << std::dec << std::endl; + std::cerr << " format type = 0x" << std::hex << panel->mMediaSource->getTextureFormatType() << std::dec << std::endl; + std::cerr << " pixels = " << (void*)pixels << std::endl; + } + + if(panel->mMediaSource->getTextureFormatSwapBytes()) + { + glPixelStorei(GL_UNPACK_SWAP_BYTES, 0); + checkGLError("glPixelStorei"); + } + + panel->mMediaSource->resetDirty(); + + panel->mReadyToRender = true; + } + else + { + std::cerr << "dirty rect is outside current media size, skipping update" << std::endl; + } + }; + }; + }; + + // GLUI requires this + if ( glutGetWindow() != mAppWindow ) + glutSetWindow( mAppWindow ); + + // trigger re-display + glutPostRedisplay(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::windowPosToTexturePos( int window_x, int window_y, + int& media_x, int& media_y, + int& id ) +{ + if ( ! mSelectedPanel ) + { + media_x = 0; + media_y = 0; + id = 0; + return; + }; + + // record cursor poisiton for a readback next frame + mCurMouseX = window_x; + // OpenGL app == coordinate system this way + // NOTE: unrelated to settings in plugin - this + // is just for this app + mCurMouseY = mWindowHeight - window_y; + + // extract x (0..1023, y (0..1023) and id (0..15) from RGB components + unsigned long pixel_read_color_bits = ( mPixelReadColor[ 0 ] << 16 ) | ( mPixelReadColor[ 1 ] << 8 ) | mPixelReadColor[ 2 ]; + int texture_x = pixel_read_color_bits & 0x3ff; + int texture_y = ( pixel_read_color_bits >> 10 ) & 0x3ff; + id = ( pixel_read_color_bits >> 20 ) & 0x0f; + + // scale to size of media (1024 because we use 10 bits for X and Y from 24) + media_x = (int)( ( (float)mSelectedPanel->mMediaWidth * (float)texture_x ) / 1024.0f ); + media_y = (int)( ( (float)mSelectedPanel->mMediaHeight * (float)texture_y ) / 1024.0f ); + + // we assume the plugin uses an inverted coordinate scheme like OpenGL + // if not, the plugin code inverts the Y coordinate for us - we don't need to + media_y = mSelectedPanel->mMediaHeight - media_y; + + if ( media_x > 0 && media_y > 0 ) + { + //std::cout << " mouse coords: " << mCurMouseX << " x " << mCurMouseY << " and id = " << id << std::endl; + //std::cout << "raw texture coords: " << texture_x << " x " << texture_y << " and id = " << id << std::endl; + //std::cout << " media coords: " << media_x << " x " << media_y << " and id = " << id << std::endl; + //std::cout << std::endl; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::selectPanelById( int id ) +{ + for( int panel = 0; panel < (int)mMediaPanels.size(); ++panel ) + { + if ( mMediaPanels[ panel ]->mId == id ) + { + selectPanel(mMediaPanels[ panel ]); + return; + }; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::selectPanel( mediaPanel* panel ) +{ + if( mSelectedPanel == panel ) + return; + + // turn off volume before we delete it + if( mSelectedPanel && mSelectedPanel->mMediaSource ) + { + mSelectedPanel->mMediaSource->setVolume( 0.0f ); + mSelectedPanel->mMediaSource->setPriority( LLPluginClassMedia::PRIORITY_LOW ); + }; + + mSelectedPanel = panel; + + if( mSelectedPanel && mSelectedPanel->mMediaSource ) + { + mSelectedPanel->mMediaSource->setVolume( (float)mMediaTimeControlVolume / 100.0f ); + mSelectedPanel->mMediaSource->setPriority( LLPluginClassMedia::PRIORITY_NORMAL ); + + if(!mSelectedPanel->mStartUrl.empty()) + { + mUrlEdit->set_text(const_cast<char*>(mSelectedPanel->mStartUrl.c_str()) ); + } + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +mediaPanel* LLMediaPluginTest::findMediaPanel( LLPluginClassMedia* source ) +{ + mediaPanel *result = NULL; + + for( int panel = 0; panel < (int)mMediaPanels.size(); ++panel ) + { + if ( mMediaPanels[ panel ]->mMediaSource == source ) + { + result = mMediaPanels[ panel ]; + } + } + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::navigateToNewURI( std::string uri ) +{ + if ( uri.length() ) + { + std::string mime_type = mimeTypeFromUrl( uri ); + + if ( !mSelectedPanel->mMediaSource->isPluginExited() && (mime_type == mSelectedPanel->mMimeType) ) + { + std::cout << "MIME type is the same" << std::endl; + mSelectedPanel->mMediaSource->loadURI( uri ); + mSelectedPanel->mMediaSource->start(); + mBookmarkList->do_selection( 0 ); + } + else + { + std::cout << "MIME type changed or plugin had exited" << std::endl; + replaceMediaPanel( mSelectedPanel, uri ); + mBookmarkList->do_selection( 0 ); + } + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::initUrlHistory( std::string uris ) +{ + if ( uris.length() > 0 ) + { + std::cout << "init URL : " << uris << std::endl; + LLSD historySD; + + char *cstr, *p; + cstr = new char[uris.size()+1]; + strcpy(cstr, uris.c_str()); + const char *DELIMS = " ,;"; + p = strtok(cstr, DELIMS); + while (p != NULL) { + historySD.insert(0, p); + p = strtok(NULL, DELIMS); + } + mSelectedPanel->mMediaSource->initializeUrlHistory(historySD); + delete[] cstr; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::gluiCallback( int control_id ) +{ + if ( control_id == mIdBookmarks ) + { + std::string uri = mBookmarks[ mSelBookmark ].second; + + navigateToNewURI( uri ); + } + else + if ( control_id == mIdUrlEdit) + { + std::string uri = mUrlEdit->get_text(); + + navigateToNewURI( uri ); + } + else + if ( control_id == mIdUrlInitHistoryEdit ) + { + std::string uri = mUrlInitHistoryEdit->get_text(); + + initUrlHistory( uri ); + } + else + if ( control_id == mIdControlAddPanel ) + { + addMediaPanel( mBookmarks[ rand() % ( mBookmarks.size() - 1 ) + 1 ].second ); + } + else + if ( control_id == mIdControlRemPanel ) + { + remMediaPanel( mSelectedPanel ); + } + else + if ( control_id == mIdDisableTimeout ) + { + // Set the "disable timeout" flag for all active plugins. + for( int i = 0; i < (int)mMediaPanels.size(); ++i ) + { + mMediaPanels[ i ]->mMediaSource->setDisableTimeout(mDisableTimeout); + } + } + else + if ( control_id == mIdControlCrashPlugin ) + { + // send message to plugin and ask it to crash + // (switch out for ReleaseCandidate version :) ) + if(mSelectedPanel && mSelectedPanel->mMediaSource) + { + mSelectedPanel->mMediaSource->crashPlugin(); + } + } + else + if ( control_id == mIdControlHangPlugin ) + { + // send message to plugin and ask it to hang + // (switch out for ReleaseCandidate version :) ) + if(mSelectedPanel && mSelectedPanel->mMediaSource) + { + mSelectedPanel->mMediaSource->hangPlugin(); + } + } + else + if ( control_id == mIdControlExitApp ) + { + // text for exiting plugin system cleanly + delete this; // clean up + exit( 0 ); + } + else + if ( control_id == mIdMediaTimeControlPlay ) + { + if ( mSelectedPanel ) + { + mSelectedPanel->mMediaSource->setLoop( false ); + mSelectedPanel->mMediaSource->start(); + }; + } + else + if ( control_id == mIdMediaTimeControlLoop ) + { + if ( mSelectedPanel ) + { + mSelectedPanel->mMediaSource->setLoop( true ); + mSelectedPanel->mMediaSource->start(); + }; + } + else + if ( control_id == mIdMediaTimeControlPause ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->pause(); + } + else + if ( control_id == mIdMediaTimeControlStop ) + { + if ( mSelectedPanel ) + { + mSelectedPanel->mMediaSource->stop(); + }; + } + else + if ( control_id == mIdMediaTimeControlSeek ) + { + if ( mSelectedPanel ) + { + // get value from spinner + float seconds_to_seek = mMediaTimeControlSeekSeconds; + mSelectedPanel->mMediaSource->seek( seconds_to_seek ); + mSelectedPanel->mMediaSource->start(); + }; + } + else + if ( control_id == mIdMediaTimeControlRewind ) + { + if ( mSelectedPanel ) + { + mSelectedPanel->mMediaSource->setLoop( false ); + mSelectedPanel->mMediaSource->start(-2.0f); + }; + } + else + if ( control_id == mIdMediaTimeControlFastForward ) + { + if ( mSelectedPanel ) + { + mSelectedPanel->mMediaSource->setLoop( false ); + mSelectedPanel->mMediaSource->start(2.0f); + }; + } + else + if ( control_id == mIdMediaBrowserControlBack ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->browse_back(); + } + else + if ( control_id == mIdMediaBrowserControlStop ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->browse_stop(); + } + else + if ( control_id == mIdMediaBrowserControlForward ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->browse_forward(); + } + else + if ( control_id == mIdMediaBrowserControlHome ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->loadURI( mHomeWebUrl ); + } + else + if ( control_id == mIdMediaBrowserControlReload ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->browse_reload( true ); + } + else + if ( control_id == mIdMediaBrowserControlClearCache ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->clear_cache(); + } + else + if ( control_id == mIdMediaBrowserControlClearCookies ) + { + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->clear_cookies(); + } + else + if ( control_id == mIdMediaBrowserControlEnableCookies ) + { + if ( mSelectedPanel ) + { + if ( mMediaBrowserControlEnableCookies ) + { + mSelectedPanel->mMediaSource->enable_cookies( true ); + } + else + { + mSelectedPanel->mMediaSource->enable_cookies( false ); + } + }; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::keyboard( int key ) +{ + //if ( key == 'a' || key == 'A' ) + // addMediaPanel( mBookmarks[ rand() % ( mBookmarks.size() - 1 ) + 1 ].second ); + //else + //if ( key == 'r' || key == 'R' ) + // remMediaPanel( mSelectedPanel ); + //else + //if ( key == 'd' || key == 'D' ) + // dumpPanelInfo(); + //else + if ( key == 27 ) + { + std::cout << "Application finished - exiting..." << std::endl; + delete this; + exit( 0 ); + }; + + mSelectedPanel->mMediaSource->keyEvent( LLPluginClassMedia::KEY_EVENT_DOWN, key, 0 ); + mSelectedPanel->mMediaSource->keyEvent( LLPluginClassMedia::KEY_EVENT_UP, key, 0 ); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::mouseButton( int button, int state, int x, int y ) +{ + if ( button == GLUT_LEFT_BUTTON ) + { + if ( state == GLUT_DOWN ) + { + int media_x, media_y, id; + windowPosToTexturePos( x, y, media_x, media_y, id ); + + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->mouseEvent( LLPluginClassMedia::MOUSE_EVENT_DOWN, media_x, media_y, 0 ); + } + else + if ( state == GLUT_UP ) + { + int media_x, media_y, id; + windowPosToTexturePos( x, y, media_x, media_y, id ); + + // only select a panel if we're on a panel + // (HACK: strictly speaking this rules out clicking on + // the origin of a panel but that's very unlikely) + if ( media_x > 0 && media_y > 0 ) + { + selectPanelById( id ); + + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->mouseEvent( LLPluginClassMedia::MOUSE_EVENT_UP, media_x, media_y, 0 ); + }; + }; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::mousePassive( int x, int y ) +{ + int media_x, media_y, id; + windowPosToTexturePos( x, y, media_x, media_y, id ); + + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->mouseEvent( LLPluginClassMedia::MOUSE_EVENT_MOVE, media_x, media_y, 0 ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::mouseMove( int x, int y ) +{ + int media_x, media_y, id; + windowPosToTexturePos( x, y, media_x, media_y, id ); + + if ( mSelectedPanel ) + mSelectedPanel->mMediaSource->mouseEvent( LLPluginClassMedia::MOUSE_EVENT_MOVE, media_x, media_y, 0 ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::makeChrome() +{ + // IDs used by GLUI + int start_id = 0x1000; + + // right side window - geometry manipulators +#if __APPLE__ + // the Apple GLUT implementation doesn't seem to set the graphic offset of subwindows correctly when they overlap in certain ways. + // Use a separate controls window in this case. + // GLUI window at right containing manipulation controls and other buttons + int x = glutGet(GLUT_WINDOW_X) + glutGet(GLUT_WINDOW_WIDTH) + 4; + int y = glutGet(GLUT_WINDOW_Y); + GLUI* right_glui_window = GLUI_Master.create_glui( "", 0, x, y ); +#else + GLUI* right_glui_window = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_RIGHT ); +#endif + mViewRotationCtrl = right_glui_window->add_rotation( "Rotation", mViewRotation ); + mViewTranslationCtrl = right_glui_window->add_translation( "Translate", GLUI_TRANSLATION_XY, mViewPos ); + mViewTranslationCtrl->set_speed( 0.01f ); + mViewScaleCtrl = right_glui_window->add_translation( "Scale", GLUI_TRANSLATION_Z, &mViewPos[ 2 ] ); + mViewScaleCtrl->set_speed( 0.05f ); + right_glui_window->set_main_gfx_window( mAppWindow ); + + // right side window - app controls + mIdControlAddPanel = start_id++; + right_glui_window->add_statictext( "" ); + right_glui_window->add_separator(); + right_glui_window->add_statictext( "" ); + right_glui_window->add_button( "Add panel", mIdControlAddPanel, gluiCallbackWrapper ); + right_glui_window->add_statictext( "" ); + mIdControlRemPanel = start_id++; + right_glui_window->add_button( "Rem panel", mIdControlRemPanel, gluiCallbackWrapper ); + right_glui_window->add_statictext( "" ); + right_glui_window->add_separator(); + right_glui_window->add_statictext( "" ); + mIdControlCrashPlugin = start_id++; + right_glui_window->add_button( "Crash plugin", mIdControlCrashPlugin, gluiCallbackWrapper ); + mIdControlHangPlugin = start_id++; + right_glui_window->add_button( "Hang plugin", mIdControlHangPlugin, gluiCallbackWrapper ); + + right_glui_window->add_statictext( "" ); + right_glui_window->add_separator(); + right_glui_window->add_statictext( "" ); + mIdControlExitApp = start_id++; + right_glui_window->add_button( "Exit app", mIdControlExitApp, gluiCallbackWrapper ); + + //// top window - holds bookmark UI + mIdBookmarks = start_id++; + mSelBookmark = 0; + GLUI* glui_window_top = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_TOP ); + mBookmarkList = glui_window_top->add_listbox( "", &mSelBookmark, mIdBookmarks, gluiCallbackWrapper ); + // only add the first 50 bookmarks - list can be very long sometimes (30,000+) + // when testing list of media URLs from AGNI for example + for( unsigned int each = 0; each < mBookmarks.size() && each < 50; ++each ) + mBookmarkList->add_item( each, const_cast< char* >( mBookmarks[ each ].first.c_str() ) ); + glui_window_top->set_main_gfx_window( mAppWindow ); + + glui_window_top->add_column( false ); + mIdUrlEdit = start_id++; + mUrlEdit = glui_window_top->add_edittext( "Url:", GLUI_EDITTEXT_TEXT, 0, mIdUrlEdit, gluiCallbackWrapper ); + mUrlEdit->set_w( 600 ); + GLUI* glui_window_top2 = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_TOP ); + mIdUrlInitHistoryEdit = start_id++; + mUrlInitHistoryEdit = glui_window_top2->add_edittext( "Init History (separate by commas or semicolons):", + GLUI_EDITTEXT_TEXT, 0, mIdUrlInitHistoryEdit, gluiCallbackWrapper ); + mUrlInitHistoryEdit->set_w( 800 ); + + // top window - media controls for "time" media types (e.g. movies) + mGluiMediaTimeControlWindow = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_TOP ); + mGluiMediaTimeControlWindow->set_main_gfx_window( mAppWindow ); + mIdMediaTimeControlPlay = start_id++; + mGluiMediaTimeControlWindow->add_button( "PLAY", mIdMediaTimeControlPlay, gluiCallbackWrapper ); + mGluiMediaTimeControlWindow->add_column( false ); + mIdMediaTimeControlLoop = start_id++; + mGluiMediaTimeControlWindow->add_button( "LOOP", mIdMediaTimeControlLoop, gluiCallbackWrapper ); + mGluiMediaTimeControlWindow->add_column( false ); + mIdMediaTimeControlPause = start_id++; + mGluiMediaTimeControlWindow->add_button( "PAUSE", mIdMediaTimeControlPause, gluiCallbackWrapper ); + mGluiMediaTimeControlWindow->add_column( false ); + + GLUI_Button *button; + mIdMediaTimeControlRewind = start_id++; + button = mGluiMediaTimeControlWindow->add_button( "<<", mIdMediaTimeControlRewind, gluiCallbackWrapper ); + button->set_w(30); + mGluiMediaTimeControlWindow->add_column( false ); + mIdMediaTimeControlFastForward = start_id++; + button = mGluiMediaTimeControlWindow->add_button( ">>", mIdMediaTimeControlFastForward, gluiCallbackWrapper ); + button->set_w(30); + + mGluiMediaTimeControlWindow->add_column( true ); + + mIdMediaTimeControlStop = start_id++; + mGluiMediaTimeControlWindow->add_button( "STOP", mIdMediaTimeControlStop, gluiCallbackWrapper ); + mGluiMediaTimeControlWindow->add_column( false ); + mIdMediaTimeControlVolume = start_id++; + GLUI_Spinner* spinner = mGluiMediaTimeControlWindow->add_spinner( "Volume", 2, &mMediaTimeControlVolume, mIdMediaTimeControlVolume, gluiCallbackWrapper); + spinner->set_float_limits( 0, 100 ); + mGluiMediaTimeControlWindow->add_column( true ); + mIdMediaTimeControlSeekSeconds = start_id++; + spinner = mGluiMediaTimeControlWindow->add_spinner( "", 2, &mMediaTimeControlSeekSeconds, mIdMediaTimeControlSeekSeconds, gluiCallbackWrapper); + spinner->set_float_limits( 0, 200 ); + spinner->set_w( 32 ); + spinner->set_speed( 0.025f ); + mGluiMediaTimeControlWindow->add_column( false ); + mIdMediaTimeControlSeek = start_id++; + mGluiMediaTimeControlWindow->add_button( "SEEK", mIdMediaTimeControlSeek, gluiCallbackWrapper ); + mGluiMediaTimeControlWindow->add_column( false ); + + + // top window - media controls for "browser" media types (e.g. web browser) + mGluiMediaBrowserControlWindow = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_TOP ); + mGluiMediaBrowserControlWindow->set_main_gfx_window( mAppWindow ); + mIdMediaBrowserControlBack = start_id++; + mMediaBrowserControlBackButton = mGluiMediaBrowserControlWindow->add_button( "BACK", mIdMediaBrowserControlBack, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlStop = start_id++; + mGluiMediaBrowserControlWindow->add_button( "STOP", mIdMediaBrowserControlStop, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlForward = start_id++; + mMediaBrowserControlForwardButton = mGluiMediaBrowserControlWindow->add_button( "FORWARD", mIdMediaBrowserControlForward, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlHome = start_id++; + mGluiMediaBrowserControlWindow->add_button( "HOME", mIdMediaBrowserControlHome, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlReload = start_id++; + mGluiMediaBrowserControlWindow->add_button( "RELOAD", mIdMediaBrowserControlReload, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlClearCache = start_id++; + mGluiMediaBrowserControlWindow->add_button( "CLEAR CACHE", mIdMediaBrowserControlClearCache, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlClearCookies = start_id++; + mGluiMediaBrowserControlWindow->add_button( "CLEAR COOKIES", mIdMediaBrowserControlClearCookies, gluiCallbackWrapper ); + mGluiMediaBrowserControlWindow->add_column( false ); + mIdMediaBrowserControlEnableCookies = start_id++; + mMediaBrowserControlEnableCookies = 0; + mGluiMediaBrowserControlWindow->add_checkbox( "Enable Cookies", &mMediaBrowserControlEnableCookies, mIdMediaBrowserControlEnableCookies, gluiCallbackWrapper ); + + // top window - misc controls + GLUI* glui_window_misc_control = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_TOP ); + mIdRandomPanelCount = start_id++; + mRandomPanelCount = 0; + glui_window_misc_control->add_checkbox( "Randomize panel count", &mRandomPanelCount, mIdRandomPanelCount, gluiCallbackWrapper ); + glui_window_misc_control->set_main_gfx_window( mAppWindow ); + glui_window_misc_control->add_column( true ); + mIdRandomBookmarks = start_id++; + mRandomBookmarks = 0; + glui_window_misc_control->add_checkbox( "Randomize bookmarks", &mRandomBookmarks, mIdRandomBookmarks, gluiCallbackWrapper ); + glui_window_misc_control->set_main_gfx_window( mAppWindow ); + glui_window_misc_control->add_column( true ); + + mIdDisableTimeout = start_id++; + mDisableTimeout = 0; + glui_window_misc_control->add_checkbox( "Disable plugin timeout", &mDisableTimeout, mIdDisableTimeout, gluiCallbackWrapper ); + glui_window_misc_control->set_main_gfx_window( mAppWindow ); + + // bottom window - status + mBottomGLUIWindow = GLUI_Master.create_glui_subwindow( mAppWindow, GLUI_SUBWINDOW_BOTTOM ); + mStatusText = mBottomGLUIWindow->add_statictext( "" ); + mBottomGLUIWindow->set_main_gfx_window( mAppWindow ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::resetView() +{ + mViewRotationCtrl->reset(); + + mViewScaleCtrl->set_x( 0.0f ); + mViewScaleCtrl->set_y( 0.0f ); + mViewScaleCtrl->set_z( 3.0f ); + + mViewTranslationCtrl->set_x( 0.0f ); + mViewTranslationCtrl->set_y( 0.0f ); + mViewTranslationCtrl->set_z( 0.0f ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::makePickTexture( int id, GLuint* texture_handle, unsigned char** texture_pixels ) +{ + int pick_texture_width = 1024; + int pick_texture_height = 1024; + int pick_texture_depth = 3; + unsigned char* ptr = new unsigned char[ pick_texture_width * pick_texture_height * pick_texture_depth ]; + for( int y = 0; y < pick_texture_height; ++y ) + { + for( int x = 0; x < pick_texture_width * pick_texture_depth ; x += pick_texture_depth ) + { + unsigned long bits = 0L; + bits |= ( id << 20 ) | ( y << 10 ) | ( x / 3 ); + unsigned char r_component = ( bits >> 16 ) & 0xff; + unsigned char g_component = ( bits >> 8 ) & 0xff; + unsigned char b_component = bits & 0xff; + + ptr[ y * pick_texture_width * pick_texture_depth + x + 0 ] = r_component; + ptr[ y * pick_texture_width * pick_texture_depth + x + 1 ] = g_component; + ptr[ y * pick_texture_width * pick_texture_depth + x + 2 ] = b_component; + }; + }; + + glGenTextures( 1, texture_handle ); + + checkGLError("glGenTextures"); + std::cout << "glGenTextures returned " << *texture_handle << std::endl; + + bindTexture( *texture_handle ); + glTexImage2D( GL_TEXTURE_2D, 0, + GL_RGB, + pick_texture_width, pick_texture_height, + 0, GL_RGB, GL_UNSIGNED_BYTE, ptr ); + + *texture_pixels = ptr; +} + +//////////////////////////////////////////////////////////////////////////////// +// +std::string LLMediaPluginTest::mimeTypeFromUrl( std::string& url ) +{ + // default to web + std::string mime_type = "text/html"; + + // we may need a more advanced MIME type accessor later :-) + if ( url.find( ".swf" ) != std::string::npos ) // Flash movies + mime_type = "application/x-shockwave-flash"; + else + if ( url.find( ".mov" ) != std::string::npos ) // Movies + mime_type = "video/quicktime"; + else + if ( url.find( ".txt" ) != std::string::npos ) // Apple Text descriptors + mime_type = "video/quicktime"; + + return mime_type; +} + +//////////////////////////////////////////////////////////////////////////////// +// +std::string LLMediaPluginTest::pluginNameFromMimeType( std::string& mime_type ) +{ +#if LL_DARWIN + std::string plugin_name( "media_plugin_null.dylib" ); + if ( mime_type == "video/quicktime" ) + plugin_name = "media_plugin_quicktime.dylib"; + else + if ( mime_type == "text/html" ) + plugin_name = "media_plugin_webkit.dylib"; + +#elif LL_WINDOWS + std::string plugin_name( "media_plugin_null.dll" ); + + if ( mime_type == "video/quicktime" ) + plugin_name = "media_plugin_quicktime.dll"; + else + if ( mime_type == "text/html" ) + plugin_name = "media_plugin_webkit.dll"; + else + if ( mime_type == "application/x-shockwave-flash" ) + plugin_name = "media_plugin_flash_activex.dll"; + +#elif LL_LINUX + std::string plugin_name( "libmedia_plugin_null.so" ); + + if ( mime_type == "video/quicktime" ) + plugin_name = "libmedia_plugin_quicktime.so"; + else + if ( mime_type == "text/html" ) + plugin_name = "libmedia_plugin_webkit.so"; +#endif + return plugin_name; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::addMediaPanel( std::string url ) +{ + // Get the plugin filename using the URL + std::string mime_type = mimeTypeFromUrl( url ); + std::string plugin_name = pluginNameFromMimeType( mime_type ); + + // create a random size for the new media + int media_width; + int media_height; + getRandomMediaSize( media_width, media_height, mime_type ); + + // make a new plugin + LLPluginClassMedia* media_source = new LLPluginClassMedia(this); + + // tell the plugin what size we asked for + media_source->setSize( media_width, media_height ); + + // Use the launcher start and initialize the plugin +#if LL_DARWIN || LL_LINUX + std::string launcher_name( "SLPlugin" ); +#elif LL_WINDOWS + std::string launcher_name( "SLPlugin.exe" ); +#endif + media_source->init( launcher_name, plugin_name ); + media_source->setDisableTimeout(mDisableTimeout); + + // make a new panel and save parameters + mediaPanel* panel = new mediaPanel; + panel->mMediaSource = media_source; + panel->mStartUrl = url; + panel->mMimeType = mime_type; + panel->mMediaWidth = media_width; + panel->mMediaHeight = media_height; + panel->mTextureWidth = 0; + panel->mTextureHeight = 0; + panel->mTextureScaleX = 0; + panel->mTextureScaleY = 0; + panel->mMediaTextureHandle = 0; + panel->mPickTextureHandle = 0; + panel->mAppTextureCoordsOpenGL = false; // really need an 'undefined' state here too + panel->mReadyToRender = false; + + // look through current media panels to find an unused index number + bool id_exists = true; + for( int nid = 0; nid < mMaxPanels; ++nid ) + { + // does this id exist already? + id_exists = false; + for( int pid = 0; pid < (int)mMediaPanels.size(); ++pid ) + { + if ( nid == mMediaPanels[ pid ]->mId ) + { + id_exists = true; + break; + }; + }; + + // id wasn't found so we can use it + if ( ! id_exists ) + { + panel->mId = nid; + break; + }; + }; + + // if we get here and this flag is set, there is no room for any more panels + if ( id_exists ) + { + std::cout << "No room for any more panels" << std::endl; + } + else + { + // now we have the ID we can use it to make the + // pick texture (id is baked into texture pixels) + makePickTexture( panel->mId, &panel->mPickTextureHandle, &panel->mPickTexturePixels ); + + // save this in the list of panels + mMediaPanels.push_back( panel ); + + // select the panel that was just created + selectPanel( panel ); + + // load and start the URL + panel->mMediaSource->loadURI( url ); + panel->mMediaSource->start(); + + std::cout << "Adding new media panel for " << url << "(" << media_width << "x" << media_height << ") with index " << panel->mId << " - total panels = " << mMediaPanels.size() << std::endl; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::updateMediaPanel( mediaPanel* panel ) +{ +// checkGLError("LLMediaPluginTest::updateMediaPanel"); + + if ( ! panel ) + return; + + if(!panel->mMediaSource || !panel->mMediaSource->textureValid()) + { + panel->mReadyToRender = false; + return; + } + + // take a reference copy of the plugin values since they + // might change during this lifetime of this function + int plugin_media_width = panel->mMediaSource->getWidth(); + int plugin_media_height = panel->mMediaSource->getHeight(); + int plugin_texture_width = panel->mMediaSource->getBitsWidth(); + int plugin_texture_height = panel->mMediaSource->getBitsHeight(); + + // If the texture isn't created or the media or texture dimensions changed AND + // the sizes are valid then we need to delete the old media texture (if necessary) + // then make a new one. + if ((panel->mMediaTextureHandle == 0 || + panel->mMediaWidth != plugin_media_width || + panel->mMediaHeight != plugin_media_height || + panel->mTextureWidth != plugin_texture_width || + panel->mTextureHeight != plugin_texture_height) && + ( plugin_media_width > 0 && plugin_media_height > 0 && + plugin_texture_width > 0 && plugin_texture_height > 0 ) ) + { + std::cout << "Valid media size (" << plugin_media_width << " x " << plugin_media_height + << ") and texture size (" << plugin_texture_width << " x " << plugin_texture_height + << ") for panel with ID=" << panel->mId << " - making texture" << std::endl; + + // delete old GL texture + if ( isTexture( panel->mMediaTextureHandle ) ) + { + std::cerr << "updateMediaPanel: deleting texture " << panel->mMediaTextureHandle << std::endl; + glDeleteTextures( 1, &panel->mMediaTextureHandle ); + panel->mMediaTextureHandle = 0; + } + + std::cerr << "before: pick texture is " << panel->mPickTextureHandle << ", media texture is " << panel->mMediaTextureHandle << std::endl; + + // make a GL texture based on the dimensions the plugin told us + GLuint new_texture = 0; + glGenTextures( 1, &new_texture ); + + checkGLError("glGenTextures"); + + std::cout << "glGenTextures returned " << new_texture << std::endl; + + panel->mMediaTextureHandle = new_texture; + + bindTexture( panel->mMediaTextureHandle ); + + std::cout << "Setting texture size to " << plugin_texture_width << " x " << plugin_texture_height << std::endl; + glTexImage2D( GL_TEXTURE_2D, 0, + GL_RGB, + plugin_texture_width, plugin_texture_height, + 0, GL_RGB, GL_UNSIGNED_BYTE, + 0 ); + + + std::cerr << "after: pick texture is " << panel->mPickTextureHandle << ", media texture is " << panel->mMediaTextureHandle << std::endl; + }; + + // update our record of the media and texture dimensions + // NOTE: do this after we we check for sizes changes + panel->mMediaWidth = plugin_media_width; + panel->mMediaHeight = plugin_media_height; + panel->mTextureWidth = plugin_texture_width; + panel->mTextureHeight = plugin_texture_height; + if ( plugin_texture_width > 0 ) + { + panel->mTextureScaleX = (double)panel->mMediaWidth / (double)panel->mTextureWidth; + }; + if ( plugin_texture_height > 0 ) + { + panel->mTextureScaleY = (double)panel->mMediaHeight / (double)panel->mTextureHeight; + }; + + // update the flag which tells us if the media source uses OprnGL coords or not. + panel->mAppTextureCoordsOpenGL = panel->mMediaSource->getTextureCoordsOpenGL(); + + // Check to see if we have enough to render this panel. + // If we do, set a flag that the display functions use so + // they only render a panel with media if it's ready. + if ( panel->mMediaWidth < 0 || + panel->mMediaHeight < 0 || + panel->mTextureWidth < 1 || + panel->mTextureHeight < 1 || + panel->mMediaTextureHandle == 0 ) + { + panel->mReadyToRender = false; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::replaceMediaPanel( mediaPanel* panel, std::string url ) +{ + // no media panels so we can't change anything - have to add + if ( mMediaPanels.size() == 0 ) + return; + + // sanity check + if ( ! panel ) + return; + + int index; + for(index = 0; index < (int)mMediaPanels.size(); index++) + { + if(mMediaPanels[index] == panel) + break; + } + + if(index >= (int)mMediaPanels.size()) + { + // panel isn't in mMediaPanels + return; + } + + std::cout << "Replacing media panel with index " << panel->mId << std::endl; + + int panel_id = panel->mId; + + if(mSelectedPanel == panel) + mSelectedPanel = NULL; + + delete panel; + + // Get the plugin filename using the URL + std::string mime_type = mimeTypeFromUrl( url ); + std::string plugin_name = pluginNameFromMimeType( mime_type ); + + // create a random size for the new media + int media_width; + int media_height; + getRandomMediaSize( media_width, media_height, mime_type ); + + // make a new plugin + LLPluginClassMedia* media_source = new LLPluginClassMedia(this); + + // tell the plugin what size we asked for + media_source->setSize( media_width, media_height ); + + // Use the launcher start and initialize the plugin +#if LL_DARWIN || LL_LINUX + std::string launcher_name( "SLPlugin" ); +#elif LL_WINDOWS + std::string launcher_name( "SLPlugin.exe" ); +#endif + media_source->init( launcher_name, plugin_name ); + media_source->setDisableTimeout(mDisableTimeout); + + // make a new panel and save parameters + panel = new mediaPanel; + panel->mMediaSource = media_source; + panel->mStartUrl = url; + panel->mMimeType = mime_type; + panel->mMediaWidth = media_width; + panel->mMediaHeight = media_height; + panel->mTextureWidth = 0; + panel->mTextureHeight = 0; + panel->mTextureScaleX = 0; + panel->mTextureScaleY = 0; + panel->mMediaTextureHandle = 0; + panel->mPickTextureHandle = 0; + panel->mAppTextureCoordsOpenGL = false; // really need an 'undefined' state here too + panel->mReadyToRender = false; + + panel->mId = panel_id; + + // Replace the entry in the panels array + mMediaPanels[index] = panel; + + // now we have the ID we can use it to make the + // pick texture (id is baked into texture pixels) + makePickTexture( panel->mId, &panel->mPickTextureHandle, &panel->mPickTexturePixels ); + + // select the panel that was just created + selectPanel( panel ); + + // load and start the URL + panel->mMediaSource->loadURI( url ); + panel->mMediaSource->start(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::getRandomMediaSize( int& width, int& height, std::string mime_type ) +{ + // Make a new media source with a random size which we'll either + // directly or the media plugin will tell us what it wants later. + // Use a random size so we can test support for weird media sizes. + // (Almost everything else will get filled in later once the + // plugin responds) + // NB. Do we need to enforce that width is on 4 pixel boundary? + width = ( ( rand() % 170 ) + 30 ) * 4; + height = ( ( rand() % 170 ) + 30 ) * 4; + + // adjust this random size if it's a browser so we get + // a more useful size for testing.. + if ( mime_type == "text/html" ) + { + width = ( ( rand() % 100 ) + 100 ) * 4; + height = ( width * ( ( rand() % 400 ) + 1000 ) ) / 1000; + } + else + // adjust this random size if it's Flash so we get + // a more useful size for testing.. + if ( mime_type == "application/x-shockwave-flash" ) + { + width = ( ( rand() % 100 ) + 100 ) * 4; + height = ( width * ( ( rand() % 400 ) + 1000 ) ) / 1000; + }; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::remMediaPanel( mediaPanel* panel ) +{ + // always leave one panel + if ( mMediaPanels.size() == 1 ) + return; + + // sanity check - don't think this can happen but see above for a case where it might... + if ( ! panel ) + return; + + std::cout << "Removing media panel with index " << panel->mId << " - total panels = " << mMediaPanels.size() - 1 << std::endl; + + if(mSelectedPanel == panel) + mSelectedPanel = NULL; + + delete panel; + + // remove from storage list + for( int i = 0; i < (int)mMediaPanels.size(); ++i ) + { + if ( mMediaPanels[ i ] == panel ) + { + mMediaPanels.erase( mMediaPanels.begin() + i ); + break; + }; + }; + + // select the first panel + selectPanel( mMediaPanels[ 0 ] ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::updateStatusBar() +{ + if ( ! mSelectedPanel ) + return; + + // cache results - this is a very slow function + static int cached_id = -1; + static int cached_media_width = -1; + static int cached_media_height = -1; + static int cached_texture_width = -1; + static int cached_texture_height = -1; + static bool cached_supports_browser_media = true; + static bool cached_supports_time_media = false; + static int cached_movie_time = -1; + + static std::string cached_plugin_version = ""; + if ( + cached_id == mSelectedPanel->mId && + cached_media_width == mSelectedPanel->mMediaWidth && + cached_media_height == mSelectedPanel->mMediaHeight && + cached_texture_width == mSelectedPanel->mTextureWidth && + cached_texture_height == mSelectedPanel->mTextureHeight && + cached_supports_browser_media == mSelectedPanel->mMediaSource->pluginSupportsMediaBrowser() && + cached_supports_time_media == mSelectedPanel->mMediaSource->pluginSupportsMediaTime() && + cached_plugin_version == mSelectedPanel->mMediaSource->getPluginVersion() && + cached_movie_time == (int)mSelectedPanel->mMediaSource->getCurrentTime() + ) + { + // nothing changed so don't spend time in this shitty function + return; + }; + + std::ostringstream stream( "" ); + + stream.str( "" ); + stream.clear(); + + stream << "Id: "; + stream << std::setw( 2 ) << std::setfill( '0' ); + stream << mSelectedPanel->mId; + stream << " | "; + stream << "Media: "; + stream << std::setw( 3 ) << std::setfill( '0' ); + stream << mSelectedPanel->mMediaWidth; + stream << " x "; + stream << std::setw( 3 ) << std::setfill( '0' ); + stream << mSelectedPanel->mMediaHeight; + stream << " | "; + stream << "Texture: "; + stream << std::setw( 4 ) << std::setfill( '0' ); + stream << mSelectedPanel->mTextureWidth; + stream << " x "; + stream << std::setw( 4 ) << std::setfill( '0' ); + stream << mSelectedPanel->mTextureHeight; + stream << " | "; + if ( mSelectedPanel->mMediaSource->pluginSupportsMediaBrowser() ) + stream << "BROWSER"; + else + if ( mSelectedPanel->mMediaSource->pluginSupportsMediaTime() ) + stream << "TIME "; + stream << " | "; + stream << mSelectedPanel->mMediaSource->getPluginVersion(); + stream << " | "; + if ( mSelectedPanel->mMediaSource->pluginSupportsMediaTime() ) + { + stream << std::setw( 3 ) << std::setfill( '0' ); + stream << (int)mSelectedPanel->mMediaSource->getCurrentTime(); + stream << " / "; + stream << std::setw( 3 ) << std::setfill( '0' ); + stream << (int)mSelectedPanel->mMediaSource->getDuration(); + stream << " @ "; + stream << (int)mSelectedPanel->mMediaSource->getCurrentPlayRate(); + stream << " | "; + }; + + glutSetWindow( mBottomGLUIWindow->get_glut_window_id() ); + mStatusText->set_text( const_cast< char*>( stream.str().c_str() ) ); + glutSetWindow( mAppWindow ); + + // caching + cached_id = mSelectedPanel->mId; + cached_media_width = mSelectedPanel->mMediaWidth; + cached_media_height = mSelectedPanel->mMediaHeight; + cached_texture_width = mSelectedPanel->mTextureWidth; + cached_texture_height = mSelectedPanel->mTextureHeight; + cached_supports_browser_media = mSelectedPanel->mMediaSource->pluginSupportsMediaBrowser(); + cached_supports_time_media = mSelectedPanel->mMediaSource->pluginSupportsMediaTime(); + cached_plugin_version = mSelectedPanel->mMediaSource->getPluginVersion(); + cached_movie_time = (int)mSelectedPanel->mMediaSource->getCurrentTime(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::dumpPanelInfo() +{ + std::cout << std::endl << "===== Media Panels =====" << std::endl; + for( int i = 0; i < (int)mMediaPanels.size(); ++i ) + { + std::cout << std::setw( 2 ) << std::setfill( '0' ); + std::cout << i + 1 << "> "; + std::cout << "Id: "; + std::cout << std::setw( 2 ) << std::setfill( '0' ); + std::cout << mMediaPanels[ i ]->mId; + std::cout << " | "; + std::cout << "Media: "; + std::cout << std::setw( 3 ) << std::setfill( '0' ); + std::cout << mMediaPanels[ i ]->mMediaWidth; + std::cout << " x "; + std::cout << std::setw( 3 ) << std::setfill( '0' ); + std::cout << mMediaPanels[ i ]->mMediaHeight; + std::cout << " | "; + std::cout << "Texture: "; + std::cout << std::setw( 4 ) << std::setfill( '0' ); + std::cout << mMediaPanels[ i ]->mTextureWidth; + std::cout << " x "; + std::cout << std::setw( 4 ) << std::setfill( '0' ); + std::cout << mMediaPanels[ i ]->mTextureHeight; + std::cout << " | "; + if ( mMediaPanels[ i ] == mSelectedPanel ) + std::cout << "(selected)"; + + std::cout << std::endl; + }; + std::cout << "========================" << std::endl; +} + +//////////////////////////////////////////////////////////////////////////////// +// +void LLMediaPluginTest::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) +{ + // Uncomment this to make things much, much quieter. +// return; + + switch(event) + { + case MEDIA_EVENT_CONTENT_UPDATED: + // too spammy -- don't log these +// std::cerr << "Media event: MEDIA_EVENT_CONTENT_UPDATED " << std::endl; + break; + + case MEDIA_EVENT_TIME_DURATION_UPDATED: + // too spammy -- don't log these +// std::cerr << "Media event: MEDIA_EVENT_TIME_DURATION_UPDATED, time is " << self->getCurrentTime() << " of " << self->getDuration() << std::endl; + break; + + case MEDIA_EVENT_SIZE_CHANGED: + std::cerr << "Media event: MEDIA_EVENT_SIZE_CHANGED " << std::endl; + break; + + case MEDIA_EVENT_CURSOR_CHANGED: + std::cerr << "Media event: MEDIA_EVENT_CURSOR_CHANGED, new cursor is " << self->getCursorName() << std::endl; + break; + + case MEDIA_EVENT_NAVIGATE_BEGIN: + std::cerr << "Media event: MEDIA_EVENT_NAVIGATE_BEGIN " << std::endl; + break; + + case MEDIA_EVENT_NAVIGATE_COMPLETE: + std::cerr << "Media event: MEDIA_EVENT_NAVIGATE_COMPLETE, result string is: " << self->getNavigateResultString() << std::endl; + break; + + case MEDIA_EVENT_PROGRESS_UPDATED: + std::cerr << "Media event: MEDIA_EVENT_PROGRESS_UPDATED, loading at " << self->getProgressPercent() << "%" << std::endl; + break; + + case MEDIA_EVENT_STATUS_TEXT_CHANGED: + std::cerr << "Media event: MEDIA_EVENT_STATUS_TEXT_CHANGED, new status text is: " << self->getStatusText() << std::endl; + break; + + case MEDIA_EVENT_LOCATION_CHANGED: + { + std::cerr << "Media event: MEDIA_EVENT_LOCATION_CHANGED, new uri is: " << self->getLocation() << std::endl; + mediaPanel* panel = findMediaPanel(self); + if(panel != NULL) + { + panel->mStartUrl = self->getLocation(); + if(panel == mSelectedPanel) + { + mUrlEdit->set_text(const_cast<char*>(panel->mStartUrl.c_str()) ); + } + } + } + break; + + case MEDIA_EVENT_CLICK_LINK_HREF: + std::cerr << "Media event: MEDIA_EVENT_CLICK_LINK_HREF, uri is " << self->getClickURL() << std::endl; + break; + + case MEDIA_EVENT_CLICK_LINK_NOFOLLOW: + std::cerr << "Media event: MEDIA_EVENT_CLICK_LINK_NOFOLLOW, uri is " << self->getClickURL() << std::endl; + break; + + case MEDIA_EVENT_PLUGIN_FAILED: + std::cerr << "Media event: MEDIA_EVENT_PLUGIN_FAILED" << std::endl; + break; + } +} + +//////////////////////////////////////////////////////////////////////////////// +// +static void gluiCallbackWrapper( int control_id ) +{ + if ( gApplication ) + gApplication->gluiCallback( control_id ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void glutReshape( int width, int height ) +{ + if ( gApplication ) + gApplication->reshape( width, height ); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void glutDisplay() +{ + if ( gApplication ) + gApplication->display(); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void glutIdle(int update_ms) +{ + GLUI_Master.set_glutTimerFunc( update_ms, glutIdle, update_ms); + + if ( gApplication ) + gApplication->idle(); + +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void glutKeyboard( unsigned char key, int x, int y ) +{ + if ( gApplication ) + gApplication->keyboard( key ); +}; + +//////////////////////////////////////////////////////////////////////////////// +// +void glutMousePassive( int x, int y ) +{ + if ( gApplication ) + gApplication->mousePassive( x, y ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void glutMouseMove( int x , int y ) +{ + if ( gApplication ) + gApplication->mouseMove( x, y ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +void glutMouseButton( int button, int state, int x, int y ) +{ + if ( gApplication ) + gApplication->mouseButton( button, state, x, y ); +} + +//////////////////////////////////////////////////////////////////////////////// +// +int main( int argc, char* argv[] ) +{ + glutInit( &argc, argv ); + glutInitDisplayMode( GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGB ); + + const int app_window_x = 80; + const int app_window_y = 0; + const int app_window_width = 964; + const int app_window_height = 964; + + glutInitWindowPosition( app_window_x, app_window_y ); + glutInitWindowSize( app_window_width, app_window_height ); + + int app_window_handle = glutCreateWindow( "LLMediaPluginTest2" ); + + glutDisplayFunc( glutDisplay ); + + GLUI_Master.set_glutReshapeFunc( glutReshape ); + GLUI_Master.set_glutKeyboardFunc( glutKeyboard ); + GLUI_Master.set_glutMouseFunc( glutMouseButton ); + + glutPassiveMotionFunc( glutMousePassive ); + glutMotionFunc( glutMouseMove ); + + glutSetWindow( app_window_handle ); + + gApplication = new LLMediaPluginTest( app_window_handle, app_window_width, app_window_height ); + + // update at approximately 60hz + int update_ms = 1000 / 60; + + GLUI_Master.set_glutTimerFunc( update_ms, glutIdle, update_ms); + + glutMainLoop(); + + delete gApplication; +} diff --git a/indra/test_apps/llplugintest/llmediaplugintest.h b/indra/test_apps/llplugintest/llmediaplugintest.h new file mode 100644 index 0000000000..079b40ddc0 --- /dev/null +++ b/indra/test_apps/llplugintest/llmediaplugintest.h @@ -0,0 +1,200 @@ +/** + * @file LLMediaPluginTest2.cpp + * @brief Primary test application for LLMedia (Separate Process) Plugin system + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_MEDIA_PLUGIN_TEST_H +#define LL_MEDIA_PLUGIN_TEST_H + +#include <vector> +#include <string> +#include "llpluginclassmedia.h" + +// Forward declarations +class GLUI_Rotation; +class GLUI_Translation; +class GLUI_Listbox; +class GLUI_EditText; +class GLUI_StaticText; +class GLUI; +class GLUI_Button; + +//////////////////////////////////////////////////////////////////////////////// +// +struct mediaPanel +{ + public: + mediaPanel(); + ~mediaPanel(); + int mId; + std::string mStartUrl; + std::string mMimeType; + LLPluginClassMedia *mMediaSource; + int mMediaWidth; + int mMediaHeight; + int mTextureWidth; + int mTextureHeight; + double mTextureScaleX; + double mTextureScaleY; + GLuint mMediaTextureHandle; + GLuint mPickTextureHandle; + unsigned char* mPickTexturePixels; + bool mAppTextureCoordsOpenGL; + bool mReadyToRender; +}; + +//////////////////////////////////////////////////////////////////////////////// +// +class LLMediaPluginTest : public LLPluginClassMediaOwner +{ + public: + LLMediaPluginTest( int app_window, int window_width, int window_height ); + ~LLMediaPluginTest(); + + void reshape( int width, int height ); + void display(); + void idle(); + void gluiCallback( int control_id ); + void keyboard( int key ); + void mousePassive( int x, int y ); + void mouseButton( int button, int state, int x, int y ); + void mouseMove( int x, int y ); + + void bindTexture(GLuint texture, GLint row_length = 0, GLint alignment = 1); + bool checkGLError(char *name = "OpenGL"); + void drawGeometry( int panel ); + void startPanelHighlight( float red, float green, float blue, float line_width ); + void endPanelHighlight(); + enum { DrawTypePickTexture, DrawTypeMediaTexture }; + void draw( int draw_type ); + void windowPosToTexturePos( int window_x, int window_y, int& media_x, int& media_y, int& id ); + + void addMediaPanel( std::string url ); + void updateMediaPanel( mediaPanel* panel ); + void remMediaPanel( mediaPanel* panel ); + void replaceMediaPanel( mediaPanel* panel, std::string url ); + void getRandomMediaSize( int& width, int& height, std::string mime_type ); + void navigateToNewURI( std::string uri ); + void initUrlHistory( std::string uri ); + void selectPanelById( int id ); + void selectPanel( mediaPanel* panel ); + mediaPanel* findMediaPanel( LLPluginClassMedia* panel ); + void makePickTexture( int id, GLuint* texture_handle, unsigned char** texture_pixels ); + void makeChrome(); + void resetView(); + + void dumpPanelInfo(); + void updateStatusBar(); + + // Inherited from LLPluginClassMediaOwner + /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, LLPluginClassMediaOwner::EMediaEvent); + + private: + const int mVersionMajor; + const int mVersionMinor; + const int mVersionPatch; + const int mMaxPanels; + int mAppWindow; + int mWindowWidth; + int mWindowHeight; + int mCurMouseX; + int mCurMouseY; + unsigned char mPixelReadColor[ 3 ]; + bool mFuzzyMedia; + const std::string mHomeWebUrl; + + std::vector< mediaPanel* > mMediaPanels; + mediaPanel* mSelectedPanel; + std::string mimeTypeFromUrl( std::string& url ); + std::string pluginNameFromMimeType( std::string& mime_type ); + + GLUI_Rotation* mViewRotationCtrl; + GLUI_Translation* mViewScaleCtrl; + GLUI_Translation* mViewTranslationCtrl; + float mViewportAspect; + float mViewPos[ 3 ]; + float mViewRotation[ 16 ]; + + int mIdControlAddPanel; + int mIdControlRemPanel; + + std::vector< std::pair< std::string, std::string > > mBookmarks; + GLUI_Listbox* mBookmarkList; + int mIdBookmarks; + int mIdUrlEdit; + GLUI_EditText* mUrlEdit; + int mIdUrlInitHistoryEdit; + GLUI_EditText* mUrlInitHistoryEdit; + int mSelBookmark; + int mIdRandomPanelCount; + int mRandomPanelCount; + int mIdRandomBookmarks; + int mRandomBookmarks; + int mIdDisableTimeout; + int mDisableTimeout; + int mIdControlCrashPlugin; + int mIdControlHangPlugin; + int mIdControlExitApp; + + GLUI* mGluiMediaTimeControlWindow; + int mIdMediaTimeControlPlay; + int mIdMediaTimeControlLoop; + int mIdMediaTimeControlPause; + int mIdMediaTimeControlStop; + int mIdMediaTimeControlSeek; + int mIdMediaTimeControlVolume; + int mMediaTimeControlVolume; + int mIdMediaTimeControlSeekSeconds; + int mMediaTimeControlSeekSeconds; + int mIdMediaTimeControlRewind; + int mIdMediaTimeControlFastForward; + + GLUI* mGluiMediaBrowserControlWindow; + int mIdMediaBrowserControlBack; + GLUI_Button* mMediaBrowserControlBackButton; + int mIdMediaBrowserControlStop; + int mIdMediaBrowserControlForward; + GLUI_Button* mMediaBrowserControlForwardButton; + bool mGluiMediaTimeControlWindowFlag; + bool mGluiMediaBrowserControlWindowFlag; + bool mMediaBrowserControlBackButtonFlag; + bool mMediaBrowserControlForwardButtonFlag; + int mIdMediaBrowserControlHome; + int mIdMediaBrowserControlReload; + int mIdMediaBrowserControlClearCache; + int mIdMediaBrowserControlClearCookies; + int mIdMediaBrowserControlEnableCookies; + int mMediaBrowserControlEnableCookies; + + GLUI* mBottomGLUIWindow; + GLUI_StaticText* mStatusText; +}; + +#endif // LL_MEDIA_PLUGIN_TEST_H + diff --git a/install.xml b/install.xml index dd50191507..051213225e 100644 --- a/install.xml +++ b/install.xml @@ -37,9 +37,9 @@ <key>windows</key> <map> <key>md5sum</key> - <string>e6ba152b7edd4ad2c9db4f9ff7bd38e1</string> + <string>91155239b02f576384603795d41eb971</string> <key>url</key> - <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/GL-windows-20080613.tar.bz2</uri> + <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/GL-windows-20090505.tar.bz2</uri> </map> </map> </map> @@ -185,6 +185,30 @@ </map> </map> </map> + <key>awesomium</key> + <map> + <key>description</key> + <string>Awesomium is a library that makes it easy for developers to embed web-content in their applications.</string> + <key>license</key> + <string>lgpl</string> + <key>packages</key> + <map> + <key>darwin</key> + <map> + <key>md5sum</key> + <string>65f3e4aa63f0596bbb0c631e3c61e29f</string> + <key>url</key> + <uri>scp:install-packages.lindenlab.com:/local/www/install-packages/doc/awesomium-1.0.8-darwin-20090603.tar.bz2</uri> + </map> + <key>windows</key> + <map> + <key>md5sum</key> + <string>f6e40cda9027ae66dc082a68a11539b0</string> + <key>url</key> + <uri>scp:install-packages.lindenlab.com:/local/www/install-packages/doc/awesomium-1.0.8-windows-20090602.tar.bz2</uri> + </map> + </map> + </map> <key>berkeley</key> <map> <key>copyright</key> @@ -367,6 +391,21 @@ </map> </map> </map> + <key>flash</key> + <map> + <key>license</key> + <string>lgpl</string> + <key>packages</key> + <map> + <key>windows</key> + <map> + <key>md5sum</key> + <string>a8a6016bd0d251575d80ad6c42079554</string> + <key>url</key> + <uri>scp:install-packages.lindenlab.com:/local/www/install-packages/doc/flash-build-10b-windows-20090518.tar.bz2</uri> + </map> + </map> + </map> <key>fmod</key> <map> <key>copyright</key> @@ -415,6 +454,21 @@ </map> </map> </map> + <key>freeglut</key> + <map> + <key>license</key> + <string>mit</string> + <key>packages</key> + <map> + <key>windows</key> + <map> + <key>md5sum</key> + <string>fcbb695ff203775fad96d184bf5f34fc</string> + <key>url</key> + <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/freeglut-2.4.0-windows-20090608.tar.bz2</uri> + </map> + </map> + </map> <key>freetype</key> <map> <key>copyright</key> @@ -526,6 +580,28 @@ </map> </map> </map> + <key>glui</key> + <map> + <key>license</key> + <string>lgpl</string> + <key>packages</key> + <map> + <key>darwin</key> + <map> + <key>md5sum</key> + <string>84f792a860691d0fad6d1de6eeb31baa</string> + <key>url</key> + <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/glui-2.36-darwin-20090623a.tar.bz2</uri> + </map> + <key>windows</key> + <map> + <key>md5sum</key> + <string>5b8631fe510d4ebaeb965c673937e1e7</string> + <key>url</key> + <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/glui-2.3.6-windows-freeglut-20090608.tar.bz2</uri> + </map> + </map> + </map> <key>google</key> <map> <key>license</key> @@ -883,42 +959,6 @@ anguage Infrstructure (CLI) international standard</string> </map> </map> </map> - <key>llmozlib</key> - <map> - <key>license</key> - <string>mozillaPL</string> - <key>packages</key> - <map> - <key>darwin</key> - <map> - <key>md5sum</key> - <string>c951587726618d33646f2b169c290bd3</string> - <key>url</key> - <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/llmozlib-2_0_0_21-darwin-20090304.tar.bz2</uri> - </map> - <key>linux</key> - <map> - <key>md5sum</key> - <string>b7ebcf0fb764ed4fa57c62d068b4a769</string> - <key>url</key> - <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/llmozlib-linux-20090304prfhk.tar.bz2</uri> - </map> - <key>linux64</key> - <map> - <key>md5sum</key> - <string>c2ae53462b56e0999ba386774ab0b789</string> - <key>url</key> - <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/llmozlib-linux64-20080909.tar.bz2</uri> - </map> - <key>windows</key> - <map> - <key>md5sum</key> - <string>e9454e258b99668782d8570481b5eda1</string> - <key>url</key> - <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/llmozlib-windows-20090306.tar.bz2</uri> - </map> - </map> - </map> <key>mesa</key> <map> <key>copyright</key> @@ -1291,6 +1331,35 @@ anguage Infrstructure (CLI) international standard</string> </map> </map> </map> + <key>webkitlibplugin</key> + <map> + <key>license</key> + <string>lgpl</string> + <key>packages</key> + <map> + <key>darwin</key> + <map> + <key>md5sum</key> + <string>90bf3648c248cdb3e7ac77b674a298d7</string> + <key>url</key> + <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/webkitlibplugin-darwin-20090709.tar.bz2</uri> + </map> + <key>linux</key> + <map> + <key>md5sum</key> + <string>d69d1fdf2a0d3393649acab4c0274dd5</string> + <key>url</key> + <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/webkitlibplugin-linux-20090630a.tar.bz2</uri> + </map> + <key>windows</key> + <map> + <key>md5sum</key> + <string>21f78cc460b67bc2b22ab986ea9158f3</string> + <key>url</key> + <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/webkitlibplugin-windows-20090709.tar.bz2</uri> + </map> + </map> + </map> <key>xmlrpc-epi</key> <map> <key>copyright</key> @@ -1365,9 +1434,9 @@ anguage Infrstructure (CLI) international standard</string> <key>windows</key> <map> <key>md5sum</key> - <string>9ad657cc5146fef77ffa8dd1c069018f</string> + <string>ddb09c2dd7d2caed1bd7cc1686c4214c</string> <key>url</key> - <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/zlib-1.2.3-windows-20080723.tar.bz2</uri> + <uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/zlib-1.2.3-windows-20090505.tar.bz2</uri> </map> </map> </map> @@ -1569,7 +1638,7 @@ your work. <key>glh_linear</key> <map> <key>text</key> - <string>glh - is a platform-indepenedent C++ OpenGL helper library + <string>glh - is a platform-indepenedent C++ OpenGL helper library Copyright (c) 2000 Cass Everitt Copyright (c) 2000 NVIDIA Corporation @@ -1581,14 +1650,14 @@ conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following -disclaimer. +disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials -provided with the distribution. +provided with the distribution. The names of contributors to this software may not be used to endorse or promote products derived from this software -without specific prior written permission. +without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS @@ -1599,13 +1668,18 @@ 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. +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. Cass Everitt - cass@r3.nu </string> </map> + <key>glut</key> + <map> + <key>url</key> + <string>http://www.xmission.com/~nate/glut/README-win32.txt</string> + </map> <key>gpl</key> <map> <key>url</key> @@ -1657,11 +1731,6 @@ Cass Everitt - cass@r3.nu <key>text</key> <string>http://www.jclark.com/xml/copying.txt</string> </map> - <key>mozillaPL</key> - <map> - <key>url</key> - <string>http://www.mozilla.org/MPL/MPL-1.1.html</string> - </map> <key>ogg-vorbis</key> <map> <key>url</key> @@ -1683,26 +1752,26 @@ Cass Everitt - cass@r3.nu <string>ENGLISH Apple Computer, Inc. -QuickTime 7 Software Developer Kit (SDK) +QuickTime 7 Software Developer Kit (SDK) Software License Agreement PLEASE READ THIS SOFTWARE LICENSE AGREEMENT ("LICENSE") BEFORE USING THE SOFTWARE. BY USING THE SOFTWARE, YOU ARE AGREEING TO BE BOUND BY THE TERMS OF THIS LICENSE. IF YOU ARE ACCESSING THE SOFTWARE ELECTRONICALLY, SIGNIFY YOUR AGREEMENT TO BE BOUND BY THE TERMS OF THIS LICENSE BY CLICKING THE "AGREE/ACCEPT" BUTTON. IF YOU DO NOT AGREE TO THE TERMS OF THIS LICENSE, RETURN THE APPLE SOFTWARE TO THE PLACE WHERE YOU OBTAINED IT FOR A REFUND OR, IF THE SOFTWARE WAS ACCESSED ELECTRONICALLY, CLICK "DISAGREE/DECLINE". -IMPORTANT NOTE: To the extent this software may be used to reproduce materials, it is licensed to you only for reproduction of materials you are authorized or legally permitted to reproduce. +IMPORTANT NOTE: To the extent this software may be used to reproduce materials, it is licensed to you only for reproduction of materials you are authorized or legally permitted to reproduce. -1. License. Any software, tools, utilities, sample code, documentation, fonts, API?s, header files and other materials accompanying this License, whether on disk, print or electronic documentation, in read only memory, or any other media, (collectively, the "Apple Software") are licensed, not sold, to you by Apple Computer, Inc. ("Apple") for use only under the terms of this License, and Apple reserves all rights not expressly granted to you. The rights granted herein are limited to Apple's and its licensors' intellectual property rights in the Apple Software and do not include any other patents or intellectual property rights. You own the media on which the Apple Software is recorded but Apple and/or Apple's licensor(s) retain ownership of the Apple Software itself. The Apple Software in this package and any copies, modifications and derivative works that this License authorizes you to make are subject to this License. +1. License. Any software, tools, utilities, sample code, documentation, fonts, API?s, header files and other materials accompanying this License, whether on disk, print or electronic documentation, in read only memory, or any other media, (collectively, the "Apple Software") are licensed, not sold, to you by Apple Computer, Inc. ("Apple") for use only under the terms of this License, and Apple reserves all rights not expressly granted to you. The rights granted herein are limited to Apple's and its licensors' intellectual property rights in the Apple Software and do not include any other patents or intellectual property rights. You own the media on which the Apple Software is recorded but Apple and/or Apple's licensor(s) retain ownership of the Apple Software itself. The Apple Software in this package and any copies, modifications and derivative works that this License authorizes you to make are subject to this License. -2. Permitted Uses and Restrictions. You may use the Apple Software to develop application software that is compatible with, and runs only on Mac OS X and/or Windows platforms with QuickTime installed. Except for compiling header files and linking libraries as necessary to build your application software, you have no right to modify, incorporate into or include in combination with your own programs, license or otherwise redistribute any portion of the Apple Software. Your software application may not interfere with the functionality of QuickTime Player or the QuickTime Plug-in, including but not limited to file type or MIME type associations that are registered to QuickTime. You may make only as many internal use copies of the Apple Software as reasonably necessary to use the Apple Software as permitted in this paragraph and distribute such copies only to your employees whose job duties require them to so use the Apple Software. You must reproduce on each copy of the Apple Software or portion thereof, the Apple copyright notice and any other proprietary legends that were on the original copy of the Apple Software. Except as expressly permitted in this License, you may not decompile, reverse engineer, disassemble, modify, rent, lease, loan, sublicense, distribute or create derivative works based upon the Apple Software in whole or part. Your rights under this License will terminate automatically without notice from Apple if you fail to comply with any term(s) of this License. In addition, Apple reserves the right to terminate this License if a new version of Apple's operating system software or the Apple Software is released which is incompatible with the Apple Software. +2. Permitted Uses and Restrictions. You may use the Apple Software to develop application software that is compatible with, and runs only on Mac OS X and/or Windows platforms with QuickTime installed. Except for compiling header files and linking libraries as necessary to build your application software, you have no right to modify, incorporate into or include in combination with your own programs, license or otherwise redistribute any portion of the Apple Software. Your software application may not interfere with the functionality of QuickTime Player or the QuickTime Plug-in, including but not limited to file type or MIME type associations that are registered to QuickTime. You may make only as many internal use copies of the Apple Software as reasonably necessary to use the Apple Software as permitted in this paragraph and distribute such copies only to your employees whose job duties require them to so use the Apple Software. You must reproduce on each copy of the Apple Software or portion thereof, the Apple copyright notice and any other proprietary legends that were on the original copy of the Apple Software. Except as expressly permitted in this License, you may not decompile, reverse engineer, disassemble, modify, rent, lease, loan, sublicense, distribute or create derivative works based upon the Apple Software in whole or part. Your rights under this License will terminate automatically without notice from Apple if you fail to comply with any term(s) of this License. In addition, Apple reserves the right to terminate this License if a new version of Apple's operating system software or the Apple Software is released which is incompatible with the Apple Software. -3. Disclaimer Of Warranty. The Apple Software may be "alpha", "beta", "development", pre-release, untested, and/or not fully tested and may contain errors that could cause failures or loss of data, be incomplete or contain inaccuracies. YOU EXPRESSLY ACKNOWLEDGE AND AGREE THAT USE OF THE APPLE SOFTWARE IS AT YOUR SOLE RISK AND THAT THE ENTIRE RISK AS TO SATISFACTORY QUALITY, PERFORMANCE, ACCURACY AND EFFORT IS WITH YOU. EXCEPT FOR THE LIMITED WARRANTY ON MEDIA SET FORTH ABOVE AND TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE APPLE SOFTWARE IS PROVIDED "AS IS", WITH ALL FAULTS AND WITHOUT WARRANTY OF ANY KIND, AND APPLE AND APPLE'S LICENSORS (COLLECTIVELY REFERRED TO AS "APPLE" FOR THE PURPOSES OF SECTIONS 4 AND 5) HEREBY DISCLAIM ALL WARRANTIES AND CONDITIONS WITH RESPECT TO THE APPLE SOFTWARE, EITHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF MERCHANTABILITY, OF SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY, OF QUIET ENJOYMENT, AND NON-INFRINGEMENT OF THIRD PARTY RIGHTS. APPLE DOES NOT WARRANT AGAINST INTERFERENCE WITH YOUR ENJOYMENT OF THE APPLE SOFTWARE, THAT THE FUNCTIONS CONTAINED IN THE APPLE SOFTWARE WILL MEET YOUR REQUIREMENTS, THAT THE OPERATION OF THE APPLE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT DEFECTS IN THE APPLE SOFTWARE WILL BE CORRECTED. NO ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY APPLE OR AN APPLE AUTHORIZED REPRESENTATIVE SHALL CREATE A WARRANTY. SHOULD THE APPLE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE ENTIRE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES OR LIMITATIONS ON APPLICABLE STATUTORY RIGHTS OF A CONSUMER, SO THE ABOVE EXCLUSION AND LIMITATIONS MAY NOT APPLY TO YOU. +3. Disclaimer Of Warranty. The Apple Software may be "alpha", "beta", "development", pre-release, untested, and/or not fully tested and may contain errors that could cause failures or loss of data, be incomplete or contain inaccuracies. YOU EXPRESSLY ACKNOWLEDGE AND AGREE THAT USE OF THE APPLE SOFTWARE IS AT YOUR SOLE RISK AND THAT THE ENTIRE RISK AS TO SATISFACTORY QUALITY, PERFORMANCE, ACCURACY AND EFFORT IS WITH YOU. EXCEPT FOR THE LIMITED WARRANTY ON MEDIA SET FORTH ABOVE AND TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE APPLE SOFTWARE IS PROVIDED "AS IS", WITH ALL FAULTS AND WITHOUT WARRANTY OF ANY KIND, AND APPLE AND APPLE'S LICENSORS (COLLECTIVELY REFERRED TO AS "APPLE" FOR THE PURPOSES OF SECTIONS 4 AND 5) HEREBY DISCLAIM ALL WARRANTIES AND CONDITIONS WITH RESPECT TO THE APPLE SOFTWARE, EITHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES AND/OR CONDITIONS OF MERCHANTABILITY, OF SATISFACTORY QUALITY, OF FITNESS FOR A PARTICULAR PURPOSE, OF ACCURACY, OF QUIET ENJOYMENT, AND NON-INFRINGEMENT OF THIRD PARTY RIGHTS. APPLE DOES NOT WARRANT AGAINST INTERFERENCE WITH YOUR ENJOYMENT OF THE APPLE SOFTWARE, THAT THE FUNCTIONS CONTAINED IN THE APPLE SOFTWARE WILL MEET YOUR REQUIREMENTS, THAT THE OPERATION OF THE APPLE SOFTWARE WILL BE UNINTERRUPTED OR ERROR-FREE, OR THAT DEFECTS IN THE APPLE SOFTWARE WILL BE CORRECTED. NO ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY APPLE OR AN APPLE AUTHORIZED REPRESENTATIVE SHALL CREATE A WARRANTY. SHOULD THE APPLE SOFTWARE PROVE DEFECTIVE, YOU ASSUME THE ENTIRE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES OR LIMITATIONS ON APPLICABLE STATUTORY RIGHTS OF A CONSUMER, SO THE ABOVE EXCLUSION AND LIMITATIONS MAY NOT APPLY TO YOU. 4. Limitation Of Liability. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT SHALL APPLE BE LIABLE FOR PERSONAL INJURY, OR ANY INCIDENTAL, SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES WHATSOEVER, INCLUDING, WITHOUT LIMITATION, DAMAGES FOR LOSS OF PROFITS, LOSS OF DATA, BUSINESS INTERRUPTION OR ANY OTHER COMMERCIAL DAMAGES OR LOSSES, ARISING OUT OF OR RELATED TO YOUR USE OR INABILITY TO USE THE APPLE SOFTWARE, HOWEVER CAUSED, REGARDLESS OF THE THEORY OF LIABILITY (CONTRACT, TORT OR OTHERWISE) AND EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. SOME JURISDICTIONS DO NOT ALLOW THE LIMITATION OF LIABILITY FOR PERSONAL INJURY, OR OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THIS LIMITATION MAY NOT APPLY TO YOU. In no event shall Apple's total liability to you for all damages (other than as may be required by applicable law in cases involving personal injury) exceed the amount of fifty dollars ($50.00). The foregoing limitations will apply even if the above stated remedy fails of its essential purpose. -5. Export Control. You may not use or otherwise export or reexport the Apple Product except as authorized by United States law and the laws of the jurisdiction in which the Apple Product was obtained. In particular, but without limitation, the Apple Product may not be exported or re-exported (a) into any U.S. embargoed countries or (b) to anyone on the U.S. Treasury Department's list of Specially Designated Nationals or the U.S. Department of Commerce Denied Person?s List or Entity List. By using the Apple Product, you represent and warrant that you are not located in any such country or on any such list. +5. Export Control. You may not use or otherwise export or reexport the Apple Product except as authorized by United States law and the laws of the jurisdiction in which the Apple Product was obtained. In particular, but without limitation, the Apple Product may not be exported or re-exported (a) into any U.S. embargoed countries or (b) to anyone on the U.S. Treasury Department's list of Specially Designated Nationals or the U.S. Department of Commerce Denied Person?s List or Entity List. By using the Apple Product, you represent and warrant that you are not located in any such country or on any such list. 6. Government End Users. The Apple Software and related documentation are "Commercial Items", as that term is defined at 48 C.F.R. ?2.101, consisting of "Commercial Computer Software" and "Commercial Computer Software Documentation", as such terms are used in 48 C.F.R. ?12.212 or 48 C.F.R. ?227.7202, as applicable. Consistent with 48 C.F.R. ?12.212 or 48 C.F.R. ?227.7202-1through 227.7202-4, as applicable, the Commercial Computer Software and Commercial Computer Software Documentation are being licensed to U.S. Government end users (a) only as Commercial Items and (b) with only those rights as are granted to all other end users pursuant to the terms and conditions herein. Unpublished-rights reserved under the copyright laws of the United States. -7. Controlling Law and Severability. This License will be governed by and construed in accordance with the laws of the State of California, as applied to agreements entered into and to be performed entirely within California between California residents. This License shall not be governed by the United Nations Convention on Contracts for the International Sale of Goods, the application of which is expressly excluded. If for any reason a court of competent jurisdiction finds any provision, or portion thereof, to be unenforceable, the remainder of this License shall continue in full force and effect. +7. Controlling Law and Severability. This License will be governed by and construed in accordance with the laws of the State of California, as applied to agreements entered into and to be performed entirely within California between California residents. This License shall not be governed by the United Nations Convention on Contracts for the International Sale of Goods, the application of which is expressly excluded. If for any reason a court of competent jurisdiction finds any provision, or portion thereof, to be unenforceable, the remainder of this License shall continue in full force and effect. 8. Complete Agreement. This License constitutes the entire agreement between the parties with respect to the use of the Apple Software licensed hereunder and supersedes all prior or contemporaneous understandings regarding such subject matter. No amendment to or modification of this License will be binding unless in writing and signed by Apple. Any translation of this License is done for local requirements and in the event of a dispute between the English and any non-English versions, the English version of this License shall govern. |