diff options
author | Jacek Antonelli | 2011-05-08 15:13:37 -0500 |
---|---|---|
committer | Jacek Antonelli | 2011-05-08 15:50:49 -0500 |
commit | 7278f0254a3944bd2bcbf1e855fb0d90c3086a27 (patch) | |
tree | 5d7ecb81ebf1a07482f0a7d3d13acd7f18360c0b /linden/indra/llplugin | |
parent | Imprudence 1.3.1 released. (diff) | |
parent | Changed version to Experimental 2011.04.19 (diff) | |
download | meta-impy-7278f0254a3944bd2bcbf1e855fb0d90c3086a27.zip meta-impy-7278f0254a3944bd2bcbf1e855fb0d90c3086a27.tar.gz meta-impy-7278f0254a3944bd2bcbf1e855fb0d90c3086a27.tar.bz2 meta-impy-7278f0254a3944bd2bcbf1e855fb0d90c3086a27.tar.xz |
Merged Experimental branch (exp) back into main line (next).
Git thought many files (almost 100) had merge conflicts. But, after
resolving the conflicts (which were mostly trivial), almost all the
files turned out to be the same as in the exp branch. So, the
conflicts are not listed here. Check the diff between commit 244ffe8
and this commit to see what really changed.
Diffstat (limited to 'linden/indra/llplugin')
24 files changed, 7380 insertions, 0 deletions
diff --git a/linden/indra/llplugin/CMakeLists.txt b/linden/indra/llplugin/CMakeLists.txt new file mode 100644 index 0000000..5dbe07c --- /dev/null +++ b/linden/indra/llplugin/CMakeLists.txt | |||
@@ -0,0 +1,82 @@ | |||
1 | # -*- cmake -*- | ||
2 | |||
3 | project(llplugin) | ||
4 | |||
5 | include(00-Common) | ||
6 | include(CURL) | ||
7 | include(LLCommon) | ||
8 | include(LLImage) | ||
9 | include(LLMath) | ||
10 | include(LLMessage) | ||
11 | include(LLRender) | ||
12 | include(LLXML) | ||
13 | include(LLWindow) | ||
14 | |||
15 | include_directories( | ||
16 | ${LLCOMMON_INCLUDE_DIRS} | ||
17 | ${LLIMAGE_INCLUDE_DIRS} | ||
18 | ${LLMATH_INCLUDE_DIRS} | ||
19 | ${LLMESSAGE_INCLUDE_DIRS} | ||
20 | ${LLRENDER_INCLUDE_DIRS} | ||
21 | ${LLXML_INCLUDE_DIRS} | ||
22 | ${LLWINDOW_INCLUDE_DIRS} | ||
23 | ) | ||
24 | |||
25 | set(llplugin_SOURCE_FILES | ||
26 | llpluginclassmedia.cpp | ||
27 | llplugincookiestore.cpp | ||
28 | llplugininstance.cpp | ||
29 | llpluginmessage.cpp | ||
30 | llpluginmessagepipe.cpp | ||
31 | llpluginprocesschild.cpp | ||
32 | llpluginprocessparent.cpp | ||
33 | llpluginsharedmemory.cpp | ||
34 | ) | ||
35 | |||
36 | set(llplugin_HEADER_FILES | ||
37 | CMakeLists.txt | ||
38 | |||
39 | llpluginclassmedia.h | ||
40 | llpluginclassmediaowner.h | ||
41 | llplugincookiestore.h | ||
42 | llplugininstance.h | ||
43 | llpluginmessage.h | ||
44 | llpluginmessageclasses.h | ||
45 | llpluginmessagepipe.h | ||
46 | llpluginprocesschild.h | ||
47 | llpluginprocessparent.h | ||
48 | llpluginsharedmemory.h | ||
49 | ) | ||
50 | |||
51 | set_source_files_properties(${llplugin_HEADER_FILES} | ||
52 | PROPERTIES HEADER_FILE_ONLY TRUE) | ||
53 | |||
54 | if(NOT CMAKE_SIZEOF_VOID_P MATCHES 4) | ||
55 | if(WINDOWS) | ||
56 | add_definitions(/FIXED:NO) | ||
57 | else(WINDOWS) # not windows therefore gcc LINUX and DARWIN | ||
58 | add_definitions(-fPIC) | ||
59 | endif(WINDOWS) | ||
60 | endif (NOT CMAKE_SIZEOF_VOID_P MATCHES 4) | ||
61 | |||
62 | list(APPEND llplugin_SOURCE_FILES ${llplugin_HEADER_FILES}) | ||
63 | |||
64 | add_library (llplugin ${llplugin_SOURCE_FILES}) | ||
65 | |||
66 | add_subdirectory(slplugin) | ||
67 | |||
68 | # # Add tests | ||
69 | # include(LLAddBuildTest) | ||
70 | # # UNIT TESTS | ||
71 | # SET(llplugin_TEST_SOURCE_FILES | ||
72 | # llplugincookiestore.cpp | ||
73 | # ) | ||
74 | # | ||
75 | # # llplugincookiestore has a dependency on curl, so we need to link the curl library into the test. | ||
76 | # set_source_files_properties( | ||
77 | # llplugincookiestore.cpp | ||
78 | # PROPERTIES | ||
79 | # LL_TEST_ADDITIONAL_LIBRARIES "${CURL_LIBRARIES}" | ||
80 | # ) | ||
81 | # | ||
82 | # LL_ADD_PROJECT_UNIT_TESTS(llplugin "${llplugin_TEST_SOURCE_FILES}") | ||
diff --git a/linden/indra/llplugin/llpluginclassmedia.cpp b/linden/indra/llplugin/llpluginclassmedia.cpp new file mode 100755 index 0000000..f0a44f7 --- /dev/null +++ b/linden/indra/llplugin/llpluginclassmedia.cpp | |||
@@ -0,0 +1,1236 @@ | |||
1 | /** | ||
2 | * @file llpluginclassmedia.cpp | ||
3 | * @brief LLPluginClassMedia handles a plugin which knows about the "media" message class. | ||
4 | * | ||
5 | * @cond | ||
6 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
7 | * | ||
8 | * Copyright (c) 2008-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 | /// IMPRUDENCE: this is part of the viewer | ||
37 | |||
38 | #include "linden_common.h" | ||
39 | #include "indra_constants.h" | ||
40 | |||
41 | #include "llpluginclassmedia.h" | ||
42 | #include "llpluginmessageclasses.h" | ||
43 | |||
44 | static int LOW_PRIORITY_TEXTURE_SIZE_DEFAULT = 256; | ||
45 | |||
46 | static int nextPowerOf2( int value ) | ||
47 | { | ||
48 | int next_power_of_2 = 1; | ||
49 | while ( next_power_of_2 < value ) | ||
50 | { | ||
51 | next_power_of_2 <<= 1; | ||
52 | } | ||
53 | |||
54 | return next_power_of_2; | ||
55 | } | ||
56 | |||
57 | LLPluginClassMedia::LLPluginClassMedia(LLPluginClassMediaOwner *owner) | ||
58 | { | ||
59 | mOwner = owner; | ||
60 | mPlugin = NULL; | ||
61 | reset(); | ||
62 | |||
63 | //debug use | ||
64 | mDeleteOK = true ; | ||
65 | } | ||
66 | |||
67 | |||
68 | LLPluginClassMedia::~LLPluginClassMedia() | ||
69 | { | ||
70 | llassert_always(mDeleteOK) ; | ||
71 | reset(); | ||
72 | } | ||
73 | |||
74 | bool LLPluginClassMedia::init(const std::string &launcher_filename, const std::string &plugin_filename, bool debug) | ||
75 | { | ||
76 | LL_DEBUGS("PluginClassMedia") << "launcher: " << launcher_filename << LL_ENDL; | ||
77 | LL_DEBUGS("PluginClassMedia") << "plugin: " << plugin_filename << LL_ENDL; | ||
78 | |||
79 | mPlugin = new LLPluginProcessParent(this); | ||
80 | mPlugin->setSleepTime(mSleepTime); | ||
81 | |||
82 | // Queue up the media init message -- it will be sent after all the currently queued messages. | ||
83 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "init"); | ||
84 | sendMessage(message); | ||
85 | |||
86 | mPlugin->init(launcher_filename, plugin_filename, debug); | ||
87 | |||
88 | return true; | ||
89 | } | ||
90 | |||
91 | |||
92 | void LLPluginClassMedia::reset() | ||
93 | { | ||
94 | if(mPlugin != NULL) | ||
95 | { | ||
96 | delete mPlugin; | ||
97 | mPlugin = NULL; | ||
98 | } | ||
99 | |||
100 | mTextureParamsReceived = false; | ||
101 | mRequestedTextureDepth = 0; | ||
102 | mRequestedTextureInternalFormat = 0; | ||
103 | mRequestedTextureFormat = 0; | ||
104 | mRequestedTextureType = 0; | ||
105 | mRequestedTextureSwapBytes = false; | ||
106 | mRequestedTextureCoordsOpenGL = false; | ||
107 | mTextureSharedMemorySize = 0; | ||
108 | mTextureSharedMemoryName.clear(); | ||
109 | mDefaultMediaWidth = 0; | ||
110 | mDefaultMediaHeight = 0; | ||
111 | mNaturalMediaWidth = 0; | ||
112 | mNaturalMediaHeight = 0; | ||
113 | mSetMediaWidth = -1; | ||
114 | mSetMediaHeight = -1; | ||
115 | mRequestedMediaWidth = 0; | ||
116 | mRequestedMediaHeight = 0; | ||
117 | mRequestedTextureWidth = 0; | ||
118 | mRequestedTextureHeight = 0; | ||
119 | mFullMediaWidth = 0; | ||
120 | mFullMediaHeight = 0; | ||
121 | mTextureWidth = 0; | ||
122 | mTextureHeight = 0; | ||
123 | mMediaWidth = 0; | ||
124 | mMediaHeight = 0; | ||
125 | mDirtyRect = LLRect::null; | ||
126 | mAutoScaleMedia = false; | ||
127 | mRequestedVolume = 1.0f; | ||
128 | mPriority = PRIORITY_NORMAL; | ||
129 | mLowPrioritySizeLimit = LOW_PRIORITY_TEXTURE_SIZE_DEFAULT; | ||
130 | mAllowDownsample = false; | ||
131 | mPadding = 0; | ||
132 | mLastMouseX = 0; | ||
133 | mLastMouseY = 0; | ||
134 | mStatus = LLPluginClassMediaOwner::MEDIA_NONE; | ||
135 | mSleepTime = 1.0f / 100.0f; | ||
136 | mCanCut = false; | ||
137 | mCanCopy = false; | ||
138 | mCanPaste = false; | ||
139 | mMediaName.clear(); | ||
140 | mMediaDescription.clear(); | ||
141 | mBackgroundColor = LLColor4(1.0f, 1.0f, 1.0f, 1.0f); | ||
142 | |||
143 | // media_browser class | ||
144 | mNavigateURI.clear(); | ||
145 | mNavigateResultCode = -1; | ||
146 | mNavigateResultString.clear(); | ||
147 | mHistoryBackAvailable = false; | ||
148 | mHistoryForwardAvailable = false; | ||
149 | mStatusText.clear(); | ||
150 | mProgressPercent = 0; | ||
151 | mClickURL.clear(); | ||
152 | mClickTarget.clear(); | ||
153 | |||
154 | // media_time class | ||
155 | mCurrentTime = 0.0f; | ||
156 | mDuration = 0.0f; | ||
157 | mCurrentRate = 0.0f; | ||
158 | mLoadedDuration = 0.0f; | ||
159 | } | ||
160 | |||
161 | void LLPluginClassMedia::idle(void) | ||
162 | { | ||
163 | if(mPlugin) | ||
164 | { | ||
165 | mPlugin->idle(); | ||
166 | } | ||
167 | |||
168 | if((mMediaWidth == -1) || (!mTextureParamsReceived) || (mPlugin == NULL) || (mPlugin->isBlocked())) | ||
169 | { | ||
170 | // Can't process a size change at this time | ||
171 | } | ||
172 | else if((mRequestedMediaWidth != mMediaWidth) || (mRequestedMediaHeight != mMediaHeight)) | ||
173 | { | ||
174 | // Calculate the correct size for the media texture | ||
175 | mRequestedTextureHeight = mRequestedMediaHeight; | ||
176 | if(mPadding < 0) | ||
177 | { | ||
178 | // negative values indicate the plugin wants a power of 2 | ||
179 | mRequestedTextureWidth = nextPowerOf2(mRequestedMediaWidth); | ||
180 | } | ||
181 | else | ||
182 | { | ||
183 | mRequestedTextureWidth = mRequestedMediaWidth; | ||
184 | |||
185 | if(mPadding > 1) | ||
186 | { | ||
187 | // Pad up to a multiple of the specified number of bytes per row | ||
188 | int rowbytes = mRequestedTextureWidth * mRequestedTextureDepth; | ||
189 | int pad = rowbytes % mPadding; | ||
190 | if(pad != 0) | ||
191 | { | ||
192 | rowbytes += mPadding - pad; | ||
193 | } | ||
194 | |||
195 | if(rowbytes % mRequestedTextureDepth == 0) | ||
196 | { | ||
197 | mRequestedTextureWidth = rowbytes / mRequestedTextureDepth; | ||
198 | } | ||
199 | else | ||
200 | { | ||
201 | LL_WARNS("PluginClassMedia") << "Unable to pad texture width, padding size " << mPadding << "is not a multiple of pixel size " << mRequestedTextureDepth << LL_ENDL; | ||
202 | } | ||
203 | } | ||
204 | } | ||
205 | |||
206 | |||
207 | // Size change has been requested but not initiated yet. | ||
208 | size_t newsize = mRequestedTextureWidth * mRequestedTextureHeight * mRequestedTextureDepth; | ||
209 | |||
210 | // Add an extra line for padding, just in case. | ||
211 | newsize += mRequestedTextureWidth * mRequestedTextureDepth; | ||
212 | |||
213 | if(newsize != mTextureSharedMemorySize) | ||
214 | { | ||
215 | if(!mTextureSharedMemoryName.empty()) | ||
216 | { | ||
217 | // Tell the plugin to remove the old memory segment | ||
218 | mPlugin->removeSharedMemory(mTextureSharedMemoryName); | ||
219 | mTextureSharedMemoryName.clear(); | ||
220 | } | ||
221 | |||
222 | mTextureSharedMemorySize = newsize; | ||
223 | mTextureSharedMemoryName = mPlugin->addSharedMemory(mTextureSharedMemorySize); | ||
224 | if(!mTextureSharedMemoryName.empty()) | ||
225 | { | ||
226 | void *addr = mPlugin->getSharedMemoryAddress(mTextureSharedMemoryName); | ||
227 | |||
228 | // clear texture memory to avoid random screen visual fuzz from uninitialized texture data | ||
229 | memset( addr, 0x00, newsize ); | ||
230 | |||
231 | // We could do this to force an update, but textureValid() will still be returning false until the first roundtrip to the plugin, | ||
232 | // so it may not be worthwhile. | ||
233 | // mDirtyRect.setOriginAndSize(0, 0, mRequestedMediaWidth, mRequestedMediaHeight); | ||
234 | } | ||
235 | } | ||
236 | |||
237 | // This is our local indicator that a change is in progress. | ||
238 | mTextureWidth = -1; | ||
239 | mTextureHeight = -1; | ||
240 | mMediaWidth = -1; | ||
241 | mMediaHeight = -1; | ||
242 | |||
243 | // This invalidates any existing dirty rect. | ||
244 | resetDirty(); | ||
245 | |||
246 | // Send a size change message to the plugin | ||
247 | { | ||
248 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change"); | ||
249 | message.setValue("name", mTextureSharedMemoryName); | ||
250 | message.setValueS32("width", mRequestedMediaWidth); | ||
251 | message.setValueS32("height", mRequestedMediaHeight); | ||
252 | message.setValueS32("texture_width", mRequestedTextureWidth); | ||
253 | message.setValueS32("texture_height", mRequestedTextureHeight); | ||
254 | message.setValueReal("background_r", mBackgroundColor.mV[VX]); | ||
255 | message.setValueReal("background_g", mBackgroundColor.mV[VY]); | ||
256 | message.setValueReal("background_b", mBackgroundColor.mV[VZ]); | ||
257 | message.setValueReal("background_a", mBackgroundColor.mV[VW]); | ||
258 | mPlugin->sendMessage(message); // DO NOT just use sendMessage() here -- we want this to jump ahead of the queue. | ||
259 | |||
260 | LL_DEBUGS("PluginClassMedia") << "Sending size_change" << LL_ENDL; | ||
261 | } | ||
262 | } | ||
263 | |||
264 | if(mPlugin && mPlugin->isRunning()) | ||
265 | { | ||
266 | // Send queued messages | ||
267 | while(!mSendQueue.empty()) | ||
268 | { | ||
269 | LLPluginMessage message = mSendQueue.front(); | ||
270 | mSendQueue.pop(); | ||
271 | mPlugin->sendMessage(message); | ||
272 | } | ||
273 | } | ||
274 | } | ||
275 | |||
276 | int LLPluginClassMedia::getTextureWidth() const | ||
277 | { | ||
278 | return nextPowerOf2(mTextureWidth); | ||
279 | } | ||
280 | |||
281 | int LLPluginClassMedia::getTextureHeight() const | ||
282 | { | ||
283 | return nextPowerOf2(mTextureHeight); | ||
284 | } | ||
285 | |||
286 | unsigned char* LLPluginClassMedia::getBitsData() | ||
287 | { | ||
288 | unsigned char *result = NULL; | ||
289 | if((mPlugin != NULL) && !mTextureSharedMemoryName.empty()) | ||
290 | { | ||
291 | result = (unsigned char*)mPlugin->getSharedMemoryAddress(mTextureSharedMemoryName); | ||
292 | } | ||
293 | return result; | ||
294 | } | ||
295 | |||
296 | void LLPluginClassMedia::setSize(int width, int height) | ||
297 | { | ||
298 | if((width > 0) && (height > 0)) | ||
299 | { | ||
300 | mSetMediaWidth = width; | ||
301 | mSetMediaHeight = height; | ||
302 | } | ||
303 | else | ||
304 | { | ||
305 | mSetMediaWidth = -1; | ||
306 | mSetMediaHeight = -1; | ||
307 | } | ||
308 | |||
309 | setSizeInternal(); | ||
310 | } | ||
311 | |||
312 | void LLPluginClassMedia::setSizeInternal(void) | ||
313 | { | ||
314 | if((mSetMediaWidth > 0) && (mSetMediaHeight > 0)) | ||
315 | { | ||
316 | mRequestedMediaWidth = mSetMediaWidth; | ||
317 | mRequestedMediaHeight = mSetMediaHeight; | ||
318 | } | ||
319 | else if((mNaturalMediaWidth > 0) && (mNaturalMediaHeight > 0)) | ||
320 | { | ||
321 | mRequestedMediaWidth = mNaturalMediaWidth; | ||
322 | mRequestedMediaHeight = mNaturalMediaHeight; | ||
323 | } | ||
324 | else | ||
325 | { | ||
326 | mRequestedMediaWidth = mDefaultMediaWidth; | ||
327 | mRequestedMediaHeight = mDefaultMediaHeight; | ||
328 | } | ||
329 | |||
330 | // Save these for size/interest calculations | ||
331 | mFullMediaWidth = mRequestedMediaWidth; | ||
332 | mFullMediaHeight = mRequestedMediaHeight; | ||
333 | |||
334 | if(mAllowDownsample) | ||
335 | { | ||
336 | switch(mPriority) | ||
337 | { | ||
338 | case PRIORITY_SLIDESHOW: | ||
339 | case PRIORITY_LOW: | ||
340 | // Reduce maximum texture dimension to (or below) mLowPrioritySizeLimit | ||
341 | while((mRequestedMediaWidth > mLowPrioritySizeLimit) || (mRequestedMediaHeight > mLowPrioritySizeLimit)) | ||
342 | { | ||
343 | mRequestedMediaWidth /= 2; | ||
344 | mRequestedMediaHeight /= 2; | ||
345 | } | ||
346 | break; | ||
347 | |||
348 | default: | ||
349 | // Don't adjust texture size | ||
350 | break; | ||
351 | } | ||
352 | } | ||
353 | |||
354 | if(mAutoScaleMedia) | ||
355 | { | ||
356 | mRequestedMediaWidth = nextPowerOf2(mRequestedMediaWidth); | ||
357 | mRequestedMediaHeight = nextPowerOf2(mRequestedMediaHeight); | ||
358 | } | ||
359 | |||
360 | if(mRequestedMediaWidth > 2048) | ||
361 | mRequestedMediaWidth = 2048; | ||
362 | |||
363 | if(mRequestedMediaHeight > 2048) | ||
364 | mRequestedMediaHeight = 2048; | ||
365 | } | ||
366 | |||
367 | void LLPluginClassMedia::setAutoScale(bool auto_scale) | ||
368 | { | ||
369 | if(auto_scale != mAutoScaleMedia) | ||
370 | { | ||
371 | mAutoScaleMedia = auto_scale; | ||
372 | setSizeInternal(); | ||
373 | } | ||
374 | } | ||
375 | |||
376 | bool LLPluginClassMedia::textureValid(void) | ||
377 | { | ||
378 | if( | ||
379 | !mTextureParamsReceived || | ||
380 | mTextureWidth <= 0 || | ||
381 | mTextureHeight <= 0 || | ||
382 | mMediaWidth <= 0 || | ||
383 | mMediaHeight <= 0 || | ||
384 | mRequestedMediaWidth != mMediaWidth || | ||
385 | mRequestedMediaHeight != mMediaHeight || | ||
386 | getBitsData() == NULL | ||
387 | ) | ||
388 | return false; | ||
389 | |||
390 | return true; | ||
391 | } | ||
392 | |||
393 | bool LLPluginClassMedia::getDirty(LLRect *dirty_rect) | ||
394 | { | ||
395 | bool result = !mDirtyRect.isEmpty(); | ||
396 | |||
397 | if(dirty_rect != NULL) | ||
398 | { | ||
399 | *dirty_rect = mDirtyRect; | ||
400 | } | ||
401 | |||
402 | return result; | ||
403 | } | ||
404 | |||
405 | void LLPluginClassMedia::resetDirty(void) | ||
406 | { | ||
407 | mDirtyRect = LLRect::null; | ||
408 | } | ||
409 | |||
410 | std::string LLPluginClassMedia::translateModifiers(MASK modifiers) | ||
411 | { | ||
412 | std::string result; | ||
413 | |||
414 | |||
415 | if(modifiers & MASK_CONTROL) | ||
416 | { | ||
417 | result += "control|"; | ||
418 | } | ||
419 | |||
420 | if(modifiers & MASK_ALT) | ||
421 | { | ||
422 | result += "alt|"; | ||
423 | } | ||
424 | |||
425 | if(modifiers & MASK_SHIFT) | ||
426 | { | ||
427 | result += "shift|"; | ||
428 | } | ||
429 | |||
430 | // TODO: should I deal with platform differences here or in callers? | ||
431 | // TODO: how do we deal with the Mac "command" key? | ||
432 | /* | ||
433 | if(modifiers & MASK_SOMETHING) | ||
434 | { | ||
435 | result += "meta|"; | ||
436 | } | ||
437 | */ | ||
438 | return result; | ||
439 | } | ||
440 | |||
441 | void LLPluginClassMedia::mouseEvent(EMouseEventType type, int button, int x, int y, MASK modifiers) | ||
442 | { | ||
443 | if(type == MOUSE_EVENT_MOVE) | ||
444 | { | ||
445 | if(!mPlugin || !mPlugin->isRunning() || mPlugin->isBlocked()) | ||
446 | { | ||
447 | // Don't queue up mouse move events that can't be delivered. | ||
448 | return; | ||
449 | } | ||
450 | |||
451 | if((x == mLastMouseX) && (y == mLastMouseY)) | ||
452 | { | ||
453 | // Don't spam unnecessary mouse move events. | ||
454 | return; | ||
455 | } | ||
456 | |||
457 | mLastMouseX = x; | ||
458 | mLastMouseY = y; | ||
459 | } | ||
460 | |||
461 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "mouse_event"); | ||
462 | std::string temp; | ||
463 | switch(type) | ||
464 | { | ||
465 | case MOUSE_EVENT_DOWN: temp = "down"; break; | ||
466 | case MOUSE_EVENT_UP: temp = "up"; break; | ||
467 | case MOUSE_EVENT_MOVE: temp = "move"; break; | ||
468 | case MOUSE_EVENT_DOUBLE_CLICK: temp = "double_click"; break; | ||
469 | } | ||
470 | message.setValue("event", temp); | ||
471 | |||
472 | message.setValueS32("button", button); | ||
473 | |||
474 | message.setValueS32("x", x); | ||
475 | |||
476 | // Incoming coordinates are OpenGL-style ((0,0) = lower left), so flip them here if the plugin has requested it. | ||
477 | if(!mRequestedTextureCoordsOpenGL) | ||
478 | { | ||
479 | // TODO: Should I use mMediaHeight or mRequestedMediaHeight here? | ||
480 | y = mMediaHeight - y; | ||
481 | } | ||
482 | message.setValueS32("y", y); | ||
483 | |||
484 | message.setValue("modifiers", translateModifiers(modifiers)); | ||
485 | |||
486 | sendMessage(message); | ||
487 | } | ||
488 | |||
489 | bool LLPluginClassMedia::keyEvent(EKeyEventType type, int key_code, MASK modifiers, LLSD native_key_data) | ||
490 | { | ||
491 | bool result = true; | ||
492 | |||
493 | // FIXME: | ||
494 | // HACK: we don't have an easy way to tell if the plugin is going to handle a particular keycode. | ||
495 | // For now, return false for the ones the webkit plugin won't handle properly. | ||
496 | |||
497 | switch(key_code) | ||
498 | { | ||
499 | case KEY_BACKSPACE: | ||
500 | case KEY_TAB: | ||
501 | case KEY_RETURN: | ||
502 | case KEY_PAD_RETURN: | ||
503 | case KEY_SHIFT: | ||
504 | case KEY_CONTROL: | ||
505 | case KEY_ALT: | ||
506 | case KEY_CAPSLOCK: | ||
507 | case KEY_ESCAPE: | ||
508 | case KEY_PAGE_UP: | ||
509 | case KEY_PAGE_DOWN: | ||
510 | case KEY_END: | ||
511 | case KEY_HOME: | ||
512 | case KEY_LEFT: | ||
513 | case KEY_UP: | ||
514 | case KEY_RIGHT: | ||
515 | case KEY_DOWN: | ||
516 | case KEY_INSERT: | ||
517 | case KEY_DELETE: | ||
518 | // These will be handled | ||
519 | break; | ||
520 | |||
521 | default: | ||
522 | // regular ASCII characters will also be handled | ||
523 | if(key_code >= KEY_SPECIAL) | ||
524 | { | ||
525 | // Other "special" codes will not work properly. | ||
526 | result = false; | ||
527 | } | ||
528 | break; | ||
529 | } | ||
530 | |||
531 | if(result) | ||
532 | { | ||
533 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "key_event"); | ||
534 | std::string temp; | ||
535 | switch(type) | ||
536 | { | ||
537 | case KEY_EVENT_DOWN: temp = "down"; break; | ||
538 | case KEY_EVENT_UP: temp = "up"; break; | ||
539 | case KEY_EVENT_REPEAT: temp = "repeat"; break; | ||
540 | } | ||
541 | message.setValue("event", temp); | ||
542 | |||
543 | message.setValueS32("key", key_code); | ||
544 | |||
545 | message.setValue("modifiers", translateModifiers(modifiers)); | ||
546 | message.setValueLLSD("native_key_data", native_key_data); | ||
547 | |||
548 | sendMessage(message); | ||
549 | } | ||
550 | |||
551 | return result; | ||
552 | } | ||
553 | |||
554 | void LLPluginClassMedia::scrollEvent(int x, int y, MASK modifiers) | ||
555 | { | ||
556 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "scroll_event"); | ||
557 | |||
558 | message.setValueS32("x", x); | ||
559 | message.setValueS32("y", y); | ||
560 | message.setValue("modifiers", translateModifiers(modifiers)); | ||
561 | |||
562 | sendMessage(message); | ||
563 | } | ||
564 | |||
565 | bool LLPluginClassMedia::textInput(const std::string &text, MASK modifiers, LLSD native_key_data) | ||
566 | { | ||
567 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "text_event"); | ||
568 | |||
569 | message.setValue("text", text); | ||
570 | message.setValue("modifiers", translateModifiers(modifiers)); | ||
571 | message.setValueLLSD("native_key_data", native_key_data); | ||
572 | |||
573 | sendMessage(message); | ||
574 | |||
575 | return true; | ||
576 | } | ||
577 | |||
578 | void LLPluginClassMedia::loadURI(const std::string &uri) | ||
579 | { | ||
580 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "load_uri"); | ||
581 | |||
582 | message.setValue("uri", uri); | ||
583 | |||
584 | sendMessage(message); | ||
585 | } | ||
586 | |||
587 | const char* LLPluginClassMedia::priorityToString(EPriority priority) | ||
588 | { | ||
589 | const char* result = "UNKNOWN"; | ||
590 | switch(priority) | ||
591 | { | ||
592 | case PRIORITY_UNLOADED: result = "unloaded"; break; | ||
593 | case PRIORITY_STOPPED: result = "stopped"; break; | ||
594 | case PRIORITY_HIDDEN: result = "hidden"; break; | ||
595 | case PRIORITY_SLIDESHOW: result = "slideshow"; break; | ||
596 | case PRIORITY_LOW: result = "low"; break; | ||
597 | case PRIORITY_NORMAL: result = "normal"; break; | ||
598 | case PRIORITY_HIGH: result = "high"; break; | ||
599 | } | ||
600 | |||
601 | return result; | ||
602 | } | ||
603 | |||
604 | void LLPluginClassMedia::setPriority(EPriority priority) | ||
605 | { | ||
606 | if(mPriority != priority) | ||
607 | { | ||
608 | mPriority = priority; | ||
609 | |||
610 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "set_priority"); | ||
611 | |||
612 | std::string priority_string = priorityToString(priority); | ||
613 | switch(priority) | ||
614 | { | ||
615 | case PRIORITY_UNLOADED: | ||
616 | mSleepTime = 1.0f; | ||
617 | break; | ||
618 | case PRIORITY_STOPPED: | ||
619 | mSleepTime = 1.0f; | ||
620 | break; | ||
621 | case PRIORITY_HIDDEN: | ||
622 | mSleepTime = 1.0f; | ||
623 | break; | ||
624 | case PRIORITY_SLIDESHOW: | ||
625 | mSleepTime = 1.0f; | ||
626 | break; | ||
627 | case PRIORITY_LOW: | ||
628 | mSleepTime = 1.0f / 25.0f; | ||
629 | break; | ||
630 | case PRIORITY_NORMAL: | ||
631 | mSleepTime = 1.0f / 50.0f; | ||
632 | break; | ||
633 | case PRIORITY_HIGH: | ||
634 | mSleepTime = 1.0f / 100.0f; | ||
635 | break; | ||
636 | } | ||
637 | |||
638 | message.setValue("priority", priority_string); | ||
639 | |||
640 | sendMessage(message); | ||
641 | |||
642 | if(mPlugin) | ||
643 | { | ||
644 | mPlugin->setSleepTime(mSleepTime); | ||
645 | } | ||
646 | |||
647 | LL_DEBUGS("PluginPriority") << this << ": setting priority to " << priority_string << LL_ENDL; | ||
648 | |||
649 | // This may affect the calculated size, so recalculate it here. | ||
650 | setSizeInternal(); | ||
651 | } | ||
652 | } | ||
653 | |||
654 | void LLPluginClassMedia::setLowPrioritySizeLimit(int size) | ||
655 | { | ||
656 | int power = nextPowerOf2(size); | ||
657 | if(mLowPrioritySizeLimit != power) | ||
658 | { | ||
659 | mLowPrioritySizeLimit = power; | ||
660 | |||
661 | // This may affect the calculated size, so recalculate it here. | ||
662 | setSizeInternal(); | ||
663 | } | ||
664 | } | ||
665 | |||
666 | F64 LLPluginClassMedia::getCPUUsage() | ||
667 | { | ||
668 | F64 result = 0.0f; | ||
669 | |||
670 | if(mPlugin) | ||
671 | { | ||
672 | result = mPlugin->getCPUUsage(); | ||
673 | } | ||
674 | |||
675 | return result; | ||
676 | } | ||
677 | |||
678 | void LLPluginClassMedia::cut() | ||
679 | { | ||
680 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_cut"); | ||
681 | sendMessage(message); | ||
682 | } | ||
683 | |||
684 | void LLPluginClassMedia::copy() | ||
685 | { | ||
686 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_copy"); | ||
687 | sendMessage(message); | ||
688 | } | ||
689 | |||
690 | void LLPluginClassMedia::paste() | ||
691 | { | ||
692 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_paste"); | ||
693 | sendMessage(message); | ||
694 | } | ||
695 | |||
696 | void LLPluginClassMedia::setUserDataPath(const std::string &user_data_path) | ||
697 | { | ||
698 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "set_user_data_path"); | ||
699 | message.setValue("path", user_data_path); | ||
700 | sendMessage(message); | ||
701 | } | ||
702 | |||
703 | void LLPluginClassMedia::setLanguageCode(const std::string &language_code) | ||
704 | { | ||
705 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "set_language_code"); | ||
706 | message.setValue("language", language_code); | ||
707 | sendMessage(message); | ||
708 | } | ||
709 | |||
710 | void LLPluginClassMedia::setPluginsEnabled(const bool enabled) | ||
711 | { | ||
712 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "plugins_enabled"); | ||
713 | message.setValueBoolean("enable", enabled); | ||
714 | sendMessage(message); | ||
715 | } | ||
716 | |||
717 | void LLPluginClassMedia::setJavascriptEnabled(const bool enabled) | ||
718 | { | ||
719 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "javascript_enabled"); | ||
720 | message.setValueBoolean("enable", enabled); | ||
721 | sendMessage(message); | ||
722 | } | ||
723 | |||
724 | /* virtual */ | ||
725 | void LLPluginClassMedia::receivePluginMessage(const LLPluginMessage &message) | ||
726 | { | ||
727 | std::string message_class = message.getClass(); | ||
728 | |||
729 | if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA) | ||
730 | { | ||
731 | std::string message_name = message.getName(); | ||
732 | if(message_name == "texture_params") | ||
733 | { | ||
734 | mRequestedTextureDepth = message.getValueS32("depth"); | ||
735 | mRequestedTextureInternalFormat = message.getValueU32("internalformat"); | ||
736 | mRequestedTextureFormat = message.getValueU32("format"); | ||
737 | mRequestedTextureType = message.getValueU32("type"); | ||
738 | mRequestedTextureSwapBytes = message.getValueBoolean("swap_bytes"); | ||
739 | mRequestedTextureCoordsOpenGL = message.getValueBoolean("coords_opengl"); | ||
740 | |||
741 | // These two are optional, and will default to 0 if they're not specified. | ||
742 | mDefaultMediaWidth = message.getValueS32("default_width"); | ||
743 | mDefaultMediaHeight = message.getValueS32("default_height"); | ||
744 | |||
745 | mAllowDownsample = message.getValueBoolean("allow_downsample"); | ||
746 | mPadding = message.getValueS32("padding"); | ||
747 | |||
748 | setSizeInternal(); | ||
749 | |||
750 | mTextureParamsReceived = true; | ||
751 | } | ||
752 | else if(message_name == "updated") | ||
753 | { | ||
754 | if(message.hasValue("left")) | ||
755 | { | ||
756 | LLRect newDirtyRect; | ||
757 | newDirtyRect.mLeft = message.getValueS32("left"); | ||
758 | newDirtyRect.mTop = message.getValueS32("top"); | ||
759 | newDirtyRect.mRight = message.getValueS32("right"); | ||
760 | newDirtyRect.mBottom = message.getValueS32("bottom"); | ||
761 | |||
762 | // The plugin is likely to have top and bottom switched, due to vertical flip and OpenGL coordinate confusion. | ||
763 | // If they're backwards, swap them. | ||
764 | if(newDirtyRect.mTop < newDirtyRect.mBottom) | ||
765 | { | ||
766 | S32 temp = newDirtyRect.mTop; | ||
767 | newDirtyRect.mTop = newDirtyRect.mBottom; | ||
768 | newDirtyRect.mBottom = temp; | ||
769 | } | ||
770 | |||
771 | if(mDirtyRect.isEmpty()) | ||
772 | { | ||
773 | mDirtyRect = newDirtyRect; | ||
774 | } | ||
775 | else | ||
776 | { | ||
777 | mDirtyRect.unionWith(newDirtyRect); | ||
778 | } | ||
779 | |||
780 | LL_DEBUGS("PluginClassMediaRect") << "adjusted incoming rect is: (" | ||
781 | << newDirtyRect.mLeft << ", " | ||
782 | << newDirtyRect.mTop << ", " | ||
783 | << newDirtyRect.mRight << ", " | ||
784 | << newDirtyRect.mBottom << "), new dirty rect is: (" | ||
785 | << mDirtyRect.mLeft << ", " | ||
786 | << mDirtyRect.mTop << ", " | ||
787 | << mDirtyRect.mRight << ", " | ||
788 | << mDirtyRect.mBottom << ")" | ||
789 | << LL_ENDL; | ||
790 | |||
791 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CONTENT_UPDATED); | ||
792 | } | ||
793 | |||
794 | |||
795 | bool time_duration_updated = false; | ||
796 | int previous_percent = mProgressPercent; | ||
797 | |||
798 | if(message.hasValue("current_time")) | ||
799 | { | ||
800 | mCurrentTime = message.getValueReal("current_time"); | ||
801 | time_duration_updated = true; | ||
802 | } | ||
803 | if(message.hasValue("duration")) | ||
804 | { | ||
805 | mDuration = message.getValueReal("duration"); | ||
806 | time_duration_updated = true; | ||
807 | } | ||
808 | |||
809 | if(message.hasValue("current_rate")) | ||
810 | { | ||
811 | mCurrentRate = message.getValueReal("current_rate"); | ||
812 | } | ||
813 | |||
814 | if(message.hasValue("loaded_duration")) | ||
815 | { | ||
816 | mLoadedDuration = message.getValueReal("loaded_duration"); | ||
817 | time_duration_updated = true; | ||
818 | } | ||
819 | else | ||
820 | { | ||
821 | // If the message doesn't contain a loaded_duration param, assume it's equal to duration | ||
822 | mLoadedDuration = mDuration; | ||
823 | } | ||
824 | |||
825 | // Calculate a percentage based on the loaded duration and total duration. | ||
826 | if(mDuration != 0.0f) // Don't divide by zero. | ||
827 | { | ||
828 | mProgressPercent = (int)((mLoadedDuration * 100.0f)/mDuration); | ||
829 | } | ||
830 | |||
831 | if(time_duration_updated) | ||
832 | { | ||
833 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_TIME_DURATION_UPDATED); | ||
834 | } | ||
835 | |||
836 | if(previous_percent != mProgressPercent) | ||
837 | { | ||
838 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PROGRESS_UPDATED); | ||
839 | } | ||
840 | } | ||
841 | else if(message_name == "media_status") | ||
842 | { | ||
843 | std::string status = message.getValue("status"); | ||
844 | |||
845 | LL_DEBUGS("PluginClassMedia") << "Status changed to: " << status << LL_ENDL; | ||
846 | |||
847 | if(status == "loading") | ||
848 | { | ||
849 | mStatus = LLPluginClassMediaOwner::MEDIA_LOADING; | ||
850 | } | ||
851 | else if(status == "loaded") | ||
852 | { | ||
853 | mStatus = LLPluginClassMediaOwner::MEDIA_LOADED; | ||
854 | } | ||
855 | else if(status == "error") | ||
856 | { | ||
857 | mStatus = LLPluginClassMediaOwner::MEDIA_ERROR; | ||
858 | } | ||
859 | else if(status == "playing") | ||
860 | { | ||
861 | mStatus = LLPluginClassMediaOwner::MEDIA_PLAYING; | ||
862 | } | ||
863 | else if(status == "paused") | ||
864 | { | ||
865 | mStatus = LLPluginClassMediaOwner::MEDIA_PAUSED; | ||
866 | } | ||
867 | else if(status == "done") | ||
868 | { | ||
869 | mStatus = LLPluginClassMediaOwner::MEDIA_DONE; | ||
870 | } | ||
871 | else | ||
872 | { | ||
873 | // empty string or any unknown string | ||
874 | mStatus = LLPluginClassMediaOwner::MEDIA_NONE; | ||
875 | } | ||
876 | } | ||
877 | else if(message_name == "size_change_request") | ||
878 | { | ||
879 | S32 width = message.getValueS32("width"); | ||
880 | S32 height = message.getValueS32("height"); | ||
881 | std::string name = message.getValue("name"); | ||
882 | |||
883 | // TODO: check that name matches? | ||
884 | mNaturalMediaWidth = width; | ||
885 | mNaturalMediaHeight = height; | ||
886 | |||
887 | setSizeInternal(); | ||
888 | } | ||
889 | else if(message_name == "size_change_response") | ||
890 | { | ||
891 | std::string name = message.getValue("name"); | ||
892 | |||
893 | // TODO: check that name matches? | ||
894 | |||
895 | mTextureWidth = message.getValueS32("texture_width"); | ||
896 | mTextureHeight = message.getValueS32("texture_height"); | ||
897 | mMediaWidth = message.getValueS32("width"); | ||
898 | mMediaHeight = message.getValueS32("height"); | ||
899 | |||
900 | // This invalidates any existing dirty rect. | ||
901 | resetDirty(); | ||
902 | |||
903 | // TODO: should we verify that the plugin sent back the right values? | ||
904 | // Two size changes in a row may cause them to not match, due to queueing, etc. | ||
905 | |||
906 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_SIZE_CHANGED); | ||
907 | } | ||
908 | else if(message_name == "cursor_changed") | ||
909 | { | ||
910 | mCursorName = message.getValue("name"); | ||
911 | |||
912 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CURSOR_CHANGED); | ||
913 | } | ||
914 | else if(message_name == "edit_state") | ||
915 | { | ||
916 | if(message.hasValue("cut")) | ||
917 | { | ||
918 | mCanCut = message.getValueBoolean("cut"); | ||
919 | } | ||
920 | if(message.hasValue("copy")) | ||
921 | { | ||
922 | mCanCopy = message.getValueBoolean("copy"); | ||
923 | } | ||
924 | if(message.hasValue("paste")) | ||
925 | { | ||
926 | mCanPaste = message.getValueBoolean("paste"); | ||
927 | } | ||
928 | } | ||
929 | else if(message_name == "name_text") | ||
930 | { | ||
931 | mMediaName = message.getValue("name"); | ||
932 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_NAME_CHANGED); | ||
933 | } | ||
934 | else | ||
935 | { | ||
936 | LL_WARNS("PluginClassMedia") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL; | ||
937 | } | ||
938 | } | ||
939 | else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER) | ||
940 | { | ||
941 | std::string message_name = message.getName(); | ||
942 | if(message_name == "navigate_begin") | ||
943 | { | ||
944 | mNavigateURI = message.getValue("uri"); | ||
945 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_NAVIGATE_BEGIN); | ||
946 | } | ||
947 | else if(message_name == "navigate_complete") | ||
948 | { | ||
949 | mNavigateURI = message.getValue("uri"); | ||
950 | mNavigateResultCode = message.getValueS32("result_code"); | ||
951 | mNavigateResultString = message.getValue("result_string"); | ||
952 | mHistoryBackAvailable = message.getValueBoolean("history_back_available"); | ||
953 | mHistoryForwardAvailable = message.getValueBoolean("history_forward_available"); | ||
954 | |||
955 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_NAVIGATE_COMPLETE); | ||
956 | } | ||
957 | else if(message_name == "progress") | ||
958 | { | ||
959 | mProgressPercent = message.getValueS32("percent"); | ||
960 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PROGRESS_UPDATED); | ||
961 | } | ||
962 | else if(message_name == "status_text") | ||
963 | { | ||
964 | mStatusText = message.getValue("status"); | ||
965 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_STATUS_TEXT_CHANGED); | ||
966 | } | ||
967 | else if(message_name == "location_changed") | ||
968 | { | ||
969 | mLocation = message.getValue("uri"); | ||
970 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_LOCATION_CHANGED); | ||
971 | } | ||
972 | else if(message_name == "click_href") | ||
973 | { | ||
974 | mClickURL = message.getValue("uri"); | ||
975 | mClickTarget = message.getValue("target"); | ||
976 | LL_DEBUGS("PluginClassMedia") << "Click target \"" << mClickTarget << "\"" << LL_ENDL; | ||
977 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_HREF); | ||
978 | } | ||
979 | else if(message_name == "click_nofollow") | ||
980 | { | ||
981 | mClickURL = message.getValue("uri"); | ||
982 | mClickTarget.clear(); | ||
983 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_NOFOLLOW); | ||
984 | } | ||
985 | else if(message_name == "cookie_set") | ||
986 | { | ||
987 | if(mOwner) | ||
988 | { | ||
989 | mOwner->handleCookieSet(this, message.getValue("cookie")); | ||
990 | } | ||
991 | } | ||
992 | else | ||
993 | { | ||
994 | LL_WARNS("PluginClassMedia") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL; | ||
995 | } | ||
996 | } | ||
997 | else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME) | ||
998 | { | ||
999 | std::string message_name = message.getName(); | ||
1000 | |||
1001 | // This class hasn't defined any incoming messages yet. | ||
1002 | // if(message_name == "message_name") | ||
1003 | // { | ||
1004 | // } | ||
1005 | // else | ||
1006 | { | ||
1007 | LL_WARNS("PluginClassMedia") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL; | ||
1008 | } | ||
1009 | } | ||
1010 | |||
1011 | } | ||
1012 | |||
1013 | /* virtual */ | ||
1014 | void LLPluginClassMedia::pluginLaunchFailed() | ||
1015 | { | ||
1016 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PLUGIN_FAILED_LAUNCH); | ||
1017 | } | ||
1018 | |||
1019 | /* virtual */ | ||
1020 | void LLPluginClassMedia::pluginDied() | ||
1021 | { | ||
1022 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PLUGIN_FAILED); | ||
1023 | } | ||
1024 | |||
1025 | void LLPluginClassMedia::mediaEvent(LLPluginClassMediaOwner::EMediaEvent event) | ||
1026 | { | ||
1027 | if(mOwner) | ||
1028 | { | ||
1029 | mOwner->handleMediaEvent(this, event); | ||
1030 | } | ||
1031 | } | ||
1032 | |||
1033 | void LLPluginClassMedia::sendMessage(const LLPluginMessage &message) | ||
1034 | { | ||
1035 | if(mPlugin && mPlugin->isRunning()) | ||
1036 | { | ||
1037 | mPlugin->sendMessage(message); | ||
1038 | } | ||
1039 | else | ||
1040 | { | ||
1041 | // The plugin isn't set up yet -- queue this message to be sent after initialization. | ||
1042 | mSendQueue.push(message); | ||
1043 | } | ||
1044 | } | ||
1045 | |||
1046 | //////////////////////////////////////////////////////////// | ||
1047 | // MARK: media_browser class functions | ||
1048 | bool LLPluginClassMedia::pluginSupportsMediaBrowser(void) | ||
1049 | { | ||
1050 | std::string version = mPlugin->getMessageClassVersion(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER); | ||
1051 | return !version.empty(); | ||
1052 | } | ||
1053 | |||
1054 | void LLPluginClassMedia::focus(bool focused) | ||
1055 | { | ||
1056 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "focus"); | ||
1057 | |||
1058 | message.setValueBoolean("focused", focused); | ||
1059 | |||
1060 | sendMessage(message); | ||
1061 | } | ||
1062 | |||
1063 | void LLPluginClassMedia::clear_cache() | ||
1064 | { | ||
1065 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "clear_cache"); | ||
1066 | sendMessage(message); | ||
1067 | } | ||
1068 | |||
1069 | void LLPluginClassMedia::clear_cookies() | ||
1070 | { | ||
1071 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "clear_cookies"); | ||
1072 | sendMessage(message); | ||
1073 | } | ||
1074 | |||
1075 | void LLPluginClassMedia::set_cookies(const std::string &cookies) | ||
1076 | { | ||
1077 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "set_cookies"); | ||
1078 | message.setValue("cookies", cookies); | ||
1079 | sendMessage(message); | ||
1080 | } | ||
1081 | |||
1082 | void LLPluginClassMedia::enable_cookies(bool enable) | ||
1083 | { | ||
1084 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "enable_cookies"); | ||
1085 | message.setValueBoolean("enable", enable); | ||
1086 | sendMessage(message); | ||
1087 | } | ||
1088 | |||
1089 | void LLPluginClassMedia::proxy_setup(bool enable, const std::string &host, int port) | ||
1090 | { | ||
1091 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "proxy_setup"); | ||
1092 | |||
1093 | message.setValueBoolean("enable", enable); | ||
1094 | message.setValue("host", host); | ||
1095 | message.setValueS32("port", port); | ||
1096 | |||
1097 | sendMessage(message); | ||
1098 | } | ||
1099 | |||
1100 | void LLPluginClassMedia::browse_stop() | ||
1101 | { | ||
1102 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_stop"); | ||
1103 | sendMessage(message); | ||
1104 | } | ||
1105 | |||
1106 | void LLPluginClassMedia::browse_reload(bool ignore_cache) | ||
1107 | { | ||
1108 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_reload"); | ||
1109 | |||
1110 | message.setValueBoolean("ignore_cache", ignore_cache); | ||
1111 | |||
1112 | sendMessage(message); | ||
1113 | } | ||
1114 | |||
1115 | void LLPluginClassMedia::browse_forward() | ||
1116 | { | ||
1117 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_forward"); | ||
1118 | sendMessage(message); | ||
1119 | } | ||
1120 | |||
1121 | void LLPluginClassMedia::browse_back() | ||
1122 | { | ||
1123 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_back"); | ||
1124 | sendMessage(message); | ||
1125 | } | ||
1126 | |||
1127 | void LLPluginClassMedia::set_status_redirect(int code, const std::string &url) | ||
1128 | { | ||
1129 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "set_status_redirect"); | ||
1130 | |||
1131 | message.setValueS32("code", code); | ||
1132 | message.setValue("url", url); | ||
1133 | |||
1134 | sendMessage(message); | ||
1135 | } | ||
1136 | |||
1137 | void LLPluginClassMedia::setBrowserUserAgent(const std::string& user_agent) | ||
1138 | { | ||
1139 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "set_user_agent"); | ||
1140 | |||
1141 | message.setValue("user_agent", user_agent); | ||
1142 | |||
1143 | sendMessage(message); | ||
1144 | } | ||
1145 | |||
1146 | void LLPluginClassMedia::crashPlugin() | ||
1147 | { | ||
1148 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "crash"); | ||
1149 | |||
1150 | sendMessage(message); | ||
1151 | } | ||
1152 | |||
1153 | void LLPluginClassMedia::hangPlugin() | ||
1154 | { | ||
1155 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "hang"); | ||
1156 | |||
1157 | sendMessage(message); | ||
1158 | } | ||
1159 | |||
1160 | |||
1161 | //////////////////////////////////////////////////////////// | ||
1162 | // MARK: media_time class functions | ||
1163 | bool LLPluginClassMedia::pluginSupportsMediaTime(void) | ||
1164 | { | ||
1165 | std::string version = mPlugin->getMessageClassVersion(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME); | ||
1166 | return !version.empty(); | ||
1167 | } | ||
1168 | |||
1169 | void LLPluginClassMedia::stop() | ||
1170 | { | ||
1171 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "stop"); | ||
1172 | sendMessage(message); | ||
1173 | } | ||
1174 | |||
1175 | void LLPluginClassMedia::start(float rate) | ||
1176 | { | ||
1177 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "start"); | ||
1178 | |||
1179 | message.setValueReal("rate", rate); | ||
1180 | |||
1181 | sendMessage(message); | ||
1182 | } | ||
1183 | |||
1184 | void LLPluginClassMedia::pause() | ||
1185 | { | ||
1186 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "pause"); | ||
1187 | sendMessage(message); | ||
1188 | } | ||
1189 | |||
1190 | void LLPluginClassMedia::seek(float time) | ||
1191 | { | ||
1192 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "seek"); | ||
1193 | |||
1194 | message.setValueReal("time", time); | ||
1195 | |||
1196 | sendMessage(message); | ||
1197 | } | ||
1198 | |||
1199 | void LLPluginClassMedia::setLoop(bool loop) | ||
1200 | { | ||
1201 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "set_loop"); | ||
1202 | |||
1203 | message.setValueBoolean("loop", loop); | ||
1204 | |||
1205 | sendMessage(message); | ||
1206 | } | ||
1207 | |||
1208 | void LLPluginClassMedia::setVolume(float volume) | ||
1209 | { | ||
1210 | if(volume != mRequestedVolume) | ||
1211 | { | ||
1212 | mRequestedVolume = volume; | ||
1213 | |||
1214 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "set_volume"); | ||
1215 | |||
1216 | message.setValueReal("volume", volume); | ||
1217 | |||
1218 | sendMessage(message); | ||
1219 | } | ||
1220 | } | ||
1221 | |||
1222 | float LLPluginClassMedia::getVolume() | ||
1223 | { | ||
1224 | return mRequestedVolume; | ||
1225 | } | ||
1226 | |||
1227 | void LLPluginClassMedia::initializeUrlHistory(const LLSD& url_history) | ||
1228 | { | ||
1229 | // Send URL history to plugin | ||
1230 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "init_history"); | ||
1231 | message.setValueLLSD("history", url_history); | ||
1232 | sendMessage(message); | ||
1233 | |||
1234 | LL_DEBUGS("PluginClassMedia") << "Sending history" << LL_ENDL; | ||
1235 | } | ||
1236 | |||
diff --git a/linden/indra/llplugin/llpluginclassmedia.h b/linden/indra/llplugin/llpluginclassmedia.h new file mode 100755 index 0000000..0004971 --- /dev/null +++ b/linden/indra/llplugin/llpluginclassmedia.h | |||
@@ -0,0 +1,377 @@ | |||
1 | /** | ||
2 | * @file llpluginclassmedia.h | ||
3 | * @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class. | ||
4 | * | ||
5 | * @cond | ||
6 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
7 | * | ||
8 | * Copyright (c) 2008-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_LLPLUGINCLASSMEDIA_H | ||
37 | #define LL_LLPLUGINCLASSMEDIA_H | ||
38 | |||
39 | #include "llgltypes.h" | ||
40 | #include "llpluginprocessparent.h" | ||
41 | #include "llrect.h" | ||
42 | #include "llpluginclassmediaowner.h" | ||
43 | #include <queue> | ||
44 | #include "v4color.h" | ||
45 | |||
46 | class LLPluginClassMedia : public LLPluginProcessParentOwner | ||
47 | { | ||
48 | LOG_CLASS(LLPluginClassMedia); | ||
49 | public: | ||
50 | LLPluginClassMedia(LLPluginClassMediaOwner *owner); | ||
51 | virtual ~LLPluginClassMedia(); | ||
52 | |||
53 | // local initialization, called by the media manager when creating a source | ||
54 | virtual bool init(const std::string &launcher_filename, | ||
55 | const std::string &plugin_filename, | ||
56 | bool debug); | ||
57 | |||
58 | // undoes everything init() didm called by the media manager when destroying a source | ||
59 | virtual void reset(); | ||
60 | |||
61 | void idle(void); | ||
62 | |||
63 | // All of these may return 0 or an actual valid value. | ||
64 | // Callers need to check the return for 0, and not use the values in that case. | ||
65 | int getWidth() const { return (mMediaWidth > 0) ? mMediaWidth : 0; }; | ||
66 | int getHeight() const { return (mMediaHeight > 0) ? mMediaHeight : 0; }; | ||
67 | int getNaturalWidth() const { return mNaturalMediaWidth; }; | ||
68 | int getNaturalHeight() const { return mNaturalMediaHeight; }; | ||
69 | int getSetWidth() const { return mSetMediaWidth; }; | ||
70 | int getSetHeight() const { return mSetMediaHeight; }; | ||
71 | int getBitsWidth() const { return (mTextureWidth > 0) ? mTextureWidth : 0; }; | ||
72 | int getBitsHeight() const { return (mTextureHeight > 0) ? mTextureHeight : 0; }; | ||
73 | int getTextureWidth() const; | ||
74 | int getTextureHeight() const; | ||
75 | int getFullWidth() const { return mFullMediaWidth; }; | ||
76 | int getFullHeight() const { return mFullMediaHeight; }; | ||
77 | |||
78 | // This may return NULL. Callers need to check for and handle this case. | ||
79 | unsigned char* getBitsData(); | ||
80 | |||
81 | // gets the format details of the texture data | ||
82 | // These may return 0 if they haven't been set up yet. The caller needs to detect this case. | ||
83 | int getTextureDepth() const { return mRequestedTextureDepth; }; | ||
84 | int getTextureFormatInternal() const { return mRequestedTextureInternalFormat; }; | ||
85 | int getTextureFormatPrimary() const { return mRequestedTextureFormat; }; | ||
86 | int getTextureFormatType() const { return mRequestedTextureType; }; | ||
87 | bool getTextureFormatSwapBytes() const { return mRequestedTextureSwapBytes; }; | ||
88 | bool getTextureCoordsOpenGL() const { return mRequestedTextureCoordsOpenGL; }; | ||
89 | |||
90 | void setSize(int width, int height); | ||
91 | void setAutoScale(bool auto_scale); | ||
92 | |||
93 | void setBackgroundColor(LLColor4 color) { mBackgroundColor = color; }; | ||
94 | |||
95 | // Returns true if all of the texture parameters (depth, format, size, and texture size) are set up and consistent. | ||
96 | // This will initially be false, and will also be false for some time after setSize while the resize is processed. | ||
97 | // Note that if this returns true, it is safe to use all the get() functions above without checking for invalid return values | ||
98 | // until you call idle() again. | ||
99 | bool textureValid(void); | ||
100 | |||
101 | bool getDirty(LLRect *dirty_rect = NULL); | ||
102 | void resetDirty(void); | ||
103 | |||
104 | typedef enum | ||
105 | { | ||
106 | MOUSE_EVENT_DOWN, | ||
107 | MOUSE_EVENT_UP, | ||
108 | MOUSE_EVENT_MOVE, | ||
109 | MOUSE_EVENT_DOUBLE_CLICK | ||
110 | }EMouseEventType; | ||
111 | |||
112 | void mouseEvent(EMouseEventType type, int button, int x, int y, MASK modifiers); | ||
113 | |||
114 | typedef enum | ||
115 | { | ||
116 | KEY_EVENT_DOWN, | ||
117 | KEY_EVENT_UP, | ||
118 | KEY_EVENT_REPEAT | ||
119 | }EKeyEventType; | ||
120 | |||
121 | bool keyEvent(EKeyEventType type, int key_code, MASK modifiers, LLSD native_key_data); | ||
122 | |||
123 | void scrollEvent(int x, int y, MASK modifiers); | ||
124 | |||
125 | // Text may be unicode (utf8 encoded) | ||
126 | bool textInput(const std::string &text, MASK modifiers, LLSD native_key_data); | ||
127 | |||
128 | void loadURI(const std::string &uri); | ||
129 | |||
130 | // "Loading" means uninitialized or any state prior to fully running (processing commands) | ||
131 | bool isPluginLoading(void) { return mPlugin?mPlugin->isLoading():false; }; | ||
132 | |||
133 | // "Running" means the steady state -- i.e. processing messages | ||
134 | bool isPluginRunning(void) { return mPlugin?mPlugin->isRunning():false; }; | ||
135 | |||
136 | // "Exited" means any regular or error state after "Running" (plugin may have crashed or exited normally) | ||
137 | bool isPluginExited(void) { return mPlugin?mPlugin->isDone():false; }; | ||
138 | |||
139 | std::string getPluginVersion() { return mPlugin?mPlugin->getPluginVersion():std::string(""); }; | ||
140 | |||
141 | bool getDisableTimeout() { return mPlugin?mPlugin->getDisableTimeout():false; }; | ||
142 | void setDisableTimeout(bool disable) { if(mPlugin) mPlugin->setDisableTimeout(disable); }; | ||
143 | |||
144 | // Inherited from LLPluginProcessParentOwner | ||
145 | /* virtual */ void receivePluginMessage(const LLPluginMessage &message); | ||
146 | /* virtual */ void pluginLaunchFailed(); | ||
147 | /* virtual */ void pluginDied(); | ||
148 | |||
149 | |||
150 | typedef enum | ||
151 | { | ||
152 | PRIORITY_UNLOADED, // media plugin isn't even loaded. | ||
153 | PRIORITY_STOPPED, // media is not playing, shouldn't need to update at all. | ||
154 | PRIORITY_HIDDEN, // media is not being displayed or is out of view, don't need to do graphic updates, but may still update audio, playhead, etc. | ||
155 | PRIORITY_SLIDESHOW, // media is in the far distance, updates very infrequently | ||
156 | PRIORITY_LOW, // media is in the distance, may be rendered at reduced size | ||
157 | PRIORITY_NORMAL, // normal (default) priority | ||
158 | PRIORITY_HIGH // media has user focus and/or is taking up most of the screen | ||
159 | }EPriority; | ||
160 | |||
161 | static const char* priorityToString(EPriority priority); | ||
162 | void setPriority(EPriority priority); | ||
163 | void setLowPrioritySizeLimit(int size); | ||
164 | |||
165 | F64 getCPUUsage(); | ||
166 | |||
167 | // Valid after a MEDIA_EVENT_CURSOR_CHANGED event | ||
168 | std::string getCursorName() const { return mCursorName; }; | ||
169 | |||
170 | LLPluginClassMediaOwner::EMediaStatus getStatus() const { return mStatus; } | ||
171 | |||
172 | void cut(); | ||
173 | bool canCut() const { return mCanCut; }; | ||
174 | |||
175 | void copy(); | ||
176 | bool canCopy() const { return mCanCopy; }; | ||
177 | |||
178 | void paste(); | ||
179 | bool canPaste() const { return mCanPaste; }; | ||
180 | |||
181 | // These can be called before init(), and they will be queued and sent before the media init message. | ||
182 | void setUserDataPath(const std::string &user_data_path); | ||
183 | void setLanguageCode(const std::string &language_code); | ||
184 | void setPluginsEnabled(const bool enabled); | ||
185 | void setJavascriptEnabled(const bool enabled); | ||
186 | |||
187 | /////////////////////////////////// | ||
188 | // media browser class functions | ||
189 | bool pluginSupportsMediaBrowser(void); | ||
190 | |||
191 | void focus(bool focused); | ||
192 | void clear_cache(); | ||
193 | void clear_cookies(); | ||
194 | void set_cookies(const std::string &cookies); | ||
195 | void enable_cookies(bool enable); | ||
196 | void proxy_setup(bool enable, const std::string &host = LLStringUtil::null, int port = 0); | ||
197 | void browse_stop(); | ||
198 | void browse_reload(bool ignore_cache = false); | ||
199 | void browse_forward(); | ||
200 | void browse_back(); | ||
201 | void set_status_redirect(int code, const std::string &url); | ||
202 | void setBrowserUserAgent(const std::string& user_agent); | ||
203 | |||
204 | // This is valid after MEDIA_EVENT_NAVIGATE_BEGIN or MEDIA_EVENT_NAVIGATE_COMPLETE | ||
205 | std::string getNavigateURI() const { return mNavigateURI; }; | ||
206 | |||
207 | // These are valid after MEDIA_EVENT_NAVIGATE_COMPLETE | ||
208 | S32 getNavigateResultCode() const { return mNavigateResultCode; }; | ||
209 | std::string getNavigateResultString() const { return mNavigateResultString; }; | ||
210 | bool getHistoryBackAvailable() const { return mHistoryBackAvailable; }; | ||
211 | bool getHistoryForwardAvailable() const { return mHistoryForwardAvailable; }; | ||
212 | |||
213 | // This is valid after MEDIA_EVENT_PROGRESS_UPDATED | ||
214 | int getProgressPercent() const { return mProgressPercent; }; | ||
215 | |||
216 | // This is valid after MEDIA_EVENT_STATUS_TEXT_CHANGED | ||
217 | std::string getStatusText() const { return mStatusText; }; | ||
218 | |||
219 | // This is valid after MEDIA_EVENT_LOCATION_CHANGED | ||
220 | std::string getLocation() const { return mLocation; }; | ||
221 | |||
222 | // This is valid after MEDIA_EVENT_CLICK_LINK_HREF or MEDIA_EVENT_CLICK_LINK_NOFOLLOW | ||
223 | std::string getClickURL() const { return mClickURL; }; | ||
224 | |||
225 | // This is valid after MEDIA_EVENT_CLICK_LINK_HREF | ||
226 | std::string getClickTarget() const { return mClickTarget; }; | ||
227 | |||
228 | |||
229 | std::string getMediaName() const { return mMediaName; }; | ||
230 | std::string getMediaDescription() const { return mMediaDescription; }; | ||
231 | |||
232 | // Crash the plugin. If you use this outside of a testbed, you will be punished. | ||
233 | void crashPlugin(); | ||
234 | |||
235 | // Hang the plugin. If you use this outside of a testbed, you will be punished. | ||
236 | void hangPlugin(); | ||
237 | |||
238 | /////////////////////////////////// | ||
239 | // media time class functions | ||
240 | bool pluginSupportsMediaTime(void); | ||
241 | void stop(); | ||
242 | void start(float rate = 0.0f); | ||
243 | void pause(); | ||
244 | void seek(float time); | ||
245 | void setLoop(bool loop); | ||
246 | void setVolume(float volume); | ||
247 | float getVolume(); | ||
248 | |||
249 | F64 getCurrentTime(void) const { return mCurrentTime; }; | ||
250 | F64 getDuration(void) const { return mDuration; }; | ||
251 | F64 getCurrentPlayRate(void) { return mCurrentRate; }; | ||
252 | F64 getLoadedDuration(void) const { return mLoadedDuration; }; | ||
253 | |||
254 | // Initialize the URL history of the plugin by sending | ||
255 | // "init_history" message | ||
256 | void initializeUrlHistory(const LLSD& url_history); | ||
257 | |||
258 | protected: | ||
259 | |||
260 | LLPluginClassMediaOwner *mOwner; | ||
261 | |||
262 | // Notify this object's owner that an event has occurred. | ||
263 | void mediaEvent(LLPluginClassMediaOwner::EMediaEvent event); | ||
264 | |||
265 | void sendMessage(const LLPluginMessage &message); // Send message internally, either queueing or sending directly. | ||
266 | std::queue<LLPluginMessage> mSendQueue; // Used to queue messages while the plugin initializes. | ||
267 | |||
268 | void setSizeInternal(void); | ||
269 | |||
270 | bool mTextureParamsReceived; // the mRequestedTexture* fields are only valid when this is true | ||
271 | S32 mRequestedTextureDepth; | ||
272 | LLGLenum mRequestedTextureInternalFormat; | ||
273 | LLGLenum mRequestedTextureFormat; | ||
274 | LLGLenum mRequestedTextureType; | ||
275 | bool mRequestedTextureSwapBytes; | ||
276 | bool mRequestedTextureCoordsOpenGL; | ||
277 | |||
278 | std::string mTextureSharedMemoryName; | ||
279 | size_t mTextureSharedMemorySize; | ||
280 | |||
281 | // True to scale requested media up to the full size of the texture (i.e. next power of two) | ||
282 | bool mAutoScaleMedia; | ||
283 | |||
284 | // default media size for the plugin, from the texture_params message. | ||
285 | int mDefaultMediaWidth; | ||
286 | int mDefaultMediaHeight; | ||
287 | |||
288 | // Size that has been requested by the plugin itself | ||
289 | int mNaturalMediaWidth; | ||
290 | int mNaturalMediaHeight; | ||
291 | |||
292 | // Size that has been requested with setSize() | ||
293 | int mSetMediaWidth; | ||
294 | int mSetMediaHeight; | ||
295 | |||
296 | // Full calculated media size (before auto-scale and downsample calculations) | ||
297 | int mFullMediaWidth; | ||
298 | int mFullMediaHeight; | ||
299 | |||
300 | // Actual media size being set (after auto-scale) | ||
301 | int mRequestedMediaWidth; | ||
302 | int mRequestedMediaHeight; | ||
303 | |||
304 | // Texture size calculated from actual media size | ||
305 | int mRequestedTextureWidth; | ||
306 | int mRequestedTextureHeight; | ||
307 | |||
308 | // Size that the plugin has acknowledged | ||
309 | int mTextureWidth; | ||
310 | int mTextureHeight; | ||
311 | int mMediaWidth; | ||
312 | int mMediaHeight; | ||
313 | |||
314 | float mRequestedVolume; | ||
315 | |||
316 | // Priority of this media stream | ||
317 | EPriority mPriority; | ||
318 | int mLowPrioritySizeLimit; | ||
319 | |||
320 | bool mAllowDownsample; | ||
321 | int mPadding; | ||
322 | |||
323 | |||
324 | LLPluginProcessParent *mPlugin; | ||
325 | |||
326 | LLRect mDirtyRect; | ||
327 | |||
328 | std::string translateModifiers(MASK modifiers); | ||
329 | |||
330 | std::string mCursorName; | ||
331 | int mLastMouseX; | ||
332 | int mLastMouseY; | ||
333 | |||
334 | LLPluginClassMediaOwner::EMediaStatus mStatus; | ||
335 | |||
336 | F64 mSleepTime; | ||
337 | |||
338 | bool mCanCut; | ||
339 | bool mCanCopy; | ||
340 | bool mCanPaste; | ||
341 | |||
342 | std::string mMediaName; | ||
343 | std::string mMediaDescription; | ||
344 | |||
345 | LLColor4 mBackgroundColor; | ||
346 | |||
347 | ///////////////////////////////////////// | ||
348 | // media_browser class | ||
349 | std::string mNavigateURI; | ||
350 | S32 mNavigateResultCode; | ||
351 | std::string mNavigateResultString; | ||
352 | bool mHistoryBackAvailable; | ||
353 | bool mHistoryForwardAvailable; | ||
354 | std::string mStatusText; | ||
355 | int mProgressPercent; | ||
356 | std::string mLocation; | ||
357 | std::string mClickURL; | ||
358 | std::string mClickTarget; | ||
359 | |||
360 | ///////////////////////////////////////// | ||
361 | // media_time class | ||
362 | F64 mCurrentTime; | ||
363 | F64 mDuration; | ||
364 | F64 mCurrentRate; | ||
365 | F64 mLoadedDuration; | ||
366 | |||
367 | //-------------------------------------- | ||
368 | //debug use only | ||
369 | // | ||
370 | private: | ||
371 | bool mDeleteOK ; | ||
372 | public: | ||
373 | void setDeleteOK(bool flag) { mDeleteOK = flag ;} | ||
374 | //-------------------------------------- | ||
375 | }; | ||
376 | |||
377 | #endif // LL_LLPLUGINCLASSMEDIA_H | ||
diff --git a/linden/indra/llplugin/llpluginclassmediaowner.h b/linden/indra/llplugin/llpluginclassmediaowner.h new file mode 100755 index 0000000..0e54f7f --- /dev/null +++ b/linden/indra/llplugin/llpluginclassmediaowner.h | |||
@@ -0,0 +1,87 @@ | |||
1 | /** | ||
2 | * @file llpluginclassmediaowner.h | ||
3 | * @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class. | ||
4 | * | ||
5 | * @cond | ||
6 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
7 | * | ||
8 | * Copyright (c) 2008-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_LLPLUGINCLASSMEDIAOWNER_H | ||
37 | #define LL_LLPLUGINCLASSMEDIAOWNER_H | ||
38 | |||
39 | #include "llpluginprocessparent.h" | ||
40 | #include "llrect.h" | ||
41 | #include <queue> | ||
42 | |||
43 | class LLPluginClassMedia; | ||
44 | //class LLPluginCookieStore; // IMPRUDENCE: this is currently not used | ||
45 | |||
46 | class LLPluginClassMediaOwner | ||
47 | { | ||
48 | public: | ||
49 | typedef enum | ||
50 | { | ||
51 | MEDIA_EVENT_CONTENT_UPDATED, // contents/dirty rect have updated | ||
52 | MEDIA_EVENT_TIME_DURATION_UPDATED, // current time and/or duration have updated | ||
53 | MEDIA_EVENT_SIZE_CHANGED, // media size has changed | ||
54 | MEDIA_EVENT_CURSOR_CHANGED, // plugin has requested a cursor change | ||
55 | |||
56 | MEDIA_EVENT_NAVIGATE_BEGIN, // browser has begun navigation | ||
57 | MEDIA_EVENT_NAVIGATE_COMPLETE, // browser has finished navigation | ||
58 | MEDIA_EVENT_PROGRESS_UPDATED, // browser has updated loading progress | ||
59 | MEDIA_EVENT_STATUS_TEXT_CHANGED, // browser has updated the status text | ||
60 | MEDIA_EVENT_NAME_CHANGED, // browser has updated the name of the media (typically <title> tag) | ||
61 | MEDIA_EVENT_LOCATION_CHANGED, // browser location (URL) has changed (maybe due to internal navagation/frames/etc) | ||
62 | MEDIA_EVENT_CLICK_LINK_HREF, // I'm not entirely sure what the semantics of these two are | ||
63 | MEDIA_EVENT_CLICK_LINK_NOFOLLOW, | ||
64 | |||
65 | MEDIA_EVENT_PLUGIN_FAILED_LAUNCH, // The plugin failed to launch | ||
66 | MEDIA_EVENT_PLUGIN_FAILED // The plugin died unexpectedly | ||
67 | |||
68 | } EMediaEvent; | ||
69 | |||
70 | typedef enum | ||
71 | { | ||
72 | MEDIA_NONE, // Uninitialized -- no useful state | ||
73 | MEDIA_LOADING, // loading or navigating | ||
74 | MEDIA_LOADED, // navigation/preroll complete | ||
75 | MEDIA_ERROR, // navigation/preroll failed | ||
76 | MEDIA_PLAYING, // playing (only for time-based media) | ||
77 | MEDIA_PAUSED, // paused (only for time-based media) | ||
78 | MEDIA_DONE // finished playing (only for time-based media) | ||
79 | |||
80 | } EMediaStatus; | ||
81 | |||
82 | virtual ~LLPluginClassMediaOwner() {}; | ||
83 | virtual void handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent /*event*/) {}; | ||
84 | virtual void handleCookieSet(LLPluginClassMedia* /*self*/, const std::string &/*cookie*/) {}; | ||
85 | }; | ||
86 | |||
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..6b193de --- /dev/null +++ b/linden/indra/llplugin/llplugincookiestore.cpp | |||
@@ -0,0 +1,673 @@ | |||
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 | /// IMPRUDENCE: this is currently not used | ||
37 | |||
38 | #include "linden_common.h" | ||
39 | #include "indra_constants.h" | ||
40 | |||
41 | #include "llplugincookiestore.h" | ||
42 | #include <iostream> | ||
43 | |||
44 | // for curl_getdate() (apparently parsing RFC 1123 dates is hard) | ||
45 | #include <curl/curl.h> | ||
46 | |||
47 | LLPluginCookieStore::LLPluginCookieStore(): | ||
48 | mHasChangedCookies(false) | ||
49 | { | ||
50 | } | ||
51 | |||
52 | |||
53 | LLPluginCookieStore::~LLPluginCookieStore() | ||
54 | { | ||
55 | clearCookies(); | ||
56 | } | ||
57 | |||
58 | |||
59 | LLPluginCookieStore::Cookie::Cookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end): | ||
60 | mCookie(s, cookie_start, cookie_end - cookie_start), | ||
61 | mNameStart(0), mNameEnd(0), | ||
62 | mValueStart(0), mValueEnd(0), | ||
63 | mDomainStart(0), mDomainEnd(0), | ||
64 | mPathStart(0), mPathEnd(0), | ||
65 | mDead(false), mChanged(true) | ||
66 | { | ||
67 | } | ||
68 | |||
69 | LLPluginCookieStore::Cookie *LLPluginCookieStore::Cookie::createFromString(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, const std::string &host) | ||
70 | { | ||
71 | Cookie *result = new Cookie(s, cookie_start, cookie_end); | ||
72 | |||
73 | if(!result->parse(host)) | ||
74 | { | ||
75 | delete result; | ||
76 | result = NULL; | ||
77 | } | ||
78 | |||
79 | return result; | ||
80 | } | ||
81 | |||
82 | std::string LLPluginCookieStore::Cookie::getKey() const | ||
83 | { | ||
84 | std::string result; | ||
85 | if(mDomainEnd > mDomainStart) | ||
86 | { | ||
87 | result += mCookie.substr(mDomainStart, mDomainEnd - mDomainStart); | ||
88 | } | ||
89 | result += ';'; | ||
90 | if(mPathEnd > mPathStart) | ||
91 | { | ||
92 | result += mCookie.substr(mPathStart, mPathEnd - mPathStart); | ||
93 | } | ||
94 | result += ';'; | ||
95 | result += mCookie.substr(mNameStart, mNameEnd - mNameStart); | ||
96 | return result; | ||
97 | } | ||
98 | |||
99 | bool LLPluginCookieStore::Cookie::parse(const std::string &host) | ||
100 | { | ||
101 | bool first_field = true; | ||
102 | |||
103 | std::string::size_type cookie_end = mCookie.size(); | ||
104 | std::string::size_type field_start = 0; | ||
105 | |||
106 | LL_DEBUGS("CookieStoreParse") << "parsing cookie: " << mCookie << LL_ENDL; | ||
107 | while(field_start < cookie_end) | ||
108 | { | ||
109 | // Finding the start of the next field requires honoring special quoting rules | ||
110 | // see the definition of 'quoted-string' in rfc2616 for details | ||
111 | std::string::size_type next_field_start = findFieldEnd(field_start); | ||
112 | |||
113 | // The end of this field should not include the terminating ';' or any trailing whitespace | ||
114 | std::string::size_type field_end = mCookie.find_last_not_of("; ", next_field_start); | ||
115 | if(field_end == std::string::npos || field_end < field_start) | ||
116 | { | ||
117 | // This field was empty or all whitespace. Set end = start so it shows as empty. | ||
118 | field_end = field_start; | ||
119 | } | ||
120 | else if (field_end < next_field_start) | ||
121 | { | ||
122 | // we actually want the index of the char _after_ what 'last not of' found | ||
123 | ++field_end; | ||
124 | } | ||
125 | |||
126 | // find the start of the actual name (skip separator and possible whitespace) | ||
127 | std::string::size_type name_start = mCookie.find_first_not_of("; ", field_start); | ||
128 | if(name_start == std::string::npos || name_start > next_field_start) | ||
129 | { | ||
130 | // Again, nothing but whitespace. | ||
131 | name_start = field_start; | ||
132 | } | ||
133 | |||
134 | // the name and value are separated by the first equals sign | ||
135 | std::string::size_type name_value_sep = mCookie.find_first_of("=", name_start); | ||
136 | if(name_value_sep == std::string::npos || name_value_sep > field_end) | ||
137 | { | ||
138 | // No separator found, so this is a field without an = | ||
139 | name_value_sep = field_end; | ||
140 | } | ||
141 | |||
142 | // the name end is before the name-value separator | ||
143 | std::string::size_type name_end = mCookie.find_last_not_of("= ", name_value_sep); | ||
144 | if(name_end == std::string::npos || name_end < name_start) | ||
145 | { | ||
146 | // I'm not sure how we'd hit this case... it seems like it would have to be an empty name. | ||
147 | name_end = name_start; | ||
148 | } | ||
149 | else if (name_end < name_value_sep) | ||
150 | { | ||
151 | // we actually want the index of the char _after_ what 'last not of' found | ||
152 | ++name_end; | ||
153 | } | ||
154 | |||
155 | // Value is between the name-value sep and the end of the field. | ||
156 | std::string::size_type value_start = mCookie.find_first_not_of("= ", name_value_sep); | ||
157 | if(value_start == std::string::npos || value_start > field_end) | ||
158 | { | ||
159 | // All whitespace or empty value | ||
160 | value_start = field_end; | ||
161 | } | ||
162 | std::string::size_type value_end = mCookie.find_last_not_of("; ", field_end); | ||
163 | if(value_end == std::string::npos || value_end < value_start) | ||
164 | { | ||
165 | // All whitespace or empty value | ||
166 | value_end = value_start; | ||
167 | } | ||
168 | else if (value_end < field_end) | ||
169 | { | ||
170 | // we actually want the index of the char _after_ what 'last not of' found | ||
171 | ++value_end; | ||
172 | } | ||
173 | |||
174 | LL_DEBUGS("CookieStoreParse") | ||
175 | << " field name: \"" << mCookie.substr(name_start, name_end - name_start) | ||
176 | << "\", value: \"" << mCookie.substr(value_start, value_end - value_start) << "\"" | ||
177 | << LL_ENDL; | ||
178 | |||
179 | // See whether this field is one we know | ||
180 | if(first_field) | ||
181 | { | ||
182 | // The first field is the name=value pair | ||
183 | mNameStart = name_start; | ||
184 | mNameEnd = name_end; | ||
185 | mValueStart = value_start; | ||
186 | mValueEnd = value_end; | ||
187 | first_field = false; | ||
188 | } | ||
189 | else | ||
190 | { | ||
191 | // Subsequent fields must come from the set in rfc2109 | ||
192 | if(matchName(name_start, name_end, "expires")) | ||
193 | { | ||
194 | std::string date_string(mCookie, value_start, value_end - value_start); | ||
195 | // If the cookie contains an "expires" field, it MUST contain a parsable date. | ||
196 | |||
197 | // HACK: LLDate apparently can't PARSE an rfc1123-format date, even though it can GENERATE one. | ||
198 | // The curl function curl_getdate can do this, but I'm hesitant to unilaterally introduce a curl dependency in LLDate. | ||
199 | #if 1 | ||
200 | time_t date = curl_getdate(date_string.c_str(), NULL ); | ||
201 | mDate.secondsSinceEpoch((F64)date); | ||
202 | LL_DEBUGS("CookieStoreParse") << " expire date parsed to: " << mDate.asRFC1123() << LL_ENDL; | ||
203 | #else | ||
204 | // This doesn't work (rfc1123-format dates cause it to fail) | ||
205 | if(!mDate.fromString(date_string)) | ||
206 | { | ||
207 | // Date failed to parse. | ||
208 | LL_WARNS("CookieStoreParse") << "failed to parse cookie's expire date: " << date << LL_ENDL; | ||
209 | return false; | ||
210 | } | ||
211 | #endif | ||
212 | } | ||
213 | else if(matchName(name_start, name_end, "domain")) | ||
214 | { | ||
215 | mDomainStart = value_start; | ||
216 | mDomainEnd = value_end; | ||
217 | } | ||
218 | else if(matchName(name_start, name_end, "path")) | ||
219 | { | ||
220 | mPathStart = value_start; | ||
221 | mPathEnd = value_end; | ||
222 | } | ||
223 | else if(matchName(name_start, name_end, "max-age")) | ||
224 | { | ||
225 | // TODO: how should we handle this? | ||
226 | } | ||
227 | else if(matchName(name_start, name_end, "secure")) | ||
228 | { | ||
229 | // We don't care about the value of this field (yet) | ||
230 | } | ||
231 | else if(matchName(name_start, name_end, "version")) | ||
232 | { | ||
233 | // We don't care about the value of this field (yet) | ||
234 | } | ||
235 | else if(matchName(name_start, name_end, "comment")) | ||
236 | { | ||
237 | // We don't care about the value of this field (yet) | ||
238 | } | ||
239 | else if(matchName(name_start, name_end, "httponly")) | ||
240 | { | ||
241 | // We don't care about the value of this field (yet) | ||
242 | } | ||
243 | else | ||
244 | { | ||
245 | // An unknown field is a parse failure | ||
246 | LL_WARNS("CookieStoreParse") << "unexpected field name: " << mCookie.substr(name_start, name_end - name_start) << LL_ENDL; | ||
247 | return false; | ||
248 | } | ||
249 | |||
250 | } | ||
251 | |||
252 | |||
253 | // move on to the next field, skipping this field's separator and any leading whitespace | ||
254 | field_start = mCookie.find_first_not_of("; ", next_field_start); | ||
255 | } | ||
256 | |||
257 | // The cookie MUST have a name | ||
258 | if(mNameEnd <= mNameStart) | ||
259 | return false; | ||
260 | |||
261 | // If the cookie doesn't have a domain, add the current host as the domain. | ||
262 | if(mDomainEnd <= mDomainStart) | ||
263 | { | ||
264 | if(host.empty()) | ||
265 | { | ||
266 | // no domain and no current host -- this is a parse failure. | ||
267 | return false; | ||
268 | } | ||
269 | |||
270 | // Figure out whether this cookie ended with a ";" or not... | ||
271 | std::string::size_type last_char = mCookie.find_last_not_of(" "); | ||
272 | if((last_char != std::string::npos) && (mCookie[last_char] != ';')) | ||
273 | { | ||
274 | mCookie += ";"; | ||
275 | } | ||
276 | |||
277 | mCookie += " domain="; | ||
278 | mDomainStart = mCookie.size(); | ||
279 | mCookie += host; | ||
280 | mDomainEnd = mCookie.size(); | ||
281 | |||
282 | LL_DEBUGS("CookieStoreParse") << "added domain (" << mDomainStart << " to " << mDomainEnd << "), new cookie is: " << mCookie << LL_ENDL; | ||
283 | } | ||
284 | |||
285 | // If the cookie doesn't have a path, add "/". | ||
286 | if(mPathEnd <= mPathStart) | ||
287 | { | ||
288 | // Figure out whether this cookie ended with a ";" or not... | ||
289 | std::string::size_type last_char = mCookie.find_last_not_of(" "); | ||
290 | if((last_char != std::string::npos) && (mCookie[last_char] != ';')) | ||
291 | { | ||
292 | mCookie += ";"; | ||
293 | } | ||
294 | |||
295 | mCookie += " path="; | ||
296 | mPathStart = mCookie.size(); | ||
297 | mCookie += "/"; | ||
298 | mPathEnd = mCookie.size(); | ||
299 | |||
300 | LL_DEBUGS("CookieStoreParse") << "added path (" << mPathStart << " to " << mPathEnd << "), new cookie is: " << mCookie << LL_ENDL; | ||
301 | } | ||
302 | |||
303 | |||
304 | return true; | ||
305 | } | ||
306 | |||
307 | std::string::size_type LLPluginCookieStore::Cookie::findFieldEnd(std::string::size_type start, std::string::size_type end) | ||
308 | { | ||
309 | std::string::size_type result = start; | ||
310 | |||
311 | if(end == std::string::npos) | ||
312 | end = mCookie.size(); | ||
313 | |||
314 | bool in_quotes = false; | ||
315 | for(; (result < end); result++) | ||
316 | { | ||
317 | switch(mCookie[result]) | ||
318 | { | ||
319 | case '\\': | ||
320 | if(in_quotes) | ||
321 | result++; // The next character is backslash-quoted. Skip over it. | ||
322 | break; | ||
323 | case '"': | ||
324 | in_quotes = !in_quotes; | ||
325 | break; | ||
326 | case ';': | ||
327 | if(!in_quotes) | ||
328 | return result; | ||
329 | break; | ||
330 | } | ||
331 | } | ||
332 | |||
333 | // If we got here, no ';' was found. | ||
334 | return end; | ||
335 | } | ||
336 | |||
337 | bool LLPluginCookieStore::Cookie::matchName(std::string::size_type start, std::string::size_type end, const char *name) | ||
338 | { | ||
339 | // NOTE: this assumes 'name' is already in lowercase. The code which uses it should be able to arrange this... | ||
340 | |||
341 | while((start < end) && (*name != '\0')) | ||
342 | { | ||
343 | if(tolower(mCookie[start]) != *name) | ||
344 | return false; | ||
345 | |||
346 | start++; | ||
347 | name++; | ||
348 | } | ||
349 | |||
350 | // iff both strings hit the end at the same time, they're equal. | ||
351 | return ((start == end) && (*name == '\0')); | ||
352 | } | ||
353 | |||
354 | std::string LLPluginCookieStore::getAllCookies() | ||
355 | { | ||
356 | std::stringstream result; | ||
357 | writeAllCookies(result); | ||
358 | return result.str(); | ||
359 | } | ||
360 | |||
361 | void LLPluginCookieStore::writeAllCookies(std::ostream& s) | ||
362 | { | ||
363 | cookie_map_t::iterator iter; | ||
364 | for(iter = mCookies.begin(); iter != mCookies.end(); iter++) | ||
365 | { | ||
366 | // Don't return expired cookies | ||
367 | if(!iter->second->isDead()) | ||
368 | { | ||
369 | s << (iter->second->getCookie()) << "\n"; | ||
370 | } | ||
371 | } | ||
372 | |||
373 | } | ||
374 | |||
375 | std::string LLPluginCookieStore::getPersistentCookies() | ||
376 | { | ||
377 | std::stringstream result; | ||
378 | writePersistentCookies(result); | ||
379 | return result.str(); | ||
380 | } | ||
381 | |||
382 | void LLPluginCookieStore::writePersistentCookies(std::ostream& s) | ||
383 | { | ||
384 | cookie_map_t::iterator iter; | ||
385 | for(iter = mCookies.begin(); iter != mCookies.end(); iter++) | ||
386 | { | ||
387 | // Don't return expired cookies or session cookies | ||
388 | if(!iter->second->isDead() && !iter->second->isSessionCookie()) | ||
389 | { | ||
390 | s << iter->second->getCookie() << "\n"; | ||
391 | } | ||
392 | } | ||
393 | } | ||
394 | |||
395 | std::string LLPluginCookieStore::getChangedCookies(bool clear_changed) | ||
396 | { | ||
397 | std::stringstream result; | ||
398 | writeChangedCookies(result, clear_changed); | ||
399 | |||
400 | return result.str(); | ||
401 | } | ||
402 | |||
403 | void LLPluginCookieStore::writeChangedCookies(std::ostream& s, bool clear_changed) | ||
404 | { | ||
405 | if(mHasChangedCookies) | ||
406 | { | ||
407 | lldebugs << "returning changed cookies: " << llendl; | ||
408 | cookie_map_t::iterator iter; | ||
409 | for(iter = mCookies.begin(); iter != mCookies.end(); ) | ||
410 | { | ||
411 | cookie_map_t::iterator next = iter; | ||
412 | next++; | ||
413 | |||
414 | // Only return cookies marked as "changed" | ||
415 | if(iter->second->isChanged()) | ||
416 | { | ||
417 | s << iter->second->getCookie() << "\n"; | ||
418 | |||
419 | lldebugs << " " << iter->second->getCookie() << llendl; | ||
420 | |||
421 | // If requested, clear the changed mark | ||
422 | if(clear_changed) | ||
423 | { | ||
424 | if(iter->second->isDead()) | ||
425 | { | ||
426 | // If this cookie was previously marked dead, it needs to be removed entirely. | ||
427 | delete iter->second; | ||
428 | mCookies.erase(iter); | ||
429 | } | ||
430 | else | ||
431 | { | ||
432 | // Not dead, just mark as not changed. | ||
433 | iter->second->setChanged(false); | ||
434 | } | ||
435 | } | ||
436 | } | ||
437 | |||
438 | iter = next; | ||
439 | } | ||
440 | } | ||
441 | |||
442 | if(clear_changed) | ||
443 | mHasChangedCookies = false; | ||
444 | } | ||
445 | |||
446 | void LLPluginCookieStore::setAllCookies(const std::string &cookies, bool mark_changed) | ||
447 | { | ||
448 | clearCookies(); | ||
449 | setCookies(cookies, mark_changed); | ||
450 | } | ||
451 | |||
452 | void LLPluginCookieStore::readAllCookies(std::istream& s, bool mark_changed) | ||
453 | { | ||
454 | clearCookies(); | ||
455 | readCookies(s, mark_changed); | ||
456 | } | ||
457 | |||
458 | void LLPluginCookieStore::setCookies(const std::string &cookies, bool mark_changed) | ||
459 | { | ||
460 | std::string::size_type start = 0; | ||
461 | |||
462 | while(start != std::string::npos) | ||
463 | { | ||
464 | std::string::size_type end = cookies.find_first_of("\r\n", start); | ||
465 | if(end > start) | ||
466 | { | ||
467 | // The line is non-empty. Try to create a cookie from it. | ||
468 | setOneCookie(cookies, start, end, mark_changed); | ||
469 | } | ||
470 | start = cookies.find_first_not_of("\r\n ", end); | ||
471 | } | ||
472 | } | ||
473 | |||
474 | void LLPluginCookieStore::setCookiesFromHost(const std::string &cookies, const std::string &host, bool mark_changed) | ||
475 | { | ||
476 | std::string::size_type start = 0; | ||
477 | |||
478 | while(start != std::string::npos) | ||
479 | { | ||
480 | std::string::size_type end = cookies.find_first_of("\r\n", start); | ||
481 | if(end > start) | ||
482 | { | ||
483 | // The line is non-empty. Try to create a cookie from it. | ||
484 | setOneCookie(cookies, start, end, mark_changed, host); | ||
485 | } | ||
486 | start = cookies.find_first_not_of("\r\n ", end); | ||
487 | } | ||
488 | } | ||
489 | |||
490 | void LLPluginCookieStore::readCookies(std::istream& s, bool mark_changed) | ||
491 | { | ||
492 | std::string line; | ||
493 | while(s.good() && !s.eof()) | ||
494 | { | ||
495 | std::getline(s, line); | ||
496 | if(!line.empty()) | ||
497 | { | ||
498 | // Try to create a cookie from this line. | ||
499 | setOneCookie(line, 0, std::string::npos, mark_changed); | ||
500 | } | ||
501 | } | ||
502 | } | ||
503 | |||
504 | std::string LLPluginCookieStore::quoteString(const std::string &s) | ||
505 | { | ||
506 | std::stringstream result; | ||
507 | |||
508 | result << '"'; | ||
509 | |||
510 | for(std::string::size_type i = 0; i < s.size(); ++i) | ||
511 | { | ||
512 | char c = s[i]; | ||
513 | switch(c) | ||
514 | { | ||
515 | // All these separators need to be quoted in HTTP headers, according to section 2.2 of rfc 2616: | ||
516 | case '(': case ')': case '<': case '>': case '@': | ||
517 | case ',': case ';': case ':': case '\\': case '"': | ||
518 | case '/': case '[': case ']': case '?': case '=': | ||
519 | case '{': case '}': case ' ': case '\t': | ||
520 | result << '\\'; | ||
521 | break; | ||
522 | } | ||
523 | |||
524 | result << c; | ||
525 | } | ||
526 | |||
527 | result << '"'; | ||
528 | |||
529 | return result.str(); | ||
530 | } | ||
531 | |||
532 | std::string LLPluginCookieStore::unquoteString(const std::string &s) | ||
533 | { | ||
534 | std::stringstream result; | ||
535 | |||
536 | bool in_quotes = false; | ||
537 | |||
538 | for(std::string::size_type i = 0; i < s.size(); ++i) | ||
539 | { | ||
540 | char c = s[i]; | ||
541 | switch(c) | ||
542 | { | ||
543 | case '\\': | ||
544 | if(in_quotes) | ||
545 | { | ||
546 | // The next character is backslash-quoted. Pass it through untouched. | ||
547 | ++i; | ||
548 | if(i < s.size()) | ||
549 | { | ||
550 | result << s[i]; | ||
551 | } | ||
552 | continue; | ||
553 | } | ||
554 | break; | ||
555 | case '"': | ||
556 | in_quotes = !in_quotes; | ||
557 | continue; | ||
558 | break; | ||
559 | } | ||
560 | |||
561 | result << c; | ||
562 | } | ||
563 | |||
564 | return result.str(); | ||
565 | } | ||
566 | |||
567 | // The flow for deleting a cookie is non-obvious enough that I should call it out here... | ||
568 | // Deleting a cookie is done by setting a cookie with the same name, path, and domain, but with an expire timestamp in the past. | ||
569 | // (This is exactly how a web server tells a browser to delete a cookie.) | ||
570 | // 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. | ||
571 | // 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 | ||
572 | // delete operation (in the form of the expired cookie) is passed along. | ||
573 | void 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) | ||
574 | { | ||
575 | Cookie *cookie = Cookie::createFromString(s, cookie_start, cookie_end, host); | ||
576 | if(cookie) | ||
577 | { | ||
578 | LL_DEBUGS("CookieStoreUpdate") << "setting cookie: " << cookie->getCookie() << LL_ENDL; | ||
579 | |||
580 | // Create a key for this cookie | ||
581 | std::string key = cookie->getKey(); | ||
582 | |||
583 | // Check to see whether this cookie should have expired | ||
584 | if(!cookie->isSessionCookie() && (cookie->getDate() < LLDate::now())) | ||
585 | { | ||
586 | // This cookie has expired. | ||
587 | if(mark_changed) | ||
588 | { | ||
589 | // If we're marking cookies as changed, we should keep it anyway since we'll need to send it out with deltas. | ||
590 | cookie->setDead(true); | ||
591 | LL_DEBUGS("CookieStoreUpdate") << " marking dead" << LL_ENDL; | ||
592 | } | ||
593 | else | ||
594 | { | ||
595 | // If we're not marking cookies as changed, we don't need to keep this cookie at all. | ||
596 | // If the cookie was already in the list, delete it. | ||
597 | removeCookie(key); | ||
598 | |||
599 | delete cookie; | ||
600 | cookie = NULL; | ||
601 | |||
602 | LL_DEBUGS("CookieStoreUpdate") << " removing" << LL_ENDL; | ||
603 | } | ||
604 | } | ||
605 | |||
606 | if(cookie) | ||
607 | { | ||
608 | // If it already exists in the map, replace it. | ||
609 | cookie_map_t::iterator iter = mCookies.find(key); | ||
610 | if(iter != mCookies.end()) | ||
611 | { | ||
612 | if(iter->second->getCookie() == cookie->getCookie()) | ||
613 | { | ||
614 | // The new cookie is identical to the old -- don't mark as changed. | ||
615 | // Just leave the old one in the map. | ||
616 | delete cookie; | ||
617 | cookie = NULL; | ||
618 | |||
619 | LL_DEBUGS("CookieStoreUpdate") << " unchanged" << LL_ENDL; | ||
620 | } | ||
621 | else | ||
622 | { | ||
623 | // A matching cookie was already in the map. Replace it. | ||
624 | delete iter->second; | ||
625 | iter->second = cookie; | ||
626 | |||
627 | cookie->setChanged(mark_changed); | ||
628 | if(mark_changed) | ||
629 | mHasChangedCookies = true; | ||
630 | |||
631 | LL_DEBUGS("CookieStoreUpdate") << " replacing" << LL_ENDL; | ||
632 | } | ||
633 | } | ||
634 | else | ||
635 | { | ||
636 | // The cookie wasn't in the map. Insert it. | ||
637 | mCookies.insert(std::make_pair(key, cookie)); | ||
638 | |||
639 | cookie->setChanged(mark_changed); | ||
640 | if(mark_changed) | ||
641 | mHasChangedCookies = true; | ||
642 | |||
643 | LL_DEBUGS("CookieStoreUpdate") << " adding" << LL_ENDL; | ||
644 | } | ||
645 | } | ||
646 | } | ||
647 | else | ||
648 | { | ||
649 | LL_WARNS("CookieStoreUpdate") << "failed to parse cookie: " << s.substr(cookie_start, cookie_end - cookie_start) << LL_ENDL; | ||
650 | } | ||
651 | |||
652 | } | ||
653 | |||
654 | void LLPluginCookieStore::clearCookies() | ||
655 | { | ||
656 | while(!mCookies.empty()) | ||
657 | { | ||
658 | cookie_map_t::iterator iter = mCookies.begin(); | ||
659 | delete iter->second; | ||
660 | mCookies.erase(iter); | ||
661 | } | ||
662 | } | ||
663 | |||
664 | void LLPluginCookieStore::removeCookie(const std::string &key) | ||
665 | { | ||
666 | cookie_map_t::iterator iter = mCookies.find(key); | ||
667 | if(iter != mCookies.end()) | ||
668 | { | ||
669 | delete iter->second; | ||
670 | mCookies.erase(iter); | ||
671 | } | ||
672 | } | ||
673 | |||
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 | |||
44 | class LLPluginCookieStore | ||
45 | { | ||
46 | LOG_CLASS(LLPluginCookieStore); | ||
47 | public: | ||
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 | |||
78 | private: | ||
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/llplugininstance.cpp b/linden/indra/llplugin/llplugininstance.cpp new file mode 100755 index 0000000..399f157 --- /dev/null +++ b/linden/indra/llplugin/llplugininstance.cpp | |||
@@ -0,0 +1,176 @@ | |||
1 | /** | ||
2 | * @file llplugininstance.cpp | ||
3 | * @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing. | ||
4 | * | ||
5 | * @cond | ||
6 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
7 | * | ||
8 | * Copyright (c) 2008-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 | /// IMPRUDENCE: this is part of the SLPlugin | ||
37 | |||
38 | #include "linden_common.h" | ||
39 | |||
40 | #include "llplugininstance.h" | ||
41 | #include "aiaprpool.h" | ||
42 | |||
43 | /** Virtual destructor. */ | ||
44 | LLPluginInstanceMessageListener::~LLPluginInstanceMessageListener() | ||
45 | { | ||
46 | } | ||
47 | |||
48 | /** | ||
49 | * TODO:DOC describe how it's used | ||
50 | */ | ||
51 | const char *LLPluginInstance::PLUGIN_INIT_FUNCTION_NAME = "LLPluginInitEntryPoint"; | ||
52 | |||
53 | /** | ||
54 | * Constructor. | ||
55 | * | ||
56 | * @param[in] owner Plugin instance. TODO:DOC is this a good description of what "owner" is? | ||
57 | */ | ||
58 | LLPluginInstance::LLPluginInstance(LLPluginInstanceMessageListener *owner) : | ||
59 | mDSOHandle(NULL), | ||
60 | mPluginUserData(NULL), | ||
61 | mPluginSendMessageFunction(NULL) | ||
62 | { | ||
63 | mOwner = owner; | ||
64 | } | ||
65 | |||
66 | /** | ||
67 | * Destructor. | ||
68 | */ | ||
69 | LLPluginInstance::~LLPluginInstance() | ||
70 | { | ||
71 | if(mDSOHandle != NULL) | ||
72 | { | ||
73 | apr_dso_unload(mDSOHandle); | ||
74 | mDSOHandle = NULL; | ||
75 | } | ||
76 | } | ||
77 | |||
78 | /** | ||
79 | * Dynamically loads the plugin and runs the plugin's init function. | ||
80 | * | ||
81 | * @param[in] plugin_file Name of plugin dll/dylib/so. TODO:DOC is this correct? see .h | ||
82 | * @return 0 if successful, APR error code or error code from the plugin's init function on failure. | ||
83 | */ | ||
84 | int LLPluginInstance::load(std::string &plugin_file) | ||
85 | { | ||
86 | pluginInitFunction init_function = NULL; | ||
87 | |||
88 | int result = apr_dso_load(&mDSOHandle, | ||
89 | plugin_file.c_str(), | ||
90 | AIAPRRootPool::get()()); | ||
91 | if(result != APR_SUCCESS) | ||
92 | { | ||
93 | char buf[1024]; | ||
94 | apr_dso_error(mDSOHandle, buf, sizeof(buf)); | ||
95 | |||
96 | LL_WARNS("PluginInstance") << "apr_dso_load of " << plugin_file << " failed with error " << result << " , additional info string: " << buf << LL_ENDL; | ||
97 | |||
98 | } | ||
99 | |||
100 | if(result == APR_SUCCESS) | ||
101 | { | ||
102 | result = apr_dso_sym((apr_dso_handle_sym_t*)&init_function, | ||
103 | mDSOHandle, | ||
104 | PLUGIN_INIT_FUNCTION_NAME); | ||
105 | |||
106 | if(result != APR_SUCCESS) | ||
107 | { | ||
108 | LL_WARNS("PluginInstance") << "apr_dso_sym failed with error " << result << LL_ENDL; | ||
109 | } | ||
110 | } | ||
111 | |||
112 | if(result == APR_SUCCESS) | ||
113 | { | ||
114 | result = init_function(staticReceiveMessage, (void*)this, &mPluginSendMessageFunction, &mPluginUserData); | ||
115 | |||
116 | if(result != APR_SUCCESS) | ||
117 | { | ||
118 | LL_WARNS("PluginInstance") << "call to init function failed with error " << result << LL_ENDL; | ||
119 | } | ||
120 | } | ||
121 | |||
122 | return (int)result; | ||
123 | } | ||
124 | |||
125 | /** | ||
126 | * Sends a message to the plugin. | ||
127 | * | ||
128 | * @param[in] message Message | ||
129 | */ | ||
130 | void LLPluginInstance::sendMessage(const std::string &message) | ||
131 | { | ||
132 | if(mPluginSendMessageFunction) | ||
133 | { | ||
134 | LL_DEBUGS("PluginInstance") << "sending message to plugin: \"" << message << "\"" << LL_ENDL; | ||
135 | mPluginSendMessageFunction(message.c_str(), &mPluginUserData); | ||
136 | } | ||
137 | else | ||
138 | { | ||
139 | LL_WARNS("PluginInstance") << "dropping message: \"" << message << "\"" << LL_ENDL; | ||
140 | } | ||
141 | } | ||
142 | |||
143 | /** | ||
144 | * Idle. TODO:DOC what's the purpose of this? | ||
145 | * | ||
146 | */ | ||
147 | void LLPluginInstance::idle(void) | ||
148 | { | ||
149 | } | ||
150 | |||
151 | // static | ||
152 | void LLPluginInstance::staticReceiveMessage(const char *message_string, void **user_data) | ||
153 | { | ||
154 | // TODO: validate that the user_data argument is still a valid LLPluginInstance pointer | ||
155 | // we could also use a key that's looked up in a map (instead of a direct pointer) for safety, but that's probably overkill | ||
156 | LLPluginInstance *self = (LLPluginInstance*)*user_data; | ||
157 | self->receiveMessage(message_string); | ||
158 | } | ||
159 | |||
160 | /** | ||
161 | * Plugin receives message from plugin loader shell. | ||
162 | * | ||
163 | * @param[in] message_string Message | ||
164 | */ | ||
165 | void LLPluginInstance::receiveMessage(const char *message_string) | ||
166 | { | ||
167 | if(mOwner) | ||
168 | { | ||
169 | LL_DEBUGS("PluginInstance") << "processing incoming message: \"" << message_string << "\"" << LL_ENDL; | ||
170 | mOwner->receivePluginMessage(message_string); | ||
171 | } | ||
172 | else | ||
173 | { | ||
174 | LL_WARNS("PluginInstance") << "dropping incoming message: \"" << message_string << "\"" << LL_ENDL; | ||
175 | } | ||
176 | } | ||
diff --git a/linden/indra/llplugin/llplugininstance.h b/linden/indra/llplugin/llplugininstance.h new file mode 100755 index 0000000..9cf6075 --- /dev/null +++ b/linden/indra/llplugin/llplugininstance.h | |||
@@ -0,0 +1,106 @@ | |||
1 | /** | ||
2 | * @file llplugininstance.h | ||
3 | * | ||
4 | * @cond | ||
5 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
6 | * | ||
7 | * Copyright (c) 2008-2010, Linden Research, Inc. | ||
8 | * | ||
9 | * Second Life Viewer Source Code | ||
10 | * The source code in this file ("Source Code") is provided by Linden Lab | ||
11 | * to you under the terms of the GNU General Public License, version 2.0 | ||
12 | * ("GPL"), unless you have obtained a separate licensing agreement | ||
13 | * ("Other License"), formally executed by you and Linden Lab. Terms of | ||
14 | * the GPL can be found in doc/GPL-license.txt in this distribution, or | ||
15 | * online at http://secondlife.com/developers/opensource/gplv2 | ||
16 | * | ||
17 | * There are special exceptions to the terms and conditions of the GPL as | ||
18 | * it is applied to this Source Code. View the full text of the exception | ||
19 | * in the file doc/FLOSS-exception.txt in this software distribution, or | ||
20 | * online at | ||
21 | * http://secondlife.com/developers/opensource/flossexception | ||
22 | * | ||
23 | * By copying, modifying or distributing this software, you acknowledge | ||
24 | * that you have read and understood your obligations described above, | ||
25 | * and agree to abide by those obligations. | ||
26 | * | ||
27 | * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | ||
28 | * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
29 | * COMPLETENESS OR PERFORMANCE. | ||
30 | * $/LicenseInfo$ | ||
31 | * | ||
32 | * @endcond | ||
33 | */ | ||
34 | |||
35 | #ifndef LL_LLPLUGININSTANCE_H | ||
36 | #define LL_LLPLUGININSTANCE_H | ||
37 | |||
38 | #include "llstring.h" | ||
39 | #include "llapr.h" | ||
40 | |||
41 | #include "apr_dso.h" | ||
42 | |||
43 | /** | ||
44 | * @brief LLPluginInstanceMessageListener receives messages sent from the plugin loader shell to the plugin. | ||
45 | */ | ||
46 | class LLPluginInstanceMessageListener | ||
47 | { | ||
48 | public: | ||
49 | virtual ~LLPluginInstanceMessageListener(); | ||
50 | /** Plugin receives message from plugin loader shell. */ | ||
51 | virtual void receivePluginMessage(const std::string &message) = 0; | ||
52 | }; | ||
53 | |||
54 | /** | ||
55 | * @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing. | ||
56 | */ | ||
57 | class LLPluginInstance | ||
58 | { | ||
59 | LOG_CLASS(LLPluginInstance); | ||
60 | public: | ||
61 | LLPluginInstance(LLPluginInstanceMessageListener *owner); | ||
62 | virtual ~LLPluginInstance(); | ||
63 | |||
64 | // Load a plugin dll/dylib/so | ||
65 | // Returns 0 if successful, APR error code or error code returned from the plugin's init function on failure. | ||
66 | int load(std::string &plugin_file); | ||
67 | |||
68 | // Sends a message to the plugin. | ||
69 | void sendMessage(const std::string &message); | ||
70 | |||
71 | // TODO:DOC is this comment obsolete? can't find "send_count" anywhere in indra tree. | ||
72 | // send_count is the maximum number of message to process from the send queue. If negative, it will drain the queue completely. | ||
73 | // The receive queue is always drained completely. | ||
74 | // Returns the total number of messages processed from both queues. | ||
75 | void idle(void); | ||
76 | |||
77 | /** The signature of the function for sending a message from plugin to plugin loader shell. | ||
78 | * | ||
79 | * @param[in] message_string Null-terminated C string | ||
80 | * @param[in] user_data The opaque reference that the callee supplied during setup. | ||
81 | */ | ||
82 | typedef void (*sendMessageFunction) (const char *message_string, void **user_data); | ||
83 | |||
84 | /** The signature of the plugin init function. TODO:DOC check direction (pluging loader shell to plugin?) | ||
85 | * | ||
86 | * @param[in] host_user_data Data from plugin loader shell. | ||
87 | * @param[in] plugin_send_function Function for sending from the plugin loader shell to plugin. | ||
88 | */ | ||
89 | typedef int (*pluginInitFunction) (sendMessageFunction host_send_func, void *host_user_data, sendMessageFunction *plugin_send_func, void **plugin_user_data); | ||
90 | |||
91 | /** Name of plugin init function */ | ||
92 | static const char *PLUGIN_INIT_FUNCTION_NAME; | ||
93 | |||
94 | private: | ||
95 | static void staticReceiveMessage(const char *message_string, void **user_data); | ||
96 | void receiveMessage(const char *message_string); | ||
97 | |||
98 | apr_dso_handle_t *mDSOHandle; | ||
99 | |||
100 | void *mPluginUserData; | ||
101 | sendMessageFunction mPluginSendMessageFunction; | ||
102 | |||
103 | LLPluginInstanceMessageListener *mOwner; | ||
104 | }; | ||
105 | |||
106 | #endif // LL_LLPLUGININSTANCE_H | ||
diff --git a/linden/indra/llplugin/llpluginmessage.cpp b/linden/indra/llplugin/llpluginmessage.cpp new file mode 100755 index 0000000..0810a04 --- /dev/null +++ b/linden/indra/llplugin/llpluginmessage.cpp | |||
@@ -0,0 +1,447 @@ | |||
1 | /** | ||
2 | * @file llpluginmessage.cpp | ||
3 | * @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins. | ||
4 | * | ||
5 | * @cond | ||
6 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
7 | * | ||
8 | * Copyright (c) 2008-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 | /// IMPRUDENCE: this is part of the viewer and the SLPlugin and the libmedia_plugin_* libraries | ||
37 | |||
38 | #include "linden_common.h" | ||
39 | |||
40 | #include "llpluginmessage.h" | ||
41 | #include "llsdserialize.h" | ||
42 | #include "u64.h" | ||
43 | |||
44 | /** | ||
45 | * Constructor. | ||
46 | */ | ||
47 | LLPluginMessage::LLPluginMessage() | ||
48 | { | ||
49 | } | ||
50 | |||
51 | /** | ||
52 | * Constructor. | ||
53 | * | ||
54 | * @param[in] p Existing message | ||
55 | */ | ||
56 | LLPluginMessage::LLPluginMessage(const LLPluginMessage &p) | ||
57 | { | ||
58 | mMessage = p.mMessage; | ||
59 | } | ||
60 | |||
61 | /** | ||
62 | * Constructor. | ||
63 | * | ||
64 | * @param[in] message_class Message class | ||
65 | * @param[in] message_name Message name | ||
66 | */ | ||
67 | LLPluginMessage::LLPluginMessage(const std::string &message_class, const std::string &message_name) | ||
68 | { | ||
69 | setMessage(message_class, message_name); | ||
70 | } | ||
71 | |||
72 | |||
73 | /** | ||
74 | * Destructor. | ||
75 | */ | ||
76 | LLPluginMessage::~LLPluginMessage() | ||
77 | { | ||
78 | } | ||
79 | |||
80 | /** | ||
81 | * Reset all internal state. | ||
82 | */ | ||
83 | void LLPluginMessage::clear() | ||
84 | { | ||
85 | mMessage = LLSD::emptyMap(); | ||
86 | mMessage["params"] = LLSD::emptyMap(); | ||
87 | } | ||
88 | |||
89 | /** | ||
90 | * Sets the message class and name. Also has the side-effect of clearing any key-value pairs in the message. | ||
91 | * | ||
92 | * @param[in] message_class Message class | ||
93 | * @param[in] message_name Message name | ||
94 | */ | ||
95 | void LLPluginMessage::setMessage(const std::string &message_class, const std::string &message_name) | ||
96 | { | ||
97 | clear(); | ||
98 | mMessage["class"] = message_class; | ||
99 | mMessage["name"] = message_name; | ||
100 | } | ||
101 | |||
102 | /** | ||
103 | * Sets a key/value pair in the message, where the value is a string. | ||
104 | * | ||
105 | * @param[in] key Key | ||
106 | * @param[in] value String value | ||
107 | */ | ||
108 | void LLPluginMessage::setValue(const std::string &key, const std::string &value) | ||
109 | { | ||
110 | mMessage["params"][key] = value; | ||
111 | } | ||
112 | |||
113 | /** | ||
114 | * Sets a key/value pair in the message, where the value is LLSD. | ||
115 | * | ||
116 | * @param[in] key Key | ||
117 | * @param[in] value LLSD value | ||
118 | */ | ||
119 | void LLPluginMessage::setValueLLSD(const std::string &key, const LLSD &value) | ||
120 | { | ||
121 | mMessage["params"][key] = value; | ||
122 | } | ||
123 | |||
124 | /** | ||
125 | * Sets a key/value pair in the message, where the value is signed 32-bit. | ||
126 | * | ||
127 | * @param[in] key Key | ||
128 | * @param[in] value 32-bit signed value | ||
129 | */ | ||
130 | void LLPluginMessage::setValueS32(const std::string &key, S32 value) | ||
131 | { | ||
132 | mMessage["params"][key] = value; | ||
133 | } | ||
134 | |||
135 | /** | ||
136 | * Sets a key/value pair in the message, where the value is unsigned 32-bit. The value is stored as a string beginning with "0x". | ||
137 | * | ||
138 | * @param[in] key Key | ||
139 | * @param[in] value 32-bit unsigned value | ||
140 | */ | ||
141 | void LLPluginMessage::setValueU32(const std::string &key, U32 value) | ||
142 | { | ||
143 | std::stringstream temp; | ||
144 | temp << "0x" << std::hex << value; | ||
145 | setValue(key, temp.str()); | ||
146 | } | ||
147 | |||
148 | /** | ||
149 | * Sets a key/value pair in the message, where the value is a bool. | ||
150 | * | ||
151 | * @param[in] key Key | ||
152 | * @param[in] value Boolean value | ||
153 | */ | ||
154 | void LLPluginMessage::setValueBoolean(const std::string &key, bool value) | ||
155 | { | ||
156 | mMessage["params"][key] = value; | ||
157 | } | ||
158 | |||
159 | /** | ||
160 | * Sets a key/value pair in the message, where the value is a double. | ||
161 | * | ||
162 | * @param[in] key Key | ||
163 | * @param[in] value Boolean value | ||
164 | */ | ||
165 | void LLPluginMessage::setValueReal(const std::string &key, F64 value) | ||
166 | { | ||
167 | mMessage["params"][key] = value; | ||
168 | } | ||
169 | |||
170 | /** | ||
171 | * Sets a key/value pair in the message, where the value is a pointer. The pointer is stored as a string. | ||
172 | * | ||
173 | * @param[in] key Key | ||
174 | * @param[in] value Pointer value | ||
175 | */ | ||
176 | void LLPluginMessage::setValuePointer(const std::string &key, void* value) | ||
177 | { | ||
178 | std::stringstream temp; | ||
179 | // iostreams should output pointer values in hex with an initial 0x by default. | ||
180 | temp << value; | ||
181 | setValue(key, temp.str()); | ||
182 | } | ||
183 | |||
184 | /** | ||
185 | * Gets the message class. | ||
186 | * | ||
187 | * @return Message class | ||
188 | */ | ||
189 | std::string LLPluginMessage::getClass(void) const | ||
190 | { | ||
191 | return mMessage["class"]; | ||
192 | } | ||
193 | |||
194 | /** | ||
195 | * Gets the message name. | ||
196 | * | ||
197 | * @return Message name | ||
198 | */ | ||
199 | std::string LLPluginMessage::getName(void) const | ||
200 | { | ||
201 | return mMessage["name"]; | ||
202 | } | ||
203 | |||
204 | /** | ||
205 | * Returns true if the specified key exists in this message (useful for optional parameters). | ||
206 | * | ||
207 | * @param[in] key Key | ||
208 | * | ||
209 | * @return True if key exists, false otherwise. | ||
210 | */ | ||
211 | bool LLPluginMessage::hasValue(const std::string &key) const | ||
212 | { | ||
213 | bool result = false; | ||
214 | |||
215 | if(mMessage["params"].has(key)) | ||
216 | { | ||
217 | result = true; | ||
218 | } | ||
219 | |||
220 | return result; | ||
221 | } | ||
222 | |||
223 | /** | ||
224 | * Gets the value of a key as a string. If the key does not exist, an empty string will be returned. | ||
225 | * | ||
226 | * @param[in] key Key | ||
227 | * | ||
228 | * @return String value of key if key exists, empty string if key does not exist. | ||
229 | */ | ||
230 | std::string LLPluginMessage::getValue(const std::string &key) const | ||
231 | { | ||
232 | std::string result; | ||
233 | |||
234 | if(mMessage["params"].has(key)) | ||
235 | { | ||
236 | result = mMessage["params"][key].asString(); | ||
237 | } | ||
238 | |||
239 | return result; | ||
240 | } | ||
241 | |||
242 | /** | ||
243 | * Gets the value of a key as LLSD. If the key does not exist, a null LLSD will be returned. | ||
244 | * | ||
245 | * @param[in] key Key | ||
246 | * | ||
247 | * @return LLSD value of key if key exists, null LLSD if key does not exist. | ||
248 | */ | ||
249 | LLSD LLPluginMessage::getValueLLSD(const std::string &key) const | ||
250 | { | ||
251 | LLSD result; | ||
252 | |||
253 | if(mMessage["params"].has(key)) | ||
254 | { | ||
255 | result = mMessage["params"][key]; | ||
256 | } | ||
257 | |||
258 | return result; | ||
259 | } | ||
260 | |||
261 | /** | ||
262 | * Gets the value of a key as signed 32-bit int. If the key does not exist, 0 will be returned. | ||
263 | * | ||
264 | * @param[in] key Key | ||
265 | * | ||
266 | * @return Signed 32-bit int value of key if key exists, 0 if key does not exist. | ||
267 | */ | ||
268 | S32 LLPluginMessage::getValueS32(const std::string &key) const | ||
269 | { | ||
270 | S32 result = 0; | ||
271 | |||
272 | if(mMessage["params"].has(key)) | ||
273 | { | ||
274 | result = mMessage["params"][key].asInteger(); | ||
275 | } | ||
276 | |||
277 | return result; | ||
278 | } | ||
279 | |||
280 | /** | ||
281 | * Gets the value of a key as unsigned 32-bit int. If the key does not exist, 0 will be returned. | ||
282 | * | ||
283 | * @param[in] key Key | ||
284 | * | ||
285 | * @return Unsigned 32-bit int value of key if key exists, 0 if key does not exist. | ||
286 | */ | ||
287 | U32 LLPluginMessage::getValueU32(const std::string &key) const | ||
288 | { | ||
289 | U32 result = 0; | ||
290 | |||
291 | if(mMessage["params"].has(key)) | ||
292 | { | ||
293 | std::string value = mMessage["params"][key].asString(); | ||
294 | |||
295 | result = (U32)strtoul(value.c_str(), NULL, 16); | ||
296 | } | ||
297 | |||
298 | return result; | ||
299 | } | ||
300 | |||
301 | /** | ||
302 | * Gets the value of a key as a bool. If the key does not exist, false will be returned. | ||
303 | * | ||
304 | * @param[in] key Key | ||
305 | * | ||
306 | * @return Boolean value of key if it exists, false otherwise. | ||
307 | */ | ||
308 | bool LLPluginMessage::getValueBoolean(const std::string &key) const | ||
309 | { | ||
310 | bool result = false; | ||
311 | |||
312 | if(mMessage["params"].has(key)) | ||
313 | { | ||
314 | result = mMessage["params"][key].asBoolean(); | ||
315 | } | ||
316 | |||
317 | return result; | ||
318 | } | ||
319 | |||
320 | /** | ||
321 | * Gets the value of a key as a double. If the key does not exist, 0 will be returned. | ||
322 | * | ||
323 | * @param[in] key Key | ||
324 | * | ||
325 | * @return Value as a double if key exists, 0 otherwise. | ||
326 | */ | ||
327 | F64 LLPluginMessage::getValueReal(const std::string &key) const | ||
328 | { | ||
329 | F64 result = 0.0f; | ||
330 | |||
331 | if(mMessage["params"].has(key)) | ||
332 | { | ||
333 | result = mMessage["params"][key].asReal(); | ||
334 | } | ||
335 | |||
336 | return result; | ||
337 | } | ||
338 | |||
339 | /** | ||
340 | * Gets the value of a key as a pointer. If the key does not exist, NULL will be returned. | ||
341 | * | ||
342 | * @param[in] key Key | ||
343 | * | ||
344 | * @return Pointer value if key exists, NULL otherwise. | ||
345 | */ | ||
346 | void* LLPluginMessage::getValuePointer(const std::string &key) const | ||
347 | { | ||
348 | void* result = NULL; | ||
349 | |||
350 | if(mMessage["params"].has(key)) | ||
351 | { | ||
352 | std::string value = mMessage["params"][key].asString(); | ||
353 | |||
354 | result = (void*)llstrtou64(value.c_str(), NULL, 16); | ||
355 | } | ||
356 | |||
357 | return result; | ||
358 | } | ||
359 | |||
360 | /** | ||
361 | * Flatten the message into a string. | ||
362 | * | ||
363 | * @return Message as a string. | ||
364 | */ | ||
365 | std::string LLPluginMessage::generate(void) const | ||
366 | { | ||
367 | std::ostringstream result; | ||
368 | |||
369 | // Pretty XML may be slightly easier to deal with while debugging... | ||
370 | // LLSDSerialize::toXML(mMessage, result); | ||
371 | LLSDSerialize::toPrettyXML(mMessage, result); | ||
372 | |||
373 | return result.str(); | ||
374 | } | ||
375 | |||
376 | /** | ||
377 | * Parse an incoming message into component parts. Clears all existing state before starting the parse. | ||
378 | * | ||
379 | * @return Returns -1 on failure, otherwise returns the number of key/value pairs in the incoming message. | ||
380 | */ | ||
381 | int LLPluginMessage::parse(const std::string &message) | ||
382 | { | ||
383 | // clear any previous state | ||
384 | clear(); | ||
385 | |||
386 | std::istringstream input(message); | ||
387 | |||
388 | S32 parse_result = LLSDSerialize::fromXML(mMessage, input); | ||
389 | |||
390 | return (int)parse_result; | ||
391 | } | ||
392 | |||
393 | |||
394 | /** | ||
395 | * Destructor | ||
396 | */ | ||
397 | LLPluginMessageListener::~LLPluginMessageListener() | ||
398 | { | ||
399 | // TODO: should listeners have a way to ensure they're removed from dispatcher lists when deleted? | ||
400 | } | ||
401 | |||
402 | |||
403 | /** | ||
404 | * Destructor | ||
405 | */ | ||
406 | LLPluginMessageDispatcher::~LLPluginMessageDispatcher() | ||
407 | { | ||
408 | |||
409 | } | ||
410 | |||
411 | /** | ||
412 | * Add a message listener. TODO:DOC need more info on what uses this. when are multiple listeners needed? | ||
413 | * | ||
414 | * @param[in] listener Message listener | ||
415 | */ | ||
416 | void LLPluginMessageDispatcher::addPluginMessageListener(LLPluginMessageListener *listener) | ||
417 | { | ||
418 | mListeners.insert(listener); | ||
419 | } | ||
420 | |||
421 | /** | ||
422 | * Remove a message listener. | ||
423 | * | ||
424 | * @param[in] listener Message listener | ||
425 | */ | ||
426 | void LLPluginMessageDispatcher::removePluginMessageListener(LLPluginMessageListener *listener) | ||
427 | { | ||
428 | mListeners.erase(listener); | ||
429 | } | ||
430 | |||
431 | /** | ||
432 | * Distribute a message to all message listeners. | ||
433 | * | ||
434 | * @param[in] message Message | ||
435 | */ | ||
436 | void LLPluginMessageDispatcher::dispatchPluginMessage(const LLPluginMessage &message) | ||
437 | { | ||
438 | for (listener_set_t::iterator it = mListeners.begin(); | ||
439 | it != mListeners.end(); | ||
440 | ) | ||
441 | { | ||
442 | LLPluginMessageListener* listener = *it; | ||
443 | listener->receivePluginMessage(message); | ||
444 | // In case something deleted an entry. | ||
445 | it = mListeners.upper_bound(listener); | ||
446 | } | ||
447 | } | ||
diff --git a/linden/indra/llplugin/llpluginmessage.h b/linden/indra/llplugin/llpluginmessage.h new file mode 100755 index 0000000..fe504c8 --- /dev/null +++ b/linden/indra/llplugin/llpluginmessage.h | |||
@@ -0,0 +1,144 @@ | |||
1 | /** | ||
2 | * @file llpluginmessage.h | ||
3 | * | ||
4 | * @cond | ||
5 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
6 | * | ||
7 | * Copyright (c) 2008-2010, Linden Research, Inc. | ||
8 | * | ||
9 | * Second Life Viewer Source Code | ||
10 | * The source code in this file ("Source Code") is provided by Linden Lab | ||
11 | * to you under the terms of the GNU General Public License, version 2.0 | ||
12 | * ("GPL"), unless you have obtained a separate licensing agreement | ||
13 | * ("Other License"), formally executed by you and Linden Lab. Terms of | ||
14 | * the GPL can be found in doc/GPL-license.txt in this distribution, or | ||
15 | * online at http://secondlife.com/developers/opensource/gplv2 | ||
16 | * | ||
17 | * There are special exceptions to the terms and conditions of the GPL as | ||
18 | * it is applied to this Source Code. View the full text of the exception | ||
19 | * in the file doc/FLOSS-exception.txt in this software distribution, or | ||
20 | * online at | ||
21 | * http://secondlife.com/developers/opensource/flossexception | ||
22 | * | ||
23 | * By copying, modifying or distributing this software, you acknowledge | ||
24 | * that you have read and understood your obligations described above, | ||
25 | * and agree to abide by those obligations. | ||
26 | * | ||
27 | * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | ||
28 | * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
29 | * COMPLETENESS OR PERFORMANCE. | ||
30 | * $/LicenseInfo$ | ||
31 | * | ||
32 | * @endcond | ||
33 | */ | ||
34 | |||
35 | #ifndef LL_LLPLUGINMESSAGE_H | ||
36 | #define LL_LLPLUGINMESSAGE_H | ||
37 | |||
38 | #include "llsd.h" | ||
39 | |||
40 | /** | ||
41 | * @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins. | ||
42 | */ | ||
43 | class LLPluginMessage | ||
44 | { | ||
45 | LOG_CLASS(LLPluginMessage); | ||
46 | public: | ||
47 | LLPluginMessage(); | ||
48 | LLPluginMessage(const LLPluginMessage &p); | ||
49 | LLPluginMessage(const std::string &message_class, const std::string &message_name); | ||
50 | ~LLPluginMessage(); | ||
51 | |||
52 | // reset all internal state | ||
53 | void clear(void); | ||
54 | |||
55 | // Sets the message class and name | ||
56 | // Also has the side-effect of clearing any key/value pairs in the message. | ||
57 | void setMessage(const std::string &message_class, const std::string &message_name); | ||
58 | |||
59 | // Sets a key/value pair in the message | ||
60 | void setValue(const std::string &key, const std::string &value); | ||
61 | void setValueLLSD(const std::string &key, const LLSD &value); | ||
62 | void setValueS32(const std::string &key, S32 value); | ||
63 | void setValueU32(const std::string &key, U32 value); | ||
64 | void setValueBoolean(const std::string &key, bool value); | ||
65 | void setValueReal(const std::string &key, F64 value); | ||
66 | void setValuePointer(const std::string &key, void *value); | ||
67 | |||
68 | std::string getClass(void) const; | ||
69 | std::string getName(void) const; | ||
70 | |||
71 | // Returns true if the specified key exists in this message (useful for optional parameters) | ||
72 | bool hasValue(const std::string &key) const; | ||
73 | |||
74 | // get the value of a particular key as a string. If the key doesn't exist in the message, an empty string will be returned. | ||
75 | std::string getValue(const std::string &key) const; | ||
76 | |||
77 | // get the value of a particular key as LLSD. If the key doesn't exist in the message, a null LLSD will be returned. | ||
78 | LLSD getValueLLSD(const std::string &key) const; | ||
79 | |||
80 | // get the value of a key as a S32. If the value wasn't set as a S32, behavior is undefined. | ||
81 | S32 getValueS32(const std::string &key) const; | ||
82 | |||
83 | // get the value of a key as a U32. Since there isn't an LLSD type for this, we use a hexadecimal string instead. | ||
84 | U32 getValueU32(const std::string &key) const; | ||
85 | |||
86 | // get the value of a key as a Boolean. | ||
87 | bool getValueBoolean(const std::string &key) const; | ||
88 | |||
89 | // get the value of a key as a float. | ||
90 | F64 getValueReal(const std::string &key) const; | ||
91 | |||
92 | // get the value of a key as a pointer. | ||
93 | void* getValuePointer(const std::string &key) const; | ||
94 | |||
95 | // Flatten the message into a string | ||
96 | std::string generate(void) const; | ||
97 | |||
98 | // Parse an incoming message into component parts | ||
99 | // (this clears out all existing state before starting the parse) | ||
100 | // Returns -1 on failure, otherwise returns the number of key/value pairs in the message. | ||
101 | int parse(const std::string &message); | ||
102 | |||
103 | |||
104 | private: | ||
105 | |||
106 | LLSD mMessage; | ||
107 | |||
108 | }; | ||
109 | |||
110 | /** | ||
111 | * @brief Listener for plugin messages. | ||
112 | */ | ||
113 | class LLPluginMessageListener | ||
114 | { | ||
115 | public: | ||
116 | virtual ~LLPluginMessageListener(); | ||
117 | /** Plugin receives message from plugin loader shell. */ | ||
118 | virtual void receivePluginMessage(const LLPluginMessage &message) = 0; | ||
119 | |||
120 | }; | ||
121 | |||
122 | /** | ||
123 | * @brief Dispatcher for plugin messages. | ||
124 | * | ||
125 | * Manages the set of plugin message listeners and distributes messages to plugin message listeners. | ||
126 | */ | ||
127 | class LLPluginMessageDispatcher | ||
128 | { | ||
129 | public: | ||
130 | virtual ~LLPluginMessageDispatcher(); | ||
131 | |||
132 | void addPluginMessageListener(LLPluginMessageListener *); | ||
133 | void removePluginMessageListener(LLPluginMessageListener *); | ||
134 | protected: | ||
135 | void dispatchPluginMessage(const LLPluginMessage &message); | ||
136 | |||
137 | /** A set of message listeners. */ | ||
138 | typedef std::set<LLPluginMessageListener*> listener_set_t; | ||
139 | /** The set of message listeners. */ | ||
140 | listener_set_t mListeners; | ||
141 | }; | ||
142 | |||
143 | |||
144 | #endif // LL_LLPLUGINMESSAGE_H | ||
diff --git a/linden/indra/llplugin/llpluginmessageclasses.h b/linden/indra/llplugin/llpluginmessageclasses.h new file mode 100755 index 0000000..8812a16 --- /dev/null +++ b/linden/indra/llplugin/llpluginmessageclasses.h | |||
@@ -0,0 +1,60 @@ | |||
1 | /** | ||
2 | * @file llpluginmessageclasses.h | ||
3 | * @brief This file defines the versions of existing message classes for LLPluginMessage. | ||
4 | * | ||
5 | * @cond | ||
6 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
7 | * | ||
8 | * Copyright (c) 2008-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_LLPLUGINMESSAGECLASSES_H | ||
37 | #define LL_LLPLUGINMESSAGECLASSES_H | ||
38 | |||
39 | // Version strings for each plugin message class. | ||
40 | // Backwards-compatible changes (i.e. changes which only add new messges) should increment the minor version (i.e. "1.0" -> "1.1"). | ||
41 | // Non-backwards-compatible changes (which delete messages or change their semantics) should increment the major version (i.e. "1.1" -> "2.0"). | ||
42 | // Plugins will supply the set of message classes they understand, with version numbers, as part of their init_response message. | ||
43 | // The contents and semantics of the base:init message must NEVER change in a non-backwards-compatible way, as a special case. | ||
44 | |||
45 | #define LLPLUGIN_MESSAGE_CLASS_INTERNAL "internal" | ||
46 | #define LLPLUGIN_MESSAGE_CLASS_INTERNAL_VERSION "1.0" | ||
47 | |||
48 | #define LLPLUGIN_MESSAGE_CLASS_BASE "base" | ||
49 | #define LLPLUGIN_MESSAGE_CLASS_BASE_VERSION "1.0" | ||
50 | |||
51 | #define LLPLUGIN_MESSAGE_CLASS_MEDIA "media" | ||
52 | #define LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION "1.0" | ||
53 | |||
54 | #define LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER "media_browser" | ||
55 | #define LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER_VERSION "1.0" | ||
56 | |||
57 | #define LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME "media_time" | ||
58 | #define LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME_VERSION "1.0" | ||
59 | |||
60 | #endif // LL_LLPLUGINMESSAGECLASSES_H | ||
diff --git a/linden/indra/llplugin/llpluginmessagepipe.cpp b/linden/indra/llplugin/llpluginmessagepipe.cpp new file mode 100755 index 0000000..2cad188 --- /dev/null +++ b/linden/indra/llplugin/llpluginmessagepipe.cpp | |||
@@ -0,0 +1,387 @@ | |||
1 | /** | ||
2 | * @file llpluginmessagepipe.cpp | ||
3 | * @brief Classes that implement connections from the plugin system to pipes/pumps. | ||
4 | * | ||
5 | * @cond | ||
6 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
7 | * | ||
8 | * Copyright (c) 2008-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 | /// IMPRUDENCE: this is part of the viewer and the SLPlugin | ||
37 | |||
38 | #include "linden_common.h" | ||
39 | |||
40 | #include "llpluginmessagepipe.h" | ||
41 | #include "llbufferstream.h" | ||
42 | |||
43 | #include "llapr.h" | ||
44 | |||
45 | static const char MESSAGE_DELIMITER = '\0'; | ||
46 | |||
47 | LLPluginMessagePipeOwner::LLPluginMessagePipeOwner() : | ||
48 | mMessagePipe(NULL), | ||
49 | mSocketError(APR_SUCCESS) | ||
50 | { | ||
51 | } | ||
52 | |||
53 | // virtual | ||
54 | LLPluginMessagePipeOwner::~LLPluginMessagePipeOwner() | ||
55 | { | ||
56 | killMessagePipe(); | ||
57 | } | ||
58 | |||
59 | // virtual | ||
60 | apr_status_t LLPluginMessagePipeOwner::socketError(apr_status_t error) | ||
61 | { | ||
62 | mSocketError = error; | ||
63 | return error; | ||
64 | }; | ||
65 | |||
66 | //virtual | ||
67 | void LLPluginMessagePipeOwner::setMessagePipe(LLPluginMessagePipe *read_pipe) | ||
68 | { | ||
69 | // Save a reference to this pipe | ||
70 | mMessagePipe = read_pipe; | ||
71 | } | ||
72 | |||
73 | bool LLPluginMessagePipeOwner::canSendMessage(void) | ||
74 | { | ||
75 | return (mMessagePipe != NULL); | ||
76 | } | ||
77 | |||
78 | bool LLPluginMessagePipeOwner::writeMessageRaw(const std::string &message) | ||
79 | { | ||
80 | bool result = true; | ||
81 | if(mMessagePipe != NULL) | ||
82 | { | ||
83 | result = mMessagePipe->addMessage(message); | ||
84 | } | ||
85 | else | ||
86 | { | ||
87 | LL_WARNS("PluginPipe") << "dropping message: " << message << LL_ENDL; | ||
88 | result = false; | ||
89 | } | ||
90 | |||
91 | return result; | ||
92 | } | ||
93 | |||
94 | void LLPluginMessagePipeOwner::killMessagePipe(void) | ||
95 | { | ||
96 | if(mMessagePipe != NULL) | ||
97 | { | ||
98 | delete mMessagePipe; | ||
99 | mMessagePipe = NULL; | ||
100 | } | ||
101 | } | ||
102 | |||
103 | LLPluginMessagePipe::LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket): | ||
104 | mOwner(owner), | ||
105 | mSocket(socket) | ||
106 | { | ||
107 | |||
108 | mOwner->setMessagePipe(this); | ||
109 | } | ||
110 | |||
111 | LLPluginMessagePipe::~LLPluginMessagePipe() | ||
112 | { | ||
113 | if(mOwner != NULL) | ||
114 | { | ||
115 | mOwner->setMessagePipe(NULL); | ||
116 | } | ||
117 | } | ||
118 | |||
119 | bool LLPluginMessagePipe::addMessage(const std::string &message) | ||
120 | { | ||
121 | // queue the message for later output | ||
122 | LLMutexLock lock(&mOutputMutex); | ||
123 | mOutput += message; | ||
124 | mOutput += MESSAGE_DELIMITER; // message separator | ||
125 | |||
126 | return true; | ||
127 | } | ||
128 | |||
129 | void LLPluginMessagePipe::clearOwner(void) | ||
130 | { | ||
131 | // The owner is done with this pipe. The next call to process_impl should send any remaining data and exit. | ||
132 | mOwner = NULL; | ||
133 | } | ||
134 | |||
135 | void LLPluginMessagePipe::setSocketTimeout(apr_interval_time_t timeout_usec) | ||
136 | { | ||
137 | // We never want to sleep forever, so force negative timeouts to become non-blocking. | ||
138 | |||
139 | // according to this page: http://dev.ariel-networks.com/apr/apr-tutorial/html/apr-tutorial-13.html | ||
140 | // blocking/non-blocking with apr sockets is somewhat non-portable. | ||
141 | |||
142 | if(timeout_usec <= 0) | ||
143 | { | ||
144 | // Make the socket non-blocking | ||
145 | apr_socket_opt_set(mSocket->getSocket(), APR_SO_NONBLOCK, 1); | ||
146 | apr_socket_timeout_set(mSocket->getSocket(), 0); | ||
147 | } | ||
148 | else | ||
149 | { | ||
150 | // Make the socket blocking-with-timeout | ||
151 | apr_socket_opt_set(mSocket->getSocket(), APR_SO_NONBLOCK, 1); | ||
152 | apr_socket_timeout_set(mSocket->getSocket(), timeout_usec); | ||
153 | } | ||
154 | } | ||
155 | |||
156 | bool LLPluginMessagePipe::pump(F64 timeout) | ||
157 | { | ||
158 | bool result = pumpOutput(); | ||
159 | |||
160 | if(result) | ||
161 | { | ||
162 | result = pumpInput(timeout); | ||
163 | } | ||
164 | |||
165 | return result; | ||
166 | } | ||
167 | |||
168 | bool LLPluginMessagePipe::pumpOutput() | ||
169 | { | ||
170 | bool result = true; | ||
171 | |||
172 | if(mSocket) | ||
173 | { | ||
174 | apr_status_t status; | ||
175 | apr_size_t size; | ||
176 | |||
177 | LLMutexLock lock(&mOutputMutex); | ||
178 | if(!mOutput.empty()) | ||
179 | { | ||
180 | // write any outgoing messages | ||
181 | size = (apr_size_t)mOutput.size(); | ||
182 | |||
183 | setSocketTimeout(0); | ||
184 | |||
185 | // LL_INFOS("PluginPipe") << "before apr_socket_send, size = " << size << LL_ENDL; | ||
186 | |||
187 | status = apr_socket_send( | ||
188 | mSocket->getSocket(), | ||
189 | (const char*)mOutput.data(), | ||
190 | &size); | ||
191 | |||
192 | // LL_INFOS("PluginPipe") << "after apr_socket_send, size = " << size << LL_ENDL; | ||
193 | |||
194 | if(status == APR_SUCCESS) | ||
195 | { | ||
196 | // success | ||
197 | mOutput = mOutput.substr(size); | ||
198 | } | ||
199 | else if(APR_STATUS_IS_EAGAIN(status)) | ||
200 | { | ||
201 | // Socket buffer is full... | ||
202 | // remove the written part from the buffer and try again later. | ||
203 | mOutput = mOutput.substr(size); | ||
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 | } | ||
216 | else | ||
217 | { | ||
218 | // some other error | ||
219 | // Treat this as fatal. | ||
220 | ll_apr_warn_status(status); | ||
221 | |||
222 | if(mOwner) | ||
223 | { | ||
224 | mOwner->socketError(status); | ||
225 | } | ||
226 | result = false; | ||
227 | } | ||
228 | } | ||
229 | } | ||
230 | |||
231 | return result; | ||
232 | } | ||
233 | |||
234 | bool LLPluginMessagePipe::pumpInput(F64 timeout) | ||
235 | { | ||
236 | bool result = true; | ||
237 | |||
238 | if(mSocket) | ||
239 | { | ||
240 | apr_status_t status; | ||
241 | apr_size_t size; | ||
242 | |||
243 | // FIXME: For some reason, the apr timeout stuff isn't working properly on windows. | ||
244 | // Until such time as we figure out why, don't try to use the socket timeout -- just sleep here instead. | ||
245 | #if LL_WINDOWS | ||
246 | if(result) | ||
247 | { | ||
248 | if(timeout != 0.0f) | ||
249 | { | ||
250 | ms_sleep((int)(timeout * 1000.0f)); | ||
251 | timeout = 0.0f; | ||
252 | } | ||
253 | } | ||
254 | #endif | ||
255 | |||
256 | // Check for incoming messages | ||
257 | if(result) | ||
258 | { | ||
259 | char input_buf[1024]; | ||
260 | apr_size_t request_size; | ||
261 | |||
262 | if(timeout == 0.0f) | ||
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 | } | ||
272 | |||
273 | // and use the timeout so we'll sleep if no data is available. | ||
274 | setSocketTimeout((apr_interval_time_t)(timeout * 1000000)); | ||
275 | |||
276 | while(1) | ||
277 | { | ||
278 | size = request_size; | ||
279 | |||
280 | // LL_INFOS("PluginPipe") << "before apr_socket_recv, size = " << size << LL_ENDL; | ||
281 | |||
282 | status = apr_socket_recv( | ||
283 | mSocket->getSocket(), | ||
284 | input_buf, | ||
285 | &size); | ||
286 | |||
287 | // LL_INFOS("PluginPipe") << "after apr_socket_recv, size = " << size << LL_ENDL; | ||
288 | |||
289 | if(size > 0) | ||
290 | { | ||
291 | LLMutexLock lock(&mInputMutex); | ||
292 | mInput.append(input_buf, size); | ||
293 | } | ||
294 | |||
295 | if(status == APR_SUCCESS) | ||
296 | { | ||
297 | LL_DEBUGS("PluginSocket") << "success, read " << size << LL_ENDL; | ||
298 | |||
299 | if(size != request_size) | ||
300 | { | ||
301 | // This was a short read, so we're done. | ||
302 | break; | ||
303 | } | ||
304 | } | ||
305 | else if(APR_STATUS_IS_TIMEUP(status)) | ||
306 | { | ||
307 | LL_DEBUGS("PluginSocket") << "TIMEUP, read " << size << LL_ENDL; | ||
308 | |||
309 | // Timeout was hit. Since the initial read is 1 byte, this should never be a partial read. | ||
310 | break; | ||
311 | } | ||
312 | else if(APR_STATUS_IS_EAGAIN(status)) | ||
313 | { | ||
314 | LL_DEBUGS("PluginSocket") << "EAGAIN, read " << size << LL_ENDL; | ||
315 | |||
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; | ||
329 | break; | ||
330 | } | ||
331 | else | ||
332 | { | ||
333 | // some other error | ||
334 | // Treat this as fatal. | ||
335 | ll_apr_warn_status(status); | ||
336 | |||
337 | if(mOwner) | ||
338 | { | ||
339 | mOwner->socketError(status); | ||
340 | } | ||
341 | result = false; | ||
342 | break; | ||
343 | } | ||
344 | |||
345 | if(timeout != 0.0f) | ||
346 | { | ||
347 | // Second and subsequent reads should not use the timeout | ||
348 | setSocketTimeout(0); | ||
349 | // and should try to fill the input buffer | ||
350 | request_size = sizeof(input_buf); | ||
351 | } | ||
352 | } | ||
353 | |||
354 | processInput(); | ||
355 | } | ||
356 | } | ||
357 | |||
358 | return result; | ||
359 | } | ||
360 | |||
361 | void LLPluginMessagePipe::processInput(void) | ||
362 | { | ||
363 | // Look for input delimiter(s) in the input buffer. | ||
364 | int delim; | ||
365 | mInputMutex.lock(); | ||
366 | while((delim = mInput.find(MESSAGE_DELIMITER)) != std::string::npos) | ||
367 | { | ||
368 | // Let the owner process this message | ||
369 | if (mOwner) | ||
370 | { | ||
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(); | ||
379 | } | ||
380 | else | ||
381 | { | ||
382 | LL_WARNS("PluginPipe") << "!mOwner" << LL_ENDL; | ||
383 | } | ||
384 | } | ||
385 | mInputMutex.unlock(); | ||
386 | } | ||
387 | |||
diff --git a/linden/indra/llplugin/llpluginmessagepipe.h b/linden/indra/llplugin/llpluginmessagepipe.h new file mode 100755 index 0000000..6eedca2 --- /dev/null +++ b/linden/indra/llplugin/llpluginmessagepipe.h | |||
@@ -0,0 +1,100 @@ | |||
1 | /** | ||
2 | * @file llpluginmessagepipe.h | ||
3 | * @brief Classes that implement connections from the plugin system to pipes/pumps. | ||
4 | * | ||
5 | * @cond | ||
6 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
7 | * | ||
8 | * Copyright (c) 2008-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_LLPLUGINMESSAGEPIPE_H | ||
37 | #define LL_LLPLUGINMESSAGEPIPE_H | ||
38 | |||
39 | #include "lliosocket.h" | ||
40 | #include "llthread.h" | ||
41 | |||
42 | class LLPluginMessagePipe; | ||
43 | |||
44 | // Inherit from this to be able to receive messages from the LLPluginMessagePipe | ||
45 | class LLPluginMessagePipeOwner | ||
46 | { | ||
47 | LOG_CLASS(LLPluginMessagePipeOwner); | ||
48 | public: | ||
49 | LLPluginMessagePipeOwner(); | ||
50 | virtual ~LLPluginMessagePipeOwner(); | ||
51 | // called with incoming messages | ||
52 | virtual void receiveMessageRaw(const std::string &message) = 0; | ||
53 | // called when the socket has an error | ||
54 | virtual apr_status_t socketError(apr_status_t error); | ||
55 | |||
56 | // called from LLPluginMessagePipe to manage the connection with LLPluginMessagePipeOwner -- do not use! | ||
57 | virtual void setMessagePipe(LLPluginMessagePipe *message_pipe); | ||
58 | |||
59 | protected: | ||
60 | // returns false if writeMessageRaw() would drop the message | ||
61 | bool canSendMessage(void); | ||
62 | // call this to send a message over the pipe | ||
63 | bool writeMessageRaw(const std::string &message); | ||
64 | // call this to close the pipe | ||
65 | void killMessagePipe(void); | ||
66 | |||
67 | LLPluginMessagePipe *mMessagePipe; | ||
68 | apr_status_t mSocketError; | ||
69 | }; | ||
70 | |||
71 | class LLPluginMessagePipe | ||
72 | { | ||
73 | LOG_CLASS(LLPluginMessagePipe); | ||
74 | public: | ||
75 | LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket); | ||
76 | virtual ~LLPluginMessagePipe(); | ||
77 | |||
78 | bool addMessage(const std::string &message); | ||
79 | void clearOwner(void); | ||
80 | |||
81 | bool pump(F64 timeout = 0.0f); | ||
82 | bool pumpOutput(); | ||
83 | bool pumpInput(F64 timeout = 0.0f); | ||
84 | |||
85 | protected: | ||
86 | void processInput(void); | ||
87 | |||
88 | // used internally by pump() | ||
89 | void setSocketTimeout(apr_interval_time_t timeout_usec); | ||
90 | |||
91 | LLMutex mInputMutex; | ||
92 | std::string mInput; | ||
93 | LLMutex mOutputMutex; | ||
94 | std::string mOutput; | ||
95 | |||
96 | LLPluginMessagePipeOwner *mOwner; | ||
97 | LLSocket::ptr_t mSocket; | ||
98 | }; | ||
99 | |||
100 | #endif // LL_LLPLUGINMESSAGE_H | ||
diff --git a/linden/indra/llplugin/llpluginprocesschild.cpp b/linden/indra/llplugin/llpluginprocesschild.cpp new file mode 100755 index 0000000..0d95cac --- /dev/null +++ b/linden/indra/llplugin/llpluginprocesschild.cpp | |||
@@ -0,0 +1,571 @@ | |||
1 | /** | ||
2 | * @file llpluginprocesschild.cpp | ||
3 | * @brief LLPluginProcessChild handles the child side of the external-process plugin API. | ||
4 | * | ||
5 | * @cond | ||
6 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
7 | * | ||
8 | * Copyright (c) 2008-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 | /// IMPRUDENCE: this is part of the SLPlugin | ||
37 | |||
38 | #include "linden_common.h" | ||
39 | |||
40 | #include "llpluginprocesschild.h" | ||
41 | #include "llplugininstance.h" | ||
42 | #include "llpluginmessagepipe.h" | ||
43 | #include "llpluginmessageclasses.h" | ||
44 | |||
45 | static const F32 HEARTBEAT_SECONDS = 1.0f; | ||
46 | static const F32 PLUGIN_IDLE_SECONDS = 1.0f / 100.0f; // Each call to idle will give the plugin this much time. | ||
47 | |||
48 | LLPluginProcessChild::LLPluginProcessChild() | ||
49 | { | ||
50 | mState = STATE_UNINITIALIZED; | ||
51 | mInstance = NULL; | ||
52 | mSocket = LLSocket::create(LLSocket::STREAM_TCP); | ||
53 | mSleepTime = PLUGIN_IDLE_SECONDS; // default: send idle messages at 100Hz | ||
54 | mCPUElapsed = 0.0f; | ||
55 | mBlockingRequest = false; | ||
56 | mBlockingResponseReceived = false; | ||
57 | } | ||
58 | |||
59 | LLPluginProcessChild::~LLPluginProcessChild() | ||
60 | { | ||
61 | if(mInstance != NULL) | ||
62 | { | ||
63 | sendMessageToPlugin(LLPluginMessage("base", "cleanup")); | ||
64 | |||
65 | // IMPORTANT: under some (unknown) circumstances the apr_dso_unload() triggered when mInstance is deleted | ||
66 | // appears to fail and lock up which means that a given instance of the slplugin process never exits. | ||
67 | // This is bad, especially when users try to update their version of SL - it fails because the slplugin | ||
68 | // process as well as a bunch of plugin specific files are locked and cannot be overwritten. | ||
69 | exit( 0 ); | ||
70 | //delete mInstance; | ||
71 | //mInstance = NULL; | ||
72 | } | ||
73 | } | ||
74 | |||
75 | void LLPluginProcessChild::killSockets(void) | ||
76 | { | ||
77 | killMessagePipe(); | ||
78 | mSocket.reset(); | ||
79 | } | ||
80 | |||
81 | void LLPluginProcessChild::init(U32 launcher_port) | ||
82 | { | ||
83 | mLauncherHost = LLHost("127.0.0.1", launcher_port); | ||
84 | setState(STATE_INITIALIZED); | ||
85 | } | ||
86 | |||
87 | void LLPluginProcessChild::idle(void) | ||
88 | { | ||
89 | bool idle_again; | ||
90 | do | ||
91 | { | ||
92 | if(APR_STATUS_IS_EOF(mSocketError)) | ||
93 | { | ||
94 | // Plugin socket was closed. This covers both normal plugin termination and host crashes. | ||
95 | setState(STATE_ERROR); | ||
96 | } | ||
97 | else if(mSocketError != APR_SUCCESS) | ||
98 | { | ||
99 | LL_INFOS("PluginChild") << "message pipe is in error state (" << mSocketError << "), moving to STATE_ERROR"<< LL_ENDL; | ||
100 | setState(STATE_ERROR); | ||
101 | } | ||
102 | |||
103 | if((mState > STATE_INITIALIZED) && (mMessagePipe == NULL)) | ||
104 | { | ||
105 | // The pipe has been closed -- we're done. | ||
106 | // TODO: This could be slightly more subtle, but I'm not sure it needs to be. | ||
107 | LL_INFOS("PluginChild") << "message pipe went away, moving to STATE_ERROR"<< LL_ENDL; | ||
108 | setState(STATE_ERROR); | ||
109 | } | ||
110 | |||
111 | // If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState(). | ||
112 | // USE THIS CAREFULLY, since it can starve other code. Specifically make sure there's no way to get into a closed cycle and never return. | ||
113 | // When in doubt, don't do it. | ||
114 | idle_again = false; | ||
115 | |||
116 | if(mInstance != NULL) | ||
117 | { | ||
118 | // Provide some time to the plugin | ||
119 | mInstance->idle(); | ||
120 | } | ||
121 | |||
122 | switch(mState) | ||
123 | { | ||
124 | case STATE_UNINITIALIZED: | ||
125 | break; | ||
126 | |||
127 | case STATE_INITIALIZED: | ||
128 | if(mSocket->blockingConnect(mLauncherHost)) | ||
129 | { | ||
130 | // This automatically sets mMessagePipe | ||
131 | new LLPluginMessagePipe(this, mSocket); | ||
132 | |||
133 | setState(STATE_CONNECTED); | ||
134 | } | ||
135 | else | ||
136 | { | ||
137 | // connect failed | ||
138 | setState(STATE_ERROR); | ||
139 | } | ||
140 | break; | ||
141 | |||
142 | case STATE_CONNECTED: | ||
143 | sendMessageToParent(LLPluginMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "hello")); | ||
144 | setState(STATE_PLUGIN_LOADING); | ||
145 | break; | ||
146 | |||
147 | case STATE_PLUGIN_LOADING: | ||
148 | if(!mPluginFile.empty()) | ||
149 | { | ||
150 | mInstance = new LLPluginInstance(this); | ||
151 | if(mInstance->load(mPluginFile) == 0) | ||
152 | { | ||
153 | mHeartbeat.start(); | ||
154 | mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS); | ||
155 | mCPUElapsed = 0.0f; | ||
156 | setState(STATE_PLUGIN_LOADED); | ||
157 | } | ||
158 | else | ||
159 | { | ||
160 | setState(STATE_ERROR); | ||
161 | } | ||
162 | } | ||
163 | break; | ||
164 | |||
165 | case STATE_PLUGIN_LOADED: | ||
166 | { | ||
167 | setState(STATE_PLUGIN_INITIALIZING); | ||
168 | LLPluginMessage message("base", "init"); | ||
169 | sendMessageToPlugin(message); | ||
170 | } | ||
171 | break; | ||
172 | |||
173 | case STATE_PLUGIN_INITIALIZING: | ||
174 | // waiting for init_response... | ||
175 | break; | ||
176 | |||
177 | case STATE_RUNNING: | ||
178 | if(mInstance != NULL) | ||
179 | { | ||
180 | // Provide some time to the plugin | ||
181 | LLPluginMessage message("base", "idle"); | ||
182 | message.setValueReal("time", PLUGIN_IDLE_SECONDS); | ||
183 | sendMessageToPlugin(message); | ||
184 | |||
185 | mInstance->idle(); | ||
186 | |||
187 | if(mHeartbeat.hasExpired()) | ||
188 | { | ||
189 | |||
190 | // This just proves that we're not stuck down inside the plugin code. | ||
191 | LLPluginMessage heartbeat(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "heartbeat"); | ||
192 | |||
193 | // Calculate the approximage CPU usage fraction (floating point value between 0 and 1) used by the plugin this heartbeat cycle. | ||
194 | // Note that this will not take into account any threads or additional processes the plugin spawns, but it's a first approximation. | ||
195 | // If we could write OS-specific functions to query the actual CPU usage of this process, that would be a better approximation. | ||
196 | heartbeat.setValueReal("cpu_usage", mCPUElapsed / mHeartbeat.getElapsedTimeF64()); | ||
197 | |||
198 | sendMessageToParent(heartbeat); | ||
199 | |||
200 | mHeartbeat.reset(); | ||
201 | mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS); | ||
202 | mCPUElapsed = 0.0f; | ||
203 | } | ||
204 | } | ||
205 | // receivePluginMessage will transition to STATE_UNLOADING | ||
206 | break; | ||
207 | |||
208 | case STATE_UNLOADING: | ||
209 | if(mInstance != NULL) | ||
210 | { | ||
211 | sendMessageToPlugin(LLPluginMessage("base", "cleanup")); | ||
212 | delete mInstance; | ||
213 | mInstance = NULL; | ||
214 | } | ||
215 | setState(STATE_UNLOADED); | ||
216 | break; | ||
217 | |||
218 | case STATE_UNLOADED: | ||
219 | killSockets(); | ||
220 | setState(STATE_DONE); | ||
221 | break; | ||
222 | |||
223 | case STATE_ERROR: | ||
224 | // Close the socket to the launcher | ||
225 | killSockets(); | ||
226 | // TODO: Where do we go from here? Just exit()? | ||
227 | setState(STATE_DONE); | ||
228 | break; | ||
229 | |||
230 | case STATE_DONE: | ||
231 | // just sit here. | ||
232 | break; | ||
233 | } | ||
234 | |||
235 | } while (idle_again); | ||
236 | } | ||
237 | |||
238 | void LLPluginProcessChild::sleep(F64 seconds) | ||
239 | { | ||
240 | deliverQueuedMessages(); | ||
241 | if(mMessagePipe) | ||
242 | { | ||
243 | mMessagePipe->pump(seconds); | ||
244 | } | ||
245 | else | ||
246 | { | ||
247 | ms_sleep((int)(seconds * 1000.0f)); | ||
248 | } | ||
249 | } | ||
250 | |||
251 | void LLPluginProcessChild::pump(void) | ||
252 | { | ||
253 | deliverQueuedMessages(); | ||
254 | if(mMessagePipe) | ||
255 | { | ||
256 | mMessagePipe->pump(0.0f); | ||
257 | } | ||
258 | else | ||
259 | { | ||
260 | // Should we warn here? | ||
261 | } | ||
262 | } | ||
263 | |||
264 | |||
265 | bool LLPluginProcessChild::isRunning(void) | ||
266 | { | ||
267 | bool result = false; | ||
268 | |||
269 | if(mState == STATE_RUNNING) | ||
270 | result = true; | ||
271 | |||
272 | return result; | ||
273 | } | ||
274 | |||
275 | bool LLPluginProcessChild::isDone(void) | ||
276 | { | ||
277 | bool result = false; | ||
278 | |||
279 | switch(mState) | ||
280 | { | ||
281 | case STATE_DONE: | ||
282 | result = true; | ||
283 | break; | ||
284 | default: | ||
285 | break; | ||
286 | } | ||
287 | |||
288 | return result; | ||
289 | } | ||
290 | |||
291 | void LLPluginProcessChild::sendMessageToPlugin(const LLPluginMessage &message) | ||
292 | { | ||
293 | if (mInstance) | ||
294 | { | ||
295 | std::string buffer = message.generate(); | ||
296 | |||
297 | LL_DEBUGS("PluginChild") << "Sending to plugin: " << buffer << LL_ENDL; | ||
298 | LLTimer elapsed; | ||
299 | |||
300 | mInstance->sendMessage(buffer); | ||
301 | |||
302 | mCPUElapsed += elapsed.getElapsedTimeF64(); | ||
303 | } | ||
304 | else | ||
305 | { | ||
306 | LL_WARNS("PluginChild") << "mInstance == NULL" << LL_ENDL; | ||
307 | } | ||
308 | } | ||
309 | |||
310 | void LLPluginProcessChild::sendMessageToParent(const LLPluginMessage &message) | ||
311 | { | ||
312 | std::string buffer = message.generate(); | ||
313 | |||
314 | LL_DEBUGS("PluginChild") << "Sending to parent: " << buffer << LL_ENDL; | ||
315 | |||
316 | writeMessageRaw(buffer); | ||
317 | } | ||
318 | |||
319 | void LLPluginProcessChild::receiveMessageRaw(const std::string &message) | ||
320 | { | ||
321 | // Incoming message from the TCP Socket | ||
322 | |||
323 | LL_DEBUGS("PluginChild") << "Received from parent: " << message << LL_ENDL; | ||
324 | |||
325 | // Decode this message | ||
326 | LLPluginMessage parsed; | ||
327 | parsed.parse(message); | ||
328 | |||
329 | if(mBlockingRequest) | ||
330 | { | ||
331 | // We're blocking the plugin waiting for a response. | ||
332 | |||
333 | if(parsed.hasValue("blocking_response")) | ||
334 | { | ||
335 | // This is the message we've been waiting for -- fall through and send it immediately. | ||
336 | mBlockingResponseReceived = true; | ||
337 | } | ||
338 | else | ||
339 | { | ||
340 | // Still waiting. Queue this message and don't process it yet. | ||
341 | mMessageQueue.push(message); | ||
342 | return; | ||
343 | } | ||
344 | } | ||
345 | |||
346 | bool passMessage = true; | ||
347 | |||
348 | // FIXME: how should we handle queueing here? | ||
349 | |||
350 | { | ||
351 | std::string message_class = parsed.getClass(); | ||
352 | if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL) | ||
353 | { | ||
354 | passMessage = false; | ||
355 | |||
356 | std::string message_name = parsed.getName(); | ||
357 | if(message_name == "load_plugin") | ||
358 | { | ||
359 | mPluginFile = parsed.getValue("file"); | ||
360 | } | ||
361 | else if(message_name == "shm_add") | ||
362 | { | ||
363 | std::string name = parsed.getValue("name"); | ||
364 | size_t size = (size_t)parsed.getValueS32("size"); | ||
365 | |||
366 | sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); | ||
367 | if(iter != mSharedMemoryRegions.end()) | ||
368 | { | ||
369 | // Need to remove the old region first | ||
370 | LL_WARNS("PluginChild") << "Adding a duplicate shared memory segment!" << LL_ENDL; | ||
371 | } | ||
372 | else | ||
373 | { | ||
374 | // This is a new region | ||
375 | LLPluginSharedMemory *region = new LLPluginSharedMemory; | ||
376 | if(region->attach(name, size)) | ||
377 | { | ||
378 | mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region)); | ||
379 | |||
380 | std::stringstream addr; | ||
381 | addr << region->getMappedAddress(); | ||
382 | |||
383 | // Send the add notification to the plugin | ||
384 | LLPluginMessage message("base", "shm_added"); | ||
385 | message.setValue("name", name); | ||
386 | message.setValueS32("size", (S32)size); | ||
387 | message.setValuePointer("address", region->getMappedAddress()); | ||
388 | sendMessageToPlugin(message); | ||
389 | |||
390 | // and send the response to the parent | ||
391 | message.setMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add_response"); | ||
392 | message.setValue("name", name); | ||
393 | sendMessageToParent(message); | ||
394 | } | ||
395 | else | ||
396 | { | ||
397 | LL_WARNS("PluginChild") << "Couldn't create a shared memory segment!" << LL_ENDL; | ||
398 | delete region; | ||
399 | } | ||
400 | } | ||
401 | |||
402 | } | ||
403 | else if(message_name == "shm_remove") | ||
404 | { | ||
405 | std::string name = parsed.getValue("name"); | ||
406 | sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); | ||
407 | if(iter != mSharedMemoryRegions.end()) | ||
408 | { | ||
409 | // forward the remove request to the plugin -- its response will trigger us to detach the segment. | ||
410 | LLPluginMessage message("base", "shm_remove"); | ||
411 | message.setValue("name", name); | ||
412 | sendMessageToPlugin(message); | ||
413 | } | ||
414 | else | ||
415 | { | ||
416 | LL_WARNS("PluginChild") << "shm_remove for unknown memory segment!" << LL_ENDL; | ||
417 | } | ||
418 | } | ||
419 | else if(message_name == "sleep_time") | ||
420 | { | ||
421 | mSleepTime = parsed.getValueReal("time"); | ||
422 | } | ||
423 | else if(message_name == "crash") | ||
424 | { | ||
425 | // Crash the plugin | ||
426 | LL_ERRS("PluginChild") << "Plugin crash requested." << LL_ENDL; | ||
427 | } | ||
428 | else if(message_name == "hang") | ||
429 | { | ||
430 | // Hang the plugin | ||
431 | LL_WARNS("PluginChild") << "Plugin hang requested." << LL_ENDL; | ||
432 | while(1) | ||
433 | { | ||
434 | // wheeeeeeeee...... | ||
435 | } | ||
436 | } | ||
437 | else | ||
438 | { | ||
439 | LL_WARNS("PluginChild") << "Unknown internal message from parent: " << message_name << LL_ENDL; | ||
440 | } | ||
441 | } | ||
442 | } | ||
443 | |||
444 | if(passMessage && mInstance != NULL) | ||
445 | { | ||
446 | LLTimer elapsed; | ||
447 | |||
448 | mInstance->sendMessage(message); | ||
449 | |||
450 | mCPUElapsed += elapsed.getElapsedTimeF64(); | ||
451 | } | ||
452 | } | ||
453 | |||
454 | /* virtual */ | ||
455 | void LLPluginProcessChild::receivePluginMessage(const std::string &message) | ||
456 | { | ||
457 | LL_DEBUGS("PluginChild") << "Received from plugin: " << message << LL_ENDL; | ||
458 | |||
459 | if(mBlockingRequest) | ||
460 | { | ||
461 | // | ||
462 | LL_ERRS("PluginChild") << "Can't send a message while already waiting on a blocking request -- aborting!" << LL_ENDL; | ||
463 | } | ||
464 | |||
465 | // Incoming message from the plugin instance | ||
466 | bool passMessage = true; | ||
467 | |||
468 | // FIXME: how should we handle queueing here? | ||
469 | |||
470 | // Intercept certain base messages (responses to ones sent by this class) | ||
471 | { | ||
472 | // Decode this message | ||
473 | LLPluginMessage parsed; | ||
474 | parsed.parse(message); | ||
475 | |||
476 | if(parsed.hasValue("blocking_request")) | ||
477 | { | ||
478 | mBlockingRequest = true; | ||
479 | } | ||
480 | |||
481 | std::string message_class = parsed.getClass(); | ||
482 | if(message_class == "base") | ||
483 | { | ||
484 | std::string message_name = parsed.getName(); | ||
485 | if(message_name == "init_response") | ||
486 | { | ||
487 | // The plugin has finished initializing. | ||
488 | setState(STATE_RUNNING); | ||
489 | |||
490 | // Don't pass this message up to the parent | ||
491 | passMessage = false; | ||
492 | |||
493 | LLPluginMessage new_message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin_response"); | ||
494 | LLSD versions = parsed.getValueLLSD("versions"); | ||
495 | new_message.setValueLLSD("versions", versions); | ||
496 | |||
497 | if(parsed.hasValue("plugin_version")) | ||
498 | { | ||
499 | std::string plugin_version = parsed.getValue("plugin_version"); | ||
500 | new_message.setValueLLSD("plugin_version", plugin_version); | ||
501 | } | ||
502 | |||
503 | // Let the parent know it's loaded and initialized. | ||
504 | sendMessageToParent(new_message); | ||
505 | } | ||
506 | else if(message_name == "shm_remove_response") | ||
507 | { | ||
508 | // Don't pass this message up to the parent | ||
509 | passMessage = false; | ||
510 | |||
511 | std::string name = parsed.getValue("name"); | ||
512 | sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); | ||
513 | if(iter != mSharedMemoryRegions.end()) | ||
514 | { | ||
515 | // detach the shared memory region | ||
516 | iter->second->detach(); | ||
517 | |||
518 | // and remove it from our map | ||
519 | mSharedMemoryRegions.erase(iter); | ||
520 | |||
521 | // Finally, send the response to the parent. | ||
522 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove_response"); | ||
523 | message.setValue("name", name); | ||
524 | sendMessageToParent(message); | ||
525 | } | ||
526 | else | ||
527 | { | ||
528 | LL_WARNS("PluginChild") << "shm_remove_response for unknown memory segment!" << LL_ENDL; | ||
529 | } | ||
530 | } | ||
531 | } | ||
532 | } | ||
533 | |||
534 | if(passMessage) | ||
535 | { | ||
536 | LL_DEBUGS("PluginChild") << "Passing through to parent: " << message << LL_ENDL; | ||
537 | writeMessageRaw(message); | ||
538 | } | ||
539 | |||
540 | while(mBlockingRequest) | ||
541 | { | ||
542 | // The plugin wants to block and wait for a response to this message. | ||
543 | sleep(mSleepTime); // this will pump the message pipe and process messages | ||
544 | |||
545 | if(mBlockingResponseReceived || mSocketError != APR_SUCCESS || (mMessagePipe == NULL)) | ||
546 | { | ||
547 | // Response has been received, or we've hit an error state. Stop waiting. | ||
548 | mBlockingRequest = false; | ||
549 | mBlockingResponseReceived = false; | ||
550 | } | ||
551 | } | ||
552 | } | ||
553 | |||
554 | |||
555 | void LLPluginProcessChild::setState(EState state) | ||
556 | { | ||
557 | LL_DEBUGS("PluginChild") << "setting state to " << state << LL_ENDL; | ||
558 | mState = state; | ||
559 | }; | ||
560 | |||
561 | void LLPluginProcessChild::deliverQueuedMessages() | ||
562 | { | ||
563 | if(!mBlockingRequest) | ||
564 | { | ||
565 | while(!mMessageQueue.empty()) | ||
566 | { | ||
567 | receiveMessageRaw(mMessageQueue.front()); | ||
568 | mMessageQueue.pop(); | ||
569 | } | ||
570 | } | ||
571 | } | ||
diff --git a/linden/indra/llplugin/llpluginprocesschild.h b/linden/indra/llplugin/llpluginprocesschild.h new file mode 100755 index 0000000..5d643d7 --- /dev/null +++ b/linden/indra/llplugin/llpluginprocesschild.h | |||
@@ -0,0 +1,121 @@ | |||
1 | /** | ||
2 | * @file llpluginprocesschild.h | ||
3 | * @brief LLPluginProcessChild handles the child side of the external-process plugin API. | ||
4 | * | ||
5 | * @cond | ||
6 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
7 | * | ||
8 | * Copyright (c) 2008-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_LLPLUGINPROCESSCHILD_H | ||
37 | #define LL_LLPLUGINPROCESSCHILD_H | ||
38 | |||
39 | #include <queue> //imprudence | ||
40 | |||
41 | #include "llpluginmessage.h" | ||
42 | #include "llpluginmessagepipe.h" | ||
43 | #include "llplugininstance.h" | ||
44 | #include "llhost.h" | ||
45 | #include "llpluginsharedmemory.h" | ||
46 | |||
47 | class LLPluginInstance; | ||
48 | |||
49 | class LLPluginProcessChild: public LLPluginMessagePipeOwner, public LLPluginInstanceMessageListener | ||
50 | { | ||
51 | LOG_CLASS(LLPluginProcessChild); | ||
52 | public: | ||
53 | LLPluginProcessChild(); | ||
54 | ~LLPluginProcessChild(); | ||
55 | |||
56 | void init(U32 launcher_port); | ||
57 | void idle(void); | ||
58 | void sleep(F64 seconds); | ||
59 | void pump(); | ||
60 | |||
61 | // returns true if the plugin is in the steady state (processing messages) | ||
62 | bool isRunning(void); | ||
63 | |||
64 | // returns true if the plugin is unloaded or we're in an unrecoverable error state. | ||
65 | bool isDone(void); | ||
66 | |||
67 | void killSockets(void); | ||
68 | |||
69 | F64 getSleepTime(void) const { return mSleepTime; }; | ||
70 | |||
71 | void sendMessageToPlugin(const LLPluginMessage &message); | ||
72 | void sendMessageToParent(const LLPluginMessage &message); | ||
73 | |||
74 | // Inherited from LLPluginMessagePipeOwner | ||
75 | /* virtual */ void receiveMessageRaw(const std::string &message); | ||
76 | |||
77 | // Inherited from LLPluginInstanceMessageListener | ||
78 | /* virtual */ void receivePluginMessage(const std::string &message); | ||
79 | |||
80 | private: | ||
81 | |||
82 | enum EState | ||
83 | { | ||
84 | STATE_UNINITIALIZED, | ||
85 | STATE_INITIALIZED, // init() has been called | ||
86 | STATE_CONNECTED, // connected back to launcher | ||
87 | STATE_PLUGIN_LOADING, // plugin library needs to be loaded | ||
88 | STATE_PLUGIN_LOADED, // plugin library has been loaded | ||
89 | STATE_PLUGIN_INITIALIZING, // plugin is processing init message | ||
90 | STATE_RUNNING, // steady state (processing messages) | ||
91 | STATE_UNLOADING, // plugin has sent shutdown_response and needs to be unloaded | ||
92 | STATE_UNLOADED, // plugin has been unloaded | ||
93 | STATE_ERROR, // generic bailout state | ||
94 | STATE_DONE // state machine will sit in this state after either error or normal termination. | ||
95 | }; | ||
96 | void setState(EState state); | ||
97 | |||
98 | EState mState; | ||
99 | |||
100 | LLHost mLauncherHost; | ||
101 | LLSocket::ptr_t mSocket; | ||
102 | |||
103 | std::string mPluginFile; | ||
104 | |||
105 | LLPluginInstance *mInstance; | ||
106 | |||
107 | typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType; | ||
108 | sharedMemoryRegionsType mSharedMemoryRegions; | ||
109 | |||
110 | LLTimer mHeartbeat; | ||
111 | F64 mSleepTime; | ||
112 | F64 mCPUElapsed; | ||
113 | bool mBlockingRequest; | ||
114 | bool mBlockingResponseReceived; | ||
115 | std::queue<std::string> mMessageQueue; | ||
116 | |||
117 | void deliverQueuedMessages(); | ||
118 | |||
119 | }; | ||
120 | |||
121 | #endif // LL_LLPLUGINPROCESSCHILD_H | ||
diff --git a/linden/indra/llplugin/llpluginprocessparent.cpp b/linden/indra/llplugin/llpluginprocessparent.cpp new file mode 100755 index 0000000..26572a0 --- /dev/null +++ b/linden/indra/llplugin/llpluginprocessparent.cpp | |||
@@ -0,0 +1,1161 @@ | |||
1 | /** | ||
2 | * @file llpluginprocessparent.cpp | ||
3 | * @brief LLPluginProcessParent handles the parent side of the external-process plugin API. | ||
4 | * | ||
5 | * @cond | ||
6 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
7 | * | ||
8 | * Copyright (c) 2008-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 | /// IMPRUDENCE: this is part of the viewer | ||
37 | |||
38 | #include "linden_common.h" | ||
39 | |||
40 | #include "llpluginprocessparent.h" | ||
41 | #include "llpluginmessagepipe.h" | ||
42 | #include "llpluginmessageclasses.h" | ||
43 | |||
44 | #include "llapr.h" | ||
45 | |||
46 | //virtual | ||
47 | LLPluginProcessParentOwner::~LLPluginProcessParentOwner() | ||
48 | { | ||
49 | |||
50 | } | ||
51 | |||
52 | bool LLPluginProcessParent::sUseReadThread = false; | ||
53 | apr_pollset_t *LLPluginProcessParent::sPollSet = NULL; | ||
54 | AIAPRPool LLPluginProcessParent::sPollSetPool; | ||
55 | bool LLPluginProcessParent::sPollsetNeedsRebuild = false; | ||
56 | LLMutex *LLPluginProcessParent::sInstancesMutex; | ||
57 | std::list<LLPluginProcessParent*> LLPluginProcessParent::sInstances; | ||
58 | LLThread *LLPluginProcessParent::sReadThread = NULL; | ||
59 | |||
60 | |||
61 | class LLPluginProcessParentPollThread: public LLThread | ||
62 | { | ||
63 | public: | ||
64 | LLPluginProcessParentPollThread() : | ||
65 | LLThread("LLPluginProcessParentPollThread") | ||
66 | { | ||
67 | } | ||
68 | protected: | ||
69 | // Inherited from LLThread | ||
70 | /*virtual*/ void run(void) | ||
71 | { | ||
72 | while(!isQuitting() && LLPluginProcessParent::getUseReadThread()) | ||
73 | { | ||
74 | LLPluginProcessParent::poll(0.1f); | ||
75 | checkPause(); | ||
76 | } | ||
77 | |||
78 | // Final poll to clean up the pollset, etc. | ||
79 | LLPluginProcessParent::poll(0.0f); | ||
80 | } | ||
81 | |||
82 | // Inherited from LLThread | ||
83 | /*virtual*/ bool runCondition(void) | ||
84 | { | ||
85 | return(LLPluginProcessParent::canPollThreadRun()); | ||
86 | } | ||
87 | |||
88 | }; | ||
89 | |||
90 | LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner) | ||
91 | { | ||
92 | if(!sInstancesMutex) | ||
93 | { | ||
94 | sInstancesMutex = new LLMutex; | ||
95 | } | ||
96 | |||
97 | mOwner = owner; | ||
98 | mBoundPort = 0; | ||
99 | mState = STATE_UNINITIALIZED; | ||
100 | mSleepTime = 0.0; | ||
101 | mCPUUsage = 0.0; | ||
102 | mDisableTimeout = false; | ||
103 | mDebug = false; | ||
104 | mBlocked = false; | ||
105 | mPolledInput = false; | ||
106 | mPollFD.client_data = NULL; | ||
107 | mPollFDPool.create(); | ||
108 | |||
109 | mPluginLaunchTimeout = 60.0f; | ||
110 | mPluginLockupTimeout = 15.0f; | ||
111 | |||
112 | // Don't start the timer here -- start it when we actually launch the plugin process. | ||
113 | mHeartbeat.stop(); | ||
114 | |||
115 | |||
116 | // Don't add to the global list until fully constructed. | ||
117 | { | ||
118 | LLMutexLock lock(sInstancesMutex); | ||
119 | sInstances.push_back(this); | ||
120 | } | ||
121 | } | ||
122 | |||
123 | LLPluginProcessParent::~LLPluginProcessParent() | ||
124 | { | ||
125 | LL_DEBUGS("PluginParent") << "destructor" << LL_ENDL; | ||
126 | |||
127 | // Remove from the global list before beginning destruction. | ||
128 | { | ||
129 | // Make sure to get the global mutex _first_ here, to avoid a possible deadlock against LLPluginProcessParent::poll() | ||
130 | LLMutexLock lock(sInstancesMutex); | ||
131 | { | ||
132 | LLMutexLock lock2(&mIncomingQueueMutex); | ||
133 | sInstances.remove(this); | ||
134 | } | ||
135 | } | ||
136 | |||
137 | // Destroy any remaining shared memory regions | ||
138 | sharedMemoryRegionsType::iterator iter; | ||
139 | while((iter = mSharedMemoryRegions.begin()) != mSharedMemoryRegions.end()) | ||
140 | { | ||
141 | // destroy the shared memory region | ||
142 | iter->second->destroy(); | ||
143 | |||
144 | // and remove it from our map | ||
145 | mSharedMemoryRegions.erase(iter); | ||
146 | } | ||
147 | |||
148 | mProcess.kill(); | ||
149 | killSockets(); | ||
150 | } | ||
151 | |||
152 | void LLPluginProcessParent::killSockets(void) | ||
153 | { | ||
154 | { | ||
155 | LLMutexLock lock(&mIncomingQueueMutex); | ||
156 | killMessagePipe(); | ||
157 | } | ||
158 | |||
159 | mListenSocket.reset(); | ||
160 | mSocket.reset(); | ||
161 | } | ||
162 | |||
163 | void LLPluginProcessParent::errorState(void) | ||
164 | { | ||
165 | if(mState < STATE_RUNNING) | ||
166 | setState(STATE_LAUNCH_FAILURE); | ||
167 | else | ||
168 | setState(STATE_ERROR); | ||
169 | } | ||
170 | |||
171 | void LLPluginProcessParent::init(const std::string &launcher_filename, const std::string &plugin_filename, bool debug) | ||
172 | { | ||
173 | mProcess.setExecutable(launcher_filename); | ||
174 | mPluginFile = plugin_filename; | ||
175 | mCPUUsage = 0.0f; | ||
176 | mDebug = debug; | ||
177 | setState(STATE_INITIALIZED); | ||
178 | } | ||
179 | |||
180 | bool LLPluginProcessParent::accept() | ||
181 | { | ||
182 | bool result = false; | ||
183 | apr_status_t status = APR_EGENERAL; | ||
184 | |||
185 | mSocket = LLSocket::create(status, mListenSocket); | ||
186 | |||
187 | if(status == APR_SUCCESS) | ||
188 | { | ||
189 | // llinfos << "SUCCESS" << llendl; | ||
190 | // Success. Create a message pipe on the new socket | ||
191 | new LLPluginMessagePipe(this, mSocket); | ||
192 | |||
193 | result = true; | ||
194 | } | ||
195 | else | ||
196 | { | ||
197 | mSocket.reset(); | ||
198 | // EAGAIN means "No incoming connections". This is not an error. | ||
199 | if (!APR_STATUS_IS_EAGAIN(status)) | ||
200 | { | ||
201 | // Some other error. | ||
202 | ll_apr_warn_status(status); | ||
203 | errorState(); | ||
204 | } | ||
205 | } | ||
206 | |||
207 | return result; | ||
208 | } | ||
209 | |||
210 | void LLPluginProcessParent::idle(void) | ||
211 | { | ||
212 | bool idle_again; | ||
213 | |||
214 | do | ||
215 | { | ||
216 | // process queued messages | ||
217 | mIncomingQueueMutex.lock(); | ||
218 | while(!mIncomingQueue.empty()) | ||
219 | { | ||
220 | LLPluginMessage message = mIncomingQueue.front(); | ||
221 | mIncomingQueue.pop(); | ||
222 | mIncomingQueueMutex.unlock(); | ||
223 | |||
224 | receiveMessage(message); | ||
225 | |||
226 | mIncomingQueueMutex.lock(); | ||
227 | } | ||
228 | |||
229 | mIncomingQueueMutex.unlock(); | ||
230 | |||
231 | // Give time to network processing | ||
232 | if(mMessagePipe) | ||
233 | { | ||
234 | // Drain any queued outgoing messages | ||
235 | mMessagePipe->pumpOutput(); | ||
236 | |||
237 | // Only do input processing here if this instance isn't in a pollset. | ||
238 | if(!mPolledInput) | ||
239 | { | ||
240 | mMessagePipe->pumpInput(); | ||
241 | } | ||
242 | } | ||
243 | |||
244 | if(mState <= STATE_RUNNING) | ||
245 | { | ||
246 | if(APR_STATUS_IS_EOF(mSocketError)) | ||
247 | { | ||
248 | // Plugin socket was closed. This covers both normal plugin termination and plugin crashes. | ||
249 | errorState(); | ||
250 | } | ||
251 | else if(mSocketError != APR_SUCCESS) | ||
252 | { | ||
253 | // The socket is in an error state -- the plugin is gone. | ||
254 | LL_WARNS("PluginParent") << "Socket hit an error state (" << mSocketError << ")" << LL_ENDL; | ||
255 | errorState(); | ||
256 | } | ||
257 | } | ||
258 | |||
259 | // If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState(). | ||
260 | // USE THIS CAREFULLY, since it can starve other code. Specifically make sure there's no way to get into a closed cycle and never return. | ||
261 | // When in doubt, don't do it. | ||
262 | idle_again = false; | ||
263 | switch(mState) | ||
264 | { | ||
265 | case STATE_UNINITIALIZED: | ||
266 | break; | ||
267 | |||
268 | case STATE_INITIALIZED: | ||
269 | { | ||
270 | |||
271 | apr_status_t status = APR_SUCCESS; | ||
272 | apr_sockaddr_t* addr = NULL; | ||
273 | mListenSocket = LLSocket::create(LLSocket::STREAM_TCP); | ||
274 | mBoundPort = 0; | ||
275 | |||
276 | // This code is based on parts of LLSocket::create() in lliosocket.cpp. | ||
277 | |||
278 | status = apr_sockaddr_info_get( | ||
279 | &addr, | ||
280 | "127.0.0.1", | ||
281 | APR_INET, | ||
282 | 0, // port 0 = ephemeral ("find me a port") | ||
283 | 0, | ||
284 | AIAPRRootPool::get()()); | ||
285 | |||
286 | if(ll_apr_warn_status(status)) | ||
287 | { | ||
288 | killSockets(); | ||
289 | errorState(); | ||
290 | break; | ||
291 | } | ||
292 | |||
293 | // This allows us to reuse the address on quick down/up. This is unlikely to create problems. | ||
294 | ll_apr_warn_status(apr_socket_opt_set(mListenSocket->getSocket(), APR_SO_REUSEADDR, 1)); | ||
295 | |||
296 | status = apr_socket_bind(mListenSocket->getSocket(), addr); | ||
297 | if(ll_apr_warn_status(status)) | ||
298 | { | ||
299 | killSockets(); | ||
300 | errorState(); | ||
301 | break; | ||
302 | } | ||
303 | |||
304 | // Get the actual port the socket was bound to | ||
305 | { | ||
306 | apr_sockaddr_t* bound_addr = NULL; | ||
307 | if(ll_apr_warn_status(apr_socket_addr_get(&bound_addr, APR_LOCAL, mListenSocket->getSocket()))) | ||
308 | { | ||
309 | killSockets(); | ||
310 | errorState(); | ||
311 | break; | ||
312 | } | ||
313 | mBoundPort = bound_addr->port; | ||
314 | |||
315 | if(mBoundPort == 0) | ||
316 | { | ||
317 | LL_WARNS("PluginParent") << "Bound port number unknown, bailing out." << LL_ENDL; | ||
318 | |||
319 | killSockets(); | ||
320 | errorState(); | ||
321 | break; | ||
322 | } | ||
323 | } | ||
324 | |||
325 | LL_DEBUGS("PluginParent") << "Bound tcp socket to port: " << addr->port << LL_ENDL; | ||
326 | |||
327 | // Make the listen socket non-blocking | ||
328 | status = apr_socket_opt_set(mListenSocket->getSocket(), APR_SO_NONBLOCK, 1); | ||
329 | if(ll_apr_warn_status(status)) | ||
330 | { | ||
331 | killSockets(); | ||
332 | errorState(); | ||
333 | break; | ||
334 | } | ||
335 | |||
336 | apr_socket_timeout_set(mListenSocket->getSocket(), 0); | ||
337 | if(ll_apr_warn_status(status)) | ||
338 | { | ||
339 | killSockets(); | ||
340 | errorState(); | ||
341 | break; | ||
342 | } | ||
343 | |||
344 | // If it's a stream based socket, we need to tell the OS | ||
345 | // to keep a queue of incoming connections for ACCEPT. | ||
346 | status = apr_socket_listen( | ||
347 | mListenSocket->getSocket(), | ||
348 | 10); // FIXME: Magic number for queue size | ||
349 | |||
350 | if(ll_apr_warn_status(status)) | ||
351 | { | ||
352 | killSockets(); | ||
353 | errorState(); | ||
354 | break; | ||
355 | } | ||
356 | |||
357 | // If we got here, we're listening. | ||
358 | setState(STATE_LISTENING); | ||
359 | } | ||
360 | break; | ||
361 | |||
362 | case STATE_LISTENING: | ||
363 | { | ||
364 | // Launch the plugin process. | ||
365 | |||
366 | // Only argument to the launcher is the port number we're listening on | ||
367 | std::stringstream stream; | ||
368 | stream << mBoundPort; | ||
369 | mProcess.addArgument(stream.str()); | ||
370 | if(mProcess.launch() != 0) | ||
371 | { | ||
372 | errorState(); | ||
373 | } | ||
374 | else | ||
375 | { | ||
376 | if(mDebug) | ||
377 | { | ||
378 | #if LL_DARWIN | ||
379 | // If we're set to debug, start up a gdb instance in a new terminal window and have it attach to the plugin process and continue. | ||
380 | |||
381 | // The command we're constructing would look like this on the command line: | ||
382 | // osascript -e 'tell application "Terminal"' -e 'set win to do script "gdb -pid 12345"' -e 'do script "continue" in win' -e 'end tell' | ||
383 | |||
384 | std::stringstream cmd; | ||
385 | |||
386 | mDebugger.setExecutable("/usr/bin/osascript"); | ||
387 | mDebugger.addArgument("-e"); | ||
388 | mDebugger.addArgument("tell application \"Terminal\""); | ||
389 | mDebugger.addArgument("-e"); | ||
390 | cmd << "set win to do script \"gdb -pid " << mProcess.getProcessID() << "\""; | ||
391 | mDebugger.addArgument(cmd.str()); | ||
392 | mDebugger.addArgument("-e"); | ||
393 | mDebugger.addArgument("do script \"continue\" in win"); | ||
394 | mDebugger.addArgument("-e"); | ||
395 | mDebugger.addArgument("end tell"); | ||
396 | mDebugger.launch(); | ||
397 | |||
398 | #elif LL_LINUX | ||
399 | |||
400 | std::stringstream cmd; | ||
401 | |||
402 | mDebugger.setExecutable("/usr/bin/gnome-terminal"); | ||
403 | mDebugger.addArgument("--geometry=165x24-0+0"); | ||
404 | mDebugger.addArgument("-e"); | ||
405 | cmd << "/usr/bin/gdb -n /proc/" << mProcess.getProcessID() << "/exe " << mProcess.getProcessID(); | ||
406 | mDebugger.addArgument(cmd.str()); | ||
407 | mDebugger.launch(); | ||
408 | |||
409 | #endif | ||
410 | } | ||
411 | |||
412 | // This will allow us to time out if the process never starts. | ||
413 | mHeartbeat.start(); | ||
414 | mHeartbeat.setTimerExpirySec(mPluginLaunchTimeout); | ||
415 | setState(STATE_LAUNCHED); | ||
416 | } | ||
417 | } | ||
418 | break; | ||
419 | |||
420 | case STATE_LAUNCHED: | ||
421 | // waiting for the plugin to connect | ||
422 | if(pluginLockedUpOrQuit()) | ||
423 | { | ||
424 | errorState(); | ||
425 | } | ||
426 | else | ||
427 | { | ||
428 | // Check for the incoming connection. | ||
429 | if(accept()) | ||
430 | { | ||
431 | // Stop listening on the server port | ||
432 | mListenSocket.reset(); | ||
433 | setState(STATE_CONNECTED); | ||
434 | } | ||
435 | } | ||
436 | break; | ||
437 | |||
438 | case STATE_CONNECTED: | ||
439 | // waiting for hello message from the plugin | ||
440 | |||
441 | if(pluginLockedUpOrQuit()) | ||
442 | { | ||
443 | errorState(); | ||
444 | } | ||
445 | break; | ||
446 | |||
447 | case STATE_HELLO: | ||
448 | LL_DEBUGS("PluginParent") << "received hello message" << LL_ENDL; | ||
449 | |||
450 | // Send the message to load the plugin | ||
451 | { | ||
452 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin"); | ||
453 | message.setValue("file", mPluginFile); | ||
454 | sendMessage(message); | ||
455 | } | ||
456 | |||
457 | setState(STATE_LOADING); | ||
458 | break; | ||
459 | |||
460 | case STATE_LOADING: | ||
461 | // The load_plugin_response message will kick us from here into STATE_RUNNING | ||
462 | if(pluginLockedUpOrQuit()) | ||
463 | { | ||
464 | errorState(); | ||
465 | } | ||
466 | break; | ||
467 | |||
468 | case STATE_RUNNING: | ||
469 | if(pluginLockedUpOrQuit()) | ||
470 | { | ||
471 | errorState(); | ||
472 | } | ||
473 | break; | ||
474 | |||
475 | case STATE_EXITING: | ||
476 | if(!mProcess.isRunning()) | ||
477 | { | ||
478 | setState(STATE_CLEANUP); | ||
479 | } | ||
480 | else if(pluginLockedUp()) | ||
481 | { | ||
482 | LL_WARNS("PluginParent") << "timeout in exiting state, bailing out" << LL_ENDL; | ||
483 | errorState(); | ||
484 | } | ||
485 | break; | ||
486 | |||
487 | case STATE_LAUNCH_FAILURE: | ||
488 | if(mOwner != NULL) | ||
489 | { | ||
490 | mOwner->pluginLaunchFailed(); | ||
491 | } | ||
492 | setState(STATE_CLEANUP); | ||
493 | break; | ||
494 | |||
495 | case STATE_ERROR: | ||
496 | if(mOwner != NULL) | ||
497 | { | ||
498 | mOwner->pluginDied(); | ||
499 | } | ||
500 | setState(STATE_CLEANUP); | ||
501 | break; | ||
502 | |||
503 | case STATE_CLEANUP: | ||
504 | mProcess.kill(); | ||
505 | killSockets(); | ||
506 | setState(STATE_DONE); | ||
507 | break; | ||
508 | |||
509 | |||
510 | case STATE_DONE: | ||
511 | // just sit here. | ||
512 | break; | ||
513 | |||
514 | } | ||
515 | |||
516 | } while (idle_again); | ||
517 | } | ||
518 | |||
519 | bool LLPluginProcessParent::isLoading(void) | ||
520 | { | ||
521 | bool result = false; | ||
522 | |||
523 | if(mState <= STATE_LOADING) | ||
524 | result = true; | ||
525 | |||
526 | return result; | ||
527 | } | ||
528 | |||
529 | bool LLPluginProcessParent::isRunning(void) | ||
530 | { | ||
531 | bool result = false; | ||
532 | |||
533 | if(mState == STATE_RUNNING) | ||
534 | result = true; | ||
535 | |||
536 | return result; | ||
537 | } | ||
538 | |||
539 | bool LLPluginProcessParent::isDone(void) | ||
540 | { | ||
541 | bool result = false; | ||
542 | |||
543 | if(mState == STATE_DONE) | ||
544 | result = true; | ||
545 | |||
546 | return result; | ||
547 | } | ||
548 | |||
549 | void LLPluginProcessParent::setSleepTime(F64 sleep_time, bool force_send) | ||
550 | { | ||
551 | if(force_send || (sleep_time != mSleepTime)) | ||
552 | { | ||
553 | // Cache the time locally | ||
554 | mSleepTime = sleep_time; | ||
555 | |||
556 | if(canSendMessage()) | ||
557 | { | ||
558 | // and send to the plugin. | ||
559 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "sleep_time"); | ||
560 | message.setValueReal("time", mSleepTime); | ||
561 | sendMessage(message); | ||
562 | } | ||
563 | else | ||
564 | { | ||
565 | // Too early to send -- the load_plugin_response message will trigger us to send mSleepTime later. | ||
566 | } | ||
567 | } | ||
568 | } | ||
569 | |||
570 | void LLPluginProcessParent::sendMessage(const LLPluginMessage &message) | ||
571 | { | ||
572 | if(message.hasValue("blocking_response")) | ||
573 | { | ||
574 | mBlocked = false; | ||
575 | |||
576 | // reset the heartbeat timer, since there will have been no heartbeats while the plugin was blocked. | ||
577 | mHeartbeat.setTimerExpirySec(mPluginLockupTimeout); | ||
578 | } | ||
579 | |||
580 | std::string buffer = message.generate(); | ||
581 | LL_DEBUGS("PluginParent") << "Sending: " << buffer << LL_ENDL; | ||
582 | writeMessageRaw(buffer); | ||
583 | |||
584 | // Try to send message immediately. | ||
585 | if(mMessagePipe) | ||
586 | { | ||
587 | mMessagePipe->pumpOutput(); | ||
588 | } | ||
589 | } | ||
590 | |||
591 | //virtual | ||
592 | void LLPluginProcessParent::setMessagePipe(LLPluginMessagePipe *message_pipe) | ||
593 | { | ||
594 | bool update_pollset = false; | ||
595 | |||
596 | if(mMessagePipe) | ||
597 | { | ||
598 | // Unsetting an existing message pipe -- remove from the pollset | ||
599 | mPollFD.client_data = NULL; | ||
600 | |||
601 | // pollset needs an update | ||
602 | update_pollset = true; | ||
603 | } | ||
604 | if(message_pipe != NULL) | ||
605 | { | ||
606 | // Set up the apr_pollfd_t | ||
607 | |||
608 | mPollFD.p = mPollFDPool(); | ||
609 | mPollFD.desc_type = APR_POLL_SOCKET; | ||
610 | mPollFD.reqevents = APR_POLLIN|APR_POLLERR|APR_POLLHUP; | ||
611 | mPollFD.rtnevents = 0; | ||
612 | mPollFD.desc.s = mSocket->getSocket(); | ||
613 | mPollFD.client_data = (void*)this; | ||
614 | |||
615 | // pollset needs an update | ||
616 | update_pollset = true; | ||
617 | } | ||
618 | |||
619 | mMessagePipe = message_pipe; | ||
620 | |||
621 | if(update_pollset) | ||
622 | { | ||
623 | dirtyPollSet(); | ||
624 | } | ||
625 | } | ||
626 | |||
627 | //static | ||
628 | void LLPluginProcessParent::dirtyPollSet() | ||
629 | { | ||
630 | sPollsetNeedsRebuild = true; | ||
631 | |||
632 | if(sReadThread) | ||
633 | { | ||
634 | LL_DEBUGS("PluginPoll") << "unpausing read thread " << LL_ENDL; | ||
635 | sReadThread->unpause(); | ||
636 | } | ||
637 | } | ||
638 | |||
639 | void LLPluginProcessParent::updatePollset() | ||
640 | { | ||
641 | if(!sInstancesMutex) | ||
642 | { | ||
643 | // No instances have been created yet. There's no work to do. | ||
644 | return; | ||
645 | } | ||
646 | |||
647 | LLMutexLock lock(sInstancesMutex); | ||
648 | |||
649 | if(sPollSet) | ||
650 | { | ||
651 | LL_DEBUGS("PluginPoll") << "destroying pollset " << sPollSet << LL_ENDL; | ||
652 | // delete the existing pollset. | ||
653 | apr_pollset_destroy(sPollSet); | ||
654 | sPollSet = NULL; | ||
655 | sPollSetPool.destroy(); | ||
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 | sPollSetPool.create(); | ||
679 | apr_status_t status = apr_pollset_create(&sPollSet, count, sPollSetPool(), APR_POLLSET_NOCOPY); | ||
680 | if(status != APR_SUCCESS) | ||
681 | { | ||
682 | #endif // APR_POLLSET_NOCOPY | ||
683 | LL_WARNS("PluginPoll") << "Couldn't create pollset. Falling back to non-pollset mode." << LL_ENDL; | ||
684 | sPollSet = NULL; | ||
685 | sPollSetPool.destroy(); | ||
686 | #ifdef APR_POLLSET_NOCOPY | ||
687 | } | ||
688 | else | ||
689 | { | ||
690 | LL_DEBUGS("PluginPoll") << "created pollset " << sPollSet << LL_ENDL; | ||
691 | |||
692 | // Pollset was created, add all instances to it. | ||
693 | for(iter = sInstances.begin(); iter != sInstances.end(); iter++) | ||
694 | { | ||
695 | if((*iter)->mPollFD.client_data) | ||
696 | { | ||
697 | status = apr_pollset_add(sPollSet, &((*iter)->mPollFD)); | ||
698 | if(status == APR_SUCCESS) | ||
699 | { | ||
700 | (*iter)->mPolledInput = true; | ||
701 | } | ||
702 | else | ||
703 | { | ||
704 | LL_WARNS("PluginPoll") << "apr_pollset_add failed with status " << status << LL_ENDL; | ||
705 | } | ||
706 | } | ||
707 | } | ||
708 | } | ||
709 | #endif // APR_POLLSET_NOCOPY | ||
710 | } | ||
711 | } | ||
712 | } | ||
713 | |||
714 | void LLPluginProcessParent::setUseReadThread(bool use_read_thread) | ||
715 | { | ||
716 | if(sUseReadThread != use_read_thread) | ||
717 | { | ||
718 | sUseReadThread = use_read_thread; | ||
719 | |||
720 | if(sUseReadThread) | ||
721 | { | ||
722 | if(!sReadThread) | ||
723 | { | ||
724 | // start up the read thread | ||
725 | LL_INFOS("PluginPoll") << "creating read thread " << LL_ENDL; | ||
726 | |||
727 | // make sure the pollset gets rebuilt. | ||
728 | sPollsetNeedsRebuild = true; | ||
729 | |||
730 | sReadThread = new LLPluginProcessParentPollThread; | ||
731 | sReadThread->start(); | ||
732 | } | ||
733 | } | ||
734 | else | ||
735 | { | ||
736 | if(sReadThread) | ||
737 | { | ||
738 | // shut down the read thread | ||
739 | LL_INFOS("PluginPoll") << "destroying read thread " << LL_ENDL; | ||
740 | delete sReadThread; | ||
741 | sReadThread = NULL; | ||
742 | } | ||
743 | } | ||
744 | |||
745 | } | ||
746 | } | ||
747 | |||
748 | void LLPluginProcessParent::poll(F64 timeout) | ||
749 | { | ||
750 | if(sPollsetNeedsRebuild || !sUseReadThread) | ||
751 | { | ||
752 | sPollsetNeedsRebuild = false; | ||
753 | updatePollset(); | ||
754 | } | ||
755 | |||
756 | if(sPollSet) | ||
757 | { | ||
758 | apr_status_t status; | ||
759 | apr_int32_t count; | ||
760 | const apr_pollfd_t *descriptors; | ||
761 | status = apr_pollset_poll(sPollSet, (apr_interval_time_t)(timeout * 1000000), &count, &descriptors); | ||
762 | if(status == APR_SUCCESS) | ||
763 | { | ||
764 | // One or more of the descriptors signalled. Call them. | ||
765 | for(int i = 0; i < count; i++) | ||
766 | { | ||
767 | LLPluginProcessParent *self = (LLPluginProcessParent *)(descriptors[i].client_data); | ||
768 | // NOTE: the descriptor returned here is actually a COPY of the original (even though we create the pollset with APR_POLLSET_NOCOPY). | ||
769 | // This means that even if the parent has set its mPollFD.client_data to NULL, the old pointer may still there in this descriptor. | ||
770 | // It's even possible that the old pointer no longer points to a valid LLPluginProcessParent. | ||
771 | // This means that we can't safely dereference the 'self' pointer here without some extra steps... | ||
772 | if(self) | ||
773 | { | ||
774 | // Make sure this pointer is still in the instances list | ||
775 | bool valid = false; | ||
776 | { | ||
777 | LLMutexLock lock(sInstancesMutex); | ||
778 | for(std::list<LLPluginProcessParent*>::iterator iter = sInstances.begin(); iter != sInstances.end(); ++iter) | ||
779 | { | ||
780 | if(*iter == self) | ||
781 | { | ||
782 | // Lock the instance's mutex before unlocking the global mutex. | ||
783 | // This avoids a possible race condition where the instance gets deleted between this check and the servicePoll() call. | ||
784 | self->mIncomingQueueMutex.lock(); | ||
785 | valid = true; | ||
786 | break; | ||
787 | } | ||
788 | } | ||
789 | } | ||
790 | |||
791 | if(valid) | ||
792 | { | ||
793 | // The instance is still valid. | ||
794 | // Pull incoming messages off the socket | ||
795 | self->servicePoll(); | ||
796 | self->mIncomingQueueMutex.unlock(); | ||
797 | } | ||
798 | else | ||
799 | { | ||
800 | LL_DEBUGS("PluginPoll") << "detected deleted instance " << self << LL_ENDL; | ||
801 | } | ||
802 | |||
803 | } | ||
804 | } | ||
805 | } | ||
806 | else if(APR_STATUS_IS_TIMEUP(status)) | ||
807 | { | ||
808 | // timed out with no incoming data. Just return. | ||
809 | } | ||
810 | else if(status == EBADF) | ||
811 | { | ||
812 | // This happens when one of the file descriptors in the pollset is destroyed, which happens whenever a plugin's socket is closed. | ||
813 | // The pollset has been or will be recreated, so just return. | ||
814 | LL_DEBUGS("PluginPoll") << "apr_pollset_poll returned EBADF" << LL_ENDL; | ||
815 | } | ||
816 | else if(status != APR_SUCCESS) | ||
817 | { | ||
818 | LL_WARNS("PluginPoll") << "apr_pollset_poll failed with status " << status << LL_ENDL; | ||
819 | } | ||
820 | } | ||
821 | } | ||
822 | |||
823 | void LLPluginProcessParent::servicePoll() | ||
824 | { | ||
825 | bool result = true; | ||
826 | |||
827 | // poll signalled on this object's socket. Try to process incoming messages. | ||
828 | if(mMessagePipe) | ||
829 | { | ||
830 | result = mMessagePipe->pumpInput(0.0f); | ||
831 | } | ||
832 | |||
833 | if(!result) | ||
834 | { | ||
835 | // If we got a read error on input, remove this pipe from the pollset | ||
836 | apr_pollset_remove(sPollSet, &mPollFD); | ||
837 | |||
838 | // and tell the code not to re-add it | ||
839 | mPollFD.client_data = NULL; | ||
840 | } | ||
841 | } | ||
842 | |||
843 | void LLPluginProcessParent::receiveMessageRaw(const std::string &message) | ||
844 | { | ||
845 | LL_DEBUGS("PluginParent") << "Received: " << message << LL_ENDL; | ||
846 | |||
847 | LLPluginMessage parsed; | ||
848 | if(parsed.parse(message) != -1) | ||
849 | { | ||
850 | if(parsed.hasValue("blocking_request")) | ||
851 | { | ||
852 | mBlocked = true; | ||
853 | } | ||
854 | |||
855 | if(mPolledInput) | ||
856 | { | ||
857 | // This is being called on the polling thread -- only do minimal processing/queueing. | ||
858 | receiveMessageEarly(parsed); | ||
859 | } | ||
860 | else | ||
861 | { | ||
862 | // This is not being called on the polling thread -- do full message processing at this time. | ||
863 | receiveMessage(parsed); | ||
864 | } | ||
865 | } | ||
866 | } | ||
867 | |||
868 | void LLPluginProcessParent::receiveMessageEarly(const LLPluginMessage &message) | ||
869 | { | ||
870 | // NOTE: this function will be called from the polling thread. It will be called with mIncomingQueueMutex _already locked_. | ||
871 | |||
872 | bool handled = false; | ||
873 | |||
874 | std::string message_class = message.getClass(); | ||
875 | if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL) | ||
876 | { | ||
877 | // no internal messages need to be handled early. | ||
878 | } | ||
879 | else | ||
880 | { | ||
881 | // Call out to the owner and see if they to reply | ||
882 | // TODO: Should this only happen when blocked? | ||
883 | if(mOwner != NULL) | ||
884 | { | ||
885 | handled = mOwner->receivePluginMessageEarly(message); | ||
886 | } | ||
887 | } | ||
888 | |||
889 | if(!handled) | ||
890 | { | ||
891 | // any message that wasn't handled early needs to be queued. | ||
892 | mIncomingQueue.push(message); | ||
893 | } | ||
894 | } | ||
895 | |||
896 | void LLPluginProcessParent::receiveMessage(const LLPluginMessage &message) | ||
897 | { | ||
898 | std::string message_class = message.getClass(); | ||
899 | if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL) | ||
900 | { | ||
901 | // internal messages should be handled here | ||
902 | std::string message_name = message.getName(); | ||
903 | if(message_name == "hello") | ||
904 | { | ||
905 | if(mState == STATE_CONNECTED) | ||
906 | { | ||
907 | // Plugin host has launched. Tell it which plugin to load. | ||
908 | setState(STATE_HELLO); | ||
909 | } | ||
910 | else | ||
911 | { | ||
912 | LL_WARNS("PluginParent") << "received hello message in wrong state -- bailing out" << LL_ENDL; | ||
913 | errorState(); | ||
914 | } | ||
915 | |||
916 | } | ||
917 | else if(message_name == "load_plugin_response") | ||
918 | { | ||
919 | if(mState == STATE_LOADING) | ||
920 | { | ||
921 | // Plugin has been loaded. | ||
922 | |||
923 | mPluginVersionString = message.getValue("plugin_version"); | ||
924 | LL_INFOS("PluginParent") << "plugin version string: " << mPluginVersionString << LL_ENDL; | ||
925 | |||
926 | // Check which message classes/versions the plugin supports. | ||
927 | // TODO: check against current versions | ||
928 | // TODO: kill plugin on major mismatches? | ||
929 | mMessageClassVersions = message.getValueLLSD("versions"); | ||
930 | LLSD::map_iterator iter; | ||
931 | for(iter = mMessageClassVersions.beginMap(); iter != mMessageClassVersions.endMap(); iter++) | ||
932 | { | ||
933 | LL_INFOS("PluginParent") << "message class: " << iter->first << " -> version: " << iter->second.asString() << LL_ENDL; | ||
934 | } | ||
935 | |||
936 | // Send initial sleep time | ||
937 | setSleepTime(mSleepTime, true); | ||
938 | |||
939 | setState(STATE_RUNNING); | ||
940 | } | ||
941 | else | ||
942 | { | ||
943 | LL_WARNS("PluginParent") << "received load_plugin_response message in wrong state -- bailing out" << LL_ENDL; | ||
944 | errorState(); | ||
945 | } | ||
946 | } | ||
947 | else if(message_name == "heartbeat") | ||
948 | { | ||
949 | // this resets our timer. | ||
950 | mHeartbeat.setTimerExpirySec(mPluginLockupTimeout); | ||
951 | |||
952 | mCPUUsage = message.getValueReal("cpu_usage"); | ||
953 | |||
954 | LL_DEBUGS("PluginSpam") << "cpu usage reported as " << mCPUUsage << LL_ENDL; | ||
955 | |||
956 | } | ||
957 | else if(message_name == "shm_add_response") | ||
958 | { | ||
959 | // Nothing to do here. | ||
960 | } | ||
961 | else if(message_name == "shm_remove_response") | ||
962 | { | ||
963 | std::string name = message.getValue("name"); | ||
964 | sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); | ||
965 | |||
966 | if(iter != mSharedMemoryRegions.end()) | ||
967 | { | ||
968 | // destroy the shared memory region | ||
969 | iter->second->destroy(); | ||
970 | |||
971 | // and remove it from our map | ||
972 | mSharedMemoryRegions.erase(iter); | ||
973 | } | ||
974 | } | ||
975 | else | ||
976 | { | ||
977 | LL_WARNS("PluginParent") << "Unknown internal message from child: " << message_name << LL_ENDL; | ||
978 | } | ||
979 | } | ||
980 | else | ||
981 | { | ||
982 | if(mOwner != NULL) | ||
983 | { | ||
984 | mOwner->receivePluginMessage(message); | ||
985 | } | ||
986 | } | ||
987 | } | ||
988 | |||
989 | std::string LLPluginProcessParent::addSharedMemory(size_t size) | ||
990 | { | ||
991 | std::string name; | ||
992 | |||
993 | LLPluginSharedMemory *region = new LLPluginSharedMemory; | ||
994 | |||
995 | // This is a new region | ||
996 | if(region->create(size)) | ||
997 | { | ||
998 | name = region->getName(); | ||
999 | |||
1000 | mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region)); | ||
1001 | |||
1002 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add"); | ||
1003 | message.setValue("name", name); | ||
1004 | message.setValueS32("size", (S32)size); | ||
1005 | sendMessage(message); | ||
1006 | } | ||
1007 | else | ||
1008 | { | ||
1009 | LL_WARNS("PluginParent") << "Couldn't create a shared memory segment!" << LL_ENDL; | ||
1010 | |||
1011 | // Don't leak | ||
1012 | delete region; | ||
1013 | } | ||
1014 | |||
1015 | return name; | ||
1016 | } | ||
1017 | |||
1018 | void LLPluginProcessParent::removeSharedMemory(const std::string &name) | ||
1019 | { | ||
1020 | sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); | ||
1021 | |||
1022 | if(iter != mSharedMemoryRegions.end()) | ||
1023 | { | ||
1024 | // This segment exists. Send the message to the child to unmap it. The response will cause the parent to unmap our end. | ||
1025 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove"); | ||
1026 | message.setValue("name", name); | ||
1027 | sendMessage(message); | ||
1028 | } | ||
1029 | else | ||
1030 | { | ||
1031 | LL_WARNS("PluginParent") << "Request to remove an unknown shared memory segment." << LL_ENDL; | ||
1032 | } | ||
1033 | } | ||
1034 | size_t LLPluginProcessParent::getSharedMemorySize(const std::string &name) | ||
1035 | { | ||
1036 | size_t result = 0; | ||
1037 | |||
1038 | sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); | ||
1039 | if(iter != mSharedMemoryRegions.end()) | ||
1040 | { | ||
1041 | result = iter->second->getSize(); | ||
1042 | } | ||
1043 | |||
1044 | return result; | ||
1045 | } | ||
1046 | void *LLPluginProcessParent::getSharedMemoryAddress(const std::string &name) | ||
1047 | { | ||
1048 | void *result = NULL; | ||
1049 | |||
1050 | sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name); | ||
1051 | if(iter != mSharedMemoryRegions.end()) | ||
1052 | { | ||
1053 | result = iter->second->getMappedAddress(); | ||
1054 | } | ||
1055 | |||
1056 | return result; | ||
1057 | } | ||
1058 | |||
1059 | std::string LLPluginProcessParent::getMessageClassVersion(const std::string &message_class) | ||
1060 | { | ||
1061 | std::string result; | ||
1062 | |||
1063 | if(mMessageClassVersions.has(message_class)) | ||
1064 | { | ||
1065 | result = mMessageClassVersions[message_class].asString(); | ||
1066 | } | ||
1067 | |||
1068 | return result; | ||
1069 | } | ||
1070 | |||
1071 | std::string LLPluginProcessParent::getPluginVersion(void) | ||
1072 | { | ||
1073 | return mPluginVersionString; | ||
1074 | } | ||
1075 | |||
1076 | void LLPluginProcessParent::setState(EState state) | ||
1077 | { | ||
1078 | LL_DEBUGS("PluginParent") << "setting state to " << stateToString(state) << LL_ENDL; | ||
1079 | mState = state; | ||
1080 | }; | ||
1081 | |||
1082 | bool LLPluginProcessParent::pluginLockedUpOrQuit() | ||
1083 | { | ||
1084 | bool result = false; | ||
1085 | |||
1086 | if(!mProcess.isRunning()) | ||
1087 | { | ||
1088 | LL_WARNS("PluginParent") << "child exited" << LL_ENDL; | ||
1089 | result = true; | ||
1090 | } | ||
1091 | else if(pluginLockedUp()) | ||
1092 | { | ||
1093 | LL_WARNS("PluginParent") << "timeout" << LL_ENDL; | ||
1094 | result = true; | ||
1095 | } | ||
1096 | |||
1097 | return result; | ||
1098 | } | ||
1099 | |||
1100 | bool LLPluginProcessParent::pluginLockedUp() | ||
1101 | { | ||
1102 | if(mDisableTimeout || mDebug || mBlocked) | ||
1103 | { | ||
1104 | // Never time out a plugin process in these cases. | ||
1105 | return false; | ||
1106 | } | ||
1107 | |||
1108 | // If the timer is running and has expired, the plugin has locked up. | ||
1109 | return (mHeartbeat.getStarted() && mHeartbeat.hasExpired()); | ||
1110 | } | ||
1111 | |||
1112 | std::string LLPluginProcessParent::stateToString(EState state) | ||
1113 | { | ||
1114 | std::string eng = "unknown plugin state"; | ||
1115 | switch (state) | ||
1116 | { | ||
1117 | case STATE_UNINITIALIZED: | ||
1118 | eng = "STATE_UNINITIALIZED"; | ||
1119 | break; | ||
1120 | case STATE_INITIALIZED: | ||
1121 | eng = "STATE_INITIALIZED - init() has been called"; | ||
1122 | break; | ||
1123 | case STATE_LISTENING: | ||
1124 | eng = "STATE_LISTENING - listening for incoming connection"; | ||
1125 | break; | ||
1126 | case STATE_LAUNCHED: | ||
1127 | eng = "STATE_LAUNCHED - process has been launched"; | ||
1128 | break; | ||
1129 | case STATE_CONNECTED: | ||
1130 | eng = "STATE_CONNECTED - process has connected"; | ||
1131 | break; | ||
1132 | case STATE_HELLO: | ||
1133 | eng = "STATE_HELLO - first message from the plugin process has been received"; | ||
1134 | break; | ||
1135 | case STATE_LOADING: | ||
1136 | eng = "STATE_LOADING - process has been asked to load the plugin"; | ||
1137 | break; | ||
1138 | case STATE_RUNNING: | ||
1139 | eng = "STATE_RUNNING - plugin running"; | ||
1140 | break; | ||
1141 | case STATE_LAUNCH_FAILURE: | ||
1142 | eng = "STATE_LAUNCH_FAILURE - failure before plugin loaded"; | ||
1143 | break; | ||
1144 | case STATE_ERROR: | ||
1145 | eng = "STATE_ERROR - generic bailout state"; | ||
1146 | break; | ||
1147 | case STATE_CLEANUP: | ||
1148 | eng = "STATE_CLEANUP - clean everything up"; | ||
1149 | break; | ||
1150 | case STATE_EXITING: | ||
1151 | eng = "STATE_EXITING - tried to kill process, waiting for it to exit"; | ||
1152 | break; | ||
1153 | case STATE_DONE: | ||
1154 | eng = "STATE_DONE - plugin done"; | ||
1155 | break; | ||
1156 | default: | ||
1157 | break; | ||
1158 | } | ||
1159 | |||
1160 | return llformat("(%d) ", (S32)state) + eng; | ||
1161 | } | ||
diff --git a/linden/indra/llplugin/llpluginprocessparent.h b/linden/indra/llplugin/llpluginprocessparent.h new file mode 100755 index 0000000..bba3643 --- /dev/null +++ b/linden/indra/llplugin/llpluginprocessparent.h | |||
@@ -0,0 +1,204 @@ | |||
1 | /** | ||
2 | * @file llpluginprocessparent.h | ||
3 | * @brief LLPluginProcessParent handles the parent side of the external-process plugin API. | ||
4 | * | ||
5 | * @cond | ||
6 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
7 | * | ||
8 | * Copyright (c) 2008-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_LLPLUGINPROCESSPARENT_H | ||
37 | #define LL_LLPLUGINPROCESSPARENT_H | ||
38 | |||
39 | #include <queue> //imprudence | ||
40 | |||
41 | #include "llapr.h" | ||
42 | #include "llprocesslauncher.h" | ||
43 | #include "llpluginmessage.h" | ||
44 | #include "llpluginmessagepipe.h" | ||
45 | #include "llpluginsharedmemory.h" | ||
46 | |||
47 | #include "lliosocket.h" | ||
48 | #include "llthread.h" | ||
49 | |||
50 | class LLPluginProcessParentOwner | ||
51 | { | ||
52 | public: | ||
53 | virtual ~LLPluginProcessParentOwner(); | ||
54 | virtual void receivePluginMessage(const LLPluginMessage &message) = 0; | ||
55 | virtual bool receivePluginMessageEarly(const LLPluginMessage &message) {return false;}; | ||
56 | // This will only be called when the plugin has died unexpectedly | ||
57 | virtual void pluginLaunchFailed() {}; | ||
58 | virtual void pluginDied() {}; | ||
59 | }; | ||
60 | |||
61 | class LLPluginProcessParent : public LLPluginMessagePipeOwner | ||
62 | { | ||
63 | LOG_CLASS(LLPluginProcessParent); | ||
64 | public: | ||
65 | LLPluginProcessParent(LLPluginProcessParentOwner *owner); | ||
66 | ~LLPluginProcessParent(); | ||
67 | |||
68 | void init(const std::string &launcher_filename, | ||
69 | const std::string &plugin_filename, | ||
70 | bool debug); | ||
71 | |||
72 | void idle(void); | ||
73 | |||
74 | // returns true if the plugin is on its way to steady state | ||
75 | bool isLoading(void); | ||
76 | |||
77 | // returns true if the plugin is in the steady state (processing messages) | ||
78 | bool isRunning(void); | ||
79 | |||
80 | // returns true if the process has exited or we've had a fatal error | ||
81 | bool isDone(void); | ||
82 | |||
83 | // returns true if the process is currently waiting on a blocking request | ||
84 | bool isBlocked(void) { return mBlocked; }; | ||
85 | |||
86 | void killSockets(void); | ||
87 | |||
88 | // Go to the proper error state | ||
89 | void errorState(void); | ||
90 | |||
91 | void setSleepTime(F64 sleep_time, bool force_send = false); | ||
92 | F64 getSleepTime(void) const { return mSleepTime; }; | ||
93 | |||
94 | void sendMessage(const LLPluginMessage &message); | ||
95 | |||
96 | void receiveMessage(const LLPluginMessage &message); | ||
97 | |||
98 | // Inherited from LLPluginMessagePipeOwner | ||
99 | /*virtual*/ void receiveMessageRaw(const std::string &message); | ||
100 | /*virtual*/ void receiveMessageEarly(const LLPluginMessage &message); | ||
101 | /*virtual*/ void setMessagePipe(LLPluginMessagePipe *message_pipe) ; | ||
102 | |||
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. | ||
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. | ||
105 | std::string addSharedMemory(size_t size); | ||
106 | // Negotiates for the removal of a shared memory segment. It is the caller's responsibility to ensure that nothing touches the memory | ||
107 | // after this has been called, since the segment will be unmapped shortly thereafter. | ||
108 | void removeSharedMemory(const std::string &name); | ||
109 | size_t getSharedMemorySize(const std::string &name); | ||
110 | void *getSharedMemoryAddress(const std::string &name); | ||
111 | |||
112 | // Returns the version string the plugin indicated for the message class, or an empty string if that class wasn't in the list. | ||
113 | std::string getMessageClassVersion(const std::string &message_class); | ||
114 | |||
115 | std::string getPluginVersion(void); | ||
116 | |||
117 | bool getDisableTimeout() { return mDisableTimeout; }; | ||
118 | void setDisableTimeout(bool disable) { mDisableTimeout = disable; }; | ||
119 | |||
120 | void setLaunchTimeout(F32 timeout) { mPluginLaunchTimeout = timeout; }; | ||
121 | void setLockupTimeout(F32 timeout) { mPluginLockupTimeout = timeout; }; | ||
122 | |||
123 | F64 getCPUUsage() { return mCPUUsage; }; | ||
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; }; | ||
129 | private: | ||
130 | |||
131 | enum EState | ||
132 | { | ||
133 | STATE_UNINITIALIZED, | ||
134 | STATE_INITIALIZED, // init() has been called | ||
135 | STATE_LISTENING, // listening for incoming connection | ||
136 | STATE_LAUNCHED, // process has been launched | ||
137 | STATE_CONNECTED, // process has connected | ||
138 | STATE_HELLO, // first message from the plugin process has been received | ||
139 | STATE_LOADING, // process has been asked to load the plugin | ||
140 | STATE_RUNNING, // | ||
141 | STATE_LAUNCH_FAILURE, // Failure before plugin loaded | ||
142 | STATE_ERROR, // generic bailout state | ||
143 | STATE_CLEANUP, // clean everything up | ||
144 | STATE_EXITING, // Tried to kill process, waiting for it to exit | ||
145 | STATE_DONE // | ||
146 | |||
147 | }; | ||
148 | EState mState; | ||
149 | void setState(EState state); | ||
150 | std::string stateToString(EState state); | ||
151 | |||
152 | bool pluginLockedUp(); | ||
153 | bool pluginLockedUpOrQuit(); | ||
154 | |||
155 | bool accept(); | ||
156 | |||
157 | LLSocket::ptr_t mListenSocket; | ||
158 | LLSocket::ptr_t mSocket; | ||
159 | U32 mBoundPort; | ||
160 | |||
161 | LLProcessLauncher mProcess; | ||
162 | |||
163 | std::string mPluginFile; | ||
164 | |||
165 | LLPluginProcessParentOwner *mOwner; | ||
166 | |||
167 | typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType; | ||
168 | sharedMemoryRegionsType mSharedMemoryRegions; | ||
169 | |||
170 | LLSD mMessageClassVersions; | ||
171 | std::string mPluginVersionString; | ||
172 | |||
173 | LLTimer mHeartbeat; | ||
174 | F64 mSleepTime; | ||
175 | F64 mCPUUsage; | ||
176 | |||
177 | bool mDisableTimeout; | ||
178 | bool mDebug; | ||
179 | bool mBlocked; | ||
180 | bool mPolledInput; | ||
181 | |||
182 | LLProcessLauncher mDebugger; | ||
183 | |||
184 | F32 mPluginLaunchTimeout; // Somewhat longer timeout for initial launch. | ||
185 | F32 mPluginLockupTimeout; // If we don't receive a heartbeat in this many seconds, we declare the plugin locked up. | ||
186 | |||
187 | static bool sUseReadThread; | ||
188 | apr_pollfd_t mPollFD; | ||
189 | AIAPRPool mPollFDPool; | ||
190 | static apr_pollset_t *sPollSet; | ||
191 | static AIAPRPool sPollSetPool; | ||
192 | static bool sPollsetNeedsRebuild; | ||
193 | static LLMutex *sInstancesMutex; | ||
194 | static std::list<LLPluginProcessParent*> sInstances; | ||
195 | static void dirtyPollSet(); | ||
196 | static void updatePollset(); | ||
197 | void servicePoll(); | ||
198 | static LLThread *sReadThread; | ||
199 | |||
200 | LLMutex mIncomingQueueMutex; | ||
201 | std::queue<LLPluginMessage> mIncomingQueue; | ||
202 | }; | ||
203 | |||
204 | #endif // LL_LLPLUGINPROCESSPARENT_H | ||
diff --git a/linden/indra/llplugin/llpluginsharedmemory.cpp b/linden/indra/llplugin/llpluginsharedmemory.cpp new file mode 100755 index 0000000..882a2a1 --- /dev/null +++ b/linden/indra/llplugin/llpluginsharedmemory.cpp | |||
@@ -0,0 +1,522 @@ | |||
1 | /** | ||
2 | * @file llpluginsharedmemory.cpp | ||
3 | * LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API. | ||
4 | * | ||
5 | * @cond | ||
6 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
7 | * | ||
8 | * Copyright (c) 2008-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 | /// IMPRUDENCE: this is part of the viewer and the SLPlugin | ||
37 | |||
38 | #include "linden_common.h" | ||
39 | |||
40 | #include "llpluginsharedmemory.h" | ||
41 | |||
42 | #if LL_WINDOWS | ||
43 | #include <process.h> | ||
44 | #else // LL_WINDOWS | ||
45 | #include <sys/types.h> | ||
46 | #include <unistd.h> | ||
47 | #endif // LL_WINDOWS | ||
48 | |||
49 | // on Mac and Linux, we use the native shm_open/mmap interface by using | ||
50 | // #define USE_SHM_OPEN_SHARED_MEMORY 1 | ||
51 | // in the appropriate sections below. | ||
52 | |||
53 | // For Windows, use: | ||
54 | // #define USE_WIN32_SHARED_MEMORY 1 | ||
55 | |||
56 | // If we ever want to fall back to the apr implementation for a platform, use: | ||
57 | // #define USE_APR_SHARED_MEMORY 1 | ||
58 | |||
59 | #if LL_WINDOWS | ||
60 | // #define USE_APR_SHARED_MEMORY 1 | ||
61 | #define USE_WIN32_SHARED_MEMORY 1 | ||
62 | #elif LL_DARWIN | ||
63 | #define USE_SHM_OPEN_SHARED_MEMORY 1 | ||
64 | #elif LL_LINUX | ||
65 | #define USE_SHM_OPEN_SHARED_MEMORY 1 | ||
66 | #endif | ||
67 | |||
68 | |||
69 | // FIXME: This path thing is evil and unacceptable. | ||
70 | #if LL_WINDOWS | ||
71 | #define APR_SHARED_MEMORY_PREFIX_STRING "C:\\LLPlugin_" | ||
72 | // Apparnently using the "Global\\" prefix here only works from administrative accounts under Vista. | ||
73 | // Other options I've seen referenced are "Local\\" and "Session\\". | ||
74 | #define WIN32_SHARED_MEMORY_PREFIX_STRING "Local\\LL_" | ||
75 | #else | ||
76 | // mac and linux | ||
77 | #define APR_SHARED_MEMORY_PREFIX_STRING "/tmp/LLPlugin_" | ||
78 | #define SHM_OPEN_SHARED_MEMORY_PREFIX_STRING "/LL" | ||
79 | #endif | ||
80 | |||
81 | #if USE_APR_SHARED_MEMORY | ||
82 | #include "llapr.h" | ||
83 | #include "apr_shm.h" | ||
84 | #elif USE_SHM_OPEN_SHARED_MEMORY | ||
85 | #include <sys/fcntl.h> | ||
86 | #include <sys/mman.h> | ||
87 | #include <errno.h> | ||
88 | #elif USE_WIN32_SHARED_MEMORY | ||
89 | # define WIN32_LEAN_AND_MEAN | ||
90 | # include <winsock2.h> | ||
91 | #include <windows.h> | ||
92 | #endif // USE_APR_SHARED_MEMORY | ||
93 | |||
94 | int LLPluginSharedMemory::sSegmentNumber = 0; | ||
95 | |||
96 | std::string LLPluginSharedMemory::createName(void) | ||
97 | { | ||
98 | std::stringstream newname; | ||
99 | |||
100 | #if LL_WINDOWS | ||
101 | newname << GetCurrentProcessId(); | ||
102 | #else // LL_WINDOWS | ||
103 | newname << getpid(); | ||
104 | #endif // LL_WINDOWS | ||
105 | |||
106 | newname << "_" << sSegmentNumber++; | ||
107 | |||
108 | return newname.str(); | ||
109 | } | ||
110 | |||
111 | /** | ||
112 | * @brief LLPluginSharedMemoryImpl is the platform-dependent implementation of LLPluginSharedMemory. TODO:DOC is this necessary/sufficient? kinda obvious. | ||
113 | * | ||
114 | */ | ||
115 | class LLPluginSharedMemoryPlatformImpl | ||
116 | { | ||
117 | public: | ||
118 | LLPluginSharedMemoryPlatformImpl(); | ||
119 | ~LLPluginSharedMemoryPlatformImpl(); | ||
120 | |||
121 | #if USE_APR_SHARED_MEMORY | ||
122 | apr_shm_t* mAprSharedMemory; | ||
123 | #elif USE_SHM_OPEN_SHARED_MEMORY | ||
124 | int mSharedMemoryFD; | ||
125 | #elif USE_WIN32_SHARED_MEMORY | ||
126 | HANDLE mMapFile; | ||
127 | #endif | ||
128 | |||
129 | }; | ||
130 | |||
131 | /** | ||
132 | * Constructor. Creates a shared memory segment. | ||
133 | */ | ||
134 | LLPluginSharedMemory::LLPluginSharedMemory() | ||
135 | { | ||
136 | mSize = 0; | ||
137 | mMappedAddress = NULL; | ||
138 | mNeedsDestroy = false; | ||
139 | |||
140 | mImpl = new LLPluginSharedMemoryPlatformImpl; | ||
141 | } | ||
142 | |||
143 | /** | ||
144 | * Destructor. Uses destroy() and detach() to ensure shared memory segment is cleaned up. | ||
145 | */ | ||
146 | LLPluginSharedMemory::~LLPluginSharedMemory() | ||
147 | { | ||
148 | if(mNeedsDestroy) | ||
149 | destroy(); | ||
150 | else | ||
151 | detach(); | ||
152 | |||
153 | unlink(); | ||
154 | |||
155 | delete mImpl; | ||
156 | } | ||
157 | |||
158 | #if USE_APR_SHARED_MEMORY | ||
159 | // MARK: apr implementation | ||
160 | |||
161 | LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl() | ||
162 | { | ||
163 | mAprSharedMemory = NULL; | ||
164 | } | ||
165 | |||
166 | LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl() | ||
167 | { | ||
168 | |||
169 | } | ||
170 | |||
171 | bool LLPluginSharedMemory::map(void) | ||
172 | { | ||
173 | mMappedAddress = apr_shm_baseaddr_get(mImpl->mAprSharedMemory); | ||
174 | if(mMappedAddress == NULL) | ||
175 | { | ||
176 | return false; | ||
177 | } | ||
178 | |||
179 | return true; | ||
180 | } | ||
181 | |||
182 | bool LLPluginSharedMemory::unmap(void) | ||
183 | { | ||
184 | // This is a no-op under apr. | ||
185 | return true; | ||
186 | } | ||
187 | |||
188 | bool LLPluginSharedMemory::close(void) | ||
189 | { | ||
190 | // This is a no-op under apr. | ||
191 | return true; | ||
192 | } | ||
193 | |||
194 | bool LLPluginSharedMemory::unlink(void) | ||
195 | { | ||
196 | // This is a no-op under apr. | ||
197 | return true; | ||
198 | } | ||
199 | |||
200 | |||
201 | bool LLPluginSharedMemory::create(size_t size) | ||
202 | { | ||
203 | mName = APR_SHARED_MEMORY_PREFIX_STRING; | ||
204 | mName += createName(); | ||
205 | mSize = size; | ||
206 | |||
207 | mPool.create(); | ||
208 | apr_status_t status = apr_shm_create( &(mImpl->mAprSharedMemory), mSize, mName.c_str(), mPool()); | ||
209 | |||
210 | if(ll_apr_warn_status(status)) | ||
211 | { | ||
212 | return false; | ||
213 | } | ||
214 | |||
215 | mNeedsDestroy = true; | ||
216 | |||
217 | return map(); | ||
218 | } | ||
219 | |||
220 | bool LLPluginSharedMemory::destroy(void) | ||
221 | { | ||
222 | if(mImpl->mAprSharedMemory) | ||
223 | { | ||
224 | apr_status_t status = apr_shm_destroy(mImpl->mAprSharedMemory); | ||
225 | if(ll_apr_warn_status(status)) | ||
226 | { | ||
227 | // TODO: Is this a fatal error? I think not... | ||
228 | } | ||
229 | mImpl->mAprSharedMemory = NULL; | ||
230 | } | ||
231 | mPool.destroy(); | ||
232 | return true; | ||
233 | } | ||
234 | |||
235 | bool LLPluginSharedMemory::attach(const std::string &name, size_t size) | ||
236 | { | ||
237 | mName = name; | ||
238 | mSize = size; | ||
239 | |||
240 | mPool.create(); | ||
241 | apr_status_t status = apr_shm_attach( &(mImpl->mAprSharedMemory), mName.c_str(), mPool() ); | ||
242 | |||
243 | if(ll_apr_warn_status(status)) | ||
244 | { | ||
245 | return false; | ||
246 | } | ||
247 | |||
248 | return map(); | ||
249 | } | ||
250 | |||
251 | |||
252 | bool LLPluginSharedMemory::detach(void) | ||
253 | { | ||
254 | if(mImpl->mAprSharedMemory) | ||
255 | { | ||
256 | apr_status_t status = apr_shm_detach(mImpl->mAprSharedMemory); | ||
257 | if(ll_apr_warn_status(status)) | ||
258 | { | ||
259 | // TODO: Is this a fatal error? I think not... | ||
260 | } | ||
261 | mImpl->mAprSharedMemory = NULL; | ||
262 | } | ||
263 | mPool.destroy(); | ||
264 | |||
265 | return true; | ||
266 | } | ||
267 | |||
268 | |||
269 | #elif USE_SHM_OPEN_SHARED_MEMORY | ||
270 | // MARK: shm_open/mmap implementation | ||
271 | |||
272 | LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl() | ||
273 | { | ||
274 | mSharedMemoryFD = -1; | ||
275 | } | ||
276 | |||
277 | LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl() | ||
278 | { | ||
279 | } | ||
280 | |||
281 | bool LLPluginSharedMemory::map(void) | ||
282 | { | ||
283 | mMappedAddress = ::mmap(NULL, mSize, PROT_READ | PROT_WRITE, MAP_SHARED, mImpl->mSharedMemoryFD, 0); | ||
284 | if(mMappedAddress == NULL) | ||
285 | { | ||
286 | return false; | ||
287 | } | ||
288 | |||
289 | LL_DEBUGS("Plugin") << "memory mapped at " << mMappedAddress << LL_ENDL; | ||
290 | |||
291 | return true; | ||
292 | } | ||
293 | |||
294 | bool LLPluginSharedMemory::unmap(void) | ||
295 | { | ||
296 | if(mMappedAddress != NULL) | ||
297 | { | ||
298 | LL_DEBUGS("Plugin") << "calling munmap(" << mMappedAddress << ", " << mSize << ")" << LL_ENDL; | ||
299 | if(::munmap(mMappedAddress, mSize) == -1) | ||
300 | { | ||
301 | // TODO: Is this a fatal error? I think not... | ||
302 | } | ||
303 | |||
304 | mMappedAddress = NULL; | ||
305 | } | ||
306 | |||
307 | return true; | ||
308 | } | ||
309 | |||
310 | bool LLPluginSharedMemory::close(void) | ||
311 | { | ||
312 | if(mImpl->mSharedMemoryFD != -1) | ||
313 | { | ||
314 | LL_DEBUGS("Plugin") << "calling close(" << mImpl->mSharedMemoryFD << ")" << LL_ENDL; | ||
315 | if(::close(mImpl->mSharedMemoryFD) == -1) | ||
316 | { | ||
317 | // TODO: Is this a fatal error? I think not... | ||
318 | } | ||
319 | |||
320 | mImpl->mSharedMemoryFD = -1; | ||
321 | } | ||
322 | return true; | ||
323 | } | ||
324 | |||
325 | bool LLPluginSharedMemory::unlink(void) | ||
326 | { | ||
327 | if(!mName.empty()) | ||
328 | { | ||
329 | if(::shm_unlink(mName.c_str()) == -1) | ||
330 | { | ||
331 | return false; | ||
332 | } | ||
333 | } | ||
334 | |||
335 | return true; | ||
336 | } | ||
337 | |||
338 | |||
339 | bool LLPluginSharedMemory::create(size_t size) | ||
340 | { | ||
341 | mName = SHM_OPEN_SHARED_MEMORY_PREFIX_STRING; | ||
342 | mName += createName(); | ||
343 | mSize = size; | ||
344 | |||
345 | // Preemptive unlink, just in case something didn't get cleaned up. | ||
346 | unlink(); | ||
347 | |||
348 | mImpl->mSharedMemoryFD = ::shm_open(mName.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); | ||
349 | if(mImpl->mSharedMemoryFD == -1) | ||
350 | { | ||
351 | return false; | ||
352 | } | ||
353 | |||
354 | mNeedsDestroy = true; | ||
355 | |||
356 | if(::ftruncate(mImpl->mSharedMemoryFD, mSize) == -1) | ||
357 | { | ||
358 | return false; | ||
359 | } | ||
360 | |||
361 | |||
362 | return map(); | ||
363 | } | ||
364 | |||
365 | bool LLPluginSharedMemory::destroy(void) | ||
366 | { | ||
367 | unmap(); | ||
368 | close(); | ||
369 | |||
370 | return true; | ||
371 | } | ||
372 | |||
373 | |||
374 | bool LLPluginSharedMemory::attach(const std::string &name, size_t size) | ||
375 | { | ||
376 | mName = name; | ||
377 | mSize = size; | ||
378 | |||
379 | mImpl->mSharedMemoryFD = ::shm_open(mName.c_str(), O_RDWR, S_IRUSR | S_IWUSR); | ||
380 | if(mImpl->mSharedMemoryFD == -1) | ||
381 | { | ||
382 | return false; | ||
383 | } | ||
384 | |||
385 | // unlink here so the segment will be cleaned up automatically after the last close. | ||
386 | unlink(); | ||
387 | |||
388 | return map(); | ||
389 | } | ||
390 | |||
391 | bool LLPluginSharedMemory::detach(void) | ||
392 | { | ||
393 | unmap(); | ||
394 | close(); | ||
395 | return true; | ||
396 | } | ||
397 | |||
398 | #elif USE_WIN32_SHARED_MEMORY | ||
399 | // MARK: Win32 CreateFileMapping-based implementation | ||
400 | |||
401 | // Reference: http://msdn.microsoft.com/en-us/library/aa366551(VS.85).aspx | ||
402 | |||
403 | LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl() | ||
404 | { | ||
405 | mMapFile = NULL; | ||
406 | } | ||
407 | |||
408 | LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl() | ||
409 | { | ||
410 | |||
411 | } | ||
412 | |||
413 | bool LLPluginSharedMemory::map(void) | ||
414 | { | ||
415 | mMappedAddress = MapViewOfFile( | ||
416 | mImpl->mMapFile, // handle to map object | ||
417 | FILE_MAP_ALL_ACCESS, // read/write permission | ||
418 | 0, | ||
419 | 0, | ||
420 | mSize); | ||
421 | |||
422 | if(mMappedAddress == NULL) | ||
423 | { | ||
424 | LL_WARNS("Plugin") << "MapViewOfFile failed: " << GetLastError() << LL_ENDL; | ||
425 | return false; | ||
426 | } | ||
427 | |||
428 | LL_DEBUGS("Plugin") << "memory mapped at " << mMappedAddress << LL_ENDL; | ||
429 | |||
430 | return true; | ||
431 | } | ||
432 | |||
433 | bool LLPluginSharedMemory::unmap(void) | ||
434 | { | ||
435 | if(mMappedAddress != NULL) | ||
436 | { | ||
437 | UnmapViewOfFile(mMappedAddress); | ||
438 | mMappedAddress = NULL; | ||
439 | } | ||
440 | |||
441 | return true; | ||
442 | } | ||
443 | |||
444 | bool LLPluginSharedMemory::close(void) | ||
445 | { | ||
446 | if(mImpl->mMapFile != NULL) | ||
447 | { | ||
448 | CloseHandle(mImpl->mMapFile); | ||
449 | mImpl->mMapFile = NULL; | ||
450 | } | ||
451 | |||
452 | return true; | ||
453 | } | ||
454 | |||
455 | bool LLPluginSharedMemory::unlink(void) | ||
456 | { | ||
457 | // This is a no-op on Windows. | ||
458 | return true; | ||
459 | } | ||
460 | |||
461 | |||
462 | bool LLPluginSharedMemory::create(size_t size) | ||
463 | { | ||
464 | mName = WIN32_SHARED_MEMORY_PREFIX_STRING; | ||
465 | mName += createName(); | ||
466 | mSize = size; | ||
467 | |||
468 | mImpl->mMapFile = CreateFileMappingA( | ||
469 | INVALID_HANDLE_VALUE, // use paging file | ||
470 | NULL, // default security | ||
471 | PAGE_READWRITE, // read/write access | ||
472 | 0, // max. object size | ||
473 | mSize, // buffer size | ||
474 | mName.c_str()); // name of mapping object | ||
475 | |||
476 | if(mImpl->mMapFile == NULL) | ||
477 | { | ||
478 | LL_WARNS("Plugin") << "CreateFileMapping failed: " << GetLastError() << LL_ENDL; | ||
479 | return false; | ||
480 | } | ||
481 | |||
482 | mNeedsDestroy = true; | ||
483 | |||
484 | return map(); | ||
485 | } | ||
486 | |||
487 | bool LLPluginSharedMemory::destroy(void) | ||
488 | { | ||
489 | unmap(); | ||
490 | close(); | ||
491 | return true; | ||
492 | } | ||
493 | |||
494 | bool LLPluginSharedMemory::attach(const std::string &name, size_t size) | ||
495 | { | ||
496 | mName = name; | ||
497 | mSize = size; | ||
498 | |||
499 | mImpl->mMapFile = OpenFileMappingA( | ||
500 | FILE_MAP_ALL_ACCESS, // read/write access | ||
501 | FALSE, // do not inherit the name | ||
502 | mName.c_str()); // name of mapping object | ||
503 | |||
504 | if(mImpl->mMapFile == NULL) | ||
505 | { | ||
506 | LL_WARNS("Plugin") << "OpenFileMapping failed: " << GetLastError() << LL_ENDL; | ||
507 | return false; | ||
508 | } | ||
509 | |||
510 | return map(); | ||
511 | } | ||
512 | |||
513 | bool LLPluginSharedMemory::detach(void) | ||
514 | { | ||
515 | unmap(); | ||
516 | close(); | ||
517 | return true; | ||
518 | } | ||
519 | |||
520 | |||
521 | |||
522 | #endif | ||
diff --git a/linden/indra/llplugin/llpluginsharedmemory.h b/linden/indra/llplugin/llpluginsharedmemory.h new file mode 100755 index 0000000..669a3e4 --- /dev/null +++ b/linden/indra/llplugin/llpluginsharedmemory.h | |||
@@ -0,0 +1,135 @@ | |||
1 | /** | ||
2 | * @file llpluginsharedmemory.h | ||
3 | * | ||
4 | * @cond | ||
5 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
6 | * | ||
7 | * Copyright (c) 2008-2010, Linden Research, Inc. | ||
8 | * | ||
9 | * Second Life Viewer Source Code | ||
10 | * The source code in this file ("Source Code") is provided by Linden Lab | ||
11 | * to you under the terms of the GNU General Public License, version 2.0 | ||
12 | * ("GPL"), unless you have obtained a separate licensing agreement | ||
13 | * ("Other License"), formally executed by you and Linden Lab. Terms of | ||
14 | * the GPL can be found in doc/GPL-license.txt in this distribution, or | ||
15 | * online at http://secondlife.com/developers/opensource/gplv2 | ||
16 | * | ||
17 | * There are special exceptions to the terms and conditions of the GPL as | ||
18 | * it is applied to this Source Code. View the full text of the exception | ||
19 | * in the file doc/FLOSS-exception.txt in this software distribution, or | ||
20 | * online at | ||
21 | * http://secondlife.com/developers/opensource/flossexception | ||
22 | * | ||
23 | * By copying, modifying or distributing this software, you acknowledge | ||
24 | * that you have read and understood your obligations described above, | ||
25 | * and agree to abide by those obligations. | ||
26 | * | ||
27 | * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO | ||
28 | * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, | ||
29 | * COMPLETENESS OR PERFORMANCE. | ||
30 | * $/LicenseInfo$ | ||
31 | * | ||
32 | * @endcond | ||
33 | */ | ||
34 | |||
35 | #ifndef LL_LLPLUGINSHAREDMEMORY_H | ||
36 | #define LL_LLPLUGINSHAREDMEMORY_H | ||
37 | |||
38 | #include "aiaprpool.h" | ||
39 | |||
40 | class LLPluginSharedMemoryPlatformImpl; | ||
41 | |||
42 | /** | ||
43 | * @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API. | ||
44 | * | ||
45 | */ | ||
46 | class LLPluginSharedMemory | ||
47 | { | ||
48 | LOG_CLASS(LLPluginSharedMemory); | ||
49 | public: | ||
50 | LLPluginSharedMemory(); | ||
51 | ~LLPluginSharedMemory(); | ||
52 | |||
53 | // Parent will use create/destroy, child will use attach/detach. | ||
54 | // Message transactions will ensure child attaches after parent creates and detaches before parent destroys. | ||
55 | |||
56 | /** | ||
57 | * Creates a shared memory segment, with a name which is guaranteed to be unique on the host at the current time. Used by parent. | ||
58 | * Message transactions will (? TODO:DOC - should? must?) ensure child attaches after parent creates and detaches before parent destroys. | ||
59 | * | ||
60 | * @param[in] size Shared memory size in TODO:DOC units = bytes?. | ||
61 | * | ||
62 | * @return False for failure, true for success. | ||
63 | */ | ||
64 | bool create(size_t size); | ||
65 | /** | ||
66 | * Destroys a shared memory segment. Used by parent. | ||
67 | * Message transactions will (? TODO:DOC - should? must?) ensure child attaches after parent creates and detaches before parent destroys. | ||
68 | * | ||
69 | * @return True. TODO:DOC - always returns true. Is this the intended behavior? | ||
70 | */ | ||
71 | bool destroy(void); | ||
72 | |||
73 | /** | ||
74 | * Creates and attaches a name to a shared memory segment. TODO:DOC what's the difference between attach() and create()? | ||
75 | * | ||
76 | * @param[in] name Name to attach to memory segment | ||
77 | * @param[in] size Size of memory segment TODO:DOC in bytes? | ||
78 | * | ||
79 | * @return False on failure, true otherwise. | ||
80 | */ | ||
81 | bool attach(const std::string &name, size_t size); | ||
82 | /** | ||
83 | * Detaches shared memory segment. | ||
84 | * | ||
85 | * @return False on failure, true otherwise. | ||
86 | */ | ||
87 | bool detach(void); | ||
88 | |||
89 | /** | ||
90 | * Checks if shared memory is mapped to a non-null address. | ||
91 | * | ||
92 | * @return True if memory address is non-null, false otherwise. | ||
93 | */ | ||
94 | bool isMapped(void) const { return (mMappedAddress != NULL); }; | ||
95 | /** | ||
96 | * Get pointer to shared memory. | ||
97 | * | ||
98 | * @return Pointer to shared memory. | ||
99 | */ | ||
100 | void *getMappedAddress(void) const { return mMappedAddress; }; | ||
101 | /** | ||
102 | * Get size of shared memory. | ||
103 | * | ||
104 | * @return Size of shared memory in bytes. TODO:DOC are bytes the correct unit? | ||
105 | */ | ||
106 | size_t getSize(void) const { return mSize; }; | ||
107 | /** | ||
108 | * Get name of shared memory. | ||
109 | * | ||
110 | * @return Name of shared memory. | ||
111 | */ | ||
112 | std::string getName() const { return mName; }; | ||
113 | |||
114 | private: | ||
115 | bool map(void); | ||
116 | bool unmap(void); | ||
117 | bool close(void); | ||
118 | bool unlink(void); | ||
119 | |||
120 | AIAPRPool mPool; | ||
121 | std::string mName; | ||
122 | size_t mSize; | ||
123 | void *mMappedAddress; | ||
124 | bool mNeedsDestroy; | ||
125 | |||
126 | LLPluginSharedMemoryPlatformImpl *mImpl; | ||
127 | |||
128 | static int sSegmentNumber; | ||
129 | static std::string createName(); | ||
130 | |||
131 | }; | ||
132 | |||
133 | |||
134 | |||
135 | #endif // LL_LLPLUGINSHAREDMEMORY_H | ||
diff --git a/linden/indra/llplugin/slplugin/CMakeLists.txt b/linden/indra/llplugin/slplugin/CMakeLists.txt new file mode 100755 index 0000000..81d9299 --- /dev/null +++ b/linden/indra/llplugin/slplugin/CMakeLists.txt | |||
@@ -0,0 +1,82 @@ | |||
1 | project(SLPlugin) | ||
2 | |||
3 | include(00-Common) | ||
4 | include(LLCommon) | ||
5 | include(LLPlugin) | ||
6 | include(Linking) | ||
7 | include(PluginAPI) | ||
8 | include(LLMessage) | ||
9 | |||
10 | include_directories( | ||
11 | ${LLPLUGIN_INCLUDE_DIRS} | ||
12 | ${LLMESSAGE_INCLUDE_DIRS} | ||
13 | ${LLCOMMON_INCLUDE_DIRS} | ||
14 | ) | ||
15 | |||
16 | if (DARWIN) | ||
17 | include(CMakeFindFrameworks) | ||
18 | find_library(CARBON_LIBRARY Carbon) | ||
19 | find_library(COCOA_LIBRARY Cocoa) | ||
20 | endif (DARWIN) | ||
21 | |||
22 | |||
23 | ### SLPlugin | ||
24 | |||
25 | set(SLPlugin_SOURCE_FILES | ||
26 | slplugin.cpp | ||
27 | ) | ||
28 | |||
29 | if (DARWIN) | ||
30 | list(APPEND SLPlugin_SOURCE_FILES | ||
31 | slplugin-objc.mm | ||
32 | ) | ||
33 | list(APPEND SLPlugin_HEADER_FILES | ||
34 | slplugin-objc.h | ||
35 | ) | ||
36 | endif (DARWIN) | ||
37 | |||
38 | set_source_files_properties(${SLPlugin_HEADER_FILES} | ||
39 | PROPERTIES HEADER_FILE_ONLY TRUE) | ||
40 | |||
41 | if (SLPlugin_HEADER_FILES) | ||
42 | list(APPEND SLPlugin_SOURCE_FILES ${SLPlugin_HEADER_FILES}) | ||
43 | endif (SLPlugin_HEADER_FILES) | ||
44 | |||
45 | add_executable(SLPlugin | ||
46 | WIN32 | ||
47 | MACOSX_BUNDLE | ||
48 | ${SLPlugin_SOURCE_FILES} | ||
49 | ) | ||
50 | |||
51 | set_target_properties(SLPlugin | ||
52 | PROPERTIES | ||
53 | MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/slplugin_info.plist | ||
54 | ) | ||
55 | |||
56 | target_link_libraries(SLPlugin | ||
57 | ${LLPLUGIN_LIBRARIES} | ||
58 | ${LLMESSAGE_LIBRARIES} | ||
59 | ${LLCOMMON_LIBRARIES} | ||
60 | ${PLUGIN_API_WINDOWS_LIBRARIES} | ||
61 | ) | ||
62 | |||
63 | add_dependencies(SLPlugin | ||
64 | ${LLPLUGIN_LIBRARIES} | ||
65 | ${LLMESSAGE_LIBRARIES} | ||
66 | ${LLCOMMON_LIBRARIES} | ||
67 | ) | ||
68 | |||
69 | if (DARWIN) | ||
70 | # Mac version needs to link against Carbon | ||
71 | target_link_libraries(SLPlugin ${CARBON_LIBRARY} ${COCOA_LIBRARY}) | ||
72 | # Make sure the app bundle has a Resources directory (it will get populated by viewer-manifest.py later) | ||
73 | add_custom_command( | ||
74 | TARGET SLPlugin POST_BUILD | ||
75 | COMMAND mkdir | ||
76 | ARGS | ||
77 | -p | ||
78 | ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/SLPlugin.app/Contents/Resources | ||
79 | ) | ||
80 | endif (DARWIN) | ||
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: */ | ||
40 | void setupCocoa(); | ||
41 | void createAutoReleasePool(); | ||
42 | void 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 | |||
44 | void 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 | |||
72 | static NSAutoreleasePool *sPool = NULL; | ||
73 | |||
74 | void createAutoReleasePool() | ||
75 | { | ||
76 | if(!sPool) | ||
77 | { | ||
78 | sPool = [[NSAutoreleasePool alloc] init]; | ||
79 | } | ||
80 | } | ||
81 | |||
82 | void 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 new file mode 100755 index 0000000..4ef24a2 --- /dev/null +++ b/linden/indra/llplugin/slplugin/slplugin.cpp | |||
@@ -0,0 +1,439 @@ | |||
1 | /** | ||
2 | * @file slplugin.cpp | ||
3 | * @brief Loader shell for plugins, intended to be launched by the plugin host application, which directly loads a plugin dynamic library. | ||
4 | * | ||
5 | * @cond | ||
6 | * | ||
7 | * $LicenseInfo:firstyear=2008&license=viewergpl$ | ||
8 | * | ||
9 | * Copyright (c) 2008-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 | /// IMPRUDENCE: this is part of the SLPlugin | ||
39 | |||
40 | #include "linden_common.h" | ||
41 | |||
42 | #include "llpluginprocesschild.h" | ||
43 | #include "llpluginmessage.h" | ||
44 | #include "llerrorcontrol.h" | ||
45 | #include "llapr.h" | ||
46 | #include "llstring.h" | ||
47 | |||
48 | #if LL_DARWIN | ||
49 | #include <Carbon/Carbon.h> | ||
50 | #include "slplugin-objc.h" | ||
51 | #endif | ||
52 | |||
53 | #if LL_DARWIN || LL_LINUX | ||
54 | #include <signal.h> | ||
55 | #endif | ||
56 | |||
57 | //imprudence: or we include lldir, or use apache runtime | ||
58 | //though the one is probably bloat and the other we rather want to avoid | ||
59 | #include <stdio.h> // FILENAME_MAX | ||
60 | #ifdef LL_WINDOWS | ||
61 | #include <direct.h> | ||
62 | #define getImpruDir _getcwd | ||
63 | #define DIR_DELIMITER "\\" | ||
64 | #else | ||
65 | #include <unistd.h> | ||
66 | #define getImpruDir getcwd | ||
67 | #define DIR_DELIMITER "/" | ||
68 | #endif | ||
69 | |||
70 | |||
71 | /* | ||
72 | 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. | ||
73 | |||
74 | 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: | ||
75 | |||
76 | -sectcreate __TEXT __info_plist /path/to/slplugin_info.plist | ||
77 | |||
78 | which means adding this to the gcc flags: | ||
79 | |||
80 | -Wl,-sectcreate,__TEXT,__info_plist,/path/to/slplugin_info.plist | ||
81 | |||
82 | 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. | ||
83 | */ | ||
84 | |||
85 | #if LL_DARWIN || LL_LINUX | ||
86 | // Signal handlers to make crashes not show an OS dialog... | ||
87 | static void crash_handler(int sig) | ||
88 | { | ||
89 | // Just exit cleanly. | ||
90 | // TODO: add our own crash reporting | ||
91 | _exit(1); | ||
92 | } | ||
93 | #endif | ||
94 | |||
95 | #if LL_WINDOWS | ||
96 | # define WIN32_LEAN_AND_MEAN | ||
97 | # include <winsock2.h> | ||
98 | #include <windows.h> | ||
99 | //////////////////////////////////////////////////////////////////////////////// | ||
100 | // Our exception handler - will probably just exit and the host application | ||
101 | // will miss the heartbeat and log the error in the usual fashion. | ||
102 | LONG WINAPI myWin32ExceptionHandler( struct _EXCEPTION_POINTERS* exception_infop ) | ||
103 | { | ||
104 | //std::cerr << "This plugin (" << __FILE__ << ") - "; | ||
105 | //std::cerr << "intercepted an unhandled exception and will exit immediately." << std::endl; | ||
106 | |||
107 | // TODO: replace exception handler before we exit? | ||
108 | return EXCEPTION_EXECUTE_HANDLER; | ||
109 | } | ||
110 | |||
111 | // Taken from : http://blog.kalmbachnet.de/?postid=75 | ||
112 | // The MSVC 2005 CRT forces the call of the default-debugger (normally Dr.Watson) | ||
113 | // even with the other exception handling code. This (terrifying) piece of code | ||
114 | // patches things so that doesn't happen. | ||
115 | LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter( | ||
116 | LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter ) | ||
117 | { | ||
118 | return NULL; | ||
119 | } | ||
120 | |||
121 | BOOL PreventSetUnhandledExceptionFilter() | ||
122 | { | ||
123 | // WARNING: This won't work on 64-bit Windows systems so we turn it off it. | ||
124 | // It should work for any flavor of 32-bit Windows we care about. | ||
125 | // If it's off, sometimes you will see an OS message when a plugin crashes | ||
126 | #ifndef _WIN64 | ||
127 | HMODULE hKernel32 = LoadLibraryA( "kernel32.dll" ); | ||
128 | if ( NULL == hKernel32 ) | ||
129 | return FALSE; | ||
130 | |||
131 | void *pOrgEntry = GetProcAddress( hKernel32, "SetUnhandledExceptionFilter" ); | ||
132 | if( NULL == pOrgEntry ) | ||
133 | return FALSE; | ||
134 | |||
135 | unsigned char newJump[ 100 ]; | ||
136 | DWORD dwOrgEntryAddr = (DWORD)pOrgEntry; | ||
137 | dwOrgEntryAddr += 5; // add 5 for 5 op-codes for jmp far | ||
138 | void *pNewFunc = &MyDummySetUnhandledExceptionFilter; | ||
139 | DWORD dwNewEntryAddr = (DWORD) pNewFunc; | ||
140 | DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr; | ||
141 | |||
142 | newJump[ 0 ] = 0xE9; // JMP absolute | ||
143 | memcpy( &newJump[ 1 ], &dwRelativeAddr, sizeof( pNewFunc ) ); | ||
144 | SIZE_T bytesWritten; | ||
145 | BOOL bRet = WriteProcessMemory( GetCurrentProcess(), pOrgEntry, newJump, sizeof( pNewFunc ) + 1, &bytesWritten ); | ||
146 | return bRet; | ||
147 | #else | ||
148 | return FALSE; | ||
149 | #endif | ||
150 | } | ||
151 | |||
152 | //////////////////////////////////////////////////////////////////////////////// | ||
153 | // Hook our exception handler and replace the system one | ||
154 | void initExceptionHandler() | ||
155 | { | ||
156 | LPTOP_LEVEL_EXCEPTION_FILTER prev_filter; | ||
157 | |||
158 | // save old exception handler in case we need to restore it at the end | ||
159 | prev_filter = SetUnhandledExceptionFilter( myWin32ExceptionHandler ); | ||
160 | PreventSetUnhandledExceptionFilter(); | ||
161 | } | ||
162 | |||
163 | bool checkExceptionHandler() | ||
164 | { | ||
165 | bool ok = true; | ||
166 | LPTOP_LEVEL_EXCEPTION_FILTER prev_filter; | ||
167 | prev_filter = SetUnhandledExceptionFilter(myWin32ExceptionHandler); | ||
168 | |||
169 | PreventSetUnhandledExceptionFilter(); | ||
170 | |||
171 | if (prev_filter != myWin32ExceptionHandler) | ||
172 | { | ||
173 | LL_WARNS("AppInit") << "Our exception handler (" << (void *)myWin32ExceptionHandler << ") replaced with " << prev_filter << "!" << LL_ENDL; | ||
174 | ok = false; | ||
175 | } | ||
176 | |||
177 | if (prev_filter == NULL) | ||
178 | { | ||
179 | ok = FALSE; | ||
180 | if (NULL == myWin32ExceptionHandler) | ||
181 | { | ||
182 | LL_WARNS("AppInit") << "Exception handler uninitialized." << LL_ENDL; | ||
183 | } | ||
184 | else | ||
185 | { | ||
186 | LL_WARNS("AppInit") << "Our exception handler (" << (void *)myWin32ExceptionHandler << ") replaced with NULL!" << LL_ENDL; | ||
187 | } | ||
188 | } | ||
189 | |||
190 | return ok; | ||
191 | } | ||
192 | #endif | ||
193 | |||
194 | // If this application on Windows platform is a console application, a console is always | ||
195 | // created which is bad. Making it a Windows "application" via CMake settings but not | ||
196 | // adding any code to explicitly create windows does the right thing. | ||
197 | #if LL_WINDOWS | ||
198 | int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) | ||
199 | #else | ||
200 | int main(int argc, char **argv) | ||
201 | #endif | ||
202 | { | ||
203 | // Set up llerror logging | ||
204 | { | ||
205 | std::string path; | ||
206 | char impruPath[FILENAME_MAX]; | ||
207 | |||
208 | if (!getImpruDir(impruPath, sizeof(impruPath))) | ||
209 | { | ||
210 | path = "."; //FIXME: root directory of the system - bad idea | ||
211 | } | ||
212 | else | ||
213 | { | ||
214 | path = std::string(impruPath); | ||
215 | |||
216 | path.append(DIR_DELIMITER); | ||
217 | path.append("app_settings"); | ||
218 | } | ||
219 | LLError::initForApplication(path); | ||
220 | // LLError::setDefaultLevel(LLError::LEVEL_INFO); | ||
221 | // LLError::setPrintLocation(true); | ||
222 | // LLError::setTagLevel("Plugin", LLError::LEVEL_DEBUG); | ||
223 | // LLError::setTagLevel("PluginPipe", LLError::LEVEL_DEBUG); | ||
224 | // LLError::setTagLevel("PluginChild", LLError::LEVEL_DEBUG); | ||
225 | // LLError::setTagLevel("PluginInstance", LLError::LEVEL_DEBUG); | ||
226 | |||
227 | // LLError::logToFile("slplugin.log"); | ||
228 | } | ||
229 | |||
230 | #if LL_WINDOWS | ||
231 | if( strlen( lpCmdLine ) == 0 ) | ||
232 | { | ||
233 | LL_ERRS("slplugin") << "usage: " << "SLPlugin" << " launcher_port" << LL_ENDL; | ||
234 | }; | ||
235 | |||
236 | U32 port = 0; | ||
237 | if(!LLStringUtil::convertToU32(lpCmdLine, port)) | ||
238 | { | ||
239 | LL_ERRS("slplugin") << "port number must be numeric" << LL_ENDL; | ||
240 | }; | ||
241 | |||
242 | // Insert our exception handler into the system so this plugin doesn't | ||
243 | // display a crash message if something bad happens. The host app will | ||
244 | // see the missing heartbeat and log appropriately. | ||
245 | initExceptionHandler(); | ||
246 | #elif LL_DARWIN || LL_LINUX | ||
247 | if(argc < 2) | ||
248 | { | ||
249 | LL_ERRS("slplugin") << "usage: " << argv[0] << " launcher_port" << LL_ENDL; | ||
250 | } | ||
251 | |||
252 | U32 port = 0; | ||
253 | if(!LLStringUtil::convertToU32(argv[1], port)) | ||
254 | { | ||
255 | LL_ERRS("slplugin") << "port number must be numeric" << LL_ENDL; | ||
256 | } | ||
257 | |||
258 | // Catch signals that most kinds of crashes will generate, and exit cleanly so the system crash dialog isn't shown. | ||
259 | signal(SIGILL, &crash_handler); // illegal instruction | ||
260 | # if LL_DARWIN | ||
261 | signal(SIGEMT, &crash_handler); // emulate instruction executed | ||
262 | # endif // LL_DARWIN | ||
263 | signal(SIGFPE, &crash_handler); // floating-point exception | ||
264 | signal(SIGBUS, &crash_handler); // bus error | ||
265 | signal(SIGSEGV, &crash_handler); // segmentation violation | ||
266 | signal(SIGSYS, &crash_handler); // non-existent system call invoked | ||
267 | #endif | ||
268 | |||
269 | #if LL_DARWIN | ||
270 | setupCocoa(); | ||
271 | createAutoReleasePool(); | ||
272 | #endif | ||
273 | |||
274 | LLPluginProcessChild *plugin = new LLPluginProcessChild(); | ||
275 | |||
276 | plugin->init(port); | ||
277 | |||
278 | #if LL_DARWIN | ||
279 | deleteAutoReleasePool(); | ||
280 | #endif | ||
281 | |||
282 | LLTimer timer; | ||
283 | timer.start(); | ||
284 | |||
285 | #if LL_WINDOWS | ||
286 | checkExceptionHandler(); | ||
287 | #endif | ||
288 | |||
289 | #if LL_DARWIN | ||
290 | // 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. | ||
291 | // Use this to track the current frontmost window and bring this process to the front if it changes. | ||
292 | WindowRef front_window = NULL; | ||
293 | WindowGroupRef layer_group = NULL; | ||
294 | int window_hack_state = 0; | ||
295 | CreateWindowGroup(kWindowGroupAttrFixedLevel, &layer_group); | ||
296 | if(layer_group) | ||
297 | { | ||
298 | // 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) | ||
299 | SetWindowGroupName(layer_group, CFSTR("SLPlugin Layer")); | ||
300 | SetWindowGroupLevel(layer_group, kCGOverlayWindowLevel); | ||
301 | } | ||
302 | #endif | ||
303 | |||
304 | #if LL_DARWIN | ||
305 | EventTargetRef event_target = GetEventDispatcherTarget(); | ||
306 | #endif | ||
307 | while(!plugin->isDone()) | ||
308 | { | ||
309 | #if LL_DARWIN | ||
310 | createAutoReleasePool(); | ||
311 | #endif | ||
312 | timer.reset(); | ||
313 | plugin->idle(); | ||
314 | #if LL_DARWIN | ||
315 | { | ||
316 | // Some plugins (webkit at least) will want an event loop. This qualifies. | ||
317 | EventRef event; | ||
318 | if(ReceiveNextEvent(0, 0, kEventDurationNoWait, true, &event) == noErr) | ||
319 | { | ||
320 | SendEventToEventTarget (event, event_target); | ||
321 | ReleaseEvent(event); | ||
322 | } | ||
323 | |||
324 | // Check for a change in this process's frontmost window. | ||
325 | if(FrontWindow() != front_window) | ||
326 | { | ||
327 | ProcessSerialNumber self = { 0, kCurrentProcess }; | ||
328 | ProcessSerialNumber parent = { 0, kNoProcess }; | ||
329 | ProcessSerialNumber front = { 0, kNoProcess }; | ||
330 | Boolean this_is_front_process = false; | ||
331 | Boolean parent_is_front_process = false; | ||
332 | { | ||
333 | // Get this process's parent | ||
334 | ProcessInfoRec info; | ||
335 | info.processInfoLength = sizeof(ProcessInfoRec); | ||
336 | info.processName = NULL; | ||
337 | info.processAppSpec = NULL; | ||
338 | if(GetProcessInformation( &self, &info ) == noErr) | ||
339 | { | ||
340 | parent = info.processLauncher; | ||
341 | } | ||
342 | |||
343 | // and figure out whether this process or its parent are currently frontmost | ||
344 | if(GetFrontProcess(&front) == noErr) | ||
345 | { | ||
346 | (void) SameProcess(&self, &front, &this_is_front_process); | ||
347 | (void) SameProcess(&parent, &front, &parent_is_front_process); | ||
348 | } | ||
349 | } | ||
350 | |||
351 | if((FrontWindow() != NULL) && (front_window == NULL)) | ||
352 | { | ||
353 | // Opening the first window | ||
354 | |||
355 | if(window_hack_state == 0) | ||
356 | { | ||
357 | // Next time through the event loop, lower the window group layer | ||
358 | window_hack_state = 1; | ||
359 | } | ||
360 | |||
361 | if(layer_group) | ||
362 | { | ||
363 | SetWindowGroup(FrontWindow(), layer_group); | ||
364 | } | ||
365 | |||
366 | if(parent_is_front_process) | ||
367 | { | ||
368 | // Bring this process's windows to the front. | ||
369 | (void) SetFrontProcess( &self ); | ||
370 | } | ||
371 | |||
372 | ActivateWindow(FrontWindow(), true); | ||
373 | } | ||
374 | else if((FrontWindow() == NULL) && (front_window != NULL)) | ||
375 | { | ||
376 | // Closing the last window | ||
377 | |||
378 | if(this_is_front_process) | ||
379 | { | ||
380 | // Try to bring this process's parent to the front | ||
381 | (void) SetFrontProcess(&parent); | ||
382 | } | ||
383 | } | ||
384 | else if(window_hack_state == 1) | ||
385 | { | ||
386 | if(layer_group) | ||
387 | { | ||
388 | // Set the window group level back to something less extreme | ||
389 | SetWindowGroupLevel(layer_group, kCGNormalWindowLevel); | ||
390 | } | ||
391 | window_hack_state = 2; | ||
392 | } | ||
393 | |||
394 | front_window = FrontWindow(); | ||
395 | |||
396 | } | ||
397 | } | ||
398 | #endif | ||
399 | F64 elapsed = timer.getElapsedTimeF64(); | ||
400 | F64 remaining = plugin->getSleepTime() - elapsed; | ||
401 | |||
402 | if(remaining <= 0.0f) | ||
403 | { | ||
404 | // We've already used our full allotment. | ||
405 | // LL_INFOS("slplugin") << "elapsed = " << elapsed * 1000.0f << " ms, remaining = " << remaining * 1000.0f << " ms, not sleeping" << LL_ENDL; | ||
406 | |||
407 | // Still need to service the network... | ||
408 | plugin->pump(); | ||
409 | } | ||
410 | else | ||
411 | { | ||
412 | |||
413 | // LL_INFOS("slplugin") << "elapsed = " << elapsed * 1000.0f << " ms, remaining = " << remaining * 1000.0f << " ms, sleeping for " << remaining * 1000.0f << " ms" << LL_ENDL; | ||
414 | // timer.reset(); | ||
415 | |||
416 | // This also services the network as needed. | ||
417 | plugin->sleep(remaining); | ||
418 | |||
419 | // LL_INFOS("slplugin") << "slept for "<< timer.getElapsedTimeF64() * 1000.0f << " ms" << LL_ENDL; | ||
420 | } | ||
421 | |||
422 | #if LL_WINDOWS | ||
423 | // More agressive checking of interfering exception handlers. | ||
424 | // Doesn't appear to be required so far - even for plugins | ||
425 | // that do crash with a single call to the intercept | ||
426 | // exception handler such as QuickTime. | ||
427 | //checkExceptionHandler(); | ||
428 | #endif | ||
429 | |||
430 | #if LL_DARWIN | ||
431 | deleteAutoReleasePool(); | ||
432 | #endif | ||
433 | } | ||
434 | |||
435 | delete plugin; | ||
436 | |||
437 | return 0; | ||
438 | } | ||
439 | |||
diff --git a/linden/indra/llplugin/slplugin/slplugin_info.plist b/linden/indra/llplugin/slplugin/slplugin_info.plist new file mode 100755 index 0000000..c459738 --- /dev/null +++ b/linden/indra/llplugin/slplugin/slplugin_info.plist | |||
@@ -0,0 +1,12 @@ | |||
1 | <?xml version="1.0" encoding="UTF-8"?> | ||
2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
3 | <plist version="1.0"> | ||
4 | <dict> | ||
5 | <key>CFBundleDevelopmentRegion</key> | ||
6 | <string>English</string> | ||
7 | <key>CFBundleInfoDictionaryVersion</key> | ||
8 | <string>6.0</string> | ||
9 | <key>LSUIElement</key> | ||
10 | <string>1</string> | ||
11 | </dict> | ||
12 | </plist> | ||