aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra
diff options
context:
space:
mode:
Diffstat (limited to 'linden/indra')
-rw-r--r--linden/indra/cmake/PulseAudio.cmake28
-rw-r--r--linden/indra/llplugin/CMakeLists.txt19
-rwxr-xr-xlinden/indra/llplugin/llpluginclassmedia.cpp26
-rwxr-xr-xlinden/indra/llplugin/llpluginclassmedia.h9
-rwxr-xr-xlinden/indra/llplugin/llpluginclassmediaowner.h2
-rw-r--r--linden/indra/llplugin/llplugincookiestore.cpp671
-rw-r--r--linden/indra/llplugin/llplugincookiestore.h127
-rwxr-xr-xlinden/indra/llplugin/llpluginmessagepipe.cpp121
-rwxr-xr-xlinden/indra/llplugin/llpluginmessagepipe.h9
-rwxr-xr-xlinden/indra/llplugin/llpluginprocesschild.cpp77
-rwxr-xr-xlinden/indra/llplugin/llpluginprocesschild.h7
-rwxr-xr-xlinden/indra/llplugin/llpluginprocessparent.cpp455
-rwxr-xr-xlinden/indra/llplugin/llpluginprocessparent.h32
-rwxr-xr-xlinden/indra/llplugin/slplugin/CMakeLists.txt39
-rw-r--r--linden/indra/llplugin/slplugin/slplugin-objc.h42
-rw-r--r--linden/indra/llplugin/slplugin/slplugin-objc.mm89
-rwxr-xr-xlinden/indra/llplugin/slplugin/slplugin.cpp111
-rwxr-xr-xlinden/indra/llplugin/slplugin/slplugin_info.plist4
-rw-r--r--linden/indra/media_plugins/webkit/CMakeLists.txt35
-rw-r--r--linden/indra/media_plugins/webkit/dummy_volume_catcher.cpp65
-rw-r--r--[-rwxr-xr-x]linden/indra/media_plugins/webkit/linux_volume_catcher.cpp79
-rw-r--r--[-rwxr-xr-x]linden/indra/media_plugins/webkit/linux_volume_catcher_pa_syms.inc0
-rw-r--r--[-rwxr-xr-x]linden/indra/media_plugins/webkit/linux_volume_catcher_paglib_syms.inc0
-rw-r--r--linden/indra/media_plugins/webkit/mac_volume_catcher.cpp275
-rwxr-xr-xlinden/indra/media_plugins/webkit/media_plugin_webkit.cpp62
-rw-r--r--linden/indra/media_plugins/webkit/volume_catcher.h61
-rw-r--r--linden/indra/media_plugins/webkit/windows_volume_catcher.cpp124
27 files changed, 2420 insertions, 149 deletions
diff --git a/linden/indra/cmake/PulseAudio.cmake b/linden/indra/cmake/PulseAudio.cmake
new file mode 100644
index 0000000..f8087a8
--- /dev/null
+++ b/linden/indra/cmake/PulseAudio.cmake
@@ -0,0 +1,28 @@
1# -*- cmake -*-
2include(Prebuilt)
3
4if (STANDALONE)
5 include(FindPkgConfig)
6
7 pkg_check_modules(PULSEAUDIO REQUIRED libpulse-mainloop-glib)
8
9elseif (LINUX)
10 use_prebuilt_binary(pulseaudio)
11 set(PULSEAUDIO_FOUND ON FORCE BOOL)
12 set(PULSEAUDIO_INCLUDE_DIRS
13 ${LIBS_PREBUILT_DIR}/include
14 )
15 # We don't need to explicitly link against pulseaudio itself, because
16 # the viewer probes for the system's copy at runtime.
17 set(PULSEAUDIO_LIBRARIES
18 # none needed!
19 )
20endif (STANDALONE)
21
22if (PULSEAUDIO_FOUND)
23 set(PULSEAUDIO ON CACHE BOOL "Build with PulseAudio support, if available.")
24endif (PULSEAUDIO_FOUND)
25
26if (PULSEAUDIO)
27 add_definitions(-DLL_PULSEAUDIO_ENABLED=1)
28endif (PULSEAUDIO)
diff --git a/linden/indra/llplugin/CMakeLists.txt b/linden/indra/llplugin/CMakeLists.txt
index 8a2eff8..8eead94 100644
--- a/linden/indra/llplugin/CMakeLists.txt
+++ b/linden/indra/llplugin/CMakeLists.txt
@@ -7,6 +7,7 @@ if(HAVE_64_BIT)
7endif(HAVE_64_BIT) 7endif(HAVE_64_BIT)
8 8
9include(00-Common) 9include(00-Common)
10include(CURL)
10include(LLCommon) 11include(LLCommon)
11include(LLImage) 12include(LLImage)
12include(LLMath) 13include(LLMath)
@@ -27,6 +28,7 @@ include_directories(
27 28
28set(llplugin_SOURCE_FILES 29set(llplugin_SOURCE_FILES
29 llpluginclassmedia.cpp 30 llpluginclassmedia.cpp
31 llplugincookiestore.cpp
30 llplugininstance.cpp 32 llplugininstance.cpp
31 llpluginmessage.cpp 33 llpluginmessage.cpp
32 llpluginmessagepipe.cpp 34 llpluginmessagepipe.cpp
@@ -40,6 +42,7 @@ set(llplugin_HEADER_FILES
40 42
41 llpluginclassmedia.h 43 llpluginclassmedia.h
42 llpluginclassmediaowner.h 44 llpluginclassmediaowner.h
45 llplugincookiestore.h
43 llplugininstance.h 46 llplugininstance.h
44 llpluginmessage.h 47 llpluginmessage.h
45 llpluginmessageclasses.h 48 llpluginmessageclasses.h
@@ -65,3 +68,19 @@ list(APPEND llplugin_SOURCE_FILES ${llplugin_HEADER_FILES})
65add_library (llplugin ${llplugin_SOURCE_FILES}) 68add_library (llplugin ${llplugin_SOURCE_FILES})
66 69
67add_subdirectory(slplugin) 70add_subdirectory(slplugin)
71
72# # Add tests
73# include(LLAddBuildTest)
74# # UNIT TESTS
75# SET(llplugin_TEST_SOURCE_FILES
76# llplugincookiestore.cpp
77# )
78#
79# # llplugincookiestore has a dependency on curl, so we need to link the curl library into the test.
80# set_source_files_properties(
81# llplugincookiestore.cpp
82# PROPERTIES
83# LL_TEST_ADDITIONAL_LIBRARIES "${CURL_LIBRARIES}"
84# )
85#
86# LL_ADD_PROJECT_UNIT_TESTS(llplugin "${llplugin_TEST_SOURCE_FILES}") \ No newline at end of file
diff --git a/linden/indra/llplugin/llpluginclassmedia.cpp b/linden/indra/llplugin/llpluginclassmedia.cpp
index b958f0c..8664524 100755
--- a/linden/indra/llplugin/llpluginclassmedia.cpp
+++ b/linden/indra/llplugin/llpluginclassmedia.cpp
@@ -59,11 +59,15 @@ LLPluginClassMedia::LLPluginClassMedia(LLPluginClassMediaOwner *owner)
59 mOwner = owner; 59 mOwner = owner;
60 mPlugin = NULL; 60 mPlugin = NULL;
61 reset(); 61 reset();
62
63 //debug use
64 mDeleteOK = true ;
62} 65}
63 66
64 67
65LLPluginClassMedia::~LLPluginClassMedia() 68LLPluginClassMedia::~LLPluginClassMedia()
66{ 69{
70 llassert_always(mDeleteOK) ;
67 reset(); 71 reset();
68} 72}
69 73
@@ -162,7 +166,7 @@ void LLPluginClassMedia::idle(void)
162 mPlugin->idle(); 166 mPlugin->idle();
163 } 167 }
164 168
165 if((mMediaWidth == -1) || (!mTextureParamsReceived) || (mPlugin == NULL)) 169 if((mMediaWidth == -1) || (!mTextureParamsReceived) || (mPlugin == NULL) || (mPlugin->isBlocked()))
166 { 170 {
167 // Can't process a size change at this time 171 // Can't process a size change at this time
168 } 172 }
@@ -439,6 +443,12 @@ void LLPluginClassMedia::mouseEvent(EMouseEventType type, int button, int x, int
439{ 443{
440 if(type == MOUSE_EVENT_MOVE) 444 if(type == MOUSE_EVENT_MOVE)
441 { 445 {
446 if(!mPlugin || !mPlugin->isRunning() || mPlugin->isBlocked())
447 {
448 // Don't queue up mouse move events that can't be delivered.
449 return;
450 }
451
442 if((x == mLastMouseX) && (y == mLastMouseY)) 452 if((x == mLastMouseX) && (y == mLastMouseY))
443 { 453 {
444 // Don't spam unnecessary mouse move events. 454 // Don't spam unnecessary mouse move events.
@@ -995,6 +1005,13 @@ void LLPluginClassMedia::receivePluginMessage(const LLPluginMessage &message)
995 mClickTargetType = TARGET_NONE; 1005 mClickTargetType = TARGET_NONE;
996 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_NOFOLLOW); 1006 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_NOFOLLOW);
997 } 1007 }
1008 else if(message_name == "cookie_set")
1009 {
1010 if(mOwner)
1011 {
1012 mOwner->handleCookieSet(this, message.getValue("cookie"));
1013 }
1014 }
998 else 1015 else
999 { 1016 {
1000 LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL; 1017 LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL;
@@ -1078,6 +1095,13 @@ void LLPluginClassMedia::clear_cookies()
1078 sendMessage(message); 1095 sendMessage(message);
1079} 1096}
1080 1097
1098void LLPluginClassMedia::set_cookies(const std::string &cookies)
1099{
1100 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "set_cookies");
1101 message.setValue("cookies", cookies);
1102 sendMessage(message);
1103}
1104
1081void LLPluginClassMedia::enable_cookies(bool enable) 1105void LLPluginClassMedia::enable_cookies(bool enable)
1082{ 1106{
1083 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "enable_cookies"); 1107 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "enable_cookies");
diff --git a/linden/indra/llplugin/llpluginclassmedia.h b/linden/indra/llplugin/llpluginclassmedia.h
index fc94563..abb7926 100755
--- a/linden/indra/llplugin/llpluginclassmedia.h
+++ b/linden/indra/llplugin/llpluginclassmedia.h
@@ -191,6 +191,7 @@ public:
191 void focus(bool focused); 191 void focus(bool focused);
192 void clear_cache(); 192 void clear_cache();
193 void clear_cookies(); 193 void clear_cookies();
194 void set_cookies(const std::string &cookies);
194 void enable_cookies(bool enable); 195 void enable_cookies(bool enable);
195 void proxy_setup(bool enable, const std::string &host = LLStringUtil::null, int port = 0); 196 void proxy_setup(bool enable, const std::string &host = LLStringUtil::null, int port = 0);
196 void browse_stop(); 197 void browse_stop();
@@ -374,6 +375,14 @@ protected:
374 F64 mCurrentRate; 375 F64 mCurrentRate;
375 F64 mLoadedDuration; 376 F64 mLoadedDuration;
376 377
378//--------------------------------------
379 //debug use only
380 //
381private:
382 bool mDeleteOK ;
383public:
384 void setDeleteOK(bool flag) { mDeleteOK = flag ;}
385//--------------------------------------
377}; 386};
378 387
379#endif // LL_LLPLUGINCLASSMEDIA_H 388#endif // LL_LLPLUGINCLASSMEDIA_H
diff --git a/linden/indra/llplugin/llpluginclassmediaowner.h b/linden/indra/llplugin/llpluginclassmediaowner.h
index c1f6ae1..9d1f352 100755
--- a/linden/indra/llplugin/llpluginclassmediaowner.h
+++ b/linden/indra/llplugin/llpluginclassmediaowner.h
@@ -41,6 +41,7 @@
41#include <queue> 41#include <queue>
42 42
43class LLPluginClassMedia; 43class LLPluginClassMedia;
44class LLPluginCookieStore;
44 45
45class LLPluginClassMediaOwner 46class LLPluginClassMediaOwner
46{ 47{
@@ -80,6 +81,7 @@ public:
80 81
81 virtual ~LLPluginClassMediaOwner() {}; 82 virtual ~LLPluginClassMediaOwner() {};
82 virtual void handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent /*event*/) {}; 83 virtual void handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent /*event*/) {};
84 virtual void handleCookieSet(LLPluginClassMedia* /*self*/, const std::string &/*cookie*/) {};
83}; 85};
84 86
85#endif // LL_LLPLUGINCLASSMEDIAOWNER_H 87#endif // LL_LLPLUGINCLASSMEDIAOWNER_H
diff --git a/linden/indra/llplugin/llplugincookiestore.cpp b/linden/indra/llplugin/llplugincookiestore.cpp
new file mode 100644
index 0000000..283ba35
--- /dev/null
+++ b/linden/indra/llplugin/llplugincookiestore.cpp
@@ -0,0 +1,671 @@
1/**
2 * @file llplugincookiestore.cpp
3 * @brief LLPluginCookieStore provides central storage for http cookies used by plugins
4 *
5 * @cond
6 * $LicenseInfo:firstyear=2010&license=viewergpl$
7 *
8 * Copyright (c) 2010, Linden Research, Inc.
9 *
10 * Second Life Viewer Source Code
11 * The source code in this file ("Source Code") is provided by Linden Lab
12 * to you under the terms of the GNU General Public License, version 2.0
13 * ("GPL"), unless you have obtained a separate licensing agreement
14 * ("Other License"), formally executed by you and Linden Lab. Terms of
15 * the GPL can be found in doc/GPL-license.txt in this distribution, or
16 * online at http://secondlife.com/developers/opensource/gplv2
17 *
18 * There are special exceptions to the terms and conditions of the GPL as
19 * it is applied to this Source Code. View the full text of the exception
20 * in the file doc/FLOSS-exception.txt in this software distribution, or
21 * online at
22 * http://secondlife.com/developers/opensource/flossexception
23 *
24 * By copying, modifying or distributing this software, you acknowledge
25 * that you have read and understood your obligations described above,
26 * and agree to abide by those obligations.
27 *
28 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
29 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
30 * COMPLETENESS OR PERFORMANCE.
31 * $/LicenseInfo$
32 *
33 * @endcond
34 */
35
36#include "linden_common.h"
37#include "indra_constants.h"
38
39#include "llplugincookiestore.h"
40#include <iostream>
41
42// for curl_getdate() (apparently parsing RFC 1123 dates is hard)
43#include <curl/curl.h>
44
45LLPluginCookieStore::LLPluginCookieStore():
46 mHasChangedCookies(false)
47{
48}
49
50
51LLPluginCookieStore::~LLPluginCookieStore()
52{
53 clearCookies();
54}
55
56
57LLPluginCookieStore::Cookie::Cookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end):
58 mCookie(s, cookie_start, cookie_end - cookie_start),
59 mNameStart(0), mNameEnd(0),
60 mValueStart(0), mValueEnd(0),
61 mDomainStart(0), mDomainEnd(0),
62 mPathStart(0), mPathEnd(0),
63 mDead(false), mChanged(true)
64{
65}
66
67LLPluginCookieStore::Cookie *LLPluginCookieStore::Cookie::createFromString(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, const std::string &host)
68{
69 Cookie *result = new Cookie(s, cookie_start, cookie_end);
70
71 if(!result->parse(host))
72 {
73 delete result;
74 result = NULL;
75 }
76
77 return result;
78}
79
80std::string LLPluginCookieStore::Cookie::getKey() const
81{
82 std::string result;
83 if(mDomainEnd > mDomainStart)
84 {
85 result += mCookie.substr(mDomainStart, mDomainEnd - mDomainStart);
86 }
87 result += ';';
88 if(mPathEnd > mPathStart)
89 {
90 result += mCookie.substr(mPathStart, mPathEnd - mPathStart);
91 }
92 result += ';';
93 result += mCookie.substr(mNameStart, mNameEnd - mNameStart);
94 return result;
95}
96
97bool LLPluginCookieStore::Cookie::parse(const std::string &host)
98{
99 bool first_field = true;
100
101 std::string::size_type cookie_end = mCookie.size();
102 std::string::size_type field_start = 0;
103
104 LL_DEBUGS("CookieStoreParse") << "parsing cookie: " << mCookie << LL_ENDL;
105 while(field_start < cookie_end)
106 {
107 // Finding the start of the next field requires honoring special quoting rules
108 // see the definition of 'quoted-string' in rfc2616 for details
109 std::string::size_type next_field_start = findFieldEnd(field_start);
110
111 // The end of this field should not include the terminating ';' or any trailing whitespace
112 std::string::size_type field_end = mCookie.find_last_not_of("; ", next_field_start);
113 if(field_end == std::string::npos || field_end < field_start)
114 {
115 // This field was empty or all whitespace. Set end = start so it shows as empty.
116 field_end = field_start;
117 }
118 else if (field_end < next_field_start)
119 {
120 // we actually want the index of the char _after_ what 'last not of' found
121 ++field_end;
122 }
123
124 // find the start of the actual name (skip separator and possible whitespace)
125 std::string::size_type name_start = mCookie.find_first_not_of("; ", field_start);
126 if(name_start == std::string::npos || name_start > next_field_start)
127 {
128 // Again, nothing but whitespace.
129 name_start = field_start;
130 }
131
132 // the name and value are separated by the first equals sign
133 std::string::size_type name_value_sep = mCookie.find_first_of("=", name_start);
134 if(name_value_sep == std::string::npos || name_value_sep > field_end)
135 {
136 // No separator found, so this is a field without an =
137 name_value_sep = field_end;
138 }
139
140 // the name end is before the name-value separator
141 std::string::size_type name_end = mCookie.find_last_not_of("= ", name_value_sep);
142 if(name_end == std::string::npos || name_end < name_start)
143 {
144 // I'm not sure how we'd hit this case... it seems like it would have to be an empty name.
145 name_end = name_start;
146 }
147 else if (name_end < name_value_sep)
148 {
149 // we actually want the index of the char _after_ what 'last not of' found
150 ++name_end;
151 }
152
153 // Value is between the name-value sep and the end of the field.
154 std::string::size_type value_start = mCookie.find_first_not_of("= ", name_value_sep);
155 if(value_start == std::string::npos || value_start > field_end)
156 {
157 // All whitespace or empty value
158 value_start = field_end;
159 }
160 std::string::size_type value_end = mCookie.find_last_not_of("; ", field_end);
161 if(value_end == std::string::npos || value_end < value_start)
162 {
163 // All whitespace or empty value
164 value_end = value_start;
165 }
166 else if (value_end < field_end)
167 {
168 // we actually want the index of the char _after_ what 'last not of' found
169 ++value_end;
170 }
171
172 LL_DEBUGS("CookieStoreParse")
173 << " field name: \"" << mCookie.substr(name_start, name_end - name_start)
174 << "\", value: \"" << mCookie.substr(value_start, value_end - value_start) << "\""
175 << LL_ENDL;
176
177 // See whether this field is one we know
178 if(first_field)
179 {
180 // The first field is the name=value pair
181 mNameStart = name_start;
182 mNameEnd = name_end;
183 mValueStart = value_start;
184 mValueEnd = value_end;
185 first_field = false;
186 }
187 else
188 {
189 // Subsequent fields must come from the set in rfc2109
190 if(matchName(name_start, name_end, "expires"))
191 {
192 std::string date_string(mCookie, value_start, value_end - value_start);
193 // If the cookie contains an "expires" field, it MUST contain a parsable date.
194
195 // HACK: LLDate apparently can't PARSE an rfc1123-format date, even though it can GENERATE one.
196 // The curl function curl_getdate can do this, but I'm hesitant to unilaterally introduce a curl dependency in LLDate.
197#if 1
198 time_t date = curl_getdate(date_string.c_str(), NULL );
199 mDate.secondsSinceEpoch((F64)date);
200 LL_DEBUGS("CookieStoreParse") << " expire date parsed to: " << mDate.asRFC1123() << LL_ENDL;
201#else
202 // This doesn't work (rfc1123-format dates cause it to fail)
203 if(!mDate.fromString(date_string))
204 {
205 // Date failed to parse.
206 LL_WARNS("CookieStoreParse") << "failed to parse cookie's expire date: " << date << LL_ENDL;
207 return false;
208 }
209#endif
210 }
211 else if(matchName(name_start, name_end, "domain"))
212 {
213 mDomainStart = value_start;
214 mDomainEnd = value_end;
215 }
216 else if(matchName(name_start, name_end, "path"))
217 {
218 mPathStart = value_start;
219 mPathEnd = value_end;
220 }
221 else if(matchName(name_start, name_end, "max-age"))
222 {
223 // TODO: how should we handle this?
224 }
225 else if(matchName(name_start, name_end, "secure"))
226 {
227 // We don't care about the value of this field (yet)
228 }
229 else if(matchName(name_start, name_end, "version"))
230 {
231 // We don't care about the value of this field (yet)
232 }
233 else if(matchName(name_start, name_end, "comment"))
234 {
235 // We don't care about the value of this field (yet)
236 }
237 else if(matchName(name_start, name_end, "httponly"))
238 {
239 // We don't care about the value of this field (yet)
240 }
241 else
242 {
243 // An unknown field is a parse failure
244 LL_WARNS("CookieStoreParse") << "unexpected field name: " << mCookie.substr(name_start, name_end - name_start) << LL_ENDL;
245 return false;
246 }
247
248 }
249
250
251 // move on to the next field, skipping this field's separator and any leading whitespace
252 field_start = mCookie.find_first_not_of("; ", next_field_start);
253 }
254
255 // The cookie MUST have a name
256 if(mNameEnd <= mNameStart)
257 return false;
258
259 // If the cookie doesn't have a domain, add the current host as the domain.
260 if(mDomainEnd <= mDomainStart)
261 {
262 if(host.empty())
263 {
264 // no domain and no current host -- this is a parse failure.
265 return false;
266 }
267
268 // Figure out whether this cookie ended with a ";" or not...
269 std::string::size_type last_char = mCookie.find_last_not_of(" ");
270 if((last_char != std::string::npos) && (mCookie[last_char] != ';'))
271 {
272 mCookie += ";";
273 }
274
275 mCookie += " domain=";
276 mDomainStart = mCookie.size();
277 mCookie += host;
278 mDomainEnd = mCookie.size();
279
280 LL_DEBUGS("CookieStoreParse") << "added domain (" << mDomainStart << " to " << mDomainEnd << "), new cookie is: " << mCookie << LL_ENDL;
281 }
282
283 // If the cookie doesn't have a path, add "/".
284 if(mPathEnd <= mPathStart)
285 {
286 // Figure out whether this cookie ended with a ";" or not...
287 std::string::size_type last_char = mCookie.find_last_not_of(" ");
288 if((last_char != std::string::npos) && (mCookie[last_char] != ';'))
289 {
290 mCookie += ";";
291 }
292
293 mCookie += " path=";
294 mPathStart = mCookie.size();
295 mCookie += "/";
296 mPathEnd = mCookie.size();
297
298 LL_DEBUGS("CookieStoreParse") << "added path (" << mPathStart << " to " << mPathEnd << "), new cookie is: " << mCookie << LL_ENDL;
299 }
300
301
302 return true;
303}
304
305std::string::size_type LLPluginCookieStore::Cookie::findFieldEnd(std::string::size_type start, std::string::size_type end)
306{
307 std::string::size_type result = start;
308
309 if(end == std::string::npos)
310 end = mCookie.size();
311
312 bool in_quotes = false;
313 for(; (result < end); result++)
314 {
315 switch(mCookie[result])
316 {
317 case '\\':
318 if(in_quotes)
319 result++; // The next character is backslash-quoted. Skip over it.
320 break;
321 case '"':
322 in_quotes = !in_quotes;
323 break;
324 case ';':
325 if(!in_quotes)
326 return result;
327 break;
328 }
329 }
330
331 // If we got here, no ';' was found.
332 return end;
333}
334
335bool LLPluginCookieStore::Cookie::matchName(std::string::size_type start, std::string::size_type end, const char *name)
336{
337 // NOTE: this assumes 'name' is already in lowercase. The code which uses it should be able to arrange this...
338
339 while((start < end) && (*name != '\0'))
340 {
341 if(tolower(mCookie[start]) != *name)
342 return false;
343
344 start++;
345 name++;
346 }
347
348 // iff both strings hit the end at the same time, they're equal.
349 return ((start == end) && (*name == '\0'));
350}
351
352std::string LLPluginCookieStore::getAllCookies()
353{
354 std::stringstream result;
355 writeAllCookies(result);
356 return result.str();
357}
358
359void LLPluginCookieStore::writeAllCookies(std::ostream& s)
360{
361 cookie_map_t::iterator iter;
362 for(iter = mCookies.begin(); iter != mCookies.end(); iter++)
363 {
364 // Don't return expired cookies
365 if(!iter->second->isDead())
366 {
367 s << (iter->second->getCookie()) << "\n";
368 }
369 }
370
371}
372
373std::string LLPluginCookieStore::getPersistentCookies()
374{
375 std::stringstream result;
376 writePersistentCookies(result);
377 return result.str();
378}
379
380void LLPluginCookieStore::writePersistentCookies(std::ostream& s)
381{
382 cookie_map_t::iterator iter;
383 for(iter = mCookies.begin(); iter != mCookies.end(); iter++)
384 {
385 // Don't return expired cookies or session cookies
386 if(!iter->second->isDead() && !iter->second->isSessionCookie())
387 {
388 s << iter->second->getCookie() << "\n";
389 }
390 }
391}
392
393std::string LLPluginCookieStore::getChangedCookies(bool clear_changed)
394{
395 std::stringstream result;
396 writeChangedCookies(result, clear_changed);
397
398 return result.str();
399}
400
401void LLPluginCookieStore::writeChangedCookies(std::ostream& s, bool clear_changed)
402{
403 if(mHasChangedCookies)
404 {
405 lldebugs << "returning changed cookies: " << llendl;
406 cookie_map_t::iterator iter;
407 for(iter = mCookies.begin(); iter != mCookies.end(); )
408 {
409 cookie_map_t::iterator next = iter;
410 next++;
411
412 // Only return cookies marked as "changed"
413 if(iter->second->isChanged())
414 {
415 s << iter->second->getCookie() << "\n";
416
417 lldebugs << " " << iter->second->getCookie() << llendl;
418
419 // If requested, clear the changed mark
420 if(clear_changed)
421 {
422 if(iter->second->isDead())
423 {
424 // If this cookie was previously marked dead, it needs to be removed entirely.
425 delete iter->second;
426 mCookies.erase(iter);
427 }
428 else
429 {
430 // Not dead, just mark as not changed.
431 iter->second->setChanged(false);
432 }
433 }
434 }
435
436 iter = next;
437 }
438 }
439
440 if(clear_changed)
441 mHasChangedCookies = false;
442}
443
444void LLPluginCookieStore::setAllCookies(const std::string &cookies, bool mark_changed)
445{
446 clearCookies();
447 setCookies(cookies, mark_changed);
448}
449
450void LLPluginCookieStore::readAllCookies(std::istream& s, bool mark_changed)
451{
452 clearCookies();
453 readCookies(s, mark_changed);
454}
455
456void LLPluginCookieStore::setCookies(const std::string &cookies, bool mark_changed)
457{
458 std::string::size_type start = 0;
459
460 while(start != std::string::npos)
461 {
462 std::string::size_type end = cookies.find_first_of("\r\n", start);
463 if(end > start)
464 {
465 // The line is non-empty. Try to create a cookie from it.
466 setOneCookie(cookies, start, end, mark_changed);
467 }
468 start = cookies.find_first_not_of("\r\n ", end);
469 }
470}
471
472void LLPluginCookieStore::setCookiesFromHost(const std::string &cookies, const std::string &host, bool mark_changed)
473{
474 std::string::size_type start = 0;
475
476 while(start != std::string::npos)
477 {
478 std::string::size_type end = cookies.find_first_of("\r\n", start);
479 if(end > start)
480 {
481 // The line is non-empty. Try to create a cookie from it.
482 setOneCookie(cookies, start, end, mark_changed, host);
483 }
484 start = cookies.find_first_not_of("\r\n ", end);
485 }
486}
487
488void LLPluginCookieStore::readCookies(std::istream& s, bool mark_changed)
489{
490 std::string line;
491 while(s.good() && !s.eof())
492 {
493 std::getline(s, line);
494 if(!line.empty())
495 {
496 // Try to create a cookie from this line.
497 setOneCookie(line, 0, std::string::npos, mark_changed);
498 }
499 }
500}
501
502std::string LLPluginCookieStore::quoteString(const std::string &s)
503{
504 std::stringstream result;
505
506 result << '"';
507
508 for(std::string::size_type i = 0; i < s.size(); ++i)
509 {
510 char c = s[i];
511 switch(c)
512 {
513 // All these separators need to be quoted in HTTP headers, according to section 2.2 of rfc 2616:
514 case '(': case ')': case '<': case '>': case '@':
515 case ',': case ';': case ':': case '\\': case '"':
516 case '/': case '[': case ']': case '?': case '=':
517 case '{': case '}': case ' ': case '\t':
518 result << '\\';
519 break;
520 }
521
522 result << c;
523 }
524
525 result << '"';
526
527 return result.str();
528}
529
530std::string LLPluginCookieStore::unquoteString(const std::string &s)
531{
532 std::stringstream result;
533
534 bool in_quotes = false;
535
536 for(std::string::size_type i = 0; i < s.size(); ++i)
537 {
538 char c = s[i];
539 switch(c)
540 {
541 case '\\':
542 if(in_quotes)
543 {
544 // The next character is backslash-quoted. Pass it through untouched.
545 ++i;
546 if(i < s.size())
547 {
548 result << s[i];
549 }
550 continue;
551 }
552 break;
553 case '"':
554 in_quotes = !in_quotes;
555 continue;
556 break;
557 }
558
559 result << c;
560 }
561
562 return result.str();
563}
564
565// The flow for deleting a cookie is non-obvious enough that I should call it out here...
566// Deleting a cookie is done by setting a cookie with the same name, path, and domain, but with an expire timestamp in the past.
567// (This is exactly how a web server tells a browser to delete a cookie.)
568// When deleting with mark_changed set to true, this replaces the existing cookie in the list with an entry that's marked both dead and changed.
569// Some time later when writeChangedCookies() is called with clear_changed set to true, the dead cookie is deleted from the list after being returned, so that the
570// delete operation (in the form of the expired cookie) is passed along.
571void LLPluginCookieStore::setOneCookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, bool mark_changed, const std::string &host)
572{
573 Cookie *cookie = Cookie::createFromString(s, cookie_start, cookie_end, host);
574 if(cookie)
575 {
576 LL_DEBUGS("CookieStoreUpdate") << "setting cookie: " << cookie->getCookie() << LL_ENDL;
577
578 // Create a key for this cookie
579 std::string key = cookie->getKey();
580
581 // Check to see whether this cookie should have expired
582 if(!cookie->isSessionCookie() && (cookie->getDate() < LLDate::now()))
583 {
584 // This cookie has expired.
585 if(mark_changed)
586 {
587 // If we're marking cookies as changed, we should keep it anyway since we'll need to send it out with deltas.
588 cookie->setDead(true);
589 LL_DEBUGS("CookieStoreUpdate") << " marking dead" << LL_ENDL;
590 }
591 else
592 {
593 // If we're not marking cookies as changed, we don't need to keep this cookie at all.
594 // If the cookie was already in the list, delete it.
595 removeCookie(key);
596
597 delete cookie;
598 cookie = NULL;
599
600 LL_DEBUGS("CookieStoreUpdate") << " removing" << LL_ENDL;
601 }
602 }
603
604 if(cookie)
605 {
606 // If it already exists in the map, replace it.
607 cookie_map_t::iterator iter = mCookies.find(key);
608 if(iter != mCookies.end())
609 {
610 if(iter->second->getCookie() == cookie->getCookie())
611 {
612 // The new cookie is identical to the old -- don't mark as changed.
613 // Just leave the old one in the map.
614 delete cookie;
615 cookie = NULL;
616
617 LL_DEBUGS("CookieStoreUpdate") << " unchanged" << LL_ENDL;
618 }
619 else
620 {
621 // A matching cookie was already in the map. Replace it.
622 delete iter->second;
623 iter->second = cookie;
624
625 cookie->setChanged(mark_changed);
626 if(mark_changed)
627 mHasChangedCookies = true;
628
629 LL_DEBUGS("CookieStoreUpdate") << " replacing" << LL_ENDL;
630 }
631 }
632 else
633 {
634 // The cookie wasn't in the map. Insert it.
635 mCookies.insert(std::make_pair(key, cookie));
636
637 cookie->setChanged(mark_changed);
638 if(mark_changed)
639 mHasChangedCookies = true;
640
641 LL_DEBUGS("CookieStoreUpdate") << " adding" << LL_ENDL;
642 }
643 }
644 }
645 else
646 {
647 LL_WARNS("CookieStoreUpdate") << "failed to parse cookie: " << s.substr(cookie_start, cookie_end - cookie_start) << LL_ENDL;
648 }
649
650}
651
652void LLPluginCookieStore::clearCookies()
653{
654 while(!mCookies.empty())
655 {
656 cookie_map_t::iterator iter = mCookies.begin();
657 delete iter->second;
658 mCookies.erase(iter);
659 }
660}
661
662void LLPluginCookieStore::removeCookie(const std::string &key)
663{
664 cookie_map_t::iterator iter = mCookies.find(key);
665 if(iter != mCookies.end())
666 {
667 delete iter->second;
668 mCookies.erase(iter);
669 }
670}
671
diff --git a/linden/indra/llplugin/llplugincookiestore.h b/linden/indra/llplugin/llplugincookiestore.h
new file mode 100644
index 0000000..69f0cf1
--- /dev/null
+++ b/linden/indra/llplugin/llplugincookiestore.h
@@ -0,0 +1,127 @@
1/**
2 * @file llplugincookiestore.h
3 * @brief LLPluginCookieStore provides central storage for http cookies used by plugins
4 *
5 * @cond
6 * $LicenseInfo:firstyear=2010&license=viewergpl$
7 *
8 * Copyright (c) 2010, Linden Research, Inc.
9 *
10 * Second Life Viewer Source Code
11 * The source code in this file ("Source Code") is provided by Linden Lab
12 * to you under the terms of the GNU General Public License, version 2.0
13 * ("GPL"), unless you have obtained a separate licensing agreement
14 * ("Other License"), formally executed by you and Linden Lab. Terms of
15 * the GPL can be found in doc/GPL-license.txt in this distribution, or
16 * online at http://secondlife.com/developers/opensource/gplv2
17 *
18 * There are special exceptions to the terms and conditions of the GPL as
19 * it is applied to this Source Code. View the full text of the exception
20 * in the file doc/FLOSS-exception.txt in this software distribution, or
21 * online at
22 * http://secondlife.com/developers/opensource/flossexception
23 *
24 * By copying, modifying or distributing this software, you acknowledge
25 * that you have read and understood your obligations described above,
26 * and agree to abide by those obligations.
27 *
28 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
29 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
30 * COMPLETENESS OR PERFORMANCE.
31 * $/LicenseInfo$
32 *
33 * @endcond
34 */
35
36#ifndef LL_LLPLUGINCOOKIESTORE_H
37#define LL_LLPLUGINCOOKIESTORE_H
38
39#include "lldate.h"
40#include <map>
41#include <string>
42#include <iostream>
43
44class LLPluginCookieStore
45{
46 LOG_CLASS(LLPluginCookieStore);
47public:
48 LLPluginCookieStore();
49 ~LLPluginCookieStore();
50
51 // gets all cookies currently in storage -- use when initializing a plugin
52 std::string getAllCookies();
53 void writeAllCookies(std::ostream& s);
54
55 // gets only persistent cookies (i.e. not session cookies) -- use when writing cookies to a file
56 std::string getPersistentCookies();
57 void writePersistentCookies(std::ostream& s);
58
59 // gets cookies which are marked as "changed" -- use when sending periodic updates to plugins
60 std::string getChangedCookies(bool clear_changed = true);
61 void writeChangedCookies(std::ostream& s, bool clear_changed = true);
62
63 // (re)initializes internal data structures and bulk-sets cookies -- use when reading cookies from a file
64 void setAllCookies(const std::string &cookies, bool mark_changed = false);
65 void readAllCookies(std::istream& s, bool mark_changed = false);
66
67 // sets one or more cookies (without reinitializing anything) -- use when receiving cookies from a plugin
68 void setCookies(const std::string &cookies, bool mark_changed = true);
69 void readCookies(std::istream& s, bool mark_changed = true);
70
71 // sets one or more cookies (without reinitializing anything), supplying a hostname the cookies came from -- use when setting a cookie manually
72 void setCookiesFromHost(const std::string &cookies, const std::string &host, bool mark_changed = true);
73
74 // quote or unquote a string as per the definition of 'quoted-string' in rfc2616
75 static std::string quoteString(const std::string &s);
76 static std::string unquoteString(const std::string &s);
77
78private:
79
80 void setOneCookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, bool mark_changed, const std::string &host = LLStringUtil::null);
81
82 class Cookie
83 {
84 public:
85 static Cookie *createFromString(const std::string &s, std::string::size_type cookie_start = 0, std::string::size_type cookie_end = std::string::npos, const std::string &host = LLStringUtil::null);
86
87 // Construct a string from the cookie that uniquely represents it, to be used as a key in a std::map.
88 std::string getKey() const;
89
90 const std::string &getCookie() const { return mCookie; };
91 bool isSessionCookie() const { return mDate.isNull(); };
92
93 bool isDead() const { return mDead; };
94 void setDead(bool dead) { mDead = dead; };
95
96 bool isChanged() const { return mChanged; };
97 void setChanged(bool changed) { mChanged = changed; };
98
99 const LLDate &getDate() const { return mDate; };
100
101 private:
102 Cookie(const std::string &s, std::string::size_type cookie_start = 0, std::string::size_type cookie_end = std::string::npos);
103 bool parse(const std::string &host);
104 std::string::size_type findFieldEnd(std::string::size_type start = 0, std::string::size_type end = std::string::npos);
105 bool matchName(std::string::size_type start, std::string::size_type end, const char *name);
106
107 std::string mCookie; // The full cookie, in RFC 2109 string format
108 LLDate mDate; // The expiration date of the cookie. For session cookies, this will be a null date (mDate.isNull() is true).
109 // Start/end indices of various parts of the cookie string. Stored as indices into the string to save space and time.
110 std::string::size_type mNameStart, mNameEnd;
111 std::string::size_type mValueStart, mValueEnd;
112 std::string::size_type mDomainStart, mDomainEnd;
113 std::string::size_type mPathStart, mPathEnd;
114 bool mDead;
115 bool mChanged;
116 };
117
118 typedef std::map<std::string, Cookie*> cookie_map_t;
119
120 cookie_map_t mCookies;
121 bool mHasChangedCookies;
122
123 void clearCookies();
124 void removeCookie(const std::string &key);
125};
126
127#endif // LL_LLPLUGINCOOKIESTORE_H
diff --git a/linden/indra/llplugin/llpluginmessagepipe.cpp b/linden/indra/llplugin/llpluginmessagepipe.cpp
index b16381c..8168b32 100755
--- a/linden/indra/llplugin/llpluginmessagepipe.cpp
+++ b/linden/indra/llplugin/llpluginmessagepipe.cpp
@@ -98,11 +98,14 @@ void LLPluginMessagePipeOwner::killMessagePipe(void)
98 } 98 }
99} 99}
100 100
101LLPluginMessagePipe::LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket) 101LLPluginMessagePipe::LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket):
102 mInputMutex(gAPRPoolp),
103 mOutputMutex(gAPRPoolp),
104 mOwner(owner),
105 mSocket(socket)
102{ 106{
103 mOwner = owner; 107
104 mOwner->setMessagePipe(this); 108 mOwner->setMessagePipe(this);
105 mSocket = socket;
106} 109}
107 110
108LLPluginMessagePipe::~LLPluginMessagePipe() 111LLPluginMessagePipe::~LLPluginMessagePipe()
@@ -116,6 +119,7 @@ LLPluginMessagePipe::~LLPluginMessagePipe()
116bool LLPluginMessagePipe::addMessage(const std::string &message) 119bool LLPluginMessagePipe::addMessage(const std::string &message)
117{ 120{
118 // queue the message for later output 121 // queue the message for later output
122 LLMutexLock lock(&mOutputMutex);
119 mOutput += message; 123 mOutput += message;
120 mOutput += MESSAGE_DELIMITER; // message separator 124 mOutput += MESSAGE_DELIMITER; // message separator
121 125
@@ -151,6 +155,18 @@ void LLPluginMessagePipe::setSocketTimeout(apr_interval_time_t timeout_usec)
151 155
152bool LLPluginMessagePipe::pump(F64 timeout) 156bool LLPluginMessagePipe::pump(F64 timeout)
153{ 157{
158 bool result = pumpOutput();
159
160 if(result)
161 {
162 result = pumpInput(timeout);
163 }
164
165 return result;
166}
167
168bool LLPluginMessagePipe::pumpOutput()
169{
154 bool result = true; 170 bool result = true;
155 171
156 if(mSocket) 172 if(mSocket)
@@ -158,6 +174,7 @@ bool LLPluginMessagePipe::pump(F64 timeout)
158 apr_status_t status; 174 apr_status_t status;
159 apr_size_t size; 175 apr_size_t size;
160 176
177 LLMutexLock lock(&mOutputMutex);
161 if(!mOutput.empty()) 178 if(!mOutput.empty())
162 { 179 {
163 // write any outgoing messages 180 // write any outgoing messages
@@ -185,6 +202,17 @@ bool LLPluginMessagePipe::pump(F64 timeout)
185 // remove the written part from the buffer and try again later. 202 // remove the written part from the buffer and try again later.
186 mOutput = mOutput.substr(size); 203 mOutput = mOutput.substr(size);
187 } 204 }
205 else if(APR_STATUS_IS_EOF(status))
206 {
207 // This is what we normally expect when a plugin exits.
208 llinfos << "Got EOF from plugin socket. " << llendl;
209
210 if(mOwner)
211 {
212 mOwner->socketError(status);
213 }
214 result = false;
215 }
188 else 216 else
189 { 217 {
190 // some other error 218 // some other error
@@ -198,6 +226,19 @@ bool LLPluginMessagePipe::pump(F64 timeout)
198 result = false; 226 result = false;
199 } 227 }
200 } 228 }
229 }
230
231 return result;
232}
233
234bool LLPluginMessagePipe::pumpInput(F64 timeout)
235{
236 bool result = true;
237
238 if(mSocket)
239 {
240 apr_status_t status;
241 apr_size_t size;
201 242
202 // FIXME: For some reason, the apr timeout stuff isn't working properly on windows. 243 // FIXME: For some reason, the apr timeout stuff isn't working properly on windows.
203 // Until such time as we figure out why, don't try to use the socket timeout -- just sleep here instead. 244 // Until such time as we figure out why, don't try to use the socket timeout -- just sleep here instead.
@@ -218,8 +259,16 @@ bool LLPluginMessagePipe::pump(F64 timeout)
218 char input_buf[1024]; 259 char input_buf[1024];
219 apr_size_t request_size; 260 apr_size_t request_size;
220 261
221 // Start out by reading one byte, so that any data received will wake us up. 262 if(timeout == 0.0f)
222 request_size = 1; 263 {
264 // If we have no timeout, start out with a full read.
265 request_size = sizeof(input_buf);
266 }
267 else
268 {
269 // Start out by reading one byte, so that any data received will wake us up.
270 request_size = 1;
271 }
223 272
224 // and use the timeout so we'll sleep if no data is available. 273 // and use the timeout so we'll sleep if no data is available.
225 setSocketTimeout((apr_interval_time_t)(timeout * 1000000)); 274 setSocketTimeout((apr_interval_time_t)(timeout * 1000000));
@@ -238,11 +287,14 @@ bool LLPluginMessagePipe::pump(F64 timeout)
238// LL_INFOS("Plugin") << "after apr_socket_recv, size = " << size << LL_ENDL; 287// LL_INFOS("Plugin") << "after apr_socket_recv, size = " << size << LL_ENDL;
239 288
240 if(size > 0) 289 if(size > 0)
290 {
291 LLMutexLock lock(&mInputMutex);
241 mInput.append(input_buf, size); 292 mInput.append(input_buf, size);
293 }
242 294
243 if(status == APR_SUCCESS) 295 if(status == APR_SUCCESS)
244 { 296 {
245// llinfos << "success, read " << size << llendl; 297 LL_DEBUGS("PluginSocket") << "success, read " << size << LL_ENDL;
246 298
247 if(size != request_size) 299 if(size != request_size)
248 { 300 {
@@ -252,16 +304,28 @@ bool LLPluginMessagePipe::pump(F64 timeout)
252 } 304 }
253 else if(APR_STATUS_IS_TIMEUP(status)) 305 else if(APR_STATUS_IS_TIMEUP(status))
254 { 306 {
255// llinfos << "TIMEUP, read " << size << llendl; 307 LL_DEBUGS("PluginSocket") << "TIMEUP, read " << size << LL_ENDL;
256 308
257 // Timeout was hit. Since the initial read is 1 byte, this should never be a partial read. 309 // Timeout was hit. Since the initial read is 1 byte, this should never be a partial read.
258 break; 310 break;
259 } 311 }
260 else if(APR_STATUS_IS_EAGAIN(status)) 312 else if(APR_STATUS_IS_EAGAIN(status))
261 { 313 {
262// llinfos << "EAGAIN, read " << size << llendl; 314 LL_DEBUGS("PluginSocket") << "EAGAIN, read " << size << LL_ENDL;
263 315
264 // We've been doing partial reads, and we're done now. 316 // Non-blocking read returned immediately.
317 break;
318 }
319 else if(APR_STATUS_IS_EOF(status))
320 {
321 // This is what we normally expect when a plugin exits.
322 LL_INFOS("PluginSocket") << "Got EOF from plugin socket. " << LL_ENDL;
323
324 if(mOwner)
325 {
326 mOwner->socketError(status);
327 }
328 result = false;
265 break; 329 break;
266 } 330 }
267 else 331 else
@@ -278,22 +342,18 @@ bool LLPluginMessagePipe::pump(F64 timeout)
278 break; 342 break;
279 } 343 }
280 344
281 // Second and subsequent reads should not use the timeout 345 if(timeout != 0.0f)
282 setSocketTimeout(0); 346 {
283 // and should try to fill the input buffer 347 // Second and subsequent reads should not use the timeout
284 request_size = sizeof(input_buf); 348 setSocketTimeout(0);
349 // and should try to fill the input buffer
350 request_size = sizeof(input_buf);
351 }
285 } 352 }
286 353
287 processInput(); 354 processInput();
288 } 355 }
289 } 356 }
290
291 if(!result)
292 {
293 // If we got an error, we're done.
294 LL_INFOS("Plugin") << "Error from socket, cleaning up." << LL_ENDL;
295 delete this;
296 }
297 357
298 return result; 358 return result;
299} 359}
@@ -301,26 +361,27 @@ bool LLPluginMessagePipe::pump(F64 timeout)
301void LLPluginMessagePipe::processInput(void) 361void LLPluginMessagePipe::processInput(void)
302{ 362{
303 // Look for input delimiter(s) in the input buffer. 363 // Look for input delimiter(s) in the input buffer.
304 int start = 0;
305 int delim; 364 int delim;
306 while((delim = mInput.find(MESSAGE_DELIMITER, start)) != std::string::npos) 365 mInputMutex.lock();
366 while((delim = mInput.find(MESSAGE_DELIMITER)) != std::string::npos)
307 { 367 {
308 // Let the owner process this message 368 // Let the owner process this message
309 if (mOwner) 369 if (mOwner)
310 { 370 {
311 mOwner->receiveMessageRaw(mInput.substr(start, delim - start)); 371 // Pull the message out of the input buffer before calling receiveMessageRaw.
372 // It's now possible for this function to get called recursively (in the case where the plugin makes a blocking request)
373 // and this guarantees that the messages will get dequeued correctly.
374 std::string message(mInput, 0, delim);
375 mInput.erase(0, delim + 1);
376 mInputMutex.unlock();
377 mOwner->receiveMessageRaw(message);
378 mInputMutex.lock();
312 } 379 }
313 else 380 else
314 { 381 {
315 LL_WARNS("Plugin") << "!mOwner" << LL_ENDL; 382 LL_WARNS("Plugin") << "!mOwner" << LL_ENDL;
316 } 383 }
317
318 start = delim + 1;
319 } 384 }
320 385 mInputMutex.unlock();
321 // Remove delivered messages from the input buffer.
322 if(start != 0)
323 mInput = mInput.substr(start);
324
325} 386}
326 387
diff --git a/linden/indra/llplugin/llpluginmessagepipe.h b/linden/indra/llplugin/llpluginmessagepipe.h
index 8f74f38..6eedca2 100755
--- a/linden/indra/llplugin/llpluginmessagepipe.h
+++ b/linden/indra/llplugin/llpluginmessagepipe.h
@@ -37,6 +37,7 @@
37#define LL_LLPLUGINMESSAGEPIPE_H 37#define LL_LLPLUGINMESSAGEPIPE_H
38 38
39#include "lliosocket.h" 39#include "lliosocket.h"
40#include "llthread.h"
40 41
41class LLPluginMessagePipe; 42class LLPluginMessagePipe;
42 43
@@ -53,7 +54,7 @@ public:
53 virtual apr_status_t socketError(apr_status_t error); 54 virtual apr_status_t socketError(apr_status_t error);
54 55
55 // called from LLPluginMessagePipe to manage the connection with LLPluginMessagePipeOwner -- do not use! 56 // called from LLPluginMessagePipe to manage the connection with LLPluginMessagePipeOwner -- do not use!
56 virtual void setMessagePipe(LLPluginMessagePipe *message_pipe) ; 57 virtual void setMessagePipe(LLPluginMessagePipe *message_pipe);
57 58
58protected: 59protected:
59 // returns false if writeMessageRaw() would drop the message 60 // returns false if writeMessageRaw() would drop the message
@@ -78,14 +79,18 @@ public:
78 void clearOwner(void); 79 void clearOwner(void);
79 80
80 bool pump(F64 timeout = 0.0f); 81 bool pump(F64 timeout = 0.0f);
81 82 bool pumpOutput();
83 bool pumpInput(F64 timeout = 0.0f);
84
82protected: 85protected:
83 void processInput(void); 86 void processInput(void);
84 87
85 // used internally by pump() 88 // used internally by pump()
86 void setSocketTimeout(apr_interval_time_t timeout_usec); 89 void setSocketTimeout(apr_interval_time_t timeout_usec);
87 90
91 LLMutex mInputMutex;
88 std::string mInput; 92 std::string mInput;
93 LLMutex mOutputMutex;
89 std::string mOutput; 94 std::string mOutput;
90 95
91 LLPluginMessagePipeOwner *mOwner; 96 LLPluginMessagePipeOwner *mOwner;
diff --git a/linden/indra/llplugin/llpluginprocesschild.cpp b/linden/indra/llplugin/llpluginprocesschild.cpp
index 0b7ce3d..8dbf2b3 100755
--- a/linden/indra/llplugin/llpluginprocesschild.cpp
+++ b/linden/indra/llplugin/llpluginprocesschild.cpp
@@ -50,6 +50,8 @@ LLPluginProcessChild::LLPluginProcessChild()
50 mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); 50 mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP);
51 mSleepTime = PLUGIN_IDLE_SECONDS; // default: send idle messages at 100Hz 51 mSleepTime = PLUGIN_IDLE_SECONDS; // default: send idle messages at 100Hz
52 mCPUElapsed = 0.0f; 52 mCPUElapsed = 0.0f;
53 mBlockingRequest = false;
54 mBlockingResponseReceived = false;
53} 55}
54 56
55LLPluginProcessChild::~LLPluginProcessChild() 57LLPluginProcessChild::~LLPluginProcessChild()
@@ -85,9 +87,14 @@ void LLPluginProcessChild::idle(void)
85 bool idle_again; 87 bool idle_again;
86 do 88 do
87 { 89 {
88 if(mSocketError != APR_SUCCESS) 90 if(APR_STATUS_IS_EOF(mSocketError))
89 { 91 {
90 LL_INFOS("Plugin") << "message pipe is in error state, moving to STATE_ERROR"<< LL_ENDL; 92 // Plugin socket was closed. This covers both normal plugin termination and host crashes.
93 setState(STATE_ERROR);
94 }
95 else if(mSocketError != APR_SUCCESS)
96 {
97 LL_INFOS("Plugin") << "message pipe is in error state (" << mSocketError << "), moving to STATE_ERROR"<< LL_ENDL;
91 setState(STATE_ERROR); 98 setState(STATE_ERROR);
92 } 99 }
93 100
@@ -228,6 +235,7 @@ void LLPluginProcessChild::idle(void)
228 235
229void LLPluginProcessChild::sleep(F64 seconds) 236void LLPluginProcessChild::sleep(F64 seconds)
230{ 237{
238 deliverQueuedMessages();
231 if(mMessagePipe) 239 if(mMessagePipe)
232 { 240 {
233 mMessagePipe->pump(seconds); 241 mMessagePipe->pump(seconds);
@@ -240,6 +248,7 @@ void LLPluginProcessChild::sleep(F64 seconds)
240 248
241void LLPluginProcessChild::pump(void) 249void LLPluginProcessChild::pump(void)
242{ 250{
251 deliverQueuedMessages();
243 if(mMessagePipe) 252 if(mMessagePipe)
244 { 253 {
245 mMessagePipe->pump(0.0f); 254 mMessagePipe->pump(0.0f);
@@ -311,15 +320,32 @@ void LLPluginProcessChild::receiveMessageRaw(const std::string &message)
311 320
312 LL_DEBUGS("Plugin") << "Received from parent: " << message << LL_ENDL; 321 LL_DEBUGS("Plugin") << "Received from parent: " << message << LL_ENDL;
313 322
323 // Decode this message
324 LLPluginMessage parsed;
325 parsed.parse(message);
326
327 if(mBlockingRequest)
328 {
329 // We're blocking the plugin waiting for a response.
330
331 if(parsed.hasValue("blocking_response"))
332 {
333 // This is the message we've been waiting for -- fall through and send it immediately.
334 mBlockingResponseReceived = true;
335 }
336 else
337 {
338 // Still waiting. Queue this message and don't process it yet.
339 mMessageQueue.push(message);
340 return;
341 }
342 }
343
314 bool passMessage = true; 344 bool passMessage = true;
315 345
316 // FIXME: how should we handle queueing here? 346 // FIXME: how should we handle queueing here?
317 347
318 { 348 {
319 // Decode this message
320 LLPluginMessage parsed;
321 parsed.parse(message);
322
323 std::string message_class = parsed.getClass(); 349 std::string message_class = parsed.getClass();
324 if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL) 350 if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL)
325 { 351 {
@@ -427,7 +453,13 @@ void LLPluginProcessChild::receiveMessageRaw(const std::string &message)
427void LLPluginProcessChild::receivePluginMessage(const std::string &message) 453void LLPluginProcessChild::receivePluginMessage(const std::string &message)
428{ 454{
429 LL_DEBUGS("Plugin") << "Received from plugin: " << message << LL_ENDL; 455 LL_DEBUGS("Plugin") << "Received from plugin: " << message << LL_ENDL;
430 456
457 if(mBlockingRequest)
458 {
459 //
460 LL_ERRS("Plugin") << "Can't send a message while already waiting on a blocking request -- aborting!" << LL_ENDL;
461 }
462
431 // Incoming message from the plugin instance 463 // Incoming message from the plugin instance
432 bool passMessage = true; 464 bool passMessage = true;
433 465
@@ -438,6 +470,12 @@ void LLPluginProcessChild::receivePluginMessage(const std::string &message)
438 // Decode this message 470 // Decode this message
439 LLPluginMessage parsed; 471 LLPluginMessage parsed;
440 parsed.parse(message); 472 parsed.parse(message);
473
474 if(parsed.hasValue("blocking_request"))
475 {
476 mBlockingRequest = true;
477 }
478
441 std::string message_class = parsed.getClass(); 479 std::string message_class = parsed.getClass();
442 if(message_class == "base") 480 if(message_class == "base")
443 { 481 {
@@ -496,6 +534,19 @@ void LLPluginProcessChild::receivePluginMessage(const std::string &message)
496 LL_DEBUGS("Plugin") << "Passing through to parent: " << message << LL_ENDL; 534 LL_DEBUGS("Plugin") << "Passing through to parent: " << message << LL_ENDL;
497 writeMessageRaw(message); 535 writeMessageRaw(message);
498 } 536 }
537
538 while(mBlockingRequest)
539 {
540 // The plugin wants to block and wait for a response to this message.
541 sleep(mSleepTime); // this will pump the message pipe and process messages
542
543 if(mBlockingResponseReceived || mSocketError != APR_SUCCESS || (mMessagePipe == NULL))
544 {
545 // Response has been received, or we've hit an error state. Stop waiting.
546 mBlockingRequest = false;
547 mBlockingResponseReceived = false;
548 }
549 }
499} 550}
500 551
501 552
@@ -504,3 +555,15 @@ void LLPluginProcessChild::setState(EState state)
504 LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL; 555 LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL;
505 mState = state; 556 mState = state;
506}; 557};
558
559void LLPluginProcessChild::deliverQueuedMessages()
560{
561 if(!mBlockingRequest)
562 {
563 while(!mMessageQueue.empty())
564 {
565 receiveMessageRaw(mMessageQueue.front());
566 mMessageQueue.pop();
567 }
568 }
569}
diff --git a/linden/indra/llplugin/llpluginprocesschild.h b/linden/indra/llplugin/llpluginprocesschild.h
index 96ae7b4..5d643d7 100755
--- a/linden/indra/llplugin/llpluginprocesschild.h
+++ b/linden/indra/llplugin/llpluginprocesschild.h
@@ -36,6 +36,8 @@
36#ifndef LL_LLPLUGINPROCESSCHILD_H 36#ifndef LL_LLPLUGINPROCESSCHILD_H
37#define LL_LLPLUGINPROCESSCHILD_H 37#define LL_LLPLUGINPROCESSCHILD_H
38 38
39#include <queue> //imprudence
40
39#include "llpluginmessage.h" 41#include "llpluginmessage.h"
40#include "llpluginmessagepipe.h" 42#include "llpluginmessagepipe.h"
41#include "llplugininstance.h" 43#include "llplugininstance.h"
@@ -108,6 +110,11 @@ private:
108 LLTimer mHeartbeat; 110 LLTimer mHeartbeat;
109 F64 mSleepTime; 111 F64 mSleepTime;
110 F64 mCPUElapsed; 112 F64 mCPUElapsed;
113 bool mBlockingRequest;
114 bool mBlockingResponseReceived;
115 std::queue<std::string> mMessageQueue;
116
117 void deliverQueuedMessages();
111 118
112}; 119};
113 120
diff --git a/linden/indra/llplugin/llpluginprocessparent.cpp b/linden/indra/llplugin/llpluginprocessparent.cpp
index 8ea28f1..1bf34c5 100755
--- a/linden/indra/llplugin/llpluginprocessparent.cpp
+++ b/linden/indra/llplugin/llpluginprocessparent.cpp
@@ -47,8 +47,51 @@ LLPluginProcessParentOwner::~LLPluginProcessParentOwner()
47 47
48} 48}
49 49
50LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner) 50bool LLPluginProcessParent::sUseReadThread = false;
51apr_pollset_t *LLPluginProcessParent::sPollSet = NULL;
52bool LLPluginProcessParent::sPollsetNeedsRebuild = false;
53LLMutex *LLPluginProcessParent::sInstancesMutex;
54std::list<LLPluginProcessParent*> LLPluginProcessParent::sInstances;
55LLThread *LLPluginProcessParent::sReadThread = NULL;
56
57
58class LLPluginProcessParentPollThread: public LLThread
51{ 59{
60public:
61 LLPluginProcessParentPollThread() :
62 LLThread("LLPluginProcessParentPollThread", gAPRPoolp)
63 {
64 }
65protected:
66 // Inherited from LLThread
67 /*virtual*/ void run(void)
68 {
69 while(!isQuitting() && LLPluginProcessParent::getUseReadThread())
70 {
71 LLPluginProcessParent::poll(0.1f);
72 checkPause();
73 }
74
75 // Final poll to clean up the pollset, etc.
76 LLPluginProcessParent::poll(0.0f);
77 }
78
79 // Inherited from LLThread
80 /*virtual*/ bool runCondition(void)
81 {
82 return(LLPluginProcessParent::canPollThreadRun());
83 }
84
85};
86
87LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner):
88 mIncomingQueueMutex(gAPRPoolp)
89{
90 if(!sInstancesMutex)
91 {
92 sInstancesMutex = new LLMutex(gAPRPoolp);
93 }
94
52 mOwner = owner; 95 mOwner = owner;
53 mBoundPort = 0; 96 mBoundPort = 0;
54 mState = STATE_UNINITIALIZED; 97 mState = STATE_UNINITIALIZED;
@@ -56,18 +99,38 @@ LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner)
56 mCPUUsage = 0.0; 99 mCPUUsage = 0.0;
57 mDisableTimeout = false; 100 mDisableTimeout = false;
58 mDebug = false; 101 mDebug = false;
102 mBlocked = false;
103 mPolledInput = false;
104 mPollFD.client_data = NULL;
59 105
60 mPluginLaunchTimeout = 60.0f; 106 mPluginLaunchTimeout = 60.0f;
61 mPluginLockupTimeout = 15.0f; 107 mPluginLockupTimeout = 15.0f;
62 108
63 // Don't start the timer here -- start it when we actually launch the plugin process. 109 // Don't start the timer here -- start it when we actually launch the plugin process.
64 mHeartbeat.stop(); 110 mHeartbeat.stop();
111
112
113 // Don't add to the global list until fully constructed.
114 {
115 LLMutexLock lock(sInstancesMutex);
116 sInstances.push_back(this);
117 }
65} 118}
66 119
67LLPluginProcessParent::~LLPluginProcessParent() 120LLPluginProcessParent::~LLPluginProcessParent()
68{ 121{
69 LL_DEBUGS("Plugin") << "destructor" << LL_ENDL; 122 LL_DEBUGS("Plugin") << "destructor" << LL_ENDL;
70 123
124 // Remove from the global list before beginning destruction.
125 {
126 // Make sure to get the global mutex _first_ here, to avoid a possible deadlock against LLPluginProcessParent::poll()
127 LLMutexLock lock(sInstancesMutex);
128 {
129 LLMutexLock lock2(&mIncomingQueueMutex);
130 sInstances.remove(this);
131 }
132 }
133
71 // Destroy any remaining shared memory regions 134 // Destroy any remaining shared memory regions
72 sharedMemoryRegionsType::iterator iter; 135 sharedMemoryRegionsType::iterator iter;
73 while((iter = mSharedMemoryRegions.begin()) != mSharedMemoryRegions.end()) 136 while((iter = mSharedMemoryRegions.begin()) != mSharedMemoryRegions.end())
@@ -79,15 +142,17 @@ LLPluginProcessParent::~LLPluginProcessParent()
79 mSharedMemoryRegions.erase(iter); 142 mSharedMemoryRegions.erase(iter);
80 } 143 }
81 144
82 // orphaning the process means it won't be killed when the LLProcessLauncher is destructed. 145 mProcess.kill();
83 // This is what we want -- it should exit cleanly once it notices the sockets have been closed.
84 mProcess.orphan();
85 killSockets(); 146 killSockets();
86} 147}
87 148
88void LLPluginProcessParent::killSockets(void) 149void LLPluginProcessParent::killSockets(void)
89{ 150{
90 killMessagePipe(); 151 {
152 LLMutexLock lock(&mIncomingQueueMutex);
153 killMessagePipe();
154 }
155
91 mListenSocket.reset(); 156 mListenSocket.reset();
92 mSocket.reset(); 157 mSocket.reset();
93} 158}
@@ -161,21 +226,47 @@ void LLPluginProcessParent::idle(void)
161 226
162 do 227 do
163 { 228 {
229 // process queued messages
230 mIncomingQueueMutex.lock();
231 while(!mIncomingQueue.empty())
232 {
233 LLPluginMessage message = mIncomingQueue.front();
234 mIncomingQueue.pop();
235 mIncomingQueueMutex.unlock();
236
237 receiveMessage(message);
238
239 mIncomingQueueMutex.lock();
240 }
241
242 mIncomingQueueMutex.unlock();
243
164 // Give time to network processing 244 // Give time to network processing
165 if(mMessagePipe) 245 if(mMessagePipe)
166 { 246 {
167 if(!mMessagePipe->pump()) 247 // Drain any queued outgoing messages
248 mMessagePipe->pumpOutput();
249
250 // Only do input processing here if this instance isn't in a pollset.
251 if(!mPolledInput)
168 { 252 {
169// LL_WARNS("Plugin") << "Message pipe hit an error state" << LL_ENDL; 253 mMessagePipe->pumpInput();
170 errorState();
171 } 254 }
172 } 255 }
173 256
174 if((mSocketError != APR_SUCCESS) && (mState <= STATE_RUNNING)) 257 if(mState <= STATE_RUNNING)
175 { 258 {
176 // The socket is in an error state -- the plugin is gone. 259 if(APR_STATUS_IS_EOF(mSocketError))
177 LL_WARNS("Plugin") << "Socket hit an error state (" << mSocketError << ")" << LL_ENDL; 260 {
178 errorState(); 261 // Plugin socket was closed. This covers both normal plugin termination and plugin crashes.
262 errorState();
263 }
264 else if(mSocketError != APR_SUCCESS)
265 {
266 // The socket is in an error state -- the plugin is gone.
267 LL_WARNS("Plugin") << "Socket hit an error state (" << mSocketError << ")" << LL_ENDL;
268 errorState();
269 }
179 } 270 }
180 271
181 // If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState(). 272 // If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState().
@@ -356,7 +447,7 @@ void LLPluginProcessParent::idle(void)
356 break; 447 break;
357 448
358 case STATE_HELLO: 449 case STATE_HELLO:
359 LL_DEBUGS("Plugin") << "received hello message" << llendl; 450 LL_DEBUGS("Plugin") << "received hello message" << LL_ENDL;
360 451
361 // Send the message to load the plugin 452 // Send the message to load the plugin
362 { 453 {
@@ -390,7 +481,7 @@ void LLPluginProcessParent::idle(void)
390 } 481 }
391 else if(pluginLockedUp()) 482 else if(pluginLockedUp())
392 { 483 {
393 LL_WARNS("Plugin") << "timeout in exiting state, bailing out" << llendl; 484 LL_WARNS("Plugin") << "timeout in exiting state, bailing out" << LL_ENDL;
394 errorState(); 485 errorState();
395 } 486 }
396 break; 487 break;
@@ -412,8 +503,7 @@ void LLPluginProcessParent::idle(void)
412 break; 503 break;
413 504
414 case STATE_CLEANUP: 505 case STATE_CLEANUP:
415 // Don't do a kill here anymore -- closing the sockets is the new 'kill'. 506 mProcess.kill();
416 mProcess.orphan();
417 killSockets(); 507 killSockets();
418 setState(STATE_DONE); 508 setState(STATE_DONE);
419 break; 509 break;
@@ -481,23 +571,323 @@ void LLPluginProcessParent::setSleepTime(F64 sleep_time, bool force_send)
481 571
482void LLPluginProcessParent::sendMessage(const LLPluginMessage &message) 572void LLPluginProcessParent::sendMessage(const LLPluginMessage &message)
483{ 573{
574 if(message.hasValue("blocking_response"))
575 {
576 mBlocked = false;
577
578 // reset the heartbeat timer, since there will have been no heartbeats while the plugin was blocked.
579 mHeartbeat.setTimerExpirySec(mPluginLockupTimeout);
580 }
484 581
485 std::string buffer = message.generate(); 582 std::string buffer = message.generate();
486 LL_DEBUGS("Plugin") << "Sending: " << buffer << LL_ENDL; 583 LL_DEBUGS("Plugin") << "Sending: " << buffer << LL_ENDL;
487 writeMessageRaw(buffer); 584 writeMessageRaw(buffer);
585
586 // Try to send message immediately.
587 if(mMessagePipe)
588 {
589 mMessagePipe->pumpOutput();
590 }
591}
592
593//virtual
594void LLPluginProcessParent::setMessagePipe(LLPluginMessagePipe *message_pipe)
595{
596 bool update_pollset = false;
597
598 if(mMessagePipe)
599 {
600 // Unsetting an existing message pipe -- remove from the pollset
601 mPollFD.client_data = NULL;
602
603 // pollset needs an update
604 update_pollset = true;
605 }
606 if(message_pipe != NULL)
607 {
608 // Set up the apr_pollfd_t
609 mPollFD.p = gAPRPoolp;
610 mPollFD.desc_type = APR_POLL_SOCKET;
611 mPollFD.reqevents = APR_POLLIN|APR_POLLERR|APR_POLLHUP;
612 mPollFD.rtnevents = 0;
613 mPollFD.desc.s = mSocket->getSocket();
614 mPollFD.client_data = (void*)this;
615
616 // pollset needs an update
617 update_pollset = true;
618 }
619
620 mMessagePipe = message_pipe;
621
622 if(update_pollset)
623 {
624 dirtyPollSet();
625 }
626}
627
628//static
629void LLPluginProcessParent::dirtyPollSet()
630{
631 sPollsetNeedsRebuild = true;
632
633 if(sReadThread)
634 {
635 LL_DEBUGS("PluginPoll") << "unpausing read thread " << LL_ENDL;
636 sReadThread->unpause();
637 }
638}
639
640void LLPluginProcessParent::updatePollset()
641{
642 if(!sInstancesMutex)
643 {
644 // No instances have been created yet. There's no work to do.
645 return;
646 }
647
648 LLMutexLock lock(sInstancesMutex);
649
650 if(sPollSet)
651 {
652 LL_DEBUGS("PluginPoll") << "destroying pollset " << sPollSet << LL_ENDL;
653 // delete the existing pollset.
654 apr_pollset_destroy(sPollSet);
655 sPollSet = NULL;
656 }
657
658 std::list<LLPluginProcessParent*>::iterator iter;
659 int count = 0;
660
661 // Count the number of instances that want to be in the pollset
662 for(iter = sInstances.begin(); iter != sInstances.end(); iter++)
663 {
664 (*iter)->mPolledInput = false;
665 if((*iter)->mPollFD.client_data)
666 {
667 // This instance has a socket that needs to be polled.
668 ++count;
669 }
670 }
671
672 if(sUseReadThread && sReadThread && !sReadThread->isQuitting())
673 {
674 if(!sPollSet && (count > 0))
675 {
676#ifdef APR_POLLSET_NOCOPY
677 // The pollset doesn't exist yet. Create it now.
678 apr_status_t status = apr_pollset_create(&sPollSet, count, gAPRPoolp, APR_POLLSET_NOCOPY);
679 if(status != APR_SUCCESS)
680 {
681#endif // APR_POLLSET_NOCOPY
682 LL_WARNS("PluginPoll") << "Couldn't create pollset. Falling back to non-pollset mode." << LL_ENDL;
683 sPollSet = NULL;
684#ifdef APR_POLLSET_NOCOPY
685 }
686 else
687 {
688 LL_DEBUGS("PluginPoll") << "created pollset " << sPollSet << LL_ENDL;
689
690 // Pollset was created, add all instances to it.
691 for(iter = sInstances.begin(); iter != sInstances.end(); iter++)
692 {
693 if((*iter)->mPollFD.client_data)
694 {
695 status = apr_pollset_add(sPollSet, &((*iter)->mPollFD));
696 if(status == APR_SUCCESS)
697 {
698 (*iter)->mPolledInput = true;
699 }
700 else
701 {
702 LL_WARNS("PluginPoll") << "apr_pollset_add failed with status " << status << LL_ENDL;
703 }
704 }
705 }
706 }
707#endif // APR_POLLSET_NOCOPY
708 }
709 }
710}
711
712void LLPluginProcessParent::setUseReadThread(bool use_read_thread)
713{
714 if(sUseReadThread != use_read_thread)
715 {
716 sUseReadThread = use_read_thread;
717
718 if(sUseReadThread)
719 {
720 if(!sReadThread)
721 {
722 // start up the read thread
723 LL_INFOS("PluginPoll") << "creating read thread " << LL_ENDL;
724
725 // make sure the pollset gets rebuilt.
726 sPollsetNeedsRebuild = true;
727
728 sReadThread = new LLPluginProcessParentPollThread;
729 sReadThread->start();
730 }
731 }
732 else
733 {
734 if(sReadThread)
735 {
736 // shut down the read thread
737 LL_INFOS("PluginPoll") << "destroying read thread " << LL_ENDL;
738 delete sReadThread;
739 sReadThread = NULL;
740 }
741 }
742
743 }
744}
745
746void LLPluginProcessParent::poll(F64 timeout)
747{
748 if(sPollsetNeedsRebuild || !sUseReadThread)
749 {
750 sPollsetNeedsRebuild = false;
751 updatePollset();
752 }
753
754 if(sPollSet)
755 {
756 apr_status_t status;
757 apr_int32_t count;
758 const apr_pollfd_t *descriptors;
759 status = apr_pollset_poll(sPollSet, (apr_interval_time_t)(timeout * 1000000), &count, &descriptors);
760 if(status == APR_SUCCESS)
761 {
762 // One or more of the descriptors signalled. Call them.
763 for(int i = 0; i < count; i++)
764 {
765 LLPluginProcessParent *self = (LLPluginProcessParent *)(descriptors[i].client_data);
766 // NOTE: the descriptor returned here is actually a COPY of the original (even though we create the pollset with APR_POLLSET_NOCOPY).
767 // This means that even if the parent has set its mPollFD.client_data to NULL, the old pointer may still there in this descriptor.
768 // It's even possible that the old pointer no longer points to a valid LLPluginProcessParent.
769 // This means that we can't safely dereference the 'self' pointer here without some extra steps...
770 if(self)
771 {
772 // Make sure this pointer is still in the instances list
773 bool valid = false;
774 {
775 LLMutexLock lock(sInstancesMutex);
776 for(std::list<LLPluginProcessParent*>::iterator iter = sInstances.begin(); iter != sInstances.end(); ++iter)
777 {
778 if(*iter == self)
779 {
780 // Lock the instance's mutex before unlocking the global mutex.
781 // This avoids a possible race condition where the instance gets deleted between this check and the servicePoll() call.
782 self->mIncomingQueueMutex.lock();
783 valid = true;
784 break;
785 }
786 }
787 }
788
789 if(valid)
790 {
791 // The instance is still valid.
792 // Pull incoming messages off the socket
793 self->servicePoll();
794 self->mIncomingQueueMutex.unlock();
795 }
796 else
797 {
798 LL_DEBUGS("PluginPoll") << "detected deleted instance " << self << LL_ENDL;
799 }
800
801 }
802 }
803 }
804 else if(APR_STATUS_IS_TIMEUP(status))
805 {
806 // timed out with no incoming data. Just return.
807 }
808 else if(status == EBADF)
809 {
810 // This happens when one of the file descriptors in the pollset is destroyed, which happens whenever a plugin's socket is closed.
811 // The pollset has been or will be recreated, so just return.
812 LL_DEBUGS("PluginPoll") << "apr_pollset_poll returned EBADF" << LL_ENDL;
813 }
814 else if(status != APR_SUCCESS)
815 {
816 LL_WARNS("PluginPoll") << "apr_pollset_poll failed with status " << status << LL_ENDL;
817 }
818 }
488} 819}
489 820
821void LLPluginProcessParent::servicePoll()
822{
823 bool result = true;
824
825 // poll signalled on this object's socket. Try to process incoming messages.
826 if(mMessagePipe)
827 {
828 result = mMessagePipe->pumpInput(0.0f);
829 }
830
831 if(!result)
832 {
833 // If we got a read error on input, remove this pipe from the pollset
834 apr_pollset_remove(sPollSet, &mPollFD);
835
836 // and tell the code not to re-add it
837 mPollFD.client_data = NULL;
838 }
839}
490 840
491void LLPluginProcessParent::receiveMessageRaw(const std::string &message) 841void LLPluginProcessParent::receiveMessageRaw(const std::string &message)
492{ 842{
493 LL_DEBUGS("Plugin") << "Received: " << message << LL_ENDL; 843 LL_DEBUGS("Plugin") << "Received: " << message << LL_ENDL;
494
495 // FIXME: should this go into a queue instead?
496 844
497 LLPluginMessage parsed; 845 LLPluginMessage parsed;
498 if(parsed.parse(message) != -1) 846 if(parsed.parse(message) != -1)
499 { 847 {
500 receiveMessage(parsed); 848 if(parsed.hasValue("blocking_request"))
849 {
850 mBlocked = true;
851 }
852
853 if(mPolledInput)
854 {
855 // This is being called on the polling thread -- only do minimal processing/queueing.
856 receiveMessageEarly(parsed);
857 }
858 else
859 {
860 // This is not being called on the polling thread -- do full message processing at this time.
861 receiveMessage(parsed);
862 }
863 }
864}
865
866void LLPluginProcessParent::receiveMessageEarly(const LLPluginMessage &message)
867{
868 // NOTE: this function will be called from the polling thread. It will be called with mIncomingQueueMutex _already locked_.
869
870 bool handled = false;
871
872 std::string message_class = message.getClass();
873 if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL)
874 {
875 // no internal messages need to be handled early.
876 }
877 else
878 {
879 // Call out to the owner and see if they to reply
880 // TODO: Should this only happen when blocked?
881 if(mOwner != NULL)
882 {
883 handled = mOwner->receivePluginMessageEarly(message);
884 }
885 }
886
887 if(!handled)
888 {
889 // any message that wasn't handled early needs to be queued.
890 mIncomingQueue.push(message);
501 } 891 }
502} 892}
503 893
@@ -691,18 +1081,15 @@ bool LLPluginProcessParent::pluginLockedUpOrQuit()
691{ 1081{
692 bool result = false; 1082 bool result = false;
693 1083
694 if(!mDisableTimeout && !mDebug) 1084 if(!mProcess.isRunning())
695 { 1085 {
696 if(!mProcess.isRunning()) 1086 LL_WARNS("Plugin") << "child exited" << LL_ENDL;
697 { 1087 result = true;
698 LL_WARNS("Plugin") << "child exited" << llendl; 1088 }
699 result = true; 1089 else if(pluginLockedUp())
700 } 1090 {
701 else if(pluginLockedUp()) 1091 LL_WARNS("Plugin") << "timeout" << LL_ENDL;
702 { 1092 result = true;
703 LL_WARNS("Plugin") << "timeout" << llendl;
704 result = true;
705 }
706 } 1093 }
707 1094
708 return result; 1095 return result;
@@ -710,6 +1097,12 @@ bool LLPluginProcessParent::pluginLockedUpOrQuit()
710 1097
711bool LLPluginProcessParent::pluginLockedUp() 1098bool LLPluginProcessParent::pluginLockedUp()
712{ 1099{
1100 if(mDisableTimeout || mDebug || mBlocked)
1101 {
1102 // Never time out a plugin process in these cases.
1103 return false;
1104 }
1105
713 // If the timer is running and has expired, the plugin has locked up. 1106 // If the timer is running and has expired, the plugin has locked up.
714 return (mHeartbeat.getStarted() && mHeartbeat.hasExpired()); 1107 return (mHeartbeat.getStarted() && mHeartbeat.hasExpired());
715} 1108}
diff --git a/linden/indra/llplugin/llpluginprocessparent.h b/linden/indra/llplugin/llpluginprocessparent.h
index 523ce51..a8929b1 100755
--- a/linden/indra/llplugin/llpluginprocessparent.h
+++ b/linden/indra/llplugin/llpluginprocessparent.h
@@ -36,6 +36,8 @@
36#ifndef LL_LLPLUGINPROCESSPARENT_H 36#ifndef LL_LLPLUGINPROCESSPARENT_H
37#define LL_LLPLUGINPROCESSPARENT_H 37#define LL_LLPLUGINPROCESSPARENT_H
38 38
39#include <queue> //imprudence
40
39#include "llapr.h" 41#include "llapr.h"
40#include "llprocesslauncher.h" 42#include "llprocesslauncher.h"
41#include "llpluginmessage.h" 43#include "llpluginmessage.h"
@@ -43,12 +45,14 @@
43#include "llpluginsharedmemory.h" 45#include "llpluginsharedmemory.h"
44 46
45#include "lliosocket.h" 47#include "lliosocket.h"
48#include "llthread.h"
46 49
47class LLPluginProcessParentOwner 50class LLPluginProcessParentOwner
48{ 51{
49public: 52public:
50 virtual ~LLPluginProcessParentOwner(); 53 virtual ~LLPluginProcessParentOwner();
51 virtual void receivePluginMessage(const LLPluginMessage &message) = 0; 54 virtual void receivePluginMessage(const LLPluginMessage &message) = 0;
55 virtual bool receivePluginMessageEarly(const LLPluginMessage &message) {return false;};
52 // This will only be called when the plugin has died unexpectedly 56 // This will only be called when the plugin has died unexpectedly
53 virtual void pluginLaunchFailed() {}; 57 virtual void pluginLaunchFailed() {};
54 virtual void pluginDied() {}; 58 virtual void pluginDied() {};
@@ -76,6 +80,9 @@ public:
76 // returns true if the process has exited or we've had a fatal error 80 // returns true if the process has exited or we've had a fatal error
77 bool isDone(void); 81 bool isDone(void);
78 82
83 // returns true if the process is currently waiting on a blocking request
84 bool isBlocked(void) { return mBlocked; };
85
79 void killSockets(void); 86 void killSockets(void);
80 87
81 // Go to the proper error state 88 // Go to the proper error state
@@ -89,7 +96,9 @@ public:
89 void receiveMessage(const LLPluginMessage &message); 96 void receiveMessage(const LLPluginMessage &message);
90 97
91 // Inherited from LLPluginMessagePipeOwner 98 // Inherited from LLPluginMessagePipeOwner
92 void receiveMessageRaw(const std::string &message); 99 /*virtual*/ void receiveMessageRaw(const std::string &message);
100 /*virtual*/ void receiveMessageEarly(const LLPluginMessage &message);
101 /*virtual*/ void setMessagePipe(LLPluginMessagePipe *message_pipe) ;
93 102
94 // This adds a memory segment shared with the client, generating a name for the segment. The name generated is guaranteed to be unique on the host. 103 // This adds a memory segment shared with the client, generating a name for the segment. The name generated is guaranteed to be unique on the host.
95 // The caller must call removeSharedMemory first (and wait until getSharedMemorySize returns 0 for the indicated name) before re-adding a segment with the same name. 104 // The caller must call removeSharedMemory first (and wait until getSharedMemorySize returns 0 for the indicated name) before re-adding a segment with the same name.
@@ -112,7 +121,11 @@ public:
112 void setLockupTimeout(F32 timeout) { mPluginLockupTimeout = timeout; }; 121 void setLockupTimeout(F32 timeout) { mPluginLockupTimeout = timeout; };
113 122
114 F64 getCPUUsage() { return mCPUUsage; }; 123 F64 getCPUUsage() { return mCPUUsage; };
115 124
125 static void poll(F64 timeout);
126 static bool canPollThreadRun() { return (sPollSet || sPollsetNeedsRebuild || sUseReadThread); };
127 static void setUseReadThread(bool use_read_thread);
128 static bool getUseReadThread() { return sUseReadThread; };
116private: 129private:
117 130
118 enum EState 131 enum EState
@@ -162,12 +175,27 @@ private:
162 175
163 bool mDisableTimeout; 176 bool mDisableTimeout;
164 bool mDebug; 177 bool mDebug;
178 bool mBlocked;
179 bool mPolledInput;
165 180
166 LLProcessLauncher mDebugger; 181 LLProcessLauncher mDebugger;
167 182
168 F32 mPluginLaunchTimeout; // Somewhat longer timeout for initial launch. 183 F32 mPluginLaunchTimeout; // Somewhat longer timeout for initial launch.
169 F32 mPluginLockupTimeout; // If we don't receive a heartbeat in this many seconds, we declare the plugin locked up. 184 F32 mPluginLockupTimeout; // If we don't receive a heartbeat in this many seconds, we declare the plugin locked up.
170 185
186 static bool sUseReadThread;
187 apr_pollfd_t mPollFD;
188 static apr_pollset_t *sPollSet;
189 static bool sPollsetNeedsRebuild;
190 static LLMutex *sInstancesMutex;
191 static std::list<LLPluginProcessParent*> sInstances;
192 static void dirtyPollSet();
193 static void updatePollset();
194 void servicePoll();
195 static LLThread *sReadThread;
196
197 LLMutex mIncomingQueueMutex;
198 std::queue<LLPluginMessage> mIncomingQueue;
171}; 199};
172 200
173#endif // LL_LLPLUGINPROCESSPARENT_H 201#endif // LL_LLPLUGINPROCESSPARENT_H
diff --git a/linden/indra/llplugin/slplugin/CMakeLists.txt b/linden/indra/llplugin/slplugin/CMakeLists.txt
index 4a7d670..81d9299 100755
--- a/linden/indra/llplugin/slplugin/CMakeLists.txt
+++ b/linden/indra/llplugin/slplugin/CMakeLists.txt
@@ -16,6 +16,7 @@ include_directories(
16if (DARWIN) 16if (DARWIN)
17 include(CMakeFindFrameworks) 17 include(CMakeFindFrameworks)
18 find_library(CARBON_LIBRARY Carbon) 18 find_library(CARBON_LIBRARY Carbon)
19 find_library(COCOA_LIBRARY Cocoa)
19endif (DARWIN) 20endif (DARWIN)
20 21
21 22
@@ -25,11 +26,33 @@ set(SLPlugin_SOURCE_FILES
25 slplugin.cpp 26 slplugin.cpp
26 ) 27 )
27 28
29if (DARWIN)
30 list(APPEND SLPlugin_SOURCE_FILES
31 slplugin-objc.mm
32 )
33 list(APPEND SLPlugin_HEADER_FILES
34 slplugin-objc.h
35 )
36endif (DARWIN)
37
38set_source_files_properties(${SLPlugin_HEADER_FILES}
39 PROPERTIES HEADER_FILE_ONLY TRUE)
40
41if (SLPlugin_HEADER_FILES)
42 list(APPEND SLPlugin_SOURCE_FILES ${SLPlugin_HEADER_FILES})
43endif (SLPlugin_HEADER_FILES)
44
28add_executable(SLPlugin 45add_executable(SLPlugin
29 WIN32 46 WIN32
47 MACOSX_BUNDLE
30 ${SLPlugin_SOURCE_FILES} 48 ${SLPlugin_SOURCE_FILES}
31) 49)
32 50
51set_target_properties(SLPlugin
52 PROPERTIES
53 MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/slplugin_info.plist
54 )
55
33target_link_libraries(SLPlugin 56target_link_libraries(SLPlugin
34 ${LLPLUGIN_LIBRARIES} 57 ${LLPLUGIN_LIBRARIES}
35 ${LLMESSAGE_LIBRARIES} 58 ${LLMESSAGE_LIBRARIES}
@@ -44,12 +67,16 @@ add_dependencies(SLPlugin
44) 67)
45 68
46if (DARWIN) 69if (DARWIN)
47 # Mac version needs to link against carbon, and also needs an embedded plist (to set LSBackgroundOnly) 70 # Mac version needs to link against Carbon
48 target_link_libraries(SLPlugin ${CARBON_LIBRARY}) 71 target_link_libraries(SLPlugin ${CARBON_LIBRARY} ${COCOA_LIBRARY})
49 set_target_properties( 72 # Make sure the app bundle has a Resources directory (it will get populated by viewer-manifest.py later)
50 SLPlugin 73 add_custom_command(
51 PROPERTIES 74 TARGET SLPlugin POST_BUILD
52 LINK_FLAGS "-Wl,-sectcreate,__TEXT,__info_plist,${CMAKE_CURRENT_SOURCE_DIR}/slplugin_info.plist" 75 COMMAND mkdir
76 ARGS
77 -p
78 ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/SLPlugin.app/Contents/Resources
53 ) 79 )
54endif (DARWIN) 80endif (DARWIN)
55 81
82#ll_deploy_sharedlibs_command(SLPlugin)
diff --git a/linden/indra/llplugin/slplugin/slplugin-objc.h b/linden/indra/llplugin/slplugin/slplugin-objc.h
new file mode 100644
index 0000000..42029e4
--- /dev/null
+++ b/linden/indra/llplugin/slplugin/slplugin-objc.h
@@ -0,0 +1,42 @@
1/**
2 * @file slplugin-objc.h
3 * @brief Header file for slplugin-objc.mm.
4 *
5 * @cond
6 *
7 * $LicenseInfo:firstyear=2010&license=viewergpl$
8 *
9 * Copyright (c) 2010, Linden Research, Inc.
10 *
11 * Second Life Viewer Source Code
12 * The source code in this file ("Source Code") is provided by Linden Lab
13 * to you under the terms of the GNU General Public License, version 2.0
14 * ("GPL"), unless you have obtained a separate licensing agreement
15 * ("Other License"), formally executed by you and Linden Lab. Terms of
16 * the GPL can be found in doc/GPL-license.txt in this distribution, or
17 * online at http://secondlife.com/developers/opensource/gplv2
18 *
19 * There are special exceptions to the terms and conditions of the GPL as
20 * it is applied to this Source Code. View the full text of the exception
21 * in the file doc/FLOSS-exception.txt in this software distribution, or
22 * online at
23 * http://secondlife.com/developers/opensource/flossexception
24 *
25 * By copying, modifying or distributing this software, you acknowledge
26 * that you have read and understood your obligations described above,
27 * and agree to abide by those obligations.
28 *
29 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
30 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
31 * COMPLETENESS OR PERFORMANCE.
32 * $/LicenseInfo$
33 *
34 *
35 * @endcond
36 */
37
38
39/* Defined in slplugin-objc.mm: */
40void setupCocoa();
41void createAutoReleasePool();
42void deleteAutoReleasePool();
diff --git a/linden/indra/llplugin/slplugin/slplugin-objc.mm b/linden/indra/llplugin/slplugin/slplugin-objc.mm
new file mode 100644
index 0000000..125b264
--- /dev/null
+++ b/linden/indra/llplugin/slplugin/slplugin-objc.mm
@@ -0,0 +1,89 @@
1/**
2 * @file slplugin-objc.mm
3 * @brief Objective-C++ file for use with the loader shell, so we can use a couple of Cocoa APIs.
4 *
5 * @cond
6 *
7 * $LicenseInfo:firstyear=2010&license=viewergpl$
8 *
9 * Copyright (c) 2010, Linden Research, Inc.
10 *
11 * Second Life Viewer Source Code
12 * The source code in this file ("Source Code") is provided by Linden Lab
13 * to you under the terms of the GNU General Public License, version 2.0
14 * ("GPL"), unless you have obtained a separate licensing agreement
15 * ("Other License"), formally executed by you and Linden Lab. Terms of
16 * the GPL can be found in doc/GPL-license.txt in this distribution, or
17 * online at http://secondlife.com/developers/opensource/gplv2
18 *
19 * There are special exceptions to the terms and conditions of the GPL as
20 * it is applied to this Source Code. View the full text of the exception
21 * in the file doc/FLOSS-exception.txt in this software distribution, or
22 * online at
23 * http://secondlife.com/developers/opensource/flossexception
24 *
25 * By copying, modifying or distributing this software, you acknowledge
26 * that you have read and understood your obligations described above,
27 * and agree to abide by those obligations.
28 *
29 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
30 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
31 * COMPLETENESS OR PERFORMANCE.
32 * $/LicenseInfo$
33 *
34 *
35 * @endcond
36 */
37
38
39#include <AppKit/AppKit.h>
40
41#include "slplugin-objc.h"
42
43
44void setupCocoa()
45{
46 static bool inited = false;
47
48 if(!inited)
49 {
50 createAutoReleasePool();
51
52 // The following prevents the Cocoa command line parser from trying to open 'unknown' arguements as documents.
53 // ie. running './secondlife -set Language fr' would cause a pop-up saying can't open document 'fr'
54 // when init'ing the Cocoa App window.
55 [[NSUserDefaults standardUserDefaults] setObject:@"NO" forKey:@"NSTreatUnknownArgumentsAsOpen"];
56
57 // This is a bit of voodoo taken from the Apple sample code "CarbonCocoa_PictureCursor":
58 // http://developer.apple.com/samplecode/CarbonCocoa_PictureCursor/index.html
59
60 // Needed for Carbon based applications which call into Cocoa
61 NSApplicationLoad();
62
63 // Must first call [[[NSWindow alloc] init] release] to get the NSWindow machinery set up so that NSCursor can use a window to cache the cursor image
64 [[[NSWindow alloc] init] release];
65
66 deleteAutoReleasePool();
67
68 inited = true;
69 }
70}
71
72static NSAutoreleasePool *sPool = NULL;
73
74void createAutoReleasePool()
75{
76 if(!sPool)
77 {
78 sPool = [[NSAutoreleasePool alloc] init];
79 }
80}
81
82void deleteAutoReleasePool()
83{
84 if(sPool)
85 {
86 [sPool release];
87 sPool = NULL;
88 }
89}
diff --git a/linden/indra/llplugin/slplugin/slplugin.cpp b/linden/indra/llplugin/slplugin/slplugin.cpp
index 649d568..64c087b 100755
--- a/linden/indra/llplugin/slplugin/slplugin.cpp
+++ b/linden/indra/llplugin/slplugin/slplugin.cpp
@@ -46,6 +46,7 @@
46 46
47#if LL_DARWIN 47#if LL_DARWIN
48 #include <Carbon/Carbon.h> 48 #include <Carbon/Carbon.h>
49 #include "slplugin-objc.h"
49#endif 50#endif
50 51
51#if LL_DARWIN || LL_LINUX 52#if LL_DARWIN || LL_LINUX
@@ -53,7 +54,7 @@
53#endif 54#endif
54 55
55/* 56/*
56 On Mac OS, since we call WaitNextEvent, this process will show up in the dock unless we set the LSBackgroundOnly flag in the Info.plist. 57 On Mac OS, since we call WaitNextEvent, this process will show up in the dock unless we set the LSBackgroundOnly or LSUIElement flag in the Info.plist.
57 58
58 Normally non-bundled binaries don't have an info.plist file, but it's possible to embed one in the binary by adding this to the linker flags: 59 Normally non-bundled binaries don't have an info.plist file, but it's possible to embed one in the binary by adding this to the linker flags:
59 60
@@ -62,7 +63,8 @@
62 which means adding this to the gcc flags: 63 which means adding this to the gcc flags:
63 64
64 -Wl,-sectcreate,__TEXT,__info_plist,/path/to/slplugin_info.plist 65 -Wl,-sectcreate,__TEXT,__info_plist,/path/to/slplugin_info.plist
65 66
67 Now that SLPlugin is a bundled app on the Mac, this is no longer necessary (it can just use a regular Info.plist file), but I'm leaving this comment in for posterity.
66*/ 68*/
67 69
68#if LL_DARWIN || LL_LINUX 70#if LL_DARWIN || LL_LINUX
@@ -230,10 +232,19 @@ int main(int argc, char **argv)
230 signal(SIGSYS, &crash_handler); // non-existent system call invoked 232 signal(SIGSYS, &crash_handler); // non-existent system call invoked
231#endif 233#endif
232 234
235#if LL_DARWIN
236 setupCocoa();
237 createAutoReleasePool();
238#endif
239
233 LLPluginProcessChild *plugin = new LLPluginProcessChild(); 240 LLPluginProcessChild *plugin = new LLPluginProcessChild();
234 241
235 plugin->init(port); 242 plugin->init(port);
236 243
244#if LL_DARWIN
245 deleteAutoReleasePool();
246#endif
247
237 LLTimer timer; 248 LLTimer timer;
238 timer.start(); 249 timer.start();
239 250
@@ -242,10 +253,28 @@ int main(int argc, char **argv)
242#endif 253#endif
243 254
244#if LL_DARWIN 255#if LL_DARWIN
256 // If the plugin opens a new window (such as the Flash plugin's fullscreen player), we may need to bring this plugin process to the foreground.
257 // Use this to track the current frontmost window and bring this process to the front if it changes.
258 WindowRef front_window = NULL;
259 WindowGroupRef layer_group = NULL;
260 int window_hack_state = 0;
261 CreateWindowGroup(kWindowGroupAttrFixedLevel, &layer_group);
262 if(layer_group)
263 {
264 // Start out with a window layer that's way out in front (fixes the problem with the menubar not getting hidden on first switch to fullscreen youtube)
265 SetWindowGroupName(layer_group, CFSTR("SLPlugin Layer"));
266 SetWindowGroupLevel(layer_group, kCGOverlayWindowLevel);
267 }
268#endif
269
270#if LL_DARWIN
245 EventTargetRef event_target = GetEventDispatcherTarget(); 271 EventTargetRef event_target = GetEventDispatcherTarget();
246#endif 272#endif
247 while(!plugin->isDone()) 273 while(!plugin->isDone())
248 { 274 {
275#if LL_DARWIN
276 createAutoReleasePool();
277#endif
249 timer.reset(); 278 timer.reset();
250 plugin->idle(); 279 plugin->idle();
251#if LL_DARWIN 280#if LL_DARWIN
@@ -257,6 +286,80 @@ int main(int argc, char **argv)
257 SendEventToEventTarget (event, event_target); 286 SendEventToEventTarget (event, event_target);
258 ReleaseEvent(event); 287 ReleaseEvent(event);
259 } 288 }
289
290 // Check for a change in this process's frontmost window.
291 if(FrontWindow() != front_window)
292 {
293 ProcessSerialNumber self = { 0, kCurrentProcess };
294 ProcessSerialNumber parent = { 0, kNoProcess };
295 ProcessSerialNumber front = { 0, kNoProcess };
296 Boolean this_is_front_process = false;
297 Boolean parent_is_front_process = false;
298 {
299 // Get this process's parent
300 ProcessInfoRec info;
301 info.processInfoLength = sizeof(ProcessInfoRec);
302 info.processName = NULL;
303 info.processAppSpec = NULL;
304 if(GetProcessInformation( &self, &info ) == noErr)
305 {
306 parent = info.processLauncher;
307 }
308
309 // and figure out whether this process or its parent are currently frontmost
310 if(GetFrontProcess(&front) == noErr)
311 {
312 (void) SameProcess(&self, &front, &this_is_front_process);
313 (void) SameProcess(&parent, &front, &parent_is_front_process);
314 }
315 }
316
317 if((FrontWindow() != NULL) && (front_window == NULL))
318 {
319 // Opening the first window
320
321 if(window_hack_state == 0)
322 {
323 // Next time through the event loop, lower the window group layer
324 window_hack_state = 1;
325 }
326
327 if(layer_group)
328 {
329 SetWindowGroup(FrontWindow(), layer_group);
330 }
331
332 if(parent_is_front_process)
333 {
334 // Bring this process's windows to the front.
335 (void) SetFrontProcess( &self );
336 }
337
338 ActivateWindow(FrontWindow(), true);
339 }
340 else if((FrontWindow() == NULL) && (front_window != NULL))
341 {
342 // Closing the last window
343
344 if(this_is_front_process)
345 {
346 // Try to bring this process's parent to the front
347 (void) SetFrontProcess(&parent);
348 }
349 }
350 else if(window_hack_state == 1)
351 {
352 if(layer_group)
353 {
354 // Set the window group level back to something less extreme
355 SetWindowGroupLevel(layer_group, kCGNormalWindowLevel);
356 }
357 window_hack_state = 2;
358 }
359
360 front_window = FrontWindow();
361
362 }
260 } 363 }
261#endif 364#endif
262 F64 elapsed = timer.getElapsedTimeF64(); 365 F64 elapsed = timer.getElapsedTimeF64();
@@ -289,6 +392,10 @@ int main(int argc, char **argv)
289 // exception handler such as QuickTime. 392 // exception handler such as QuickTime.
290 //checkExceptionHandler(); 393 //checkExceptionHandler();
291#endif 394#endif
395
396#if LL_DARWIN
397 deleteAutoReleasePool();
398#endif
292 } 399 }
293 400
294 delete plugin; 401 delete plugin;
diff --git a/linden/indra/llplugin/slplugin/slplugin_info.plist b/linden/indra/llplugin/slplugin/slplugin_info.plist
index b1daf87..c459738 100755
--- a/linden/indra/llplugin/slplugin/slplugin_info.plist
+++ b/linden/indra/llplugin/slplugin/slplugin_info.plist
@@ -6,7 +6,7 @@
6 <string>English</string> 6 <string>English</string>
7 <key>CFBundleInfoDictionaryVersion</key> 7 <key>CFBundleInfoDictionaryVersion</key>
8 <string>6.0</string> 8 <string>6.0</string>
9 <key>LSBackgroundOnly</key> 9 <key>LSUIElement</key>
10 <true/> 10 <string>1</string>
11</dict> 11</dict>
12</plist> 12</plist>
diff --git a/linden/indra/media_plugins/webkit/CMakeLists.txt b/linden/indra/media_plugins/webkit/CMakeLists.txt
index a47d5e0..2ab4a95 100644
--- a/linden/indra/media_plugins/webkit/CMakeLists.txt
+++ b/linden/indra/media_plugins/webkit/CMakeLists.txt
@@ -18,7 +18,7 @@ include(Linking)
18include(PluginAPI) 18include(PluginAPI)
19include(MediaPluginBase) 19include(MediaPluginBase)
20include(FindOpenGL) 20include(FindOpenGL)
21#include(PulseAudio) 21include(PulseAudio)
22 22
23include(WebKitLibPlugin) 23include(WebKitLibPlugin)
24 24
@@ -48,6 +48,9 @@ if(NOT CMAKE_SIZEOF_VOID_P MATCHES 4)
48 add_definitions(-fPIC) 48 add_definitions(-fPIC)
49 endif(WINDOWS) 49 endif(WINDOWS)
50endif (NOT CMAKE_SIZEOF_VOID_P MATCHES 4) 50endif (NOT CMAKE_SIZEOF_VOID_P MATCHES 4)
51set(media_plugin_webkit_HEADER_FILES
52 volume_catcher.h
53 )
51 54
52set(media_plugin_webkit_LINK_LIBRARIES 55set(media_plugin_webkit_LINK_LIBRARIES
53 ${LLPLUGIN_LIBRARIES} 56 ${LLPLUGIN_LIBRARIES}
@@ -58,13 +61,33 @@ set(media_plugin_webkit_LINK_LIBRARIES
58 ${PULSEAUDIO_LIBRARIES} 61 ${PULSEAUDIO_LIBRARIES}
59) 62)
60 63
64# Select which VolumeCatcher implementation to use
61if (LINUX) 65if (LINUX)
62 list(APPEND media_plugin_webkit_SOURCE_FILES linux_volume_catcher.cpp) 66 if (PULSEAUDIO)
67 list(APPEND media_plugin_webkit_SOURCE_FILES linux_volume_catcher.cpp)
68 endif (PULSEAUDIO)
63 list(APPEND media_plugin_webkit_LINK_LIBRARIES 69 list(APPEND media_plugin_webkit_LINK_LIBRARIES
64 ${UI_LIBRARIES} # for glib/GTK 70 ${UI_LIBRARIES} # for glib/GTK
65 ) 71 )
66endif(LINUX) 72elseif (DARWIN)
67 73 list(APPEND media_plugin_webkit_SOURCE_FILES mac_volume_catcher.cpp)
74 find_library(CORESERVICES_LIBRARY CoreServices)
75 find_library(AUDIOUNIT_LIBRARY AudioUnit)
76 list(APPEND media_plugin_webkit_LINK_LIBRARIES
77 ${CORESERVICES_LIBRARY} # for Component Manager calls
78 ${AUDIOUNIT_LIBRARY} # for AudioUnit calls
79 )
80elseif (WINDOWS)
81 list(APPEND media_plugin_webkit_SOURCE_FILES windows_volume_catcher.cpp)
82else (LINUX)
83 # All other platforms use the dummy volume catcher for now.
84 list(APPEND media_plugin_webkit_SOURCE_FILES dummy_volume_catcher.cpp)
85endif (LINUX)
86
87set_source_files_properties(${media_plugin_webkit_HEADER_FILES}
88 PROPERTIES HEADER_FILE_ONLY TRUE)
89
90list(APPEND media_plugin_webkit_SOURCE_FILES ${media_plugin_webkit_HEADER_FILES})
68 91
69add_library(media_plugin_webkit 92add_library(media_plugin_webkit
70 SHARED 93 SHARED
diff --git a/linden/indra/media_plugins/webkit/dummy_volume_catcher.cpp b/linden/indra/media_plugins/webkit/dummy_volume_catcher.cpp
new file mode 100644
index 0000000..4df9887
--- /dev/null
+++ b/linden/indra/media_plugins/webkit/dummy_volume_catcher.cpp
@@ -0,0 +1,65 @@
1/**
2 * @file dummy_volume_catcher.cpp
3 * @brief A null implementation of the "VolumeCatcher" class for platforms where it's not implemented yet.
4 *
5 * @cond
6 * $LicenseInfo:firstyear=2010&license=viewergpl$
7 *
8 * Copyright (c) 2010, Linden Research, Inc.
9 *
10 * Second Life Viewer Source Code
11 * The source code in this file ("Source Code") is provided by Linden Lab
12 * to you under the terms of the GNU General Public License, version 2.0
13 * ("GPL"), unless you have obtained a separate licensing agreement
14 * ("Other License"), formally executed by you and Linden Lab. Terms of
15 * the GPL can be found in doc/GPL-license.txt in this distribution, or
16 * online at http://secondlife.com/developers/opensource/gplv2
17 *
18 * There are special exceptions to the terms and conditions of the GPL as
19 * it is applied to this Source Code. View the full text of the exception
20 * in the file doc/FLOSS-exception.txt in this software distribution, or
21 * online at
22 * http://secondlife.com/developers/opensource/flossexception
23 *
24 * By copying, modifying or distributing this software, you acknowledge
25 * that you have read and understood your obligations described above,
26 * and agree to abide by those obligations.
27 *
28 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
29 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
30 * COMPLETENESS OR PERFORMANCE.
31 * $/LicenseInfo$
32 *
33 * @endcond
34 */
35
36#include "volume_catcher.h"
37
38
39class VolumeCatcherImpl
40{
41};
42
43/////////////////////////////////////////////////////
44
45VolumeCatcher::VolumeCatcher()
46{
47 pimpl = NULL;
48}
49
50VolumeCatcher::~VolumeCatcher()
51{
52}
53
54void VolumeCatcher::setVolume(F32 volume)
55{
56}
57
58void VolumeCatcher::setPan(F32 pan)
59{
60}
61
62void VolumeCatcher::pump()
63{
64}
65
diff --git a/linden/indra/media_plugins/webkit/linux_volume_catcher.cpp b/linden/indra/media_plugins/webkit/linux_volume_catcher.cpp
index 15a2dfb..c4c4181 100755..100644
--- a/linden/indra/media_plugins/webkit/linux_volume_catcher.cpp
+++ b/linden/indra/media_plugins/webkit/linux_volume_catcher.cpp
@@ -42,15 +42,16 @@
42 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 42 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
43 */ 43 */
44 44
45#include "linden_common.h" 45# include <set> //imprudence
46 46
47#include "linux_volume_catcher.h" 47#include "linden_common.h"
48 48
49#include "volume_catcher.h"
49 50
50#if LL_PULSEAUDIO_ENABLED
51 51
52extern "C" { 52extern "C" {
53#include <glib.h> 53#include <glib.h>
54#include <glib-object.h>
54 55
55#include <pulse/introspect.h> 56#include <pulse/introspect.h>
56#include <pulse/context.h> 57#include <pulse/context.h>
@@ -163,11 +164,11 @@ extern "C" {
163} 164}
164 165
165 166
166class LinuxVolumeCatcherImpl 167class VolumeCatcherImpl
167{ 168{
168public: 169public:
169 LinuxVolumeCatcherImpl(); 170 VolumeCatcherImpl();
170 ~LinuxVolumeCatcherImpl(); 171 ~VolumeCatcherImpl();
171 172
172 void setVolume(F32 volume); 173 void setVolume(F32 volume);
173 void pump(void); 174 void pump(void);
@@ -191,7 +192,7 @@ public:
191 bool mGotSyms; 192 bool mGotSyms;
192}; 193};
193 194
194LinuxVolumeCatcherImpl::LinuxVolumeCatcherImpl() 195VolumeCatcherImpl::VolumeCatcherImpl()
195 : mDesiredVolume(0.0f), 196 : mDesiredVolume(0.0f),
196 mMainloop(NULL), 197 mMainloop(NULL),
197 mPAContext(NULL), 198 mPAContext(NULL),
@@ -201,17 +202,17 @@ LinuxVolumeCatcherImpl::LinuxVolumeCatcherImpl()
201 init(); 202 init();
202} 203}
203 204
204LinuxVolumeCatcherImpl::~LinuxVolumeCatcherImpl() 205VolumeCatcherImpl::~VolumeCatcherImpl()
205{ 206{
206 cleanup(); 207 cleanup();
207} 208}
208 209
209bool LinuxVolumeCatcherImpl::loadsyms(std::string pulse_dso_name) 210bool VolumeCatcherImpl::loadsyms(std::string pulse_dso_name)
210{ 211{
211 return grab_pa_syms(pulse_dso_name); 212 return grab_pa_syms(pulse_dso_name);
212} 213}
213 214
214void LinuxVolumeCatcherImpl::init() 215void VolumeCatcherImpl::init()
215{ 216{
216 // try to be as defensive as possible because PA's interface is a 217 // try to be as defensive as possible because PA's interface is a
217 // bit fragile and (for our purposes) we'd rather simply not function 218 // bit fragile and (for our purposes) we'd rather simply not function
@@ -224,6 +225,10 @@ void LinuxVolumeCatcherImpl::init()
224 mGotSyms = loadsyms("libpulse-mainloop-glib.so.0"); 225 mGotSyms = loadsyms("libpulse-mainloop-glib.so.0");
225 if (!mGotSyms) return; 226 if (!mGotSyms) return;
226 227
228 // better make double-sure glib itself is initialized properly.
229 if (!g_thread_supported ()) g_thread_init (NULL);
230 g_type_init();
231
227 mMainloop = llpa_glib_mainloop_new(g_main_context_default()); 232 mMainloop = llpa_glib_mainloop_new(g_main_context_default());
228 if (mMainloop) 233 if (mMainloop)
229 { 234 {
@@ -264,7 +269,7 @@ void LinuxVolumeCatcherImpl::init()
264 } 269 }
265} 270}
266 271
267void LinuxVolumeCatcherImpl::cleanup() 272void VolumeCatcherImpl::cleanup()
268{ 273{
269 mConnected = false; 274 mConnected = false;
270 275
@@ -282,7 +287,7 @@ void LinuxVolumeCatcherImpl::cleanup()
282 mMainloop = NULL; 287 mMainloop = NULL;
283} 288}
284 289
285void LinuxVolumeCatcherImpl::setVolume(F32 volume) 290void VolumeCatcherImpl::setVolume(F32 volume)
286{ 291{
287 mDesiredVolume = volume; 292 mDesiredVolume = volume;
288 293
@@ -296,13 +301,13 @@ void LinuxVolumeCatcherImpl::setVolume(F32 volume)
296 pump(); 301 pump();
297} 302}
298 303
299void LinuxVolumeCatcherImpl::pump() 304void VolumeCatcherImpl::pump()
300{ 305{
301 gboolean may_block = FALSE; 306 gboolean may_block = FALSE;
302 g_main_context_iteration(g_main_context_default(), may_block); 307 g_main_context_iteration(g_main_context_default(), may_block);
303} 308}
304 309
305void LinuxVolumeCatcherImpl::connected_okay() 310void VolumeCatcherImpl::connected_okay()
306{ 311{
307 pa_operation *op; 312 pa_operation *op;
308 313
@@ -326,7 +331,7 @@ void LinuxVolumeCatcherImpl::connected_okay()
326 } 331 }
327} 332}
328 333
329void LinuxVolumeCatcherImpl::update_all_volumes(F32 volume) 334void VolumeCatcherImpl::update_all_volumes(F32 volume)
330{ 335{
331 for (std::set<U32>::iterator it = mSinkInputIndices.begin(); 336 for (std::set<U32>::iterator it = mSinkInputIndices.begin();
332 it != mSinkInputIndices.end(); ++it) 337 it != mSinkInputIndices.end(); ++it)
@@ -335,7 +340,7 @@ void LinuxVolumeCatcherImpl::update_all_volumes(F32 volume)
335 } 340 }
336} 341}
337 342
338void LinuxVolumeCatcherImpl::update_index_volume(U32 index, F32 volume) 343void VolumeCatcherImpl::update_index_volume(U32 index, F32 volume)
339{ 344{
340 static pa_cvolume cvol; 345 static pa_cvolume cvol;
341 llpa_cvolume_set(&cvol, mSinkInputNumChannels[index], 346 llpa_cvolume_set(&cvol, mSinkInputNumChannels[index],
@@ -357,7 +362,7 @@ void LinuxVolumeCatcherImpl::update_index_volume(U32 index, F32 volume)
357 362
358void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *sii, int eol, void *userdata) 363void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info *sii, int eol, void *userdata)
359{ 364{
360 LinuxVolumeCatcherImpl *impl = dynamic_cast<LinuxVolumeCatcherImpl*>((LinuxVolumeCatcherImpl*)userdata); 365 VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata);
361 llassert(impl); 366 llassert(impl);
362 367
363 if (0 == eol) 368 if (0 == eol)
@@ -388,7 +393,7 @@ void callback_discovered_sinkinput(pa_context *context, const pa_sink_input_info
388 393
389void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata) 394void callback_subscription_alert(pa_context *context, pa_subscription_event_type_t t, uint32_t index, void *userdata)
390{ 395{
391 LinuxVolumeCatcherImpl *impl = dynamic_cast<LinuxVolumeCatcherImpl*>((LinuxVolumeCatcherImpl*)userdata); 396 VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata);
392 llassert(impl); 397 llassert(impl);
393 398
394 switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { 399 switch (t & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
@@ -422,7 +427,7 @@ void callback_subscription_alert(pa_context *context, pa_subscription_event_type
422 427
423void callback_context_state(pa_context *context, void *userdata) 428void callback_context_state(pa_context *context, void *userdata)
424{ 429{
425 LinuxVolumeCatcherImpl *impl = dynamic_cast<LinuxVolumeCatcherImpl*>((LinuxVolumeCatcherImpl*)userdata); 430 VolumeCatcherImpl *impl = dynamic_cast<VolumeCatcherImpl*>((VolumeCatcherImpl*)userdata);
426 llassert(impl); 431 llassert(impl);
427 432
428 switch (llpa_context_get_state(context)) 433 switch (llpa_context_get_state(context))
@@ -443,48 +448,30 @@ void callback_context_state(pa_context *context, void *userdata)
443 448
444///////////////////////////////////////////////////// 449/////////////////////////////////////////////////////
445 450
446LinuxVolumeCatcher::LinuxVolumeCatcher() 451VolumeCatcher::VolumeCatcher()
447{ 452{
448 pimpl = new LinuxVolumeCatcherImpl(); 453 pimpl = new VolumeCatcherImpl();
449} 454}
450 455
451LinuxVolumeCatcher::~LinuxVolumeCatcher() 456VolumeCatcher::~VolumeCatcher()
452{ 457{
453 delete pimpl; 458 delete pimpl;
454 pimpl = NULL; 459 pimpl = NULL;
455} 460}
456 461
457void LinuxVolumeCatcher::setVolume(F32 volume) 462void VolumeCatcher::setVolume(F32 volume)
458{ 463{
459 llassert(pimpl); 464 llassert(pimpl);
460 pimpl->setVolume(volume); 465 pimpl->setVolume(volume);
461} 466}
462 467
463void LinuxVolumeCatcher::pump() 468void VolumeCatcher::setPan(F32 pan)
464{
465 llassert(pimpl);
466 pimpl->pump();
467}
468
469#else // !LL_PULSEAUDIO_ENABLED
470
471// stub.
472
473LinuxVolumeCatcher::LinuxVolumeCatcher()
474{
475 pimpl = NULL;
476}
477
478LinuxVolumeCatcher::~LinuxVolumeCatcher()
479{
480}
481
482void LinuxVolumeCatcher::setVolume(F32 volume)
483{ 469{
470 // TODO: implement this (if possible)
484} 471}
485 472
486void LinuxVolumeCatcher::pump() 473void VolumeCatcher::pump()
487{ 474{
475 llassert(pimpl);
476 pimpl->pump();
488} 477}
489
490#endif // LL_PULSEAUDIO_ENABLED
diff --git a/linden/indra/media_plugins/webkit/linux_volume_catcher_pa_syms.inc b/linden/indra/media_plugins/webkit/linux_volume_catcher_pa_syms.inc
index d806b48..d806b48 100755..100644
--- a/linden/indra/media_plugins/webkit/linux_volume_catcher_pa_syms.inc
+++ b/linden/indra/media_plugins/webkit/linux_volume_catcher_pa_syms.inc
diff --git a/linden/indra/media_plugins/webkit/linux_volume_catcher_paglib_syms.inc b/linden/indra/media_plugins/webkit/linux_volume_catcher_paglib_syms.inc
index abf628c..abf628c 100755..100644
--- a/linden/indra/media_plugins/webkit/linux_volume_catcher_paglib_syms.inc
+++ b/linden/indra/media_plugins/webkit/linux_volume_catcher_paglib_syms.inc
diff --git a/linden/indra/media_plugins/webkit/mac_volume_catcher.cpp b/linden/indra/media_plugins/webkit/mac_volume_catcher.cpp
new file mode 100644
index 0000000..190823f
--- /dev/null
+++ b/linden/indra/media_plugins/webkit/mac_volume_catcher.cpp
@@ -0,0 +1,275 @@
1/**
2 * @file mac_volume_catcher.cpp
3 * @brief A Mac OS X specific hack to control the volume level of all audio channels opened by a process.
4 *
5 * @cond
6 * $LicenseInfo:firstyear=2010&license=viewergpl$
7 *
8 * Copyright (c) 2010, Linden Research, Inc.
9 *
10 * Second Life Viewer Source Code
11 * The source code in this file ("Source Code") is provided by Linden Lab
12 * to you under the terms of the GNU General Public License, version 2.0
13 * ("GPL"), unless you have obtained a separate licensing agreement
14 * ("Other License"), formally executed by you and Linden Lab. Terms of
15 * the GPL can be found in doc/GPL-license.txt in this distribution, or
16 * online at http://secondlife.com/developers/opensource/gplv2
17 *
18 * There are special exceptions to the terms and conditions of the GPL as
19 * it is applied to this Source Code. View the full text of the exception
20 * in the file doc/FLOSS-exception.txt in this software distribution, or
21 * online at
22 * http://secondlife.com/developers/opensource/flossexception
23 *
24 * By copying, modifying or distributing this software, you acknowledge
25 * that you have read and understood your obligations described above,
26 * and agree to abide by those obligations.
27 *
28 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
29 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
30 * COMPLETENESS OR PERFORMANCE.
31 * $/LicenseInfo$
32 *
33 * @endcond
34 */
35
36/**************************************************************************************************************
37 This code works by using CaptureComponent to capture the "Default Output" audio component
38 (kAudioUnitType_Output/kAudioUnitSubType_DefaultOutput) and delegating all calls to the original component.
39 It does this just to keep track of all instances of the default output component, so that it can set the
40 kHALOutputParam_Volume parameter on all of them to adjust the output volume.
41**************************************************************************************************************/
42
43#include "volume_catcher.h"
44
45#include <Carbon/Carbon.h>
46#include <QuickTime/QuickTime.h>
47#include <AudioUnit/AudioUnit.h>
48
49struct VolumeCatcherStorage;
50
51class VolumeCatcherImpl
52{
53public:
54
55 void setVolume(F32 volume);
56 void setPan(F32 pan);
57
58 void setInstanceVolume(VolumeCatcherStorage *instance);
59
60 std::list<VolumeCatcherStorage*> mComponentInstances;
61 Component mOriginalDefaultOutput;
62 Component mVolumeAdjuster;
63
64 static VolumeCatcherImpl *getInstance();
65private:
66 // This is a singleton class -- both callers and the component implementation should use getInstance() to find the instance.
67 VolumeCatcherImpl();
68 static VolumeCatcherImpl *sInstance;
69
70 // The singlar instance of this class is expected to last until the process exits.
71 // To ensure this, we declare the destructor here but never define it, so any code which attempts to destroy the instance will not link.
72 ~VolumeCatcherImpl();
73
74 F32 mVolume;
75 F32 mPan;
76};
77
78VolumeCatcherImpl *VolumeCatcherImpl::sInstance = NULL;;
79
80struct VolumeCatcherStorage
81{
82 ComponentInstance self;
83 ComponentInstance delegate;
84};
85
86static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, Handle componentStorage);
87static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self);
88static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *storage, ComponentInstance self);
89
90VolumeCatcherImpl *VolumeCatcherImpl::getInstance()
91{
92 if(!sInstance)
93 {
94 sInstance = new VolumeCatcherImpl;
95 }
96
97 return sInstance;
98}
99
100VolumeCatcherImpl::VolumeCatcherImpl()
101{
102 mVolume = 1.0; // default to full volume
103 mPan = 0.0; // and center pan
104
105 ComponentDescription desc;
106 desc.componentType = kAudioUnitType_Output;
107 desc.componentSubType = kAudioUnitSubType_DefaultOutput;
108 desc.componentManufacturer = kAudioUnitManufacturer_Apple;
109 desc.componentFlags = 0;
110 desc.componentFlagsMask = 0;
111
112 // Find the original default output component
113 mOriginalDefaultOutput = FindNextComponent(NULL, &desc);
114
115 // Register our own output component with the same parameters
116 mVolumeAdjuster = RegisterComponent(&desc, NewComponentRoutineUPP(volume_catcher_component_entry), 0, NULL, NULL, NULL);
117
118 // Capture the original component, so we always get found instead.
119 CaptureComponent(mOriginalDefaultOutput, mVolumeAdjuster);
120
121}
122
123static ComponentResult volume_catcher_component_entry(ComponentParameters *cp, Handle componentStorage)
124{
125 ComponentResult result = badComponentSelector;
126 VolumeCatcherStorage *storage = (VolumeCatcherStorage*)componentStorage;
127
128 switch(cp->what)
129 {
130 case kComponentOpenSelect:
131// std::cerr << "kComponentOpenSelect" << std::endl;
132 result = CallComponentFunctionWithStorageProcInfo((Handle)storage, cp, (ProcPtr)volume_catcher_component_open, uppCallComponentOpenProcInfo);
133 break;
134
135 case kComponentCloseSelect:
136// std::cerr << "kComponentCloseSelect" << std::endl;
137 result = CallComponentFunctionWithStorageProcInfo((Handle)storage, cp, (ProcPtr)volume_catcher_component_close, uppCallComponentCloseProcInfo);
138 // CallComponentFunctionWithStorageProcInfo
139 break;
140
141 default:
142// std::cerr << "Delegating selector: " << cp->what << " to component instance " << storage->delegate << std::endl;
143 result = DelegateComponentCall(cp, storage->delegate);
144 break;
145 }
146
147 return result;
148}
149
150static ComponentResult volume_catcher_component_open(VolumeCatcherStorage *storage, ComponentInstance self)
151{
152 ComponentResult result = noErr;
153 VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
154
155 storage = new VolumeCatcherStorage;
156
157 storage->self = self;
158 storage->delegate = NULL;
159
160 result = OpenAComponent(impl->mOriginalDefaultOutput, &(storage->delegate));
161
162 if(result != noErr)
163 {
164// std::cerr << "OpenAComponent result = " << result << ", component ref = " << storage->delegate << std::endl;
165
166 // If we failed to open the delagate component, our open is going to fail. Clean things up.
167 delete storage;
168 }
169 else
170 {
171 // Success -- set up this component's storage
172 SetComponentInstanceStorage(self, (Handle)storage);
173
174 // add this instance to the global list
175 impl->mComponentInstances.push_back(storage);
176
177 // and set up the initial volume
178 impl->setInstanceVolume(storage);
179 }
180
181 return result;
182}
183
184static ComponentResult volume_catcher_component_close(VolumeCatcherStorage *storage, ComponentInstance self)
185{
186 ComponentResult result = noErr;
187
188 if(storage)
189 {
190 if(storage->delegate)
191 {
192 CloseComponent(storage->delegate);
193 storage->delegate = NULL;
194 }
195
196 VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
197 impl->mComponentInstances.remove(storage);
198 delete[] storage;
199 }
200
201 return result;
202}
203
204void VolumeCatcherImpl::setVolume(F32 volume)
205{
206 VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
207 impl->mVolume = volume;
208
209 // Iterate through all known instances, setting the volume on each.
210 for(std::list<VolumeCatcherStorage*>::iterator iter = mComponentInstances.begin(); iter != mComponentInstances.end(); ++iter)
211 {
212 impl->setInstanceVolume(*iter);
213 }
214}
215
216void VolumeCatcherImpl::setPan(F32 pan)
217{
218 VolumeCatcherImpl *impl = VolumeCatcherImpl::getInstance();
219 impl->mPan = pan;
220
221 // TODO: implement this.
222 // This will probably require adding a "panner" audio unit to the chain somehow.
223 // There's also a "3d mixer" component that we might be able to use...
224}
225
226void VolumeCatcherImpl::setInstanceVolume(VolumeCatcherStorage *instance)
227{
228// std::cerr << "Setting volume on component instance: " << (instance->delegate) << " to " << mVolume << std::endl;
229
230 OSStatus err = noErr;
231
232 if(instance && instance->delegate)
233 {
234 err = AudioUnitSetParameter(
235 instance->delegate,
236 kHALOutputParam_Volume,
237 kAudioUnitScope_Global,
238 0,
239 mVolume,
240 0);
241 }
242
243 if(err)
244 {
245// std::cerr << " AudioUnitSetParameter returned " << err << std::endl;
246 }
247}
248
249/////////////////////////////////////////////////////
250
251VolumeCatcher::VolumeCatcher()
252{
253 pimpl = VolumeCatcherImpl::getInstance();
254}
255
256VolumeCatcher::~VolumeCatcher()
257{
258 // Let the instance persist until exit.
259}
260
261void VolumeCatcher::setVolume(F32 volume)
262{
263 pimpl->setVolume(volume);
264}
265
266void VolumeCatcher::setPan(F32 pan)
267{
268 pimpl->setPan(pan);
269}
270
271void VolumeCatcher::pump()
272{
273 // No periodic tasks are necessary for this implementation.
274}
275
diff --git a/linden/indra/media_plugins/webkit/media_plugin_webkit.cpp b/linden/indra/media_plugins/webkit/media_plugin_webkit.cpp
index 09ba8dc..f9b6451 100755
--- a/linden/indra/media_plugins/webkit/media_plugin_webkit.cpp
+++ b/linden/indra/media_plugins/webkit/media_plugin_webkit.cpp
@@ -49,13 +49,15 @@
49#if LL_LINUX 49#if LL_LINUX
50# include <iomanip> 50# include <iomanip>
51# define LL_QTWEBKIT_USES_PIXMAPS 0 51# define LL_QTWEBKIT_USES_PIXMAPS 0
52extern "C" {
53# include <glib.h>
54# include <glib-object.h>
55}
52#else 56#else
53# define LL_QTWEBKIT_USES_PIXMAPS 0 57# define LL_QTWEBKIT_USES_PIXMAPS 0
54#endif // LL_LINUX 58#endif // LL_LINUX
55 59
56#if LL_LINUX 60# include "volume_catcher.h"
57# include "linux_volume_catcher.h"
58#endif // LL_LINUX
59 61
60#if LL_WINDOWS 62#if LL_WINDOWS
61# include <direct.h> 63# include <direct.h>
@@ -65,7 +67,7 @@
65#endif 67#endif
66 68
67#if LL_WINDOWS 69#if LL_WINDOWS
68 // *NOTE:Mani - This captures the module handle fo rthe dll. This is used below 70 // *NOTE:Mani - This captures the module handle for the dll. This is used below
69 // to get the path to this dll for webkit initialization. 71 // to get the path to this dll for webkit initialization.
70 // I don't know how/if this can be done with apr... 72 // I don't know how/if this can be done with apr...
71 namespace { HMODULE gModuleHandle;}; 73 namespace { HMODULE gModuleHandle;};
@@ -122,9 +124,7 @@ private:
122 F32 mBackgroundG; 124 F32 mBackgroundG;
123 F32 mBackgroundB; 125 F32 mBackgroundB;
124 126
125#if LL_LINUX 127 VolumeCatcher mVolumeCatcher;
126 LinuxVolumeCatcher mLinuxVolumeCatcher;
127#endif // LL_LINUX
128 128
129 void setInitState(int state) 129 void setInitState(int state)
130 { 130 {
@@ -136,11 +136,19 @@ private:
136 // 136 //
137 void update(int milliseconds) 137 void update(int milliseconds)
138 { 138 {
139#if LL_QTLINUX_DOESNT_HAVE_GLIB
140 // pump glib generously, as Linux browser plugins are on the
141 // glib main loop, even if the browser itself isn't - ugh
142 // This is NOT NEEDED if Qt itself was built with glib
143 // mainloop integration.
144 GMainContext *mainc = g_main_context_default();
145 while(g_main_context_iteration(mainc, FALSE));
146#endif // LL_QTLINUX_DOESNT_HAVE_GLIB
147
148 // pump qt
139 LLQtWebKit::getInstance()->pump( milliseconds ); 149 LLQtWebKit::getInstance()->pump( milliseconds );
140 150
141#if LL_LINUX 151 mVolumeCatcher.pump();
142 mLinuxVolumeCatcher.pump();
143#endif // LL_LINUX
144 152
145 checkEditState(); 153 checkEditState();
146 154
@@ -209,6 +217,14 @@ private:
209 } 217 }
210 std::string application_dir = std::string( cwd ); 218 std::string application_dir = std::string( cwd );
211 219
220#if LL_LINUX
221 // take care to initialize glib properly, because some
222 // versions of Qt don't, and we indirectly need it for (some
223 // versions of) Flash to not crash the browser.
224 if (!g_thread_supported ()) g_thread_init (NULL);
225 g_type_init();
226#endif
227
212#if LL_DARWIN 228#if LL_DARWIN
213 // When running under the Xcode debugger, there's a setting called "Break on Debugger()/DebugStr()" which defaults to being turned on. 229 // When running under the Xcode debugger, there's a setting called "Break on Debugger()/DebugStr()" which defaults to being turned on.
214 // This causes the environment variable USERBREAK to be set to 1, which causes these legacy calls to break into the debugger. 230 // This causes the environment variable USERBREAK to be set to 1, which causes these legacy calls to break into the debugger.
@@ -306,11 +322,14 @@ private:
306 // append details to agent string 322 // append details to agent string
307 LLQtWebKit::getInstance()->setBrowserAgentId( mUserAgent ); 323 LLQtWebKit::getInstance()->setBrowserAgentId( mUserAgent );
308 324
325 // Set up window open behavior
326 LLQtWebKit::getInstance()->setWindowOpenBehavior(mBrowserWindowId, LLQtWebKit::WOB_SIMULATE_BLANK_HREF_CLICK);
327
309#if !LL_QTWEBKIT_USES_PIXMAPS 328#if !LL_QTWEBKIT_USES_PIXMAPS
310 // don't flip bitmap 329 // don't flip bitmap
311 LLQtWebKit::getInstance()->flipWindow( mBrowserWindowId, true ); 330 LLQtWebKit::getInstance()->flipWindow( mBrowserWindowId, true );
312#endif // !LL_QTWEBKIT_USES_PIXMAPS 331#endif // !LL_QTWEBKIT_USES_PIXMAPS
313 332
314 // set background color 333 // set background color
315 // convert background color channels from [0.0, 1.0] to [0, 255]; 334 // convert background color channels from [0.0, 1.0] to [0, 255];
316 LLQtWebKit::getInstance()->setBackgroundColor( mBrowserWindowId, int(mBackgroundR * 255.0f), int(mBackgroundG * 255.0f), int(mBackgroundB * 255.0f) ); 335 LLQtWebKit::getInstance()->setBackgroundColor( mBrowserWindowId, int(mBackgroundR * 255.0f), int(mBackgroundG * 255.0f), int(mBackgroundB * 255.0f) );
@@ -511,6 +530,19 @@ private:
511 sendMessage(message); 530 sendMessage(message);
512 } 531 }
513 532
533
534 ////////////////////////////////////////////////////////////////////////////////
535 // virtual
536 void onCookieChanged(const EventType& event)
537 {
538 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "cookie_set");
539 message.setValue("cookie", event.getStringValue());
540 // These could be passed through as well, but aren't really needed.
541// message.setValue("uri", event.getEventUri());
542// message.setValueBoolean("dead", (event.getIntValue() != 0))
543 sendMessage(message);
544 }
545
514 LLQtWebKit::EKeyboardModifier decodeModifiers(std::string &modifiers) 546 LLQtWebKit::EKeyboardModifier decodeModifiers(std::string &modifiers)
515 { 547 {
516 int result = 0; 548 int result = 0;
@@ -1056,6 +1088,10 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)
1056 mJavascriptEnabled = message_in.getValueBoolean("enable"); 1088 mJavascriptEnabled = message_in.getValueBoolean("enable");
1057 //LLQtWebKit::getInstance()->enableJavascript( mJavascriptEnabled ); 1089 //LLQtWebKit::getInstance()->enableJavascript( mJavascriptEnabled );
1058 } 1090 }
1091 else if(message_name == "set_cookies")
1092 {
1093 LLQtWebKit::getInstance()->setCookies(message_in.getValue("cookies"));
1094 }
1059 else if(message_name == "proxy_setup") 1095 else if(message_name == "proxy_setup")
1060 { 1096 {
1061 bool val = message_in.getValueBoolean("enable"); 1097 bool val = message_in.getValueBoolean("enable");
@@ -1125,9 +1161,7 @@ void MediaPluginWebKit::receiveMessage(const char *message_string)
1125 1161
1126void MediaPluginWebKit::setVolume(F32 volume) 1162void MediaPluginWebKit::setVolume(F32 volume)
1127{ 1163{
1128#if LL_LINUX 1164 mVolumeCatcher.setVolume(volume);
1129 mLinuxVolumeCatcher.setVolume(volume);
1130#endif // LL_LINUX
1131} 1165}
1132 1166
1133int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data) 1167int init_media_plugin(LLPluginInstance::sendMessageFunction host_send_func, void *host_user_data, LLPluginInstance::sendMessageFunction *plugin_send_func, void **plugin_user_data)
diff --git a/linden/indra/media_plugins/webkit/volume_catcher.h b/linden/indra/media_plugins/webkit/volume_catcher.h
new file mode 100644
index 0000000..855e99f
--- /dev/null
+++ b/linden/indra/media_plugins/webkit/volume_catcher.h
@@ -0,0 +1,61 @@
1/**
2 * @file volume_catcher.h
3 * @brief Interface to a class with platform-specific implementations that allows control of the audio volume of all sources in the current process.
4 *
5 * @cond
6 * $LicenseInfo:firstyear=2010&license=viewergpl$
7 *
8 * Copyright (c) 2010, Linden Research, Inc.
9 *
10 * Second Life Viewer Source Code
11 * The source code in this file ("Source Code") is provided by Linden Lab
12 * to you under the terms of the GNU General Public License, version 2.0
13 * ("GPL"), unless you have obtained a separate licensing agreement
14 * ("Other License"), formally executed by you and Linden Lab. Terms of
15 * the GPL can be found in doc/GPL-license.txt in this distribution, or
16 * online at http://secondlife.com/developers/opensource/gplv2
17 *
18 * There are special exceptions to the terms and conditions of the GPL as
19 * it is applied to this Source Code. View the full text of the exception
20 * in the file doc/FLOSS-exception.txt in this software distribution, or
21 * online at
22 * http://secondlife.com/developers/opensource/flossexception
23 *
24 * By copying, modifying or distributing this software, you acknowledge
25 * that you have read and understood your obligations described above,
26 * and agree to abide by those obligations.
27 *
28 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
29 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
30 * COMPLETENESS OR PERFORMANCE.
31 * $/LicenseInfo$
32 *
33 * @endcond
34 */
35
36#ifndef VOLUME_CATCHER_H
37#define VOLUME_CATCHER_H
38
39#include "linden_common.h"
40
41class VolumeCatcherImpl;
42
43class VolumeCatcher
44{
45 public:
46 VolumeCatcher();
47 ~VolumeCatcher();
48
49 void setVolume(F32 volume); // 0.0 - 1.0
50
51 // Set the left-right pan of audio sources
52 // where -1.0 = left, 0 = center, and 1.0 = right
53 void setPan(F32 pan);
54
55 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
56
57 private:
58 VolumeCatcherImpl *pimpl;
59};
60
61#endif // VOLUME_CATCHER_H
diff --git a/linden/indra/media_plugins/webkit/windows_volume_catcher.cpp b/linden/indra/media_plugins/webkit/windows_volume_catcher.cpp
new file mode 100644
index 0000000..c02e7fc
--- /dev/null
+++ b/linden/indra/media_plugins/webkit/windows_volume_catcher.cpp
@@ -0,0 +1,124 @@
1/**
2 * @file windows_volume_catcher.cpp
3 * @brief A Windows implementation of volume level control of all audio channels opened by a process.
4 *
5 * @cond
6 * $LicenseInfo:firstyear=2010&license=viewergpl$
7 *
8 * Copyright (c) 2010, Linden Research, Inc.
9 *
10 * Second Life Viewer Source Code
11 * The source code in this file ("Source Code") is provided by Linden Lab
12 * to you under the terms of the GNU General Public License, version 2.0
13 * ("GPL"), unless you have obtained a separate licensing agreement
14 * ("Other License"), formally executed by you and Linden Lab. Terms of
15 * the GPL can be found in doc/GPL-license.txt in this distribution, or
16 * online at http://secondlife.com/developers/opensource/gplv2
17 *
18 * There are special exceptions to the terms and conditions of the GPL as
19 * it is applied to this Source Code. View the full text of the exception
20 * in the file doc/FLOSS-exception.txt in this software distribution, or
21 * online at
22 * http://secondlife.com/developers/opensource/flossexception
23 *
24 * By copying, modifying or distributing this software, you acknowledge
25 * that you have read and understood your obligations described above,
26 * and agree to abide by those obligations.
27 *
28 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
29 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
30 * COMPLETENESS OR PERFORMANCE.
31 * $/LicenseInfo$
32 *
33 * @endcond
34 */
35
36#include "volume_catcher.h"
37#include <windows.h>
38#include "llsingleton.h"
39class VolumeCatcherImpl : public LLSingleton<VolumeCatcherImpl>
40{
41friend LLSingleton<VolumeCatcherImpl>;
42public:
43
44 void setVolume(F32 volume);
45 void setPan(F32 pan);
46
47private:
48 // This is a singleton class -- both callers and the component implementation should use getInstance() to find the instance.
49 VolumeCatcherImpl();
50 ~VolumeCatcherImpl();
51
52 typedef void (WINAPI *set_volume_func_t)(F32);
53 typedef void (WINAPI *set_mute_func_t)(bool);
54
55 set_volume_func_t mSetVolumeFunc;
56 set_mute_func_t mSetMuteFunc;
57
58 F32 mVolume;
59 F32 mPan;
60};
61VolumeCatcherImpl::VolumeCatcherImpl()
62: mVolume(1.0f), // default volume is max
63 mPan(0.f) // default pan is centered
64{
65 HMODULE handle = ::LoadLibrary(L"winmm.dll");
66 if(handle)
67 {
68 mSetVolumeFunc = (set_volume_func_t)::GetProcAddress(handle, "setPluginVolume");
69 mSetMuteFunc = (set_mute_func_t)::GetProcAddress(handle, "setPluginMute");
70 }
71}
72
73VolumeCatcherImpl::~VolumeCatcherImpl()
74{
75}
76
77
78void VolumeCatcherImpl::setVolume(F32 volume)
79{
80 mVolume = volume;
81
82 if (mSetMuteFunc)
83 {
84 mSetMuteFunc(volume == 0.f);
85 }
86 if (mSetVolumeFunc)
87 {
88 mSetVolumeFunc(mVolume);
89 }
90}
91
92void VolumeCatcherImpl::setPan(F32 pan)
93{ // remember pan for calculating individual channel levels later
94 mPan = pan;
95}
96
97/////////////////////////////////////////////////////
98
99VolumeCatcher::VolumeCatcher()
100{
101 pimpl = VolumeCatcherImpl::getInstance();
102}
103
104VolumeCatcher::~VolumeCatcher()
105{
106 // Let the instance persist until exit.
107}
108
109void VolumeCatcher::setVolume(F32 volume)
110{
111 pimpl->setVolume(volume);
112}
113
114void VolumeCatcher::setPan(F32 pan)
115{
116 pimpl->setPan(pan);
117}
118
119void VolumeCatcher::pump()
120{
121 // No periodic tasks are necessary for this implementation.
122}
123
124