summaryrefslogtreecommitdiff
path: root/indra/llplugin
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llplugin')
-rw-r--r--indra/llplugin/CMakeLists.txt63
-rw-r--r--indra/llplugin/llpluginclassmedia.cpp1196
-rw-r--r--indra/llplugin/llpluginclassmedia.h369
-rw-r--r--indra/llplugin/llpluginclassmediaowner.h83
-rw-r--r--indra/llplugin/llplugininstance.cpp173
-rw-r--r--indra/llplugin/llplugininstance.h104
-rw-r--r--indra/llplugin/llpluginmessage.cpp443
-rw-r--r--indra/llplugin/llpluginmessage.h142
-rw-r--r--indra/llplugin/llpluginmessageclasses.h58
-rw-r--r--indra/llplugin/llpluginmessagepipe.cpp317
-rw-r--r--indra/llplugin/llpluginmessagepipe.h93
-rw-r--r--indra/llplugin/llpluginprocesschild.cpp497
-rw-r--r--indra/llplugin/llpluginprocesschild.h113
-rw-r--r--indra/llplugin/llpluginprocessparent.cpp715
-rw-r--r--indra/llplugin/llpluginprocessparent.h170
-rw-r--r--indra/llplugin/llpluginsharedmemory.cpp507
-rw-r--r--indra/llplugin/llpluginsharedmemory.h130
-rw-r--r--indra/llplugin/slplugin/CMakeLists.txt55
-rw-r--r--indra/llplugin/slplugin/slplugin.cpp291
-rw-r--r--indra/llplugin/slplugin/slplugin_info.plist12
20 files changed, 5531 insertions, 0 deletions
diff --git a/indra/llplugin/CMakeLists.txt b/indra/llplugin/CMakeLists.txt
new file mode 100644
index 0000000000..51cb0d78b4
--- /dev/null
+++ b/indra/llplugin/CMakeLists.txt
@@ -0,0 +1,63 @@
+# -*- 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)
+
+if(NOT WORD_SIZE EQUAL 32)
+ if(WINDOWS)
+ add_definitions(/FIXED:NO)
+ else(WINDOWS) # not windows therefore gcc LINUX and DARWIN
+ add_definitions(-fPIC)
+ endif(WINDOWS)
+endif(NOT WORD_SIZE EQUAL 32)
+
+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..3d2eaed5c5
--- /dev/null
+++ b/indra/llplugin/llpluginclassmedia.cpp
@@ -0,0 +1,1196 @@
+/**
+ * @file llpluginclassmedia.cpp
+ * @brief LLPluginClassMedia handles a plugin which knows about the "media" message class.
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#include "linden_common.h"
+#include "indra_constants.h"
+
+#include "llpluginclassmedia.h"
+#include "llpluginmessageclasses.h"
+
+#include "llqtwebkit.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, bool debug, const std::string &user_data_path)
+{
+ LL_DEBUGS("Plugin") << "launcher: " << launcher_filename << LL_ENDL;
+ LL_DEBUGS("Plugin") << "plugin: " << plugin_filename << LL_ENDL;
+ LL_DEBUGS("Plugin") << "user_data_path: " << user_data_path << LL_ENDL;
+
+ mPlugin = new LLPluginProcessParent(this);
+ mPlugin->setSleepTime(mSleepTime);
+ mPlugin->init(launcher_filename, plugin_filename, debug, user_data_path);
+
+ 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;
+ mFullMediaWidth = 0;
+ mFullMediaHeight = 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;
+ mLastMouseX = 0;
+ mLastMouseY = 0;
+ mStatus = LLPluginClassMediaOwner::MEDIA_NONE;
+ mSleepTime = 1.0f / 100.0f;
+ mCanCut = false;
+ mCanCopy = false;
+ mCanPaste = false;
+ mMediaName.clear();
+ mMediaDescription.clear();
+ mBackgroundColor = LLColor4(1.0f, 1.0f, 1.0f, 1.0f);
+
+ // media_browser class
+ mNavigateURI.clear();
+ mNavigateResultCode = -1;
+ mNavigateResultString.clear();
+ mHistoryBackAvailable = false;
+ mHistoryForwardAvailable = false;
+ mStatusText.clear();
+ mProgressPercent = 0;
+ mClickURL.clear();
+ mClickTarget.clear();
+ mClickTargetType = TARGET_NONE;
+
+ // media_time class
+ mCurrentTime = 0.0f;
+ mDuration = 0.0f;
+ mCurrentRate = 0.0f;
+ mLoadedDuration = 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);
+ message.setValueReal("background_r", mBackgroundColor.mV[VX]);
+ message.setValueReal("background_g", mBackgroundColor.mV[VY]);
+ message.setValueReal("background_b", mBackgroundColor.mV[VZ]);
+ message.setValueReal("background_a", mBackgroundColor.mV[VW]);
+ 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)
+{
+ if((width > 0) && (height > 0))
+ {
+ mSetMediaWidth = width;
+ mSetMediaHeight = height;
+ }
+ else
+ {
+ mSetMediaWidth = -1;
+ mSetMediaHeight = -1;
+ }
+
+ setSizeInternal();
+}
+
+void LLPluginClassMedia::setSizeInternal(void)
+{
+ if((mSetMediaWidth > 0) && (mSetMediaHeight > 0))
+ {
+ mRequestedMediaWidth = mSetMediaWidth;
+ mRequestedMediaHeight = mSetMediaHeight;
+ }
+ else if((mNaturalMediaWidth > 0) && (mNaturalMediaHeight > 0))
+ {
+ mRequestedMediaWidth = mNaturalMediaWidth;
+ mRequestedMediaHeight = mNaturalMediaHeight;
+ }
+ else
+ {
+ mRequestedMediaWidth = mDefaultMediaWidth;
+ mRequestedMediaHeight = mDefaultMediaHeight;
+ }
+
+ // Save these for size/interest calculations
+ mFullMediaWidth = mRequestedMediaWidth;
+ mFullMediaHeight = mRequestedMediaHeight;
+
+ if(mAllowDownsample)
+ {
+ switch(mPriority)
+ {
+ case PRIORITY_SLIDESHOW:
+ 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);
+ }
+
+ if(mRequestedMediaWidth > 2048)
+ mRequestedMediaWidth = 2048;
+
+ if(mRequestedMediaHeight > 2048)
+ mRequestedMediaHeight = 2048;
+}
+
+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 button, int x, int y, MASK modifiers)
+{
+ if(type == MOUSE_EVENT_MOVE)
+ {
+ if((x == mLastMouseX) && (y == mLastMouseY))
+ {
+ // Don't spam unnecessary mouse move events.
+ return;
+ }
+
+ mLastMouseX = x;
+ mLastMouseY = y;
+ }
+
+ 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("button", button);
+
+ 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, MASK modifiers)
+{
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "text_event");
+
+ message.setValue("text", text);
+ message.setValue("modifiers", translateModifiers(modifiers));
+
+ 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);
+}
+
+const char* LLPluginClassMedia::priorityToString(EPriority priority)
+{
+ const char* result = "UNKNOWN";
+ switch(priority)
+ {
+ case PRIORITY_UNLOADED: result = "unloaded"; break;
+ case PRIORITY_STOPPED: result = "stopped"; break;
+ case PRIORITY_HIDDEN: result = "hidden"; break;
+ case PRIORITY_SLIDESHOW: result = "slideshow"; break;
+ case PRIORITY_LOW: result = "low"; break;
+ case PRIORITY_NORMAL: result = "normal"; break;
+ case PRIORITY_HIGH: result = "high"; break;
+ }
+
+ return result;
+}
+
+void LLPluginClassMedia::setPriority(EPriority priority)
+{
+ if(mPriority != priority)
+ {
+ mPriority = priority;
+
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "set_priority");
+
+ std::string priority_string = priorityToString(priority);
+ switch(priority)
+ {
+ case PRIORITY_UNLOADED:
+ mSleepTime = 1.0f;
+ break;
+ case PRIORITY_STOPPED:
+ mSleepTime = 1.0f;
+ break;
+ case PRIORITY_HIDDEN:
+ mSleepTime = 1.0f;
+ break;
+ case PRIORITY_SLIDESHOW:
+ mSleepTime = 1.0f;
+ break;
+ case PRIORITY_LOW:
+ mSleepTime = 1.0f / 25.0f;
+ break;
+ case PRIORITY_NORMAL:
+ mSleepTime = 1.0f / 50.0f;
+ break;
+ case PRIORITY_HIGH:
+ mSleepTime = 1.0f / 100.0f;
+ break;
+ }
+
+ message.setValue("priority", priority_string);
+
+ sendMessage(message);
+
+ if(mPlugin)
+ {
+ mPlugin->setSleepTime(mSleepTime);
+ }
+
+ LL_DEBUGS("PluginPriority") << this << ": setting priority to " << priority_string << LL_ENDL;
+
+ // This may affect the calculated size, so recalculate it here.
+ setSizeInternal();
+ }
+}
+
+void LLPluginClassMedia::setLowPrioritySizeLimit(int size)
+{
+ int power = nextPowerOf2(size);
+ if(mLowPrioritySizeLimit != power)
+ {
+ mLowPrioritySizeLimit = power;
+
+ // This may affect the calculated size, so recalculate it here.
+ setSizeInternal();
+ }
+}
+
+F64 LLPluginClassMedia::getCPUUsage()
+{
+ F64 result = 0.0f;
+
+ if(mPlugin)
+ {
+ result = mPlugin->getCPUUsage();
+ }
+
+ return result;
+}
+
+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);
+}
+
+LLPluginClassMedia::ETargetType getTargetTypeFromLLQtWebkit(int target_type)
+{
+ // convert a LinkTargetType value from llqtwebkit to an ETargetType
+ // so that we don't expose the llqtwebkit header in viewer code
+ switch (target_type)
+ {
+ case LinkTargetType::LTT_TARGET_NONE:
+ return LLPluginClassMedia::TARGET_NONE;
+
+ case LinkTargetType::LTT_TARGET_BLANK:
+ return LLPluginClassMedia::TARGET_BLANK;
+
+ case LinkTargetType::LTT_TARGET_EXTERNAL:
+ return LLPluginClassMedia::TARGET_EXTERNAL;
+
+ default:
+ return LLPluginClassMedia::TARGET_OTHER;
+ }
+}
+
+/* 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;
+ int previous_percent = mProgressPercent;
+
+ 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(message.hasValue("loaded_duration"))
+ {
+ mLoadedDuration = message.getValueReal("loaded_duration");
+ time_duration_updated = true;
+ }
+ else
+ {
+ // If the message doesn't contain a loaded_duration param, assume it's equal to duration
+ mLoadedDuration = mDuration;
+ }
+
+ // Calculate a percentage based on the loaded duration and total duration.
+ if(mDuration != 0.0f) // Don't divide by zero.
+ {
+ mProgressPercent = (int)((mLoadedDuration * 100.0f)/mDuration);
+ }
+
+ if(time_duration_updated)
+ {
+ mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_TIME_DURATION_UPDATED);
+ }
+
+ if(previous_percent != mProgressPercent)
+ {
+ mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PROGRESS_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 if(status == "done")
+ {
+ mStatus = LLPluginClassMediaOwner::MEDIA_DONE;
+ }
+ 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;
+
+ setSizeInternal();
+ }
+ 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 if(message_name == "name_text")
+ {
+ mMediaName = message.getValue("name");
+ mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_NAME_CHANGED);
+ }
+ 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");
+ U32 target_type = message.getValueU32("target_type");
+ mClickTargetType = ::getTargetTypeFromLLQtWebkit(target_type);
+ mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_HREF);
+ }
+ else if(message_name == "click_nofollow")
+ {
+ mClickURL = message.getValue("uri");
+ mClickTarget.clear();
+ mClickTargetType = TARGET_NONE;
+ 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::pluginLaunchFailed()
+{
+ mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PLUGIN_FAILED_LAUNCH);
+}
+
+/* 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);
+ }
+}
+
+float LLPluginClassMedia::getVolume()
+{
+ return mRequestedVolume;
+}
+
+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..ebb9099576
--- /dev/null
+++ b/indra/llplugin/llpluginclassmedia.h
@@ -0,0 +1,369 @@
+/**
+ * @file llpluginclassmedia.h
+ * @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class.
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#ifndef LL_LLPLUGINCLASSMEDIA_H
+#define LL_LLPLUGINCLASSMEDIA_H
+
+#include "llgltypes.h"
+#include "llpluginprocessparent.h"
+#include "llrect.h"
+#include "llpluginclassmediaowner.h"
+#include <queue>
+#include "v4color.h"
+
+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, bool debug, const std::string &user_data_path);
+
+ // 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;
+ int getFullWidth() const { return mFullMediaWidth; };
+ int getFullHeight() const { return mFullMediaHeight; };
+
+ // 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);
+
+ void setBackgroundColor(LLColor4 color) { mBackgroundColor = color; };
+
+ // 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 button, 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, MASK modifiers);
+
+ 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 pluginLaunchFailed();
+ /* virtual */ void pluginDied();
+
+
+ typedef enum
+ {
+ PRIORITY_UNLOADED, // media plugin isn't even loaded.
+ 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_SLIDESHOW, // media is in the far distance, updates very infrequently
+ PRIORITY_LOW, // media is in the 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;
+
+ static const char* priorityToString(EPriority priority);
+ void setPriority(EPriority priority);
+ void setLowPrioritySizeLimit(int size);
+
+ F64 getCPUUsage();
+
+ // 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; };
+
+ typedef enum
+ {
+ TARGET_NONE, // empty href target string
+ TARGET_BLANK, // target to open link in user's preferred browser
+ TARGET_EXTERNAL, // target to open link in external browser
+ TARGET_OTHER // nonempty and unsupported target type
+ }ETargetType;
+
+ // This is valid after MEDIA_EVENT_CLICK_LINK_HREF
+ ETargetType getClickTargetType() const { return mClickTargetType; };
+
+ 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);
+ float getVolume();
+
+ F64 getCurrentTime(void) const { return mCurrentTime; };
+ F64 getDuration(void) const { return mDuration; };
+ F64 getCurrentPlayRate(void) { return mCurrentRate; };
+ F64 getLoadedDuration(void) const { return mLoadedDuration; };
+
+ // 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;
+
+ // Full calculated media size (before auto-scale and downsample calculations)
+ int mFullMediaWidth;
+ int mFullMediaHeight;
+
+ // Actual media size being set (after 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;
+ int mLastMouseX;
+ int mLastMouseY;
+
+ LLPluginClassMediaOwner::EMediaStatus mStatus;
+
+ F64 mSleepTime;
+
+ bool mCanCut;
+ bool mCanCopy;
+ bool mCanPaste;
+
+ std::string mMediaName;
+ std::string mMediaDescription;
+
+ LLColor4 mBackgroundColor;
+
+ /////////////////////////////////////////
+ // 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;
+ ETargetType mClickTargetType;
+
+ /////////////////////////////////////////
+ // media_time class
+ F64 mCurrentTime;
+ F64 mDuration;
+ F64 mCurrentRate;
+ F64 mLoadedDuration;
+
+};
+
+#endif // LL_LLPLUGINCLASSMEDIA_H
diff --git a/indra/llplugin/llpluginclassmediaowner.h b/indra/llplugin/llpluginclassmediaowner.h
new file mode 100644
index 0000000000..6d369cd51a
--- /dev/null
+++ b/indra/llplugin/llpluginclassmediaowner.h
@@ -0,0 +1,83 @@
+/**
+ * @file llpluginclassmediaowner.h
+ * @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class.
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#ifndef LL_LLPLUGINCLASSMEDIAOWNER_H
+#define LL_LLPLUGINCLASSMEDIAOWNER_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_NAME_CHANGED, // browser has updated the name of the media (typically <title> tag)
+ 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_LAUNCH, // The plugin failed to launch
+ MEDIA_EVENT_PLUGIN_FAILED // The plugin 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)
+ MEDIA_DONE // finished playing (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..44e3b4950f
--- /dev/null
+++ b/indra/llplugin/llplugininstance.cpp
@@ -0,0 +1,173 @@
+/**
+ * @file llplugininstance.cpp
+ * @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing.
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#include "linden_common.h"
+
+#include "llplugininstance.h"
+
+#include "llapr.h"
+
+/** Virtual destructor. */
+LLPluginInstanceMessageListener::~LLPluginInstanceMessageListener()
+{
+}
+
+/**
+ * TODO:DOC describe how it's used
+ */
+const char *LLPluginInstance::PLUGIN_INIT_FUNCTION_NAME = "LLPluginInitEntryPoint";
+
+/**
+ * Constructor.
+ *
+ * @param[in] owner Plugin instance. TODO:DOC is this a good description of what "owner" is?
+ */
+LLPluginInstance::LLPluginInstance(LLPluginInstanceMessageListener *owner) :
+ mDSOHandle(NULL),
+ mPluginUserData(NULL),
+ mPluginSendMessageFunction(NULL)
+{
+ mOwner = owner;
+}
+
+/**
+ * Destructor.
+ */
+LLPluginInstance::~LLPluginInstance()
+{
+ if(mDSOHandle != NULL)
+ {
+ apr_dso_unload(mDSOHandle);
+ mDSOHandle = NULL;
+ }
+}
+
+/**
+ * Dynamically loads the plugin and runs the plugin's init function.
+ *
+ * @param[in] plugin_file Name of plugin dll/dylib/so. TODO:DOC is this correct? see .h
+ * @return 0 if successful, APR error code or error code from the plugin's init function on failure.
+ */
+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;
+}
+
+/**
+ * Sends a message to the plugin.
+ *
+ * @param[in] message Message
+ */
+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;
+ }
+}
+
+/**
+ * Idle. TODO:DOC what's the purpose of this?
+ *
+ */
+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);
+}
+
+/**
+ * Plugin receives message from plugin loader shell.
+ *
+ * @param[in] message_string Message
+ */
+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..c11d5ab5d4
--- /dev/null
+++ b/indra/llplugin/llplugininstance.h
@@ -0,0 +1,104 @@
+/**
+ * @file llplugininstance.h
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#ifndef LL_LLPLUGININSTANCE_H
+#define LL_LLPLUGININSTANCE_H
+
+#include "llstring.h"
+#include "llapr.h"
+
+#include "apr_dso.h"
+
+/**
+ * @brief LLPluginInstanceMessageListener receives messages sent from the plugin loader shell to the plugin.
+ */
+class LLPluginInstanceMessageListener
+{
+public:
+ virtual ~LLPluginInstanceMessageListener();
+ /** Plugin receives message from plugin loader shell. */
+ virtual void receivePluginMessage(const std::string &message) = 0;
+};
+
+/**
+ * @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing.
+ */
+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);
+
+ // TODO:DOC is this comment obsolete? can't find "send_count" anywhere in indra tree.
+ // 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);
+
+ /** The signature of the function for sending a message from plugin to plugin loader shell.
+ *
+ * @param[in] message_string Null-terminated C string
+ * @param[in] user_data The opaque reference that the callee supplied during setup.
+ */
+ typedef void (*sendMessageFunction) (const char *message_string, void **user_data);
+
+ /** The signature of the plugin init function. TODO:DOC check direction (pluging loader shell to plugin?)
+ *
+ * @param[in] host_user_data Data from plugin loader shell.
+ * @param[in] plugin_send_function Function for sending from the plugin loader shell to plugin.
+ */
+ typedef int (*pluginInitFunction) (sendMessageFunction host_send_func, void *host_user_data, sendMessageFunction *plugin_send_func, void **plugin_user_data);
+
+ /** Name of plugin init function */
+ 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..f76d848a70
--- /dev/null
+++ b/indra/llplugin/llpluginmessage.cpp
@@ -0,0 +1,443 @@
+/**
+ * @file llpluginmessage.cpp
+ * @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins.
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#include "linden_common.h"
+
+#include "llpluginmessage.h"
+#include "llsdserialize.h"
+#include "u64.h"
+
+/**
+ * Constructor.
+ */
+LLPluginMessage::LLPluginMessage()
+{
+}
+
+/**
+ * Constructor.
+ *
+ * @param[in] p Existing message
+ */
+LLPluginMessage::LLPluginMessage(const LLPluginMessage &p)
+{
+ mMessage = p.mMessage;
+}
+
+/**
+ * Constructor.
+ *
+ * @param[in] message_class Message class
+ * @param[in] message_name Message name
+ */
+LLPluginMessage::LLPluginMessage(const std::string &message_class, const std::string &message_name)
+{
+ setMessage(message_class, message_name);
+}
+
+
+/**
+ * Destructor.
+ */
+LLPluginMessage::~LLPluginMessage()
+{
+}
+
+/**
+ * Reset all internal state.
+ */
+void LLPluginMessage::clear()
+{
+ mMessage = LLSD::emptyMap();
+ mMessage["params"] = LLSD::emptyMap();
+}
+
+/**
+ * Sets the message class and name. Also has the side-effect of clearing any key-value pairs in the message.
+ *
+ * @param[in] message_class Message class
+ * @param[in] message_name Message name
+ */
+void LLPluginMessage::setMessage(const std::string &message_class, const std::string &message_name)
+{
+ clear();
+ mMessage["class"] = message_class;
+ mMessage["name"] = message_name;
+}
+
+/**
+ * Sets a key/value pair in the message, where the value is a string.
+ *
+ * @param[in] key Key
+ * @param[in] value String value
+ */
+void LLPluginMessage::setValue(const std::string &key, const std::string &value)
+{
+ mMessage["params"][key] = value;
+}
+
+/**
+ * Sets a key/value pair in the message, where the value is LLSD.
+ *
+ * @param[in] key Key
+ * @param[in] value LLSD value
+ */
+void LLPluginMessage::setValueLLSD(const std::string &key, const LLSD &value)
+{
+ mMessage["params"][key] = value;
+}
+
+/**
+ * Sets a key/value pair in the message, where the value is signed 32-bit.
+ *
+ * @param[in] key Key
+ * @param[in] value 32-bit signed value
+ */
+void LLPluginMessage::setValueS32(const std::string &key, S32 value)
+{
+ mMessage["params"][key] = value;
+}
+
+/**
+ * Sets a key/value pair in the message, where the value is unsigned 32-bit. The value is stored as a string beginning with "0x".
+ *
+ * @param[in] key Key
+ * @param[in] value 32-bit unsigned value
+ */
+void LLPluginMessage::setValueU32(const std::string &key, U32 value)
+{
+ std::stringstream temp;
+ temp << "0x" << std::hex << value;
+ setValue(key, temp.str());
+}
+
+/**
+ * Sets a key/value pair in the message, where the value is a bool.
+ *
+ * @param[in] key Key
+ * @param[in] value Boolean value
+ */
+void LLPluginMessage::setValueBoolean(const std::string &key, bool value)
+{
+ mMessage["params"][key] = value;
+}
+
+/**
+ * Sets a key/value pair in the message, where the value is a double.
+ *
+ * @param[in] key Key
+ * @param[in] value Boolean value
+ */
+void LLPluginMessage::setValueReal(const std::string &key, F64 value)
+{
+ mMessage["params"][key] = value;
+}
+
+/**
+ * Sets a key/value pair in the message, where the value is a pointer. The pointer is stored as a string.
+ *
+ * @param[in] key Key
+ * @param[in] value Pointer value
+ */
+void LLPluginMessage::setValuePointer(const std::string &key, void* value)
+{
+ std::stringstream temp;
+ // iostreams should output pointer values in hex with an initial 0x by default.
+ temp << value;
+ setValue(key, temp.str());
+}
+
+/**
+ * Gets the message class.
+ *
+ * @return Message class
+ */
+std::string LLPluginMessage::getClass(void) const
+{
+ return mMessage["class"];
+}
+
+/**
+ * Gets the message name.
+ *
+ * @return Message name
+ */
+std::string LLPluginMessage::getName(void) const
+{
+ return mMessage["name"];
+}
+
+/**
+ * Returns true if the specified key exists in this message (useful for optional parameters).
+ *
+ * @param[in] key Key
+ *
+ * @return True if key exists, false otherwise.
+ */
+bool LLPluginMessage::hasValue(const std::string &key) const
+{
+ bool result = false;
+
+ if(mMessage["params"].has(key))
+ {
+ result = true;
+ }
+
+ return result;
+}
+
+/**
+ * Gets the value of a key as a string. If the key does not exist, an empty string will be returned.
+ *
+ * @param[in] key Key
+ *
+ * @return String value of key if key exists, empty string if key does not exist.
+ */
+std::string LLPluginMessage::getValue(const std::string &key) const
+{
+ std::string result;
+
+ if(mMessage["params"].has(key))
+ {
+ result = mMessage["params"][key].asString();
+ }
+
+ return result;
+}
+
+/**
+ * Gets the value of a key as LLSD. If the key does not exist, a null LLSD will be returned.
+ *
+ * @param[in] key Key
+ *
+ * @return LLSD value of key if key exists, null LLSD if key does not exist.
+ */
+LLSD LLPluginMessage::getValueLLSD(const std::string &key) const
+{
+ LLSD result;
+
+ if(mMessage["params"].has(key))
+ {
+ result = mMessage["params"][key];
+ }
+
+ return result;
+}
+
+/**
+ * Gets the value of a key as signed 32-bit int. If the key does not exist, 0 will be returned.
+ *
+ * @param[in] key Key
+ *
+ * @return Signed 32-bit int value of key if key exists, 0 if key does not exist.
+ */
+S32 LLPluginMessage::getValueS32(const std::string &key) const
+{
+ S32 result = 0;
+
+ if(mMessage["params"].has(key))
+ {
+ result = mMessage["params"][key].asInteger();
+ }
+
+ return result;
+}
+
+/**
+ * Gets the value of a key as unsigned 32-bit int. If the key does not exist, 0 will be returned.
+ *
+ * @param[in] key Key
+ *
+ * @return Unsigned 32-bit int value of key if key exists, 0 if key does not exist.
+ */
+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;
+}
+
+/**
+ * Gets the value of a key as a bool. If the key does not exist, false will be returned.
+ *
+ * @param[in] key Key
+ *
+ * @return Boolean value of key if it exists, false otherwise.
+ */
+bool LLPluginMessage::getValueBoolean(const std::string &key) const
+{
+ bool result = false;
+
+ if(mMessage["params"].has(key))
+ {
+ result = mMessage["params"][key].asBoolean();
+ }
+
+ return result;
+}
+
+/**
+ * Gets the value of a key as a double. If the key does not exist, 0 will be returned.
+ *
+ * @param[in] key Key
+ *
+ * @return Value as a double if key exists, 0 otherwise.
+ */
+F64 LLPluginMessage::getValueReal(const std::string &key) const
+{
+ F64 result = 0.0f;
+
+ if(mMessage["params"].has(key))
+ {
+ result = mMessage["params"][key].asReal();
+ }
+
+ return result;
+}
+
+/**
+ * Gets the value of a key as a pointer. If the key does not exist, NULL will be returned.
+ *
+ * @param[in] key Key
+ *
+ * @return Pointer value if key exists, NULL otherwise.
+ */
+void* LLPluginMessage::getValuePointer(const std::string &key) const
+{
+ void* result = NULL;
+
+ if(mMessage["params"].has(key))
+ {
+ std::string value = mMessage["params"][key].asString();
+
+ result = (void*)llstrtou64(value.c_str(), NULL, 16);
+ }
+
+ return result;
+}
+
+/**
+ * Flatten the message into a string.
+ *
+ * @return Message as a string.
+ */
+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();
+}
+
+/**
+ * Parse an incoming message into component parts. Clears all existing state before starting the parse.
+ *
+ * @return Returns -1 on failure, otherwise returns the number of key/value pairs in the incoming message.
+ */
+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;
+}
+
+
+/**
+ * Destructor
+ */
+LLPluginMessageListener::~LLPluginMessageListener()
+{
+ // TODO: should listeners have a way to ensure they're removed from dispatcher lists when deleted?
+}
+
+
+/**
+ * Destructor
+ */
+LLPluginMessageDispatcher::~LLPluginMessageDispatcher()
+{
+
+}
+
+/**
+ * Add a message listener. TODO:DOC need more info on what uses this. when are multiple listeners needed?
+ *
+ * @param[in] listener Message listener
+ */
+void LLPluginMessageDispatcher::addPluginMessageListener(LLPluginMessageListener *listener)
+{
+ mListeners.insert(listener);
+}
+
+/**
+ * Remove a message listener.
+ *
+ * @param[in] listener Message listener
+ */
+void LLPluginMessageDispatcher::removePluginMessageListener(LLPluginMessageListener *listener)
+{
+ mListeners.erase(listener);
+}
+
+/**
+ * Distribute a message to all message listeners.
+ *
+ * @param[in] message Message
+ */
+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..cbd31e0964
--- /dev/null
+++ b/indra/llplugin/llpluginmessage.h
@@ -0,0 +1,142 @@
+/**
+ * @file llpluginmessage.h
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#ifndef LL_LLPLUGINMESSAGE_H
+#define LL_LLPLUGINMESSAGE_H
+
+#include "llsd.h"
+
+/**
+ * @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins.
+ */
+class LLPluginMessage
+{
+ LOG_CLASS(LLPluginMessage);
+public:
+ LLPluginMessage();
+ LLPluginMessage(const LLPluginMessage &p);
+ 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);
+ void setValuePointer(const std::string &key, void *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;
+
+ // get the value of a key as a pointer.
+ void* getValuePointer(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;
+
+};
+
+/**
+ * @brief Listener for plugin messages.
+ */
+class LLPluginMessageListener
+{
+public:
+ virtual ~LLPluginMessageListener();
+ /** Plugin receives message from plugin loader shell. */
+ virtual void receivePluginMessage(const LLPluginMessage &message) = 0;
+
+};
+
+/**
+ * @brief Dispatcher for plugin messages.
+ *
+ * Manages the set of plugin message listeners and distributes messages to plugin message listeners.
+ */
+class LLPluginMessageDispatcher
+{
+public:
+ virtual ~LLPluginMessageDispatcher();
+
+ void addPluginMessageListener(LLPluginMessageListener *);
+ void removePluginMessageListener(LLPluginMessageListener *);
+protected:
+ void dispatchPluginMessage(const LLPluginMessage &message);
+
+ /** A set of message listeners. */
+ typedef std::set<LLPluginMessageListener*> listener_set_t;
+ /** The set of message listeners. */
+ 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..01fddb92c5
--- /dev/null
+++ b/indra/llplugin/llpluginmessageclasses.h
@@ -0,0 +1,58 @@
+/**
+ * @file llpluginmessageclasses.h
+ * @brief This file defines the versions of existing message classes for LLPluginMessage.
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#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..cc193fca42
--- /dev/null
+++ b/indra/llplugin/llpluginmessagepipe.cpp
@@ -0,0 +1,317 @@
+/**
+ * @file llpluginmessagepipe.cpp
+ * @brief Classes that implement connections from the plugin system to pipes/pumps.
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#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..1ddb38de68
--- /dev/null
+++ b/indra/llplugin/llpluginmessagepipe.h
@@ -0,0 +1,93 @@
+/**
+ * @file llpluginmessagepipe.h
+ * @brief Classes that implement connections from the plugin system to pipes/pumps.
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#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..07fc82c770
--- /dev/null
+++ b/indra/llplugin/llpluginprocesschild.cpp
@@ -0,0 +1,497 @@
+/**
+ * @file llpluginprocesschild.cpp
+ * @brief LLPluginProcessChild handles the child side of the external-process plugin API.
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#include "linden_common.h"
+
+#include "llpluginprocesschild.h"
+#include "llplugininstance.h"
+#include "llpluginmessagepipe.h"
+#include "llpluginmessageclasses.h"
+
+static const F32 HEARTBEAT_SECONDS = 1.0f;
+static const F32 PLUGIN_IDLE_SECONDS = 1.0f / 100.0f; // Each call to idle will give the plugin this much time.
+
+LLPluginProcessChild::LLPluginProcessChild()
+{
+ mInstance = NULL;
+ mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP);
+ mSleepTime = PLUGIN_IDLE_SECONDS; // default: send idle messages at 100Hz
+ mCPUElapsed = 0.0f;
+}
+
+LLPluginProcessChild::~LLPluginProcessChild()
+{
+ if(mInstance != NULL)
+ {
+ sendMessageToPlugin(LLPluginMessage("base", "cleanup"));
+
+ // IMPORTANT: under some (unknown) circumstances the apr_dso_unload() triggered when mInstance is deleted
+ // appears to fail and lock up which means that a given instance of the slplugin process never exits.
+ // This is bad, especially when users try to update their version of SL - it fails because the slplugin
+ // process as well as a bunch of plugin specific files are locked and cannot be overwritten.
+ exit( 0 );
+ //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);
+ mCPUElapsed = 0.0f;
+ setState(STATE_PLUGIN_LOADED);
+ }
+ else
+ {
+ setState(STATE_ERROR);
+ }
+ }
+ break;
+
+ case STATE_PLUGIN_LOADED:
+ {
+ setState(STATE_PLUGIN_INITIALIZING);
+ LLPluginMessage message("base", "init");
+ message.setValue("user_data_path", mUserDataPath);
+ sendMessageToPlugin(message);
+ }
+ 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", PLUGIN_IDLE_SECONDS);
+ sendMessageToPlugin(message);
+
+ mInstance->idle();
+
+ if(mHeartbeat.hasExpired())
+ {
+
+ // This just proves that we're not stuck down inside the plugin code.
+ LLPluginMessage heartbeat(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "heartbeat");
+
+ // Calculate the approximage CPU usage fraction (floating point value between 0 and 1) used by the plugin this heartbeat cycle.
+ // Note that this will not take into account any threads or additional processes the plugin spawns, but it's a first approximation.
+ // If we could write OS-specific functions to query the actual CPU usage of this process, that would be a better approximation.
+ heartbeat.setValueReal("cpu_usage", mCPUElapsed / mHeartbeat.getElapsedTimeF64());
+
+ sendMessageToParent(heartbeat);
+
+ mHeartbeat.reset();
+ mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS);
+ mCPUElapsed = 0.0f;
+ }
+ }
+ // 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;
+ LLTimer elapsed;
+
+ mInstance->sendMessage(buffer);
+
+ mCPUElapsed += elapsed.getElapsedTimeF64();
+}
+
+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");
+ mUserDataPath = parsed.getValue("user_data_path");
+ }
+ 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);
+ message.setValuePointer("address", region->getMappedAddress());
+ 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)
+ {
+ LLTimer elapsed;
+
+ mInstance->sendMessage(message);
+
+ mCPUElapsed += elapsed.getElapsedTimeF64();
+ }
+}
+
+/* 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)
+ {
+ LL_DEBUGS("Plugin") << "Passing through to parent: " << message << LL_ENDL;
+ 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..1cfd9dcaf9
--- /dev/null
+++ b/indra/llplugin/llpluginprocesschild.h
@@ -0,0 +1,113 @@
+/**
+ * @file llpluginprocesschild.h
+ * @brief LLPluginProcessChild handles the child side of the external-process plugin API.
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#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;
+
+ std::string mUserDataPath;
+
+ LLPluginInstance *mInstance;
+
+ typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType;
+ sharedMemoryRegionsType mSharedMemoryRegions;
+
+ LLTimer mHeartbeat;
+ F64 mSleepTime;
+ F64 mCPUElapsed;
+
+};
+
+#endif // LL_LLPLUGINPROCESSCHILD_H
diff --git a/indra/llplugin/llpluginprocessparent.cpp b/indra/llplugin/llpluginprocessparent.cpp
new file mode 100644
index 0000000000..49f9783824
--- /dev/null
+++ b/indra/llplugin/llpluginprocessparent.cpp
@@ -0,0 +1,715 @@
+/**
+ * @file llpluginprocessparent.cpp
+ * @brief LLPluginProcessParent handles the parent side of the external-process plugin API.
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#include "linden_common.h"
+
+#include "llpluginprocessparent.h"
+#include "llpluginmessagepipe.h"
+#include "llpluginmessageclasses.h"
+
+#include "llapr.h"
+
+//virtual
+LLPluginProcessParentOwner::~LLPluginProcessParentOwner()
+{
+
+}
+
+LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner)
+{
+ mOwner = owner;
+ mBoundPort = 0;
+ mState = STATE_UNINITIALIZED;
+ mDisableTimeout = false;
+ mDebug = false;
+
+ mPluginLaunchTimeout = 60.0f;
+ mPluginLockupTimeout = 15.0f;
+
+ // Don't start the timer here -- start it when we actually launch the plugin process.
+ mHeartbeat.stop();
+}
+
+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);
+ }
+
+ // orphaning the process means it won't be killed when the LLProcessLauncher is destructed.
+ // This is what we want -- it should exit cleanly once it notices the sockets have been closed.
+ mProcess.orphan();
+ killSockets();
+}
+
+void LLPluginProcessParent::killSockets(void)
+{
+ killMessagePipe();
+ mListenSocket.reset();
+ mSocket.reset();
+}
+
+void LLPluginProcessParent::errorState(void)
+{
+ if(mState < STATE_RUNNING)
+ setState(STATE_LAUNCH_FAILURE);
+ else
+ setState(STATE_ERROR);
+}
+
+void LLPluginProcessParent::init(const std::string &launcher_filename, const std::string &plugin_filename, bool debug, const std::string &user_data_path)
+{
+ mProcess.setExecutable(launcher_filename);
+ mPluginFile = plugin_filename;
+ mCPUUsage = 0.0f;
+ mDebug = debug;
+ mUserDataPath = user_data_path;
+
+ 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.
+ errorState();
+ }
+
+ 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;
+ errorState();
+ }
+ }
+
+ if((mSocketError != APR_SUCCESS) && (mState <= STATE_RUNNING))
+ {
+ // The socket is in an error state -- the plugin is gone.
+ LL_WARNS("Plugin") << "Socket hit an error state (" << mSocketError << ")" << LL_ENDL;
+ errorState();
+ }
+
+ // 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();
+ errorState();
+ 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();
+ errorState();
+ 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();
+ errorState();
+ break;
+ }
+ mBoundPort = bound_addr->port;
+
+ if(mBoundPort == 0)
+ {
+ LL_WARNS("Plugin") << "Bound port number unknown, bailing out." << LL_ENDL;
+
+ killSockets();
+ errorState();
+ 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();
+ errorState();
+ break;
+ }
+
+ apr_socket_timeout_set(mListenSocket->getSocket(), 0);
+ if(ll_apr_warn_status(status))
+ {
+ killSockets();
+ errorState();
+ 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();
+ errorState();
+ 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)
+ {
+ errorState();
+ }
+ else
+ {
+ if(mDebug)
+ {
+ #if LL_DARWIN
+ // If we're set to debug, start up a gdb instance in a new terminal window and have it attach to the plugin process and continue.
+
+ // The command we're constructing would look like this on the command line:
+ // osascript -e 'tell application "Terminal"' -e 'set win to do script "gdb -pid 12345"' -e 'do script "continue" in win' -e 'end tell'
+
+ std::stringstream cmd;
+
+ mDebugger.setExecutable("/usr/bin/osascript");
+ mDebugger.addArgument("-e");
+ mDebugger.addArgument("tell application \"Terminal\"");
+ mDebugger.addArgument("-e");
+ cmd << "set win to do script \"gdb -pid " << mProcess.getProcessID() << "\"";
+ mDebugger.addArgument(cmd.str());
+ mDebugger.addArgument("-e");
+ mDebugger.addArgument("do script \"continue\" in win");
+ mDebugger.addArgument("-e");
+ mDebugger.addArgument("end tell");
+ mDebugger.launch();
+
+ #endif
+ }
+
+ // This will allow us to time out if the process never starts.
+ mHeartbeat.start();
+ mHeartbeat.setTimerExpirySec(mPluginLaunchTimeout);
+ setState(STATE_LAUNCHED);
+ }
+ }
+ break;
+
+ case STATE_LAUNCHED:
+ // waiting for the plugin to connect
+ if(pluginLockedUpOrQuit())
+ {
+ errorState();
+ }
+ 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())
+ {
+ errorState();
+ }
+ 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);
+ message.setValue("user_data_path", mUserDataPath);
+ sendMessage(message);
+ }
+
+ setState(STATE_LOADING);
+ break;
+
+ case STATE_LOADING:
+ // The load_plugin_response message will kick us from here into STATE_RUNNING
+ if(pluginLockedUpOrQuit())
+ {
+ errorState();
+ }
+ break;
+
+ case STATE_RUNNING:
+ if(pluginLockedUpOrQuit())
+ {
+ errorState();
+ }
+ break;
+
+ case STATE_EXITING:
+ if(!mProcess.isRunning())
+ {
+ setState(STATE_CLEANUP);
+ }
+ else if(pluginLockedUp())
+ {
+ LL_WARNS("Plugin") << "timeout in exiting state, bailing out" << llendl;
+ errorState();
+ }
+ break;
+
+ case STATE_LAUNCH_FAILURE:
+ if(mOwner != NULL)
+ {
+ mOwner->pluginLaunchFailed();
+ }
+ setState(STATE_CLEANUP);
+ break;
+
+ case STATE_ERROR:
+ if(mOwner != NULL)
+ {
+ mOwner->pluginDied();
+ }
+ setState(STATE_CLEANUP);
+ break;
+
+ case STATE_CLEANUP:
+ // Don't do a kill here anymore -- closing the sockets is the new 'kill'.
+ mProcess.orphan();
+ 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;
+ errorState();
+ }
+
+ }
+ else if(message_name == "load_plugin_response")
+ {
+ if(mState == STATE_LOADING)
+ {
+ // Plugin has been loaded.
+
+ mPluginVersionString = message.getValue("plugin_version");
+ LL_INFOS("Plugin") << "plugin version string: " << mPluginVersionString << LL_ENDL;
+
+ // 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;
+ }
+
+ // 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;
+ errorState();
+ }
+ }
+ else if(message_name == "heartbeat")
+ {
+ // this resets our timer.
+ mHeartbeat.setTimerExpirySec(mPluginLockupTimeout);
+
+ mCPUUsage = message.getValueReal("cpu_usage");
+
+ LL_DEBUGS("Plugin") << "cpu usage reported as " << mCPUUsage << LL_ENDL;
+
+ }
+ 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 && !mDebug)
+ {
+ 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 is running and has expired, the plugin has locked up.
+ return (mHeartbeat.getStarted() && mHeartbeat.hasExpired());
+}
+
diff --git a/indra/llplugin/llpluginprocessparent.h b/indra/llplugin/llpluginprocessparent.h
new file mode 100644
index 0000000000..524cd9923f
--- /dev/null
+++ b/indra/llplugin/llpluginprocessparent.h
@@ -0,0 +1,170 @@
+/**
+ * @file llpluginprocessparent.h
+ * @brief LLPluginProcessParent handles the parent side of the external-process plugin API.
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#ifndef LL_LLPLUGINPROCESSPARENT_H
+#define LL_LLPLUGINPROCESSPARENT_H
+
+#include "llapr.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 pluginLaunchFailed() {};
+ 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, bool debug, const std::string &user_data_path);
+ 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);
+
+ // Go to the proper error state
+ void errorState(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; };
+
+ void setLaunchTimeout(F32 timeout) { mPluginLaunchTimeout = timeout; };
+ void setLockupTimeout(F32 timeout) { mPluginLockupTimeout = timeout; };
+
+ F64 getCPUUsage() { return mCPUUsage; };
+
+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_LAUNCH_FAILURE, // Failure before plugin loaded
+ 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;
+
+ std::string mUserDataPath;
+
+ LLPluginProcessParentOwner *mOwner;
+
+ typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType;
+ sharedMemoryRegionsType mSharedMemoryRegions;
+
+ LLSD mMessageClassVersions;
+ std::string mPluginVersionString;
+
+ LLTimer mHeartbeat;
+ F64 mSleepTime;
+ F64 mCPUUsage;
+
+ bool mDisableTimeout;
+ bool mDebug;
+
+ LLProcessLauncher mDebugger;
+
+ F32 mPluginLaunchTimeout; // Somewhat longer timeout for initial launch.
+ F32 mPluginLockupTimeout; // If we don't receive a heartbeat in this many seconds, we declare the plugin locked up.
+
+};
+
+#endif // LL_LLPLUGINPROCESSPARENT_H
diff --git a/indra/llplugin/llpluginsharedmemory.cpp b/indra/llplugin/llpluginsharedmemory.cpp
new file mode 100644
index 0000000000..9c18b410c7
--- /dev/null
+++ b/indra/llplugin/llpluginsharedmemory.cpp
@@ -0,0 +1,507 @@
+/**
+ * @file llpluginsharedmemory.cpp
+ * LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API.
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#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();
+}
+
+/**
+ * @brief LLPluginSharedMemoryImpl is the platform-dependent implementation of LLPluginSharedMemory. TODO:DOC is this necessary/sufficient? kinda obvious.
+ *
+ */
+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
+
+};
+
+/**
+ * Constructor. Creates a shared memory segment.
+ */
+LLPluginSharedMemory::LLPluginSharedMemory()
+{
+ mSize = 0;
+ mMappedAddress = NULL;
+ mNeedsDestroy = false;
+
+ mImpl = new LLPluginSharedMemoryPlatformImpl;
+}
+
+/**
+ * Destructor. Uses destroy() and detach() to ensure shared memory segment is cleaned up.
+ */
+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..00c54ef08c
--- /dev/null
+++ b/indra/llplugin/llpluginsharedmemory.h
@@ -0,0 +1,130 @@
+/**
+ * @file llpluginsharedmemory.h
+ *
+ * @cond
+ * $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$
+ * @endcond
+ */
+
+#ifndef LL_LLPLUGINSHAREDMEMORY_H
+#define LL_LLPLUGINSHAREDMEMORY_H
+
+class LLPluginSharedMemoryPlatformImpl;
+
+/**
+ * @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API.
+ *
+ */
+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.
+
+ /**
+ * Creates a shared memory segment, with a name which is guaranteed to be unique on the host at the current time. Used by parent.
+ * Message transactions will (? TODO:DOC - should? must?) ensure child attaches after parent creates and detaches before parent destroys.
+ *
+ * @param[in] size Shared memory size in TODO:DOC units = bytes?.
+ *
+ * @return False for failure, true for success.
+ */
+ bool create(size_t size);
+ /**
+ * Destroys a shared memory segment. Used by parent.
+ * Message transactions will (? TODO:DOC - should? must?) ensure child attaches after parent creates and detaches before parent destroys.
+ *
+ * @return True. TODO:DOC - always returns true. Is this the intended behavior?
+ */
+ bool destroy(void);
+
+ /**
+ * Creates and attaches a name to a shared memory segment. TODO:DOC what's the difference between attach() and create()?
+ *
+ * @param[in] name Name to attach to memory segment
+ * @param[in] size Size of memory segment TODO:DOC in bytes?
+ *
+ * @return False on failure, true otherwise.
+ */
+ bool attach(const std::string &name, size_t size);
+ /**
+ * Detaches shared memory segment.
+ *
+ * @return False on failure, true otherwise.
+ */
+ bool detach(void);
+
+ /**
+ * Checks if shared memory is mapped to a non-null address.
+ *
+ * @return True if memory address is non-null, false otherwise.
+ */
+ bool isMapped(void) const { return (mMappedAddress != NULL); };
+ /**
+ * Get pointer to shared memory.
+ *
+ * @return Pointer to shared memory.
+ */
+ void *getMappedAddress(void) const { return mMappedAddress; };
+ /**
+ * Get size of shared memory.
+ *
+ * @return Size of shared memory in bytes. TODO:DOC are bytes the correct unit?
+ */
+ size_t getSize(void) const { return mSize; };
+ /**
+ * Get name of shared memory.
+ *
+ * @return Name of shared memory.
+ */
+ 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..23dc532ba5
--- /dev/null
+++ b/indra/llplugin/slplugin/slplugin.cpp
@@ -0,0 +1,291 @@
+/**
+ * @file slplugin.cpp
+ * @brief Loader shell for plugins, intended to be launched by the plugin host application, which directly loads a plugin dynamic library.
+ *
+ * @cond
+ *
+ * $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$
+ *
+ * @endcond
+ */
+
+
+#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;
+}
+
+// Taken from : http://blog.kalmbachnet.de/?postid=75
+// The MSVC 2005 CRT forces the call of the default-debugger (normally Dr.Watson)
+// even with the other exception handling code. This (terrifying) piece of code
+// patches things so that doesn't happen.
+LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter(
+ LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter )
+{
+ return NULL;
+}
+
+BOOL PreventSetUnhandledExceptionFilter()
+{
+// WARNING: This won't work on 64-bit Windows systems so we turn it off it.
+// It should work for any flavor of 32-bit Windows we care about.
+// If it's off, sometimes you will see an OS message when a plugin crashes
+#ifndef _WIN64
+ HMODULE hKernel32 = LoadLibraryA( "kernel32.dll" );
+ if ( NULL == hKernel32 )
+ return FALSE;
+
+ void *pOrgEntry = GetProcAddress( hKernel32, "SetUnhandledExceptionFilter" );
+ if( NULL == pOrgEntry )
+ return FALSE;
+
+ unsigned char newJump[ 100 ];
+ DWORD dwOrgEntryAddr = (DWORD)pOrgEntry;
+ dwOrgEntryAddr += 5; // add 5 for 5 op-codes for jmp far
+ void *pNewFunc = &MyDummySetUnhandledExceptionFilter;
+ DWORD dwNewEntryAddr = (DWORD) pNewFunc;
+ DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr;
+
+ newJump[ 0 ] = 0xE9; // JMP absolute
+ memcpy( &newJump[ 1 ], &dwRelativeAddr, sizeof( pNewFunc ) );
+ SIZE_T bytesWritten;
+ BOOL bRet = WriteProcessMemory( GetCurrentProcess(), pOrgEntry, newJump, sizeof( pNewFunc ) + 1, &bytesWritten );
+ return bRet;
+#else
+ return FALSE;
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// 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 );
+ PreventSetUnhandledExceptionFilter();
+}
+
+bool checkExceptionHandler()
+{
+ bool ok = true;
+ LPTOP_LEVEL_EXCEPTION_FILTER prev_filter;
+ prev_filter = SetUnhandledExceptionFilter(myWin32ExceptionHandler);
+
+ PreventSetUnhandledExceptionFilter();
+
+ 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>