/** * @file llpluginclassmedia.cpp * @brief LLPluginClassMedia handles a plugin which knows about the "media" message class. * * @cond * $LicenseInfo:firstyear=2008&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ * @endcond */ #include "linden_common.h" #include "indra_constants.h" #include "llpluginclassmedia.h" #include "llpluginmessageclasses.h" #include "llcontrol.h" #if LL_DARWIN || LL_LINUX || __FreeBSD__ extern LLControlGroup gSavedSettings; #endif #if LL_DARWIN extern bool gHiDPISupport; #endif 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; reset(); //debug use mDeleteOK = true ; } LLPluginClassMedia::~LLPluginClassMedia() { llassert_always(mDeleteOK) ; reset(); } bool LLPluginClassMedia::init(const std::string &launcher_filename, const std::string &plugin_dir, const std::string &plugin_filename, bool debug) { LL_DEBUGS("Plugin") << "launcher: " << launcher_filename << LL_ENDL; LL_DEBUGS("Plugin") << "dir: " << plugin_dir << LL_ENDL; LL_DEBUGS("Plugin") << "plugin: " << plugin_filename << LL_ENDL; mPlugin = LLPluginProcessParent::create(this); mPlugin->setSleepTime(mSleepTime); // Queue up the media init message -- it will be sent after all the currently queued messages. LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "init"); message.setValue("target", mTarget); message.setValueReal("factor", mZoomFactor); sendMessage(message); mPlugin->init(launcher_filename, plugin_dir, plugin_filename, debug); return true; } void LLPluginClassMedia::reset() { if(mPlugin) { mPlugin->requestShutdown(); mPlugin.reset(); } 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; mRequestedTextureWidth = 0; mRequestedTextureHeight = 0; mFullMediaWidth = 0; mFullMediaHeight = 0; mTextureWidth = 0; mTextureHeight = 0; mMediaWidth = 0; mMediaHeight = 0; mDirtyRect = LLRect::null; mAutoScaleMedia = false; mRequestedVolume = 0.0f; mPriority = PRIORITY_NORMAL; mLowPrioritySizeLimit = LOW_PRIORITY_TEXTURE_SIZE_DEFAULT; mAllowDownsample = false; mPadding = 0; mLastMouseX = 0; mLastMouseY = 0; mStatus = LLPluginClassMediaOwner::MEDIA_NONE; mSleepTime = 1.0f / 100.0f; mCanCut = false; mCanCopy = false; mCanPaste = false; mMediaName.clear(); mMediaDescription.clear(); mBackgroundColor = LLColor4(1.0f, 1.0f, 1.0f, 1.0f); // media_browser class mNavigateURI.clear(); mNavigateResultCode = -1; mNavigateResultString.clear(); mHistoryBackAvailable = false; mHistoryForwardAvailable = false; mStatusText.clear(); mProgressPercent = 0; mClickURL.clear(); mClickNavType.clear(); mClickTarget.clear(); mClickUUID.clear(); mStatusCode = 0; mClickEnforceTarget = false; // media_time class mCurrentTime = 0.0f; mDuration = 0.0f; mCurrentRate = 0.0f; mLoadedDuration = 0.0f; } void LLPluginClassMedia::idle(void) { if(mPlugin) { mPlugin->idle(); } if((mMediaWidth == -1) || (!mTextureParamsReceived) || (mPlugin == NULL) || (mPlugin->isBlocked()) || (mOwner == 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 if (addr) { memset( addr, 0x00, newsize ); } else { LL_WARNS("Plugin") << "Failed to get previously created shared memory address: " << mTextureSharedMemoryName << " size: " << mTextureSharedMemorySize << LL_ENDL; } // We could do this to force an update, but textureValid() will still be returning false until the first roundtrip to the plugin, // so it may not be worthwhile. // mDirtyRect.setOriginAndSize(0, 0, mRequestedMediaWidth, mRequestedMediaHeight); } } // This is our local indicator that a change is in progress. mTextureWidth = -1; mTextureHeight = -1; mMediaWidth = -1; mMediaHeight = -1; // This invalidates any existing dirty rect. resetDirty(); // Send a size change message to the plugin { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change"); message.setValue("name", mTextureSharedMemoryName); message.setValueS32("width", mRequestedMediaWidth); message.setValueS32("height", mRequestedMediaHeight); message.setValueS32("texture_width", mRequestedTextureWidth); message.setValueS32("texture_height", mRequestedTextureHeight); message.setValueReal("background_r", mBackgroundColor.mV[VRED]); message.setValueReal("background_g", mBackgroundColor.mV[VGREEN]); message.setValueReal("background_b", mBackgroundColor.mV[VBLUE]); message.setValueReal("background_a", mBackgroundColor.mV[VALPHA]); mPlugin->sendMessage(message); // DO NOT just use sendMessage() here -- we want this to jump ahead of the queue. LL_DEBUGS("Plugin") << "Sending size_change" << LL_ENDL; } } if(mPlugin && mPlugin->isRunning()) { // Send queued messages while(!mSendQueue.empty()) { LLPluginMessage message = mSendQueue.front(); mSendQueue.pop(); mPlugin->sendMessage(message); } } } int LLPluginClassMedia::getTextureWidth() const { return nextPowerOf2(mTextureWidth); } int LLPluginClassMedia::getTextureHeight() const { return nextPowerOf2(mTextureHeight); } unsigned char* LLPluginClassMedia::getBitsData() { unsigned char *result = NULL; if((mPlugin != NULL) && !mTextureSharedMemoryName.empty()) { result = (unsigned char*)mPlugin->getSharedMemoryAddress(mTextureSharedMemoryName); } return result; } void LLPluginClassMedia::setSize(int width, int height) { if((width > 0) && (height > 0)) { mSetMediaWidth = width; mSetMediaHeight = height; } else { mSetMediaWidth = -1; mSetMediaHeight = -1; } setSizeInternal(); } void LLPluginClassMedia::setSizeInternal(void) { if((mSetMediaWidth > 0) && (mSetMediaHeight > 0)) { mRequestedMediaWidth = mSetMediaWidth; mRequestedMediaHeight = mSetMediaHeight; } else if((mNaturalMediaWidth > 0) && (mNaturalMediaHeight > 0)) { mRequestedMediaWidth = mNaturalMediaWidth; mRequestedMediaHeight = mNaturalMediaHeight; } else { mRequestedMediaWidth = mDefaultMediaWidth; mRequestedMediaHeight = mDefaultMediaHeight; } // Save these for size/interest calculations mFullMediaWidth = mRequestedMediaWidth; mFullMediaHeight = mRequestedMediaHeight; if(mAllowDownsample) { switch(mPriority) { case PRIORITY_SLIDESHOW: case PRIORITY_LOW: // Reduce maximum texture dimension to (or below) mLowPrioritySizeLimit while((mRequestedMediaWidth > mLowPrioritySizeLimit) || (mRequestedMediaHeight > mLowPrioritySizeLimit)) { mRequestedMediaWidth /= 2; mRequestedMediaHeight /= 2; } break; default: // Don't adjust texture size break; } } if(mAutoScaleMedia) { mRequestedMediaWidth = nextPowerOf2(mRequestedMediaWidth); mRequestedMediaHeight = nextPowerOf2(mRequestedMediaHeight); } #if LL_DARWIN if (!gHiDPISupport) #endif { if (mRequestedMediaWidth > 2048) mRequestedMediaWidth = 2048; if (mRequestedMediaHeight > 2048) mRequestedMediaHeight = 2048; } } void LLPluginClassMedia::setAutoScale(bool auto_scale) { if(auto_scale != mAutoScaleMedia) { mAutoScaleMedia = auto_scale; setSizeInternal(); } } bool LLPluginClassMedia::textureValid(void) { if( !mTextureParamsReceived || mTextureWidth <= 0 || mTextureHeight <= 0 || mMediaWidth <= 0 || mMediaHeight <= 0 || mRequestedMediaWidth != mMediaWidth || mRequestedMediaHeight != mMediaHeight || getBitsData() == NULL ) return false; return true; } bool LLPluginClassMedia::getDirty(LLRect *dirty_rect) { bool result = !mDirtyRect.isEmpty(); if(dirty_rect != NULL) { *dirty_rect = mDirtyRect; } return result; } void LLPluginClassMedia::resetDirty(void) { mDirtyRect = LLRect::null; } std::string LLPluginClassMedia::translateModifiers(MASK modifiers) { std::string result; if(modifiers & MASK_CONTROL) { result += "control|"; } if(modifiers & MASK_ALT) { result += "alt|"; } if(modifiers & MASK_SHIFT) { result += "shift|"; } // TODO: should I deal with platform differences here or in callers? // TODO: how do we deal with the Mac "command" key? /* if(modifiers & MASK_SOMETHING) { result += "meta|"; } */ return result; } void LLPluginClassMedia::jsEnableObject( bool enable ) { if( ! mPlugin || !mPlugin->isRunning() || mPlugin->isBlocked() ) { return; } LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "js_enable_object"); message.setValueBoolean( "enable", enable ); sendMessage( message ); } void LLPluginClassMedia::jsAgentLocationEvent( double x, double y, double z ) { if( ! mPlugin || !mPlugin->isRunning() || mPlugin->isBlocked() ) { return; } LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "js_agent_location"); message.setValueReal( "x", x ); message.setValueReal( "y", y ); message.setValueReal( "z", z ); sendMessage( message ); } void LLPluginClassMedia::jsAgentGlobalLocationEvent( double x, double y, double z ) { if( ! mPlugin || !mPlugin->isRunning() || mPlugin->isBlocked() ) { return; } LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "js_agent_global_location"); message.setValueReal( "x", x ); message.setValueReal( "y", y ); message.setValueReal( "z", z ); sendMessage( message ); } void LLPluginClassMedia::jsAgentOrientationEvent( double angle ) { if( ! mPlugin || !mPlugin->isRunning() || mPlugin->isBlocked() ) { return; } LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "js_agent_orientation"); message.setValueReal( "angle", angle ); sendMessage( message ); } void LLPluginClassMedia::jsAgentLanguageEvent( const std::string& language ) { if( ! mPlugin || !mPlugin->isRunning() || mPlugin->isBlocked() ) { return; } LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "js_agent_language"); message.setValue( "language", language ); sendMessage( message ); } void LLPluginClassMedia::jsAgentRegionEvent( const std::string& region ) { if( ! mPlugin || !mPlugin->isRunning() || mPlugin->isBlocked() ) { return; } LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "js_agent_region"); message.setValue( "region", region ); sendMessage( message ); } void LLPluginClassMedia::jsAgentMaturityEvent( const std::string& maturity ) { if( ! mPlugin || !mPlugin->isRunning() || mPlugin->isBlocked() ) { return; } LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "js_agent_maturity"); message.setValue( "maturity", maturity ); sendMessage( message ); } void LLPluginClassMedia::mouseEvent(EMouseEventType type, int button, int x, int y, MASK modifiers) { if(type == MOUSE_EVENT_MOVE) { if(!mPlugin || !mPlugin->isRunning() || mPlugin->isBlocked()) { // Don't queue up mouse move events that can't be delivered. return; } if((x == mLastMouseX) && (y == mLastMouseY)) { // Don't spam unnecessary mouse move events. return; } mLastMouseX = x; mLastMouseY = y; } LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "mouse_event"); std::string temp; switch(type) { case MOUSE_EVENT_DOWN: temp = "down"; break; case MOUSE_EVENT_UP: temp = "up"; break; case MOUSE_EVENT_MOVE: temp = "move"; break; case MOUSE_EVENT_DOUBLE_CLICK: temp = "double_click"; break; } message.setValue("event", temp); message.setValueS32("button", button); message.setValueS32("x", x); // Incoming coordinates are OpenGL-style ((0,0) = lower left), so flip them here if the plugin has requested it. if(!mRequestedTextureCoordsOpenGL) { // TODO: Should I use mMediaHeight or mRequestedMediaHeight here? y = mMediaHeight - y; } message.setValueS32("y", y); message.setValue("modifiers", translateModifiers(modifiers)); sendMessage(message); } bool LLPluginClassMedia::keyEvent(EKeyEventType type, int key_code, MASK modifiers, LLSD native_key_data) { 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 LL_DARWIN if(modifiers & MASK_ALT) { // Option-key modified characters should be handled by the unicode input path instead of this one. result = false; } #endif 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)); message.setValueLLSD("native_key_data", native_key_data); sendMessage(message); } return result; } void LLPluginClassMedia::scrollEvent(int x, int y, int clicks_x, int clicks_y, MASK modifiers) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "scroll_event"); message.setValueS32("x", x); message.setValueS32("y", y); message.setValueS32("clicks_x", clicks_x); message.setValueS32("clicks_y", clicks_y); message.setValue("modifiers", translateModifiers(modifiers)); sendMessage(message); } bool LLPluginClassMedia::textInput(const std::string &text, MASK modifiers, LLSD native_key_data) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "text_event"); message.setValue("text", text); message.setValue("modifiers", translateModifiers(modifiers)); message.setValueLLSD("native_key_data", native_key_data); sendMessage(message); return true; } // This function injects a previously stored OpenID cookie into // each new media instance - see SL-15867 for details. It appears // that the way we use the cache, shared between multiple CEF // instances means that sometimes the OpenID cookie cannot be read // even though it appears to be there. The long term solution to // this is to create a separate cache directory for each instance // but that has its own set of problems. This short term approach // "forces" each new media instance to have a copy of the cookie // so that a page that needs it - e.g. Profiles - finds it and // can log in successfully. void LLPluginClassMedia::injectOpenIDCookie() { // can be called before we know who the user is at login // and there is no OpenID cookie at that point so no // need to try to set it (these values will all be empty) if (sOIDcookieName.length() && sOIDcookieValue.length()) { setCookie(sOIDcookieUrl, sOIDcookieName, sOIDcookieValue, sOIDcookieHost, sOIDcookiePath, sOIDcookieHttpOnly, sOIDcookieSecure); } } // We store each component of the OpenI cookie individuality here // because previously, there was some significant parsing to // break up the raw string into these components and we do not // want to have to do that again here. Stored as statics because // we want to share their value between all instances of this // class - the ones that receive it at login and any others // that open afterwards (e.g. the Profiles floater) std::string LLPluginClassMedia::sOIDcookieUrl = std::string(); std::string LLPluginClassMedia::sOIDcookieName = std::string(); std::string LLPluginClassMedia::sOIDcookieValue = std::string(); std::string LLPluginClassMedia::sOIDcookieHost = std::string(); std::string LLPluginClassMedia::sOIDcookiePath = std::string(); bool LLPluginClassMedia::sOIDcookieHttpOnly = false; bool LLPluginClassMedia::sOIDcookieSecure = false; // Once we receive the OpenID cookie, it is parsed/processed // in llViewerMedia::parseRawCookie() and then the component // values are stored here so that next time a new media // instance is created, we can use injectOpenIDCookie() // to "insist" that the cookie store remember its value. // One might ask why we need to go via LLViewerMedia (which // makes this call) - this is because the raw cookie arrives // here in this file but undergoes non-trivial processing // in LLViewerMedia. void LLPluginClassMedia::storeOpenIDCookie(const std::string url, const std::string name, const std::string value, const std::string host, const std::string path, bool httponly, bool secure) { sOIDcookieUrl = url; sOIDcookieName = name; sOIDcookieValue = value; sOIDcookieHost = host; sOIDcookiePath = path; sOIDcookieHttpOnly = httponly; sOIDcookieSecure = secure; } void LLPluginClassMedia::setCookie(std::string uri, std::string name, std::string value, std::string domain, std::string path, bool httponly, bool secure) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "set_cookie"); message.setValue("uri", uri); message.setValue("name", name); message.setValue("value", value); message.setValue("domain", domain); message.setValue("path", path); message.setValueBoolean("httponly", httponly); message.setValueBoolean("secure", secure); sendMessage(message); } void LLPluginClassMedia::loadURI(const std::string &uri) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "load_uri"); message.setValue("uri", uri); sendMessage(message); } void LLPluginClassMedia::executeJavaScript(const std::string &code) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "execute_javascript"); message.setValue("code", code); sendMessage(message); } const char* LLPluginClassMedia::priorityToString(EPriority priority) { const char* result = "UNKNOWN"; switch(priority) { case PRIORITY_UNLOADED: result = "unloaded"; break; case PRIORITY_STOPPED: result = "stopped"; break; case PRIORITY_HIDDEN: result = "hidden"; break; case PRIORITY_SLIDESHOW: result = "slideshow"; break; case PRIORITY_LOW: result = "low"; break; case PRIORITY_NORMAL: result = "normal"; break; case PRIORITY_HIGH: result = "high"; break; } return result; } void LLPluginClassMedia::setPriority(EPriority priority) { if(mPriority != priority) { mPriority = priority; LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "set_priority"); std::string priority_string = priorityToString(priority); switch(priority) { case PRIORITY_UNLOADED: mSleepTime = 1.0f; break; case PRIORITY_STOPPED: mSleepTime = 1.0f; break; case PRIORITY_HIDDEN: mSleepTime = 1.0f; break; case PRIORITY_SLIDESHOW: mSleepTime = 1.0f; break; case PRIORITY_LOW: mSleepTime = 1.0f / 25.0f; break; case PRIORITY_NORMAL: mSleepTime = 1.0f / 50.0f; break; case PRIORITY_HIGH: mSleepTime = 1.0f / 100.0f; break; } message.setValue("priority", priority_string); sendMessage(message); if(mPlugin) { mPlugin->setSleepTime(mSleepTime); } LL_DEBUGS("PluginPriority") << this << ": setting priority to " << priority_string << LL_ENDL; // This may affect the calculated size, so recalculate it here. setSizeInternal(); } } void LLPluginClassMedia::setLowPrioritySizeLimit(int size) { int power = nextPowerOf2(size); if(mLowPrioritySizeLimit != power) { mLowPrioritySizeLimit = power; // This may affect the calculated size, so recalculate it here. setSizeInternal(); } } F64 LLPluginClassMedia::getCPUUsage() { F64 result = 0.0f; if(mPlugin) { result = mPlugin->getCPUUsage(); } return result; } void LLPluginClassMedia::sendPickFileResponse(const std::vector files) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "pick_file_response"); if(mPlugin && mPlugin->isBlocked()) { // If the plugin sent a blocking pick-file request, the response should unblock it. message.setValueBoolean("blocking_response", true); } LLSD file_list = LLSD::emptyArray(); for (std::vector::const_iterator in_iter = files.begin(); in_iter != files.end(); ++in_iter) { file_list.append(LLSD::String(*in_iter)); } message.setValueLLSD("file_list", file_list); sendMessage(message); } void LLPluginClassMedia::sendAuthResponse(bool ok, const std::string &username, const std::string &password) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "auth_response"); message.setValueBoolean("ok", ok); message.setValue("username", username); message.setValue("password", password); if(mPlugin && mPlugin->isBlocked()) { // If the plugin sent a blocking pick-file request, the response should unblock it. message.setValueBoolean("blocking_response", true); } sendMessage(message); } 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); } void LLPluginClassMedia::setUserDataPath(const std::string &user_data_path_cache, const std::string &username, const std::string &user_data_path_cef_log) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "set_user_data_path"); message.setValue("cache_path", user_data_path_cache); message.setValue("username", username); // cef shares cache between users but creates user-based contexts message.setValue("cef_log_file", user_data_path_cef_log); #if LL_DARWIN || LL_LINUX || __FreeBSD__ bool cef_verbose_log = gSavedSettings.getBOOL("CefVerboseLog"); message.setValueBoolean("cef_verbose_log", cef_verbose_log); #endif sendMessage(message); } void LLPluginClassMedia::setLanguageCode(const std::string &language_code) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "set_language_code"); message.setValue("language", language_code); sendMessage(message); } void LLPluginClassMedia::setPluginsEnabled(const bool enabled) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "plugins_enabled"); message.setValueBoolean("enable", enabled); sendMessage(message); } void LLPluginClassMedia::setJavascriptEnabled(const bool enabled) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "javascript_enabled"); message.setValueBoolean("enable", enabled); sendMessage(message); } void LLPluginClassMedia::setWebSecurityDisabled(const bool disabled) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "web_security_disabled"); message.setValueBoolean("disabled", disabled); sendMessage(message); } void LLPluginClassMedia::setFileAccessFromFileUrlsEnabled(const bool enabled) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "file_access_from_file_urls"); message.setValueBoolean("enabled", enabled); sendMessage(message); } void LLPluginClassMedia::enableMediaPluginDebugging( bool enable ) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "enable_media_plugin_debugging"); message.setValueBoolean( "enable", enable ); sendMessage( message ); } #if LL_LINUX void LLPluginClassMedia::enablePipeWireVolumeCatcher( bool enable ) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "enable_pipewire_volume_catcher"); message.setValueBoolean( "enable", enable ); sendMessage( message ); } #endif void LLPluginClassMedia::setTarget(const std::string &target) { mTarget = target; } /* virtual */ void LLPluginClassMedia::receivePluginMessage(const LLPluginMessage &message) { std::string message_class = message.getClass(); if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA) { std::string message_name = message.getName(); if(message_name == "texture_params") { mRequestedTextureDepth = message.getValueS32("depth"); mRequestedTextureInternalFormat = message.getValueU32("internalformat"); mRequestedTextureFormat = message.getValueU32("format"); mRequestedTextureType = message.getValueU32("type"); mRequestedTextureSwapBytes = message.getValueBoolean("swap_bytes"); mRequestedTextureCoordsOpenGL = message.getValueBoolean("coords_opengl"); // These two are optional, and will default to 0 if they're not specified. mDefaultMediaWidth = message.getValueS32("default_width"); mDefaultMediaHeight = message.getValueS32("default_height"); mAllowDownsample = message.getValueBoolean("allow_downsample"); mPadding = message.getValueS32("padding"); setSizeInternal(); mTextureParamsReceived = true; } else if(message_name == "updated") { if(message.hasValue("left")) { LLRect newDirtyRect; newDirtyRect.mLeft = message.getValueS32("left"); newDirtyRect.mTop = message.getValueS32("top"); newDirtyRect.mRight = message.getValueS32("right"); newDirtyRect.mBottom = message.getValueS32("bottom"); // The plugin is likely to have top and bottom switched, due to vertical flip and OpenGL coordinate confusion. // If they're backwards, swap them. if(newDirtyRect.mTop < newDirtyRect.mBottom) { S32 temp = newDirtyRect.mTop; newDirtyRect.mTop = newDirtyRect.mBottom; newDirtyRect.mBottom = temp; } if(mDirtyRect.isEmpty()) { mDirtyRect = newDirtyRect; } else { mDirtyRect.unionWith(newDirtyRect); } LL_DEBUGS("Plugin") << "adjusted incoming rect is: (" << newDirtyRect.mLeft << ", " << newDirtyRect.mTop << ", " << newDirtyRect.mRight << ", " << newDirtyRect.mBottom << "), new dirty rect is: (" << mDirtyRect.mLeft << ", " << mDirtyRect.mTop << ", " << mDirtyRect.mRight << ", " << mDirtyRect.mBottom << ")" << LL_ENDL; mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CONTENT_UPDATED); } bool time_duration_updated = false; int previous_percent = mProgressPercent; if(message.hasValue("current_time")) { mCurrentTime = message.getValueReal("current_time"); time_duration_updated = true; } if(message.hasValue("duration")) { mDuration = message.getValueReal("duration"); time_duration_updated = true; } if(message.hasValue("current_rate")) { mCurrentRate = message.getValueReal("current_rate"); } if(message.hasValue("loaded_duration")) { mLoadedDuration = message.getValueReal("loaded_duration"); time_duration_updated = true; } else { // If the message doesn't contain a loaded_duration param, assume it's equal to duration mLoadedDuration = mDuration; } // Calculate a percentage based on the loaded duration and total duration. if(mDuration != 0.0f) // Don't divide by zero. { mProgressPercent = (int)((mLoadedDuration * 100.0f)/mDuration); } if(time_duration_updated) { mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_TIME_DURATION_UPDATED); } if(previous_percent != mProgressPercent) { mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PROGRESS_UPDATED); } } else if(message_name == "media_status") { std::string status = message.getValue("status"); LL_DEBUGS("Plugin") << "Status changed to: " << status << LL_ENDL; if(status == "loading") { mStatus = LLPluginClassMediaOwner::MEDIA_LOADING; } else if(status == "loaded") { mStatus = LLPluginClassMediaOwner::MEDIA_LOADED; } else if(status == "error") { mStatus = LLPluginClassMediaOwner::MEDIA_ERROR; } else if(status == "playing") { mStatus = LLPluginClassMediaOwner::MEDIA_PLAYING; } else if(status == "paused") { mStatus = LLPluginClassMediaOwner::MEDIA_PAUSED; } else if(status == "done") { mStatus = LLPluginClassMediaOwner::MEDIA_DONE; } else { // empty string or any unknown string mStatus = LLPluginClassMediaOwner::MEDIA_NONE; } } else if(message_name == "size_change_request") { S32 width = message.getValueS32("width"); S32 height = message.getValueS32("height"); std::string name = message.getValue("name"); // TODO: check that name matches? mNaturalMediaWidth = width; mNaturalMediaHeight = height; setSizeInternal(); } else if(message_name == "size_change_response") { std::string name = message.getValue("name"); // TODO: check that name matches? mTextureWidth = message.getValueS32("texture_width"); mTextureHeight = message.getValueS32("texture_height"); mMediaWidth = message.getValueS32("width"); mMediaHeight = message.getValueS32("height"); // This invalidates any existing dirty rect. resetDirty(); // TODO: should we verify that the plugin sent back the right values? // Two size changes in a row may cause them to not match, due to queueing, etc. mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_SIZE_CHANGED); } else if(message_name == "cursor_changed") { mCursorName = message.getValue("name"); mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CURSOR_CHANGED); } else if(message_name == "edit_state") { if(message.hasValue("cut")) { mCanCut = message.getValueBoolean("cut"); } if(message.hasValue("copy")) { mCanCopy = message.getValueBoolean("copy"); } if(message.hasValue("paste")) { mCanPaste = message.getValueBoolean("paste"); } } else if(message_name == "name_text") { mHistoryBackAvailable = message.getValueBoolean("history_back_available"); mHistoryForwardAvailable = message.getValueBoolean("history_forward_available"); mMediaName = message.getValue("name"); mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_NAME_CHANGED); } else if(message_name == "pick_file") { mIsMultipleFilePick = message.getValueBoolean("multiple_files"); mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PICK_FILE_REQUEST); } else if(message_name == "auth_request") { mAuthURL = message.getValue("url"); mAuthRealm = message.getValue("realm"); mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_AUTH_REQUEST); } else if (message_name == "file_download") { mFileDownloadFilename = message.getValue("filename"); mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_FILE_DOWNLOAD); } else if(message_name == "debug_message") { mDebugMessageText = message.getValue("message_text"); mDebugMessageLevel = message.getValue("message_level"); mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_DEBUG_MESSAGE); } else if (message_name == "tooltip_text") { mHoverText = message.getValue("tooltip"); } 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"); // need a link to have a UUID that identifies it to a system further // upstream - plugin could make it but we have access to LLUUID here // so why don't we use it mClickUUID = LLUUID::generateNewID().asString(); mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_HREF); } else if(message_name == "click_nofollow") { mClickURL = message.getValue("uri"); mClickNavType = message.getValue("nav_type"); mClickTarget.clear(); mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_NOFOLLOW); } else if(message_name == "navigate_error_page") { mStatusCode = message.getValueS32("status_code"); mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_NAVIGATE_ERROR_PAGE); } else if(message_name == "close_request") { mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLOSE_REQUEST); } else if(message_name == "geometry_change") { mClickUUID = message.getValue("uuid"); mGeometryX = message.getValueS32("x"); mGeometryY = message.getValueS32("y"); mGeometryWidth = message.getValueS32("width"); mGeometryHeight = message.getValueS32("height"); mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_GEOMETRY_CHANGE); } else if(message_name == "link_hovered") { // text is not currently used -- the tooltip hover text is taken from the "title". mHoverLink = message.getValue("link"); mHoverText = message.getValue("title"); // message.getValue("text"); mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_LINK_HOVERED); } else { LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL; } } else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME) { std::string message_name = message.getName(); // This class hasn't defined any incoming messages yet. // if(message_name == "message_name") // { // } // else { LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL; } } } /* virtual */ void LLPluginClassMedia::pluginLaunchFailed() { mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PLUGIN_FAILED_LAUNCH); } /* virtual */ void LLPluginClassMedia::pluginDied() { mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PLUGIN_FAILED); } void LLPluginClassMedia::mediaEvent(LLPluginClassMediaOwner::EMediaEvent event) { if(mOwner) { mOwner->handleMediaEvent(this, event); } } void LLPluginClassMedia::sendMessage(const LLPluginMessage &message) { if(mPlugin && mPlugin->isRunning()) { mPlugin->sendMessage(message); } else { // The plugin isn't set up yet -- queue this message to be sent after initialization. mSendQueue.push(message); } } //////////////////////////////////////////////////////////// // MARK: media_browser class functions bool LLPluginClassMedia::pluginSupportsMediaBrowser(void) { std::string version = mPlugin->getMessageClassVersion(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER); return !version.empty(); } void LLPluginClassMedia::focus(bool focused) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "focus"); message.setValueBoolean("focused", focused); sendMessage(message); } void LLPluginClassMedia::set_page_zoom_factor( F64 factor ) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "set_page_zoom_factor"); message.setValueReal("factor", factor); 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::cookies_enabled(bool enable) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "cookies_enabled"); message.setValueBoolean("enable", enable); 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::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::showWebInspector( bool show ) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "show_web_inspector"); message.setValueBoolean("show", true); // only open for now - closed manually by user sendMessage(message); } void LLPluginClassMedia::proxyWindowOpened(const std::string &target, const std::string &uuid) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "proxy_window_opened"); message.setValue("target", target); message.setValue("uuid", uuid); sendMessage(message); } void LLPluginClassMedia::proxyWindowClosed(const std::string &uuid) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "proxy_window_closed"); message.setValue("uuid", uuid); sendMessage(message); } void LLPluginClassMedia::ignore_ssl_cert_errors(bool ignore) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "ignore_ssl_cert_errors"); message.setValueBoolean("ignore", ignore); sendMessage(message); } void LLPluginClassMedia::addCertificateFilePath(const std::string& path) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "add_certificate_file_path"); message.setValue("path", path); sendMessage(message); } void LLPluginClassMedia::setOverrideClickTarget(const std::string &target) { mClickEnforceTarget = true; mOverrideClickTarget = target; } 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); mCurrentTime = time; // assume that it worked and we will receive an update later sendMessage(message); } void LLPluginClassMedia::setLoop(bool loop) { LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "set_loop"); message.setValueBoolean("loop", loop); sendMessage(message); } void LLPluginClassMedia::setVolume(float volume) { if(volume != mRequestedVolume) { mRequestedVolume = volume; LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "set_volume"); message.setValueReal("volume", volume); sendMessage(message); } } float LLPluginClassMedia::getVolume() { return mRequestedVolume; } void LLPluginClassMedia::initializeUrlHistory(const LLSD& url_history) { // Send URL history to plugin LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "init_history"); message.setValueLLSD("history", url_history); sendMessage(message); LL_DEBUGS("Plugin") << "Sending history" << LL_ENDL; }