aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/llplugin
diff options
context:
space:
mode:
authorJacek Antonelli2011-05-08 15:13:37 -0500
committerJacek Antonelli2011-05-08 15:50:49 -0500
commit7278f0254a3944bd2bcbf1e855fb0d90c3086a27 (patch)
tree5d7ecb81ebf1a07482f0a7d3d13acd7f18360c0b /linden/indra/llplugin
parentImprudence 1.3.1 released. (diff)
parentChanged version to Experimental 2011.04.19 (diff)
downloadmeta-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')
-rw-r--r--linden/indra/llplugin/CMakeLists.txt82
-rwxr-xr-xlinden/indra/llplugin/llpluginclassmedia.cpp1236
-rwxr-xr-xlinden/indra/llplugin/llpluginclassmedia.h377
-rwxr-xr-xlinden/indra/llplugin/llpluginclassmediaowner.h87
-rw-r--r--linden/indra/llplugin/llplugincookiestore.cpp673
-rw-r--r--linden/indra/llplugin/llplugincookiestore.h127
-rwxr-xr-xlinden/indra/llplugin/llplugininstance.cpp176
-rwxr-xr-xlinden/indra/llplugin/llplugininstance.h106
-rwxr-xr-xlinden/indra/llplugin/llpluginmessage.cpp447
-rwxr-xr-xlinden/indra/llplugin/llpluginmessage.h144
-rwxr-xr-xlinden/indra/llplugin/llpluginmessageclasses.h60
-rwxr-xr-xlinden/indra/llplugin/llpluginmessagepipe.cpp387
-rwxr-xr-xlinden/indra/llplugin/llpluginmessagepipe.h100
-rwxr-xr-xlinden/indra/llplugin/llpluginprocesschild.cpp571
-rwxr-xr-xlinden/indra/llplugin/llpluginprocesschild.h121
-rwxr-xr-xlinden/indra/llplugin/llpluginprocessparent.cpp1161
-rwxr-xr-xlinden/indra/llplugin/llpluginprocessparent.h204
-rwxr-xr-xlinden/indra/llplugin/llpluginsharedmemory.cpp522
-rwxr-xr-xlinden/indra/llplugin/llpluginsharedmemory.h135
-rwxr-xr-xlinden/indra/llplugin/slplugin/CMakeLists.txt82
-rw-r--r--linden/indra/llplugin/slplugin/slplugin-objc.h42
-rw-r--r--linden/indra/llplugin/slplugin/slplugin-objc.mm89
-rwxr-xr-xlinden/indra/llplugin/slplugin/slplugin.cpp439
-rwxr-xr-xlinden/indra/llplugin/slplugin/slplugin_info.plist12
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
3project(llplugin)
4
5include(00-Common)
6include(CURL)
7include(LLCommon)
8include(LLImage)
9include(LLMath)
10include(LLMessage)
11include(LLRender)
12include(LLXML)
13include(LLWindow)
14
15include_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
25set(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
36set(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
51set_source_files_properties(${llplugin_HEADER_FILES}
52 PROPERTIES HEADER_FILE_ONLY TRUE)
53
54if(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)
60endif (NOT CMAKE_SIZEOF_VOID_P MATCHES 4)
61
62list(APPEND llplugin_SOURCE_FILES ${llplugin_HEADER_FILES})
63
64add_library (llplugin ${llplugin_SOURCE_FILES})
65
66add_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
44static int LOW_PRIORITY_TEXTURE_SIZE_DEFAULT = 256;
45
46static 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
57LLPluginClassMedia::LLPluginClassMedia(LLPluginClassMediaOwner *owner)
58{
59 mOwner = owner;
60 mPlugin = NULL;
61 reset();
62
63 //debug use
64 mDeleteOK = true ;
65}
66
67
68LLPluginClassMedia::~LLPluginClassMedia()
69{
70 llassert_always(mDeleteOK) ;
71 reset();
72}
73
74bool 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
92void 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
161void 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
276int LLPluginClassMedia::getTextureWidth() const
277{
278 return nextPowerOf2(mTextureWidth);
279}
280
281int LLPluginClassMedia::getTextureHeight() const
282{
283 return nextPowerOf2(mTextureHeight);
284}
285
286unsigned 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
296void 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
312void 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
367void LLPluginClassMedia::setAutoScale(bool auto_scale)
368{
369 if(auto_scale != mAutoScaleMedia)
370 {
371 mAutoScaleMedia = auto_scale;
372 setSizeInternal();
373 }
374}
375
376bool 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
393bool 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
405void LLPluginClassMedia::resetDirty(void)
406{
407 mDirtyRect = LLRect::null;
408}
409
410std::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
441void 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
489bool 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
554void 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
565bool 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
578void 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
587const 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
604void 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
654void 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
666F64 LLPluginClassMedia::getCPUUsage()
667{
668 F64 result = 0.0f;
669
670 if(mPlugin)
671 {
672 result = mPlugin->getCPUUsage();
673 }
674
675 return result;
676}
677
678void LLPluginClassMedia::cut()
679{
680 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_cut");
681 sendMessage(message);
682}
683
684void LLPluginClassMedia::copy()
685{
686 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_copy");
687 sendMessage(message);
688}
689
690void LLPluginClassMedia::paste()
691{
692 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_paste");
693 sendMessage(message);
694}
695
696void 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
703void 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
710void 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
717void 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 */
725void 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 */
1014void LLPluginClassMedia::pluginLaunchFailed()
1015{
1016 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PLUGIN_FAILED_LAUNCH);
1017}
1018
1019/* virtual */
1020void LLPluginClassMedia::pluginDied()
1021{
1022 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PLUGIN_FAILED);
1023}
1024
1025void LLPluginClassMedia::mediaEvent(LLPluginClassMediaOwner::EMediaEvent event)
1026{
1027 if(mOwner)
1028 {
1029 mOwner->handleMediaEvent(this, event);
1030 }
1031}
1032
1033void 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
1048bool LLPluginClassMedia::pluginSupportsMediaBrowser(void)
1049{
1050 std::string version = mPlugin->getMessageClassVersion(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER);
1051 return !version.empty();
1052}
1053
1054void 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
1063void LLPluginClassMedia::clear_cache()
1064{
1065 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "clear_cache");
1066 sendMessage(message);
1067}
1068
1069void LLPluginClassMedia::clear_cookies()
1070{
1071 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "clear_cookies");
1072 sendMessage(message);
1073}
1074
1075void 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
1082void 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
1089void 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
1100void LLPluginClassMedia::browse_stop()
1101{
1102 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_stop");
1103 sendMessage(message);
1104}
1105
1106void 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
1115void LLPluginClassMedia::browse_forward()
1116{
1117 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_forward");
1118 sendMessage(message);
1119}
1120
1121void LLPluginClassMedia::browse_back()
1122{
1123 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_back");
1124 sendMessage(message);
1125}
1126
1127void 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
1137void 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
1146void LLPluginClassMedia::crashPlugin()
1147{
1148 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "crash");
1149
1150 sendMessage(message);
1151}
1152
1153void 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
1163bool LLPluginClassMedia::pluginSupportsMediaTime(void)
1164{
1165 std::string version = mPlugin->getMessageClassVersion(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME);
1166 return !version.empty();
1167}
1168
1169void LLPluginClassMedia::stop()
1170{
1171 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "stop");
1172 sendMessage(message);
1173}
1174
1175void 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
1184void LLPluginClassMedia::pause()
1185{
1186 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "pause");
1187 sendMessage(message);
1188}
1189
1190void 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
1199void 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
1208void 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
1222float LLPluginClassMedia::getVolume()
1223{
1224 return mRequestedVolume;
1225}
1226
1227void 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
46class LLPluginClassMedia : public LLPluginProcessParentOwner
47{
48 LOG_CLASS(LLPluginClassMedia);
49public:
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
258protected:
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 //
370private:
371 bool mDeleteOK ;
372public:
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
43class LLPluginClassMedia;
44//class LLPluginCookieStore; // IMPRUDENCE: this is currently not used
45
46class LLPluginClassMediaOwner
47{
48public:
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
47LLPluginCookieStore::LLPluginCookieStore():
48 mHasChangedCookies(false)
49{
50}
51
52
53LLPluginCookieStore::~LLPluginCookieStore()
54{
55 clearCookies();
56}
57
58
59LLPluginCookieStore::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
69LLPluginCookieStore::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
82std::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
99bool 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
307std::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
337bool 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
354std::string LLPluginCookieStore::getAllCookies()
355{
356 std::stringstream result;
357 writeAllCookies(result);
358 return result.str();
359}
360
361void 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
375std::string LLPluginCookieStore::getPersistentCookies()
376{
377 std::stringstream result;
378 writePersistentCookies(result);
379 return result.str();
380}
381
382void 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
395std::string LLPluginCookieStore::getChangedCookies(bool clear_changed)
396{
397 std::stringstream result;
398 writeChangedCookies(result, clear_changed);
399
400 return result.str();
401}
402
403void 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
446void LLPluginCookieStore::setAllCookies(const std::string &cookies, bool mark_changed)
447{
448 clearCookies();
449 setCookies(cookies, mark_changed);
450}
451
452void LLPluginCookieStore::readAllCookies(std::istream& s, bool mark_changed)
453{
454 clearCookies();
455 readCookies(s, mark_changed);
456}
457
458void 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
474void 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
490void 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
504std::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
532std::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.
573void 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
654void 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
664void 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
44class LLPluginCookieStore
45{
46 LOG_CLASS(LLPluginCookieStore);
47public:
48 LLPluginCookieStore();
49 ~LLPluginCookieStore();
50
51 // gets all cookies currently in storage -- use when initializing a plugin
52 std::string getAllCookies();
53 void writeAllCookies(std::ostream& s);
54
55 // gets only persistent cookies (i.e. not session cookies) -- use when writing cookies to a file
56 std::string getPersistentCookies();
57 void writePersistentCookies(std::ostream& s);
58
59 // gets cookies which are marked as "changed" -- use when sending periodic updates to plugins
60 std::string getChangedCookies(bool clear_changed = true);
61 void writeChangedCookies(std::ostream& s, bool clear_changed = true);
62
63 // (re)initializes internal data structures and bulk-sets cookies -- use when reading cookies from a file
64 void setAllCookies(const std::string &cookies, bool mark_changed = false);
65 void readAllCookies(std::istream& s, bool mark_changed = false);
66
67 // sets one or more cookies (without reinitializing anything) -- use when receiving cookies from a plugin
68 void setCookies(const std::string &cookies, bool mark_changed = true);
69 void readCookies(std::istream& s, bool mark_changed = true);
70
71 // sets one or more cookies (without reinitializing anything), supplying a hostname the cookies came from -- use when setting a cookie manually
72 void setCookiesFromHost(const std::string &cookies, const std::string &host, bool mark_changed = true);
73
74 // quote or unquote a string as per the definition of 'quoted-string' in rfc2616
75 static std::string quoteString(const std::string &s);
76 static std::string unquoteString(const std::string &s);
77
78private:
79
80 void setOneCookie(const std::string &s, std::string::size_type cookie_start, std::string::size_type cookie_end, bool mark_changed, const std::string &host = LLStringUtil::null);
81
82 class Cookie
83 {
84 public:
85 static Cookie *createFromString(const std::string &s, std::string::size_type cookie_start = 0, std::string::size_type cookie_end = std::string::npos, const std::string &host = LLStringUtil::null);
86
87 // Construct a string from the cookie that uniquely represents it, to be used as a key in a std::map.
88 std::string getKey() const;
89
90 const std::string &getCookie() const { return mCookie; };
91 bool isSessionCookie() const { return mDate.isNull(); };
92
93 bool isDead() const { return mDead; };
94 void setDead(bool dead) { mDead = dead; };
95
96 bool isChanged() const { return mChanged; };
97 void setChanged(bool changed) { mChanged = changed; };
98
99 const LLDate &getDate() const { return mDate; };
100
101 private:
102 Cookie(const std::string &s, std::string::size_type cookie_start = 0, std::string::size_type cookie_end = std::string::npos);
103 bool parse(const std::string &host);
104 std::string::size_type findFieldEnd(std::string::size_type start = 0, std::string::size_type end = std::string::npos);
105 bool matchName(std::string::size_type start, std::string::size_type end, const char *name);
106
107 std::string mCookie; // The full cookie, in RFC 2109 string format
108 LLDate mDate; // The expiration date of the cookie. For session cookies, this will be a null date (mDate.isNull() is true).
109 // Start/end indices of various parts of the cookie string. Stored as indices into the string to save space and time.
110 std::string::size_type mNameStart, mNameEnd;
111 std::string::size_type mValueStart, mValueEnd;
112 std::string::size_type mDomainStart, mDomainEnd;
113 std::string::size_type mPathStart, mPathEnd;
114 bool mDead;
115 bool mChanged;
116 };
117
118 typedef std::map<std::string, Cookie*> cookie_map_t;
119
120 cookie_map_t mCookies;
121 bool mHasChangedCookies;
122
123 void clearCookies();
124 void removeCookie(const std::string &key);
125};
126
127#endif // LL_LLPLUGINCOOKIESTORE_H
diff --git a/linden/indra/llplugin/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. */
44LLPluginInstanceMessageListener::~LLPluginInstanceMessageListener()
45{
46}
47
48/**
49 * TODO:DOC describe how it's used
50 */
51const 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 */
58LLPluginInstance::LLPluginInstance(LLPluginInstanceMessageListener *owner) :
59 mDSOHandle(NULL),
60 mPluginUserData(NULL),
61 mPluginSendMessageFunction(NULL)
62{
63 mOwner = owner;
64}
65
66/**
67 * Destructor.
68 */
69LLPluginInstance::~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 */
84int 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 */
130void 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 */
147void LLPluginInstance::idle(void)
148{
149}
150
151// static
152void 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 */
165void 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 */
46class LLPluginInstanceMessageListener
47{
48public:
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 */
57class LLPluginInstance
58{
59 LOG_CLASS(LLPluginInstance);
60public:
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
94private:
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 */
47LLPluginMessage::LLPluginMessage()
48{
49}
50
51/**
52 * Constructor.
53 *
54 * @param[in] p Existing message
55 */
56LLPluginMessage::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 */
67LLPluginMessage::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 */
76LLPluginMessage::~LLPluginMessage()
77{
78}
79
80/**
81 * Reset all internal state.
82 */
83void 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 */
95void 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 */
108void 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 */
119void 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 */
130void 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 */
141void 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 */
154void 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 */
165void 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 */
176void 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 */
189std::string LLPluginMessage::getClass(void) const
190{
191 return mMessage["class"];
192}
193
194/**
195 * Gets the message name.
196 *
197 * @return Message name
198 */
199std::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 */
211bool 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 */
230std::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 */
249LLSD 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 */
268S32 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 */
287U32 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 */
308bool 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 */
327F64 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 */
346void* 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 */
365std::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 */
381int 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 */
397LLPluginMessageListener::~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 */
406LLPluginMessageDispatcher::~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 */
416void 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 */
426void 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 */
436void 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 */
43class LLPluginMessage
44{
45 LOG_CLASS(LLPluginMessage);
46public:
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
104private:
105
106 LLSD mMessage;
107
108};
109
110/**
111 * @brief Listener for plugin messages.
112 */
113class LLPluginMessageListener
114{
115public:
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 */
127class LLPluginMessageDispatcher
128{
129public:
130 virtual ~LLPluginMessageDispatcher();
131
132 void addPluginMessageListener(LLPluginMessageListener *);
133 void removePluginMessageListener(LLPluginMessageListener *);
134protected:
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
45static const char MESSAGE_DELIMITER = '\0';
46
47LLPluginMessagePipeOwner::LLPluginMessagePipeOwner() :
48 mMessagePipe(NULL),
49 mSocketError(APR_SUCCESS)
50{
51}
52
53// virtual
54LLPluginMessagePipeOwner::~LLPluginMessagePipeOwner()
55{
56 killMessagePipe();
57}
58
59// virtual
60apr_status_t LLPluginMessagePipeOwner::socketError(apr_status_t error)
61{
62 mSocketError = error;
63 return error;
64};
65
66//virtual
67void LLPluginMessagePipeOwner::setMessagePipe(LLPluginMessagePipe *read_pipe)
68{
69 // Save a reference to this pipe
70 mMessagePipe = read_pipe;
71}
72
73bool LLPluginMessagePipeOwner::canSendMessage(void)
74{
75 return (mMessagePipe != NULL);
76}
77
78bool 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
94void LLPluginMessagePipeOwner::killMessagePipe(void)
95{
96 if(mMessagePipe != NULL)
97 {
98 delete mMessagePipe;
99 mMessagePipe = NULL;
100 }
101}
102
103LLPluginMessagePipe::LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket):
104 mOwner(owner),
105 mSocket(socket)
106{
107
108 mOwner->setMessagePipe(this);
109}
110
111LLPluginMessagePipe::~LLPluginMessagePipe()
112{
113 if(mOwner != NULL)
114 {
115 mOwner->setMessagePipe(NULL);
116 }
117}
118
119bool 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
129void 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
135void 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
156bool 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
168bool 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
234bool LLPluginMessagePipe::pumpInput(F64 timeout)
235{
236 bool result = true;
237
238 if(mSocket)
239 {
240 apr_status_t status;
241 apr_size_t size;
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
361void 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
42class LLPluginMessagePipe;
43
44// Inherit from this to be able to receive messages from the LLPluginMessagePipe
45class LLPluginMessagePipeOwner
46{
47 LOG_CLASS(LLPluginMessagePipeOwner);
48public:
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
59protected:
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
71class LLPluginMessagePipe
72{
73 LOG_CLASS(LLPluginMessagePipe);
74public:
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
85protected:
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
45static const F32 HEARTBEAT_SECONDS = 1.0f;
46static const F32 PLUGIN_IDLE_SECONDS = 1.0f / 100.0f; // Each call to idle will give the plugin this much time.
47
48LLPluginProcessChild::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
59LLPluginProcessChild::~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
75void LLPluginProcessChild::killSockets(void)
76{
77 killMessagePipe();
78 mSocket.reset();
79}
80
81void LLPluginProcessChild::init(U32 launcher_port)
82{
83 mLauncherHost = LLHost("127.0.0.1", launcher_port);
84 setState(STATE_INITIALIZED);
85}
86
87void 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
238void 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
251void 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
265bool LLPluginProcessChild::isRunning(void)
266{
267 bool result = false;
268
269 if(mState == STATE_RUNNING)
270 result = true;
271
272 return result;
273}
274
275bool 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
291void 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
310void 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
319void 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 */
455void 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
555void LLPluginProcessChild::setState(EState state)
556{
557 LL_DEBUGS("PluginChild") << "setting state to " << state << LL_ENDL;
558 mState = state;
559};
560
561void 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
47class LLPluginInstance;
48
49class LLPluginProcessChild: public LLPluginMessagePipeOwner, public LLPluginInstanceMessageListener
50{
51 LOG_CLASS(LLPluginProcessChild);
52public:
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
80private:
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
47LLPluginProcessParentOwner::~LLPluginProcessParentOwner()
48{
49
50}
51
52bool LLPluginProcessParent::sUseReadThread = false;
53apr_pollset_t *LLPluginProcessParent::sPollSet = NULL;
54AIAPRPool LLPluginProcessParent::sPollSetPool;
55bool LLPluginProcessParent::sPollsetNeedsRebuild = false;
56LLMutex *LLPluginProcessParent::sInstancesMutex;
57std::list<LLPluginProcessParent*> LLPluginProcessParent::sInstances;
58LLThread *LLPluginProcessParent::sReadThread = NULL;
59
60
61class LLPluginProcessParentPollThread: public LLThread
62{
63public:
64 LLPluginProcessParentPollThread() :
65 LLThread("LLPluginProcessParentPollThread")
66 {
67 }
68protected:
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
90LLPluginProcessParent::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
123LLPluginProcessParent::~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
152void LLPluginProcessParent::killSockets(void)
153{
154 {
155 LLMutexLock lock(&mIncomingQueueMutex);
156 killMessagePipe();
157 }
158
159 mListenSocket.reset();
160 mSocket.reset();
161}
162
163void LLPluginProcessParent::errorState(void)
164{
165 if(mState < STATE_RUNNING)
166 setState(STATE_LAUNCH_FAILURE);
167 else
168 setState(STATE_ERROR);
169}
170
171void 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
180bool 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
210void 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
519bool LLPluginProcessParent::isLoading(void)
520{
521 bool result = false;
522
523 if(mState <= STATE_LOADING)
524 result = true;
525
526 return result;
527}
528
529bool LLPluginProcessParent::isRunning(void)
530{
531 bool result = false;
532
533 if(mState == STATE_RUNNING)
534 result = true;
535
536 return result;
537}
538
539bool LLPluginProcessParent::isDone(void)
540{
541 bool result = false;
542
543 if(mState == STATE_DONE)
544 result = true;
545
546 return result;
547}
548
549void 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
570void 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
592void 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
628void 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
639void 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
714void 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
748void 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
823void 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
843void 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
868void 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
896void 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
989std::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
1018void 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}
1034size_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}
1046void *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
1059std::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
1071std::string LLPluginProcessParent::getPluginVersion(void)
1072{
1073 return mPluginVersionString;
1074}
1075
1076void LLPluginProcessParent::setState(EState state)
1077{
1078 LL_DEBUGS("PluginParent") << "setting state to " << stateToString(state) << LL_ENDL;
1079 mState = state;
1080};
1081
1082bool 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
1100bool 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
1112std::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
50class LLPluginProcessParentOwner
51{
52public:
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
61class LLPluginProcessParent : public LLPluginMessagePipeOwner
62{
63 LOG_CLASS(LLPluginProcessParent);
64public:
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; };
129private:
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
94int LLPluginSharedMemory::sSegmentNumber = 0;
95
96std::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 */
115class LLPluginSharedMemoryPlatformImpl
116{
117public:
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 */
134LLPluginSharedMemory::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 */
146LLPluginSharedMemory::~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
161LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl()
162{
163 mAprSharedMemory = NULL;
164}
165
166LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl()
167{
168
169}
170
171bool 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
182bool LLPluginSharedMemory::unmap(void)
183{
184 // This is a no-op under apr.
185 return true;
186}
187
188bool LLPluginSharedMemory::close(void)
189{
190 // This is a no-op under apr.
191 return true;
192}
193
194bool LLPluginSharedMemory::unlink(void)
195{
196 // This is a no-op under apr.
197 return true;
198}
199
200
201bool 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
220bool 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
235bool 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
252bool 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
272LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl()
273{
274 mSharedMemoryFD = -1;
275}
276
277LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl()
278{
279}
280
281bool 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
294bool 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
310bool 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
325bool 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
339bool 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
365bool LLPluginSharedMemory::destroy(void)
366{
367 unmap();
368 close();
369
370 return true;
371}
372
373
374bool 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
391bool 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
403LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl()
404{
405 mMapFile = NULL;
406}
407
408LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl()
409{
410
411}
412
413bool 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
433bool LLPluginSharedMemory::unmap(void)
434{
435 if(mMappedAddress != NULL)
436 {
437 UnmapViewOfFile(mMappedAddress);
438 mMappedAddress = NULL;
439 }
440
441 return true;
442}
443
444bool 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
455bool LLPluginSharedMemory::unlink(void)
456{
457 // This is a no-op on Windows.
458 return true;
459}
460
461
462bool 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
487bool LLPluginSharedMemory::destroy(void)
488{
489 unmap();
490 close();
491 return true;
492}
493
494bool 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
513bool 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
40class LLPluginSharedMemoryPlatformImpl;
41
42/**
43 * @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API.
44 *
45 */
46class LLPluginSharedMemory
47{
48 LOG_CLASS(LLPluginSharedMemory);
49public:
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
114private:
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 @@
1project(SLPlugin)
2
3include(00-Common)
4include(LLCommon)
5include(LLPlugin)
6include(Linking)
7include(PluginAPI)
8include(LLMessage)
9
10include_directories(
11 ${LLPLUGIN_INCLUDE_DIRS}
12 ${LLMESSAGE_INCLUDE_DIRS}
13 ${LLCOMMON_INCLUDE_DIRS}
14)
15
16if (DARWIN)
17 include(CMakeFindFrameworks)
18 find_library(CARBON_LIBRARY Carbon)
19 find_library(COCOA_LIBRARY Cocoa)
20endif (DARWIN)
21
22
23### SLPlugin
24
25set(SLPlugin_SOURCE_FILES
26 slplugin.cpp
27 )
28
29if (DARWIN)
30 list(APPEND SLPlugin_SOURCE_FILES
31 slplugin-objc.mm
32 )
33 list(APPEND SLPlugin_HEADER_FILES
34 slplugin-objc.h
35 )
36endif (DARWIN)
37
38set_source_files_properties(${SLPlugin_HEADER_FILES}
39 PROPERTIES HEADER_FILE_ONLY TRUE)
40
41if (SLPlugin_HEADER_FILES)
42 list(APPEND SLPlugin_SOURCE_FILES ${SLPlugin_HEADER_FILES})
43endif (SLPlugin_HEADER_FILES)
44
45add_executable(SLPlugin
46 WIN32
47 MACOSX_BUNDLE
48 ${SLPlugin_SOURCE_FILES}
49)
50
51set_target_properties(SLPlugin
52 PROPERTIES
53 MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/slplugin_info.plist
54 )
55
56target_link_libraries(SLPlugin
57 ${LLPLUGIN_LIBRARIES}
58 ${LLMESSAGE_LIBRARIES}
59 ${LLCOMMON_LIBRARIES}
60 ${PLUGIN_API_WINDOWS_LIBRARIES}
61)
62
63add_dependencies(SLPlugin
64 ${LLPLUGIN_LIBRARIES}
65 ${LLMESSAGE_LIBRARIES}
66 ${LLCOMMON_LIBRARIES}
67)
68
69if (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 )
80endif (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: */
40void setupCocoa();
41void createAutoReleasePool();
42void deleteAutoReleasePool();
diff --git a/linden/indra/llplugin/slplugin/slplugin-objc.mm b/linden/indra/llplugin/slplugin/slplugin-objc.mm
new file mode 100644
index 0000000..125b264
--- /dev/null
+++ b/linden/indra/llplugin/slplugin/slplugin-objc.mm
@@ -0,0 +1,89 @@
1/**
2 * @file slplugin-objc.mm
3 * @brief Objective-C++ file for use with the loader shell, so we can use a couple of Cocoa APIs.
4 *
5 * @cond
6 *
7 * $LicenseInfo:firstyear=2010&license=viewergpl$
8 *
9 * Copyright (c) 2010, Linden Research, Inc.
10 *
11 * Second Life Viewer Source Code
12 * The source code in this file ("Source Code") is provided by Linden Lab
13 * to you under the terms of the GNU General Public License, version 2.0
14 * ("GPL"), unless you have obtained a separate licensing agreement
15 * ("Other License"), formally executed by you and Linden Lab. Terms of
16 * the GPL can be found in doc/GPL-license.txt in this distribution, or
17 * online at http://secondlife.com/developers/opensource/gplv2
18 *
19 * There are special exceptions to the terms and conditions of the GPL as
20 * it is applied to this Source Code. View the full text of the exception
21 * in the file doc/FLOSS-exception.txt in this software distribution, or
22 * online at
23 * http://secondlife.com/developers/opensource/flossexception
24 *
25 * By copying, modifying or distributing this software, you acknowledge
26 * that you have read and understood your obligations described above,
27 * and agree to abide by those obligations.
28 *
29 * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
30 * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
31 * COMPLETENESS OR PERFORMANCE.
32 * $/LicenseInfo$
33 *
34 *
35 * @endcond
36 */
37
38
39#include <AppKit/AppKit.h>
40
41#include "slplugin-objc.h"
42
43
44void setupCocoa()
45{
46 static bool inited = false;
47
48 if(!inited)
49 {
50 createAutoReleasePool();
51
52 // The following prevents the Cocoa command line parser from trying to open 'unknown' arguements as documents.
53 // ie. running './secondlife -set Language fr' would cause a pop-up saying can't open document 'fr'
54 // when init'ing the Cocoa App window.
55 [[NSUserDefaults standardUserDefaults] setObject:@"NO" forKey:@"NSTreatUnknownArgumentsAsOpen"];
56
57 // This is a bit of voodoo taken from the Apple sample code "CarbonCocoa_PictureCursor":
58 // http://developer.apple.com/samplecode/CarbonCocoa_PictureCursor/index.html
59
60 // Needed for Carbon based applications which call into Cocoa
61 NSApplicationLoad();
62
63 // Must first call [[[NSWindow alloc] init] release] to get the NSWindow machinery set up so that NSCursor can use a window to cache the cursor image
64 [[[NSWindow alloc] init] release];
65
66 deleteAutoReleasePool();
67
68 inited = true;
69 }
70}
71
72static NSAutoreleasePool *sPool = NULL;
73
74void createAutoReleasePool()
75{
76 if(!sPool)
77 {
78 sPool = [[NSAutoreleasePool alloc] init];
79 }
80}
81
82void deleteAutoReleasePool()
83{
84 if(sPool)
85 {
86 [sPool release];
87 sPool = NULL;
88 }
89}
diff --git a/linden/indra/llplugin/slplugin/slplugin.cpp b/linden/indra/llplugin/slplugin/slplugin.cpp
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...
87static 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.
102LONG 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.
115LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter(
116 LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter )
117{
118 return NULL;
119}
120
121BOOL 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
154void 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
163bool 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
198int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
199#else
200int 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>