summaryrefslogtreecommitdiff
path: root/indra/media_plugins/webkit
diff options
context:
space:
mode:
Diffstat (limited to 'indra/media_plugins/webkit')
-rw-r--r--indra/media_plugins/webkit/CMakeLists.txt128
-rw-r--r--indra/media_plugins/webkit/dummy_volume_catcher.cpp58
-rw-r--r--indra/media_plugins/webkit/linux_volume_catcher.cpp468
-rw-r--r--indra/media_plugins/webkit/linux_volume_catcher_pa_syms.inc21
-rw-r--r--indra/media_plugins/webkit/linux_volume_catcher_paglib_syms.inc6
-rw-r--r--indra/media_plugins/webkit/mac_volume_catcher.cpp268
-rw-r--r--indra/media_plugins/webkit/media_plugin_webkit.cpp1238
-rw-r--r--indra/media_plugins/webkit/volume_catcher.h54
-rw-r--r--indra/media_plugins/webkit/windows_volume_catcher.cpp117
9 files changed, 2358 insertions, 0 deletions
diff --git a/indra/media_plugins/webkit/CMakeLists.txt b/indra/media_plugins/webkit/CMakeLists.txt
new file mode 100644
index 0000000000..05f1236606
--- /dev/null
+++ b/indra/media_plugins/webkit/CMakeLists.txt
@@ -0,0 +1,128 @@
+# -*- cmake -*-
+
+project(media_plugin_webkit)
+
+include(00-Common)
+include(LLCommon)
+include(LLImage)
+include(LLPlugin)
+include(LLMath)
+include(LLRender)
+include(LLWindow)
+include(UI)
+include(Linking)
+include(PluginAPI)
+include(MediaPluginBase)
+include(FindOpenGL)
+include(PulseAudio)
+
+include(WebKitLibPlugin)
+
+include_directories(
+ ${PULSEAUDIO_INCLUDE_DIRS}
+ ${LLPLUGIN_INCLUDE_DIRS}
+ ${MEDIA_PLUGIN_BASE_INCLUDE_DIRS}
+ ${LLCOMMON_INCLUDE_DIRS}
+ ${LLMATH_INCLUDE_DIRS}
+ ${LLIMAGE_INCLUDE_DIRS}
+ ${LLRENDER_INCLUDE_DIRS}
+ ${LLWINDOW_INCLUDE_DIRS}
+)
+
+
+### media_plugin_webkit
+
+if(NOT WORD_SIZE EQUAL 32)
+ if(WINDOWS)
+ add_definitions(/FIXED:NO)
+ else(WINDOWS) # not windows therefore gcc LINUX and DARWIN
+ add_definitions(-fPIC)
+ endif(WINDOWS)
+endif(NOT WORD_SIZE EQUAL 32)
+
+set(media_plugin_webkit_SOURCE_FILES
+ media_plugin_webkit.cpp
+ )
+
+set(media_plugin_webkit_HEADER_FILES
+ volume_catcher.h
+ )
+
+set(media_plugin_webkit_LINK_LIBRARIES
+ ${LLPLUGIN_LIBRARIES}
+ ${MEDIA_PLUGIN_BASE_LIBRARIES}
+ ${LLCOMMON_LIBRARIES}
+ ${WEBKIT_PLUGIN_LIBRARIES}
+ ${PLUGIN_API_WINDOWS_LIBRARIES}
+ ${PULSEAUDIO_LIBRARIES}
+)
+
+# Select which VolumeCatcher implementation to use
+if (LINUX)
+ if (PULSEAUDIO_FOUND)
+ list(APPEND media_plugin_webkit_SOURCE_FILES linux_volume_catcher.cpp)
+ else (PULSEAUDIO_FOUND)
+ list(APPEND media_plugin_webkit_SOURCE_FILES dummy_volume_catcher.cpp)
+ endif (PULSEAUDIO_FOUND)
+ list(APPEND media_plugin_webkit_LINK_LIBRARIES
+ ${UI_LIBRARIES} # for glib/GTK
+ )
+elseif (DARWIN)
+ list(APPEND media_plugin_webkit_SOURCE_FILES mac_volume_catcher.cpp)
+ find_library(CORESERVICES_LIBRARY CoreServices)
+ find_library(AUDIOUNIT_LIBRARY AudioUnit)
+ list(APPEND media_plugin_webkit_LINK_LIBRARIES
+ ${CORESERVICES_LIBRARY} # for Component Manager calls
+ ${AUDIOUNIT_LIBRARY} # for AudioUnit calls
+ )
+elseif (WINDOWS)
+ list(APPEND media_plugin_webkit_SOURCE_FILES windows_volume_catcher.cpp)
+endif (LINUX)
+
+set_source_files_properties(${media_plugin_webkit_HEADER_FILES}
+ PROPERTIES HEADER_FILE_ONLY TRUE)
+
+list(APPEND media_plugin_webkit_SOURCE_FILES ${media_plugin_webkit_HEADER_FILES})
+
+add_library(media_plugin_webkit
+ SHARED
+ ${media_plugin_webkit_SOURCE_FILES}
+)
+
+target_link_libraries(media_plugin_webkit ${media_plugin_webkit_LINK_LIBRARIES})
+
+add_dependencies(media_plugin_webkit
+ ${LLPLUGIN_LIBRARIES}
+ ${MEDIA_PLUGIN_BASE_LIBRARIES}
+ ${LLCOMMON_LIBRARIES}
+)
+
+if (WINDOWS)
+ set_target_properties(
+ media_plugin_webkit
+ PROPERTIES
+ LINK_FLAGS "/MANIFEST:NO"
+ )
+endif (WINDOWS)
+
+if (DARWIN)
+ # Don't prepend 'lib' to the executable name, and don't embed a full path in the library's install name
+ set_target_properties(
+ media_plugin_webkit
+ PROPERTIES
+ PREFIX ""
+ BUILD_WITH_INSTALL_RPATH 1
+ INSTALL_NAME_DIR "@executable_path"
+ LINK_FLAGS "-exported_symbols_list ${CMAKE_CURRENT_SOURCE_DIR}/../base/media_plugin_base.exp"
+ )
+
+ # copy the webkit dylib to the build directory
+ add_custom_command(
+ TARGET media_plugin_webkit POST_BUILD
+# OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/libllqtwebkit.dylib
+ COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_SOURCE_DIR}/../libraries/universal-darwin/lib_release/libllqtwebkit.dylib ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/
+ DEPENDS media_plugin_webkit ${CMAKE_SOURCE_DIR}/../libraries/universal-darwin/lib_release/libllqtwebkit.dylib
+ )
+
+endif (DARWIN)
+
diff --git a/indra/media_plugins/webkit/dummy_volume_catcher.cpp b/indra/media_plugins/webkit/dummy_volume_catcher.cpp
new file mode 100644
index 0000000000..d54b31b2ae
--- /dev/null
+++ b/indra/media_plugins/webkit/dummy_volume_catcher.cpp
@@ -0,0 +1,58 @@
+/**
+ * @file dummy_volume_catcher.cpp
+ * @brief A null implementation of the "VolumeCatcher" class for platforms where it's not implemented yet.
+ *
+ * @cond
+ * $LicenseInfo:firstyear=2010&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ * @endcond
+ */
+
+#include "volume_catcher.h"
+
+
+class VolumeCatcherImpl
+{
+};
+
+/////////////////////////////////////////////////////
+
+VolumeCatcher::VolumeCatcher()
+{
+ pimpl = NULL;
+}
+
+VolumeCatcher::~VolumeCatcher()
+{
+}
+
+void VolumeCatcher::setVolume(F32 volume)
+{
+}
+
+void VolumeCatcher::setPan(F32 pan)
+{
+}
+
+void VolumeCatcher::pump()
+{
+}
+
diff --git a/indra/media_plugins/webkit/linux_volume_catcher.cpp b/indra/media_plugins/webkit/linux_volume_catcher.cpp
new file mode 100644
index 0000000000..91be3a89e9
--- /dev/null
+++ b/indra/media_plugins/webkit/linux_volume_catcher.cpp
@@ -0,0 +1,468 @@
+/**
+ * @file linux_volume_catcher.cpp
+ * @brief A Linux-specific, PulseAudio-specific hack to detect and volume-adjust new audio sources
+ *
+ * @cond
+ * $LicenseInfo:firstyear=2010&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ * @endcond
+ */
+
+/*
+ The high-level design is as follows:
+ 1) Connect to the PulseAudio daemon
+ 2) Watch for the creation of new audio players connecting to the daemon (this includes ALSA clients running on the PulseAudio emulation layer, such as Flash plugins)
+ 3) Examine any new audio player's PID to see if it belongs to our own process
+ 4) If so, tell PA to adjust the volume of that audio player ('sink input' in PA parlance)
+ 5) Keep a list of all living audio players that we care about, adjust the volumes of all of them when we get a new setVolume() call
+ */
+
+#include "linden_common.h"
+
+#include "volume_catcher.h"
+
+
+extern "C" {
+#include <glib.h>
+#include <glib-object.h>
+
+#include <pulse/introspect.h>
+#include <pulse/context.h>
+#include <pulse/subscribe.h>
+#include <pulse/glib-mainloop.h> // There's no special reason why we want the *glib* PA mainloop, but the generic polling implementation seems broken.
+
+#include "apr_pools.h"
+#include "apr_dso.h"
+}
+
+////////////////////////////////////////////////////
+
+#define DEBUGMSG(...) do {} while(0)
+#define INFOMSG(...) do {} while(0)
+#define WARNMSG(...) do {} while(0)
+
+#define LL_PA_SYM(REQUIRED, PASYM, RTN, ...) RTN (*ll##PASYM)(__VA_ARGS__) = NULL
+#include "linux_volume_catcher_pa_syms.inc"
+#include "linux_volume_catcher_paglib_syms.inc"
+#undef LL_PA_SYM
+
+static bool sSymsGrabbed = false;
+static apr_pool_t *sSymPADSOMemoryPool = NULL;
+static apr_dso_handle_t *sSymPADSOHandleG = NULL;
+
+bool grab_pa_syms(std::string pulse_dso_name)
+{
+ if (sSymsGrabbed)
+ {
+ // already have grabbed good syms
+ return true;
+ }
+
+ bool sym_error = false;
+ bool rtn = false;
+ apr_status_t rv;
+ apr_dso_handle_t *sSymPADSOHandle = NULL;
+
+#define LL_PA_SYM(REQUIRED, PASYM, RTN, ...) do{rv = apr_dso_sym((apr_dso_handle_sym_t*)&ll##PASYM, sSymPADSOHandle, #PASYM); if (rv != APR_SUCCESS) {INFOMSG("Failed to grab symbol: %s", #PASYM); if (REQUIRED) sym_error = true;} else DEBUGMSG("grabbed symbol: %s from %p", #PASYM, (void*)ll##PASYM);}while(0)
+
+ //attempt to load the shared library
+ apr_pool_create(&sSymPADSOMemoryPool, NULL);
+
+ if ( APR_SUCCESS == (rv = apr_dso_load(&sSymPADSOHandle,
+ pulse_dso_name.c_str(),
+ sSymPADSOMemoryPool) ))
+ {
+ INFOMSG("Found DSO: %s", pulse_dso_name.c_str());
+
+#include "linux_volume_catcher_pa_syms.inc"
+#include "linux_volume_catcher_paglib_syms.inc"
+
+ if ( sSymPADSOHandle )
+ {
+ sSymPADSOHandleG = sSymPADSOHandle;
+ sSymPADSOHandle = NULL;
+ }
+
+ rtn = !sym_error;
+ }
+ else
+ {
+ INFOMSG("Couldn't load DSO: %s", pulse_dso_name.c_str());
+ rtn = false; // failure
+ }
+
+ if (sym_error)
+ {
+ WARNMSG("Failed to find necessary symbols in PulseAudio libraries.");
+ }
+#undef LL_PA_SYM
+
+ sSymsGrabbed = rtn;
+ return rtn;
+}
+
+
+void ungrab_pa_syms()
+{
+ // should be safe to call regardless of whether we've
+ // actually grabbed syms.
+
+ if ( sSymPADSOHandleG )
+ {
+ apr_dso_unload(sSymPADSOHandleG);
+ sSymPADSOHandleG = NULL;
+ }
+
+ if ( sSymPADSOMemoryPool )
+ {
+ apr_pool_destroy(sSymPADSOMemoryPool);
+ sSymPADSOMemoryPool = NULL;
+ }
+
+ // NULL-out all of the symbols we'd grabbed
+#define LL_PA_SYM(REQUIRED, PASYM, RTN, ...) do{ll##PASYM = NULL;}while(0)
+#include "linux_volume_catcher_pa_syms.inc"
+#include "linux_volume_catcher_paglib_syms.inc"
+#undef LL_PA_SYM
+
+ sSymsGrabbed = false;
+}
+////////////////////////////////////////////////////
+
+// PulseAudio requires a chain of callbacks with C linkage
+extern "C" {
+ void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *i, int eol, void *userdata);
+ void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata);
+ void callback_context_state(pa_context *context, void *userdata);
+}
+
+
+class VolumeCatcherImpl
+{
+public:
+ VolumeCatcherImpl();
+ ~VolumeCatcherImpl();
+
+ void setVolume(F32 volume);
+ void pump(void);
+
+ // for internal use - can't be private because used from our C callbacks
+
+ bool loadsyms(std::string pulse_dso_name);
+ void init();
+ void cleanup();
+
+ void update_all_volumes(F32 volume);
+ void update_index_volume(U32 index, F32 volume);
+ void connected_okay();
+
+ std::set<U32> mSinkInputIndices;
+ std::map<U32,U32> mSinkInputNumChannels;
+ F32 mDesiredVolume;
+ pa_glib_mainloop *mMainloop;
+ pa_context *mPAContext;
+ bool mConnected;
+ bool mGotSyms;
+};
+
+VolumeCatcherImpl::VolumeCatcherImpl()
+ : mDesiredVolume(0.0f),
+ mMainloop(NULL),
+ mPAContext(NULL),
+ mConnected(false),
+ mGotSyms(false)
+{
+ init();
+}
+
+VolumeCatcherImpl::~VolumeCatcherImpl()
+{
+ cleanup();
+}
+
+bool VolumeCatcherImpl::loadsyms(std::string pulse_dso_name)
+{
+ return grab_pa_syms(pulse_dso_name);
+}
+
+void VolumeCatcherImpl::init()
+{
+ // try to be as defensive as possible because PA's interface is a
+ // bit fragile and (for our purposes) we'd rather simply not function
+ // than crash
+
+ // we cheat and rely upon libpulse-mainloop-glib.so.0 to pull-in
+ // libpulse.so.0 - this isn't a great assumption, and the two DSOs should
+ // probably be loaded separately. Our Linux DSO framework needs refactoring,
+ // we do this sort of thing a lot with practically identical logic...
+ mGotSyms = loadsyms("libpulse-mainloop-glib.so.0");
+ if (!mGotSyms) return;
+
+ // better make double-sure glib itself is initialized properly.
+ if (!g_thread_supported ()) g_thread_init (NULL);
+ g_type_init();
+
+ mMainloop = llpa_glib_mainloop_new(g_main_context_default());
+ if (mMainloop)
+ {
+ pa_mainloop_api *api = llpa_glib_mainloop_get_api(mMainloop);
+ if (api)
+ {
+ pa_proplist *proplist = llpa_proplist_new();
+ if (proplist)
+ {
+ llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ICON_NAME, "multimedia-player");
+ llpa_proplist_sets(proplist, PA_PROP_APPLICATION_ID, "com.secondlife.viewer.mediaplugvoladjust");
+ llpa_proplist_sets(proplist, PA_PROP_APPLICATION_NAME, "SL Plugin Volume Adjuster");
+ llpa_proplist_sets(proplist, PA_PROP_APPLICATION_VERSION, "1");
+
+ // plain old pa_context_new() is broken!
+ mPAContext = llpa_context_new_with_proplist(api, NULL, proplist);
+ llpa_proplist_free(proplist);
+ }
+ }
+ }
+
+ // Now we've set up a PA context and mainloop, try connecting the
+ // PA context to a PA daemon.
+ if (mPAContext)
+ {
+ llpa_context_set_state_callback(mPAContext, callback_context_state, this);
+ pa_context_flags_t cflags = (pa_context_flags)0; // maybe add PA_CONTEXT_NOAUTOSPAWN?
+ if (llpa_context_connect(mPAContext, NULL, cflags, NULL) >= 0)
+ {
+ // Okay! We haven't definitely connected, but we
+ // haven't definitely failed yet.
+ }
+ else
+ {
+ // Failed to connect to PA manager... we'll leave
+ // things like that. Perhaps we should try again later.
+ }
+ }
+}
+
+void VolumeCatcherImpl::cleanup()
+{
+ mConnected = false;
+
+ if (mGotSyms && mPAContext)
+ {
+ llpa_context_disconnect(mPAContext);
+ llpa_context_unref(mPAContext);
+ }
+ mPAContext = NULL;
+
+ if (mGotSyms && mMainloop)
+ {
+ llpa_glib_mainloop_free(mMainloop);
+ }
+ mMainloop = NULL;
+}
+
+void VolumeCatcherImpl::setVolume(F32 volume)
+{
+ mDesiredVolume = volume;
+
+ if (!mGotSyms) return;
+
+ if (mConnected && mPAContext)
+ {
+ update_all_volumes(mDesiredVolume);
+ }
+
+ pump();
+}
+
+void VolumeCatcherImpl::pump()
+{
+ gboolean may_block = FALSE;
+ g_main_context_iteration(g_main_context_default(), may_block);
+}
+
+void VolumeCatcherImpl::connected_okay()
+{
+ pa_operation *op;
+
+ // fetch global list of existing sinkinputs
+ if ((op = llpa_context_get_sink_input_info_list(mPAContext,
+ callback_discovered_sinkinput,
+ this)))
+ {
+ llpa_operation_unref(op);
+ }
+
+ // subscribe to future global sinkinput changes
+ llpa_context_set_subscribe_callback(mPAContext,
+ callback_subscription_alert,
+ this);
+ if ((op = llpa_context_subscribe(mPAContext, (pa_subscription_mask_t)
+ (PA_SUBSCRIPTION_MASK_SINK_INPUT),
+ NULL, NULL)))
+ {
+ llpa_operation_unref(op);
+ }
+}
+
+void VolumeCatcherImpl::update_all_volumes(F32 volume)
+{
+ for (std::set<U32>::iterator it = mSinkInputIndices.begin();
+ it != mSinkInputIndices.end(); ++it)
+ {
+ update_index_volume(*it, volume);
+ }
+}
+
+void VolumeCatcherImpl::update_index_volume(U32 index, F32 volume)
+{
+ static pa_cvolume cvol;
+ llpa_cvolume_set(&cvol, mSinkInputNumChannels[index],
+ llpa_sw_volume_from_linear(volume));
+
+ pa_context *c = mPAContext;
+ uint32_t idx = index;
+ const pa_cvolume *cvolumep = &cvol;
+ pa_context_success_cb_t cb = NULL; // okay as null
+ void *userdata = NULL; // okay as null
+
+ pa_operation *op;
+ if ((op = llpa_context_set_sink_input_volume(c, idx, cvolumep, cb, userdata)))
+ {
+ llpa_operation_unref(op);
+ }
+}
+
+
+void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *sii, int eol, void *userdata)
+{
+ VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata);
+ llassert(impl);
+
+ if (0 == eol)
+ {
+ pa_proplist *proplist = sii->proplist;
+ pid_t sinkpid = atoll(llpa_proplist_gets(proplist, PA_PROP_APPLICATION_PROCESS_ID));
+
+ if (sinkpid == getpid()) // does the discovered sinkinput belong to this process?
+ {
+ bool is_new = (impl->mSinkInputIndices.find(sii->index) ==
+ impl->mSinkInputIndices.end());
+
+ impl->mSinkInputIndices.insert(sii->index);
+ impl->mSinkInputNumChannels[sii->index] = sii->channel_map.channels;
+
+ if (is_new)
+ {
+ // new!
+ impl->update_index_volume(sii->index, impl->mDesiredVolume);
+ }
+ else
+ {
+ // seen it already, do nothing.
+ }
+ }
+ }
+}
+
+void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata)
+{
+ VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata);
+ llassert(impl);
+
+ switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
+ case PA_SUBSCRIPTION_EVENT_SINK_INPUT:
+ if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
+ PA_SUBSCRIPTION_EVENT_REMOVE)
+ {
+ // forget this sinkinput, if we were caring about it
+ impl->mSinkInputIndices.erase(index);
+ impl->mSinkInputNumChannels.erase(index);
+ }
+ else if ((t & PA_SUBSCRIPTION_EVENT_TYPE_MASK) ==
+ PA_SUBSCRIPTION_EVENT_NEW)
+ {
+ // ask for more info about this new sinkinput
+ pa_operation *op;
+ if ((op = llpa_context_get_sink_input_info(impl->mPAContext, index, callback_discovered_sinkinput, impl)))
+ {
+ llpa_operation_unref(op);
+ }
+ }
+ else
+ {
+ // property change on this sinkinput - we don't care.
+ }
+ break;
+
+ default:;
+ }
+}
+
+void callback_context_state(pa_context *context, void *userdata)
+{
+ VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata);
+ llassert(impl);
+
+ switch (llpa_context_get_state(context))
+ {
+ case PA_CONTEXT_READY:
+ impl->mConnected = true;
+ impl->connected_okay();
+ break;
+ case PA_CONTEXT_TERMINATED:
+ impl->mConnected = false;
+ break;
+ case PA_CONTEXT_FAILED:
+ impl->mConnected = false;
+ break;
+ default:;
+ }
+}
+
+/////////////////////////////////////////////////////
+
+VolumeCatcher::VolumeCatcher()
+{
+ pimpl = new VolumeCatcherImpl();
+}
+
+VolumeCatcher::~VolumeCatcher()
+{
+ delete pimpl;
+ pimpl = NULL;
+}
+
+void VolumeCatcher::setVolume(F32 volume)
+{
+ llassert(pimpl);
+ pimpl->setVolume(volume);
+}
+
+void VolumeCatcher::setPan(F32 pan)
+{
+ // TODO: implement this (if possible)
+}
+
+void VolumeCatcher::pump()
+{
+ llassert(pimpl);
+ pimpl->pump();
+}
diff --git a/indra/media_plugins/webkit/linux_volume_catcher_pa_syms.inc b/indra/media_plugins/webkit/linux_volume_catcher_pa_syms.inc
new file mode 100644
index 0000000000..d806b48428
--- /dev/null
+++ b/indra/media_plugins/webkit/linux_volume_catcher_pa_syms.inc
@@ -0,0 +1,21 @@
+// required symbols to grab
+LL_PA_SYM(true, pa_context_connect, int, pa_context *c, const char *server, pa_context_flags_t flags, const pa_spawn_api *api);
+LL_PA_SYM(true, pa_context_disconnect, void, pa_context *c);
+LL_PA_SYM(true, pa_context_get_sink_input_info, pa_operation*, pa_context *c, uint32_t idx, pa_sink_input_info_cb_t cb, void *userdata);
+LL_PA_SYM(true, pa_context_get_sink_input_info_list, pa_operation*, pa_context *c, pa_sink_input_info_cb_t cb, void *userdata);
+LL_PA_SYM(true, pa_context_get_state, pa_context_state_t, pa_context *c);
+LL_PA_SYM(true, pa_context_new_with_proplist, pa_context*, pa_mainloop_api *mainloop, const char *name, pa_proplist *proplist);
+LL_PA_SYM(true, pa_context_set_sink_input_volume, pa_operation*, pa_context *c, uint32_t idx, const pa_cvolume *volume, pa_context_success_cb_t cb, void *userdata);
+LL_PA_SYM(true, pa_context_set_state_callback, void, pa_context *c, pa_context_notify_cb_t cb, void *userdata);
+LL_PA_SYM(true, pa_context_set_subscribe_callback, void, pa_context *c, pa_context_subscribe_cb_t cb, void *userdata);
+LL_PA_SYM(true, pa_context_subscribe, pa_operation*, pa_context *c, pa_subscription_mask_t m, pa_context_success_cb_t cb, void *userdata);
+LL_PA_SYM(true, pa_context_unref, void, pa_context *c);
+LL_PA_SYM(true, pa_cvolume_set, pa_cvolume*, pa_cvolume *a, unsigned channels, pa_volume_t v);
+LL_PA_SYM(true, pa_operation_unref, void, pa_operation *o);
+LL_PA_SYM(true, pa_proplist_free, void, pa_proplist* p);
+LL_PA_SYM(true, pa_proplist_gets, const char*, pa_proplist *p, const char *key);
+LL_PA_SYM(true, pa_proplist_new, pa_proplist*, void);
+LL_PA_SYM(true, pa_proplist_sets, int, pa_proplist *p, const char *key, const char *value);
+LL_PA_SYM(true, pa_sw_volume_from_linear, pa_volume_t, double v);
+
+// optional symbols to grab
diff --git a/indra/media_plugins/webkit/linux_volume_catcher_paglib_syms.inc b/indra/media_plugins/webkit/linux_volume_catcher_paglib_syms.inc
new file mode 100644
index 0000000000..abf628c96c
--- /dev/null
+++ b/indra/media_plugins/webkit/linux_volume_catcher_paglib_syms.inc
@@ -0,0 +1,6 @@
+// required symbols to grab
+LL_PA_SYM(true, pa_glib_mainloop_free, void, pa_glib_mainloop* g);
+LL_PA_SYM(true, pa_glib_mainloop_get_api, pa_mainloop_api*, pa_glib_mainloop* g);
+LL_PA_SYM(true, pa_glib_mainloop_new, pa_glib_mainloop *, GMainContext *c);
+
+// optional symbols to grab
diff --git a/indra/media_plugins/webkit/mac_volume_catcher.cpp b/indra/media_plugins/webkit/mac_volume_catcher.cpp
new file mode 100644
index 0000000000..8a06bb8487
--- /dev/null
+++ b/indra/media_plugins/webkit/mac_volume_catcher.cpp
@@ -0,0 +1,268 @@
+/**
+ * @file mac_volume_catcher.cpp
+ * @brief A Mac OS X specific hack to control the volume level of all audio channels opened by a process.
+ *
+ * @cond
+ * $LicenseInfo:firstyear=2010&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ * @endcond
+ */
+
+/**************************************************************************************************************
+ This code works by using CaptureComponent to capture the "Default Output" audio component
+ (kAudioUnitType_Output/kAudioUnitSubType_DefaultOutput) and delegating all calls to the original component.
+ It does this just to keep track of all instances of the default output component, so that it can set the
+ kHALOutputParam_Volume parameter on all of them to adjust the output volume.
+**************************************************************************************************************/
+
+#include "volume_catcher.h"
+
+#include <Carbon/Carbon.h>
+#include <QuickTime/QuickTime.h>
+#include <AudioUnit/AudioUnit.h>
+
+struct VolumeCatcherStorage;
+
+class VolumeCatcherImpl
+{
+public:
+
+ void setVolume(F32 volume);
+ void setPan(F32 pan);
+
+ void setInstanceVolume(VolumeCatcherStorage *instance);
+
+ std::list<VolumeCatcherStorage*> mComponentInstances;
+ Component mOriginalDefaultOutput;
+ Component mVolumeAdjuster;
+
+ static VolumeCatcherImpl *getInstance();
+private:
+ // This is a singleton class -- both callers and the component implementation should use getInstance() to find the instance.
+ VolumeCatcherImpl();
+ static VolumeCatcherImpl *sInstance;
+
+ // The singlar instance of this class is expected to last until the process exits.
+ // To ensure this, we declare the destructor here but never define it, so any code which attempts to destroy the instance will not link.
+ ~VolumeCatcherImpl();
+
+ F32 mVolume;
+ F32 mPan;
+};
+
+VolumeCatcherImpl *VolumeCatcherImpl::sInstance = NULL;;
+
+struct VolumeCatcherStorage
+{
+ ComponentInstance self;
+ ComponentInstance delegate;
+};
+
+static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, Handle componentStorage);
+static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self);
+static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *storage, ComponentInstance self);
+
+VolumeCatcherImpl *VolumeCatcherImpl::getInstance()
+{
+ if(!sInstance)
+ {
+ sInstance = new VolumeCatcherImpl;
+ }
+
+ return sInstance;
+}
+
+VolumeCatcherImpl::VolumeCatcherImpl()
+{
+ mVolume = 1.0; // default to full volume
+ mPan = 0.0; // and center pan
+
+ ComponentDescription desc;
+ desc.componentType = kAudioUnitType_Output;
+ desc.componentSubType = kAudioUnitSubType_DefaultOutput;
+ desc.componentManufacturer = kAudioUnitManufacturer_Apple;
+ desc.componentFlags = 0;
+ desc.componentFlagsMask = 0;
+
+ // Find the original default output component
+ mOriginalDefaultOutput = FindNextComponent(NULL, &desc);
+
+ // Register our own output component with the same parameters
+ mVolumeAdjuster = RegisterComponent(&desc, NewComponentRoutineUPP(volume_catcher_component_entry), 0, NULL, NULL, NULL);
+
+ // Capture the original component, so we always get found instead.
+ CaptureComponent(mOriginalDefaultOutput, mVolumeAdjuster);
+
+}
+
+static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, Handle componentStorage)
+{
+ ComponentResult result = badComponentSelector;
+ VolumeCatcherStorage *storage = (VolumeCatcherStorage*)componentStorage;
+
+ switch(cp->what)
+ {
+ case kComponentOpenSelect:
+// std::cerr << "kComponentOpenSelect" << std::endl;
+ result = CallComponentFunctionWithStorageProcInfo((Handle)storage, cp, (ProcPtr)volume_catcher_component_open, uppCallComponentOpenProcInfo);
+ break;
+
+ case kComponentCloseSelect:
+// std::cerr << "kComponentCloseSelect" << std::endl;
+ result = CallComponentFunctionWithStorageProcInfo((Handle)storage, cp, (ProcPtr)volume_catcher_component_close, uppCallComponentCloseProcInfo);
+ // CallComponentFunctionWithStorageProcInfo
+ break;
+
+ default:
+// std::cerr << "Delegating selector: " << cp->what << " to component instance " << storage->delegate << std::endl;
+ result = DelegateComponentCall(cp, storage->delegate);
+ break;
+ }
+
+ return result;
+}
+
+static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self)
+{
+ ComponentResult result = noErr;
+ VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
+
+ storage = new VolumeCatcherStorage;
+
+ storage->self = self;
+ storage->delegate = NULL;
+
+ result = OpenAComponent(impl->mOriginalDefaultOutput, &(storage->delegate));
+
+ if(result != noErr)
+ {
+// std::cerr << "OpenAComponent result = " << result << ", component ref = " << storage->delegate << std::endl;
+
+ // If we failed to open the delagate component, our open is going to fail. Clean things up.
+ delete storage;
+ }
+ else
+ {
+ // Success -- set up this component's storage
+ SetComponentInstanceStorage(self, (Handle)storage);
+
+ // add this instance to the global list
+ impl->mComponentInstances.push_back(storage);
+
+ // and set up the initial volume
+ impl->setInstanceVolume(storage);
+ }
+
+ return result;
+}
+
+static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *storage, ComponentInstance self)
+{
+ ComponentResult result = noErr;
+
+ if(storage)
+ {
+ if(storage->delegate)
+ {
+ CloseComponent(storage->delegate);
+ storage->delegate = NULL;
+ }
+
+ VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
+ impl->mComponentInstances.remove(storage);
+ delete[] storage;
+ }
+
+ return result;
+}
+
+void VolumeCatcherImpl::setVolume(F32 volume)
+{
+ VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
+ impl->mVolume = volume;
+
+ // Iterate through all known instances, setting the volume on each.
+ for(std::list<VolumeCatcherStorage*>::iterator iter = mComponentInstances.begin(); iter != mComponentInstances.end(); ++iter)
+ {
+ impl->setInstanceVolume(*iter);
+ }
+}
+
+void VolumeCatcherImpl::setPan(F32 pan)
+{
+ VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
+ impl->mPan = pan;
+
+ // TODO: implement this.
+ // This will probably require adding a "panner" audio unit to the chain somehow.
+ // There's also a "3d mixer" component that we might be able to use...
+}
+
+void VolumeCatcherImpl::setInstanceVolume(VolumeCatcherStorage *instance)
+{
+// std::cerr << "Setting volume on component instance: " << (instance->delegate) << " to " << mVolume << std::endl;
+
+ OSStatus err = noErr;
+
+ if(instance && instance->delegate)
+ {
+ err = AudioUnitSetParameter(
+ instance->delegate,
+ kHALOutputParam_Volume,
+ kAudioUnitScope_Global,
+ 0,
+ mVolume,
+ 0);
+ }
+
+ if(err)
+ {
+// std::cerr << " AudioUnitSetParameter returned " << err << std::endl;
+ }
+}
+
+/////////////////////////////////////////////////////
+
+VolumeCatcher::VolumeCatcher()
+{
+ pimpl = VolumeCatcherImpl::getInstance();
+}
+
+VolumeCatcher::~VolumeCatcher()
+{
+ // Let the instance persist until exit.
+}
+
+void VolumeCatcher::setVolume(F32 volume)
+{
+ pimpl->setVolume(volume);
+}
+
+void VolumeCatcher::setPan(F32 pan)
+{
+ pimpl->setPan(pan);
+}
+
+void VolumeCatcher::pump()
+{
+ // No periodic tasks are necessary for this implementation.
+}
+
diff --git a/indra/media_plugins/webkit/media_plugin_webkit.cpp b/indra/media_plugins/webkit/media_plugin_webkit.cpp
new file mode 100644
index 0000000000..bd1a44a930
--- /dev/null
+++ b/indra/media_plugins/webkit/media_plugin_webkit.cpp
@@ -0,0 +1,1238 @@
+/**
+ * @file media_plugin_webkit.cpp
+ * @brief Webkit plugin for LLMedia API plugin system
+ *
+ * @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 "llqtwebkit.h"
+
+#include "linden_common.h"
+#include "indra_constants.h" // for indra keyboard codes
+
+#include "llgl.h"
+
+#include "llplugininstance.h"
+#include "llpluginmessage.h"
+#include "llpluginmessageclasses.h"
+#include "media_plugin_base.h"
+
+// set to 1 if you're using the version of llqtwebkit that's QPixmap-ified
+#if LL_LINUX
+# define LL_QTWEBKIT_USES_PIXMAPS 0
+extern "C" {
+# include <glib.h>
+# include <glib-object.h>
+}
+#else
+# define LL_QTWEBKIT_USES_PIXMAPS 0
+#endif // LL_LINUX
+
+# include "volume_catcher.h"
+
+#if LL_WINDOWS
+# include <direct.h>
+#else
+# include <unistd.h>
+# include <stdlib.h>
+#endif
+
+#if LL_WINDOWS
+ // *NOTE:Mani - This captures the module handle for the dll. This is used below
+ // to get the path to this dll for webkit initialization.
+ // I don't know how/if this can be done with apr...
+ namespace { HMODULE gModuleHandle;};
+ BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
+ {
+ gModuleHandle = (HMODULE) hinstDLL;
+ return TRUE;
+ }
+#endif
+
+////////////////////////////////////////////////////////////////////////////////
+//
+class MediaPluginWebKit :
+ public MediaPluginBase,
+ public LLEmbeddedBrowserWindowObserver
+{
+public:
+ MediaPluginWebKit(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data);
+ ~MediaPluginWebKit();
+
+ /*virtual*/ void receiveMessage(const char *message_string);
+
+private:
+
+ std::string mProfileDir;
+ std::string mHostLanguage;
+ std::string mUserAgent;
+ bool mCookiesEnabled;
+ bool mJavascriptEnabled;
+ bool mPluginsEnabled;
+
+ enum
+ {
+ INIT_STATE_UNINITIALIZED, // LLQtWebkit hasn't been set up yet
+ INIT_STATE_INITIALIZED, // LLQtWebkit has been set up, but no browser window has been created yet.
+ INIT_STATE_NAVIGATING, // Browser instance has been set up and initial navigate to about:blank has been issued
+ INIT_STATE_NAVIGATE_COMPLETE, // initial navigate to about:blank has completed
+ INIT_STATE_WAIT_REDRAW, // First real navigate begin has been received, waiting for page changed event to start handling redraws
+ INIT_STATE_WAIT_COMPLETE, // Waiting for first real navigate complete event
+ INIT_STATE_RUNNING // All initialization gymnastics are complete.
+ };
+ int mBrowserWindowId;
+ int mInitState;
+ std::string mInitialNavigateURL;
+ bool mNeedsUpdate;
+
+ bool mCanCut;
+ bool mCanCopy;
+ bool mCanPaste;
+ int mLastMouseX;
+ int mLastMouseY;
+ bool mFirstFocus;
+ F32 mBackgroundR;
+ F32 mBackgroundG;
+ F32 mBackgroundB;
+ std::string mTarget;
+
+ VolumeCatcher mVolumeCatcher;
+
+ void setInitState(int state)
+ {
+// std::cerr << "changing init state to " << state << std::endl;
+ mInitState = state;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ void update(int milliseconds)
+ {
+#if LL_QTLINUX_DOESNT_HAVE_GLIB
+ // pump glib generously, as Linux browser plugins are on the
+ // glib main loop, even if the browser itself isn't - ugh
+ // This is NOT NEEDED if Qt itself was built with glib
+ // mainloop integration.
+ GMainContext *mainc = g_main_context_default();
+ while(g_main_context_iteration(mainc, FALSE));
+#endif // LL_QTLINUX_DOESNT_HAVE_GLIB
+
+ // pump qt
+ LLQtWebKit::getInstance()->pump( milliseconds );
+
+ mVolumeCatcher.pump();
+
+ checkEditState();
+
+ if(mInitState == INIT_STATE_NAVIGATE_COMPLETE)
+ {
+ if(!mInitialNavigateURL.empty())
+ {
+ // We already have the initial navigate URL -- kick off the navigate.
+ LLQtWebKit::getInstance()->navigateTo( mBrowserWindowId, mInitialNavigateURL );
+ mInitialNavigateURL.clear();
+ }
+ }
+
+ if ( (mInitState > INIT_STATE_WAIT_REDRAW) && mNeedsUpdate )
+ {
+ const unsigned char* browser_pixels = LLQtWebKit::getInstance()->grabBrowserWindow( mBrowserWindowId );
+
+ unsigned int rowspan = LLQtWebKit::getInstance()->getBrowserRowSpan( mBrowserWindowId );
+ unsigned int height = LLQtWebKit::getInstance()->getBrowserHeight( mBrowserWindowId );
+#if !LL_QTWEBKIT_USES_PIXMAPS
+ unsigned int buffer_size = rowspan * height;
+#endif // !LL_QTWEBKIT_USES_PIXMAPS
+
+// std::cerr << "webkit plugin: updating" << std::endl;
+
+ // TODO: should get rid of this memcpy if possible
+ if ( mPixels && browser_pixels )
+ {
+// std::cerr << " memcopy of " << buffer_size << " bytes" << std::endl;
+
+#if LL_QTWEBKIT_USES_PIXMAPS
+ // copy the pixel data upside-down because of the co-ord system
+ for (int y=0; y<height; ++y)
+ {
+ memcpy( &mPixels[(height-y-1)*rowspan], &browser_pixels[y*rowspan], rowspan );
+ }
+#else
+ memcpy( mPixels, browser_pixels, buffer_size );
+#endif // LL_QTWEBKIT_USES_PIXMAPS
+ }
+
+ if ( mWidth > 0 && mHeight > 0 )
+ {
+// std::cerr << "Setting dirty, " << mWidth << " x " << mHeight << std::endl;
+ setDirty( 0, 0, mWidth, mHeight );
+ }
+
+ mNeedsUpdate = false;
+ };
+ };
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ bool initBrowser()
+ {
+ // already initialized
+ if ( mInitState > INIT_STATE_UNINITIALIZED )
+ return true;
+
+ // set up directories
+ char cwd[ FILENAME_MAX ]; // I *think* this is defined on all platforms we use
+ if (NULL == getcwd( cwd, FILENAME_MAX - 1 ))
+ {
+ llwarns << "Couldn't get cwd - probably too long - failing to init." << llendl;
+ return false;
+ }
+ std::string application_dir = std::string( cwd );
+
+#if LL_LINUX
+ // take care to initialize glib properly, because some
+ // versions of Qt don't, and we indirectly need it for (some
+ // versions of) Flash to not crash the browser.
+ if (!g_thread_supported ()) g_thread_init (NULL);
+ g_type_init();
+#endif
+
+#if LL_DARWIN
+ // When running under the Xcode debugger, there's a setting called "Break on Debugger()/DebugStr()" which defaults to being turned on.
+ // This causes the environment variable USERBREAK to be set to 1, which causes these legacy calls to break into the debugger.
+ // This wouldn't cause any problems except for the fact that the current release version of the Flash plugin has a call to Debugger() in it
+ // which gets hit when the plugin is probed by webkit.
+ // Unsetting the environment variable here works around this issue.
+ unsetenv("USERBREAK");
+#endif
+
+#if LL_WINDOWS
+ //*NOTE:Mani - On windows, at least, the component path is the
+ // location of this dll's image file.
+ std::string component_dir;
+ char dll_path[_MAX_PATH];
+ DWORD len = GetModuleFileNameA(gModuleHandle, (LPCH)&dll_path, _MAX_PATH);
+ while(len && dll_path[ len ] != ('\\') )
+ {
+ len--;
+ }
+ if(len >= 0)
+ {
+ dll_path[len] = 0;
+ component_dir = dll_path;
+ }
+ else
+ {
+ // *NOTE:Mani - This case should be an rare exception.
+ // GetModuleFileNameA should always give you a full path, no?
+ component_dir = application_dir;
+ }
+#else
+ std::string component_dir = application_dir;
+#endif
+
+ // window handle - needed on Windows and must be app window.
+#if LL_WINDOWS
+ char window_title[ MAX_PATH ];
+ GetConsoleTitleA( window_title, MAX_PATH );
+ void* native_window_handle = (void*)FindWindowA( NULL, window_title );
+#else
+ void* native_window_handle = 0;
+#endif
+
+ // main browser initialization
+ bool result = LLQtWebKit::getInstance()->init( application_dir, component_dir, mProfileDir, native_window_handle );
+ if ( result )
+ {
+ mInitState = INIT_STATE_INITIALIZED;
+
+ return true;
+ };
+
+ return false;
+ };
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ bool initBrowserWindow()
+ {
+ // already initialized
+ if ( mInitState > INIT_STATE_INITIALIZED )
+ return true;
+
+ // not enough information to initialize the browser yet.
+ if ( mWidth < 0 || mHeight < 0 || mDepth < 0 ||
+ mTextureWidth < 0 || mTextureHeight < 0 )
+ {
+ return false;
+ };
+
+ // Set up host language before creating browser window
+ if(!mHostLanguage.empty())
+ {
+ LLQtWebKit::getInstance()->setHostLanguage(mHostLanguage);
+ }
+
+ // turn on/off cookies based on what host app tells us
+ LLQtWebKit::getInstance()->enableCookies( mCookiesEnabled );
+
+ // turn on/off plugins based on what host app tells us
+ LLQtWebKit::getInstance()->enablePlugins( mPluginsEnabled );
+
+ // turn on/off Javascript based on what host app tells us
+ LLQtWebKit::getInstance()->enableJavascript( mJavascriptEnabled );
+
+ // create single browser window
+ mBrowserWindowId = LLQtWebKit::getInstance()->createBrowserWindow( mWidth, mHeight, mTarget);
+
+ // tell LLQtWebKit about the size of the browser window
+ LLQtWebKit::getInstance()->setSize( mBrowserWindowId, mWidth, mHeight );
+
+ // observer events that LLQtWebKit emits
+ LLQtWebKit::getInstance()->addObserver( mBrowserWindowId, this );
+
+ // append details to agent string
+ LLQtWebKit::getInstance()->setBrowserAgentId( mUserAgent );
+
+#if !LL_QTWEBKIT_USES_PIXMAPS
+ // don't flip bitmap
+ LLQtWebKit::getInstance()->flipWindow( mBrowserWindowId, true );
+#endif // !LL_QTWEBKIT_USES_PIXMAPS
+
+ // set background color
+ // convert background color channels from [0.0, 1.0] to [0, 255];
+ LLQtWebKit::getInstance()->setBackgroundColor( mBrowserWindowId, int(mBackgroundR * 255.0f), int(mBackgroundG * 255.0f), int(mBackgroundB * 255.0f) );
+
+ // Set state _before_ starting the navigate, since onNavigateBegin might get called before this call returns.
+ setInitState(INIT_STATE_NAVIGATING);
+
+ // Don't do this here -- it causes the dreaded "white flash" when loading a browser instance.
+ // FIXME: Re-added this because navigating to a "page" initializes things correctly - especially
+ // for the HTTP AUTH dialog issues (DEV-41731). Will fix at a later date.
+ // Build a data URL like this: "data:text/html,%3Chtml%3E%3Cbody bgcolor=%22#RRGGBB%22%3E%3C/body%3E%3C/html%3E"
+ // where RRGGBB is the background color in HTML style
+ std::stringstream url;
+
+ url << "data:text/html,%3Chtml%3E%3Cbody%20bgcolor=%22#";
+ // convert background color channels from [0.0, 1.0] to [0, 255];
+ url << std::setfill('0') << std::setw(2) << std::hex << int(mBackgroundR * 255.0f);
+ url << std::setfill('0') << std::setw(2) << std::hex << int(mBackgroundG * 255.0f);
+ url << std::setfill('0') << std::setw(2) << std::hex << int(mBackgroundB * 255.0f);
+ url << "%22%3E%3C/body%3E%3C/html%3E";
+
+ lldebugs << "data url is: " << url.str() << llendl;
+
+ LLQtWebKit::getInstance()->navigateTo( mBrowserWindowId, url.str() );
+// LLQtWebKit::getInstance()->navigateTo( mBrowserWindowId, "about:blank" );
+
+ return true;
+ }
+
+ void setVolume(F32 vol);
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // virtual
+ void onCursorChanged(const EventType& event)
+ {
+ LLQtWebKit::ECursor llqt_cursor = (LLQtWebKit::ECursor)event.getIntValue();
+ std::string name;
+
+ switch(llqt_cursor)
+ {
+ case LLQtWebKit::C_ARROW:
+ name = "arrow";
+ break;
+ case LLQtWebKit::C_IBEAM:
+ name = "ibeam";
+ break;
+ case LLQtWebKit::C_SPLITV:
+ name = "splitv";
+ break;
+ case LLQtWebKit::C_SPLITH:
+ name = "splith";
+ break;
+ case LLQtWebKit::C_POINTINGHAND:
+ name = "hand";
+ break;
+
+ default:
+ llwarns << "Unknown cursor ID: " << (int)llqt_cursor << llendl;
+ break;
+ }
+
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "cursor_changed");
+ message.setValue("name", name);
+ sendMessage(message);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // virtual
+ void onPageChanged( const EventType& event )
+ {
+ if(mInitState == INIT_STATE_WAIT_REDRAW)
+ {
+ setInitState(INIT_STATE_WAIT_COMPLETE);
+ }
+
+ // flag that an update is required
+ mNeedsUpdate = true;
+ };
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // virtual
+ void onNavigateBegin(const EventType& event)
+ {
+ if(mInitState >= INIT_STATE_NAVIGATE_COMPLETE)
+ {
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "navigate_begin");
+ message.setValue("uri", event.getEventUri());
+ sendMessage(message);
+
+ setStatus(STATUS_LOADING);
+ }
+
+ if(mInitState == INIT_STATE_NAVIGATE_COMPLETE)
+ {
+ // Skip the WAIT_REDRAW state now -- with the right background color set, it should no longer be necessary.
+// setInitState(INIT_STATE_WAIT_REDRAW);
+ setInitState(INIT_STATE_WAIT_COMPLETE);
+ }
+
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // virtual
+ void onNavigateComplete(const EventType& event)
+ {
+ if(mInitState >= INIT_STATE_NAVIGATE_COMPLETE)
+ {
+ if(mInitState < INIT_STATE_RUNNING)
+ {
+ setInitState(INIT_STATE_RUNNING);
+
+ // Clear the history, so the "back" button doesn't take you back to "about:blank".
+ LLQtWebKit::getInstance()->clearHistory(mBrowserWindowId);
+ }
+
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "navigate_complete");
+ message.setValue("uri", event.getEventUri());
+ message.setValueS32("result_code", event.getIntValue());
+ message.setValue("result_string", event.getStringValue());
+ message.setValueBoolean("history_back_available", LLQtWebKit::getInstance()->userActionIsEnabled( mBrowserWindowId, LLQtWebKit::UA_NAVIGATE_BACK));
+ message.setValueBoolean("history_forward_available", LLQtWebKit::getInstance()->userActionIsEnabled( mBrowserWindowId, LLQtWebKit::UA_NAVIGATE_FORWARD));
+ sendMessage(message);
+
+ setStatus(STATUS_LOADED);
+ }
+ else if(mInitState == INIT_STATE_NAVIGATING)
+ {
+ setInitState(INIT_STATE_NAVIGATE_COMPLETE);
+ }
+
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // virtual
+ void onUpdateProgress(const EventType& event)
+ {
+ if(mInitState >= INIT_STATE_NAVIGATE_COMPLETE)
+ {
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "progress");
+ message.setValueS32("percent", event.getIntValue());
+ sendMessage(message);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // virtual
+ void onStatusTextChange(const EventType& event)
+ {
+ if(mInitState >= INIT_STATE_NAVIGATE_COMPLETE)
+ {
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "status_text");
+ message.setValue("status", event.getStringValue());
+ sendMessage(message);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // virtual
+ void onTitleChange(const EventType& event)
+ {
+ if(mInitState >= INIT_STATE_NAVIGATE_COMPLETE)
+ {
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "name_text");
+ message.setValue("name", event.getStringValue());
+ sendMessage(message);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // virtual
+ void onLocationChange(const EventType& event)
+ {
+ if(mInitState >= INIT_STATE_NAVIGATE_COMPLETE)
+ {
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "location_changed");
+ message.setValue("uri", event.getEventUri());
+ sendMessage(message);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // virtual
+ void onClickLinkHref(const EventType& event)
+ {
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "click_href");
+ message.setValue("uri", event.getEventUri());
+ message.setValue("target", event.getStringValue());
+ message.setValue("uuid", event.getStringValue2());
+ sendMessage(message);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // virtual
+ void onClickLinkNoFollow(const EventType& event)
+ {
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "click_nofollow");
+ message.setValue("uri", event.getEventUri());
+ sendMessage(message);
+ }
+
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // virtual
+ void onCookieChanged(const EventType& event)
+ {
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "cookie_set");
+ message.setValue("cookie", event.getStringValue());
+ // These could be passed through as well, but aren't really needed.
+// message.setValue("uri", event.getEventUri());
+// message.setValueBoolean("dead", (event.getIntValue() != 0))
+ sendMessage(message);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // virtual
+ void onWindowCloseRequested(const EventType& event)
+ {
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "close_request");
+ message.setValue("uuid", event.getStringValue());
+ sendMessage(message);
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // virtual
+ void onWindowGeometryChangeRequested(const EventType& event)
+ {
+ int x, y, width, height;
+ event.getRectValue(x, y, width, height);
+
+ // This sometimes gets called with a zero-size request. Don't pass these along.
+ if(width > 0 && height > 0)
+ {
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "geometry_change");
+ message.setValue("uuid", event.getStringValue());
+ message.setValueS32("x", x);
+ message.setValueS32("y", y);
+ message.setValueS32("width", width);
+ message.setValueS32("height", height);
+ sendMessage(message);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ // virtual
+ std::string onRequestFilePicker( const EventType& eventIn )
+ {
+ return blockingPickFile();
+ }
+
+ LLQtWebKit::EKeyboardModifier decodeModifiers(std::string &modifiers)
+ {
+ int result = 0;
+
+ if(modifiers.find("shift") != std::string::npos)
+ result |= LLQtWebKit::KM_MODIFIER_SHIFT;
+
+ if(modifiers.find("alt") != std::string::npos)
+ result |= LLQtWebKit::KM_MODIFIER_ALT;
+
+ if(modifiers.find("control") != std::string::npos)
+ result |= LLQtWebKit::KM_MODIFIER_CONTROL;
+
+ if(modifiers.find("meta") != std::string::npos)
+ result |= LLQtWebKit::KM_MODIFIER_META;
+
+ return (LLQtWebKit::EKeyboardModifier)result;
+ }
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ void deserializeKeyboardData( LLSD native_key_data, uint32_t& native_scan_code, uint32_t& native_virtual_key, uint32_t& native_modifiers )
+ {
+ native_scan_code = 0;
+ native_virtual_key = 0;
+ native_modifiers = 0;
+
+ if( native_key_data.isMap() )
+ {
+#if LL_DARWIN
+ native_scan_code = (uint32_t)(native_key_data["char_code"].asInteger());
+ native_virtual_key = (uint32_t)(native_key_data["key_code"].asInteger());
+ native_modifiers = (uint32_t)(native_key_data["modifiers"].asInteger());
+#elif LL_WINDOWS
+ native_scan_code = (uint32_t)(native_key_data["scan_code"].asInteger());
+ native_virtual_key = (uint32_t)(native_key_data["virtual_key"].asInteger());
+ // TODO: I don't think we need to do anything with native modifiers here -- please verify
+#elif LL_LINUX
+ native_scan_code = (uint32_t)(native_key_data["scan_code"].asInteger());
+ native_virtual_key = (uint32_t)(native_key_data["virtual_key"].asInteger());
+ native_modifiers = (uint32_t)(native_key_data["modifiers"].asInteger());
+#else
+ // Add other platforms here as needed
+#endif
+ };
+ };
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ void keyEvent(LLQtWebKit::EKeyEvent key_event, int key, LLQtWebKit::EKeyboardModifier modifiers, LLSD native_key_data = LLSD::emptyMap())
+ {
+ // The incoming values for 'key' will be the ones from indra_constants.h
+ std::string utf8_text;
+
+ if(key < KEY_SPECIAL)
+ {
+ // Low-ascii characters need to get passed through.
+ utf8_text = (char)key;
+ }
+
+ // Any special-case handling we want to do for particular keys...
+ switch((KEY)key)
+ {
+ // ASCII codes for some standard keys
+ case LLQtWebKit::KEY_BACKSPACE: utf8_text = (char)8; break;
+ case LLQtWebKit::KEY_TAB: utf8_text = (char)9; break;
+ case LLQtWebKit::KEY_RETURN: utf8_text = (char)13; break;
+ case LLQtWebKit::KEY_PAD_RETURN: utf8_text = (char)13; break;
+ case LLQtWebKit::KEY_ESCAPE: utf8_text = (char)27; break;
+
+ default:
+ break;
+ }
+
+// std::cerr << "key event " << (int)key_event << ", native_key_data = " << native_key_data << std::endl;
+
+ uint32_t native_scan_code = 0;
+ uint32_t native_virtual_key = 0;
+ uint32_t native_modifiers = 0;
+ deserializeKeyboardData( native_key_data, native_scan_code, native_virtual_key, native_modifiers );
+
+ LLQtWebKit::getInstance()->keyboardEvent( mBrowserWindowId, key_event, (uint32_t)key, utf8_text.c_str(), modifiers, native_scan_code, native_virtual_key, native_modifiers);
+
+ checkEditState();
+ };
+
+ ////////////////////////////////////////////////////////////////////////////////
+ //
+ void unicodeInput( const std::string &utf8str, LLQtWebKit::EKeyboardModifier modifiers, LLSD native_key_data = LLSD::emptyMap())
+ {
+ uint32_t key = LLQtWebKit::KEY_NONE;
+
+// std::cerr << "unicode input, native_key_data = " << native_key_data << std::endl;
+
+ if(utf8str.size() == 1)
+ {
+ // The only way a utf8 string can be one byte long is if it's actually a single 7-bit ascii character.
+ // In this case, use it as the key value.
+ key = utf8str[0];
+ }
+
+ uint32_t native_scan_code = 0;
+ uint32_t native_virtual_key = 0;
+ uint32_t native_modifiers = 0;
+ deserializeKeyboardData( native_key_data, native_scan_code, native_virtual_key, native_modifiers );
+
+ LLQtWebKit::getInstance()->keyboardEvent( mBrowserWindowId, LLQtWebKit::KE_KEY_DOWN, (uint32_t)key, utf8str.c_str(), modifiers, native_scan_code, native_virtual_key, native_modifiers);
+ LLQtWebKit::getInstance()->keyboardEvent( mBrowserWindowId, LLQtWebKit::KE_KEY_UP, (uint32_t)key, utf8str.c_str(), modifiers, native_scan_code, native_virtual_key, native_modifiers);
+
+ checkEditState();
+ };
+
+ void checkEditState(void)
+ {
+ bool can_cut = LLQtWebKit::getInstance()->userActionIsEnabled( mBrowserWindowId, LLQtWebKit::UA_EDIT_CUT);
+ bool can_copy = LLQtWebKit::getInstance()->userActionIsEnabled( mBrowserWindowId, LLQtWebKit::UA_EDIT_COPY);
+ bool can_paste = LLQtWebKit::getInstance()->userActionIsEnabled( mBrowserWindowId, LLQtWebKit::UA_EDIT_PASTE);
+
+ if((can_cut != mCanCut) || (can_copy != mCanCopy) || (can_paste != mCanPaste))
+ {
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_state");
+
+ if(can_cut != mCanCut)
+ {
+ mCanCut = can_cut;
+ message.setValueBoolean("cut", can_cut);
+ }
+
+ if(can_copy != mCanCopy)
+ {
+ mCanCopy = can_copy;
+ message.setValueBoolean("copy", can_copy);
+ }
+
+ if(can_paste != mCanPaste)
+ {
+ mCanPaste = can_paste;
+ message.setValueBoolean("paste", can_paste);
+ }
+
+ sendMessage(message);
+
+ }
+ }
+
+ std::string mPickedFile;
+
+ std::string blockingPickFile(void)
+ {
+ mPickedFile.clear();
+
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "pick_file");
+ message.setValueBoolean("blocking_request", true);
+
+ // The "blocking_request" key in the message means this sendMessage call will block until a response is received.
+ sendMessage(message);
+
+ return mPickedFile;
+ }
+
+ void onPickFileResponse(const std::string &file)
+ {
+ mPickedFile = file;
+ }
+
+};
+
+MediaPluginWebKit::MediaPluginWebKit(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data) :
+ MediaPluginBase(host_send_func, host_user_data)
+{
+// std::cerr << "MediaPluginWebKit constructor" << std::endl;
+
+ mBrowserWindowId = 0;
+ mInitState = INIT_STATE_UNINITIALIZED;
+ mNeedsUpdate = true;
+ mCanCut = false;
+ mCanCopy = false;
+ mCanPaste = false;
+ mLastMouseX = 0;
+ mLastMouseY = 0;
+ mFirstFocus = true;
+ mBackgroundR = 0.0f;
+ mBackgroundG = 0.0f;
+ mBackgroundB = 0.0f;
+
+ mHostLanguage = "en"; // default to english
+ mJavascriptEnabled = true; // default to on
+ mPluginsEnabled = true; // default to on
+ mUserAgent = "LLPluginMedia Web Browser";
+}
+
+MediaPluginWebKit::~MediaPluginWebKit()
+{
+ // unhook observer
+ LLQtWebKit::getInstance()->remObserver( mBrowserWindowId, this );
+
+ // clean up
+ LLQtWebKit::getInstance()->reset();
+
+// std::cerr << "MediaPluginWebKit destructor" << std::endl;
+}
+
+void MediaPluginWebKit::receiveMessage(const char *message_string)
+{
+// std::cerr << "MediaPluginWebKit::receiveMessage: received message: \"" << message_string << "\"" << std::endl;
+ LLPluginMessage message_in;
+
+ if(message_in.parse(message_string) >= 0)
+ {
+ std::string message_class = message_in.getClass();
+ std::string message_name = message_in.getName();
+ if(message_class == LLPLUGIN_MESSAGE_CLASS_BASE)
+ {
+ if(message_name == "init")
+ {
+ LLPluginMessage message("base", "init_response");
+ LLSD versions = LLSD::emptyMap();
+ versions[LLPLUGIN_MESSAGE_CLASS_BASE] = LLPLUGIN_MESSAGE_CLASS_BASE_VERSION;
+ versions[LLPLUGIN_MESSAGE_CLASS_MEDIA] = LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION;
+ versions[LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER] = LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER_VERSION;
+ message.setValueLLSD("versions", versions);
+
+ std::string plugin_version = "Webkit media plugin, Webkit version ";
+ plugin_version += LLQtWebKit::getInstance()->getVersion();
+ message.setValue("plugin_version", plugin_version);
+ sendMessage(message);
+ }
+ else if(message_name == "idle")
+ {
+ // no response is necessary here.
+ F64 time = message_in.getValueReal("time");
+
+ // Convert time to milliseconds for update()
+ update((int)(time * 1000.0f));
+ }
+ else if(message_name == "cleanup")
+ {
+ // DTOR most likely won't be called but the recent change to the way this process
+ // is (not) killed means we see this message and can do what we need to here.
+ // Note: this cleanup is ultimately what writes cookies to the disk
+ LLQtWebKit::getInstance()->remObserver( mBrowserWindowId, this );
+ LLQtWebKit::getInstance()->reset();
+ }
+ else if(message_name == "shm_added")
+ {
+ SharedSegmentInfo info;
+ info.mAddress = message_in.getValuePointer("address");
+ info.mSize = (size_t)message_in.getValueS32("size");
+ std::string name = message_in.getValue("name");
+
+// std::cerr << "MediaPluginWebKit::receiveMessage: shared memory added, name: " << name
+// << ", size: " << info.mSize
+// << ", address: " << info.mAddress
+// << std::endl;
+
+ mSharedSegments.insert(SharedSegmentMap::value_type(name, info));
+
+ }
+ else if(message_name == "shm_remove")
+ {
+ std::string name = message_in.getValue("name");
+
+// std::cerr << "MediaPluginWebKit::receiveMessage: shared memory remove, name = " << name << std::endl;
+
+ SharedSegmentMap::iterator iter = mSharedSegments.find(name);
+ if(iter != mSharedSegments.end())
+ {
+ if(mPixels == iter->second.mAddress)
+ {
+ // This is the currently active pixel buffer. Make sure we stop drawing to it.
+ mPixels = NULL;
+ mTextureSegmentName.clear();
+ }
+ mSharedSegments.erase(iter);
+ }
+ else
+ {
+// std::cerr << "MediaPluginWebKit::receiveMessage: unknown shared memory region!" << std::endl;
+ }
+
+ // Send the response so it can be cleaned up.
+ LLPluginMessage message("base", "shm_remove_response");
+ message.setValue("name", name);
+ sendMessage(message);
+ }
+ else
+ {
+// std::cerr << "MediaPluginWebKit::receiveMessage: unknown base message: " << message_name << std::endl;
+ }
+ }
+ else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME)
+ {
+ if(message_name == "set_volume")
+ {
+ F32 volume = message_in.getValueReal("volume");
+ setVolume(volume);
+ }
+ }
+ else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA)
+ {
+ if(message_name == "init")
+ {
+ mTarget = message_in.getValue("target");
+
+ // This is the media init message -- all necessary data for initialization should have been received.
+ if(initBrowser())
+ {
+
+ // Plugin gets to decide the texture parameters to use.
+ mDepth = 4;
+
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params");
+ message.setValueS32("default_width", 1024);
+ message.setValueS32("default_height", 1024);
+ message.setValueS32("depth", mDepth);
+ message.setValueU32("internalformat", GL_RGBA);
+ #if LL_QTWEBKIT_USES_PIXMAPS
+ message.setValueU32("format", GL_BGRA_EXT); // I hope this isn't system-dependant... is it? If so, we'll have to check the root window's pixel layout or something... yuck.
+ #else
+ message.setValueU32("format", GL_RGBA);
+ #endif // LL_QTWEBKIT_USES_PIXMAPS
+ message.setValueU32("type", GL_UNSIGNED_BYTE);
+ message.setValueBoolean("coords_opengl", true);
+ sendMessage(message);
+ }
+ else
+ {
+ // if initialization failed, we're done.
+ mDeleteMe = true;
+ }
+
+ }
+ else if(message_name == "set_user_data_path")
+ {
+ std::string user_data_path = message_in.getValue("path"); // n.b. always has trailing platform-specific dir-delimiter
+ mProfileDir = user_data_path + "browser_profile";
+
+ // FIXME: Should we do anything with this if it comes in after the browser has been initialized?
+ }
+ else if(message_name == "set_language_code")
+ {
+ mHostLanguage = message_in.getValue("language");
+
+ // FIXME: Should we do anything with this if it comes in after the browser has been initialized?
+ }
+ else if(message_name == "plugins_enabled")
+ {
+ mPluginsEnabled = message_in.getValueBoolean("enable");
+ }
+ else if(message_name == "javascript_enabled")
+ {
+ mJavascriptEnabled = message_in.getValueBoolean("enable");
+ }
+ else if(message_name == "size_change")
+ {
+ std::string name = message_in.getValue("name");
+ S32 width = message_in.getValueS32("width");
+ S32 height = message_in.getValueS32("height");
+ S32 texture_width = message_in.getValueS32("texture_width");
+ S32 texture_height = message_in.getValueS32("texture_height");
+ mBackgroundR = message_in.getValueReal("background_r");
+ mBackgroundG = message_in.getValueReal("background_g");
+ mBackgroundB = message_in.getValueReal("background_b");
+// mBackgroundA = message_in.setValueReal("background_a"); // Ignore any alpha
+
+ if(!name.empty())
+ {
+ // Find the shared memory region with this name
+ SharedSegmentMap::iterator iter = mSharedSegments.find(name);
+ if(iter != mSharedSegments.end())
+ {
+ mPixels = (unsigned char*)iter->second.mAddress;
+ mWidth = width;
+ mHeight = height;
+
+ if(initBrowserWindow())
+ {
+
+ // size changed so tell the browser
+ LLQtWebKit::getInstance()->setSize( mBrowserWindowId, mWidth, mHeight );
+
+ // std::cerr << "webkit plugin: set size to " << mWidth << " x " << mHeight
+ // << ", rowspan is " << LLQtWebKit::getInstance()->getBrowserRowSpan(mBrowserWindowId) << std::endl;
+
+ S32 real_width = LLQtWebKit::getInstance()->getBrowserRowSpan(mBrowserWindowId) / LLQtWebKit::getInstance()->getBrowserDepth(mBrowserWindowId);
+
+ // The actual width the browser will be drawing to is probably smaller... let the host know by modifying texture_width in the response.
+ if(real_width <= texture_width)
+ {
+ texture_width = real_width;
+ }
+ else
+ {
+ // This won't work -- it'll be bigger than the allocated memory. This is a fatal error.
+ // std::cerr << "Fatal error: browser rowbytes greater than texture width" << std::endl;
+ mDeleteMe = true;
+ return;
+ }
+ }
+ else
+ {
+ // Setting up the browser window failed. This is a fatal error.
+ mDeleteMe = true;
+ }
+
+
+ mTextureWidth = texture_width;
+ mTextureHeight = texture_height;
+
+ };
+ };
+
+ LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change_response");
+ message.setValue("name", name);
+ message.setValueS32("width", width);
+ message.setValueS32("height", height);
+ message.setValueS32("texture_width", texture_width);
+ message.setValueS32("texture_height", texture_height);
+ sendMessage(message);
+
+ }
+ else if(message_name == "load_uri")
+ {
+ std::string uri = message_in.getValue("uri");
+
+// std::cout << "loading URI: " << uri << std::endl;
+
+ if(!uri.empty())
+ {
+ if(mInitState >= INIT_STATE_NAVIGATE_COMPLETE)
+ {
+ LLQtWebKit::getInstance()->navigateTo( mBrowserWindowId, uri );
+ }
+ else
+ {
+ mInitialNavigateURL = uri;
+ }
+ }
+ }
+ else if(message_name == "mouse_event")
+ {
+ std::string event = message_in.getValue("event");
+ S32 button = message_in.getValueS32("button");
+ mLastMouseX = message_in.getValueS32("x");
+ mLastMouseY = message_in.getValueS32("y");
+ std::string modifiers = message_in.getValue("modifiers");
+
+ // Treat unknown mouse events as mouse-moves.
+ LLQtWebKit::EMouseEvent mouse_event = LLQtWebKit::ME_MOUSE_MOVE;
+ if(event == "down")
+ {
+ mouse_event = LLQtWebKit::ME_MOUSE_DOWN;
+ }
+ else if(event == "up")
+ {
+ mouse_event = LLQtWebKit::ME_MOUSE_UP;
+ }
+ else if(event == "double_click")
+ {
+ mouse_event = LLQtWebKit::ME_MOUSE_DOUBLE_CLICK;
+ }
+
+ LLQtWebKit::getInstance()->mouseEvent( mBrowserWindowId, mouse_event, button, mLastMouseX, mLastMouseY, decodeModifiers(modifiers));
+ checkEditState();
+ }
+ else if(message_name == "scroll_event")
+ {
+ S32 x = message_in.getValueS32("x");
+ S32 y = message_in.getValueS32("y");
+ std::string modifiers = message_in.getValue("modifiers");
+
+ // Incoming scroll events are adjusted so that 1 detent is approximately 1 unit.
+ // Qt expects 1 detent to be 120 units.
+ // It also seems that our y scroll direction is inverted vs. what Qt expects.
+
+ x *= 120;
+ y *= -120;
+
+ LLQtWebKit::getInstance()->scrollWheelEvent(mBrowserWindowId, mLastMouseX, mLastMouseY, x, y, decodeModifiers(modifiers));
+ }
+ else if(message_name == "key_event")
+ {
+ std::string event = message_in.getValue("event");
+ S32 key = message_in.getValueS32("key");
+ std::string modifiers = message_in.getValue("modifiers");
+ LLSD native_key_data = message_in.getValueLLSD("native_key_data");
+
+ // Treat unknown events as key-up for safety.
+ LLQtWebKit::EKeyEvent key_event = LLQtWebKit::KE_KEY_UP;
+ if(event == "down")
+ {
+ key_event = LLQtWebKit::KE_KEY_DOWN;
+ }
+ else if(event == "repeat")
+ {
+ key_event = LLQtWebKit::KE_KEY_REPEAT;
+ }
+
+ keyEvent(key_event, key, decodeModifiers(modifiers), native_key_data);
+ }
+ else if(message_name == "text_event")
+ {
+ std::string text = message_in.getValue("text");
+ std::string modifiers = message_in.getValue("modifiers");
+ LLSD native_key_data = message_in.getValueLLSD("native_key_data");
+
+ unicodeInput(text, decodeModifiers(modifiers), native_key_data);
+ }
+ if(message_name == "edit_cut")
+ {
+ LLQtWebKit::getInstance()->userAction( mBrowserWindowId, LLQtWebKit::UA_EDIT_CUT );
+ checkEditState();
+ }
+ if(message_name == "edit_copy")
+ {
+ LLQtWebKit::getInstance()->userAction( mBrowserWindowId, LLQtWebKit::UA_EDIT_COPY );
+ checkEditState();
+ }
+ if(message_name == "edit_paste")
+ {
+ LLQtWebKit::getInstance()->userAction( mBrowserWindowId, LLQtWebKit::UA_EDIT_PASTE );
+ checkEditState();
+ }
+ if(message_name == "pick_file_response")
+ {
+ onPickFileResponse(message_in.getValue("file"));
+ }
+ else
+ {
+// std::cerr << "MediaPluginWebKit::receiveMessage: unknown media message: " << message_string << std::endl;
+ }
+ }
+ else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER)
+ {
+ if(message_name == "focus")
+ {
+ bool val = message_in.getValueBoolean("focused");
+ LLQtWebKit::getInstance()->focusBrowser( mBrowserWindowId, val );
+
+ if(mFirstFocus && val)
+ {
+ // On the first focus, post a tab key event. This fixes a problem with initial focus.
+ std::string empty;
+ keyEvent(LLQtWebKit::KE_KEY_DOWN, KEY_TAB, decodeModifiers(empty));
+ keyEvent(LLQtWebKit::KE_KEY_UP, KEY_TAB, decodeModifiers(empty));
+ mFirstFocus = false;
+ }
+ }
+ else if(message_name == "clear_cache")
+ {
+ LLQtWebKit::getInstance()->clearCache();
+ }
+ else if(message_name == "clear_cookies")
+ {
+ LLQtWebKit::getInstance()->clearAllCookies();
+ }
+ else if(message_name == "enable_cookies")
+ {
+ mCookiesEnabled = message_in.getValueBoolean("enable");
+ LLQtWebKit::getInstance()->enableCookies( mCookiesEnabled );
+ }
+ else if(message_name == "enable_plugins")
+ {
+ mPluginsEnabled = message_in.getValueBoolean("enable");
+ LLQtWebKit::getInstance()->enablePlugins( mPluginsEnabled );
+ }
+ else if(message_name == "enable_javascript")
+ {
+ mJavascriptEnabled = message_in.getValueBoolean("enable");
+ //LLQtWebKit::getInstance()->enableJavascript( mJavascriptEnabled );
+ }
+ else if(message_name == "set_cookies")
+ {
+ LLQtWebKit::getInstance()->setCookies(message_in.getValue("cookies"));
+ }
+ else if(message_name == "proxy_setup")
+ {
+ bool val = message_in.getValueBoolean("enable");
+ std::string host = message_in.getValue("host");
+ int port = message_in.getValueS32("port");
+ LLQtWebKit::getInstance()->enableProxy( val, host, port );
+ }
+ else if(message_name == "browse_stop")
+ {
+ LLQtWebKit::getInstance()->userAction( mBrowserWindowId, LLQtWebKit::UA_NAVIGATE_STOP );
+ }
+ else if(message_name == "browse_reload")
+ {
+ // foo = message_in.getValueBoolean("ignore_cache");
+ LLQtWebKit::getInstance()->userAction( mBrowserWindowId, LLQtWebKit::UA_NAVIGATE_RELOAD );
+ }
+ else if(message_name == "browse_forward")
+ {
+ LLQtWebKit::getInstance()->userAction( mBrowserWindowId, LLQtWebKit::UA_NAVIGATE_FORWARD );
+ }
+ else if(message_name == "browse_back")
+ {
+ LLQtWebKit::getInstance()->userAction( mBrowserWindowId, LLQtWebKit::UA_NAVIGATE_BACK );
+ }
+ else if(message_name == "set_status_redirect")
+ {
+ int code = message_in.getValueS32("code");
+ std::string url = message_in.getValue("url");
+ if ( 404 == code ) // browser lib only supports 404 right now
+ {
+ LLQtWebKit::getInstance()->set404RedirectUrl( mBrowserWindowId, url );
+ };
+ }
+ else if(message_name == "set_user_agent")
+ {
+ mUserAgent = message_in.getValue("user_agent");
+ LLQtWebKit::getInstance()->setBrowserAgentId( mUserAgent );
+ }
+ else if(message_name == "init_history")
+ {
+ // Initialize browser history
+ LLSD history = message_in.getValueLLSD("history");
+ // First, clear the URL history
+ LLQtWebKit::getInstance()->clearHistory(mBrowserWindowId);
+ // Then, add the history items in order
+ LLSD::array_iterator iter_history = history.beginArray();
+ LLSD::array_iterator end_history = history.endArray();
+ for(; iter_history != end_history; ++iter_history)
+ {
+ std::string url = (*iter_history).asString();
+ if(! url.empty()) {
+ LLQtWebKit::getInstance()->prependHistoryUrl(mBrowserWindowId, url);
+ }
+ }
+ }
+ else if(message_name == "proxy_window_opened")
+ {
+ std::string target = message_in.getValue("target");
+ std::string uuid = message_in.getValue("uuid");
+ LLQtWebKit::getInstance()->proxyWindowOpened(mBrowserWindowId, target, uuid);
+ }
+ else if(message_name == "proxy_window_closed")
+ {
+ std::string uuid = message_in.getValue("uuid");
+ LLQtWebKit::getInstance()->proxyWindowClosed(mBrowserWindowId, uuid);
+ }
+ else
+ {
+// std::cerr << "MediaPluginWebKit::receiveMessage: unknown media_browser message: " << message_string << std::endl;
+ };
+ }
+ else
+ {
+// std::cerr << "MediaPluginWebKit::receiveMessage: unknown message class: " << message_class << std::endl;
+ };
+ }
+}
+
+void MediaPluginWebKit::setVolume(F32 volume)
+{
+ mVolumeCatcher.setVolume(volume);
+}
+
+int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data)
+{
+ MediaPluginWebKit *self = new MediaPluginWebKit(host_send_func, host_user_data);
+ *plugin_send_func = MediaPluginWebKit::staticReceiveMessage;
+ *plugin_user_data = (void*)self;
+
+ return 0;
+}
+
diff --git a/indra/media_plugins/webkit/volume_catcher.h b/indra/media_plugins/webkit/volume_catcher.h
new file mode 100644
index 0000000000..337f2913d3
--- /dev/null
+++ b/indra/media_plugins/webkit/volume_catcher.h
@@ -0,0 +1,54 @@
+/**
+ * @file volume_catcher.h
+ * @brief Interface to a class with platform-specific implementations that allows control of the audio volume of all sources in the current process.
+ *
+ * @cond
+ * $LicenseInfo:firstyear=2010&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ * @endcond
+ */
+
+#ifndef VOLUME_CATCHER_H
+#define VOLUME_CATCHER_H
+
+#include "linden_common.h"
+
+class VolumeCatcherImpl;
+
+class VolumeCatcher
+{
+ public:
+ VolumeCatcher();
+ ~VolumeCatcher();
+
+ void setVolume(F32 volume); // 0.0 - 1.0
+
+ // Set the left-right pan of audio sources
+ // where -1.0 = left, 0 = center, and 1.0 = right
+ void setPan(F32 pan);
+
+ void pump(); // call this at least a few times a second if you can - it affects how quickly we can 'catch' a new audio source and adjust its volume
+
+ private:
+ VolumeCatcherImpl *pimpl;
+};
+
+#endif // VOLUME_CATCHER_H
diff --git a/indra/media_plugins/webkit/windows_volume_catcher.cpp b/indra/media_plugins/webkit/windows_volume_catcher.cpp
new file mode 100644
index 0000000000..5fb84756ee
--- /dev/null
+++ b/indra/media_plugins/webkit/windows_volume_catcher.cpp
@@ -0,0 +1,117 @@
+/**
+ * @file windows_volume_catcher.cpp
+ * @brief A Windows implementation of volume level control of all audio channels opened by a process.
+ *
+ * @cond
+ * $LicenseInfo:firstyear=2010&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ * @endcond
+ */
+
+#include "volume_catcher.h"
+#include <windows.h>
+#include "llsingleton.h"
+class VolumeCatcherImpl : public LLSingleton<VolumeCatcherImpl>
+{
+friend LLSingleton<VolumeCatcherImpl>;
+public:
+
+ void setVolume(F32 volume);
+ void setPan(F32 pan);
+
+private:
+ // This is a singleton class -- both callers and the component implementation should use getInstance() to find the instance.
+ VolumeCatcherImpl();
+ ~VolumeCatcherImpl();
+
+ typedef void (WINAPI *set_volume_func_t)(F32);
+ typedef void (WINAPI *set_mute_func_t)(bool);
+
+ set_volume_func_t mSetVolumeFunc;
+ set_mute_func_t mSetMuteFunc;
+
+ F32 mVolume;
+ F32 mPan;
+};
+VolumeCatcherImpl::VolumeCatcherImpl()
+: mVolume(1.0f), // default volume is max
+ mPan(0.f) // default pan is centered
+{
+ HMODULE handle = ::LoadLibrary(L"winmm.dll");
+ if(handle)
+ {
+ mSetVolumeFunc = (set_volume_func_t)::GetProcAddress(handle, "setPluginVolume");
+ mSetMuteFunc = (set_mute_func_t)::GetProcAddress(handle, "setPluginMute");
+ }
+}
+
+VolumeCatcherImpl::~VolumeCatcherImpl()
+{
+}
+
+
+void VolumeCatcherImpl::setVolume(F32 volume)
+{
+ mVolume = volume;
+
+ if (mSetMuteFunc)
+ {
+ mSetMuteFunc(volume == 0.f);
+ }
+ if (mSetVolumeFunc)
+ {
+ mSetVolumeFunc(mVolume);
+ }
+}
+
+void VolumeCatcherImpl::setPan(F32 pan)
+{ // remember pan for calculating individual channel levels later
+ mPan = pan;
+}
+
+/////////////////////////////////////////////////////
+
+VolumeCatcher::VolumeCatcher()
+{
+ pimpl = VolumeCatcherImpl::getInstance();
+}
+
+VolumeCatcher::~VolumeCatcher()
+{
+ // Let the instance persist until exit.
+}
+
+void VolumeCatcher::setVolume(F32 volume)
+{
+ pimpl->setVolume(volume);
+}
+
+void VolumeCatcher::setPan(F32 pan)
+{
+ pimpl->setPan(pan);
+}
+
+void VolumeCatcher::pump()
+{
+ // No periodic tasks are necessary for this implementation.
+}
+
+