diff options
| author | Monroe Williams <monroe@lindenlab.com> | 2009-08-27 19:00:18 +0000 | 
|---|---|---|
| committer | Monroe Williams <monroe@lindenlab.com> | 2009-08-27 19:00:18 +0000 | 
| commit | 745845f79987e4b4ab7f5728746a0eda8898930f (patch) | |
| tree | f10efd4a638a6a7eda92a960cdb97e5256ff736a /indra/llplugin | |
| parent | 71344b233d5ae3d5262a492b636af04544952611 (diff) | |
svn merge -r 129841:129910 svn+ssh://svn.lindenlab.com/svn/linden/branches/moss/pluginapi_05-merge@129910
svn merge -r 129913:131718 svn+ssh://svn.lindenlab.com/svn/linden/branches/pluginapi/pluginapi_05
Some branch shenannigans in the pluginapi_05 branch caused this to become a two-part merge.
Diffstat (limited to 'indra/llplugin')
20 files changed, 4834 insertions, 0 deletions
| diff --git a/indra/llplugin/CMakeLists.txt b/indra/llplugin/CMakeLists.txt new file mode 100644 index 0000000000..6706775d4f --- /dev/null +++ b/indra/llplugin/CMakeLists.txt @@ -0,0 +1,55 @@ +# -*- cmake -*- + +project(llplugin) + +include(00-Common) +include(LLCommon) +include(LLImage) +include(LLMath) +include(LLMessage) +include(LLRender) +include(LLXML) +include(LLWindow) + +include_directories( +    ${LLCOMMON_INCLUDE_DIRS} +    ${LLIMAGE_INCLUDE_DIRS} +    ${LLMATH_INCLUDE_DIRS} +    ${LLMESSAGE_INCLUDE_DIRS} +    ${LLRENDER_INCLUDE_DIRS} +    ${LLXML_INCLUDE_DIRS} +    ${LLWINDOW_INCLUDE_DIRS} +    ) + +set(llplugin_SOURCE_FILES +    llpluginclassmedia.cpp +    llplugininstance.cpp +    llpluginmessage.cpp +    llpluginmessagepipe.cpp +    llpluginprocesschild.cpp +    llpluginprocessparent.cpp +    llpluginsharedmemory.cpp +    ) + +set(llplugin_HEADER_FILES +    CMakeLists.txt + +    llpluginclassmedia.h +    llpluginclassmediaowner.h +    llplugininstance.h +    llpluginmessage.h +    llpluginmessageclasses.h +    llpluginmessagepipe.h +    llpluginprocesschild.h +    llpluginprocessparent.h +    llpluginsharedmemory.h +    ) + +set_source_files_properties(${llplugin_HEADER_FILES} +                            PROPERTIES HEADER_FILE_ONLY TRUE) + +list(APPEND llplugin_SOURCE_FILES ${llplugin_HEADER_FILES}) + +add_library (llplugin ${llplugin_SOURCE_FILES}) + +add_subdirectory(slplugin) diff --git a/indra/llplugin/llpluginclassmedia.cpp b/indra/llplugin/llpluginclassmedia.cpp new file mode 100644 index 0000000000..54f153d182 --- /dev/null +++ b/indra/llplugin/llpluginclassmedia.cpp @@ -0,0 +1,1042 @@ +/**  + * @file llpluginclassmedia.cpp + * @brief LLPluginClassMedia handles a plugin which knows about the "media" message class. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "indra_constants.h" + +#include "llpluginclassmedia.h" +#include "llpluginmessageclasses.h" + +static int LOW_PRIORITY_TEXTURE_SIZE_DEFAULT = 256; + +static int nextPowerOf2( int value ) +{ +	int next_power_of_2 = 1; +	while ( next_power_of_2 < value ) +	{ +		next_power_of_2 <<= 1; +	} +	 +	return next_power_of_2; +} + +LLPluginClassMedia::LLPluginClassMedia(LLPluginClassMediaOwner *owner) +{ +	mOwner = owner; +	mPlugin = NULL; +	reset(); +} + + +LLPluginClassMedia::~LLPluginClassMedia() +{ +	reset(); +} + +bool LLPluginClassMedia::init(const std::string &launcher_filename, const std::string &plugin_filename) +{	 +	LL_DEBUGS("Plugin") << "launcher: " << launcher_filename << LL_ENDL; +	LL_DEBUGS("Plugin") << "plugin: " << plugin_filename << LL_ENDL; +	 +	mPlugin = new LLPluginProcessParent(this); +	mPlugin->setSleepTime(mSleepTime); +	mPlugin->init(launcher_filename, plugin_filename); + +	return true; +} + + +void LLPluginClassMedia::reset() +{ +	if(mPlugin != NULL) +	{ +		delete mPlugin; +		mPlugin = NULL; +	} + +	mTextureParamsReceived = false; +	mRequestedTextureDepth = 0; +	mRequestedTextureInternalFormat = 0; +	mRequestedTextureFormat = 0; +	mRequestedTextureType = 0; +	mRequestedTextureSwapBytes = false; +	mRequestedTextureCoordsOpenGL = false; +	mTextureSharedMemorySize = 0; +	mTextureSharedMemoryName.clear(); +	mDefaultMediaWidth = 0; +	mDefaultMediaHeight = 0; +	mNaturalMediaWidth = 0; +	mNaturalMediaHeight = 0; +	mSetMediaWidth = -1; +	mSetMediaHeight = -1; +	mRequestedMediaWidth = 0; +	mRequestedMediaHeight = 0; +	mTextureWidth = 0; +	mTextureHeight = 0; +	mMediaWidth = 0; +	mMediaHeight = 0; +	mDirtyRect = LLRect::null;	 +	mAutoScaleMedia = false; +	mRequestedVolume = 1.0f; +	mPriority = PRIORITY_NORMAL; +	mLowPrioritySizeLimit = LOW_PRIORITY_TEXTURE_SIZE_DEFAULT; +	mAllowDownsample = false; +	mPadding = 0; +	mStatus = LLPluginClassMediaOwner::MEDIA_NONE; +	mSleepTime = 1.0f / 100.0f; +	mCanCut = false; +	mCanCopy = false; +	mCanPaste = false; +	mMediaName.clear(); +	mMediaDescription.clear(); + +	// media_browser class +	mNavigateURI.clear(); +	mNavigateResultCode = -1; +	mNavigateResultString.clear(); +	mHistoryBackAvailable = false; +	mHistoryForwardAvailable = false; +	mStatusText.clear(); +	mProgressPercent = 0;	 +	 +	// media_time class +	mCurrentTime = 0.0f; +	mDuration = 0.0f; +	mCurrentRate = 0.0f; +} + +void LLPluginClassMedia::idle(void) +{ +	if(mPlugin) +	{ +		mPlugin->idle(); +	} +	 +	if((mMediaWidth == -1) || (!mTextureParamsReceived) || (mPlugin == NULL)) +	{ +		// Can't process a size change at this time +	} +	else if((mRequestedMediaWidth != mMediaWidth) || (mRequestedMediaHeight != mMediaHeight)) +	{ +		// Calculate the correct size for the media texture +		mRequestedTextureHeight = mRequestedMediaHeight; +		if(mPadding < 0) +		{ +			// negative values indicate the plugin wants a power of 2 +			mRequestedTextureWidth = nextPowerOf2(mRequestedMediaWidth); +		} +		else +		{ +			mRequestedTextureWidth = mRequestedMediaWidth; +			 +			if(mPadding > 1) +			{ +				// Pad up to a multiple of the specified number of bytes per row +				int rowbytes = mRequestedTextureWidth * mRequestedTextureDepth; +				int pad = rowbytes % mPadding; +				if(pad != 0) +				{ +					rowbytes += mPadding - pad; +				} +				 +				if(rowbytes % mRequestedTextureDepth == 0) +				{ +					mRequestedTextureWidth = rowbytes / mRequestedTextureDepth; +				} +				else +				{ +					LL_WARNS("Plugin") << "Unable to pad texture width, padding size " << mPadding << "is not a multiple of pixel size " << mRequestedTextureDepth << LL_ENDL; +				} +			} +		} + +		 +		// Size change has been requested but not initiated yet. +		size_t newsize = mRequestedTextureWidth * mRequestedTextureHeight * mRequestedTextureDepth; + +		// Add an extra line for padding, just in case. +		newsize += mRequestedTextureWidth * mRequestedTextureDepth; + +		if(newsize != mTextureSharedMemorySize) +		{ +			if(!mTextureSharedMemoryName.empty()) +			{ +				// Tell the plugin to remove the old memory segment +				mPlugin->removeSharedMemory(mTextureSharedMemoryName); +				mTextureSharedMemoryName.clear(); +			} +			 +			mTextureSharedMemorySize = newsize; +			mTextureSharedMemoryName = mPlugin->addSharedMemory(mTextureSharedMemorySize); +			if(!mTextureSharedMemoryName.empty()) +			{ +				void *addr = mPlugin->getSharedMemoryAddress(mTextureSharedMemoryName); +				 +				// clear texture memory to avoid random screen visual fuzz from uninitialized texture data +				memset( addr, 0x00, newsize ); +				 +				// We could do this to force an update, but textureValid() will still be returning false until the first roundtrip to the plugin, +				// so it may not be worthwhile. +				// mDirtyRect.setOriginAndSize(0, 0, mRequestedMediaWidth, mRequestedMediaHeight); +			} +		} +		 +		// This is our local indicator that a change is in progress. +		mTextureWidth = -1; +		mTextureHeight = -1; +		mMediaWidth = -1; +		mMediaHeight = -1; + +		// This invalidates any existing dirty rect. +		resetDirty(); +		 +		// Send a size change message to the plugin +		{ +			LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change"); +			message.setValue("name", mTextureSharedMemoryName); +			message.setValueS32("width", mRequestedMediaWidth); +			message.setValueS32("height", mRequestedMediaHeight); +			message.setValueS32("texture_width", mRequestedTextureWidth); +			message.setValueS32("texture_height", mRequestedTextureHeight); +			mPlugin->sendMessage(message);	// DO NOT just use sendMessage() here -- we want this to jump ahead of the queue. +			 +			LL_DEBUGS("Plugin") << "Sending size_change" << LL_ENDL; +		} +	} +	 +	if(mPlugin && mPlugin->isRunning()) +	{ +		// Send queued messages +		while(!mSendQueue.empty()) +		{ +			LLPluginMessage message = mSendQueue.front(); +			mSendQueue.pop(); +			mPlugin->sendMessage(message); +		} +	} +} + +int LLPluginClassMedia::getTextureWidth() const +{ +	return nextPowerOf2(mTextureWidth); +} + +int LLPluginClassMedia::getTextureHeight() const +{ +	return nextPowerOf2(mTextureHeight); +} + +unsigned char* LLPluginClassMedia::getBitsData() +{ +	unsigned char *result = NULL; +	if((mPlugin != NULL) && !mTextureSharedMemoryName.empty()) +	{ +		result = (unsigned char*)mPlugin->getSharedMemoryAddress(mTextureSharedMemoryName); +	} +	return result; +} + +void LLPluginClassMedia::setSize(int width, int height) +{ +	mSetMediaWidth = width; +	mSetMediaHeight = height; + +	setSizeInternal(); +} + +void LLPluginClassMedia::setSizeInternal(void) +{ +	if((mSetMediaWidth > 0) && (mSetMediaHeight > 0)) +	{ +		mRequestedMediaWidth = mSetMediaWidth; +		mRequestedMediaHeight = mSetMediaHeight; +	} +	else +	{ +		mRequestedMediaWidth = mDefaultMediaWidth; +		mRequestedMediaHeight = mDefaultMediaHeight; +	} +	 +	if(mAllowDownsample) +	{ +		switch(mPriority) +		{ +			case PRIORITY_LOW: +				// Reduce maximum texture dimension to (or below) mLowPrioritySizeLimit +				while((mRequestedMediaWidth > mLowPrioritySizeLimit) || (mRequestedMediaHeight > mLowPrioritySizeLimit)) +				{ +					mRequestedMediaWidth /= 2; +					mRequestedMediaHeight /= 2; +				} +			break; +			 +			default: +				// Don't adjust texture size +			break; +		} +	} +	 +	if(mAutoScaleMedia) +	{ +		mRequestedMediaWidth = nextPowerOf2(mRequestedMediaWidth); +		mRequestedMediaHeight = nextPowerOf2(mRequestedMediaHeight); +	} +} + +void LLPluginClassMedia::setAutoScale(bool auto_scale) +{ +	if(auto_scale != mAutoScaleMedia) +	{ +		mAutoScaleMedia = auto_scale; +		setSizeInternal(); +	} +} + +bool LLPluginClassMedia::textureValid(void) +{ +	if( +		!mTextureParamsReceived || +		mTextureWidth <= 0 || +		mTextureHeight <= 0 || +		mMediaWidth <= 0 || +		mMediaHeight <= 0 || +		mRequestedMediaWidth != mMediaWidth || +		mRequestedMediaHeight != mMediaHeight || +		getBitsData() == NULL +	)	 +		return false; +	 +	return true; +} + +bool LLPluginClassMedia::getDirty(LLRect *dirty_rect) +{ +	bool result = !mDirtyRect.isEmpty(); + +	if(dirty_rect != NULL) +	{ +		*dirty_rect = mDirtyRect; +	} + +	return result; +} + +void LLPluginClassMedia::resetDirty(void) +{ +	mDirtyRect = LLRect::null; +} + +std::string LLPluginClassMedia::translateModifiers(MASK modifiers) +{ +	std::string result; +	 +	 +	if(modifiers & MASK_CONTROL) +	{ +		result += "control|"; +	} + +	if(modifiers & MASK_ALT) +	{ +		result += "alt|"; +	} + +	if(modifiers & MASK_SHIFT) +	{ +		result += "shift|"; +	} + +	// TODO: should I deal with platform differences here or in callers? +	// TODO: how do we deal with the Mac "command" key? +/* +	if(modifiers & MASK_SOMETHING) +	{ +		result += "meta|"; +	} +*/	 +	return result; +} + +void LLPluginClassMedia::mouseEvent(EMouseEventType type, int x, int y, MASK modifiers) +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "mouse_event"); +	std::string temp; +	switch(type) +	{ +		case MOUSE_EVENT_DOWN:			temp = "down";			break; +		case MOUSE_EVENT_UP:			temp = "up";			break; +		case MOUSE_EVENT_MOVE:			temp = "move";			break; +		case MOUSE_EVENT_DOUBLE_CLICK:	temp = "double_click";	break; +	} +	message.setValue("event", temp); + +	message.setValueS32("x", x); +	 +	// Incoming coordinates are OpenGL-style ((0,0) = lower left), so flip them here if the plugin has requested it. +	if(!mRequestedTextureCoordsOpenGL) +	{ +		// TODO: Should I use mMediaHeight or mRequestedMediaHeight here? +		y = mMediaHeight - y; +	} +	message.setValueS32("y", y); + +	message.setValue("modifiers", translateModifiers(modifiers)); +	 +	sendMessage(message); +} + +bool LLPluginClassMedia::keyEvent(EKeyEventType type, int key_code, MASK modifiers) +{ +	bool result = true; +	 +	// FIXME: +	// HACK: we don't have an easy way to tell if the plugin is going to handle a particular keycode. +	// For now, return false for the ones the webkit plugin won't handle properly. +	 +	switch(key_code) +	{ +		case KEY_BACKSPACE:		 +		case KEY_TAB:			 +		case KEY_RETURN:		 +		case KEY_PAD_RETURN:	 +		case KEY_SHIFT:			 +		case KEY_CONTROL:		 +		case KEY_ALT:			 +		case KEY_CAPSLOCK:		 +		case KEY_ESCAPE:		 +		case KEY_PAGE_UP:		 +		case KEY_PAGE_DOWN:		 +		case KEY_END:			 +		case KEY_HOME:			 +		case KEY_LEFT:			 +		case KEY_UP:			 +		case KEY_RIGHT:			 +		case KEY_DOWN:			 +		case KEY_INSERT:		 +		case KEY_DELETE: +			// These will be handled		 +		break; +		 +		default: +			// regular ASCII characters will also be handled +			if(key_code >= KEY_SPECIAL) +			{ +				// Other "special" codes will not work properly. +				result = false; +			} +		break; +	} +	 +	if(result) +	{ +		LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "key_event"); +		std::string temp; +		switch(type) +		{ +			case KEY_EVENT_DOWN:			temp = "down";			break; +			case KEY_EVENT_UP:				temp = "up";			break; +			case KEY_EVENT_REPEAT:			temp = "repeat";		break; +		} +		message.setValue("event", temp); +		 +		message.setValueS32("key", key_code); + +		message.setValue("modifiers", translateModifiers(modifiers)); +		 +		sendMessage(message); +	} +		 +	return result; +} + +void LLPluginClassMedia::scrollEvent(int x, int y, MASK modifiers) +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "scroll_event"); + +	message.setValueS32("x", x); +	message.setValueS32("y", y); +	message.setValue("modifiers", translateModifiers(modifiers)); +	 +	sendMessage(message); +} +	 +bool LLPluginClassMedia::textInput(const std::string &text) +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "text_event"); + +	message.setValue("text", text); +	 +	sendMessage(message); +	 +	return true; +} + +void LLPluginClassMedia::loadURI(const std::string &uri) +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "load_uri"); + +	message.setValue("uri", uri); +	 +	sendMessage(message); +} + +void LLPluginClassMedia::setPriority(EPriority priority) +{ +	if(mPriority != priority) +	{ +		mPriority = priority; + +		LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "set_priority"); +		 +		std::string priority_string; +		switch(priority) +		{ +			case PRIORITY_STOPPED:	 +				priority_string = "stopped";	 +				mSleepTime = 1.0f; +			break; +			case PRIORITY_HIDDEN:	 +				priority_string = "hidden";	 +				mSleepTime = 1.0f; +			break; +			case PRIORITY_LOW:		 +				priority_string = "low";		 +				mSleepTime = 1.0f / 50.0f; +			break; +			case PRIORITY_NORMAL:	 +				priority_string = "normal";	 +				mSleepTime = 1.0f / 100.0f; +			break; +			case PRIORITY_HIGH:		 +				priority_string = "high";		 +				mSleepTime = 1.0f / 100.0f; +			break; +		} +		 +		message.setValue("priority", priority_string); + +		sendMessage(message); +		 +		if(mPlugin) +		{ +			mPlugin->setSleepTime(mSleepTime); +		} +		 +		// This may affect the calculated size, so recalculate it here. +		setSizeInternal(); +	} +} + +void LLPluginClassMedia::setLowPrioritySizeLimit(int size) +{ +	if(mLowPrioritySizeLimit != size) +	{ +		mLowPrioritySizeLimit = size; + +		// This may affect the calculated size, so recalculate it here. +		setSizeInternal(); +	} +} + + +void LLPluginClassMedia::cut() +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_cut"); +	sendMessage(message); +} + +void LLPluginClassMedia::copy() +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_copy"); +	sendMessage(message); +} + +void LLPluginClassMedia::paste() +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_paste"); +	sendMessage(message); +} + +/* virtual */  +void LLPluginClassMedia::receivePluginMessage(const LLPluginMessage &message) +{ +	std::string message_class = message.getClass(); +	 +	if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA) +	{ +		std::string message_name = message.getName(); +		if(message_name == "texture_params") +		{ +			mRequestedTextureDepth = message.getValueS32("depth"); +			mRequestedTextureInternalFormat = message.getValueU32("internalformat"); +			mRequestedTextureFormat = message.getValueU32("format"); +			mRequestedTextureType = message.getValueU32("type"); +			mRequestedTextureSwapBytes = message.getValueBoolean("swap_bytes"); +			mRequestedTextureCoordsOpenGL = message.getValueBoolean("coords_opengl");			 +			 +			// These two are optional, and will default to 0 if they're not specified. +			mDefaultMediaWidth = message.getValueS32("default_width"); +			mDefaultMediaHeight = message.getValueS32("default_height"); +			 +			mAllowDownsample = message.getValueBoolean("allow_downsample"); +			mPadding = message.getValueS32("padding"); + +			setSizeInternal(); +			 +			mTextureParamsReceived = true; +		} +		else if(message_name == "updated") +		{			 +			if(message.hasValue("left")) +			{ +				LLRect newDirtyRect; +				newDirtyRect.mLeft = message.getValueS32("left"); +				newDirtyRect.mTop = message.getValueS32("top"); +				newDirtyRect.mRight = message.getValueS32("right"); +				newDirtyRect.mBottom = message.getValueS32("bottom"); +							 +				// The plugin is likely to have top and bottom switched, due to vertical flip and OpenGL coordinate confusion. +				// If they're backwards, swap them. +				if(newDirtyRect.mTop < newDirtyRect.mBottom) +				{ +					S32 temp = newDirtyRect.mTop; +					newDirtyRect.mTop = newDirtyRect.mBottom; +					newDirtyRect.mBottom = temp; +				} +				 +				if(mDirtyRect.isEmpty()) +				{ +					mDirtyRect = newDirtyRect; +				} +				else +				{ +					mDirtyRect.unionWith(newDirtyRect); +				} + +				LL_DEBUGS("Plugin") << "adjusted incoming rect is: ("  +					<< newDirtyRect.mLeft << ", " +					<< newDirtyRect.mTop << ", " +					<< newDirtyRect.mRight << ", " +					<< newDirtyRect.mBottom << "), new dirty rect is: (" +					<< mDirtyRect.mLeft << ", " +					<< mDirtyRect.mTop << ", " +					<< mDirtyRect.mRight << ", " +					<< mDirtyRect.mBottom << ")" +					<< LL_ENDL; +				 +				mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CONTENT_UPDATED); +			}			 +			 + +			bool time_duration_updated = false; + +			if(message.hasValue("current_time")) +			{ +				mCurrentTime = message.getValueReal("current_time"); +				time_duration_updated = true; +			} +			if(message.hasValue("duration")) +			{ +				mDuration = message.getValueReal("duration"); +				time_duration_updated = true; +			} + +			if(message.hasValue("current_rate")) +			{ +				mCurrentRate = message.getValueReal("current_rate"); +			} +			 +			if(time_duration_updated) +			{ +				mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_TIME_DURATION_UPDATED); +			} +			 +		} +		else if(message_name == "media_status") +		{ +			std::string status = message.getValue("status"); +			 +			LL_DEBUGS("Plugin") << "Status changed to: " << status << LL_ENDL; +			 +			if(status == "loading") +			{ +				mStatus = LLPluginClassMediaOwner::MEDIA_LOADING; +			} +			else if(status == "loaded") +			{ +				mStatus = LLPluginClassMediaOwner::MEDIA_LOADED; +			} +			else if(status == "error") +			{ +				mStatus = LLPluginClassMediaOwner::MEDIA_ERROR; +			} +			else if(status == "playing") +			{ +				mStatus = LLPluginClassMediaOwner::MEDIA_PLAYING; +			} +			else if(status == "paused") +			{ +				mStatus = LLPluginClassMediaOwner::MEDIA_PAUSED; +			} +			else +			{ +				// empty string or any unknown string +				mStatus = LLPluginClassMediaOwner::MEDIA_NONE; +			} +		} +		else if(message_name == "size_change_request") +		{ +			S32 width = message.getValueS32("width"); +			S32 height = message.getValueS32("height"); +			std::string name = message.getValue("name"); + +			// TODO: check that name matches? +			mNaturalMediaWidth = width; +			mNaturalMediaHeight = height; +			 +			setSize(width, height); +		} +		else if(message_name == "size_change_response") +		{ +			std::string name = message.getValue("name"); +			 +			// TODO: check that name matches? +			 +			mTextureWidth = message.getValueS32("texture_width"); +			mTextureHeight = message.getValueS32("texture_height"); +			mMediaWidth = message.getValueS32("width"); +			mMediaHeight = message.getValueS32("height"); +			 +			// This invalidates any existing dirty rect. +			resetDirty(); +			 +			// TODO: should we verify that the plugin sent back the right values?   +			// Two size changes in a row may cause them to not match, due to queueing, etc. + +			mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_SIZE_CHANGED); +		} +		else if(message_name == "cursor_changed") +		{ +			mCursorName = message.getValue("name"); + +			mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CURSOR_CHANGED); +		} +		else if(message_name == "edit_state") +		{ +			if(message.hasValue("cut")) +			{ +				mCanCut = message.getValueBoolean("cut"); +			} +			if(message.hasValue("copy")) +			{ +				mCanCopy = message.getValueBoolean("copy"); +			} +			if(message.hasValue("paste")) +			{ +				mCanPaste = message.getValueBoolean("paste"); +			} +		} +		else +		{ +			LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL; +		} +	} +	else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER) +	{ +		std::string message_name = message.getName(); +		if(message_name == "navigate_begin") +		{ +			mNavigateURI = message.getValue("uri"); +			mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_NAVIGATE_BEGIN); +		} +		else if(message_name == "navigate_complete") +		{ +			mNavigateURI = message.getValue("uri"); +			mNavigateResultCode = message.getValueS32("result_code"); +			mNavigateResultString = message.getValue("result_string"); +			mHistoryBackAvailable = message.getValueBoolean("history_back_available"); +			mHistoryForwardAvailable = message.getValueBoolean("history_forward_available"); +			 +			mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_NAVIGATE_COMPLETE); +		} +		else if(message_name == "progress") +		{ +			mProgressPercent = message.getValueS32("percent"); +			mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PROGRESS_UPDATED); +		} +		else if(message_name == "status_text") +		{ +			mStatusText = message.getValue("status"); +			mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_STATUS_TEXT_CHANGED); +		} +		else if(message_name == "location_changed") +		{ +			mLocation = message.getValue("uri"); +			mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_LOCATION_CHANGED); +		} +		else if(message_name == "click_href") +		{ +			mClickURL = message.getValue("uri"); +			mClickTarget = message.getValue("target"); +			mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_HREF); +		} +		else if(message_name == "click_nofollow") +		{ +			mClickURL = message.getValue("uri"); +			mClickTarget.clear(); +			mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_NOFOLLOW); +		} +		else +		{ +			LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL; +		} +	} +	else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME) +	{ +		std::string message_name = message.getName(); + +		// This class hasn't defined any incoming messages yet. +//		if(message_name == "message_name") +//		{ +//		} +//		else  +		{ +			LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL; +		} +	} + +} + +/* virtual */  +void LLPluginClassMedia::pluginDied() +{ +	mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PLUGIN_FAILED); +} + +void LLPluginClassMedia::mediaEvent(LLPluginClassMediaOwner::EMediaEvent event) +{ +	if(mOwner) +	{ +		mOwner->handleMediaEvent(this, event); +	} +} + +void LLPluginClassMedia::sendMessage(const LLPluginMessage &message) +{ +	if(mPlugin && mPlugin->isRunning()) +	{ +		mPlugin->sendMessage(message); +	} +	else +	{ +		// The plugin isn't set up yet -- queue this message to be sent after initialization. +		mSendQueue.push(message); +	} +} + +//////////////////////////////////////////////////////////// +// MARK: media_browser class functions +bool LLPluginClassMedia::pluginSupportsMediaBrowser(void) +{ +	std::string version = mPlugin->getMessageClassVersion(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER); +	return !version.empty(); +} + +void LLPluginClassMedia::focus(bool focused) +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "focus"); + +	message.setValueBoolean("focused", focused); +	 +	sendMessage(message); +} + +void LLPluginClassMedia::clear_cache() +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "clear_cache"); +	sendMessage(message); +} + +void LLPluginClassMedia::clear_cookies() +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "clear_cookies"); +	sendMessage(message); +} + +void LLPluginClassMedia::enable_cookies(bool enable) +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "enable_cookies"); +	sendMessage(message); +} + +void LLPluginClassMedia::proxy_setup(bool enable, const std::string &host, int port) +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "proxy_setup"); + +	message.setValueBoolean("enable", enable); +	message.setValue("host", host); +	message.setValueS32("port", port); + +	sendMessage(message); +} + +void LLPluginClassMedia::browse_stop() +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_stop"); +	sendMessage(message); +} + +void LLPluginClassMedia::browse_reload(bool ignore_cache) +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_reload"); + +	message.setValueBoolean("ignore_cache", ignore_cache); +	 +	sendMessage(message); +} + +void LLPluginClassMedia::browse_forward() +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_forward"); +	sendMessage(message); +} + +void LLPluginClassMedia::browse_back() +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_back"); +	sendMessage(message); +} + +void LLPluginClassMedia::set_status_redirect(int code, const std::string &url) +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "set_status_redirect"); + +	message.setValueS32("code", code); +	message.setValue("url", url); + +	sendMessage(message); +} + +void LLPluginClassMedia::setBrowserUserAgent(const std::string& user_agent) +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "set_user_agent"); + +	message.setValue("user_agent", user_agent); + +	sendMessage(message); +} + +void LLPluginClassMedia::crashPlugin() +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "crash"); + +	sendMessage(message); +} + +void LLPluginClassMedia::hangPlugin() +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "hang"); + +	sendMessage(message); +} + + +//////////////////////////////////////////////////////////// +// MARK: media_time class functions +bool LLPluginClassMedia::pluginSupportsMediaTime(void) +{ +	std::string version = mPlugin->getMessageClassVersion(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME); +	return !version.empty(); +} + +void LLPluginClassMedia::stop() +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "stop"); +	sendMessage(message); +} + +void LLPluginClassMedia::start(float rate) +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "start"); + +	message.setValueReal("rate", rate); + +	sendMessage(message); +} + +void LLPluginClassMedia::pause() +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "pause"); +	sendMessage(message); +} + +void LLPluginClassMedia::seek(float time) +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "seek"); + +	message.setValueReal("time", time); +	 +	sendMessage(message); +} + +void LLPluginClassMedia::setLoop(bool loop) +{ +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "set_loop"); + +	message.setValueBoolean("loop", loop); + +	sendMessage(message); +} + +void LLPluginClassMedia::setVolume(float volume) +{ +	if(volume != mRequestedVolume) +	{ +		mRequestedVolume = volume; +		 +		LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "set_volume"); + +		message.setValueReal("volume", volume); +		 +		sendMessage(message); +	} +} + +void LLPluginClassMedia::initializeUrlHistory(const LLSD& url_history) +{ +	// Send URL history to plugin +	LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "init_history"); +	message.setValueLLSD("history", url_history); +	sendMessage(message); + +	LL_DEBUGS("Plugin") << "Sending history" << LL_ENDL; +} + diff --git a/indra/llplugin/llpluginclassmedia.h b/indra/llplugin/llpluginclassmedia.h new file mode 100644 index 0000000000..7a8586fe2f --- /dev/null +++ b/indra/llplugin/llpluginclassmedia.h @@ -0,0 +1,333 @@ +/**  + * @file llpluginclassmedia.h + * @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINCLASSMEDIA_H +#define LL_LLPLUGINCLASSMEDIA_H + +#include "llgl.h" +#include "llpluginprocessparent.h" +#include "llrect.h" +#include "llpluginclassmediaowner.h" +#include <queue> + + +class LLPluginClassMedia : public LLPluginProcessParentOwner +{ +	LOG_CLASS(LLPluginClassMedia); +public: +	LLPluginClassMedia(LLPluginClassMediaOwner *owner); +	virtual ~LLPluginClassMedia(); + +	// local initialization, called by the media manager when creating a source +	virtual bool init(const std::string &launcher_filename, const std::string &plugin_filename); + +	// undoes everything init() didm called by the media manager when destroying a source +	virtual void reset(); +	 +	void idle(void); +	 +	// All of these may return 0 or an actual valid value. +	// Callers need to check the return for 0, and not use the values in that case. +	int getWidth() const { return (mMediaWidth > 0) ? mMediaWidth : 0; }; +	int getHeight() const { return (mMediaHeight > 0) ? mMediaHeight : 0; }; +	int getNaturalWidth() const { return mNaturalMediaWidth; }; +	int getNaturalHeight() const { return mNaturalMediaHeight; }; +	int getSetWidth() const { return mSetMediaWidth; }; +	int getSetHeight() const { return mSetMediaHeight; }; +	int getBitsWidth() const { return (mTextureWidth > 0) ? mTextureWidth : 0; }; +	int getBitsHeight() const { return (mTextureHeight > 0) ? mTextureHeight : 0; }; +	int getTextureWidth() const; +	int getTextureHeight() const; +	 +	// This may return NULL.  Callers need to check for and handle this case. +	unsigned char* getBitsData(); + +	// gets the format details of the texture data +	// These may return 0 if they haven't been set up yet.  The caller needs to detect this case. +	int getTextureDepth() const { return mRequestedTextureDepth; }; +	int getTextureFormatInternal() const { return mRequestedTextureInternalFormat; }; +	int getTextureFormatPrimary() const { return mRequestedTextureFormat; }; +	int getTextureFormatType() const { return mRequestedTextureType; }; +	bool getTextureFormatSwapBytes() const { return mRequestedTextureSwapBytes; }; +	bool getTextureCoordsOpenGL() const { return mRequestedTextureCoordsOpenGL; }; + +	void setSize(int width, int height); +	void setAutoScale(bool auto_scale); +	 +	// Returns true if all of the texture parameters (depth, format, size, and texture size) are set up and consistent. +	// This will initially be false, and will also be false for some time after setSize while the resize is processed. +	// Note that if this returns true, it is safe to use all the get() functions above without checking for invalid return values +	// until you call idle() again. +	bool textureValid(void); +	 +	bool getDirty(LLRect *dirty_rect = NULL); +	void resetDirty(void); +	 +	typedef enum  +	{ +		MOUSE_EVENT_DOWN, +		MOUSE_EVENT_UP, +		MOUSE_EVENT_MOVE, +		MOUSE_EVENT_DOUBLE_CLICK +	}EMouseEventType; +	 +	void mouseEvent(EMouseEventType type, int x, int y, MASK modifiers); + +	typedef enum  +	{ +		KEY_EVENT_DOWN, +		KEY_EVENT_UP, +		KEY_EVENT_REPEAT +	}EKeyEventType; +	 +	bool keyEvent(EKeyEventType type, int key_code, MASK modifiers); + +	void scrollEvent(int x, int y, MASK modifiers); +	 +	// Text may be unicode (utf8 encoded) +	bool textInput(const std::string &text); +	 +	void loadURI(const std::string &uri); +	 +	// "Loading" means uninitialized or any state prior to fully running (processing commands) +	bool isPluginLoading(void) { return mPlugin?mPlugin->isLoading():false; }; + +	// "Running" means the steady state -- i.e. processing messages +	bool isPluginRunning(void) { return mPlugin?mPlugin->isRunning():false; }; +	 +	// "Exited" means any regular or error state after "Running" (plugin may have crashed or exited normally) +	bool isPluginExited(void) { return mPlugin?mPlugin->isDone():false; }; + +	std::string getPluginVersion() { return mPlugin?mPlugin->getPluginVersion():std::string(""); }; + +	bool getDisableTimeout() { return mPlugin?mPlugin->getDisableTimeout():false; }; +	void setDisableTimeout(bool disable) { if(mPlugin) mPlugin->setDisableTimeout(disable); }; +	 +	// Inherited from LLPluginProcessParentOwner +	/* virtual */ void receivePluginMessage(const LLPluginMessage &message); +	/* virtual */ void pluginDied(); +	 +	 +	typedef enum  +	{ +		PRIORITY_STOPPED,	// media is not playing, shouldn't need to update at all. +		PRIORITY_HIDDEN,	// media is not being displayed or is out of view, don't need to do graphic updates, but may still update audio, playhead, etc. +		PRIORITY_LOW,		// media is in the far distance, may be rendered at reduced size +		PRIORITY_NORMAL,	// normal (default) priority +		PRIORITY_HIGH		// media has user focus and/or is taking up most of the screen +	}EPriority; + +	void setPriority(EPriority priority); +	void setLowPrioritySizeLimit(int size); +	 +	// Valid after a MEDIA_EVENT_CURSOR_CHANGED event +	std::string getCursorName() const { return mCursorName; }; + +	LLPluginClassMediaOwner::EMediaStatus getStatus() const { return mStatus; } + +	void	cut(); +	bool	canCut() const { return mCanCut; }; + +	void	copy(); +	bool	canCopy() const { return mCanCopy; }; + +	void	paste(); +	bool	canPaste() const { return mCanPaste; }; +		 +	/////////////////////////////////// +	// media browser class functions +	bool pluginSupportsMediaBrowser(void); +	 +	void focus(bool focused); +	void clear_cache(); +	void clear_cookies(); +	void enable_cookies(bool enable); +	void proxy_setup(bool enable, const std::string &host = LLStringUtil::null, int port = 0); +	void browse_stop(); +	void browse_reload(bool ignore_cache = false); +	void browse_forward(); +	void browse_back(); +	void set_status_redirect(int code, const std::string &url); +	void setBrowserUserAgent(const std::string& user_agent); +	 +	// This is valid after MEDIA_EVENT_NAVIGATE_BEGIN or MEDIA_EVENT_NAVIGATE_COMPLETE +	std::string	getNavigateURI() const { return mNavigateURI; }; + +	// These are valid after MEDIA_EVENT_NAVIGATE_COMPLETE +	S32			getNavigateResultCode() const { return mNavigateResultCode; }; +	std::string getNavigateResultString() const { return mNavigateResultString; }; +	bool		getHistoryBackAvailable() const { return mHistoryBackAvailable; }; +	bool		getHistoryForwardAvailable() const { return mHistoryForwardAvailable; }; + +	// This is valid after MEDIA_EVENT_PROGRESS_UPDATED +	int			getProgressPercent() const { return mProgressPercent; }; +	 +	// This is valid after MEDIA_EVENT_STATUS_TEXT_CHANGED +	std::string getStatusText() const { return mStatusText; }; +	 +	// This is valid after MEDIA_EVENT_LOCATION_CHANGED +	std::string getLocation() const { return mLocation; }; +	 +	// This is valid after MEDIA_EVENT_CLICK_LINK_HREF or MEDIA_EVENT_CLICK_LINK_NOFOLLOW +	std::string getClickURL() const { return mClickURL; }; + +	// This is valid after MEDIA_EVENT_CLICK_LINK_HREF +	std::string getClickTarget() const { return mClickTarget; }; + +	std::string getMediaName() const { return mMediaName; }; +	std::string getMediaDescription() const { return mMediaDescription; }; + +	// Crash the plugin.  If you use this outside of a testbed, you will be punished. +	void		crashPlugin(); +	 +	// Hang the plugin.  If you use this outside of a testbed, you will be punished. +	void		hangPlugin(); + +	/////////////////////////////////// +	// media time class functions +	bool pluginSupportsMediaTime(void); +	void stop(); +	void start(float rate = 0.0f); +	void pause(); +	void seek(float time); +	void setLoop(bool loop); +	void setVolume(float volume); +	 +	F64 getCurrentTime(void) const { return mCurrentTime; }; +	F64 getDuration(void) const { return mDuration; }; +	F64 getCurrentPlayRate(void) { return mCurrentRate; }; +	 +	// Initialize the URL history of the plugin by sending +	// "init_history" message  +	void initializeUrlHistory(const LLSD& url_history); + +protected: +	LLPluginClassMediaOwner *mOwner; + +	// Notify this object's owner that an event has occurred. +	void mediaEvent(LLPluginClassMediaOwner::EMediaEvent event); +		 +	void sendMessage(const LLPluginMessage &message);  // Send message internally, either queueing or sending directly. +	std::queue<LLPluginMessage> mSendQueue;		// Used to queue messages while the plugin initializes. +	 +	void setSizeInternal(void); + +	bool		mTextureParamsReceived;		// the mRequestedTexture* fields are only valid when this is true +	S32 		mRequestedTextureDepth; +	LLGLenum	mRequestedTextureInternalFormat; +	LLGLenum	mRequestedTextureFormat; +	LLGLenum	mRequestedTextureType; +	bool		mRequestedTextureSwapBytes; +	bool		mRequestedTextureCoordsOpenGL; +	 +	std::string mTextureSharedMemoryName; +	size_t		mTextureSharedMemorySize; +	 +	// True to scale requested media up to the full size of the texture (i.e. next power of two) +	bool		mAutoScaleMedia; + +	// default media size for the plugin, from the texture_params message. +	int			mDefaultMediaWidth; +	int			mDefaultMediaHeight; + +	// Size that has been requested by the plugin itself +	int			mNaturalMediaWidth; +	int			mNaturalMediaHeight; + +	// Size that has been requested with setSize() +	int			mSetMediaWidth; +	int			mSetMediaHeight; +	 +	// Actual media size being set (may be affected by auto-scale) +	int			mRequestedMediaWidth; +	int			mRequestedMediaHeight; +	 +	// Texture size calculated from actual media size +	int			mRequestedTextureWidth; +	int			mRequestedTextureHeight; +	 +	// Size that the plugin has acknowledged +	int			mTextureWidth; +	int			mTextureHeight; +	int			mMediaWidth; +	int			mMediaHeight; +	 +	float		mRequestedVolume; +	 +	// Priority of this media stream +	EPriority	mPriority; +	int			mLowPrioritySizeLimit; +	 +	bool		mAllowDownsample; +	int			mPadding; +	 +	 +	LLPluginProcessParent *mPlugin; +	 +	LLRect mDirtyRect; +	 +	std::string translateModifiers(MASK modifiers); +	 +	std::string mCursorName; + +	LLPluginClassMediaOwner::EMediaStatus mStatus; +	 +	F64				mSleepTime; + +	bool			mCanCut; +	bool			mCanCopy; +	bool			mCanPaste; +	 +	std::string		mMediaName; +	std::string		mMediaDescription; +	 +	///////////////////////////////////////// +	// media_browser class +	std::string		mNavigateURI; +	S32				mNavigateResultCode; +	std::string		mNavigateResultString; +	bool			mHistoryBackAvailable; +	bool			mHistoryForwardAvailable; +	std::string		mStatusText; +	int				mProgressPercent; +	std::string		mLocation; +	std::string		mClickURL; +	std::string		mClickTarget; +	 +	///////////////////////////////////////// +	// media_time class +	F64				mCurrentTime; +	F64				mDuration; +	F64				mCurrentRate; +	 +}; + +#endif // LL_LLPLUGINCLASSMEDIA_H diff --git a/indra/llplugin/llpluginclassmediaowner.h b/indra/llplugin/llpluginclassmediaowner.h new file mode 100644 index 0000000000..3ae176cbeb --- /dev/null +++ b/indra/llplugin/llpluginclassmediaowner.h @@ -0,0 +1,79 @@ +/**  + * @file llpluginclassmediaowner.h + * @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINCLASSMEDIAOWNER_H +#define LL_LLPLUGINCLASSMEDIAOWNER_H + +#include "llgl.h" +#include "llpluginprocessparent.h" +#include "llrect.h" +#include <queue> + +class LLPluginClassMedia; + +class LLPluginClassMediaOwner +{ +public: +	typedef enum +	{ +		MEDIA_EVENT_CONTENT_UPDATED,		// contents/dirty rect have updated  +		MEDIA_EVENT_TIME_DURATION_UPDATED,	// current time and/or duration have updated +		MEDIA_EVENT_SIZE_CHANGED,			// media size has changed +		MEDIA_EVENT_CURSOR_CHANGED,			// plugin has requested a cursor change +		 +		MEDIA_EVENT_NAVIGATE_BEGIN,			// browser has begun navigation +		MEDIA_EVENT_NAVIGATE_COMPLETE,		// browser has finished navigation +		MEDIA_EVENT_PROGRESS_UPDATED,		// browser has updated loading progress +		MEDIA_EVENT_STATUS_TEXT_CHANGED,	// browser has updated the status text +		MEDIA_EVENT_LOCATION_CHANGED,		// browser location (URL) has changed (maybe due to internal navagation/frames/etc) +		MEDIA_EVENT_CLICK_LINK_HREF,		// I'm not entirely sure what the semantics of these two are +		MEDIA_EVENT_CLICK_LINK_NOFOLLOW, +		 +		MEDIA_EVENT_PLUGIN_FAILED			// The plugin failed to launch or died unexpectedly +		 +	} EMediaEvent; +	 +	typedef enum +	{ +		MEDIA_NONE,			// Uninitialized -- no useful state +		MEDIA_LOADING,		// loading or navigating +		MEDIA_LOADED,		// navigation/preroll complete +		MEDIA_ERROR,		// navigation/preroll failed +		MEDIA_PLAYING,		// playing (only for time-based media) +		MEDIA_PAUSED,		// paused (only for time-based media) +		 +	} EMediaStatus; +	 +	virtual ~LLPluginClassMediaOwner() {}; +	virtual void handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent /*event*/) {}; +}; + +#endif // LL_LLPLUGINCLASSMEDIAOWNER_H diff --git a/indra/llplugin/llplugininstance.cpp b/indra/llplugin/llplugininstance.cpp new file mode 100644 index 0000000000..58fb792d0d --- /dev/null +++ b/indra/llplugin/llplugininstance.cpp @@ -0,0 +1,140 @@ +/**  + * @file llplugininstance.cpp + * @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llplugininstance.h" + +#include "llapr.h" + +//virtual  +LLPluginInstanceMessageListener::~LLPluginInstanceMessageListener() +{ +} + +const char *LLPluginInstance::PLUGIN_INIT_FUNCTION_NAME = "LLPluginInitEntryPoint"; + +LLPluginInstance::LLPluginInstance(LLPluginInstanceMessageListener *owner) : +	mDSOHandle(NULL), +	mPluginUserData(NULL), +	mPluginSendMessageFunction(NULL) +{ +	mOwner = owner; +} + +LLPluginInstance::~LLPluginInstance() +{ +	if(mDSOHandle != NULL) +	{ +		apr_dso_unload(mDSOHandle); +		mDSOHandle = NULL; +	} +} + +int LLPluginInstance::load(std::string &plugin_file) +{ +	pluginInitFunction init_function = NULL; +	 +	int result = apr_dso_load(&mDSOHandle, +					  plugin_file.c_str(), +					  gAPRPoolp); +	if(result != APR_SUCCESS) +	{ +		char buf[1024]; +		apr_dso_error(mDSOHandle, buf, sizeof(buf)); + +		LL_WARNS("Plugin") << "apr_dso_load of " << plugin_file << " failed with error " << result << " , additional info string: " << buf << LL_ENDL; +		 +	} +	 +	if(result == APR_SUCCESS) +	{ +		result = apr_dso_sym((apr_dso_handle_sym_t*)&init_function, +						 mDSOHandle, +						 PLUGIN_INIT_FUNCTION_NAME); + +		if(result != APR_SUCCESS) +		{ +			LL_WARNS("Plugin") << "apr_dso_sym failed with error " << result << LL_ENDL; +		} +	} +	 +	if(result == APR_SUCCESS) +	{ +		result = init_function(staticReceiveMessage, (void*)this, &mPluginSendMessageFunction, &mPluginUserData); + +		if(result != APR_SUCCESS) +		{ +			LL_WARNS("Plugin") << "call to init function failed with error " << result << LL_ENDL; +		} +	} +	 +	return (int)result; +} + +void LLPluginInstance::sendMessage(const std::string &message) +{ +	if(mPluginSendMessageFunction) +	{ +		LL_DEBUGS("Plugin") << "sending message to plugin: \"" << message << "\"" << LL_ENDL; +		mPluginSendMessageFunction(message.c_str(), &mPluginUserData); +	} +	else +	{ +		LL_WARNS("Plugin") << "dropping message: \"" << message << "\"" << LL_ENDL; +	} +} + +void LLPluginInstance::idle(void) +{ +} + +// static +void LLPluginInstance::staticReceiveMessage(const char *message_string, void **user_data) +{ +	// TODO: validate that the user_data argument is still a valid LLPluginInstance pointer +	// we could also use a key that's looked up in a map (instead of a direct pointer) for safety, but that's probably overkill +	LLPluginInstance *self = (LLPluginInstance*)*user_data; +	self->receiveMessage(message_string); +} + +void LLPluginInstance::receiveMessage(const char *message_string) +{ +	if(mOwner) +	{ +		LL_DEBUGS("Plugin") << "processing incoming message: \"" << message_string << "\"" << LL_ENDL;		 +		mOwner->receivePluginMessage(message_string); +	} +	else +	{ +		LL_WARNS("Plugin") << "dropping incoming message: \"" << message_string << "\"" << LL_ENDL;		 +	}	 +} diff --git a/indra/llplugin/llplugininstance.h b/indra/llplugin/llplugininstance.h new file mode 100644 index 0000000000..ba569df10c --- /dev/null +++ b/indra/llplugin/llplugininstance.h @@ -0,0 +1,88 @@ +/**  + * @file llplugininstance.h + * @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGININSTANCE_H +#define LL_LLPLUGININSTANCE_H + +#include "llstring.h" +#include "llapr.h" + +#include "apr_dso.h" + +class LLPluginInstanceMessageListener +{ +public: +	virtual ~LLPluginInstanceMessageListener(); +	virtual void receivePluginMessage(const std::string &message) = 0; +}; + +class LLPluginInstance +{ +	LOG_CLASS(LLPluginInstance); +public: +	LLPluginInstance(LLPluginInstanceMessageListener *owner); +	virtual ~LLPluginInstance(); +	 +	// Load a plugin dll/dylib/so +	// Returns 0 if successful, APR error code or error code returned from the plugin's init function on failure. +	int load(std::string &plugin_file); +	 +	// Sends a message to the plugin. +	void sendMessage(const std::string &message); +	 +	// send_count is the maximum number of message to process from the send queue.  If negative, it will drain the queue completely. +	// The receive queue is always drained completely. +	// Returns the total number of messages processed from both queues. +	void idle(void); +	 +	// this is the signature of the "send a message" function.   +	// message_string is a null-terminated C string +	// user_data is the opaque reference that the callee supplied during setup. +	typedef void (*sendMessageFunction) (const char *message_string, void **user_data); + +	// signature of the plugin init function +	typedef int (*pluginInitFunction) (sendMessageFunction host_send_func, void *host_user_data, sendMessageFunction *plugin_send_func, void **plugin_user_data); +	 +	static const char *PLUGIN_INIT_FUNCTION_NAME; +	 +private: +	static void staticReceiveMessage(const char *message_string, void **user_data); +	void receiveMessage(const char *message_string); + +	apr_dso_handle_t *mDSOHandle; +	 +	void *mPluginUserData; +	sendMessageFunction mPluginSendMessageFunction; +	 +	LLPluginInstanceMessageListener *mOwner; +}; + +#endif // LL_LLPLUGININSTANCE_H diff --git a/indra/llplugin/llpluginmessage.cpp b/indra/llplugin/llpluginmessage.cpp new file mode 100644 index 0000000000..bfabc5b7ca --- /dev/null +++ b/indra/llplugin/llpluginmessage.cpp @@ -0,0 +1,249 @@ +/**  + * @file llpluginmessage.cpp + * @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llpluginmessage.h" +#include "llsdserialize.h" + +LLPluginMessage::LLPluginMessage() +{ +} + +LLPluginMessage::LLPluginMessage(const std::string &message_class, const std::string &message_name) +{ +	setMessage(message_class, message_name); +} + + +LLPluginMessage::~LLPluginMessage() +{ +} + +void LLPluginMessage::clear() +{ +	mMessage = LLSD::emptyMap(); +	mMessage["params"] = LLSD::emptyMap(); +} + +void LLPluginMessage::setMessage(const std::string &message_class, const std::string &message_name) +{ +	clear(); +	mMessage["class"] = message_class; +	mMessage["name"] = message_name; +} + +void LLPluginMessage::setValue(const std::string &key, const std::string &value) +{ +	mMessage["params"][key] = value; +} + +void LLPluginMessage::setValueLLSD(const std::string &key, const LLSD &value) +{ +	mMessage["params"][key] = value; +} + +void LLPluginMessage::setValueS32(const std::string &key, S32 value) +{ +	mMessage["params"][key] = value; +} + +void LLPluginMessage::setValueU32(const std::string &key, U32 value) +{ +	std::stringstream temp; +	temp << "0x" << std::hex << value; +	setValue(key, temp.str()); +} + +void LLPluginMessage::setValueBoolean(const std::string &key, bool value) +{ +	mMessage["params"][key] = value; +} + +void LLPluginMessage::setValueReal(const std::string &key, F64 value) +{ +	mMessage["params"][key] = value; +} + +std::string LLPluginMessage::getClass(void) const +{ +	return mMessage["class"]; +} + +std::string LLPluginMessage::getName(void) const +{ +	return mMessage["name"]; +} + +bool LLPluginMessage::hasValue(const std::string &key) const +{ +	bool result = false; +	 +	if(mMessage["params"].has(key)) +	{ +		result = true; +	} +	 +	return result; +} + +std::string LLPluginMessage::getValue(const std::string &key) const +{ +	std::string result; +	 +	if(mMessage["params"].has(key)) +	{ +		result = mMessage["params"][key].asString(); +	} +	 +	return result; +} + +LLSD LLPluginMessage::getValueLLSD(const std::string &key) const +{ +	LLSD result; + +	if(mMessage["params"].has(key)) +	{ +		result = mMessage["params"][key]; +	} +	 +	return result; +} + +S32 LLPluginMessage::getValueS32(const std::string &key) const +{ +	S32 result = 0; + +	if(mMessage["params"].has(key)) +	{ +		result = mMessage["params"][key].asInteger(); +	} +	 +	return result; +} + +U32 LLPluginMessage::getValueU32(const std::string &key) const +{ +	U32 result = 0; + +	if(mMessage["params"].has(key)) +	{ +		std::string value = mMessage["params"][key].asString(); +		 +		result = (U32)strtoul(value.c_str(), NULL, 16); +	} +	 +	return result; +} + +bool LLPluginMessage::getValueBoolean(const std::string &key) const +{ +	bool result = false; + +	if(mMessage["params"].has(key)) +	{ +		result = mMessage["params"][key].asBoolean(); +	} +	 +	return result; +} + +F64 LLPluginMessage::getValueReal(const std::string &key) const +{ +	F64 result = 0.0f; + +	if(mMessage["params"].has(key)) +	{ +		result = mMessage["params"][key].asReal(); +	} +	 +	return result; +} + +std::string LLPluginMessage::generate(void) const +{ +	std::ostringstream result; +	 +	// Pretty XML may be slightly easier to deal with while debugging... +//	LLSDSerialize::toXML(mMessage, result); +	LLSDSerialize::toPrettyXML(mMessage, result); +	 +	return result.str(); +} + + +int LLPluginMessage::parse(const std::string &message) +{ +	// clear any previous state +	clear(); + +	std::istringstream input(message); +	 +	S32 parse_result = LLSDSerialize::fromXML(mMessage, input); +	 +	return (int)parse_result; +} + + +LLPluginMessageListener::~LLPluginMessageListener() +{ +	// TODO: should listeners have a way to ensure they're removed from dispatcher lists when deleted? +} + + +LLPluginMessageDispatcher::~LLPluginMessageDispatcher() +{ +	 +} +	 +void LLPluginMessageDispatcher::addPluginMessageListener(LLPluginMessageListener *listener) +{ +	mListeners.insert(listener); +} + +void LLPluginMessageDispatcher::removePluginMessageListener(LLPluginMessageListener *listener) +{ +	mListeners.erase(listener); +} + +void LLPluginMessageDispatcher::dispatchPluginMessage(const LLPluginMessage &message) +{ +	for (listener_set_t::iterator it = mListeners.begin(); +		it != mListeners.end(); +		) +	{ +		LLPluginMessageListener* listener = *it; +		listener->receivePluginMessage(message); +		// In case something deleted an entry. +		it = mListeners.upper_bound(listener); +	} +} diff --git a/indra/llplugin/llpluginmessage.h b/indra/llplugin/llpluginmessage.h new file mode 100644 index 0000000000..a17ec4bb98 --- /dev/null +++ b/indra/llplugin/llpluginmessage.h @@ -0,0 +1,123 @@ +/**  + * @file llpluginmessage.h + * @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINMESSAGE_H +#define LL_LLPLUGINMESSAGE_H + +#include "llsd.h" + + +class LLPluginMessage +{ +	LOG_CLASS(LLPluginMessage); +public: +	LLPluginMessage(); +	LLPluginMessage(const std::string &message_class, const std::string &message_name); +	~LLPluginMessage(); +	 +	// reset all internal state +	void clear(void); +	 +	// Sets the message class and name +	// Also has the side-effect of clearing any key/value pairs in the message. +	void setMessage(const std::string &message_class, const std::string &message_name); +	 +	// Sets a key/value pair in the message +	void setValue(const std::string &key, const std::string &value); +	void setValueLLSD(const std::string &key, const LLSD &value); +	void setValueS32(const std::string &key, S32 value); +	void setValueU32(const std::string &key, U32 value); +	void setValueBoolean(const std::string &key, bool value); +	void setValueReal(const std::string &key, F64 value); +	 +	std::string getClass(void) const; +	std::string getName(void) const; +	 +	// Returns true if the specified key exists in this message (useful for optional parameters) +	bool hasValue(const std::string &key) const; +	 +	// get the value of a particular key as a string.  If the key doesn't exist in the message, an empty string will be returned. +	std::string getValue(const std::string &key) const; + +	// get the value of a particular key as LLSD.  If the key doesn't exist in the message, a null LLSD will be returned. +	LLSD getValueLLSD(const std::string &key) const; +	 +	// get the value of a key as a S32.  If the value wasn't set as a S32, behavior is undefined. +	S32 getValueS32(const std::string &key) const; +	 +	// get the value of a key as a U32.  Since there isn't an LLSD type for this, we use a hexadecimal string instead. +	U32 getValueU32(const std::string &key) const; + +	// get the value of a key as a Boolean. +	bool getValueBoolean(const std::string &key) const; + +	// get the value of a key as a float. +	F64 getValueReal(const std::string &key) const; + +	// Flatten the message into a string +	std::string generate(void) const; + +	// Parse an incoming message into component parts +	// (this clears out all existing state before starting the parse) +	// Returns -1 on failure, otherwise returns the number of key/value pairs in the message. +	int parse(const std::string &message); +	 +	 +private: +	 +	LLSD mMessage; + +}; + +class LLPluginMessageListener +{ +public: +	virtual ~LLPluginMessageListener(); +	virtual void receivePluginMessage(const LLPluginMessage &message) = 0; +	 +}; + +class LLPluginMessageDispatcher +{ +public: +	virtual ~LLPluginMessageDispatcher(); +	 +	void addPluginMessageListener(LLPluginMessageListener *); +	void removePluginMessageListener(LLPluginMessageListener *); +protected: +	void dispatchPluginMessage(const LLPluginMessage &message); + +	typedef std::set<LLPluginMessageListener*> listener_set_t; +	listener_set_t mListeners; +}; + + +#endif // LL_LLPLUGINMESSAGE_H diff --git a/indra/llplugin/llpluginmessageclasses.h b/indra/llplugin/llpluginmessageclasses.h new file mode 100644 index 0000000000..927fcf2eb2 --- /dev/null +++ b/indra/llplugin/llpluginmessageclasses.h @@ -0,0 +1,56 @@ +/**  + * @file llpluginmessageclasses.h + * @brief This file defines the versions of existing message classes for LLPluginMessage. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINMESSAGECLASSES_H +#define LL_LLPLUGINMESSAGECLASSES_H + +// Version strings for each plugin message class.   +// Backwards-compatible changes (i.e. changes which only add new messges) should increment the minor version (i.e. "1.0" -> "1.1"). +// Non-backwards-compatible changes (which delete messages or change their semantics) should increment the major version (i.e. "1.1" -> "2.0"). +// Plugins will supply the set of message classes they understand, with version numbers, as part of their init_response message. +// The contents and semantics of the base:init message must NEVER change in a non-backwards-compatible way, as a special case. + +#define LLPLUGIN_MESSAGE_CLASS_INTERNAL "internal" +#define LLPLUGIN_MESSAGE_CLASS_INTERNAL_VERSION "1.0" + +#define LLPLUGIN_MESSAGE_CLASS_BASE "base" +#define LLPLUGIN_MESSAGE_CLASS_BASE_VERSION "1.0" + +#define LLPLUGIN_MESSAGE_CLASS_MEDIA "media" +#define LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION "1.0" + +#define LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER "media_browser" +#define LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER_VERSION "1.0" + +#define LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME "media_time" +#define LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME_VERSION "1.0" + +#endif // LL_LLPLUGINMESSAGECLASSES_H diff --git a/indra/llplugin/llpluginmessagepipe.cpp b/indra/llplugin/llpluginmessagepipe.cpp new file mode 100644 index 0000000000..31ea138912 --- /dev/null +++ b/indra/llplugin/llpluginmessagepipe.cpp @@ -0,0 +1,315 @@ +/**  + * @file llpluginmessagepipe.cpp + * @brief Classes that implement connections from the plugin system to pipes/pumps. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llpluginmessagepipe.h" +#include "llbufferstream.h" + +#include "llapr.h" + +static const char MESSAGE_DELIMITER = '\0'; + +LLPluginMessagePipeOwner::LLPluginMessagePipeOwner() : +	mMessagePipe(NULL), +	mSocketError(APR_SUCCESS) +{ +} + +// virtual  +LLPluginMessagePipeOwner::~LLPluginMessagePipeOwner() +{ +	killMessagePipe(); +} + +// virtual  +apr_status_t LLPluginMessagePipeOwner::socketError(apr_status_t error) +{  +	mSocketError = error; +	return error;  +}; + +//virtual  +void LLPluginMessagePipeOwner::setMessagePipe(LLPluginMessagePipe *read_pipe) +{ +	// Save a reference to this pipe +	mMessagePipe = read_pipe; +} + +bool LLPluginMessagePipeOwner::canSendMessage(void) +{ +	return (mMessagePipe != NULL); +} + +bool LLPluginMessagePipeOwner::writeMessageRaw(const std::string &message) +{ +	bool result = true; +	if(mMessagePipe != NULL) +	{ +		result = mMessagePipe->addMessage(message); +	} +	else +	{ +		LL_WARNS("Plugin") << "dropping message: " << message << LL_ENDL; +		result = false; +	} +	 +	return result; +} + +void LLPluginMessagePipeOwner::killMessagePipe(void) +{ +	if(mMessagePipe != NULL) +	{ +		delete mMessagePipe; +		mMessagePipe = NULL; +	} +} + +LLPluginMessagePipe::LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket) +{ +	mOwner = owner; +	mOwner->setMessagePipe(this); +	mSocket = socket; +} + +LLPluginMessagePipe::~LLPluginMessagePipe() +{ +	if(mOwner != NULL) +	{ +		mOwner->setMessagePipe(NULL); +	} +} + +bool LLPluginMessagePipe::addMessage(const std::string &message) +{ +	// queue the message for later output +	mOutput += message; +	mOutput += MESSAGE_DELIMITER;	// message separator +	 +	return true; +} + +void LLPluginMessagePipe::clearOwner(void) +{ +	// The owner is done with this pipe.  The next call to process_impl should send any remaining data and exit. +	mOwner = NULL; +} + +void LLPluginMessagePipe::setSocketTimeout(apr_interval_time_t timeout_usec) +{ +	// We never want to sleep forever, so force negative timeouts to become non-blocking. + +	// according to this page: http://dev.ariel-networks.com/apr/apr-tutorial/html/apr-tutorial-13.html +	// blocking/non-blocking with apr sockets is somewhat non-portable. +	 +	if(timeout_usec <= 0) +	{ +		// Make the socket non-blocking +		apr_socket_opt_set(mSocket->getSocket(), APR_SO_NONBLOCK, 1); +		apr_socket_timeout_set(mSocket->getSocket(), 0); +	} +	else +	{ +		// Make the socket blocking-with-timeout +		apr_socket_opt_set(mSocket->getSocket(), APR_SO_NONBLOCK, 1); +		apr_socket_timeout_set(mSocket->getSocket(), timeout_usec); +	} +} + +bool LLPluginMessagePipe::pump(F64 timeout) +{ +	bool result = true; +	 +	if(mSocket) +	{ +		apr_status_t status; +		apr_size_t size; +		 +		if(!mOutput.empty()) +		{ +			// write any outgoing messages +			size = (apr_size_t)mOutput.size(); +			 +			setSocketTimeout(0); +			 +//			LL_INFOS("Plugin") << "before apr_socket_send, size = " << size << LL_ENDL; + +			status = apr_socket_send( +					mSocket->getSocket(), +					(const char*)mOutput.data(), +					&size); + +//			LL_INFOS("Plugin") << "after apr_socket_send, size = " << size << LL_ENDL; +			 +			if(status == APR_SUCCESS) +			{ +				// success +				mOutput = mOutput.substr(size); +			} +			else if(APR_STATUS_IS_EAGAIN(status)) +			{ +				// Socket buffer is full...  +				// remove the written part from the buffer and try again later. +				mOutput = mOutput.substr(size); +			} +			else  +			{ +				// some other error +				// Treat this as fatal. +				ll_apr_warn_status(status); +				 +				if(mOwner) +				{ +					mOwner->socketError(status); +				} +				result = false; +			} +		} + +		// FIXME: For some reason, the apr timeout stuff isn't working properly on windows. +		// Until such time as we figure out why, don't try to use the socket timeout -- just sleep here instead. +#if LL_WINDOWS +		if(result) +		{ +			if(timeout != 0.0f) +			{ +				ms_sleep((int)(timeout * 1000.0f)); +				timeout = 0.0f; +			} +		} +#endif +		 +		// Check for incoming messages +		if(result) +		{ +			char input_buf[1024]; +			apr_size_t request_size; +			 +			// Start out by reading one byte, so that any data received will wake us up. +			request_size = 1; +			 +			// and use the timeout so we'll sleep if no data is available. +			setSocketTimeout((apr_interval_time_t)(timeout * 1000000)); + +			while(1)		 +			{ +				size = request_size; + +//				LL_INFOS("Plugin") << "before apr_socket_recv, size = " << size << LL_ENDL; + +				status = apr_socket_recv( +						mSocket->getSocket(),  +						input_buf,  +						&size); + +//				LL_INFOS("Plugin") << "after apr_socket_recv, size = " << size << LL_ENDL; +				 +				if(size > 0) +					mInput.append(input_buf, size); + +				if(status == APR_SUCCESS) +				{ +//					llinfos << "success, read " << size << llendl; + +					if(size != request_size) +					{ +						// This was a short read, so we're done. +						break; +					} +				} +				else if(APR_STATUS_IS_TIMEUP(status)) +				{ +//					llinfos << "TIMEUP, read " << size << llendl; + +					// Timeout was hit.  Since the initial read is 1 byte, this should never be a partial read. +					break; +				} +				else if(APR_STATUS_IS_EAGAIN(status)) +				{ +//					llinfos << "EAGAIN, read " << size << llendl; + +					// We've been doing partial reads, and we're done now. +					break; +				} +				else +				{ +					// some other error +					// Treat this as fatal. +					ll_apr_warn_status(status); + +					if(mOwner) +					{ +						mOwner->socketError(status); +					} +					result = false; +					break; +				} + +				// Second and subsequent reads should not use the timeout +				setSocketTimeout(0); +				// and should try to fill the input buffer +				request_size = sizeof(input_buf); +			} +			 +			processInput(); +		} +	} + +	if(!result) +	{ +		// If we got an error, we're done. +		LL_INFOS("Plugin") << "Error from socket, cleaning up." << LL_ENDL; +		delete this; +	} +	 +	return result;	 +} + +void LLPluginMessagePipe::processInput(void) +{ +	// Look for input delimiter(s) in the input buffer. +	int start = 0; +	int delim; +	while((delim = mInput.find(MESSAGE_DELIMITER, start)) != std::string::npos) +	{	 +		// Let the owner process this message +		mOwner->receiveMessageRaw(mInput.substr(start, delim - start)); +		 +		start = delim + 1; +	} +	 +	// Remove delivered messages from the input buffer. +	if(start != 0) +		mInput = mInput.substr(start); +	 +} + diff --git a/indra/llplugin/llpluginmessagepipe.h b/indra/llplugin/llpluginmessagepipe.h new file mode 100644 index 0000000000..4eb6575bd4 --- /dev/null +++ b/indra/llplugin/llpluginmessagepipe.h @@ -0,0 +1,91 @@ +/**  + * @file llpluginmessagepipe.h + * @brief Classes that implement connections from the plugin system to pipes/pumps. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINMESSAGEPIPE_H +#define LL_LLPLUGINMESSAGEPIPE_H + +#include "lliosocket.h" + +class LLPluginMessagePipe; + +// Inherit from this to be able to receive messages from the LLPluginMessagePipe +class LLPluginMessagePipeOwner +{ +	LOG_CLASS(LLPluginMessagePipeOwner); +public: +	LLPluginMessagePipeOwner(); +	virtual ~LLPluginMessagePipeOwner(); +	// called with incoming messages +	virtual void receiveMessageRaw(const std::string &message) = 0; +	// called when the socket has an error +	virtual apr_status_t socketError(apr_status_t error); + +	// called from LLPluginMessagePipe to manage the connection with LLPluginMessagePipeOwner -- do not use! +	virtual void setMessagePipe(LLPluginMessagePipe *message_pipe) ; + +protected: +	// returns false if writeMessageRaw() would drop the message +	bool canSendMessage(void); +	// call this to send a message over the pipe +	bool writeMessageRaw(const std::string &message); +	// call this to close the pipe +	void killMessagePipe(void); +	 +	LLPluginMessagePipe *mMessagePipe; +	apr_status_t mSocketError; +}; + +class LLPluginMessagePipe +{ +	LOG_CLASS(LLPluginMessagePipe); +public: +	LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket); +	virtual ~LLPluginMessagePipe(); +	 +	bool addMessage(const std::string &message); +	void clearOwner(void); +	 +	bool pump(F64 timeout = 0.0f); +	 +protected:	 +	void processInput(void); + +	// used internally by pump() +	void setSocketTimeout(apr_interval_time_t timeout_usec); +	 +	std::string mInput; +	std::string mOutput; + +	LLPluginMessagePipeOwner *mOwner; +	LLSocket::ptr_t mSocket; +}; + +#endif // LL_LLPLUGINMESSAGE_H diff --git a/indra/llplugin/llpluginprocesschild.cpp b/indra/llplugin/llpluginprocesschild.cpp new file mode 100644 index 0000000000..293dea6fe1 --- /dev/null +++ b/indra/llplugin/llpluginprocesschild.cpp @@ -0,0 +1,466 @@ +/**  + * @file llpluginprocesschild.cpp + * @brief LLPluginProcessChild handles the child side of the external-process plugin API.  + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llpluginprocesschild.h" +#include "llplugininstance.h" +#include "llpluginmessagepipe.h" +#include "llpluginmessageclasses.h" + +static const F32 HEARTBEAT_SECONDS = 1.0f; + +LLPluginProcessChild::LLPluginProcessChild() +{ +	mInstance = NULL; +	mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); +	mSleepTime = 1.0f / 100.0f;	// default: send idle messages at 100Hz +} + +LLPluginProcessChild::~LLPluginProcessChild() +{ +	if(mInstance != NULL) +	{ +		sendMessageToPlugin(LLPluginMessage("base", "cleanup")); +		delete mInstance; +		mInstance = NULL; +	} +} + +void LLPluginProcessChild::killSockets(void) +{ +	killMessagePipe(); +	mSocket.reset(); +} + +void LLPluginProcessChild::init(U32 launcher_port) +{ +	mLauncherHost = LLHost("127.0.0.1", launcher_port); +	setState(STATE_INITIALIZED); +} + +void LLPluginProcessChild::idle(void) +{ +	bool idle_again; +	do +	{ +		if(mSocketError != APR_SUCCESS) +		{ +			LL_INFOS("Plugin") << "message pipe is in error state, moving to STATE_ERROR"<< LL_ENDL; +			setState(STATE_ERROR); +		}	 + +		if((mState > STATE_INITIALIZED) && (mMessagePipe == NULL)) +		{ +			// The pipe has been closed -- we're done. +			// TODO: This could be slightly more subtle, but I'm not sure it needs to be. +			LL_INFOS("Plugin") << "message pipe went away, moving to STATE_ERROR"<< LL_ENDL; +			setState(STATE_ERROR); +		} +	 +		// If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState(). +		// USE THIS CAREFULLY, since it can starve other code.  Specifically make sure there's no way to get into a closed cycle and never return. +		// When in doubt, don't do it. +		idle_again = false; +		 +		if(mInstance != NULL) +		{ +			// Provide some time to the plugin +			mInstance->idle(); +		} +		 +		switch(mState) +		{ +			case STATE_UNINITIALIZED: +			break; + +			case STATE_INITIALIZED: +				if(mSocket->blockingConnect(mLauncherHost)) +				{ +					// This automatically sets mMessagePipe +					new LLPluginMessagePipe(this, mSocket); +					 +					setState(STATE_CONNECTED); +				} +				else +				{ +					// connect failed +					setState(STATE_ERROR); +				} +			break; +			 +			case STATE_CONNECTED: +				sendMessageToParent(LLPluginMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "hello")); +				setState(STATE_PLUGIN_LOADING); +			break; +						 +			case STATE_PLUGIN_LOADING: +				if(!mPluginFile.empty()) +				{ +					mInstance = new LLPluginInstance(this); +					if(mInstance->load(mPluginFile) == 0) +					{ +						mHeartbeat.start(); +						mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS); +						setState(STATE_PLUGIN_LOADED); +					} +					else +					{ +						setState(STATE_ERROR); +					} +				} +			break; +			 +			case STATE_PLUGIN_LOADED: +				setState(STATE_PLUGIN_INITIALIZING); +				sendMessageToPlugin(LLPluginMessage("base", "init")); +			break; +			 +			case STATE_PLUGIN_INITIALIZING: +				// waiting for init_response... +			break; +			 +			case STATE_RUNNING: +				if(mInstance != NULL) +				{ +					// Provide some time to the plugin +					LLPluginMessage message("base", "idle"); +					message.setValueReal("time", mSleepTime); +					sendMessageToPlugin(message); +					 +					mInstance->idle(); +					 +					if(mHeartbeat.checkExpirationAndReset(HEARTBEAT_SECONDS)) +					{ +						// This just proves that we're not stuck down inside the plugin code. +						sendMessageToParent(LLPluginMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "heartbeat")); +					} +				} +				// receivePluginMessage will transition to STATE_UNLOADING +			break; + +			case STATE_UNLOADING: +				if(mInstance != NULL) +				{ +					sendMessageToPlugin(LLPluginMessage("base", "cleanup")); +					delete mInstance; +					mInstance = NULL; +				} +				setState(STATE_UNLOADED); +			break; +			 +			case STATE_UNLOADED: +				killSockets(); +				setState(STATE_DONE); +			break; + +			case STATE_ERROR: +				// Close the socket to the launcher +				killSockets();				 +				// TODO: Where do we go from here?  Just exit()? +				setState(STATE_DONE); +			break; +			 +			case STATE_DONE: +				// just sit here. +			break; +		} +	 +	} while (idle_again); +} + +void LLPluginProcessChild::sleep(F64 seconds) +{ +	if(mMessagePipe) +	{ +		mMessagePipe->pump(seconds); +	} +	else +	{ +		ms_sleep((int)(seconds * 1000.0f)); +	} +} + +void LLPluginProcessChild::pump(void) +{ +	if(mMessagePipe) +	{ +		mMessagePipe->pump(0.0f); +	} +	else +	{ +		// Should we warn here? +	} +} + + +bool LLPluginProcessChild::isRunning(void) +{ +	bool result = false; +	 +	if(mState == STATE_RUNNING) +		result = true; +		 +	return result; +} + +bool LLPluginProcessChild::isDone(void) +{ +	bool result = false; +	 +	switch(mState) +	{ +		case STATE_DONE: +		result = true; +		break; +		default: +		break; +	} +		 +	return result; +} + +void LLPluginProcessChild::sendMessageToPlugin(const LLPluginMessage &message) +{ +	std::string buffer = message.generate(); + +	LL_DEBUGS("Plugin") << "Sending to plugin: " << buffer << LL_ENDL; + +	mInstance->sendMessage(buffer); +} + +void LLPluginProcessChild::sendMessageToParent(const LLPluginMessage &message) +{ +	std::string buffer = message.generate(); + +	LL_DEBUGS("Plugin") << "Sending to parent: " << buffer << LL_ENDL; + +	writeMessageRaw(buffer); +} + +void LLPluginProcessChild::receiveMessageRaw(const std::string &message) +{ +	// Incoming message from the TCP Socket + +	LL_DEBUGS("Plugin") << "Received from parent: " << message << LL_ENDL; + +	bool passMessage = true; +	 +	// FIXME: how should we handle queueing here? +	 +	{ +		// Decode this message +		LLPluginMessage parsed; +		parsed.parse(message); +		 +		std::string message_class = parsed.getClass(); +		if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL) +		{ +			passMessage = false; +			 +			std::string message_name = parsed.getName(); +			if(message_name == "load_plugin") +			{ +				mPluginFile = parsed.getValue("file"); +			} +			else if(message_name == "shm_add") +			{ +				std::string name = parsed.getValue("name"); +				size_t size = (size_t)parsed.getValueS32("size"); +				 +				sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); +				if(iter != mSharedMemoryRegions.end()) +				{ +					// Need to remove the old region first +					LL_WARNS("Plugin") << "Adding a duplicate shared memory segment!" << LL_ENDL; +				} +				else +				{ +					// This is a new region +					LLPluginSharedMemory *region = new LLPluginSharedMemory; +					if(region->attach(name, size)) +					{ +						mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region)); +						 +						std::stringstream addr; +						addr << region->getMappedAddress(); +						 +						// Send the add notification to the plugin +						LLPluginMessage message("base", "shm_added"); +						message.setValue("name", name); +						message.setValueS32("size", (S32)size); +						// shm address is split into 2x32bit values because LLSD doesn't serialize 64bit values and we need to support 64-bit addressing. +						void * address = region->getMappedAddress(); +						U32 address_lo = (U32)address; +						U32 address_hi = (U32)(U64(address) / (U64(1)<<31)); +						message.setValueU32("address", address_lo); +						message.setValueU32("address_1", address_hi); +						sendMessageToPlugin(message); +						 +						// and send the response to the parent +						message.setMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add_response"); +						message.setValue("name", name); +						sendMessageToParent(message); +					} +					else +					{ +						LL_WARNS("Plugin") << "Couldn't create a shared memory segment!" << LL_ENDL; +					} +				} +				 +			} +			else if(message_name == "shm_remove") +			{ +				std::string name = parsed.getValue("name"); +				sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); +				if(iter != mSharedMemoryRegions.end()) +				{ +					// forward the remove request to the plugin -- its response will trigger us to detach the segment. +					LLPluginMessage message("base", "shm_remove"); +					message.setValue("name", name); +					sendMessageToPlugin(message); +				} +				else +				{ +					LL_WARNS("Plugin") << "shm_remove for unknown memory segment!" << LL_ENDL; +				} +			} +			else if(message_name == "sleep_time") +			{ +				mSleepTime = parsed.getValueReal("time"); +			} +			else if(message_name == "crash") +			{ +				// Crash the plugin +				LL_ERRS("Plugin") << "Plugin crash requested." << LL_ENDL; +			} +			else if(message_name == "hang") +			{ +				// Hang the plugin +				LL_WARNS("Plugin") << "Plugin hang requested." << LL_ENDL; +				while(1) +				{ +					// wheeeeeeeee...... +				} +			} +			else +			{ +				LL_WARNS("Plugin") << "Unknown internal message from parent: " << message_name << LL_ENDL; +			} +		} +	} +	 +	if(passMessage && mInstance != NULL) +	{ +		mInstance->sendMessage(message); +	} +} + +/* virtual */  +void LLPluginProcessChild::receivePluginMessage(const std::string &message) +{ +	LL_DEBUGS("Plugin") << "Received from plugin: " << message << LL_ENDL; + +	// Incoming message from the plugin instance +	bool passMessage = true; + +	// FIXME: how should we handle queueing here? +	 +	// Intercept certain base messages (responses to ones sent by this class) +	{ +		// Decode this message +		LLPluginMessage parsed; +		parsed.parse(message); +		std::string message_class = parsed.getClass(); +		if(message_class == "base") +		{ +			std::string message_name = parsed.getName(); +			if(message_name == "init_response") +			{ +				// The plugin has finished initializing. +				setState(STATE_RUNNING); + +				// Don't pass this message up to the parent +				passMessage = false; +				 +				LLPluginMessage new_message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin_response"); +				LLSD versions = parsed.getValueLLSD("versions"); +				new_message.setValueLLSD("versions", versions); +				 +				if(parsed.hasValue("plugin_version")) +				{ +					std::string plugin_version = parsed.getValue("plugin_version"); +					new_message.setValueLLSD("plugin_version", plugin_version); +				} + +				// Let the parent know it's loaded and initialized. +				sendMessageToParent(new_message); +			} +			else if(message_name == "shm_remove_response") +			{ +				// Don't pass this message up to the parent +				passMessage = false; + +				std::string name = parsed.getValue("name"); +				sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);				 +				if(iter != mSharedMemoryRegions.end()) +				{ +					// detach the shared memory region +					iter->second->detach(); +					 +					// and remove it from our map +					mSharedMemoryRegions.erase(iter); +					 +					// Finally, send the response to the parent. +					LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove_response"); +					message.setValue("name", name); +					sendMessageToParent(message); +				} +				else +				{ +					LL_WARNS("Plugin") << "shm_remove_response for unknown memory segment!" << LL_ENDL; +				} +			} +		} +	} +	 +	if(passMessage) +	{ +		writeMessageRaw(message); +	} +} + + +void LLPluginProcessChild::setState(EState state) +{ +	LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL; +	mState = state;  +}; diff --git a/indra/llplugin/llpluginprocesschild.h b/indra/llplugin/llpluginprocesschild.h new file mode 100644 index 0000000000..f92905e8bd --- /dev/null +++ b/indra/llplugin/llpluginprocesschild.h @@ -0,0 +1,108 @@ +/**  + * @file llpluginprocesschild.h + * @brief LLPluginProcessChild handles the child side of the external-process plugin API.  + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINPROCESSCHILD_H +#define LL_LLPLUGINPROCESSCHILD_H + +#include "llpluginmessage.h" +#include "llpluginmessagepipe.h" +#include "llplugininstance.h" +#include "llhost.h" +#include "llpluginsharedmemory.h" + +class LLPluginInstance; + +class LLPluginProcessChild: public LLPluginMessagePipeOwner, public LLPluginInstanceMessageListener +{ +	LOG_CLASS(LLPluginProcessChild); +public: +	LLPluginProcessChild(); +	~LLPluginProcessChild(); + +	void init(U32 launcher_port); +	void idle(void); +	void sleep(F64 seconds); +	void pump(); + +	// returns true if the plugin is in the steady state (processing messages) +	bool isRunning(void); +	 +	// returns true if the plugin is unloaded or we're in an unrecoverable error state.	 +	bool isDone(void); +	 +	void killSockets(void); + +	F64 getSleepTime(void) const { return mSleepTime; }; +	 +	void sendMessageToPlugin(const LLPluginMessage &message); +	void sendMessageToParent(const LLPluginMessage &message); +	 +	// Inherited from LLPluginMessagePipeOwner +	/* virtual */ void receiveMessageRaw(const std::string &message); + +	// Inherited from LLPluginInstanceMessageListener +	/* virtual */ void receivePluginMessage(const std::string &message); +	 +private: + +	enum EState +	{ +		STATE_UNINITIALIZED, +		STATE_INITIALIZED,			// init() has been called +		STATE_CONNECTED,			// connected back to launcher +		STATE_PLUGIN_LOADING,		// plugin library needs to be loaded +		STATE_PLUGIN_LOADED,		// plugin library has been loaded +		STATE_PLUGIN_INITIALIZING,	// plugin is processing init message +		STATE_RUNNING,				// steady state (processing messages) +		STATE_UNLOADING,			// plugin has sent shutdown_response and needs to be unloaded +		STATE_UNLOADED,				// plugin has been unloaded +		STATE_ERROR,				// generic bailout state +		STATE_DONE					// state machine will sit in this state after either error or normal termination. +	}; +	EState mState; +	void setState(EState state); +	 +	LLHost mLauncherHost; +	LLSocket::ptr_t mSocket; +	 +	std::string mPluginFile; +	 +	LLPluginInstance *mInstance; + +	typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType; +	sharedMemoryRegionsType mSharedMemoryRegions; +	 +	LLTimer mHeartbeat; +	F64		mSleepTime; +	 +}; + +#endif // LL_LLPLUGINPROCESSCHILD_H diff --git a/indra/llplugin/llpluginprocessparent.cpp b/indra/llplugin/llpluginprocessparent.cpp new file mode 100644 index 0000000000..f18a117dde --- /dev/null +++ b/indra/llplugin/llpluginprocessparent.cpp @@ -0,0 +1,658 @@ +/**  + * @file llpluginprocessparent.cpp + * @brief LLPluginProcessParent handles the parent side of the external-process plugin API. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llpluginprocessparent.h" +#include "llpluginmessagepipe.h" +#include "llpluginmessageclasses.h" + +#include "llapr.h" + +// If we don't receive a heartbeat in this many seconds, we declare the plugin locked up. +static const F32 PLUGIN_LOCKED_UP_SECONDS = 10.0f; + +// Somewhat longer timeout for initial launch. +static const F32 PLUGIN_LAUNCH_SECONDS = 20.0f; + +//virtual  +LLPluginProcessParentOwner::~LLPluginProcessParentOwner() +{ +	 +} + +LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner) +{ +	mOwner = owner; +	mBoundPort = 0; +	mState = STATE_UNINITIALIZED; +	mDisableTimeout = false; +} + +LLPluginProcessParent::~LLPluginProcessParent() +{ +	LL_DEBUGS("Plugin") << "destructor" << LL_ENDL; + +	// Destroy any remaining shared memory regions +	sharedMemoryRegionsType::iterator iter; +	while((iter = mSharedMemoryRegions.begin()) != mSharedMemoryRegions.end()) +	{ +		// destroy the shared memory region +		iter->second->destroy(); +		 +		// and remove it from our map +		mSharedMemoryRegions.erase(iter); +	} + +	mProcess.kill(); +	killSockets(); +} + +void LLPluginProcessParent::killSockets(void) +{ +	killMessagePipe(); +	mListenSocket.reset(); +	mSocket.reset(); +} + +void LLPluginProcessParent::init(const std::string &launcher_filename, const std::string &plugin_filename) +{	 +	mProcess.setExecutable(launcher_filename); +	mPluginFile = plugin_filename; +	 +	setState(STATE_INITIALIZED); +} + +bool LLPluginProcessParent::accept() +{ +	bool result = false; +	 +	apr_status_t status = APR_EGENERAL; +	apr_socket_t *new_socket = NULL; +	 +	status = apr_socket_accept( +		&new_socket, +		mListenSocket->getSocket(), +		gAPRPoolp); + +	 +	if(status == APR_SUCCESS) +	{ +//		llinfos << "SUCCESS" << llendl; +		// Success.  Create a message pipe on the new socket + +		// we MUST create a new pool for the LLSocket, since it will take ownership of it and delete it in its destructor! +		apr_pool_t* new_pool = NULL; +		status = apr_pool_create(&new_pool, gAPRPoolp); + +		mSocket = LLSocket::create(new_socket, new_pool); +		new LLPluginMessagePipe(this, mSocket); + +		result = true; +	} +	else if(APR_STATUS_IS_EAGAIN(status)) +	{ +//		llinfos << "EAGAIN" << llendl; + +		// No incoming connections.  This is not an error. +		status = APR_SUCCESS; +	} +	else +	{ +//		llinfos << "Error:" << llendl; +		ll_apr_warn_status(status); +		 +		// Some other error. +		setState(STATE_ERROR); +	} +	 +	return result;	 +} + +void LLPluginProcessParent::idle(void) +{ +	bool idle_again; + +	do +	{ +		// Give time to network processing +		if(mMessagePipe) +		{ +			if(!mMessagePipe->pump()) +			{ +//				LL_WARNS("Plugin") << "Message pipe hit an error state" << LL_ENDL; +				setState(STATE_ERROR); +			} +		} + +		if((mSocketError != APR_SUCCESS) && (mState < STATE_ERROR)) +		{ +			// The socket is in an error state -- the plugin is gone. +			LL_WARNS("Plugin") << "Socket hit an error state (" << mSocketError << ")" << LL_ENDL; +			setState(STATE_ERROR); +		}	 +		 +		// If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState(). +		// USE THIS CAREFULLY, since it can starve other code.  Specifically make sure there's no way to get into a closed cycle and never return. +		// When in doubt, don't do it. +		idle_again = false; +		switch(mState) +		{ +			case STATE_UNINITIALIZED: +			break; + +			case STATE_INITIALIZED: +			{ +	 +				apr_status_t status = APR_SUCCESS; +				apr_sockaddr_t* addr = NULL; +				mListenSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); +				mBoundPort = 0; +				 +				// This code is based on parts of LLSocket::create() in lliosocket.cpp. +				 +				status = apr_sockaddr_info_get( +					&addr, +					"127.0.0.1", +					APR_INET, +					0,	// port 0 = ephemeral ("find me a port") +					0, +					gAPRPoolp); +					 +				if(ll_apr_warn_status(status)) +				{ +					killSockets(); +					setState(STATE_ERROR); +					break; +				} + +				// This allows us to reuse the address on quick down/up. This is unlikely to create problems. +				ll_apr_warn_status(apr_socket_opt_set(mListenSocket->getSocket(), APR_SO_REUSEADDR, 1)); +				 +				status = apr_socket_bind(mListenSocket->getSocket(), addr); +				if(ll_apr_warn_status(status)) +				{ +					killSockets(); +					setState(STATE_ERROR); +					break; +				} + +				// Get the actual port the socket was bound to +				{ +					apr_sockaddr_t* bound_addr = NULL; +					if(ll_apr_warn_status(apr_socket_addr_get(&bound_addr, APR_LOCAL, mListenSocket->getSocket()))) +					{ +						killSockets(); +						setState(STATE_ERROR); +						break; +					} +					mBoundPort = bound_addr->port;	 + +					if(mBoundPort == 0) +					{ +						LL_WARNS("Plugin") << "Bound port number unknown, bailing out." << LL_ENDL; +						 +						killSockets(); +						setState(STATE_ERROR); +						break; +					} +				} +				 +				LL_DEBUGS("Plugin") << "Bound tcp socket to port: " << addr->port << LL_ENDL; + +				// Make the listen socket non-blocking +				status = apr_socket_opt_set(mListenSocket->getSocket(), APR_SO_NONBLOCK, 1); +				if(ll_apr_warn_status(status)) +				{ +					killSockets(); +					setState(STATE_ERROR); +					break; +				} + +				apr_socket_timeout_set(mListenSocket->getSocket(), 0); +				if(ll_apr_warn_status(status)) +				{ +					killSockets(); +					setState(STATE_ERROR); +					break; +				} +				 +				// If it's a stream based socket, we need to tell the OS +				// to keep a queue of incoming connections for ACCEPT. +				status = apr_socket_listen( +					mListenSocket->getSocket(), +					10); // FIXME: Magic number for queue size +					 +				if(ll_apr_warn_status(status)) +				{ +					killSockets(); +					setState(STATE_ERROR); +					break; +				} +				 +				// If we got here, we're listening. +				setState(STATE_LISTENING); +			} +			break; +			 +			case STATE_LISTENING: +			{ +				// Launch the plugin process. +				 +				// Only argument to the launcher is the port number we're listening on +				std::stringstream stream; +				stream << mBoundPort; +				mProcess.addArgument(stream.str()); +				if(mProcess.launch() != 0) +				{ +					setState(STATE_ERROR); +				} +				else +				{ +					// This will allow us to time out if the process never starts. +					mHeartbeat.start(); +					mHeartbeat.setTimerExpirySec(PLUGIN_LAUNCH_SECONDS); +					setState(STATE_LAUNCHED); +				} +			} +			break; + +			case STATE_LAUNCHED: +				// waiting for the plugin to connect +				if(pluginLockedUpOrQuit()) +				{ +					setState(STATE_ERROR); +				} +				else +				{ +					// Check for the incoming connection. +					if(accept()) +					{ +						// Stop listening on the server port +						mListenSocket.reset(); +						setState(STATE_CONNECTED); +					} +				} +			break; +			 +			case STATE_CONNECTED: +				// waiting for hello message from the plugin + +				if(pluginLockedUpOrQuit()) +				{ +					setState(STATE_ERROR); +				} +			break; + +			case STATE_HELLO: +				LL_DEBUGS("Plugin") << "received hello message" << llendl; +				 +				// Send the message to load the plugin +				{ +					LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin"); +					message.setValue("file", mPluginFile); +					sendMessage(message); +				} + +				setState(STATE_LOADING); +			break; +			 +			case STATE_LOADING: +				// The load_plugin_response message will kick us from here into STATE_RUNNING +				if(pluginLockedUpOrQuit()) +				{ +					setState(STATE_ERROR); +				} +			break; +			 +			case STATE_RUNNING: +				if(pluginLockedUpOrQuit()) +				{ +					setState(STATE_ERROR); +				} +			break; +			 +			case STATE_EXITING: +				if(!mProcess.isRunning()) +				{ +					setState(STATE_CLEANUP); +				} +				else if(pluginLockedUp()) +				{ +					LL_WARNS("Plugin") << "timeout in exiting state, bailing out" << llendl; +					setState(STATE_ERROR); +				} +			break; + +			case STATE_ERROR: +				if(mOwner != NULL) +				{ +					mOwner->pluginDied(); +				} +				setState(STATE_CLEANUP); +			break; +			 +			case STATE_CLEANUP: +				mProcess.kill(); +				killSockets(); +				setState(STATE_DONE); +			break; +			 +			 +			case STATE_DONE: +				// just sit here. +			break; +			 +		} +	 +	} while (idle_again); +} + +bool LLPluginProcessParent::isLoading(void) +{ +	bool result = false; +	 +	if(mState <= STATE_LOADING) +		result = true; +		 +	return result; +} + +bool LLPluginProcessParent::isRunning(void) +{ +	bool result = false; +	 +	if(mState == STATE_RUNNING) +		result = true; +		 +	return result; +} + +bool LLPluginProcessParent::isDone(void) +{ +	bool result = false; +	 +	if(mState == STATE_DONE) +		result = true; +		 +	return result; +} + +void LLPluginProcessParent::setSleepTime(F64 sleep_time, bool force_send) +{ +	if(force_send || (sleep_time != mSleepTime)) +	{ +		// Cache the time locally +		mSleepTime = sleep_time; +		 +		if(canSendMessage()) +		{ +			// and send to the plugin. +			LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "sleep_time"); +			message.setValueReal("time", mSleepTime); +			sendMessage(message); +		} +		else +		{ +			// Too early to send -- the load_plugin_response message will trigger us to send mSleepTime later. +		} +	} +} + +void LLPluginProcessParent::sendMessage(const LLPluginMessage &message) +{ +	 +	std::string buffer = message.generate(); +	LL_DEBUGS("Plugin") << "Sending: " << buffer << LL_ENDL;	 +	writeMessageRaw(buffer); +} + + +void LLPluginProcessParent::receiveMessageRaw(const std::string &message) +{ +	LL_DEBUGS("Plugin") << "Received: " << message << LL_ENDL; + +	// FIXME: should this go into a queue instead? +	 +	LLPluginMessage parsed; +	if(parsed.parse(message) != -1) +	{ +		receiveMessage(parsed); +	} +} + +void LLPluginProcessParent::receiveMessage(const LLPluginMessage &message) +{ +	std::string message_class = message.getClass(); +	if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL) +	{ +		// internal messages should be handled here +		std::string message_name = message.getName(); +		if(message_name == "hello") +		{ +			if(mState == STATE_CONNECTED) +			{ +				// Plugin host has launched.  Tell it which plugin to load. +				setState(STATE_HELLO); +			} +			else +			{ +				LL_WARNS("Plugin") << "received hello message in wrong state -- bailing out" << LL_ENDL; +				setState(STATE_ERROR); +			} +			 +		} +		else if(message_name == "load_plugin_response") +		{ +			if(mState == STATE_LOADING) +			{ +				// Plugin has been loaded.  +				 +				// Check which message classes/versions the plugin supports. +				// TODO: check against current versions +				// TODO: kill plugin on major mismatches? +				mMessageClassVersions = message.getValueLLSD("versions"); +				LLSD::map_iterator iter; +				for(iter = mMessageClassVersions.beginMap(); iter != mMessageClassVersions.endMap(); iter++) +				{ +					LL_INFOS("Plugin") << "message class: " << iter->first << " -> version: " << iter->second.asString() << LL_ENDL; +				} +				 +				mPluginVersionString = message.getValue("plugin_version"); +				 +				// Send initial sleep time +				setSleepTime(mSleepTime, true);			 + +				setState(STATE_RUNNING); +			} +			else +			{ +				LL_WARNS("Plugin") << "received load_plugin_response message in wrong state -- bailing out" << LL_ENDL; +				setState(STATE_ERROR); +			} +		} +		else if(message_name == "heartbeat") +		{ +			// this resets our timer. +			mHeartbeat.setTimerExpirySec(PLUGIN_LOCKED_UP_SECONDS); +		} +		else if(message_name == "shm_add_response") +		{ +			// Nothing to do here. +		} +		else if(message_name == "shm_remove_response") +		{ +			std::string name = message.getValue("name"); +			sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); +			 +			if(iter != mSharedMemoryRegions.end()) +			{ +				// destroy the shared memory region +				iter->second->destroy(); +				 +				// and remove it from our map +				mSharedMemoryRegions.erase(iter); +			} +		} +		else +		{ +			LL_WARNS("Plugin") << "Unknown internal message from child: " << message_name << LL_ENDL; +		} +	} +	else +	{ +		if(mOwner != NULL) +		{ +			mOwner->receivePluginMessage(message); +		} +	} +} + +std::string LLPluginProcessParent::addSharedMemory(size_t size) +{ +	std::string name; +	 +	LLPluginSharedMemory *region = new LLPluginSharedMemory; + +	// This is a new region +	if(region->create(size)) +	{ +		name = region->getName(); +		 +		mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region)); +		 +		LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add"); +		message.setValue("name", name); +		message.setValueS32("size", (S32)size); +		sendMessage(message); +	} +	else +	{ +		LL_WARNS("Plugin") << "Couldn't create a shared memory segment!" << LL_ENDL; + +		// Don't leak +		delete region; +	} + +	return name; +} + +void LLPluginProcessParent::removeSharedMemory(const std::string &name) +{ +	sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); +	 +	if(iter != mSharedMemoryRegions.end()) +	{ +		// This segment exists.  Send the message to the child to unmap it.  The response will cause the parent to unmap our end. +		LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove"); +		message.setValue("name", name); +		sendMessage(message); +	} +	else +	{ +		LL_WARNS("Plugin") << "Request to remove an unknown shared memory segment." << LL_ENDL; +	} +} +size_t LLPluginProcessParent::getSharedMemorySize(const std::string &name) +{ +	size_t result = 0; +	 +	sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); +	if(iter != mSharedMemoryRegions.end()) +	{ +		result = iter->second->getSize(); +	} +	 +	return result; +} +void *LLPluginProcessParent::getSharedMemoryAddress(const std::string &name) +{ +	void *result = NULL; +	 +	sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); +	if(iter != mSharedMemoryRegions.end()) +	{ +		result = iter->second->getMappedAddress(); +	} +	 +	return result; +} + +std::string LLPluginProcessParent::getMessageClassVersion(const std::string &message_class) +{ +	std::string result; +	 +	if(mMessageClassVersions.has(message_class)) +	{ +		result = mMessageClassVersions[message_class].asString(); +	} +	 +	return result; +} + +std::string LLPluginProcessParent::getPluginVersion(void) +{ +	return mPluginVersionString; +} + +void LLPluginProcessParent::setState(EState state) +{ +	LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL; +	mState = state;  +}; + +bool LLPluginProcessParent::pluginLockedUpOrQuit() +{ +	bool result = false; +	 +	if(!mDisableTimeout) +	{ +		if(!mProcess.isRunning()) +		{ +			LL_WARNS("Plugin") << "child exited" << llendl; +			result = true; +		} +		else if(pluginLockedUp()) +		{ +			LL_WARNS("Plugin") << "timeout" << llendl; +			result = true; +		} +	} +	 +	return result; +} + +bool LLPluginProcessParent::pluginLockedUp() +{ +	// If the timer has expired, the plugin has locked up. +	return mHeartbeat.hasExpired(); +} + diff --git a/indra/llplugin/llpluginprocessparent.h b/indra/llplugin/llpluginprocessparent.h new file mode 100644 index 0000000000..545eb85c9a --- /dev/null +++ b/indra/llplugin/llpluginprocessparent.h @@ -0,0 +1,147 @@ +/**  + * @file llpluginprocessparent.h + * @brief LLPluginProcessParent handles the parent side of the external-process plugin API. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINPROCESSPARENT_H +#define LL_LLPLUGINPROCESSPARENT_H + +#include "llprocesslauncher.h" +#include "llpluginmessage.h" +#include "llpluginmessagepipe.h" +#include "llpluginsharedmemory.h" + +#include "lliosocket.h" + +class LLPluginProcessParentOwner +{ +public: +	virtual ~LLPluginProcessParentOwner(); +	virtual void receivePluginMessage(const LLPluginMessage &message) = 0; +	// This will only be called when the plugin has died unexpectedly  +	virtual void pluginDied() {}; +}; + +class LLPluginProcessParent : public LLPluginMessagePipeOwner +{ +	LOG_CLASS(LLPluginProcessParent); +public: +	LLPluginProcessParent(LLPluginProcessParentOwner *owner); +	~LLPluginProcessParent(); +		 +	void init(const std::string &launcher_filename, const std::string &plugin_filename); +	void idle(void); +	 +	// returns true if the plugin is on its way to steady state +	bool isLoading(void); + +	// returns true if the plugin is in the steady state (processing messages) +	bool isRunning(void); + +	// returns true if the process has exited or we've had a fatal error +	bool isDone(void);	 +	 +	void killSockets(void); + +	void setSleepTime(F64 sleep_time, bool force_send = false); +	F64 getSleepTime(void) const { return mSleepTime; }; + +	void sendMessage(const LLPluginMessage &message); +	 +	void receiveMessage(const LLPluginMessage &message); +	 +	// Inherited from LLPluginMessagePipeOwner +	void receiveMessageRaw(const std::string &message); +	 +	// This adds a memory segment shared with the client, generating a name for the segment.  The name generated is guaranteed to be unique on the host. +	// The caller must call removeSharedMemory first (and wait until getSharedMemorySize returns 0 for the indicated name) before re-adding a segment with the same name. +	std::string addSharedMemory(size_t size); +	// Negotiates for the removal of a shared memory segment.  It is the caller's responsibility to ensure that nothing touches the memory +	// after this has been called, since the segment will be unmapped shortly thereafter. +	void removeSharedMemory(const std::string &name); +	size_t getSharedMemorySize(const std::string &name); +	void *getSharedMemoryAddress(const std::string &name); +	 +	// Returns the version string the plugin indicated for the message class, or an empty string if that class wasn't in the list. +	std::string getMessageClassVersion(const std::string &message_class); + +	std::string getPluginVersion(void); +	 +	bool getDisableTimeout() { return mDisableTimeout; }; +	void setDisableTimeout(bool disable) { mDisableTimeout = disable; }; + +private: + +	enum EState +	{ +		STATE_UNINITIALIZED, +		STATE_INITIALIZED,		// init() has been called +		STATE_LISTENING,		// listening for incoming connection +		STATE_LAUNCHED,			// process has been launched +		STATE_CONNECTED,		// process has connected +		STATE_HELLO,			// first message from the plugin process has been received +		STATE_LOADING,			// process has been asked to load the plugin +		STATE_RUNNING,			//  +		STATE_ERROR,			// generic bailout state +		STATE_CLEANUP,			// clean everything up +		STATE_EXITING,			// Tried to kill process, waiting for it to exit +		STATE_DONE				// + +	}; +	EState mState; +	void setState(EState state); +	 +	bool pluginLockedUp(); +	bool pluginLockedUpOrQuit(); + +	bool accept(); +		 +	LLSocket::ptr_t mListenSocket; +	LLSocket::ptr_t mSocket; +	U32 mBoundPort; +	 +	LLProcessLauncher mProcess; +	 +	std::string mPluginFile; + +	LLPluginProcessParentOwner *mOwner; +	 +	typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType; +	sharedMemoryRegionsType mSharedMemoryRegions; +	 +	LLSD mMessageClassVersions; +	std::string mPluginVersionString; +	 +	LLTimer mHeartbeat; +	F64		mSleepTime; +	 +	bool mDisableTimeout; +}; + +#endif // LL_LLPLUGINPROCESSPARENT_H diff --git a/indra/llplugin/llpluginsharedmemory.cpp b/indra/llplugin/llpluginsharedmemory.cpp new file mode 100644 index 0000000000..ce8b8e3e09 --- /dev/null +++ b/indra/llplugin/llpluginsharedmemory.cpp @@ -0,0 +1,495 @@ +/**  + * @file llpluginsharedmemory.cpp + * @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llpluginsharedmemory.h" + +// on Mac and Linux, we use the native shm_open/mmap interface by using +//	#define USE_SHM_OPEN_SHARED_MEMORY 1 +// in the appropriate sections below. + +// For Windows, use: +//	#define USE_WIN32_SHARED_MEMORY 1 + +// If we ever want to fall back to the apr implementation for a platform, use: +//	#define USE_APR_SHARED_MEMORY 1 + +#if LL_WINDOWS +//	#define USE_APR_SHARED_MEMORY 1 +	#define USE_WIN32_SHARED_MEMORY 1 +#elif LL_DARWIN +	#define USE_SHM_OPEN_SHARED_MEMORY 1 +#elif LL_LINUX +	#define USE_SHM_OPEN_SHARED_MEMORY 1 +#endif + + +// FIXME: This path thing is evil and unacceptable. +#if LL_WINDOWS +	#define APR_SHARED_MEMORY_PREFIX_STRING "C:\\LLPlugin_" +	// Apparnently using the "Global\\" prefix here only works from administrative accounts under Vista. +	// Other options I've seen referenced are "Local\\" and "Session\\". +	#define WIN32_SHARED_MEMORY_PREFIX_STRING "Local\\LL_" +#else +	// mac and linux +	#define APR_SHARED_MEMORY_PREFIX_STRING "/tmp/LLPlugin_" +	#define SHM_OPEN_SHARED_MEMORY_PREFIX_STRING "/LL" +#endif + +#if USE_APR_SHARED_MEMORY  +	#include "llapr.h" +	#include "apr_shm.h" +#elif USE_SHM_OPEN_SHARED_MEMORY +	#include <sys/fcntl.h> +	#include <sys/mman.h> +	#include <errno.h> +#elif USE_WIN32_SHARED_MEMORY +#include <windows.h> +#endif // USE_APR_SHARED_MEMORY + + +int LLPluginSharedMemory::sSegmentNumber = 0; + +std::string LLPluginSharedMemory::createName(void) +{ +	std::stringstream newname; + +#if LL_WINDOWS +	newname << GetCurrentProcessId(); +#else // LL_WINDOWS +	newname << getpid(); +#endif // LL_WINDOWS +		 +	newname << "_" << sSegmentNumber++; +	 +	return newname.str(); +} + +class LLPluginSharedMemoryPlatformImpl +{ +public: +	LLPluginSharedMemoryPlatformImpl(); +	~LLPluginSharedMemoryPlatformImpl(); +	 +#if USE_APR_SHARED_MEMORY +	apr_shm_t* mAprSharedMemory;	 +#elif USE_SHM_OPEN_SHARED_MEMORY +	int mSharedMemoryFD; +#elif USE_WIN32_SHARED_MEMORY +	HANDLE mMapFile; +#endif + +}; + +LLPluginSharedMemory::LLPluginSharedMemory() +{ +	mSize = 0; +	mMappedAddress = NULL; +	mNeedsDestroy = false; + +	mImpl = new LLPluginSharedMemoryPlatformImpl; +} + +LLPluginSharedMemory::~LLPluginSharedMemory() +{ +	if(mNeedsDestroy) +		destroy(); +	else +		detach(); +		 +	unlink(); +	 +	delete mImpl; +} + +#if USE_APR_SHARED_MEMORY +// MARK: apr implementation + +LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl() +{ +	mAprSharedMemory = NULL; +} + +LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl() +{ +	 +} + +bool LLPluginSharedMemory::map(void) +{ +	mMappedAddress = apr_shm_baseaddr_get(mImpl->mAprSharedMemory); +	if(mMappedAddress == NULL) +	{ +		return false; +	} +	 +	return true; +} + +bool LLPluginSharedMemory::unmap(void) +{ +	// This is a no-op under apr. +	return true; +} + +bool LLPluginSharedMemory::close(void) +{ +	// This is a no-op under apr. +	return true; +} + +bool LLPluginSharedMemory::unlink(void) +{ +	// This is a no-op under apr. +	return true; +} + + +bool LLPluginSharedMemory::create(size_t size) +{ +	mName = APR_SHARED_MEMORY_PREFIX_STRING; +	mName += createName(); +	mSize = size; +	 +	apr_status_t status = apr_shm_create( &(mImpl->mAprSharedMemory), mSize, mName.c_str(), gAPRPoolp ); +	 +	if(ll_apr_warn_status(status)) +	{ +		return false; +	} + +	mNeedsDestroy = true; +	 +	return map(); +} + +bool LLPluginSharedMemory::destroy(void) +{ +	if(mImpl->mAprSharedMemory) +	{ +		apr_status_t status = apr_shm_destroy(mImpl->mAprSharedMemory); +		if(ll_apr_warn_status(status)) +		{ +			// TODO: Is this a fatal error?  I think not... +		} +		mImpl->mAprSharedMemory = NULL; +	} +	 +	return true; +} + +bool LLPluginSharedMemory::attach(const std::string &name, size_t size) +{ +	mName = name; +	mSize = size; +	 +	apr_status_t status = apr_shm_attach( &(mImpl->mAprSharedMemory), mName.c_str(), gAPRPoolp ); +	 +	if(ll_apr_warn_status(status)) +	{ +		return false; +	} + +	return map(); +} + + +bool LLPluginSharedMemory::detach(void) +{ +	if(mImpl->mAprSharedMemory) +	{ +		apr_status_t status = apr_shm_detach(mImpl->mAprSharedMemory); +		if(ll_apr_warn_status(status)) +		{ +			// TODO: Is this a fatal error?  I think not... +		} +		mImpl->mAprSharedMemory = NULL; +	} +	 +	return true; +} + + +#elif USE_SHM_OPEN_SHARED_MEMORY +// MARK: shm_open/mmap implementation + +LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl() +{ +	mSharedMemoryFD = -1; +} + +LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl() +{ +} + +bool LLPluginSharedMemory::map(void) +{ +	mMappedAddress = ::mmap(NULL, mSize, PROT_READ | PROT_WRITE, MAP_SHARED, mImpl->mSharedMemoryFD, 0); +	if(mMappedAddress == NULL) +	{ +		return false; +	} +	 +	LL_DEBUGS("Plugin") << "memory mapped at " << mMappedAddress << LL_ENDL; + +	return true; +} + +bool LLPluginSharedMemory::unmap(void) +{ +	if(mMappedAddress != NULL) +	{ +		LL_DEBUGS("Plugin") << "calling munmap(" << mMappedAddress << ", " << mSize << ")" << LL_ENDL; +		if(::munmap(mMappedAddress, mSize) == -1)	 +		{ +			// TODO: Is this a fatal error?  I think not... +		} +		 +		mMappedAddress = NULL; +	} + +	return true; +} + +bool LLPluginSharedMemory::close(void) +{ +	if(mImpl->mSharedMemoryFD != -1) +	{ +		LL_DEBUGS("Plugin") << "calling close(" << mImpl->mSharedMemoryFD << ")" << LL_ENDL; +		if(::close(mImpl->mSharedMemoryFD) == -1) +		{ +			// TODO: Is this a fatal error?  I think not... +		} +		 +		mImpl->mSharedMemoryFD = -1; +	} +	return true; +} + +bool LLPluginSharedMemory::unlink(void) +{ +	if(!mName.empty()) +	{ +		if(::shm_unlink(mName.c_str()) == -1) +		{ +			return false; +		} +	} +		 +	return true; +} + + +bool LLPluginSharedMemory::create(size_t size) +{ +	mName = SHM_OPEN_SHARED_MEMORY_PREFIX_STRING; +	mName += createName(); +	mSize = size; +	 +	// Preemptive unlink, just in case something didn't get cleaned up. +	unlink(); + +	mImpl->mSharedMemoryFD = ::shm_open(mName.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); +	if(mImpl->mSharedMemoryFD == -1) +	{ +		return false; +	} +	 +	mNeedsDestroy = true; +	 +	if(::ftruncate(mImpl->mSharedMemoryFD, mSize) == -1) +	{ +		return false; +	} +	 +	 +	return map(); +} + +bool LLPluginSharedMemory::destroy(void) +{ +	unmap(); +	close();	 +	 +	return true; +} + + +bool LLPluginSharedMemory::attach(const std::string &name, size_t size) +{ +	mName = name; +	mSize = size; + +	mImpl->mSharedMemoryFD = ::shm_open(mName.c_str(), O_RDWR, S_IRUSR | S_IWUSR); +	if(mImpl->mSharedMemoryFD == -1) +	{ +		return false; +	} +	 +	// unlink here so the segment will be cleaned up automatically after the last close. +	unlink(); +	 +	return map(); +} + +bool LLPluginSharedMemory::detach(void) +{ +	unmap(); +	close();	 +	return true; +} + +#elif USE_WIN32_SHARED_MEMORY +// MARK: Win32 CreateFileMapping-based implementation + +// Reference: http://msdn.microsoft.com/en-us/library/aa366551(VS.85).aspx + +LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl() +{ +	mMapFile = NULL; +} + +LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl() +{ +	 +} + +bool LLPluginSharedMemory::map(void) +{ +	mMappedAddress = MapViewOfFile( +		mImpl->mMapFile,			// handle to map object +		FILE_MAP_ALL_ACCESS,		// read/write permission +		0,                    +		0,                    +		mSize); +		 +	if(mMappedAddress == NULL) +	{ +		LL_WARNS("Plugin") << "MapViewOfFile failed: " << GetLastError() << LL_ENDL; +		return false; +	} +	 +	LL_DEBUGS("Plugin") << "memory mapped at " << mMappedAddress << LL_ENDL; + +	return true; +} + +bool LLPluginSharedMemory::unmap(void) +{ +	if(mMappedAddress != NULL) +	{ +		UnmapViewOfFile(mMappedAddress);	 +		mMappedAddress = NULL; +	} + +	return true; +} + +bool LLPluginSharedMemory::close(void) +{ +	if(mImpl->mMapFile != NULL) +	{ +		CloseHandle(mImpl->mMapFile); +		mImpl->mMapFile = NULL; +	} +	 +	return true; +} + +bool LLPluginSharedMemory::unlink(void) +{ +	// This is a no-op on Windows. +	return true; +} + + +bool LLPluginSharedMemory::create(size_t size) +{ +	mName = WIN32_SHARED_MEMORY_PREFIX_STRING; +	mName += createName(); +	mSize = size; + +	mImpl->mMapFile = CreateFileMappingA( +                 INVALID_HANDLE_VALUE,		// use paging file +                 NULL,						// default security  +                 PAGE_READWRITE,			// read/write access +                 0,							// max. object size  +                 mSize,						// buffer size   +                 mName.c_str());			// name of mapping object + +	if(mImpl->mMapFile == NULL) +	{ +		LL_WARNS("Plugin") << "CreateFileMapping failed: " << GetLastError() << LL_ENDL; +		return false; +	} + +	mNeedsDestroy = true; +		 +	return map(); +} + +bool LLPluginSharedMemory::destroy(void) +{ +	unmap(); +	close(); +	return true; +} + +bool LLPluginSharedMemory::attach(const std::string &name, size_t size) +{ +	mName = name; +	mSize = size; + +	mImpl->mMapFile = OpenFileMappingA( +				FILE_MAP_ALL_ACCESS,		// read/write access +				FALSE,						// do not inherit the name +				mName.c_str());				// name of mapping object +	 +	if(mImpl->mMapFile == NULL) +	{ +		LL_WARNS("Plugin") << "OpenFileMapping failed: " << GetLastError() << LL_ENDL; +		return false; +	} +		 +	return map(); +} + +bool LLPluginSharedMemory::detach(void) +{ +	unmap(); +	close(); +	return true; +} + + + +#endif diff --git a/indra/llplugin/llpluginsharedmemory.h b/indra/llplugin/llpluginsharedmemory.h new file mode 100644 index 0000000000..a4613b9a54 --- /dev/null +++ b/indra/llplugin/llpluginsharedmemory.h @@ -0,0 +1,79 @@ +/**  + * @file llpluginsharedmemory.h + * @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LL_LLPLUGINSHAREDMEMORY_H +#define LL_LLPLUGINSHAREDMEMORY_H + +class LLPluginSharedMemoryPlatformImpl; + +class LLPluginSharedMemory +{ +	LOG_CLASS(LLPluginSharedMemory); +public: +	LLPluginSharedMemory(); +	~LLPluginSharedMemory(); +	 +	// Parent will use create/destroy, child will use attach/detach. +	// Message transactions will ensure child attaches after parent creates and detaches before parent destroys. +	 +	// create() implicitly creates a name for the segment which is guaranteed to be unique on the host at the current time. +	bool create(size_t size); +	bool destroy(void); +	 +	bool attach(const std::string &name, size_t size); +	bool detach(void); + +	bool isMapped(void) const { return (mMappedAddress != NULL); }; +	void *getMappedAddress(void) const { return mMappedAddress; }; +	size_t getSize(void) const { return mSize; }; +	std::string getName() const { return mName; }; +	 +private: +	bool map(void); +	bool unmap(void); +	bool close(void); +	bool unlink(void); +	 +	std::string mName; +	size_t mSize; +	void *mMappedAddress; +	bool mNeedsDestroy; +	 +	LLPluginSharedMemoryPlatformImpl *mImpl; +	 +	static int sSegmentNumber; +	static std::string createName(); +	 +}; + + + +#endif // LL_LLPLUGINSHAREDMEMORY_H diff --git a/indra/llplugin/slplugin/CMakeLists.txt b/indra/llplugin/slplugin/CMakeLists.txt new file mode 100644 index 0000000000..4a7d670c23 --- /dev/null +++ b/indra/llplugin/slplugin/CMakeLists.txt @@ -0,0 +1,55 @@ +project(SLPlugin) + +include(00-Common) +include(LLCommon) +include(LLPlugin) +include(Linking) +include(PluginAPI) +include(LLMessage) + +include_directories( +    ${LLPLUGIN_INCLUDE_DIRS} +    ${LLMESSAGE_INCLUDE_DIRS} +    ${LLCOMMON_INCLUDE_DIRS} +) + +if (DARWIN) +    include(CMakeFindFrameworks) +    find_library(CARBON_LIBRARY Carbon) +endif (DARWIN) + + +### SLPlugin + +set(SLPlugin_SOURCE_FILES +    slplugin.cpp +    ) + +add_executable(SLPlugin +    WIN32 +    ${SLPlugin_SOURCE_FILES} +) + +target_link_libraries(SLPlugin +  ${LLPLUGIN_LIBRARIES} +  ${LLMESSAGE_LIBRARIES} +  ${LLCOMMON_LIBRARIES} +  ${PLUGIN_API_WINDOWS_LIBRARIES} +) + +add_dependencies(SLPlugin +  ${LLPLUGIN_LIBRARIES} +  ${LLMESSAGE_LIBRARIES} +  ${LLCOMMON_LIBRARIES} +) + +if (DARWIN) +  # Mac version needs to link against carbon, and also needs an embedded plist (to set LSBackgroundOnly) +  target_link_libraries(SLPlugin ${CARBON_LIBRARY}) +  set_target_properties( +    SLPlugin +    PROPERTIES +    LINK_FLAGS "-Wl,-sectcreate,__TEXT,__info_plist,${CMAKE_CURRENT_SOURCE_DIR}/slplugin_info.plist" +  ) +endif (DARWIN) + diff --git a/indra/llplugin/slplugin/slplugin.cpp b/indra/llplugin/slplugin/slplugin.cpp new file mode 100644 index 0000000000..005e427572 --- /dev/null +++ b/indra/llplugin/slplugin/slplugin.cpp @@ -0,0 +1,243 @@ +/**  + * @file slplugin.cpp + * @brief Loader shell for plugins, intended to be launched by the plugin host application, which directly loads a plugin dynamic library. + * + * $LicenseInfo:firstyear=2008&license=viewergpl$ + * + * Copyright (c) 2008, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + + +#include "linden_common.h" + +#include "llpluginprocesschild.h" +#include "llpluginmessage.h" +#include "llerrorcontrol.h" +#include "llapr.h" +#include "llstring.h" + +#if LL_DARWIN +	#include <Carbon/Carbon.h> +#endif + +#if LL_DARWIN || LL_LINUX +	#include <signal.h> +#endif + +/* +	On Mac OS, since we call WaitNextEvent, this process will show up in the dock unless we set the LSBackgroundOnly flag in the Info.plist. +	 +	Normally non-bundled binaries don't have an info.plist file, but it's possible to embed one in the binary by adding this to the linker flags: +	 +	-sectcreate __TEXT __info_plist /path/to/slplugin_info.plist +	 +	which means adding this to the gcc flags: +	 +	-Wl,-sectcreate,__TEXT,__info_plist,/path/to/slplugin_info.plist +	 +*/ + +#if LL_DARWIN || LL_LINUX +// Signal handlers to make crashes not show an OS dialog... +static void crash_handler(int sig) +{ +	// Just exit cleanly. +	// TODO: add our own crash reporting +	_exit(1); +} +#endif	 + +#if LL_WINDOWS +#include <windows.h> +//////////////////////////////////////////////////////////////////////////////// +//	Our exception handler - will probably just exit and the host application +//	will miss the heartbeat and log the error in the usual fashion. +LONG WINAPI myWin32ExceptionHandler( struct _EXCEPTION_POINTERS* exception_infop ) +{ +	//std::cerr << "This plugin (" << __FILE__ << ") - "; +	//std::cerr << "intercepted an unhandled exception and will exit immediately." << std::endl; + +	// TODO: replace exception handler before we exit? +	return EXCEPTION_EXECUTE_HANDLER;	 +} + +//////////////////////////////////////////////////////////////////////////////// +//	Hook our exception handler and replace the system one +void initExceptionHandler() +{ +	LPTOP_LEVEL_EXCEPTION_FILTER prev_filter; + +	// save old exception handler in case we need to restore it at the end +	prev_filter = SetUnhandledExceptionFilter( myWin32ExceptionHandler ); +} + +bool checkExceptionHandler() +{ +	bool ok = true; +	LPTOP_LEVEL_EXCEPTION_FILTER prev_filter; +	prev_filter = SetUnhandledExceptionFilter(myWin32ExceptionHandler); + +	if (prev_filter != myWin32ExceptionHandler) +	{ +		LL_WARNS("AppInit") << "Our exception handler (" << (void *)myWin32ExceptionHandler << ") replaced with " << prev_filter << "!" << LL_ENDL; +		ok = false; +	} + +	if (prev_filter == NULL) +	{ +		ok = FALSE; +		if (myWin32ExceptionHandler == NULL) +		{ +			LL_WARNS("AppInit") << "Exception handler uninitialized." << LL_ENDL; +		} +		else +		{ +			LL_WARNS("AppInit") << "Our exception handler (" << (void *)myWin32ExceptionHandler << ") replaced with NULL!" << LL_ENDL; +		} +	} + +	return ok; +} +#endif + +// If this application on Windows platform is a console application, a console is always  +// created which is bad. Making it a Windows "application" via CMake settings but not +// adding any code to explicitly create windows does the right thing. +#if LL_WINDOWS +int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) +#else +int main(int argc, char **argv) +#endif +{ +	ll_init_apr(); + +	// Set up llerror logging  +	{ +		LLError::initForApplication("."); +		LLError::setDefaultLevel(LLError::LEVEL_INFO); +//		LLError::setTagLevel("Plugin", LLError::LEVEL_DEBUG); +//		LLError::logToFile("slplugin.log"); +	} + +#if LL_WINDOWS +	if( strlen( lpCmdLine ) == 0 ) +	{ +		LL_ERRS("slplugin") << "usage: " << "SLPlugin" << " launcher_port" << LL_ENDL; +	}; +	 +	U32 port = 0; +	if(!LLStringUtil::convertToU32(lpCmdLine, port)) +	{ +		LL_ERRS("slplugin") << "port number must be numeric" << LL_ENDL; +	}; + +	// Insert our exception handler into the system so this plugin doesn't  +	// display a crash message if something bad happens. The host app will +	// see the missing heartbeat and log appropriately. +	initExceptionHandler(); +#elif LL_DARWIN || LL_LINUX +	if(argc < 2) +	{ +		LL_ERRS("slplugin") << "usage: " << argv[0] << " launcher_port" << LL_ENDL; +	} +	 +	U32 port = 0; +	if(!LLStringUtil::convertToU32(argv[1], port)) +	{ +		LL_ERRS("slplugin") << "port number must be numeric" << LL_ENDL; +	} + +	// Catch signals that most kinds of crashes will generate, and exit cleanly so the system crash dialog isn't shown. +	signal(SIGILL, &crash_handler);		// illegal instruction +# if LL_DARWIN +	signal(SIGEMT, &crash_handler);		// emulate instruction executed +# endif // LL_DARWIN +	signal(SIGFPE, &crash_handler);		// floating-point exception +	signal(SIGBUS, &crash_handler);		// bus error +	signal(SIGSEGV, &crash_handler);	// segmentation violation +	signal(SIGSYS, &crash_handler);		// non-existent system call invoked +#endif + +	LLPluginProcessChild *plugin = new LLPluginProcessChild(); + +	plugin->init(port); +	 +	LLTimer timer; +	timer.start(); + +#if LL_WINDOWS +	checkExceptionHandler(); +#endif +		 +	while(!plugin->isDone()) +	{ +		timer.reset();	 +		plugin->idle(); +#if LL_DARWIN +		{ +			// Some plugins (webkit at least) will want an event loop.  This qualifies. +			EventRecord evt; +			WaitNextEvent(0, &evt, 0, NULL); +		} +#endif +		F64 elapsed = timer.getElapsedTimeF64(); +		F64 remaining = plugin->getSleepTime() - elapsed; +		 +		if(remaining <= 0.0f) +		{ +			// We've already used our full allotment. +//			LL_INFOS("slplugin") << "elapsed = " << elapsed * 1000.0f << " ms, remaining = " << remaining * 1000.0f << " ms, not sleeping" << LL_ENDL; + +			// Still need to service the network... +			plugin->pump(); +		} +		else +		{ + +//			LL_INFOS("slplugin") << "elapsed = " << elapsed * 1000.0f << " ms, remaining = " << remaining * 1000.0f << " ms, sleeping for " << remaining * 1000.0f << " ms" << LL_ENDL; +//			timer.reset();	 +			 +			// This also services the network as needed. +			plugin->sleep(remaining); +			 +//			LL_INFOS("slplugin") << "slept for "<< timer.getElapsedTimeF64() * 1000.0f << " ms" <<  LL_ENDL; +		} + +#if LL_WINDOWS +	// More agressive checking of interfering exception handlers. +	// Doesn't appear to be required so far - even for plugins  +	// that do crash with a single call to the intercept  +	// exception handler such as QuickTime. +	//checkExceptionHandler(); +#endif +	} + +	delete plugin; +	 +	ll_cleanup_apr();	 + +	return 0; +} + diff --git a/indra/llplugin/slplugin/slplugin_info.plist b/indra/llplugin/slplugin/slplugin_info.plist new file mode 100644 index 0000000000..b1daf87424 --- /dev/null +++ b/indra/llplugin/slplugin/slplugin_info.plist @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> +	<key>CFBundleDevelopmentRegion</key> +	<string>English</string> +	<key>CFBundleInfoDictionaryVersion</key> +	<string>6.0</string> +	<key>LSBackgroundOnly</key> +	<true/> +</dict> +</plist> | 
