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