Newer webkit plugin

This commit is contained in:
siana
2010-12-24 01:49:01 +01:00
parent 9417eadb4c
commit 80fe022718
11 changed files with 1457 additions and 185 deletions

28
indra/cmake/PulseAudio.cmake Executable file
View File

@@ -0,0 +1,28 @@
# -*- cmake -*-
include(Prebuilt)
set(PULSEAUDIO ON CACHE BOOL "Build with PulseAudio support, if available.")
if (PULSEAUDIO)
if (STANDALONE)
include(FindPkgConfig)
pkg_check_modules(PULSEAUDIO libpulse)
elseif (LINUX)
use_prebuilt_binary(pulseaudio)
set(PULSEAUDIO_FOUND ON FORCE BOOL)
set(PULSEAUDIO_INCLUDE_DIRS
${LIBS_PREBUILT_DIR}/include
)
# We don't need to explicitly link against pulseaudio itself, because
# the viewer probes for the system's copy at runtime.
set(PULSEAUDIO_LIBRARIES
# none needed!
)
endif (STANDALONE)
endif (PULSEAUDIO)
if (PULSEAUDIO_FOUND)
add_definitions(-DLL_PULSEAUDIO_ENABLED=1)
endif (PULSEAUDIO_FOUND)

View File

@@ -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}
@@ -25,23 +27,26 @@ include_directories(
${LLIMAGE_INCLUDE_DIRS}
${LLRENDER_INCLUDE_DIRS}
${LLWINDOW_INCLUDE_DIRS}
${LLQTWEBKIT_INCLUDE_DIR}
)
### 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
)
if(WORD_SIZE EQUAL 64)
if(WINDOWS)
add_definitions(/FIXED:NO)
else(WINDOWS) # not windows therefore gcc LINUX and DARWIN
add_definitions(-fPIC)
endif(WINDOWS)
endif (WORD_SIZE EQUAL 64)
set(media_plugin_webkit_HEADER_FILES
volume_catcher.h
)
set(media_plugin_webkit_LINK_LIBRARIES
${LLPLUGIN_LIBRARIES}
@@ -49,14 +54,35 @@ set(media_plugin_webkit_LINK_LIBRARIES
${LLCOMMON_LIBRARIES}
${WEBKIT_PLUGIN_LIBRARIES}
${PLUGIN_API_WINDOWS_LIBRARIES}
${PULSEAUDIO_LIBRARIES}
)
if(LINUX)
# 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
)
endif(LINUX)
${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
@@ -99,3 +125,4 @@ if (DARWIN)
)
endif (DARWIN)

View File

@@ -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()
{
}

View File

@@ -0,0 +1,469 @@
/**
* @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 <set>
#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();
}

View File

@@ -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

View File

@@ -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

View File

@@ -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.
}

View File

@@ -2,9 +2,10 @@
* @file media_plugin_webkit.cpp
* @brief Webkit plugin for LLMedia API plugin system
*
* @cond
* $LicenseInfo:firstyear=2008&license=viewergpl$
*
* Copyright (c) 2008-2009, Linden Research, Inc.
* Copyright (c) 2008-2010, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
@@ -12,13 +13,13 @@
* ("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://secondlifegrid.net/programs/open_source/licensing/gplv2
* 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://secondlifegrid.net/programs/open_source/licensing/flossexception
* 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,
@@ -28,6 +29,8 @@
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*
* @endcond
*/
#include "llqtwebkit.h"
@@ -42,22 +45,29 @@
#include "llpluginmessageclasses.h"
#include "media_plugin_base.h"
#if LL_WINDOWS
#include <direct.h>
#else
#include <unistd.h>
#include <stdlib.h>
#endif
// 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>
# include <glib.h>
# include <glib-object.h>
}
#else
# define LL_QTWEBKIT_USES_PIXMAPS 0
#endif // LL_LINUX
# include "volume_catcher.h"
#if LL_WINDOWS
// NOTE - This captures the module handle of the dll. This is used below
# include <direct.h>
#else
# include <unistd.h>
# include <stdlib.h>
#endif
#include <iomanip>
#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;};
@@ -83,10 +93,15 @@ 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
@@ -105,7 +120,13 @@ private:
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;
@@ -128,6 +149,8 @@ private:
// pump qt
LLQtWebKit::getInstance()->pump( milliseconds );
mVolumeCatcher.pump();
checkEditState();
if(mInitState == INIT_STATE_NAVIGATE_COMPLETE)
@@ -140,11 +163,15 @@ private:
}
}
if (mInitState > INIT_STATE_WAIT_REDRAW && mNeedsUpdate)
if ( (mInitState > INIT_STATE_WAIT_REDRAW) && mNeedsUpdate )
{
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;
@@ -152,7 +179,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 )
@@ -188,7 +224,7 @@ private:
// versions of) Flash to not crash the browser.
if (!g_thread_supported ()) g_thread_init (NULL);
g_type_init();
#endif // LL_LINUX
#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.
@@ -200,7 +236,7 @@ private:
#endif
#if LL_WINDOWS
// NOTE - On windows, at least, the component path is the
//*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];
@@ -216,8 +252,8 @@ private:
}
else
{
// NOTE - This case should be a rare exception.
// GetModuleFileNameA should always give you a full path.
// *NOTE:Mani - This case should be an rare exception.
// GetModuleFileNameA should always give you a full path, no?
component_dir = application_dir;
}
#else
@@ -238,7 +274,7 @@ private:
if ( result )
{
mInitState = INIT_STATE_INITIALIZED;
return true;
};
@@ -250,50 +286,78 @@ private:
bool initBrowserWindow()
{
// already initialized
if (mInitState > INIT_STATE_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)
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(true);
LLQtWebKit::getInstance()->enableCookies( mCookiesEnabled );
// turn on/off plugins based on what host app tells us
LLQtWebKit::getInstance()->enablePlugins(true);
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);
mBrowserWindowId = LLQtWebKit::getInstance()->createBrowserWindow( mWidth, mHeight, mTarget);
// tell LLQtWebKit about the size of the browser window
LLQtWebKit::getInstance()->setSize(mBrowserWindowId, 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);
// observer events that LLQtWebKit emits
LLQtWebKit::getInstance()->addObserver( mBrowserWindowId, this );
// append details to agent string
LLQtWebKit::getInstance()->setBrowserAgentId("LLPluginMedia Web Browser");
// 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
// don't flip bitmap
LLQtWebKit::getInstance()->flipWindow(mBrowserWindowId, true);
// set background color to be black - mostly for initial login page
LLQtWebKit::getInstance()->setBackgroundColor(mBrowserWindowId, 0x00, 0x00, 0x00);
// 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);
// 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.
LLQtWebKit::getInstance()->navigateTo(mBrowserWindowId, "about:blank");
// 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;
};
return true;
}
void setVolume(F32 vol);
////////////////////////////////////////////////////////////////////////////////
// virtual
@@ -371,14 +435,14 @@ private:
{
if(mInitState >= INIT_STATE_NAVIGATE_COMPLETE)
{
if (mInitState < INIT_STATE_RUNNING)
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());
@@ -449,8 +513,9 @@ private:
void onClickLinkHref(const EventType& event)
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "click_href");
message.setValue("uri", event.getStringValue());
message.setValue("target", event.getStringValue2());
message.setValue("uri", event.getEventUri());
message.setValue("target", event.getStringValue());
message.setValue("uuid", event.getStringValue2());
sendMessage(message);
}
@@ -459,10 +524,59 @@ private:
void onClickLinkNoFollow(const EventType& event)
{
LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "click_nofollow");
message.setValue("uri", event.getStringValue());
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;
@@ -482,92 +596,96 @@ private:
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)
void keyEvent(LLQtWebKit::EKeyEvent key_event, int key, LLQtWebKit::EKeyboardModifier modifiers, LLSD native_key_data = LLSD::emptyMap())
{
int llqt_key;
// 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();
};
@@ -603,6 +721,26 @@ private:
}
}
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;
}
};
@@ -620,6 +758,14 @@ MediaPluginWebKit::MediaPluginWebKit(LLPluginInstance::sendMessageFunction host_
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()
@@ -646,12 +792,6 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)
{
if(message_name == "init")
{
// This is the media init message -- all necessary data for initialization should have been received.
if (initBrowser())
{
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;
@@ -663,25 +803,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 initialization failed, we're done.
mDeleteMe = true;
}
}
else if(message_name == "idle")
{
@@ -706,7 +827,6 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)
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
@@ -747,16 +867,81 @@ 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")
{
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
@@ -767,37 +952,40 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)
mWidth = width;
mHeight = height;
if (initBrowserWindow())
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;
}
// 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;
};
};
@@ -874,6 +1062,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;
@@ -886,14 +1075,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")
{
@@ -910,10 +1100,14 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)
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)
{
@@ -941,8 +1135,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")
{
@@ -979,8 +1187,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")
{
@@ -999,6 +1207,17 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)
}
}
}
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;
@@ -1011,6 +1230,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);

View File

@@ -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

View File

@@ -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 "llmemory.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.
}

View File

@@ -884,9 +884,9 @@ anguage Infrstructure (CLI) international standard</string>
<key>windows</key>
<map>
<key>md5sum</key>
<string>92cff05661b5547caae7cc6c66d09870</string>
<string>fa5041874761895a06a20782b8ba34a2</string>
<key>url</key>
<uri>http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/llqtwebkit-windows-20091123.tar.bz2</uri>
<uri>http://liny-odell.users.sourceforge.net/llqtwebkit-windows-qt4.6-20101122.tar.bz2</uri>
</map>
</map>
</map>