diff options
Diffstat (limited to 'indra/media_plugins/webkit')
| -rw-r--r-- | indra/media_plugins/webkit/CMakeLists.txt | 40 | ||||
| -rw-r--r-- | indra/media_plugins/webkit/dummy_volume_catcher.cpp | 63 | ||||
| -rw-r--r-- | indra/media_plugins/webkit/linux_volume_catcher.cpp | 468 | ||||
| -rw-r--r-- | indra/media_plugins/webkit/linux_volume_catcher_pa_syms.inc | 21 | ||||
| -rw-r--r-- | indra/media_plugins/webkit/linux_volume_catcher_paglib_syms.inc | 6 | ||||
| -rw-r--r-- | indra/media_plugins/webkit/mac_volume_catcher.cpp | 273 | ||||
| -rw-r--r-- | indra/media_plugins/webkit/media_plugin_webkit.cpp | 484 | ||||
| -rw-r--r-- | indra/media_plugins/webkit/volume_catcher.h | 59 | ||||
| -rw-r--r-- | indra/media_plugins/webkit/windows_volume_catcher.cpp | 122 | 
9 files changed, 1361 insertions, 175 deletions
| diff --git a/indra/media_plugins/webkit/CMakeLists.txt b/indra/media_plugins/webkit/CMakeLists.txt index 812760a116..d576638dd7 100644 --- a/indra/media_plugins/webkit/CMakeLists.txt +++ b/indra/media_plugins/webkit/CMakeLists.txt @@ -14,10 +14,12 @@ 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} @@ -34,10 +36,9 @@ set(media_plugin_webkit_SOURCE_FILES      media_plugin_webkit.cpp      ) -add_library(media_plugin_webkit -    SHARED -    ${media_plugin_webkit_SOURCE_FILES} -) +set(media_plugin_webkit_HEADER_FILES +    volume_catcher.h +    )  set(media_plugin_webkit_LINK_LIBRARIES    ${LLPLUGIN_LIBRARIES} @@ -45,14 +46,42 @@ set(media_plugin_webkit_LINK_LIBRARIES    ${LLCOMMON_LIBRARIES}    ${WEBKIT_PLUGIN_LIBRARIES}    ${PLUGIN_API_WINDOWS_LIBRARIES} +  ${PULSEAUDIO_LIBRARIES}  ) +# Select which VolumeCatcher implementation to use  if (LINUX) +  if (PULSEAUDIO) +    list(APPEND media_plugin_webkit_SOURCE_FILES linux_volume_catcher.cpp) +  endif (PULSEAUDIO)    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) +else (LINUX) +  # All other platforms use the dummy volume catcher for now. +  list(APPEND media_plugin_webkit_SOURCE_FILES dummy_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 @@ -88,4 +117,5 @@ if (DARWIN)      DEPENDS media_plugin_webkit ${CMAKE_SOURCE_DIR}/../libraries/universal-darwin/lib_release/libllqtwebkit.dylib      ) -endif (DARWIN)
\ No newline at end of file +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..45b2c62eba --- /dev/null +++ b/indra/media_plugins/webkit/dummy_volume_catcher.cpp @@ -0,0 +1,63 @@ +/**  + * @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=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + * @endcond + */ + +#include "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..2e7fda865e --- /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=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + * @endcond + */ + +/* +  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 <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; + +	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..38727e5965 --- /dev/null +++ b/indra/media_plugins/webkit/mac_volume_catcher.cpp @@ -0,0 +1,273 @@ +/**  + * @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=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + * @endcond + */ + +/************************************************************************************************************** +	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 index b607d2f66a..a9ff7bf752 100644 --- a/indra/media_plugins/webkit/media_plugin_webkit.cpp +++ b/indra/media_plugins/webkit/media_plugin_webkit.cpp @@ -43,12 +43,18 @@  #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>  } +#else +# define LL_QTWEBKIT_USES_PIXMAPS 0  #endif // LL_LINUX +# include "volume_catcher.h" +  #if LL_WINDOWS  # include <direct.h>  #else @@ -83,10 +89,16 @@ public:  private:  	std::string mProfileDir; +	std::string mHostLanguage; +	std::string mUserAgent; +	bool mCookiesEnabled; +	bool mJavascriptEnabled; +	bool mPluginsEnabled;  	enum  	{ -		INIT_STATE_UNINITIALIZED,		// Browser instance hasn't been set up yet +		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 @@ -108,6 +120,8 @@ private:  	F32 mBackgroundG;  	F32 mBackgroundB; +	VolumeCatcher mVolumeCatcher; +  	void setInitState(int state)  	{  //		std::cerr << "changing init state to " << state << std::endl; @@ -130,6 +144,8 @@ private:  		// pump qt  		LLQtWebKit::getInstance()->pump( milliseconds ); +		mVolumeCatcher.pump(); +  		checkEditState();  		if(mInitState == INIT_STATE_NAVIGATE_COMPLETE) @@ -146,7 +162,11 @@ private:  		{  			const unsigned char* browser_pixels = LLQtWebKit::getInstance()->grabBrowserWindow( mBrowserWindowId ); -			unsigned int buffer_size = LLQtWebKit::getInstance()->getBrowserRowSpan( mBrowserWindowId ) * LLQtWebKit::getInstance()->getBrowserHeight( 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; @@ -154,7 +174,16 @@ private:  			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 ) @@ -175,13 +204,6 @@ private:  		if ( mInitState > INIT_STATE_UNINITIALIZED )  			return true; -		// not enough information to initialize the browser yet. -		if ( mWidth < 0 || mHeight < 0 || mDepth < 0 ||  -				mTextureWidth < 0 || mTextureHeight < 0 ) -		{ -			return false; -		}; -  		// set up directories  		char cwd[ FILENAME_MAX ];	// I *think* this is defined on all platforms we use  		if (NULL == getcwd( cwd, FILENAME_MAX - 1 )) @@ -192,12 +214,12 @@ private:  		std::string application_dir = std::string( cwd );  #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"); +		// 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 @@ -238,65 +260,94 @@ private:  		bool result = LLQtWebKit::getInstance()->init( application_dir, component_dir, mProfileDir, native_window_handle );  		if ( result )  		{ -			// create single browser window -			mBrowserWindowId = LLQtWebKit::getInstance()->createBrowserWindow( mWidth, mHeight ); -#if LL_WINDOWS -			// Enable plugins -			LLQtWebKit::getInstance()->enablePlugins(true); -#elif LL_DARWIN -			// Enable plugins -			LLQtWebKit::getInstance()->enablePlugins(true); -#elif LL_LINUX -			// Enable plugins -			LLQtWebKit::getInstance()->enablePlugins(true); -#endif -			// Enable cookies -			LLQtWebKit::getInstance()->enableCookies( true ); - -			// 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( "LLPluginMedia Web Browser" ); - -			// don't flip bitmap -			LLQtWebKit::getInstance()->flipWindow( mBrowserWindowId, true ); -			 -			// 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; +			mInitState = INIT_STATE_INITIALIZED; -			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;  		};  		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 ); + +		// 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 ); + +		// Set up window open behavior +		LLQtWebKit::getInstance()->setWindowOpenBehavior(mBrowserWindowId, LLQtWebKit::WOB_SIMULATE_BLANK_HREF_CLICK); +		 +#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 @@ -467,6 +518,19 @@ private:  		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); +	} +	  	LLQtWebKit::EKeyboardModifier decodeModifiers(std::string &modifiers)  	{  		int result = 0; @@ -486,92 +550,96 @@ private:  		return (LLQtWebKit::EKeyboardModifier)result;  	} -  	////////////////////////////////////////////////////////////////////////////////  	// -	void keyEvent(LLQtWebKit::EKeyEvent key_event, int key, LLQtWebKit::EKeyboardModifier modifiers) +	void deserializeKeyboardData( LLSD native_key_data, uint32_t& native_scan_code, uint32_t& native_virtual_key, uint32_t& native_modifiers )  	{ -		int llqt_key; +		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 -		// the outgoing values are the ones from llqtwebkit.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)  		{ -			// This is the list that the llqtwebkit implementation actually maps into Qt keys. -//			case KEY_XXX:			llqt_key = LL_DOM_VK_CANCEL;			break; -//			case KEY_XXX:			llqt_key = LL_DOM_VK_HELP;			break; -			case KEY_BACKSPACE:		llqt_key = LL_DOM_VK_BACK_SPACE;		break; -			case KEY_TAB:			llqt_key = LL_DOM_VK_TAB;			break; -//			case KEY_XXX:			llqt_key = LL_DOM_VK_CLEAR;			break; -			case KEY_RETURN:		llqt_key = LL_DOM_VK_RETURN;			break; -			case KEY_PAD_RETURN:	llqt_key = LL_DOM_VK_ENTER;			break; -			case KEY_SHIFT:			llqt_key = LL_DOM_VK_SHIFT;			break; -			case KEY_CONTROL:		llqt_key = LL_DOM_VK_CONTROL;		break; -			case KEY_ALT:			llqt_key = LL_DOM_VK_ALT;			break; -//			case KEY_XXX:			llqt_key = LL_DOM_VK_PAUSE;			break; -			case KEY_CAPSLOCK:		llqt_key = LL_DOM_VK_CAPS_LOCK;		break; -			case KEY_ESCAPE:		llqt_key = LL_DOM_VK_ESCAPE;			break; -			case KEY_PAGE_UP:		llqt_key = LL_DOM_VK_PAGE_UP;		break; -			case KEY_PAGE_DOWN:		llqt_key = LL_DOM_VK_PAGE_DOWN;		break; -			case KEY_END:			llqt_key = LL_DOM_VK_END;			break; -			case KEY_HOME:			llqt_key = LL_DOM_VK_HOME;			break; -			case KEY_LEFT:			llqt_key = LL_DOM_VK_LEFT;			break; -			case KEY_UP:			llqt_key = LL_DOM_VK_UP;				break; -			case KEY_RIGHT:			llqt_key = LL_DOM_VK_RIGHT;			break; -			case KEY_DOWN:			llqt_key = LL_DOM_VK_DOWN;			break; -//			case KEY_XXX:			llqt_key = LL_DOM_VK_PRINTSCREEN;	break; -			case KEY_INSERT:		llqt_key = LL_DOM_VK_INSERT;			break; -			case KEY_DELETE:		llqt_key = LL_DOM_VK_DELETE;			break; -//			case KEY_XXX:			llqt_key = LL_DOM_VK_CONTEXT_MENU;	break; +			// 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: -				if(key < KEY_SPECIAL) -				{ -					// Pass the incoming key through -- it should be regular ASCII, which should be correct for webkit. -					llqt_key = key; -				} -				else -				{ -					// Don't pass through untranslated special keys -- they'll be all wrong. -					llqt_key = 0; -				} +			default:    			break;  		} -//		std::cerr << "keypress, original code = 0x" << std::hex << key << ", converted code = 0x" << std::hex << llqt_key << std::dec << std::endl; +//		std::cerr << "key event " << (int)key_event << ", native_key_data = " << native_key_data << std::endl; -		if(llqt_key != 0) -		{ -			LLQtWebKit::getInstance()->keyEvent( mBrowserWindowId, key_event, llqt_key, modifiers); -		} +		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) -	{ -		LLWString wstr = utf8str_to_wstring(utf8str); +	void unicodeInput( const std::string &utf8str, LLQtWebKit::EKeyboardModifier modifiers, LLSD native_key_data = LLSD::emptyMap()) +	{		 +		uint32_t key = LLQtWebKit::KEY_NONE; -		unsigned int i; -		for(i=0; i < wstr.size(); i++) +//		std::cerr << "unicode input, native_key_data = " << native_key_data << std::endl; +		 +		if(utf8str.size() == 1)  		{ -//			std::cerr << "unicode input, code = 0x" << std::hex << (unsigned long)(wstr[i]) << std::dec << std::endl; -			 -			if(wstr[i] == 32) -			{ -				// For some reason, the webkit plugin really wants the space bar to come in through the key-event path, not the unicode path. -				LLQtWebKit::getInstance()->keyEvent( mBrowserWindowId, LLQtWebKit::KE_KEY_DOWN, 32, modifiers); -				LLQtWebKit::getInstance()->keyEvent( mBrowserWindowId, LLQtWebKit::KE_KEY_UP, 32, modifiers); -			} -			else -			{ -				LLQtWebKit::getInstance()->unicodeInput(mBrowserWindowId, wstr[i], modifiers); -			} +			// 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();  	}; @@ -627,6 +695,11 @@ MediaPluginWebKit::MediaPluginWebKit(LLPluginInstance::sendMessageFunction host_  	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() @@ -653,9 +726,6 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)  		{  			if(message_name == "init")  			{ -				std::string user_data_path = message_in.getValue("user_data_path"); // n.b. always has trailing platform-specific dir-delimiter -				mProfileDir = user_data_path + "browser_profile"; -  				LLPluginMessage message("base", "init_response");  				LLSD versions = LLSD::emptyMap();  				versions[LLPLUGIN_MESSAGE_CLASS_BASE] = LLPLUGIN_MESSAGE_CLASS_BASE_VERSION; @@ -667,19 +737,6 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)  				plugin_version += LLQtWebKit::getInstance()->getVersion();  				message.setValue("plugin_version", plugin_version);  				sendMessage(message); -				 -				// Plugin gets to decide the texture parameters to use. -				mDepth = 4; - -				message.setMessage(LLPLUGIN_MESSAGE_CLASS_MEDIA, "texture_params"); -				message.setValueS32("default_width", 1024); -				message.setValueS32("default_height", 1024); -				message.setValueS32("depth", mDepth); -				message.setValueU32("internalformat", GL_RGBA); -				message.setValueU32("format", GL_RGBA); -				message.setValueU32("type", GL_UNSIGNED_BYTE); -				message.setValueBoolean("coords_opengl", true); -				sendMessage(message);  			}  			else if(message_name == "idle")  			{ @@ -744,9 +801,68 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)  //				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 == "size_change") +			if(message_name == "init") +			{ +				// 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"); @@ -768,29 +884,36 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)  						mWidth = width;  						mHeight = height; -						// initialize (only gets called once) -						initBrowser(); - -						// 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) +						if(initBrowserWindow())  						{ -							texture_width = real_width; + +							// 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  						{ -							// 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; +							// Setting up the browser window failed.  This is a fatal error.  							mDeleteMe = true; -							return;  						} +  						mTextureWidth = texture_width;  						mTextureHeight = texture_height; @@ -871,6 +994,7 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)  				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; @@ -883,14 +1007,15 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)  					key_event = LLQtWebKit::KE_KEY_REPEAT;  				} -				keyEvent(key_event, key, decodeModifiers(modifiers)); +				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)); +				unicodeInput(text, decodeModifiers(modifiers), native_key_data);  			}  			if(message_name == "edit_cut")  			{ @@ -938,8 +1063,22 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)  			}  			else if(message_name == "enable_cookies")  			{ -				bool val = message_in.getValueBoolean("enable"); -				LLQtWebKit::getInstance()->enableCookies( val ); +				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")  			{ @@ -976,8 +1115,8 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)  			}  			else if(message_name == "set_user_agent")  			{ -				std::string user_agent = message_in.getValue("user_agent"); -				LLQtWebKit::getInstance()->setBrowserAgentId( user_agent ); +				mUserAgent = message_in.getValue("user_agent"); +				LLQtWebKit::getInstance()->setBrowserAgentId( mUserAgent );  			}  			else if(message_name == "init_history")  			{ @@ -1008,6 +1147,11 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)  	}  } +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); diff --git a/indra/media_plugins/webkit/volume_catcher.h b/indra/media_plugins/webkit/volume_catcher.h new file mode 100644 index 0000000000..77b10cfed0 --- /dev/null +++ b/indra/media_plugins/webkit/volume_catcher.h @@ -0,0 +1,59 @@ +/**  + * @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=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + * @endcond + */ + +#ifndef 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..ef96102a0a --- /dev/null +++ b/indra/media_plugins/webkit/windows_volume_catcher.cpp @@ -0,0 +1,122 @@ +/**  + * @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=viewergpl$ + * + * Copyright (c) 2010, Linden Research, Inc. + *  + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab.  Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + *  + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + *  + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + *  + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + * @endcond + */ + +#include "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. +} + + | 
