aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/linden/indra/llplugin
diff options
context:
space:
mode:
authorArmin Weatherwax2010-06-14 12:04:49 +0200
committerArmin Weatherwax2010-09-23 15:38:25 +0200
commit35df5441d3e2789663532c948731aff3a1e04728 (patch)
treeac7674289784a5f96106ea507637055a8dada78a /linden/indra/llplugin
parentChanged version to Experimental 2010.09.18 (diff)
downloadmeta-impy-35df5441d3e2789663532c948731aff3a1e04728.zip
meta-impy-35df5441d3e2789663532c948731aff3a1e04728.tar.gz
meta-impy-35df5441d3e2789663532c948731aff3a1e04728.tar.bz2
meta-impy-35df5441d3e2789663532c948731aff3a1e04728.tar.xz
llmediaplugins first step
Diffstat (limited to 'linden/indra/llplugin')
-rw-r--r--linden/indra/llplugin/CMakeLists.txt55
-rw-r--r--linden/indra/llplugin/llpluginclassmedia.cpp1162
-rw-r--r--linden/indra/llplugin/llpluginclassmedia.h352
-rw-r--r--linden/indra/llplugin/llpluginclassmediaowner.h82
-rw-r--r--linden/indra/llplugin/llplugininstance.cpp172
-rw-r--r--linden/indra/llplugin/llplugininstance.h104
-rw-r--r--linden/indra/llplugin/llpluginmessage.cpp442
-rw-r--r--linden/indra/llplugin/llpluginmessage.h142
-rw-r--r--linden/indra/llplugin/llpluginmessageclasses.h57
-rw-r--r--linden/indra/llplugin/llpluginmessagepipe.cpp316
-rw-r--r--linden/indra/llplugin/llpluginmessagepipe.h92
-rw-r--r--linden/indra/llplugin/llpluginprocesschild.cpp490
-rw-r--r--linden/indra/llplugin/llpluginprocesschild.h112
-rw-r--r--linden/indra/llplugin/llpluginprocessparent.cpp714
-rw-r--r--linden/indra/llplugin/llpluginprocessparent.h169
-rw-r--r--linden/indra/llplugin/llpluginsharedmemory.cpp506
-rw-r--r--linden/indra/llplugin/llpluginsharedmemory.h130
-rw-r--r--linden/indra/llplugin/slplugin/CMakeLists.txt55
-rw-r--r--linden/indra/llplugin/slplugin/slplugin.cpp288
-rw-r--r--linden/indra/llplugin/slplugin/slplugin_info.plist12
20 files changed, 5452 insertions, 0 deletions
diff --git a/linden/indra/llplugin/CMakeLists.txt b/linden/indra/llplugin/CMakeLists.txt
new file mode 100644
index 0000000..6706775
--- /dev/null
+++ b/linden/indra/llplugin/CMakeLists.txt
@@ -0,0 +1,55 @@
1# -*- cmake -*-
2
3project(llplugin)
4
5include(00-Common)
6include(LLCommon)
7include(LLImage)
8include(LLMath)
9include(LLMessage)
10include(LLRender)
11include(LLXML)
12include(LLWindow)
13
14include_directories(
15 ${LLCOMMON_INCLUDE_DIRS}
16 ${LLIMAGE_INCLUDE_DIRS}
17 ${LLMATH_INCLUDE_DIRS}
18 ${LLMESSAGE_INCLUDE_DIRS}
19 ${LLRENDER_INCLUDE_DIRS}
20 ${LLXML_INCLUDE_DIRS}
21 ${LLWINDOW_INCLUDE_DIRS}
22 )
23
24set(llplugin_SOURCE_FILES
25 llpluginclassmedia.cpp
26 llplugininstance.cpp
27 llpluginmessage.cpp
28 llpluginmessagepipe.cpp
29 llpluginprocesschild.cpp
30 llpluginprocessparent.cpp
31 llpluginsharedmemory.cpp
32 )
33
34set(llplugin_HEADER_FILES
35 CMakeLists.txt
36
37 llpluginclassmedia.h
38 llpluginclassmediaowner.h
39 llplugininstance.h
40 llpluginmessage.h
41 llpluginmessageclasses.h
42 llpluginmessagepipe.h
43 llpluginprocesschild.h
44 llpluginprocessparent.h
45 llpluginsharedmemory.h
46 )
47
48set_source_files_properties(${llplugin_HEADER_FILES}
49 PROPERTIES HEADER_FILE_ONLY TRUE)
50
51list(APPEND llplugin_SOURCE_FILES ${llplugin_HEADER_FILES})
52
53add_library (llplugin ${llplugin_SOURCE_FILES})
54
55add_subdirectory(slplugin)
diff --git a/linden/indra/llplugin/llpluginclassmedia.cpp b/linden/indra/llplugin/llpluginclassmedia.cpp
new file mode 100644
index 0000000..a6f6f30
--- /dev/null
+++ b/linden/indra/llplugin/llpluginclassmedia.cpp
@@ -0,0 +1,1162 @@
1/**
2 * @file llpluginclassmedia.cpp
3 * @brief LLPluginClassMedia handles a plugin which knows about the "media" message class.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#include "linden_common.h"
34#include "indra_constants.h"
35
36#include "llpluginclassmedia.h"
37#include "llpluginmessageclasses.h"
38
39static int LOW_PRIORITY_TEXTURE_SIZE_DEFAULT = 256;
40
41static int nextPowerOf2( int value )
42{
43 int next_power_of_2 = 1;
44 while ( next_power_of_2 < value )
45 {
46 next_power_of_2 <<= 1;
47 }
48
49 return next_power_of_2;
50}
51
52LLPluginClassMedia::LLPluginClassMedia(LLPluginClassMediaOwner *owner)
53{
54 mOwner = owner;
55 mPlugin = NULL;
56 reset();
57}
58
59
60LLPluginClassMedia::~LLPluginClassMedia()
61{
62 reset();
63}
64
65bool LLPluginClassMedia::init(const std::string &launcher_filename, const std::string &plugin_filename, bool debug, const std::string &user_data_path)
66{
67 LL_DEBUGS("Plugin") << "launcher: " << launcher_filename << LL_ENDL;
68 LL_DEBUGS("Plugin") << "plugin: " << plugin_filename << LL_ENDL;
69 LL_DEBUGS("Plugin") << "user_data_path: " << user_data_path << LL_ENDL;
70
71 mPlugin = new LLPluginProcessParent(this);
72 mPlugin->setSleepTime(mSleepTime);
73 mPlugin->init(launcher_filename, plugin_filename, debug, user_data_path);
74
75 return true;
76}
77
78
79void LLPluginClassMedia::reset()
80{
81 if(mPlugin != NULL)
82 {
83 delete mPlugin;
84 mPlugin = NULL;
85 }
86
87 mTextureParamsReceived = false;
88 mRequestedTextureDepth = 0;
89 mRequestedTextureInternalFormat = 0;
90 mRequestedTextureFormat = 0;
91 mRequestedTextureType = 0;
92 mRequestedTextureSwapBytes = false;
93 mRequestedTextureCoordsOpenGL = false;
94 mTextureSharedMemorySize = 0;
95 mTextureSharedMemoryName.clear();
96 mDefaultMediaWidth = 0;
97 mDefaultMediaHeight = 0;
98 mNaturalMediaWidth = 0;
99 mNaturalMediaHeight = 0;
100 mSetMediaWidth = -1;
101 mSetMediaHeight = -1;
102 mRequestedMediaWidth = 0;
103 mRequestedMediaHeight = 0;
104 mFullMediaWidth = 0;
105 mFullMediaHeight = 0;
106 mTextureWidth = 0;
107 mTextureHeight = 0;
108 mMediaWidth = 0;
109 mMediaHeight = 0;
110 mDirtyRect = LLRect::null;
111 mAutoScaleMedia = false;
112 mRequestedVolume = 1.0f;
113 mPriority = PRIORITY_NORMAL;
114 mLowPrioritySizeLimit = LOW_PRIORITY_TEXTURE_SIZE_DEFAULT;
115 mAllowDownsample = false;
116 mPadding = 0;
117 mLastMouseX = 0;
118 mLastMouseY = 0;
119 mStatus = LLPluginClassMediaOwner::MEDIA_NONE;
120 mSleepTime = 1.0f / 100.0f;
121 mCanCut = false;
122 mCanCopy = false;
123 mCanPaste = false;
124 mMediaName.clear();
125 mMediaDescription.clear();
126
127 // media_browser class
128 mNavigateURI.clear();
129 mNavigateResultCode = -1;
130 mNavigateResultString.clear();
131 mHistoryBackAvailable = false;
132 mHistoryForwardAvailable = false;
133 mStatusText.clear();
134 mProgressPercent = 0;
135
136 // media_time class
137 mCurrentTime = 0.0f;
138 mDuration = 0.0f;
139 mCurrentRate = 0.0f;
140 mLoadedDuration = 0.0f;
141}
142
143void LLPluginClassMedia::idle(void)
144{
145 if(mPlugin)
146 {
147 mPlugin->idle();
148 }
149
150 if((mMediaWidth == -1) || (!mTextureParamsReceived) || (mPlugin == NULL))
151 {
152 // Can't process a size change at this time
153 }
154 else if((mRequestedMediaWidth != mMediaWidth) || (mRequestedMediaHeight != mMediaHeight))
155 {
156 // Calculate the correct size for the media texture
157 mRequestedTextureHeight = mRequestedMediaHeight;
158 if(mPadding < 0)
159 {
160 // negative values indicate the plugin wants a power of 2
161 mRequestedTextureWidth = nextPowerOf2(mRequestedMediaWidth);
162 }
163 else
164 {
165 mRequestedTextureWidth = mRequestedMediaWidth;
166
167 if(mPadding > 1)
168 {
169 // Pad up to a multiple of the specified number of bytes per row
170 int rowbytes = mRequestedTextureWidth * mRequestedTextureDepth;
171 int pad = rowbytes % mPadding;
172 if(pad != 0)
173 {
174 rowbytes += mPadding - pad;
175 }
176
177 if(rowbytes % mRequestedTextureDepth == 0)
178 {
179 mRequestedTextureWidth = rowbytes / mRequestedTextureDepth;
180 }
181 else
182 {
183 LL_WARNS("Plugin") << "Unable to pad texture width, padding size " << mPadding << "is not a multiple of pixel size " << mRequestedTextureDepth << LL_ENDL;
184 }
185 }
186 }
187
188
189 // Size change has been requested but not initiated yet.
190 size_t newsize = mRequestedTextureWidth * mRequestedTextureHeight * mRequestedTextureDepth;
191
192 // Add an extra line for padding, just in case.
193 newsize += mRequestedTextureWidth * mRequestedTextureDepth;
194
195 if(newsize != mTextureSharedMemorySize)
196 {
197 if(!mTextureSharedMemoryName.empty())
198 {
199 // Tell the plugin to remove the old memory segment
200 mPlugin->removeSharedMemory(mTextureSharedMemoryName);
201 mTextureSharedMemoryName.clear();
202 }
203
204 mTextureSharedMemorySize = newsize;
205 mTextureSharedMemoryName = mPlugin->addSharedMemory(mTextureSharedMemorySize);
206 if(!mTextureSharedMemoryName.empty())
207 {
208 void *addr = mPlugin->getSharedMemoryAddress(mTextureSharedMemoryName);
209
210 // clear texture memory to avoid random screen visual fuzz from uninitialized texture data
211 memset( addr, 0x00, newsize );
212
213 // We could do this to force an update, but textureValid() will still be returning false until the first roundtrip to the plugin,
214 // so it may not be worthwhile.
215 // mDirtyRect.setOriginAndSize(0, 0, mRequestedMediaWidth, mRequestedMediaHeight);
216 }
217 }
218
219 // This is our local indicator that a change is in progress.
220 mTextureWidth = -1;
221 mTextureHeight = -1;
222 mMediaWidth = -1;
223 mMediaHeight = -1;
224
225 // This invalidates any existing dirty rect.
226 resetDirty();
227
228 // Send a size change message to the plugin
229 {
230 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "size_change");
231 message.setValue("name", mTextureSharedMemoryName);
232 message.setValueS32("width", mRequestedMediaWidth);
233 message.setValueS32("height", mRequestedMediaHeight);
234 message.setValueS32("texture_width", mRequestedTextureWidth);
235 message.setValueS32("texture_height", mRequestedTextureHeight);
236 mPlugin->sendMessage(message); // DO NOT just use sendMessage() here -- we want this to jump ahead of the queue.
237
238 LL_DEBUGS("Plugin") << "Sending size_change" << LL_ENDL;
239 }
240 }
241
242 if(mPlugin && mPlugin->isRunning())
243 {
244 // Send queued messages
245 while(!mSendQueue.empty())
246 {
247 LLPluginMessage message = mSendQueue.front();
248 mSendQueue.pop();
249 mPlugin->sendMessage(message);
250 }
251 }
252}
253
254int LLPluginClassMedia::getTextureWidth() const
255{
256 return nextPowerOf2(mTextureWidth);
257}
258
259int LLPluginClassMedia::getTextureHeight() const
260{
261 return nextPowerOf2(mTextureHeight);
262}
263
264unsigned char* LLPluginClassMedia::getBitsData()
265{
266 unsigned char *result = NULL;
267 if((mPlugin != NULL) && !mTextureSharedMemoryName.empty())
268 {
269 result = (unsigned char*)mPlugin->getSharedMemoryAddress(mTextureSharedMemoryName);
270 }
271 return result;
272}
273
274void LLPluginClassMedia::setSize(int width, int height)
275{
276 if((width > 0) && (height > 0))
277 {
278 mSetMediaWidth = width;
279 mSetMediaHeight = height;
280 }
281 else
282 {
283 mSetMediaWidth = -1;
284 mSetMediaHeight = -1;
285 }
286
287 setSizeInternal();
288}
289
290void LLPluginClassMedia::setSizeInternal(void)
291{
292 if((mSetMediaWidth > 0) && (mSetMediaHeight > 0))
293 {
294 mRequestedMediaWidth = mSetMediaWidth;
295 mRequestedMediaHeight = mSetMediaHeight;
296 }
297 else if((mNaturalMediaWidth > 0) && (mNaturalMediaHeight > 0))
298 {
299 mRequestedMediaWidth = mNaturalMediaWidth;
300 mRequestedMediaHeight = mNaturalMediaHeight;
301 }
302 else
303 {
304 mRequestedMediaWidth = mDefaultMediaWidth;
305 mRequestedMediaHeight = mDefaultMediaHeight;
306 }
307
308 // Save these for size/interest calculations
309 mFullMediaWidth = mRequestedMediaWidth;
310 mFullMediaHeight = mRequestedMediaHeight;
311
312 if(mAllowDownsample)
313 {
314 switch(mPriority)
315 {
316 case PRIORITY_SLIDESHOW:
317 case PRIORITY_LOW:
318 // Reduce maximum texture dimension to (or below) mLowPrioritySizeLimit
319 while((mRequestedMediaWidth > mLowPrioritySizeLimit) || (mRequestedMediaHeight > mLowPrioritySizeLimit))
320 {
321 mRequestedMediaWidth /= 2;
322 mRequestedMediaHeight /= 2;
323 }
324 break;
325
326 default:
327 // Don't adjust texture size
328 break;
329 }
330 }
331
332 if(mAutoScaleMedia)
333 {
334 mRequestedMediaWidth = nextPowerOf2(mRequestedMediaWidth);
335 mRequestedMediaHeight = nextPowerOf2(mRequestedMediaHeight);
336 }
337
338 if(mRequestedMediaWidth > 2048)
339 mRequestedMediaWidth = 2048;
340
341 if(mRequestedMediaHeight > 2048)
342 mRequestedMediaHeight = 2048;
343}
344
345void LLPluginClassMedia::setAutoScale(bool auto_scale)
346{
347 if(auto_scale != mAutoScaleMedia)
348 {
349 mAutoScaleMedia = auto_scale;
350 setSizeInternal();
351 }
352}
353
354bool LLPluginClassMedia::textureValid(void)
355{
356 if(
357 !mTextureParamsReceived ||
358 mTextureWidth <= 0 ||
359 mTextureHeight <= 0 ||
360 mMediaWidth <= 0 ||
361 mMediaHeight <= 0 ||
362 mRequestedMediaWidth != mMediaWidth ||
363 mRequestedMediaHeight != mMediaHeight ||
364 getBitsData() == NULL
365 )
366 return false;
367
368 return true;
369}
370
371bool LLPluginClassMedia::getDirty(LLRect *dirty_rect)
372{
373 bool result = !mDirtyRect.isNull();
374
375 if(dirty_rect != NULL)
376 {
377 *dirty_rect = mDirtyRect;
378 }
379
380 return result;
381}
382
383void LLPluginClassMedia::resetDirty(void)
384{
385 mDirtyRect = LLRect::null;
386}
387
388std::string LLPluginClassMedia::translateModifiers(MASK modifiers)
389{
390 std::string result;
391
392
393 if(modifiers & MASK_CONTROL)
394 {
395 result += "control|";
396 }
397
398 if(modifiers & MASK_ALT)
399 {
400 result += "alt|";
401 }
402
403 if(modifiers & MASK_SHIFT)
404 {
405 result += "shift|";
406 }
407
408 // TODO: should I deal with platform differences here or in callers?
409 // TODO: how do we deal with the Mac "command" key?
410/*
411 if(modifiers & MASK_SOMETHING)
412 {
413 result += "meta|";
414 }
415*/
416 return result;
417}
418
419void LLPluginClassMedia::mouseEvent(EMouseEventType type, int button, int x, int y, MASK modifiers)
420{
421 if(type == MOUSE_EVENT_MOVE)
422 {
423 if((x == mLastMouseX) && (y == mLastMouseY))
424 {
425 // Don't spam unnecessary mouse move events.
426 return;
427 }
428
429 mLastMouseX = x;
430 mLastMouseY = y;
431 }
432
433 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "mouse_event");
434 std::string temp;
435 switch(type)
436 {
437 case MOUSE_EVENT_DOWN: temp = "down"; break;
438 case MOUSE_EVENT_UP: temp = "up"; break;
439 case MOUSE_EVENT_MOVE: temp = "move"; break;
440 case MOUSE_EVENT_DOUBLE_CLICK: temp = "double_click"; break;
441 }
442 message.setValue("event", temp);
443
444 message.setValueS32("button", button);
445
446 message.setValueS32("x", x);
447
448 // Incoming coordinates are OpenGL-style ((0,0) = lower left), so flip them here if the plugin has requested it.
449 if(!mRequestedTextureCoordsOpenGL)
450 {
451 // TODO: Should I use mMediaHeight or mRequestedMediaHeight here?
452 y = mMediaHeight - y;
453 }
454 message.setValueS32("y", y);
455
456 message.setValue("modifiers", translateModifiers(modifiers));
457
458 sendMessage(message);
459}
460
461bool LLPluginClassMedia::keyEvent(EKeyEventType type, int key_code, MASK modifiers)
462{
463 bool result = true;
464
465 // FIXME:
466 // HACK: we don't have an easy way to tell if the plugin is going to handle a particular keycode.
467 // For now, return false for the ones the webkit plugin won't handle properly.
468
469 switch(key_code)
470 {
471 case KEY_BACKSPACE:
472 case KEY_TAB:
473 case KEY_RETURN:
474 case KEY_PAD_RETURN:
475 case KEY_SHIFT:
476 case KEY_CONTROL:
477 case KEY_ALT:
478 case KEY_CAPSLOCK:
479 case KEY_ESCAPE:
480 case KEY_PAGE_UP:
481 case KEY_PAGE_DOWN:
482 case KEY_END:
483 case KEY_HOME:
484 case KEY_LEFT:
485 case KEY_UP:
486 case KEY_RIGHT:
487 case KEY_DOWN:
488 case KEY_INSERT:
489 case KEY_DELETE:
490 // These will be handled
491 break;
492
493 default:
494 // regular ASCII characters will also be handled
495 if(key_code >= KEY_SPECIAL)
496 {
497 // Other "special" codes will not work properly.
498 result = false;
499 }
500 break;
501 }
502
503 if(result)
504 {
505 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "key_event");
506 std::string temp;
507 switch(type)
508 {
509 case KEY_EVENT_DOWN: temp = "down"; break;
510 case KEY_EVENT_UP: temp = "up"; break;
511 case KEY_EVENT_REPEAT: temp = "repeat"; break;
512 }
513 message.setValue("event", temp);
514
515 message.setValueS32("key", key_code);
516
517 message.setValue("modifiers", translateModifiers(modifiers));
518
519 sendMessage(message);
520 }
521
522 return result;
523}
524
525void LLPluginClassMedia::scrollEvent(int x, int y, MASK modifiers)
526{
527 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "scroll_event");
528
529 message.setValueS32("x", x);
530 message.setValueS32("y", y);
531 message.setValue("modifiers", translateModifiers(modifiers));
532
533 sendMessage(message);
534}
535
536bool LLPluginClassMedia::textInput(const std::string &text, MASK modifiers)
537{
538 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "text_event");
539
540 message.setValue("text", text);
541 message.setValue("modifiers", translateModifiers(modifiers));
542
543 sendMessage(message);
544
545 return true;
546}
547
548void LLPluginClassMedia::loadURI(const std::string &uri)
549{
550 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "load_uri");
551
552 message.setValue("uri", uri);
553
554 sendMessage(message);
555}
556
557const char* LLPluginClassMedia::priorityToString(EPriority priority)
558{
559 const char* result = "UNKNOWN";
560 switch(priority)
561 {
562 case PRIORITY_UNLOADED: result = "unloaded"; break;
563 case PRIORITY_STOPPED: result = "stopped"; break;
564 case PRIORITY_HIDDEN: result = "hidden"; break;
565 case PRIORITY_SLIDESHOW: result = "slideshow"; break;
566 case PRIORITY_LOW: result = "low"; break;
567 case PRIORITY_NORMAL: result = "normal"; break;
568 case PRIORITY_HIGH: result = "high"; break;
569 }
570
571 return result;
572}
573
574void LLPluginClassMedia::setPriority(EPriority priority)
575{
576 if(mPriority != priority)
577 {
578 mPriority = priority;
579
580 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "set_priority");
581
582 std::string priority_string = priorityToString(priority);
583 switch(priority)
584 {
585 case PRIORITY_UNLOADED:
586 mSleepTime = 1.0f;
587 break;
588 case PRIORITY_STOPPED:
589 mSleepTime = 1.0f;
590 break;
591 case PRIORITY_HIDDEN:
592 mSleepTime = 1.0f;
593 break;
594 case PRIORITY_SLIDESHOW:
595 mSleepTime = 1.0f;
596 break;
597 case PRIORITY_LOW:
598 mSleepTime = 1.0f / 25.0f;
599 break;
600 case PRIORITY_NORMAL:
601 mSleepTime = 1.0f / 50.0f;
602 break;
603 case PRIORITY_HIGH:
604 mSleepTime = 1.0f / 100.0f;
605 break;
606 }
607
608 message.setValue("priority", priority_string);
609
610 sendMessage(message);
611
612 if(mPlugin)
613 {
614 mPlugin->setSleepTime(mSleepTime);
615 }
616
617 LL_DEBUGS("PluginPriority") << this << ": setting priority to " << priority_string << LL_ENDL;
618
619 // This may affect the calculated size, so recalculate it here.
620 setSizeInternal();
621 }
622}
623
624void LLPluginClassMedia::setLowPrioritySizeLimit(int size)
625{
626 int power = nextPowerOf2(size);
627 if(mLowPrioritySizeLimit != power)
628 {
629 mLowPrioritySizeLimit = power;
630
631 // This may affect the calculated size, so recalculate it here.
632 setSizeInternal();
633 }
634}
635
636F64 LLPluginClassMedia::getCPUUsage()
637{
638 F64 result = 0.0f;
639
640 if(mPlugin)
641 {
642 result = mPlugin->getCPUUsage();
643 }
644
645 return result;
646}
647
648void LLPluginClassMedia::cut()
649{
650 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_cut");
651 sendMessage(message);
652}
653
654void LLPluginClassMedia::copy()
655{
656 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_copy");
657 sendMessage(message);
658}
659
660void LLPluginClassMedia::paste()
661{
662 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_paste");
663 sendMessage(message);
664}
665
666/* virtual */
667void LLPluginClassMedia::receivePluginMessage(const LLPluginMessage &message)
668{
669 std::string message_class = message.getClass();
670
671 if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA)
672 {
673 std::string message_name = message.getName();
674 if(message_name == "texture_params")
675 {
676 mRequestedTextureDepth = message.getValueS32("depth");
677 mRequestedTextureInternalFormat = message.getValueU32("internalformat");
678 mRequestedTextureFormat = message.getValueU32("format");
679 mRequestedTextureType = message.getValueU32("type");
680 mRequestedTextureSwapBytes = message.getValueBoolean("swap_bytes");
681 mRequestedTextureCoordsOpenGL = message.getValueBoolean("coords_opengl");
682
683 // These two are optional, and will default to 0 if they're not specified.
684 mDefaultMediaWidth = message.getValueS32("default_width");
685 mDefaultMediaHeight = message.getValueS32("default_height");
686
687 mAllowDownsample = message.getValueBoolean("allow_downsample");
688 mPadding = message.getValueS32("padding");
689
690 setSizeInternal();
691
692 mTextureParamsReceived = true;
693 }
694 else if(message_name == "updated")
695 {
696 if(message.hasValue("left"))
697 {
698 LLRect newDirtyRect;
699 newDirtyRect.mLeft = message.getValueS32("left");
700 newDirtyRect.mTop = message.getValueS32("top");
701 newDirtyRect.mRight = message.getValueS32("right");
702 newDirtyRect.mBottom = message.getValueS32("bottom");
703
704 // The plugin is likely to have top and bottom switched, due to vertical flip and OpenGL coordinate confusion.
705 // If they're backwards, swap them.
706 if(newDirtyRect.mTop < newDirtyRect.mBottom)
707 {
708 S32 temp = newDirtyRect.mTop;
709 newDirtyRect.mTop = newDirtyRect.mBottom;
710 newDirtyRect.mBottom = temp;
711 }
712
713 if(mDirtyRect.isNull())
714 {
715 mDirtyRect = newDirtyRect;
716 }
717 else
718 {
719 mDirtyRect.unionWith(newDirtyRect);
720 }
721
722 LL_DEBUGS("Plugin") << "adjusted incoming rect is: ("
723 << newDirtyRect.mLeft << ", "
724 << newDirtyRect.mTop << ", "
725 << newDirtyRect.mRight << ", "
726 << newDirtyRect.mBottom << "), new dirty rect is: ("
727 << mDirtyRect.mLeft << ", "
728 << mDirtyRect.mTop << ", "
729 << mDirtyRect.mRight << ", "
730 << mDirtyRect.mBottom << ")"
731 << LL_ENDL;
732
733 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CONTENT_UPDATED);
734 }
735
736
737 bool time_duration_updated = false;
738 int previous_percent = mProgressPercent;
739
740 if(message.hasValue("current_time"))
741 {
742 mCurrentTime = message.getValueReal("current_time");
743 time_duration_updated = true;
744 }
745 if(message.hasValue("duration"))
746 {
747 mDuration = message.getValueReal("duration");
748 time_duration_updated = true;
749 }
750
751 if(message.hasValue("current_rate"))
752 {
753 mCurrentRate = message.getValueReal("current_rate");
754 }
755
756 if(message.hasValue("loaded_duration"))
757 {
758 mLoadedDuration = message.getValueReal("loaded_duration");
759 time_duration_updated = true;
760 }
761 else
762 {
763 // If the message doesn't contain a loaded_duration param, assume it's equal to duration
764 mLoadedDuration = mDuration;
765 }
766
767 // Calculate a percentage based on the loaded duration and total duration.
768 if(mDuration != 0.0f) // Don't divide by zero.
769 {
770 mProgressPercent = (int)((mLoadedDuration * 100.0f)/mDuration);
771 }
772
773 if(time_duration_updated)
774 {
775 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_TIME_DURATION_UPDATED);
776 }
777
778 if(previous_percent != mProgressPercent)
779 {
780 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PROGRESS_UPDATED);
781 }
782 }
783 else if(message_name == "media_status")
784 {
785 std::string status = message.getValue("status");
786
787 LL_DEBUGS("Plugin") << "Status changed to: " << status << LL_ENDL;
788
789 if(status == "loading")
790 {
791 mStatus = LLPluginClassMediaOwner::MEDIA_LOADING;
792 }
793 else if(status == "loaded")
794 {
795 mStatus = LLPluginClassMediaOwner::MEDIA_LOADED;
796 }
797 else if(status == "error")
798 {
799 mStatus = LLPluginClassMediaOwner::MEDIA_ERROR;
800 }
801 else if(status == "playing")
802 {
803 mStatus = LLPluginClassMediaOwner::MEDIA_PLAYING;
804 }
805 else if(status == "paused")
806 {
807 mStatus = LLPluginClassMediaOwner::MEDIA_PAUSED;
808 }
809 else if(status == "done")
810 {
811 mStatus = LLPluginClassMediaOwner::MEDIA_DONE;
812 }
813 else
814 {
815 // empty string or any unknown string
816 mStatus = LLPluginClassMediaOwner::MEDIA_NONE;
817 }
818 }
819 else if(message_name == "size_change_request")
820 {
821 S32 width = message.getValueS32("width");
822 S32 height = message.getValueS32("height");
823 std::string name = message.getValue("name");
824
825 // TODO: check that name matches?
826 mNaturalMediaWidth = width;
827 mNaturalMediaHeight = height;
828
829 setSizeInternal();
830 }
831 else if(message_name == "size_change_response")
832 {
833 std::string name = message.getValue("name");
834
835 // TODO: check that name matches?
836
837 mTextureWidth = message.getValueS32("texture_width");
838 mTextureHeight = message.getValueS32("texture_height");
839 mMediaWidth = message.getValueS32("width");
840 mMediaHeight = message.getValueS32("height");
841
842 // This invalidates any existing dirty rect.
843 resetDirty();
844
845 // TODO: should we verify that the plugin sent back the right values?
846 // Two size changes in a row may cause them to not match, due to queueing, etc.
847
848 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_SIZE_CHANGED);
849 }
850 else if(message_name == "cursor_changed")
851 {
852 mCursorName = message.getValue("name");
853
854 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CURSOR_CHANGED);
855 }
856 else if(message_name == "edit_state")
857 {
858 if(message.hasValue("cut"))
859 {
860 mCanCut = message.getValueBoolean("cut");
861 }
862 if(message.hasValue("copy"))
863 {
864 mCanCopy = message.getValueBoolean("copy");
865 }
866 if(message.hasValue("paste"))
867 {
868 mCanPaste = message.getValueBoolean("paste");
869 }
870 }
871 else if(message_name == "name_text")
872 {
873 mMediaName = message.getValue("name");
874 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_NAME_CHANGED);
875 }
876 else
877 {
878 LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL;
879 }
880 }
881 else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER)
882 {
883 std::string message_name = message.getName();
884 if(message_name == "navigate_begin")
885 {
886 mNavigateURI = message.getValue("uri");
887 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_NAVIGATE_BEGIN);
888 }
889 else if(message_name == "navigate_complete")
890 {
891 mNavigateURI = message.getValue("uri");
892 mNavigateResultCode = message.getValueS32("result_code");
893 mNavigateResultString = message.getValue("result_string");
894 mHistoryBackAvailable = message.getValueBoolean("history_back_available");
895 mHistoryForwardAvailable = message.getValueBoolean("history_forward_available");
896
897 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_NAVIGATE_COMPLETE);
898 }
899 else if(message_name == "progress")
900 {
901 mProgressPercent = message.getValueS32("percent");
902 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PROGRESS_UPDATED);
903 }
904 else if(message_name == "status_text")
905 {
906 mStatusText = message.getValue("status");
907 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_STATUS_TEXT_CHANGED);
908 }
909 else if(message_name == "location_changed")
910 {
911 mLocation = message.getValue("uri");
912 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_LOCATION_CHANGED);
913 }
914 else if(message_name == "click_href")
915 {
916 mClickURL = message.getValue("uri");
917 mClickTarget = message.getValue("target");
918 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_HREF);
919 }
920 else if(message_name == "click_nofollow")
921 {
922 mClickURL = message.getValue("uri");
923 mClickTarget.clear();
924 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_NOFOLLOW);
925 }
926 else
927 {
928 LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL;
929 }
930 }
931 else if(message_class == LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME)
932 {
933 std::string message_name = message.getName();
934
935 // This class hasn't defined any incoming messages yet.
936// if(message_name == "message_name")
937// {
938// }
939// else
940 {
941 LL_WARNS("Plugin") << "Unknown " << message_name << " class message: " << message_name << LL_ENDL;
942 }
943 }
944
945}
946
947/* virtual */
948void LLPluginClassMedia::pluginLaunchFailed()
949{
950 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PLUGIN_FAILED_LAUNCH);
951}
952
953/* virtual */
954void LLPluginClassMedia::pluginDied()
955{
956 mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PLUGIN_FAILED);
957}
958
959void LLPluginClassMedia::mediaEvent(LLPluginClassMediaOwner::EMediaEvent event)
960{
961 if(mOwner)
962 {
963 mOwner->handleMediaEvent(this, event);
964 }
965}
966
967void LLPluginClassMedia::sendMessage(const LLPluginMessage &message)
968{
969 if(mPlugin && mPlugin->isRunning())
970 {
971 mPlugin->sendMessage(message);
972 }
973 else
974 {
975 // The plugin isn't set up yet -- queue this message to be sent after initialization.
976 mSendQueue.push(message);
977 }
978}
979
980////////////////////////////////////////////////////////////
981// MARK: media_browser class functions
982bool LLPluginClassMedia::pluginSupportsMediaBrowser(void)
983{
984 std::string version = mPlugin->getMessageClassVersion(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER);
985 return !version.empty();
986}
987
988void LLPluginClassMedia::focus(bool focused)
989{
990 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "focus");
991
992 message.setValueBoolean("focused", focused);
993
994 sendMessage(message);
995}
996
997void LLPluginClassMedia::clear_cache()
998{
999 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "clear_cache");
1000 sendMessage(message);
1001}
1002
1003void LLPluginClassMedia::clear_cookies()
1004{
1005 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "clear_cookies");
1006 sendMessage(message);
1007}
1008
1009void LLPluginClassMedia::enable_cookies(bool enable)
1010{
1011 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "enable_cookies");
1012 sendMessage(message);
1013}
1014
1015void LLPluginClassMedia::proxy_setup(bool enable, const std::string &host, int port)
1016{
1017 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "proxy_setup");
1018
1019 message.setValueBoolean("enable", enable);
1020 message.setValue("host", host);
1021 message.setValueS32("port", port);
1022
1023 sendMessage(message);
1024}
1025
1026void LLPluginClassMedia::browse_stop()
1027{
1028 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_stop");
1029 sendMessage(message);
1030}
1031
1032void LLPluginClassMedia::browse_reload(bool ignore_cache)
1033{
1034 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_reload");
1035
1036 message.setValueBoolean("ignore_cache", ignore_cache);
1037
1038 sendMessage(message);
1039}
1040
1041void LLPluginClassMedia::browse_forward()
1042{
1043 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_forward");
1044 sendMessage(message);
1045}
1046
1047void LLPluginClassMedia::browse_back()
1048{
1049 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_back");
1050 sendMessage(message);
1051}
1052
1053void LLPluginClassMedia::set_status_redirect(int code, const std::string &url)
1054{
1055 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "set_status_redirect");
1056
1057 message.setValueS32("code", code);
1058 message.setValue("url", url);
1059
1060 sendMessage(message);
1061}
1062
1063void LLPluginClassMedia::setBrowserUserAgent(const std::string& user_agent)
1064{
1065 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "set_user_agent");
1066
1067 message.setValue("user_agent", user_agent);
1068
1069 sendMessage(message);
1070}
1071
1072void LLPluginClassMedia::crashPlugin()
1073{
1074 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "crash");
1075
1076 sendMessage(message);
1077}
1078
1079void LLPluginClassMedia::hangPlugin()
1080{
1081 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "hang");
1082
1083 sendMessage(message);
1084}
1085
1086
1087////////////////////////////////////////////////////////////
1088// MARK: media_time class functions
1089bool LLPluginClassMedia::pluginSupportsMediaTime(void)
1090{
1091 std::string version = mPlugin->getMessageClassVersion(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME);
1092 return !version.empty();
1093}
1094
1095void LLPluginClassMedia::stop()
1096{
1097 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "stop");
1098 sendMessage(message);
1099}
1100
1101void LLPluginClassMedia::start(float rate)
1102{
1103 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "start");
1104
1105 message.setValueReal("rate", rate);
1106
1107 sendMessage(message);
1108}
1109
1110void LLPluginClassMedia::pause()
1111{
1112 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "pause");
1113 sendMessage(message);
1114}
1115
1116void LLPluginClassMedia::seek(float time)
1117{
1118 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "seek");
1119
1120 message.setValueReal("time", time);
1121
1122 sendMessage(message);
1123}
1124
1125void LLPluginClassMedia::setLoop(bool loop)
1126{
1127 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "set_loop");
1128
1129 message.setValueBoolean("loop", loop);
1130
1131 sendMessage(message);
1132}
1133
1134void LLPluginClassMedia::setVolume(float volume)
1135{
1136 if(volume != mRequestedVolume)
1137 {
1138 mRequestedVolume = volume;
1139
1140 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "set_volume");
1141
1142 message.setValueReal("volume", volume);
1143
1144 sendMessage(message);
1145 }
1146}
1147
1148float LLPluginClassMedia::getVolume()
1149{
1150 return mRequestedVolume;
1151}
1152
1153void LLPluginClassMedia::initializeUrlHistory(const LLSD& url_history)
1154{
1155 // Send URL history to plugin
1156 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "init_history");
1157 message.setValueLLSD("history", url_history);
1158 sendMessage(message);
1159
1160 LL_DEBUGS("Plugin") << "Sending history" << LL_ENDL;
1161}
1162
diff --git a/linden/indra/llplugin/llpluginclassmedia.h b/linden/indra/llplugin/llpluginclassmedia.h
new file mode 100644
index 0000000..c45010e
--- /dev/null
+++ b/linden/indra/llplugin/llpluginclassmedia.h
@@ -0,0 +1,352 @@
1/**
2 * @file llpluginclassmedia.h
3 * @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#ifndef LL_LLPLUGINCLASSMEDIA_H
34#define LL_LLPLUGINCLASSMEDIA_H
35
36#include "llgltypes.h"
37#include "llpluginprocessparent.h"
38#include "llrect.h"
39#include "llpluginclassmediaowner.h"
40#include <queue>
41
42
43class LLPluginClassMedia : public LLPluginProcessParentOwner
44{
45 LOG_CLASS(LLPluginClassMedia);
46public:
47 LLPluginClassMedia(LLPluginClassMediaOwner *owner);
48 virtual ~LLPluginClassMedia();
49
50 // local initialization, called by the media manager when creating a source
51 virtual bool init(const std::string &launcher_filename, const std::string &plugin_filename, bool debug, const std::string &user_data_path);
52
53 // undoes everything init() didm called by the media manager when destroying a source
54 virtual void reset();
55
56 void idle(void);
57
58 // All of these may return 0 or an actual valid value.
59 // Callers need to check the return for 0, and not use the values in that case.
60 int getWidth() const { return (mMediaWidth > 0) ? mMediaWidth : 0; };
61 int getHeight() const { return (mMediaHeight > 0) ? mMediaHeight : 0; };
62 int getNaturalWidth() const { return mNaturalMediaWidth; };
63 int getNaturalHeight() const { return mNaturalMediaHeight; };
64 int getSetWidth() const { return mSetMediaWidth; };
65 int getSetHeight() const { return mSetMediaHeight; };
66 int getBitsWidth() const { return (mTextureWidth > 0) ? mTextureWidth : 0; };
67 int getBitsHeight() const { return (mTextureHeight > 0) ? mTextureHeight : 0; };
68 int getTextureWidth() const;
69 int getTextureHeight() const;
70 int getFullWidth() const { return mFullMediaWidth; };
71 int getFullHeight() const { return mFullMediaHeight; };
72
73 // This may return NULL. Callers need to check for and handle this case.
74 unsigned char* getBitsData();
75
76 // gets the format details of the texture data
77 // These may return 0 if they haven't been set up yet. The caller needs to detect this case.
78 int getTextureDepth() const { return mRequestedTextureDepth; };
79 int getTextureFormatInternal() const { return mRequestedTextureInternalFormat; };
80 int getTextureFormatPrimary() const { return mRequestedTextureFormat; };
81 int getTextureFormatType() const { return mRequestedTextureType; };
82 bool getTextureFormatSwapBytes() const { return mRequestedTextureSwapBytes; };
83 bool getTextureCoordsOpenGL() const { return mRequestedTextureCoordsOpenGL; };
84
85 void setSize(int width, int height);
86 void setAutoScale(bool auto_scale);
87
88 // Returns true if all of the texture parameters (depth, format, size, and texture size) are set up and consistent.
89 // This will initially be false, and will also be false for some time after setSize while the resize is processed.
90 // Note that if this returns true, it is safe to use all the get() functions above without checking for invalid return values
91 // until you call idle() again.
92 bool textureValid(void);
93
94 bool getDirty(LLRect *dirty_rect = NULL);
95 void resetDirty(void);
96
97 typedef enum
98 {
99 MOUSE_EVENT_DOWN,
100 MOUSE_EVENT_UP,
101 MOUSE_EVENT_MOVE,
102 MOUSE_EVENT_DOUBLE_CLICK
103 }EMouseEventType;
104
105 void mouseEvent(EMouseEventType type, int button, int x, int y, MASK modifiers);
106
107 typedef enum
108 {
109 KEY_EVENT_DOWN,
110 KEY_EVENT_UP,
111 KEY_EVENT_REPEAT
112 }EKeyEventType;
113
114 bool keyEvent(EKeyEventType type, int key_code, MASK modifiers);
115
116 void scrollEvent(int x, int y, MASK modifiers);
117
118 // Text may be unicode (utf8 encoded)
119 bool textInput(const std::string &text, MASK modifiers);
120
121 void loadURI(const std::string &uri);
122
123 // "Loading" means uninitialized or any state prior to fully running (processing commands)
124 bool isPluginLoading(void) { return mPlugin?mPlugin->isLoading():false; };
125
126 // "Running" means the steady state -- i.e. processing messages
127 bool isPluginRunning(void) { return mPlugin?mPlugin->isRunning():false; };
128
129 // "Exited" means any regular or error state after "Running" (plugin may have crashed or exited normally)
130 bool isPluginExited(void) { return mPlugin?mPlugin->isDone():false; };
131
132 std::string getPluginVersion() { return mPlugin?mPlugin->getPluginVersion():std::string(""); };
133
134 bool getDisableTimeout() { return mPlugin?mPlugin->getDisableTimeout():false; };
135 void setDisableTimeout(bool disable) { if(mPlugin) mPlugin->setDisableTimeout(disable); };
136
137 // Inherited from LLPluginProcessParentOwner
138 /* virtual */ void receivePluginMessage(const LLPluginMessage &message);
139 /* virtual */ void pluginLaunchFailed();
140 /* virtual */ void pluginDied();
141
142
143 typedef enum
144 {
145 PRIORITY_UNLOADED, // media plugin isn't even loaded.
146 PRIORITY_STOPPED, // media is not playing, shouldn't need to update at all.
147 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.
148 PRIORITY_SLIDESHOW, // media is in the far distance, updates very infrequently
149 PRIORITY_LOW, // media is in the distance, may be rendered at reduced size
150 PRIORITY_NORMAL, // normal (default) priority
151 PRIORITY_HIGH // media has user focus and/or is taking up most of the screen
152 }EPriority;
153
154 static const char* priorityToString(EPriority priority);
155 void setPriority(EPriority priority);
156 void setLowPrioritySizeLimit(int size);
157
158 F64 getCPUUsage();
159
160 // Valid after a MEDIA_EVENT_CURSOR_CHANGED event
161 std::string getCursorName() const { return mCursorName; };
162
163 LLPluginClassMediaOwner::EMediaStatus getStatus() const { return mStatus; }
164
165 void cut();
166 bool canCut() const { return mCanCut; };
167
168 void copy();
169 bool canCopy() const { return mCanCopy; };
170
171 void paste();
172 bool canPaste() const { return mCanPaste; };
173
174 ///////////////////////////////////
175 // media browser class functions
176 bool pluginSupportsMediaBrowser(void);
177
178 void focus(bool focused);
179 void clear_cache();
180 void clear_cookies();
181 void enable_cookies(bool enable);
182 void proxy_setup(bool enable, const std::string &host = LLStringUtil::null, int port = 0);
183 void browse_stop();
184 void browse_reload(bool ignore_cache = false);
185 void browse_forward();
186 void browse_back();
187 void set_status_redirect(int code, const std::string &url);
188 void setBrowserUserAgent(const std::string& user_agent);
189
190 // This is valid after MEDIA_EVENT_NAVIGATE_BEGIN or MEDIA_EVENT_NAVIGATE_COMPLETE
191 std::string getNavigateURI() const { return mNavigateURI; };
192
193 // These are valid after MEDIA_EVENT_NAVIGATE_COMPLETE
194 S32 getNavigateResultCode() const { return mNavigateResultCode; };
195 std::string getNavigateResultString() const { return mNavigateResultString; };
196 bool getHistoryBackAvailable() const { return mHistoryBackAvailable; };
197 bool getHistoryForwardAvailable() const { return mHistoryForwardAvailable; };
198
199 // This is valid after MEDIA_EVENT_PROGRESS_UPDATED
200 int getProgressPercent() const { return mProgressPercent; };
201
202 // This is valid after MEDIA_EVENT_STATUS_TEXT_CHANGED
203 std::string getStatusText() const { return mStatusText; };
204
205 // This is valid after MEDIA_EVENT_LOCATION_CHANGED
206 std::string getLocation() const { return mLocation; };
207
208 // This is valid after MEDIA_EVENT_CLICK_LINK_HREF or MEDIA_EVENT_CLICK_LINK_NOFOLLOW
209 std::string getClickURL() const { return mClickURL; };
210
211 // This is valid after MEDIA_EVENT_CLICK_LINK_HREF
212 std::string getClickTarget() const { return mClickTarget; };
213
214 std::string getMediaName() const { return mMediaName; };
215 std::string getMediaDescription() const { return mMediaDescription; };
216
217 // Crash the plugin. If you use this outside of a testbed, you will be punished.
218 void crashPlugin();
219
220 // Hang the plugin. If you use this outside of a testbed, you will be punished.
221 void hangPlugin();
222
223 ///////////////////////////////////
224 // media time class functions
225 bool pluginSupportsMediaTime(void);
226 void stop();
227 void start(float rate = 0.0f);
228 void pause();
229 void seek(float time);
230 void setLoop(bool loop);
231 void setVolume(float volume);
232 float getVolume();
233
234 F64 getCurrentTime(void) const { return mCurrentTime; };
235 F64 getDuration(void) const { return mDuration; };
236 F64 getCurrentPlayRate(void) { return mCurrentRate; };
237 F64 getLoadedDuration(void) const { return mLoadedDuration; };
238
239 // Initialize the URL history of the plugin by sending
240 // "init_history" message
241 void initializeUrlHistory(const LLSD& url_history);
242
243protected:
244
245 LLPluginClassMediaOwner *mOwner;
246
247 // Notify this object's owner that an event has occurred.
248 void mediaEvent(LLPluginClassMediaOwner::EMediaEvent event);
249
250 void sendMessage(const LLPluginMessage &message); // Send message internally, either queueing or sending directly.
251 std::queue<LLPluginMessage> mSendQueue; // Used to queue messages while the plugin initializes.
252
253 void setSizeInternal(void);
254
255 bool mTextureParamsReceived; // the mRequestedTexture* fields are only valid when this is true
256 S32 mRequestedTextureDepth;
257 LLGLenum mRequestedTextureInternalFormat;
258 LLGLenum mRequestedTextureFormat;
259 LLGLenum mRequestedTextureType;
260 bool mRequestedTextureSwapBytes;
261 bool mRequestedTextureCoordsOpenGL;
262
263 std::string mTextureSharedMemoryName;
264 size_t mTextureSharedMemorySize;
265
266 // True to scale requested media up to the full size of the texture (i.e. next power of two)
267 bool mAutoScaleMedia;
268
269 // default media size for the plugin, from the texture_params message.
270 int mDefaultMediaWidth;
271 int mDefaultMediaHeight;
272
273 // Size that has been requested by the plugin itself
274 int mNaturalMediaWidth;
275 int mNaturalMediaHeight;
276
277 // Size that has been requested with setSize()
278 int mSetMediaWidth;
279 int mSetMediaHeight;
280
281 // Full calculated media size (before auto-scale and downsample calculations)
282 int mFullMediaWidth;
283 int mFullMediaHeight;
284
285 // Actual media size being set (after auto-scale)
286 int mRequestedMediaWidth;
287 int mRequestedMediaHeight;
288
289 // Texture size calculated from actual media size
290 int mRequestedTextureWidth;
291 int mRequestedTextureHeight;
292
293 // Size that the plugin has acknowledged
294 int mTextureWidth;
295 int mTextureHeight;
296 int mMediaWidth;
297 int mMediaHeight;
298
299 float mRequestedVolume;
300
301 // Priority of this media stream
302 EPriority mPriority;
303 int mLowPrioritySizeLimit;
304
305 bool mAllowDownsample;
306 int mPadding;
307
308
309 LLPluginProcessParent *mPlugin;
310
311 LLRect mDirtyRect;
312
313 std::string translateModifiers(MASK modifiers);
314
315 std::string mCursorName;
316 int mLastMouseX;
317 int mLastMouseY;
318
319 LLPluginClassMediaOwner::EMediaStatus mStatus;
320
321 F64 mSleepTime;
322
323 bool mCanCut;
324 bool mCanCopy;
325 bool mCanPaste;
326
327 std::string mMediaName;
328 std::string mMediaDescription;
329
330 /////////////////////////////////////////
331 // media_browser class
332 std::string mNavigateURI;
333 S32 mNavigateResultCode;
334 std::string mNavigateResultString;
335 bool mHistoryBackAvailable;
336 bool mHistoryForwardAvailable;
337 std::string mStatusText;
338 int mProgressPercent;
339 std::string mLocation;
340 std::string mClickURL;
341 std::string mClickTarget;
342
343 /////////////////////////////////////////
344 // media_time class
345 F64 mCurrentTime;
346 F64 mDuration;
347 F64 mCurrentRate;
348 F64 mLoadedDuration;
349
350};
351
352#endif // LL_LLPLUGINCLASSMEDIA_H
diff --git a/linden/indra/llplugin/llpluginclassmediaowner.h b/linden/indra/llplugin/llpluginclassmediaowner.h
new file mode 100644
index 0000000..182eb92
--- /dev/null
+++ b/linden/indra/llplugin/llpluginclassmediaowner.h
@@ -0,0 +1,82 @@
1/**
2 * @file llpluginclassmediaowner.h
3 * @brief LLPluginClassMedia handles interaction with a plugin which knows about the "media" message class.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#ifndef LL_LLPLUGINCLASSMEDIAOWNER_H
34#define LL_LLPLUGINCLASSMEDIAOWNER_H
35
36#include "llpluginprocessparent.h"
37#include "llrect.h"
38#include <queue>
39
40class LLPluginClassMedia;
41
42class LLPluginClassMediaOwner
43{
44public:
45 typedef enum
46 {
47 MEDIA_EVENT_CONTENT_UPDATED, // contents/dirty rect have updated
48 MEDIA_EVENT_TIME_DURATION_UPDATED, // current time and/or duration have updated
49 MEDIA_EVENT_SIZE_CHANGED, // media size has changed
50 MEDIA_EVENT_CURSOR_CHANGED, // plugin has requested a cursor change
51
52 MEDIA_EVENT_NAVIGATE_BEGIN, // browser has begun navigation
53 MEDIA_EVENT_NAVIGATE_COMPLETE, // browser has finished navigation
54 MEDIA_EVENT_PROGRESS_UPDATED, // browser has updated loading progress
55 MEDIA_EVENT_STATUS_TEXT_CHANGED, // browser has updated the status text
56 MEDIA_EVENT_NAME_CHANGED, // browser has updated the name of the media (typically <title> tag)
57 MEDIA_EVENT_LOCATION_CHANGED, // browser location (URL) has changed (maybe due to internal navagation/frames/etc)
58 MEDIA_EVENT_CLICK_LINK_HREF, // I'm not entirely sure what the semantics of these two are
59 MEDIA_EVENT_CLICK_LINK_NOFOLLOW,
60
61 MEDIA_EVENT_PLUGIN_FAILED_LAUNCH, // The plugin failed to launch
62 MEDIA_EVENT_PLUGIN_FAILED // The plugin died unexpectedly
63
64 } EMediaEvent;
65
66 typedef enum
67 {
68 MEDIA_NONE, // Uninitialized -- no useful state
69 MEDIA_LOADING, // loading or navigating
70 MEDIA_LOADED, // navigation/preroll complete
71 MEDIA_ERROR, // navigation/preroll failed
72 MEDIA_PLAYING, // playing (only for time-based media)
73 MEDIA_PAUSED, // paused (only for time-based media)
74 MEDIA_DONE // finished playing (only for time-based media)
75
76 } EMediaStatus;
77
78 virtual ~LLPluginClassMediaOwner() {};
79 virtual void handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent /*event*/) {};
80};
81
82#endif // LL_LLPLUGINCLASSMEDIAOWNER_H
diff --git a/linden/indra/llplugin/llplugininstance.cpp b/linden/indra/llplugin/llplugininstance.cpp
new file mode 100644
index 0000000..5185b36
--- /dev/null
+++ b/linden/indra/llplugin/llplugininstance.cpp
@@ -0,0 +1,172 @@
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 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#include "linden_common.h"
34
35#include "llplugininstance.h"
36
37#include "llapr.h"
38
39/** Virtual destructor. */
40LLPluginInstanceMessageListener::~LLPluginInstanceMessageListener()
41{
42}
43
44/**
45 * TODO:DOC describe how it's used
46 */
47const char *LLPluginInstance::PLUGIN_INIT_FUNCTION_NAME = "LLPluginInitEntryPoint";
48
49/**
50 * Constructor.
51 *
52 * @param[in] owner Plugin instance. TODO:DOC is this a good description of what "owner" is?
53 */
54LLPluginInstance::LLPluginInstance(LLPluginInstanceMessageListener *owner) :
55 mDSOHandle(NULL),
56 mPluginUserData(NULL),
57 mPluginSendMessageFunction(NULL)
58{
59 mOwner = owner;
60}
61
62/**
63 * Destructor.
64 */
65LLPluginInstance::~LLPluginInstance()
66{
67 if(mDSOHandle != NULL)
68 {
69 apr_dso_unload(mDSOHandle);
70 mDSOHandle = NULL;
71 }
72}
73
74/**
75 * Dynamically loads the plugin and runs the plugin's init function.
76 *
77 * @param[in] plugin_file Name of plugin dll/dylib/so. TODO:DOC is this correct? see .h
78 * @return 0 if successful, APR error code or error code from the plugin's init function on failure.
79 */
80int LLPluginInstance::load(std::string &plugin_file)
81{
82 pluginInitFunction init_function = NULL;
83
84 int result = apr_dso_load(&mDSOHandle,
85 plugin_file.c_str(),
86 gAPRPoolp);
87 if(result != APR_SUCCESS)
88 {
89 char buf[1024];
90 apr_dso_error(mDSOHandle, buf, sizeof(buf));
91
92 LL_WARNS("Plugin") << "apr_dso_load of " << plugin_file << " failed with error " << result << " , additional info string: " << buf << LL_ENDL;
93
94 }
95
96 if(result == APR_SUCCESS)
97 {
98 result = apr_dso_sym((apr_dso_handle_sym_t*)&init_function,
99 mDSOHandle,
100 PLUGIN_INIT_FUNCTION_NAME);
101
102 if(result != APR_SUCCESS)
103 {
104 LL_WARNS("Plugin") << "apr_dso_sym failed with error " << result << LL_ENDL;
105 }
106 }
107
108 if(result == APR_SUCCESS)
109 {
110 result = init_function(staticReceiveMessage, (void*)this, &mPluginSendMessageFunction, &mPluginUserData);
111
112 if(result != APR_SUCCESS)
113 {
114 LL_WARNS("Plugin") << "call to init function failed with error " << result << LL_ENDL;
115 }
116 }
117
118 return (int)result;
119}
120
121/**
122 * Sends a message to the plugin.
123 *
124 * @param[in] message Message
125 */
126void LLPluginInstance::sendMessage(const std::string &message)
127{
128 if(mPluginSendMessageFunction)
129 {
130 LL_DEBUGS("Plugin") << "sending message to plugin: \"" << message << "\"" << LL_ENDL;
131 mPluginSendMessageFunction(message.c_str(), &mPluginUserData);
132 }
133 else
134 {
135 LL_WARNS("Plugin") << "dropping message: \"" << message << "\"" << LL_ENDL;
136 }
137}
138
139/**
140 * Idle. TODO:DOC what's the purpose of this?
141 *
142 */
143void LLPluginInstance::idle(void)
144{
145}
146
147// static
148void LLPluginInstance::staticReceiveMessage(const char *message_string, void **user_data)
149{
150 // TODO: validate that the user_data argument is still a valid LLPluginInstance pointer
151 // 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
152 LLPluginInstance *self = (LLPluginInstance*)*user_data;
153 self->receiveMessage(message_string);
154}
155
156/**
157 * Plugin receives message from plugin loader shell.
158 *
159 * @param[in] message_string Message
160 */
161void LLPluginInstance::receiveMessage(const char *message_string)
162{
163 if(mOwner)
164 {
165 LL_DEBUGS("Plugin") << "processing incoming message: \"" << message_string << "\"" << LL_ENDL;
166 mOwner->receivePluginMessage(message_string);
167 }
168 else
169 {
170 LL_WARNS("Plugin") << "dropping incoming message: \"" << message_string << "\"" << LL_ENDL;
171 }
172}
diff --git a/linden/indra/llplugin/llplugininstance.h b/linden/indra/llplugin/llplugininstance.h
new file mode 100644
index 0000000..0b53b5f
--- /dev/null
+++ b/linden/indra/llplugin/llplugininstance.h
@@ -0,0 +1,104 @@
1/**
2 * @file llplugininstance.h
3 * @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#ifndef LL_LLPLUGININSTANCE_H
34#define LL_LLPLUGININSTANCE_H
35
36#include "llstring.h"
37#include "llapr.h"
38
39#include "apr_dso.h"
40
41/**
42 * @brief LLPluginInstanceMessageListener receives messages sent from the plugin loader shell to the plugin.
43 */
44class LLPluginInstanceMessageListener
45{
46public:
47 virtual ~LLPluginInstanceMessageListener();
48 /** Plugin receives message from plugin loader shell. */
49 virtual void receivePluginMessage(const std::string &message) = 0;
50};
51
52/**
53 * @brief LLPluginInstance handles loading the dynamic library of a plugin and setting up its entry points for message passing.
54 */
55class LLPluginInstance
56{
57 LOG_CLASS(LLPluginInstance);
58public:
59 LLPluginInstance(LLPluginInstanceMessageListener *owner);
60 virtual ~LLPluginInstance();
61
62 // Load a plugin dll/dylib/so
63 // Returns 0 if successful, APR error code or error code returned from the plugin's init function on failure.
64 int load(std::string &plugin_file);
65
66 // Sends a message to the plugin.
67 void sendMessage(const std::string &message);
68
69 // TODO:DOC is this comment obsolete? can't find "send_count" anywhere in indra tree.
70 // send_count is the maximum number of message to process from the send queue. If negative, it will drain the queue completely.
71 // The receive queue is always drained completely.
72 // Returns the total number of messages processed from both queues.
73 void idle(void);
74
75 /** The signature of the function for sending a message from plugin to plugin loader shell.
76 *
77 * @param[in] message_string Null-terminated C string
78 * @param[in] user_data The opaque reference that the callee supplied during setup.
79 */
80 typedef void (*sendMessageFunction) (const char *message_string, void **user_data);
81
82 /** The signature of the plugin init function. TODO:DOC check direction (pluging loader shell to plugin?)
83 *
84 * @param[in] host_user_data Data from plugin loader shell.
85 * @param[in] plugin_send_function Function for sending from the plugin loader shell to plugin.
86 */
87 typedef int (*pluginInitFunction) (sendMessageFunction host_send_func, void *host_user_data, sendMessageFunction *plugin_send_func, void **plugin_user_data);
88
89 /** Name of plugin init function */
90 static const char *PLUGIN_INIT_FUNCTION_NAME;
91
92private:
93 static void staticReceiveMessage(const char *message_string, void **user_data);
94 void receiveMessage(const char *message_string);
95
96 apr_dso_handle_t *mDSOHandle;
97
98 void *mPluginUserData;
99 sendMessageFunction mPluginSendMessageFunction;
100
101 LLPluginInstanceMessageListener *mOwner;
102};
103
104#endif // LL_LLPLUGININSTANCE_H
diff --git a/linden/indra/llplugin/llpluginmessage.cpp b/linden/indra/llplugin/llpluginmessage.cpp
new file mode 100644
index 0000000..67ac995
--- /dev/null
+++ b/linden/indra/llplugin/llpluginmessage.cpp
@@ -0,0 +1,442 @@
1/**
2 * @file llpluginmessage.cpp
3 * @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#include "linden_common.h"
34
35#include "llpluginmessage.h"
36#include "llsdserialize.h"
37#include "u64.h"
38
39/**
40 * Constructor.
41 */
42LLPluginMessage::LLPluginMessage()
43{
44}
45
46/**
47 * Constructor.
48 *
49 * @param[in] p Existing message
50 */
51LLPluginMessage::LLPluginMessage(const LLPluginMessage &p)
52{
53 mMessage = p.mMessage;
54}
55
56/**
57 * Constructor.
58 *
59 * @param[in] message_class Message class
60 * @param[in] message_name Message name
61 */
62LLPluginMessage::LLPluginMessage(const std::string &message_class, const std::string &message_name)
63{
64 setMessage(message_class, message_name);
65}
66
67
68/**
69 * Destructor.
70 */
71LLPluginMessage::~LLPluginMessage()
72{
73}
74
75/**
76 * Reset all internal state.
77 */
78void LLPluginMessage::clear()
79{
80 mMessage = LLSD::emptyMap();
81 mMessage["params"] = LLSD::emptyMap();
82}
83
84/**
85 * Sets the message class and name. Also has the side-effect of clearing any key-value pairs in the message.
86 *
87 * @param[in] message_class Message class
88 * @param[in] message_name Message name
89 */
90void LLPluginMessage::setMessage(const std::string &message_class, const std::string &message_name)
91{
92 clear();
93 mMessage["class"] = message_class;
94 mMessage["name"] = message_name;
95}
96
97/**
98 * Sets a key/value pair in the message, where the value is a string.
99 *
100 * @param[in] key Key
101 * @param[in] value String value
102 */
103void LLPluginMessage::setValue(const std::string &key, const std::string &value)
104{
105 mMessage["params"][key] = value;
106}
107
108/**
109 * Sets a key/value pair in the message, where the value is LLSD.
110 *
111 * @param[in] key Key
112 * @param[in] value LLSD value
113 */
114void LLPluginMessage::setValueLLSD(const std::string &key, const LLSD &value)
115{
116 mMessage["params"][key] = value;
117}
118
119/**
120 * Sets a key/value pair in the message, where the value is signed 32-bit.
121 *
122 * @param[in] key Key
123 * @param[in] value 32-bit signed value
124 */
125void LLPluginMessage::setValueS32(const std::string &key, S32 value)
126{
127 mMessage["params"][key] = value;
128}
129
130/**
131 * 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".
132 *
133 * @param[in] key Key
134 * @param[in] value 32-bit unsigned value
135 */
136void LLPluginMessage::setValueU32(const std::string &key, U32 value)
137{
138 std::stringstream temp;
139 temp << "0x" << std::hex << value;
140 setValue(key, temp.str());
141}
142
143/**
144 * Sets a key/value pair in the message, where the value is a bool.
145 *
146 * @param[in] key Key
147 * @param[in] value Boolean value
148 */
149void LLPluginMessage::setValueBoolean(const std::string &key, bool value)
150{
151 mMessage["params"][key] = value;
152}
153
154/**
155 * Sets a key/value pair in the message, where the value is a double.
156 *
157 * @param[in] key Key
158 * @param[in] value Boolean value
159 */
160void LLPluginMessage::setValueReal(const std::string &key, F64 value)
161{
162 mMessage["params"][key] = value;
163}
164
165/**
166 * Sets a key/value pair in the message, where the value is a pointer. The pointer is stored as a string.
167 *
168 * @param[in] key Key
169 * @param[in] value Pointer value
170 */
171void LLPluginMessage::setValuePointer(const std::string &key, void* value)
172{
173 std::stringstream temp;
174 // iostreams should output pointer values in hex with an initial 0x by default.
175 temp << value;
176 setValue(key, temp.str());
177}
178
179/**
180 * Gets the message class.
181 *
182 * @return Message class
183 */
184std::string LLPluginMessage::getClass(void) const
185{
186 return mMessage["class"];
187}
188
189/**
190 * Gets the message name.
191 *
192 * @return Message name
193 */
194std::string LLPluginMessage::getName(void) const
195{
196 return mMessage["name"];
197}
198
199/**
200 * Returns true if the specified key exists in this message (useful for optional parameters).
201 *
202 * @param[in] key Key
203 *
204 * @return True if key exists, false otherwise.
205 */
206bool LLPluginMessage::hasValue(const std::string &key) const
207{
208 bool result = false;
209
210 if(mMessage["params"].has(key))
211 {
212 result = true;
213 }
214
215 return result;
216}
217
218/**
219 * Gets the value of a key as a string. If the key does not exist, an empty string will be returned.
220 *
221 * @param[in] key Key
222 *
223 * @return String value of key if key exists, empty string if key does not exist.
224 */
225std::string LLPluginMessage::getValue(const std::string &key) const
226{
227 std::string result;
228
229 if(mMessage["params"].has(key))
230 {
231 result = mMessage["params"][key].asString();
232 }
233
234 return result;
235}
236
237/**
238 * Gets the value of a key as LLSD. If the key does not exist, a null LLSD will be returned.
239 *
240 * @param[in] key Key
241 *
242 * @return LLSD value of key if key exists, null LLSD if key does not exist.
243 */
244LLSD LLPluginMessage::getValueLLSD(const std::string &key) const
245{
246 LLSD result;
247
248 if(mMessage["params"].has(key))
249 {
250 result = mMessage["params"][key];
251 }
252
253 return result;
254}
255
256/**
257 * Gets the value of a key as signed 32-bit int. If the key does not exist, 0 will be returned.
258 *
259 * @param[in] key Key
260 *
261 * @return Signed 32-bit int value of key if key exists, 0 if key does not exist.
262 */
263S32 LLPluginMessage::getValueS32(const std::string &key) const
264{
265 S32 result = 0;
266
267 if(mMessage["params"].has(key))
268 {
269 result = mMessage["params"][key].asInteger();
270 }
271
272 return result;
273}
274
275/**
276 * Gets the value of a key as unsigned 32-bit int. If the key does not exist, 0 will be returned.
277 *
278 * @param[in] key Key
279 *
280 * @return Unsigned 32-bit int value of key if key exists, 0 if key does not exist.
281 */
282U32 LLPluginMessage::getValueU32(const std::string &key) const
283{
284 U32 result = 0;
285
286 if(mMessage["params"].has(key))
287 {
288 std::string value = mMessage["params"][key].asString();
289
290 result = (U32)strtoul(value.c_str(), NULL, 16);
291 }
292
293 return result;
294}
295
296/**
297 * Gets the value of a key as a bool. If the key does not exist, false will be returned.
298 *
299 * @param[in] key Key
300 *
301 * @return Boolean value of key if it exists, false otherwise.
302 */
303bool LLPluginMessage::getValueBoolean(const std::string &key) const
304{
305 bool result = false;
306
307 if(mMessage["params"].has(key))
308 {
309 result = mMessage["params"][key].asBoolean();
310 }
311
312 return result;
313}
314
315/**
316 * Gets the value of a key as a double. If the key does not exist, 0 will be returned.
317 *
318 * @param[in] key Key
319 *
320 * @return Value as a double if key exists, 0 otherwise.
321 */
322F64 LLPluginMessage::getValueReal(const std::string &key) const
323{
324 F64 result = 0.0f;
325
326 if(mMessage["params"].has(key))
327 {
328 result = mMessage["params"][key].asReal();
329 }
330
331 return result;
332}
333
334/**
335 * Gets the value of a key as a pointer. If the key does not exist, NULL will be returned.
336 *
337 * @param[in] key Key
338 *
339 * @return Pointer value if key exists, NULL otherwise.
340 */
341void* LLPluginMessage::getValuePointer(const std::string &key) const
342{
343 void* result = NULL;
344
345 if(mMessage["params"].has(key))
346 {
347 std::string value = mMessage["params"][key].asString();
348
349 result = (void*)llstrtou64(value.c_str(), NULL, 16);
350 }
351
352 return result;
353}
354
355/**
356 * Flatten the message into a string.
357 *
358 * @return Message as a string.
359 */
360std::string LLPluginMessage::generate(void) const
361{
362 std::ostringstream result;
363
364 // Pretty XML may be slightly easier to deal with while debugging...
365// LLSDSerialize::toXML(mMessage, result);
366 LLSDSerialize::toPrettyXML(mMessage, result);
367
368 return result.str();
369}
370
371/**
372 * Parse an incoming message into component parts. Clears all existing state before starting the parse.
373 *
374 * @return Returns -1 on failure, otherwise returns the number of key/value pairs in the incoming message.
375 */
376int LLPluginMessage::parse(const std::string &message)
377{
378 // clear any previous state
379 clear();
380
381 std::istringstream input(message);
382
383 S32 parse_result = LLSDSerialize::fromXML(mMessage, input);
384
385 return (int)parse_result;
386}
387
388
389/**
390 * Destructor
391 */
392LLPluginMessageListener::~LLPluginMessageListener()
393{
394 // TODO: should listeners have a way to ensure they're removed from dispatcher lists when deleted?
395}
396
397
398/**
399 * Destructor
400 */
401LLPluginMessageDispatcher::~LLPluginMessageDispatcher()
402{
403
404}
405
406/**
407 * Add a message listener. TODO:DOC need more info on what uses this. when are multiple listeners needed?
408 *
409 * @param[in] listener Message listener
410 */
411void LLPluginMessageDispatcher::addPluginMessageListener(LLPluginMessageListener *listener)
412{
413 mListeners.insert(listener);
414}
415
416/**
417 * Remove a message listener.
418 *
419 * @param[in] listener Message listener
420 */
421void LLPluginMessageDispatcher::removePluginMessageListener(LLPluginMessageListener *listener)
422{
423 mListeners.erase(listener);
424}
425
426/**
427 * Distribute a message to all message listeners.
428 *
429 * @param[in] message Message
430 */
431void LLPluginMessageDispatcher::dispatchPluginMessage(const LLPluginMessage &message)
432{
433 for (listener_set_t::iterator it = mListeners.begin();
434 it != mListeners.end();
435 )
436 {
437 LLPluginMessageListener* listener = *it;
438 listener->receivePluginMessage(message);
439 // In case something deleted an entry.
440 it = mListeners.upper_bound(listener);
441 }
442}
diff --git a/linden/indra/llplugin/llpluginmessage.h b/linden/indra/llplugin/llpluginmessage.h
new file mode 100644
index 0000000..8bcb896
--- /dev/null
+++ b/linden/indra/llplugin/llpluginmessage.h
@@ -0,0 +1,142 @@
1/**
2 * @file llpluginmessage.h
3 * @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#ifndef LL_LLPLUGINMESSAGE_H
34#define LL_LLPLUGINMESSAGE_H
35
36#include "llsd.h"
37
38/**
39 * @brief LLPluginMessage encapsulates the serialization/deserialization of messages passed to and from plugins.
40 */
41class LLPluginMessage
42{
43 LOG_CLASS(LLPluginMessage);
44public:
45 LLPluginMessage();
46 LLPluginMessage(const LLPluginMessage &p);
47 LLPluginMessage(const std::string &message_class, const std::string &message_name);
48 ~LLPluginMessage();
49
50 // reset all internal state
51 void clear(void);
52
53 // Sets the message class and name
54 // Also has the side-effect of clearing any key/value pairs in the message.
55 void setMessage(const std::string &message_class, const std::string &message_name);
56
57 // Sets a key/value pair in the message
58 void setValue(const std::string &key, const std::string &value);
59 void setValueLLSD(const std::string &key, const LLSD &value);
60 void setValueS32(const std::string &key, S32 value);
61 void setValueU32(const std::string &key, U32 value);
62 void setValueBoolean(const std::string &key, bool value);
63 void setValueReal(const std::string &key, F64 value);
64 void setValuePointer(const std::string &key, void *value);
65
66 std::string getClass(void) const;
67 std::string getName(void) const;
68
69 // Returns true if the specified key exists in this message (useful for optional parameters)
70 bool hasValue(const std::string &key) const;
71
72 // 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.
73 std::string getValue(const std::string &key) const;
74
75 // get the value of a particular key as LLSD. If the key doesn't exist in the message, a null LLSD will be returned.
76 LLSD getValueLLSD(const std::string &key) const;
77
78 // get the value of a key as a S32. If the value wasn't set as a S32, behavior is undefined.
79 S32 getValueS32(const std::string &key) const;
80
81 // get the value of a key as a U32. Since there isn't an LLSD type for this, we use a hexadecimal string instead.
82 U32 getValueU32(const std::string &key) const;
83
84 // get the value of a key as a Boolean.
85 bool getValueBoolean(const std::string &key) const;
86
87 // get the value of a key as a float.
88 F64 getValueReal(const std::string &key) const;
89
90 // get the value of a key as a pointer.
91 void* getValuePointer(const std::string &key) const;
92
93 // Flatten the message into a string
94 std::string generate(void) const;
95
96 // Parse an incoming message into component parts
97 // (this clears out all existing state before starting the parse)
98 // Returns -1 on failure, otherwise returns the number of key/value pairs in the message.
99 int parse(const std::string &message);
100
101
102private:
103
104 LLSD mMessage;
105
106};
107
108/**
109 * @brief Listener for plugin messages.
110 */
111class LLPluginMessageListener
112{
113public:
114 virtual ~LLPluginMessageListener();
115 /** Plugin receives message from plugin loader shell. */
116 virtual void receivePluginMessage(const LLPluginMessage &message) = 0;
117
118};
119
120/**
121 * @brief Dispatcher for plugin messages.
122 *
123 * Manages the set of plugin message listeners and distributes messages to plugin message listeners.
124 */
125class LLPluginMessageDispatcher
126{
127public:
128 virtual ~LLPluginMessageDispatcher();
129
130 void addPluginMessageListener(LLPluginMessageListener *);
131 void removePluginMessageListener(LLPluginMessageListener *);
132protected:
133 void dispatchPluginMessage(const LLPluginMessage &message);
134
135 /** A set of message listeners. */
136 typedef std::set<LLPluginMessageListener*> listener_set_t;
137 /** The set of message listeners. */
138 listener_set_t mListeners;
139};
140
141
142#endif // LL_LLPLUGINMESSAGE_H
diff --git a/linden/indra/llplugin/llpluginmessageclasses.h b/linden/indra/llplugin/llpluginmessageclasses.h
new file mode 100644
index 0000000..1f60d5e
--- /dev/null
+++ b/linden/indra/llplugin/llpluginmessageclasses.h
@@ -0,0 +1,57 @@
1/**
2 * @file llpluginmessageclasses.h
3 * @brief This file defines the versions of existing message classes for LLPluginMessage.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#ifndef LL_LLPLUGINMESSAGECLASSES_H
34#define LL_LLPLUGINMESSAGECLASSES_H
35
36// Version strings for each plugin message class.
37// Backwards-compatible changes (i.e. changes which only add new messges) should increment the minor version (i.e. "1.0" -> "1.1").
38// Non-backwards-compatible changes (which delete messages or change their semantics) should increment the major version (i.e. "1.1" -> "2.0").
39// Plugins will supply the set of message classes they understand, with version numbers, as part of their init_response message.
40// The contents and semantics of the base:init message must NEVER change in a non-backwards-compatible way, as a special case.
41
42#define LLPLUGIN_MESSAGE_CLASS_INTERNAL "internal"
43#define LLPLUGIN_MESSAGE_CLASS_INTERNAL_VERSION "1.0"
44
45#define LLPLUGIN_MESSAGE_CLASS_BASE "base"
46#define LLPLUGIN_MESSAGE_CLASS_BASE_VERSION "1.0"
47
48#define LLPLUGIN_MESSAGE_CLASS_MEDIA "media"
49#define LLPLUGIN_MESSAGE_CLASS_MEDIA_VERSION "1.0"
50
51#define LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER "media_browser"
52#define LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER_VERSION "1.0"
53
54#define LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME "media_time"
55#define LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME_VERSION "1.0"
56
57#endif // LL_LLPLUGINMESSAGECLASSES_H
diff --git a/linden/indra/llplugin/llpluginmessagepipe.cpp b/linden/indra/llplugin/llpluginmessagepipe.cpp
new file mode 100644
index 0000000..209f49f
--- /dev/null
+++ b/linden/indra/llplugin/llpluginmessagepipe.cpp
@@ -0,0 +1,316 @@
1/**
2 * @file llpluginmessagepipe.cpp
3 * @brief Classes that implement connections from the plugin system to pipes/pumps.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#include "linden_common.h"
34
35#include "llpluginmessagepipe.h"
36#include "llbufferstream.h"
37
38#include "llapr.h"
39
40static const char MESSAGE_DELIMITER = '\0';
41
42LLPluginMessagePipeOwner::LLPluginMessagePipeOwner() :
43 mMessagePipe(NULL),
44 mSocketError(APR_SUCCESS)
45{
46}
47
48// virtual
49LLPluginMessagePipeOwner::~LLPluginMessagePipeOwner()
50{
51 killMessagePipe();
52}
53
54// virtual
55apr_status_t LLPluginMessagePipeOwner::socketError(apr_status_t error)
56{
57 mSocketError = error;
58 return error;
59};
60
61//virtual
62void LLPluginMessagePipeOwner::setMessagePipe(LLPluginMessagePipe *read_pipe)
63{
64 // Save a reference to this pipe
65 mMessagePipe = read_pipe;
66}
67
68bool LLPluginMessagePipeOwner::canSendMessage(void)
69{
70 return (mMessagePipe != NULL);
71}
72
73bool LLPluginMessagePipeOwner::writeMessageRaw(const std::string &message)
74{
75 bool result = true;
76 if(mMessagePipe != NULL)
77 {
78 result = mMessagePipe->addMessage(message);
79 }
80 else
81 {
82 LL_WARNS("Plugin") << "dropping message: " << message << LL_ENDL;
83 result = false;
84 }
85
86 return result;
87}
88
89void LLPluginMessagePipeOwner::killMessagePipe(void)
90{
91 if(mMessagePipe != NULL)
92 {
93 delete mMessagePipe;
94 mMessagePipe = NULL;
95 }
96}
97
98LLPluginMessagePipe::LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket)
99{
100 mOwner = owner;
101 mOwner->setMessagePipe(this);
102 mSocket = socket;
103}
104
105LLPluginMessagePipe::~LLPluginMessagePipe()
106{
107 if(mOwner != NULL)
108 {
109 mOwner->setMessagePipe(NULL);
110 }
111}
112
113bool LLPluginMessagePipe::addMessage(const std::string &message)
114{
115 // queue the message for later output
116 mOutput += message;
117 mOutput += MESSAGE_DELIMITER; // message separator
118
119 return true;
120}
121
122void LLPluginMessagePipe::clearOwner(void)
123{
124 // The owner is done with this pipe. The next call to process_impl should send any remaining data and exit.
125 mOwner = NULL;
126}
127
128void LLPluginMessagePipe::setSocketTimeout(apr_interval_time_t timeout_usec)
129{
130 // We never want to sleep forever, so force negative timeouts to become non-blocking.
131
132 // according to this page: http://dev.ariel-networks.com/apr/apr-tutorial/html/apr-tutorial-13.html
133 // blocking/non-blocking with apr sockets is somewhat non-portable.
134
135 if(timeout_usec <= 0)
136 {
137 // Make the socket non-blocking
138 apr_socket_opt_set(mSocket->getSocket(), APR_SO_NONBLOCK, 1);
139 apr_socket_timeout_set(mSocket->getSocket(), 0);
140 }
141 else
142 {
143 // Make the socket blocking-with-timeout
144 apr_socket_opt_set(mSocket->getSocket(), APR_SO_NONBLOCK, 1);
145 apr_socket_timeout_set(mSocket->getSocket(), timeout_usec);
146 }
147}
148
149bool LLPluginMessagePipe::pump(F64 timeout)
150{
151 bool result = true;
152
153 if(mSocket)
154 {
155 apr_status_t status;
156 apr_size_t size;
157
158 if(!mOutput.empty())
159 {
160 // write any outgoing messages
161 size = (apr_size_t)mOutput.size();
162
163 setSocketTimeout(0);
164
165// LL_INFOS("Plugin") << "before apr_socket_send, size = " << size << LL_ENDL;
166
167 status = apr_socket_send(
168 mSocket->getSocket(),
169 (const char*)mOutput.data(),
170 &size);
171
172// LL_INFOS("Plugin") << "after apr_socket_send, size = " << size << LL_ENDL;
173
174 if(status == APR_SUCCESS)
175 {
176 // success
177 mOutput = mOutput.substr(size);
178 }
179 else if(APR_STATUS_IS_EAGAIN(status))
180 {
181 // Socket buffer is full...
182 // remove the written part from the buffer and try again later.
183 mOutput = mOutput.substr(size);
184 }
185 else
186 {
187 // some other error
188 // Treat this as fatal.
189 ll_apr_warn_status(status);
190
191 if(mOwner)
192 {
193 mOwner->socketError(status);
194 }
195 result = false;
196 }
197 }
198
199 // FIXME: For some reason, the apr timeout stuff isn't working properly on windows.
200 // Until such time as we figure out why, don't try to use the socket timeout -- just sleep here instead.
201#if LL_WINDOWS
202 if(result)
203 {
204 if(timeout != 0.0f)
205 {
206 ms_sleep((int)(timeout * 1000.0f));
207 timeout = 0.0f;
208 }
209 }
210#endif
211
212 // Check for incoming messages
213 if(result)
214 {
215 char input_buf[1024];
216 apr_size_t request_size;
217
218 // Start out by reading one byte, so that any data received will wake us up.
219 request_size = 1;
220
221 // and use the timeout so we'll sleep if no data is available.
222 setSocketTimeout((apr_interval_time_t)(timeout * 1000000));
223
224 while(1)
225 {
226 size = request_size;
227
228// LL_INFOS("Plugin") << "before apr_socket_recv, size = " << size << LL_ENDL;
229
230 status = apr_socket_recv(
231 mSocket->getSocket(),
232 input_buf,
233 &size);
234
235// LL_INFOS("Plugin") << "after apr_socket_recv, size = " << size << LL_ENDL;
236
237 if(size > 0)
238 mInput.append(input_buf, size);
239
240 if(status == APR_SUCCESS)
241 {
242// llinfos << "success, read " << size << llendl;
243
244 if(size != request_size)
245 {
246 // This was a short read, so we're done.
247 break;
248 }
249 }
250 else if(APR_STATUS_IS_TIMEUP(status))
251 {
252// llinfos << "TIMEUP, read " << size << llendl;
253
254 // Timeout was hit. Since the initial read is 1 byte, this should never be a partial read.
255 break;
256 }
257 else if(APR_STATUS_IS_EAGAIN(status))
258 {
259// llinfos << "EAGAIN, read " << size << llendl;
260
261 // We've been doing partial reads, and we're done now.
262 break;
263 }
264 else
265 {
266 // some other error
267 // Treat this as fatal.
268 ll_apr_warn_status(status);
269
270 if(mOwner)
271 {
272 mOwner->socketError(status);
273 }
274 result = false;
275 break;
276 }
277
278 // Second and subsequent reads should not use the timeout
279 setSocketTimeout(0);
280 // and should try to fill the input buffer
281 request_size = sizeof(input_buf);
282 }
283
284 processInput();
285 }
286 }
287
288 if(!result)
289 {
290 // If we got an error, we're done.
291 LL_INFOS("Plugin") << "Error from socket, cleaning up." << LL_ENDL;
292 delete this;
293 }
294
295 return result;
296}
297
298void LLPluginMessagePipe::processInput(void)
299{
300 // Look for input delimiter(s) in the input buffer.
301 int start = 0;
302 int delim;
303 while((delim = mInput.find(MESSAGE_DELIMITER, start)) != std::string::npos)
304 {
305 // Let the owner process this message
306 mOwner->receiveMessageRaw(mInput.substr(start, delim - start));
307
308 start = delim + 1;
309 }
310
311 // Remove delivered messages from the input buffer.
312 if(start != 0)
313 mInput = mInput.substr(start);
314
315}
316
diff --git a/linden/indra/llplugin/llpluginmessagepipe.h b/linden/indra/llplugin/llpluginmessagepipe.h
new file mode 100644
index 0000000..9bf1781
--- /dev/null
+++ b/linden/indra/llplugin/llpluginmessagepipe.h
@@ -0,0 +1,92 @@
1/**
2 * @file llpluginmessagepipe.h
3 * @brief Classes that implement connections from the plugin system to pipes/pumps.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#ifndef LL_LLPLUGINMESSAGEPIPE_H
34#define LL_LLPLUGINMESSAGEPIPE_H
35
36#include "lliosocket.h"
37
38class LLPluginMessagePipe;
39
40// Inherit from this to be able to receive messages from the LLPluginMessagePipe
41class LLPluginMessagePipeOwner
42{
43 LOG_CLASS(LLPluginMessagePipeOwner);
44public:
45 LLPluginMessagePipeOwner();
46 virtual ~LLPluginMessagePipeOwner();
47 // called with incoming messages
48 virtual void receiveMessageRaw(const std::string &message) = 0;
49 // called when the socket has an error
50 virtual apr_status_t socketError(apr_status_t error);
51
52 // called from LLPluginMessagePipe to manage the connection with LLPluginMessagePipeOwner -- do not use!
53 virtual void setMessagePipe(LLPluginMessagePipe *message_pipe) ;
54
55protected:
56 // returns false if writeMessageRaw() would drop the message
57 bool canSendMessage(void);
58 // call this to send a message over the pipe
59 bool writeMessageRaw(const std::string &message);
60 // call this to close the pipe
61 void killMessagePipe(void);
62
63 LLPluginMessagePipe *mMessagePipe;
64 apr_status_t mSocketError;
65};
66
67class LLPluginMessagePipe
68{
69 LOG_CLASS(LLPluginMessagePipe);
70public:
71 LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket);
72 virtual ~LLPluginMessagePipe();
73
74 bool addMessage(const std::string &message);
75 void clearOwner(void);
76
77 bool pump(F64 timeout = 0.0f);
78
79protected:
80 void processInput(void);
81
82 // used internally by pump()
83 void setSocketTimeout(apr_interval_time_t timeout_usec);
84
85 std::string mInput;
86 std::string mOutput;
87
88 LLPluginMessagePipeOwner *mOwner;
89 LLSocket::ptr_t mSocket;
90};
91
92#endif // LL_LLPLUGINMESSAGE_H
diff --git a/linden/indra/llplugin/llpluginprocesschild.cpp b/linden/indra/llplugin/llpluginprocesschild.cpp
new file mode 100644
index 0000000..9b5eafc
--- /dev/null
+++ b/linden/indra/llplugin/llpluginprocesschild.cpp
@@ -0,0 +1,490 @@
1/**
2 * @file llpluginprocesschild.cpp
3 * @brief LLPluginProcessChild handles the child side of the external-process plugin API.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#include "linden_common.h"
34
35#include "llpluginprocesschild.h"
36#include "llplugininstance.h"
37#include "llpluginmessagepipe.h"
38#include "llpluginmessageclasses.h"
39
40static const F32 HEARTBEAT_SECONDS = 1.0f;
41static const F32 PLUGIN_IDLE_SECONDS = 1.0f / 100.0f; // Each call to idle will give the plugin this much time.
42
43LLPluginProcessChild::LLPluginProcessChild()
44{
45 mInstance = NULL;
46 mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP);
47 mSleepTime = PLUGIN_IDLE_SECONDS; // default: send idle messages at 100Hz
48 mCPUElapsed = 0.0f;
49}
50
51LLPluginProcessChild::~LLPluginProcessChild()
52{
53 if(mInstance != NULL)
54 {
55 sendMessageToPlugin(LLPluginMessage("base", "cleanup"));
56 delete mInstance;
57 mInstance = NULL;
58 }
59}
60
61void LLPluginProcessChild::killSockets(void)
62{
63 killMessagePipe();
64 mSocket.reset();
65}
66
67void LLPluginProcessChild::init(U32 launcher_port)
68{
69 mLauncherHost = LLHost("127.0.0.1", launcher_port);
70 setState(STATE_INITIALIZED);
71}
72
73void LLPluginProcessChild::idle(void)
74{
75 bool idle_again;
76 do
77 {
78 if(mSocketError != APR_SUCCESS)
79 {
80 LL_INFOS("Plugin") << "message pipe is in error state, moving to STATE_ERROR"<< LL_ENDL;
81 setState(STATE_ERROR);
82 }
83
84 if((mState > STATE_INITIALIZED) && (mMessagePipe == NULL))
85 {
86 // The pipe has been closed -- we're done.
87 // TODO: This could be slightly more subtle, but I'm not sure it needs to be.
88 LL_INFOS("Plugin") << "message pipe went away, moving to STATE_ERROR"<< LL_ENDL;
89 setState(STATE_ERROR);
90 }
91
92 // If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState().
93 // 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.
94 // When in doubt, don't do it.
95 idle_again = false;
96
97 if(mInstance != NULL)
98 {
99 // Provide some time to the plugin
100 mInstance->idle();
101 }
102
103 switch(mState)
104 {
105 case STATE_UNINITIALIZED:
106 break;
107
108 case STATE_INITIALIZED:
109 if(mSocket->blockingConnect(mLauncherHost))
110 {
111 // This automatically sets mMessagePipe
112 new LLPluginMessagePipe(this, mSocket);
113
114 setState(STATE_CONNECTED);
115 }
116 else
117 {
118 // connect failed
119 setState(STATE_ERROR);
120 }
121 break;
122
123 case STATE_CONNECTED:
124 sendMessageToParent(LLPluginMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "hello"));
125 setState(STATE_PLUGIN_LOADING);
126 break;
127
128 case STATE_PLUGIN_LOADING:
129 if(!mPluginFile.empty())
130 {
131 mInstance = new LLPluginInstance(this);
132 if(mInstance->load(mPluginFile) == 0)
133 {
134 mHeartbeat.start();
135 mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS);
136 mCPUElapsed = 0.0f;
137 setState(STATE_PLUGIN_LOADED);
138 }
139 else
140 {
141 setState(STATE_ERROR);
142 }
143 }
144 break;
145
146 case STATE_PLUGIN_LOADED:
147 {
148 setState(STATE_PLUGIN_INITIALIZING);
149 LLPluginMessage message("base", "init");
150 message.setValue("user_data_path", mUserDataPath);
151 sendMessageToPlugin(message);
152 }
153 break;
154
155 case STATE_PLUGIN_INITIALIZING:
156 // waiting for init_response...
157 break;
158
159 case STATE_RUNNING:
160 if(mInstance != NULL)
161 {
162 // Provide some time to the plugin
163 LLPluginMessage message("base", "idle");
164 message.setValueReal("time", PLUGIN_IDLE_SECONDS);
165 sendMessageToPlugin(message);
166
167 mInstance->idle();
168
169 if(mHeartbeat.hasExpired())
170 {
171
172 // This just proves that we're not stuck down inside the plugin code.
173 LLPluginMessage heartbeat(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "heartbeat");
174
175 // Calculate the approximage CPU usage fraction (floating point value between 0 and 1) used by the plugin this heartbeat cycle.
176 // Note that this will not take into account any threads or additional processes the plugin spawns, but it's a first approximation.
177 // If we could write OS-specific functions to query the actual CPU usage of this process, that would be a better approximation.
178 heartbeat.setValueReal("cpu_usage", mCPUElapsed / mHeartbeat.getElapsedTimeF64());
179
180 sendMessageToParent(heartbeat);
181
182 mHeartbeat.reset();
183 mHeartbeat.setTimerExpirySec(HEARTBEAT_SECONDS);
184 mCPUElapsed = 0.0f;
185 }
186 }
187 // receivePluginMessage will transition to STATE_UNLOADING
188 break;
189
190 case STATE_UNLOADING:
191 if(mInstance != NULL)
192 {
193 sendMessageToPlugin(LLPluginMessage("base", "cleanup"));
194 delete mInstance;
195 mInstance = NULL;
196 }
197 setState(STATE_UNLOADED);
198 break;
199
200 case STATE_UNLOADED:
201 killSockets();
202 setState(STATE_DONE);
203 break;
204
205 case STATE_ERROR:
206 // Close the socket to the launcher
207 killSockets();
208 // TODO: Where do we go from here? Just exit()?
209 setState(STATE_DONE);
210 break;
211
212 case STATE_DONE:
213 // just sit here.
214 break;
215 }
216
217 } while (idle_again);
218}
219
220void LLPluginProcessChild::sleep(F64 seconds)
221{
222 if(mMessagePipe)
223 {
224 mMessagePipe->pump(seconds);
225 }
226 else
227 {
228 ms_sleep((int)(seconds * 1000.0f));
229 }
230}
231
232void LLPluginProcessChild::pump(void)
233{
234 if(mMessagePipe)
235 {
236 mMessagePipe->pump(0.0f);
237 }
238 else
239 {
240 // Should we warn here?
241 }
242}
243
244
245bool LLPluginProcessChild::isRunning(void)
246{
247 bool result = false;
248
249 if(mState == STATE_RUNNING)
250 result = true;
251
252 return result;
253}
254
255bool LLPluginProcessChild::isDone(void)
256{
257 bool result = false;
258
259 switch(mState)
260 {
261 case STATE_DONE:
262 result = true;
263 break;
264 default:
265 break;
266 }
267
268 return result;
269}
270
271void LLPluginProcessChild::sendMessageToPlugin(const LLPluginMessage &message)
272{
273 std::string buffer = message.generate();
274
275 LL_DEBUGS("Plugin") << "Sending to plugin: " << buffer << LL_ENDL;
276 LLTimer elapsed;
277
278 mInstance->sendMessage(buffer);
279
280 mCPUElapsed += elapsed.getElapsedTimeF64();
281}
282
283void LLPluginProcessChild::sendMessageToParent(const LLPluginMessage &message)
284{
285 std::string buffer = message.generate();
286
287 LL_DEBUGS("Plugin") << "Sending to parent: " << buffer << LL_ENDL;
288
289 writeMessageRaw(buffer);
290}
291
292void LLPluginProcessChild::receiveMessageRaw(const std::string &message)
293{
294 // Incoming message from the TCP Socket
295
296 LL_DEBUGS("Plugin") << "Received from parent: " << message << LL_ENDL;
297
298 bool passMessage = true;
299
300 // FIXME: how should we handle queueing here?
301
302 {
303 // Decode this message
304 LLPluginMessage parsed;
305 parsed.parse(message);
306
307 std::string message_class = parsed.getClass();
308 if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL)
309 {
310 passMessage = false;
311
312 std::string message_name = parsed.getName();
313 if(message_name == "load_plugin")
314 {
315 mPluginFile = parsed.getValue("file");
316 mUserDataPath = parsed.getValue("user_data_path");
317 }
318 else if(message_name == "shm_add")
319 {
320 std::string name = parsed.getValue("name");
321 size_t size = (size_t)parsed.getValueS32("size");
322
323 sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
324 if(iter != mSharedMemoryRegions.end())
325 {
326 // Need to remove the old region first
327 LL_WARNS("Plugin") << "Adding a duplicate shared memory segment!" << LL_ENDL;
328 }
329 else
330 {
331 // This is a new region
332 LLPluginSharedMemory *region = new LLPluginSharedMemory;
333 if(region->attach(name, size))
334 {
335 mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region));
336
337 std::stringstream addr;
338 addr << region->getMappedAddress();
339
340 // Send the add notification to the plugin
341 LLPluginMessage message("base", "shm_added");
342 message.setValue("name", name);
343 message.setValueS32("size", (S32)size);
344 message.setValuePointer("address", region->getMappedAddress());
345 sendMessageToPlugin(message);
346
347 // and send the response to the parent
348 message.setMessage(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add_response");
349 message.setValue("name", name);
350 sendMessageToParent(message);
351 }
352 else
353 {
354 LL_WARNS("Plugin") << "Couldn't create a shared memory segment!" << LL_ENDL;
355 }
356 }
357
358 }
359 else if(message_name == "shm_remove")
360 {
361 std::string name = parsed.getValue("name");
362 sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
363 if(iter != mSharedMemoryRegions.end())
364 {
365 // forward the remove request to the plugin -- its response will trigger us to detach the segment.
366 LLPluginMessage message("base", "shm_remove");
367 message.setValue("name", name);
368 sendMessageToPlugin(message);
369 }
370 else
371 {
372 LL_WARNS("Plugin") << "shm_remove for unknown memory segment!" << LL_ENDL;
373 }
374 }
375 else if(message_name == "sleep_time")
376 {
377 mSleepTime = parsed.getValueReal("time");
378 }
379 else if(message_name == "crash")
380 {
381 // Crash the plugin
382 LL_ERRS("Plugin") << "Plugin crash requested." << LL_ENDL;
383 }
384 else if(message_name == "hang")
385 {
386 // Hang the plugin
387 LL_WARNS("Plugin") << "Plugin hang requested." << LL_ENDL;
388 while(1)
389 {
390 // wheeeeeeeee......
391 }
392 }
393 else
394 {
395 LL_WARNS("Plugin") << "Unknown internal message from parent: " << message_name << LL_ENDL;
396 }
397 }
398 }
399
400 if(passMessage && mInstance != NULL)
401 {
402 LLTimer elapsed;
403
404 mInstance->sendMessage(message);
405
406 mCPUElapsed += elapsed.getElapsedTimeF64();
407 }
408}
409
410/* virtual */
411void LLPluginProcessChild::receivePluginMessage(const std::string &message)
412{
413 LL_DEBUGS("Plugin") << "Received from plugin: " << message << LL_ENDL;
414
415 // Incoming message from the plugin instance
416 bool passMessage = true;
417
418 // FIXME: how should we handle queueing here?
419
420 // Intercept certain base messages (responses to ones sent by this class)
421 {
422 // Decode this message
423 LLPluginMessage parsed;
424 parsed.parse(message);
425 std::string message_class = parsed.getClass();
426 if(message_class == "base")
427 {
428 std::string message_name = parsed.getName();
429 if(message_name == "init_response")
430 {
431 // The plugin has finished initializing.
432 setState(STATE_RUNNING);
433
434 // Don't pass this message up to the parent
435 passMessage = false;
436
437 LLPluginMessage new_message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin_response");
438 LLSD versions = parsed.getValueLLSD("versions");
439 new_message.setValueLLSD("versions", versions);
440
441 if(parsed.hasValue("plugin_version"))
442 {
443 std::string plugin_version = parsed.getValue("plugin_version");
444 new_message.setValueLLSD("plugin_version", plugin_version);
445 }
446
447 // Let the parent know it's loaded and initialized.
448 sendMessageToParent(new_message);
449 }
450 else if(message_name == "shm_remove_response")
451 {
452 // Don't pass this message up to the parent
453 passMessage = false;
454
455 std::string name = parsed.getValue("name");
456 sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
457 if(iter != mSharedMemoryRegions.end())
458 {
459 // detach the shared memory region
460 iter->second->detach();
461
462 // and remove it from our map
463 mSharedMemoryRegions.erase(iter);
464
465 // Finally, send the response to the parent.
466 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove_response");
467 message.setValue("name", name);
468 sendMessageToParent(message);
469 }
470 else
471 {
472 LL_WARNS("Plugin") << "shm_remove_response for unknown memory segment!" << LL_ENDL;
473 }
474 }
475 }
476 }
477
478 if(passMessage)
479 {
480 LL_DEBUGS("Plugin") << "Passing through to parent: " << message << LL_ENDL;
481 writeMessageRaw(message);
482 }
483}
484
485
486void LLPluginProcessChild::setState(EState state)
487{
488 LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL;
489 mState = state;
490};
diff --git a/linden/indra/llplugin/llpluginprocesschild.h b/linden/indra/llplugin/llpluginprocesschild.h
new file mode 100644
index 0000000..16a1ae8
--- /dev/null
+++ b/linden/indra/llplugin/llpluginprocesschild.h
@@ -0,0 +1,112 @@
1/**
2 * @file llpluginprocesschild.h
3 * @brief LLPluginProcessChild handles the child side of the external-process plugin API.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#ifndef LL_LLPLUGINPROCESSCHILD_H
34#define LL_LLPLUGINPROCESSCHILD_H
35
36#include "llpluginmessage.h"
37#include "llpluginmessagepipe.h"
38#include "llplugininstance.h"
39#include "llhost.h"
40#include "llpluginsharedmemory.h"
41
42class LLPluginInstance;
43
44class LLPluginProcessChild: public LLPluginMessagePipeOwner, public LLPluginInstanceMessageListener
45{
46 LOG_CLASS(LLPluginProcessChild);
47public:
48 LLPluginProcessChild();
49 ~LLPluginProcessChild();
50
51 void init(U32 launcher_port);
52 void idle(void);
53 void sleep(F64 seconds);
54 void pump();
55
56 // returns true if the plugin is in the steady state (processing messages)
57 bool isRunning(void);
58
59 // returns true if the plugin is unloaded or we're in an unrecoverable error state.
60 bool isDone(void);
61
62 void killSockets(void);
63
64 F64 getSleepTime(void) const { return mSleepTime; };
65
66 void sendMessageToPlugin(const LLPluginMessage &message);
67 void sendMessageToParent(const LLPluginMessage &message);
68
69 // Inherited from LLPluginMessagePipeOwner
70 /* virtual */ void receiveMessageRaw(const std::string &message);
71
72 // Inherited from LLPluginInstanceMessageListener
73 /* virtual */ void receivePluginMessage(const std::string &message);
74
75private:
76
77 enum EState
78 {
79 STATE_UNINITIALIZED,
80 STATE_INITIALIZED, // init() has been called
81 STATE_CONNECTED, // connected back to launcher
82 STATE_PLUGIN_LOADING, // plugin library needs to be loaded
83 STATE_PLUGIN_LOADED, // plugin library has been loaded
84 STATE_PLUGIN_INITIALIZING, // plugin is processing init message
85 STATE_RUNNING, // steady state (processing messages)
86 STATE_UNLOADING, // plugin has sent shutdown_response and needs to be unloaded
87 STATE_UNLOADED, // plugin has been unloaded
88 STATE_ERROR, // generic bailout state
89 STATE_DONE // state machine will sit in this state after either error or normal termination.
90 };
91 EState mState;
92 void setState(EState state);
93
94 LLHost mLauncherHost;
95 LLSocket::ptr_t mSocket;
96
97 std::string mPluginFile;
98
99 std::string mUserDataPath;
100
101 LLPluginInstance *mInstance;
102
103 typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType;
104 sharedMemoryRegionsType mSharedMemoryRegions;
105
106 LLTimer mHeartbeat;
107 F64 mSleepTime;
108 F64 mCPUElapsed;
109
110};
111
112#endif // LL_LLPLUGINPROCESSCHILD_H
diff --git a/linden/indra/llplugin/llpluginprocessparent.cpp b/linden/indra/llplugin/llpluginprocessparent.cpp
new file mode 100644
index 0000000..bd36d11
--- /dev/null
+++ b/linden/indra/llplugin/llpluginprocessparent.cpp
@@ -0,0 +1,714 @@
1/**
2 * @file llpluginprocessparent.cpp
3 * @brief LLPluginProcessParent handles the parent side of the external-process plugin API.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#include "linden_common.h"
34
35#include "llpluginprocessparent.h"
36#include "llpluginmessagepipe.h"
37#include "llpluginmessageclasses.h"
38
39#include "llapr.h"
40
41//virtual
42LLPluginProcessParentOwner::~LLPluginProcessParentOwner()
43{
44
45}
46
47LLPluginProcessParent::LLPluginProcessParent(LLPluginProcessParentOwner *owner)
48{
49 mOwner = owner;
50 mBoundPort = 0;
51 mState = STATE_UNINITIALIZED;
52 mDisableTimeout = false;
53 mDebug = false;
54
55 mPluginLaunchTimeout = 60.0f;
56 mPluginLockupTimeout = 30.0f;
57
58 // Don't start the timer here -- start it when we actually launch the plugin process.
59 mHeartbeat.stop();
60}
61
62LLPluginProcessParent::~LLPluginProcessParent()
63{
64 LL_DEBUGS("Plugin") << "destructor" << LL_ENDL;
65
66 // Destroy any remaining shared memory regions
67 sharedMemoryRegionsType::iterator iter;
68 while((iter = mSharedMemoryRegions.begin()) != mSharedMemoryRegions.end())
69 {
70 // destroy the shared memory region
71 iter->second->destroy();
72
73 // and remove it from our map
74 mSharedMemoryRegions.erase(iter);
75 }
76
77 // orphaning the process means it won't be killed when the LLProcessLauncher is destructed.
78 // This is what we want -- it should exit cleanly once it notices the sockets have been closed.
79 mProcess.orphan();
80 killSockets();
81}
82
83void LLPluginProcessParent::killSockets(void)
84{
85 killMessagePipe();
86 mListenSocket.reset();
87 mSocket.reset();
88}
89
90void LLPluginProcessParent::errorState(void)
91{
92 if(mState < STATE_RUNNING)
93 setState(STATE_LAUNCH_FAILURE);
94 else
95 setState(STATE_ERROR);
96}
97
98void LLPluginProcessParent::init(const std::string &launcher_filename, const std::string &plugin_filename, bool debug, const std::string &user_data_path)
99{
100 mProcess.setExecutable(launcher_filename);
101 mPluginFile = plugin_filename;
102 mCPUUsage = 0.0f;
103 mDebug = debug;
104 mUserDataPath = user_data_path;
105
106 setState(STATE_INITIALIZED);
107}
108
109bool LLPluginProcessParent::accept()
110{
111 bool result = false;
112
113 apr_status_t status = APR_EGENERAL;
114 apr_socket_t *new_socket = NULL;
115
116 status = apr_socket_accept(
117 &new_socket,
118 mListenSocket->getSocket(),
119 gAPRPoolp);
120
121
122 if(status == APR_SUCCESS)
123 {
124// llinfos << "SUCCESS" << llendl;
125 // Success. Create a message pipe on the new socket
126
127 // we MUST create a new pool for the LLSocket, since it will take ownership of it and delete it in its destructor!
128 apr_pool_t* new_pool = NULL;
129 status = apr_pool_create(&new_pool, gAPRPoolp);
130
131 mSocket = LLSocket::create(new_socket, new_pool);
132 new LLPluginMessagePipe(this, mSocket);
133
134 result = true;
135 }
136 else if(APR_STATUS_IS_EAGAIN(status))
137 {
138// llinfos << "EAGAIN" << llendl;
139
140 // No incoming connections. This is not an error.
141 status = APR_SUCCESS;
142 }
143 else
144 {
145// llinfos << "Error:" << llendl;
146 ll_apr_warn_status(status);
147
148 // Some other error.
149 errorState();
150 }
151
152 return result;
153}
154
155void LLPluginProcessParent::idle(void)
156{
157 bool idle_again;
158
159 do
160 {
161 // Give time to network processing
162 if(mMessagePipe)
163 {
164 if(!mMessagePipe->pump())
165 {
166// LL_WARNS("Plugin") << "Message pipe hit an error state" << LL_ENDL;
167 errorState();
168 }
169 }
170
171 if((mSocketError != APR_SUCCESS) && (mState <= STATE_RUNNING))
172 {
173 // The socket is in an error state -- the plugin is gone.
174 LL_WARNS("Plugin") << "Socket hit an error state (" << mSocketError << ")" << LL_ENDL;
175 errorState();
176 }
177
178 // If a state needs to go directly to another state (as a performance enhancement), it can set idle_again to true after calling setState().
179 // 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.
180 // When in doubt, don't do it.
181 idle_again = false;
182 switch(mState)
183 {
184 case STATE_UNINITIALIZED:
185 break;
186
187 case STATE_INITIALIZED:
188 {
189
190 apr_status_t status = APR_SUCCESS;
191 apr_sockaddr_t* addr = NULL;
192 mListenSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP);
193 mBoundPort = 0;
194
195 // This code is based on parts of LLSocket::create() in lliosocket.cpp.
196
197 status = apr_sockaddr_info_get(
198 &addr,
199 "127.0.0.1",
200 APR_INET,
201 0, // port 0 = ephemeral ("find me a port")
202 0,
203 gAPRPoolp);
204
205 if(ll_apr_warn_status(status))
206 {
207 killSockets();
208 errorState();
209 break;
210 }
211
212 // This allows us to reuse the address on quick down/up. This is unlikely to create problems.
213 ll_apr_warn_status(apr_socket_opt_set(mListenSocket->getSocket(), APR_SO_REUSEADDR, 1));
214
215 status = apr_socket_bind(mListenSocket->getSocket(), addr);
216 if(ll_apr_warn_status(status))
217 {
218 killSockets();
219 errorState();
220 break;
221 }
222
223 // Get the actual port the socket was bound to
224 {
225 apr_sockaddr_t* bound_addr = NULL;
226 if(ll_apr_warn_status(apr_socket_addr_get(&bound_addr, APR_LOCAL, mListenSocket->getSocket())))
227 {
228 killSockets();
229 errorState();
230 break;
231 }
232 mBoundPort = bound_addr->port;
233
234 if(mBoundPort == 0)
235 {
236 LL_WARNS("Plugin") << "Bound port number unknown, bailing out." << LL_ENDL;
237
238 killSockets();
239 errorState();
240 break;
241 }
242 }
243
244 LL_DEBUGS("Plugin") << "Bound tcp socket to port: " << addr->port << LL_ENDL;
245
246 // Make the listen socket non-blocking
247 status = apr_socket_opt_set(mListenSocket->getSocket(), APR_SO_NONBLOCK, 1);
248 if(ll_apr_warn_status(status))
249 {
250 killSockets();
251 errorState();
252 break;
253 }
254
255 apr_socket_timeout_set(mListenSocket->getSocket(), 0);
256 if(ll_apr_warn_status(status))
257 {
258 killSockets();
259 errorState();
260 break;
261 }
262
263 // If it's a stream based socket, we need to tell the OS
264 // to keep a queue of incoming connections for ACCEPT.
265 status = apr_socket_listen(
266 mListenSocket->getSocket(),
267 10); // FIXME: Magic number for queue size
268
269 if(ll_apr_warn_status(status))
270 {
271 killSockets();
272 errorState();
273 break;
274 }
275
276 // If we got here, we're listening.
277 setState(STATE_LISTENING);
278 }
279 break;
280
281 case STATE_LISTENING:
282 {
283 // Launch the plugin process.
284
285 // Only argument to the launcher is the port number we're listening on
286 std::stringstream stream;
287 stream << mBoundPort;
288 mProcess.addArgument(stream.str());
289 if(mProcess.launch() != 0)
290 {
291 errorState();
292 }
293 else
294 {
295 if(mDebug)
296 {
297 #if LL_DARWIN
298 // 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.
299
300 // The command we're constructing would look like this on the command line:
301 // osascript -e 'tell application "Terminal"' -e 'set win to do script "gdb -pid 12345"' -e 'do script "continue" in win' -e 'end tell'
302
303 std::stringstream cmd;
304
305 mDebugger.setExecutable("/usr/bin/osascript");
306 mDebugger.addArgument("-e");
307 mDebugger.addArgument("tell application \"Terminal\"");
308 mDebugger.addArgument("-e");
309 cmd << "set win to do script \"gdb -pid " << mProcess.getProcessID() << "\"";
310 mDebugger.addArgument(cmd.str());
311 mDebugger.addArgument("-e");
312 mDebugger.addArgument("do script \"continue\" in win");
313 mDebugger.addArgument("-e");
314 mDebugger.addArgument("end tell");
315 mDebugger.launch();
316
317 #endif
318 }
319
320 // This will allow us to time out if the process never starts.
321 mHeartbeat.start();
322 mHeartbeat.setTimerExpirySec(mPluginLaunchTimeout);
323 setState(STATE_LAUNCHED);
324 }
325 }
326 break;
327
328 case STATE_LAUNCHED:
329 // waiting for the plugin to connect
330 if(pluginLockedUpOrQuit())
331 {
332 errorState();
333 }
334 else
335 {
336 // Check for the incoming connection.
337 if(accept())
338 {
339 // Stop listening on the server port
340 mListenSocket.reset();
341 setState(STATE_CONNECTED);
342 }
343 }
344 break;
345
346 case STATE_CONNECTED:
347 // waiting for hello message from the plugin
348
349 if(pluginLockedUpOrQuit())
350 {
351 errorState();
352 }
353 break;
354
355 case STATE_HELLO:
356 LL_DEBUGS("Plugin") << "received hello message" << llendl;
357
358 // Send the message to load the plugin
359 {
360 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "load_plugin");
361 message.setValue("file", mPluginFile);
362 message.setValue("user_data_path", mUserDataPath);
363 sendMessage(message);
364 }
365
366 setState(STATE_LOADING);
367 break;
368
369 case STATE_LOADING:
370 // The load_plugin_response message will kick us from here into STATE_RUNNING
371 if(pluginLockedUpOrQuit())
372 {
373 errorState();
374 }
375 break;
376
377 case STATE_RUNNING:
378 if(pluginLockedUpOrQuit())
379 {
380 errorState();
381 }
382 break;
383
384 case STATE_EXITING:
385 if(!mProcess.isRunning())
386 {
387 setState(STATE_CLEANUP);
388 }
389 else if(pluginLockedUp())
390 {
391 LL_WARNS("Plugin") << "timeout in exiting state, bailing out" << llendl;
392 errorState();
393 }
394 break;
395
396 case STATE_LAUNCH_FAILURE:
397 if(mOwner != NULL)
398 {
399 mOwner->pluginLaunchFailed();
400 }
401 setState(STATE_CLEANUP);
402 break;
403
404 case STATE_ERROR:
405 if(mOwner != NULL)
406 {
407 mOwner->pluginDied();
408 }
409 setState(STATE_CLEANUP);
410 break;
411
412 case STATE_CLEANUP:
413 // Don't do a kill here anymore -- closing the sockets is the new 'kill'.
414 mProcess.orphan();
415 killSockets();
416 setState(STATE_DONE);
417 break;
418
419
420 case STATE_DONE:
421 // just sit here.
422 break;
423
424 }
425
426 } while (idle_again);
427}
428
429bool LLPluginProcessParent::isLoading(void)
430{
431 bool result = false;
432
433 if(mState <= STATE_LOADING)
434 result = true;
435
436 return result;
437}
438
439bool LLPluginProcessParent::isRunning(void)
440{
441 bool result = false;
442
443 if(mState == STATE_RUNNING)
444 result = true;
445
446 return result;
447}
448
449bool LLPluginProcessParent::isDone(void)
450{
451 bool result = false;
452
453 if(mState == STATE_DONE)
454 result = true;
455
456 return result;
457}
458
459void LLPluginProcessParent::setSleepTime(F64 sleep_time, bool force_send)
460{
461 if(force_send || (sleep_time != mSleepTime))
462 {
463 // Cache the time locally
464 mSleepTime = sleep_time;
465
466 if(canSendMessage())
467 {
468 // and send to the plugin.
469 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "sleep_time");
470 message.setValueReal("time", mSleepTime);
471 sendMessage(message);
472 }
473 else
474 {
475 // Too early to send -- the load_plugin_response message will trigger us to send mSleepTime later.
476 }
477 }
478}
479
480void LLPluginProcessParent::sendMessage(const LLPluginMessage &message)
481{
482
483 std::string buffer = message.generate();
484 LL_DEBUGS("Plugin") << "Sending: " << buffer << LL_ENDL;
485 writeMessageRaw(buffer);
486}
487
488
489void LLPluginProcessParent::receiveMessageRaw(const std::string &message)
490{
491 LL_DEBUGS("Plugin") << "Received: " << message << LL_ENDL;
492
493 // FIXME: should this go into a queue instead?
494
495 LLPluginMessage parsed;
496 if(parsed.parse(message) != -1)
497 {
498 receiveMessage(parsed);
499 }
500}
501
502void LLPluginProcessParent::receiveMessage(const LLPluginMessage &message)
503{
504 std::string message_class = message.getClass();
505 if(message_class == LLPLUGIN_MESSAGE_CLASS_INTERNAL)
506 {
507 // internal messages should be handled here
508 std::string message_name = message.getName();
509 if(message_name == "hello")
510 {
511 if(mState == STATE_CONNECTED)
512 {
513 // Plugin host has launched. Tell it which plugin to load.
514 setState(STATE_HELLO);
515 }
516 else
517 {
518 LL_WARNS("Plugin") << "received hello message in wrong state -- bailing out" << LL_ENDL;
519 errorState();
520 }
521
522 }
523 else if(message_name == "load_plugin_response")
524 {
525 if(mState == STATE_LOADING)
526 {
527 // Plugin has been loaded.
528
529 mPluginVersionString = message.getValue("plugin_version");
530 LL_INFOS("Plugin") << "plugin version string: " << mPluginVersionString << LL_ENDL;
531
532 // Check which message classes/versions the plugin supports.
533 // TODO: check against current versions
534 // TODO: kill plugin on major mismatches?
535 mMessageClassVersions = message.getValueLLSD("versions");
536 LLSD::map_iterator iter;
537 for(iter = mMessageClassVersions.beginMap(); iter != mMessageClassVersions.endMap(); iter++)
538 {
539 LL_INFOS("Plugin") << "message class: " << iter->first << " -> version: " << iter->second.asString() << LL_ENDL;
540 }
541
542 // Send initial sleep time
543 setSleepTime(mSleepTime, true);
544
545 setState(STATE_RUNNING);
546 }
547 else
548 {
549 LL_WARNS("Plugin") << "received load_plugin_response message in wrong state -- bailing out" << LL_ENDL;
550 errorState();
551 }
552 }
553 else if(message_name == "heartbeat")
554 {
555 // this resets our timer.
556 mHeartbeat.setTimerExpirySec(mPluginLockupTimeout);
557
558 mCPUUsage = message.getValueReal("cpu_usage");
559
560 LL_DEBUGS("Plugin") << "cpu usage reported as " << mCPUUsage << LL_ENDL;
561
562 }
563 else if(message_name == "shm_add_response")
564 {
565 // Nothing to do here.
566 }
567 else if(message_name == "shm_remove_response")
568 {
569 std::string name = message.getValue("name");
570 sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
571
572 if(iter != mSharedMemoryRegions.end())
573 {
574 // destroy the shared memory region
575 iter->second->destroy();
576
577 // and remove it from our map
578 mSharedMemoryRegions.erase(iter);
579 }
580 }
581 else
582 {
583 LL_WARNS("Plugin") << "Unknown internal message from child: " << message_name << LL_ENDL;
584 }
585 }
586 else
587 {
588 if(mOwner != NULL)
589 {
590 mOwner->receivePluginMessage(message);
591 }
592 }
593}
594
595std::string LLPluginProcessParent::addSharedMemory(size_t size)
596{
597 std::string name;
598
599 LLPluginSharedMemory *region = new LLPluginSharedMemory;
600
601 // This is a new region
602 if(region->create(size))
603 {
604 name = region->getName();
605
606 mSharedMemoryRegions.insert(sharedMemoryRegionsType::value_type(name, region));
607
608 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_add");
609 message.setValue("name", name);
610 message.setValueS32("size", (S32)size);
611 sendMessage(message);
612 }
613 else
614 {
615 LL_WARNS("Plugin") << "Couldn't create a shared memory segment!" << LL_ENDL;
616
617 // Don't leak
618 delete region;
619 }
620
621 return name;
622}
623
624void LLPluginProcessParent::removeSharedMemory(const std::string &name)
625{
626 sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
627
628 if(iter != mSharedMemoryRegions.end())
629 {
630 // This segment exists. Send the message to the child to unmap it. The response will cause the parent to unmap our end.
631 LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "shm_remove");
632 message.setValue("name", name);
633 sendMessage(message);
634 }
635 else
636 {
637 LL_WARNS("Plugin") << "Request to remove an unknown shared memory segment." << LL_ENDL;
638 }
639}
640size_t LLPluginProcessParent::getSharedMemorySize(const std::string &name)
641{
642 size_t result = 0;
643
644 sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
645 if(iter != mSharedMemoryRegions.end())
646 {
647 result = iter->second->getSize();
648 }
649
650 return result;
651}
652void *LLPluginProcessParent::getSharedMemoryAddress(const std::string &name)
653{
654 void *result = NULL;
655
656 sharedMemoryRegionsType::iterator iter = mSharedMemoryRegions.find(name);
657 if(iter != mSharedMemoryRegions.end())
658 {
659 result = iter->second->getMappedAddress();
660 }
661
662 return result;
663}
664
665std::string LLPluginProcessParent::getMessageClassVersion(const std::string &message_class)
666{
667 std::string result;
668
669 if(mMessageClassVersions.has(message_class))
670 {
671 result = mMessageClassVersions[message_class].asString();
672 }
673
674 return result;
675}
676
677std::string LLPluginProcessParent::getPluginVersion(void)
678{
679 return mPluginVersionString;
680}
681
682void LLPluginProcessParent::setState(EState state)
683{
684 LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL;
685 mState = state;
686};
687
688bool LLPluginProcessParent::pluginLockedUpOrQuit()
689{
690 bool result = false;
691
692 if(!mDisableTimeout && !mDebug)
693 {
694 if(!mProcess.isRunning())
695 {
696 LL_WARNS("Plugin") << "child exited" << llendl;
697 result = true;
698 }
699 else if(pluginLockedUp())
700 {
701 LL_WARNS("Plugin") << "timeout" << llendl;
702 result = true;
703 }
704 }
705
706 return result;
707}
708
709bool LLPluginProcessParent::pluginLockedUp()
710{
711 // If the timer is running and has expired, the plugin has locked up.
712 return (mHeartbeat.getStarted() && mHeartbeat.hasExpired());
713}
714
diff --git a/linden/indra/llplugin/llpluginprocessparent.h b/linden/indra/llplugin/llpluginprocessparent.h
new file mode 100644
index 0000000..00c60b5
--- /dev/null
+++ b/linden/indra/llplugin/llpluginprocessparent.h
@@ -0,0 +1,169 @@
1/**
2 * @file llpluginprocessparent.h
3 * @brief LLPluginProcessParent handles the parent side of the external-process plugin API.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#ifndef LL_LLPLUGINPROCESSPARENT_H
34#define LL_LLPLUGINPROCESSPARENT_H
35
36#include "llapr.h"
37#include "llprocesslauncher.h"
38#include "llpluginmessage.h"
39#include "llpluginmessagepipe.h"
40#include "llpluginsharedmemory.h"
41
42#include "lliosocket.h"
43
44class LLPluginProcessParentOwner
45{
46public:
47 virtual ~LLPluginProcessParentOwner();
48 virtual void receivePluginMessage(const LLPluginMessage &message) = 0;
49 // This will only be called when the plugin has died unexpectedly
50 virtual void pluginLaunchFailed() {};
51 virtual void pluginDied() {};
52};
53
54class LLPluginProcessParent : public LLPluginMessagePipeOwner
55{
56 LOG_CLASS(LLPluginProcessParent);
57public:
58 LLPluginProcessParent(LLPluginProcessParentOwner *owner);
59 ~LLPluginProcessParent();
60
61 void init(const std::string &launcher_filename, const std::string &plugin_filename, bool debug, const std::string &user_data_path);
62 void idle(void);
63
64 // returns true if the plugin is on its way to steady state
65 bool isLoading(void);
66
67 // returns true if the plugin is in the steady state (processing messages)
68 bool isRunning(void);
69
70 // returns true if the process has exited or we've had a fatal error
71 bool isDone(void);
72
73 void killSockets(void);
74
75 // Go to the proper error state
76 void errorState(void);
77
78 void setSleepTime(F64 sleep_time, bool force_send = false);
79 F64 getSleepTime(void) const { return mSleepTime; };
80
81 void sendMessage(const LLPluginMessage &message);
82
83 void receiveMessage(const LLPluginMessage &message);
84
85 // Inherited from LLPluginMessagePipeOwner
86 void receiveMessageRaw(const std::string &message);
87
88 // 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.
89 // 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.
90 std::string addSharedMemory(size_t size);
91 // Negotiates for the removal of a shared memory segment. It is the caller's responsibility to ensure that nothing touches the memory
92 // after this has been called, since the segment will be unmapped shortly thereafter.
93 void removeSharedMemory(const std::string &name);
94 size_t getSharedMemorySize(const std::string &name);
95 void *getSharedMemoryAddress(const std::string &name);
96
97 // Returns the version string the plugin indicated for the message class, or an empty string if that class wasn't in the list.
98 std::string getMessageClassVersion(const std::string &message_class);
99
100 std::string getPluginVersion(void);
101
102 bool getDisableTimeout() { return mDisableTimeout; };
103 void setDisableTimeout(bool disable) { mDisableTimeout = disable; };
104
105 void setLaunchTimeout(F32 timeout) { mPluginLaunchTimeout = timeout; };
106 void setLockupTimeout(F32 timeout) { mPluginLockupTimeout = timeout; };
107
108 F64 getCPUUsage() { return mCPUUsage; };
109
110private:
111
112 enum EState
113 {
114 STATE_UNINITIALIZED,
115 STATE_INITIALIZED, // init() has been called
116 STATE_LISTENING, // listening for incoming connection
117 STATE_LAUNCHED, // process has been launched
118 STATE_CONNECTED, // process has connected
119 STATE_HELLO, // first message from the plugin process has been received
120 STATE_LOADING, // process has been asked to load the plugin
121 STATE_RUNNING, //
122 STATE_LAUNCH_FAILURE, // Failure before plugin loaded
123 STATE_ERROR, // generic bailout state
124 STATE_CLEANUP, // clean everything up
125 STATE_EXITING, // Tried to kill process, waiting for it to exit
126 STATE_DONE //
127
128 };
129 EState mState;
130 void setState(EState state);
131
132 bool pluginLockedUp();
133 bool pluginLockedUpOrQuit();
134
135 bool accept();
136
137 LLSocket::ptr_t mListenSocket;
138 LLSocket::ptr_t mSocket;
139 U32 mBoundPort;
140
141 LLProcessLauncher mProcess;
142
143 std::string mPluginFile;
144
145 std::string mUserDataPath;
146
147 LLPluginProcessParentOwner *mOwner;
148
149 typedef std::map<std::string, LLPluginSharedMemory*> sharedMemoryRegionsType;
150 sharedMemoryRegionsType mSharedMemoryRegions;
151
152 LLSD mMessageClassVersions;
153 std::string mPluginVersionString;
154
155 LLTimer mHeartbeat;
156 F64 mSleepTime;
157 F64 mCPUUsage;
158
159 bool mDisableTimeout;
160 bool mDebug;
161
162 LLProcessLauncher mDebugger;
163
164 F32 mPluginLaunchTimeout; // Somewhat longer timeout for initial launch.
165 F32 mPluginLockupTimeout; // If we don't receive a heartbeat in this many seconds, we declare the plugin locked up.
166
167};
168
169#endif // LL_LLPLUGINPROCESSPARENT_H
diff --git a/linden/indra/llplugin/llpluginsharedmemory.cpp b/linden/indra/llplugin/llpluginsharedmemory.cpp
new file mode 100644
index 0000000..2be4648
--- /dev/null
+++ b/linden/indra/llplugin/llpluginsharedmemory.cpp
@@ -0,0 +1,506 @@
1/**
2 * @file llpluginsharedmemory.cpp
3 * @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#include "linden_common.h"
34
35#include "llpluginsharedmemory.h"
36
37// on Mac and Linux, we use the native shm_open/mmap interface by using
38// #define USE_SHM_OPEN_SHARED_MEMORY 1
39// in the appropriate sections below.
40
41// For Windows, use:
42// #define USE_WIN32_SHARED_MEMORY 1
43
44// If we ever want to fall back to the apr implementation for a platform, use:
45// #define USE_APR_SHARED_MEMORY 1
46
47#if LL_WINDOWS
48// #define USE_APR_SHARED_MEMORY 1
49 #define USE_WIN32_SHARED_MEMORY 1
50#elif LL_DARWIN
51 #define USE_SHM_OPEN_SHARED_MEMORY 1
52#elif LL_LINUX
53 #define USE_SHM_OPEN_SHARED_MEMORY 1
54#endif
55
56
57// FIXME: This path thing is evil and unacceptable.
58#if LL_WINDOWS
59 #define APR_SHARED_MEMORY_PREFIX_STRING "C:\\LLPlugin_"
60 // Apparnently using the "Global\\" prefix here only works from administrative accounts under Vista.
61 // Other options I've seen referenced are "Local\\" and "Session\\".
62 #define WIN32_SHARED_MEMORY_PREFIX_STRING "Local\\LL_"
63#else
64 // mac and linux
65 #define APR_SHARED_MEMORY_PREFIX_STRING "/tmp/LLPlugin_"
66 #define SHM_OPEN_SHARED_MEMORY_PREFIX_STRING "/LL"
67#endif
68
69#if USE_APR_SHARED_MEMORY
70 #include "llapr.h"
71 #include "apr_shm.h"
72#elif USE_SHM_OPEN_SHARED_MEMORY
73 #include <sys/fcntl.h>
74 #include <sys/mman.h>
75 #include <errno.h>
76#elif USE_WIN32_SHARED_MEMORY
77#include <windows.h>
78#endif // USE_APR_SHARED_MEMORY
79
80
81int LLPluginSharedMemory::sSegmentNumber = 0;
82
83std::string LLPluginSharedMemory::createName(void)
84{
85 std::stringstream newname;
86
87#if LL_WINDOWS
88 newname << GetCurrentProcessId();
89#else // LL_WINDOWS
90 newname << getpid();
91#endif // LL_WINDOWS
92
93 newname << "_" << sSegmentNumber++;
94
95 return newname.str();
96}
97
98/**
99 * @brief LLPluginSharedMemoryImpl is the platform-dependent implementation of LLPluginSharedMemory. TODO:DOC is this necessary/sufficient? kinda obvious.
100 *
101 */
102class LLPluginSharedMemoryPlatformImpl
103{
104public:
105 LLPluginSharedMemoryPlatformImpl();
106 ~LLPluginSharedMemoryPlatformImpl();
107
108#if USE_APR_SHARED_MEMORY
109 apr_shm_t* mAprSharedMemory;
110#elif USE_SHM_OPEN_SHARED_MEMORY
111 int mSharedMemoryFD;
112#elif USE_WIN32_SHARED_MEMORY
113 HANDLE mMapFile;
114#endif
115
116};
117
118/**
119 * Constructor. Creates a shared memory segment.
120 */
121LLPluginSharedMemory::LLPluginSharedMemory()
122{
123 mSize = 0;
124 mMappedAddress = NULL;
125 mNeedsDestroy = false;
126
127 mImpl = new LLPluginSharedMemoryPlatformImpl;
128}
129
130/**
131 * Destructor. Uses destroy() and detach() to ensure shared memory segment is cleaned up.
132 */
133LLPluginSharedMemory::~LLPluginSharedMemory()
134{
135 if(mNeedsDestroy)
136 destroy();
137 else
138 detach();
139
140 unlink();
141
142 delete mImpl;
143}
144
145#if USE_APR_SHARED_MEMORY
146// MARK: apr implementation
147
148LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl()
149{
150 mAprSharedMemory = NULL;
151}
152
153LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl()
154{
155
156}
157
158bool LLPluginSharedMemory::map(void)
159{
160 mMappedAddress = apr_shm_baseaddr_get(mImpl->mAprSharedMemory);
161 if(mMappedAddress == NULL)
162 {
163 return false;
164 }
165
166 return true;
167}
168
169bool LLPluginSharedMemory::unmap(void)
170{
171 // This is a no-op under apr.
172 return true;
173}
174
175bool LLPluginSharedMemory::close(void)
176{
177 // This is a no-op under apr.
178 return true;
179}
180
181bool LLPluginSharedMemory::unlink(void)
182{
183 // This is a no-op under apr.
184 return true;
185}
186
187
188bool LLPluginSharedMemory::create(size_t size)
189{
190 mName = APR_SHARED_MEMORY_PREFIX_STRING;
191 mName += createName();
192 mSize = size;
193
194 apr_status_t status = apr_shm_create( &(mImpl->mAprSharedMemory), mSize, mName.c_str(), gAPRPoolp );
195
196 if(ll_apr_warn_status(status))
197 {
198 return false;
199 }
200
201 mNeedsDestroy = true;
202
203 return map();
204}
205
206bool LLPluginSharedMemory::destroy(void)
207{
208 if(mImpl->mAprSharedMemory)
209 {
210 apr_status_t status = apr_shm_destroy(mImpl->mAprSharedMemory);
211 if(ll_apr_warn_status(status))
212 {
213 // TODO: Is this a fatal error? I think not...
214 }
215 mImpl->mAprSharedMemory = NULL;
216 }
217
218 return true;
219}
220
221bool LLPluginSharedMemory::attach(const std::string &name, size_t size)
222{
223 mName = name;
224 mSize = size;
225
226 apr_status_t status = apr_shm_attach( &(mImpl->mAprSharedMemory), mName.c_str(), gAPRPoolp );
227
228 if(ll_apr_warn_status(status))
229 {
230 return false;
231 }
232
233 return map();
234}
235
236
237bool LLPluginSharedMemory::detach(void)
238{
239 if(mImpl->mAprSharedMemory)
240 {
241 apr_status_t status = apr_shm_detach(mImpl->mAprSharedMemory);
242 if(ll_apr_warn_status(status))
243 {
244 // TODO: Is this a fatal error? I think not...
245 }
246 mImpl->mAprSharedMemory = NULL;
247 }
248
249 return true;
250}
251
252
253#elif USE_SHM_OPEN_SHARED_MEMORY
254// MARK: shm_open/mmap implementation
255
256LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl()
257{
258 mSharedMemoryFD = -1;
259}
260
261LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl()
262{
263}
264
265bool LLPluginSharedMemory::map(void)
266{
267 mMappedAddress = ::mmap(NULL, mSize, PROT_READ | PROT_WRITE, MAP_SHARED, mImpl->mSharedMemoryFD, 0);
268 if(mMappedAddress == NULL)
269 {
270 return false;
271 }
272
273 LL_DEBUGS("Plugin") << "memory mapped at " << mMappedAddress << LL_ENDL;
274
275 return true;
276}
277
278bool LLPluginSharedMemory::unmap(void)
279{
280 if(mMappedAddress != NULL)
281 {
282 LL_DEBUGS("Plugin") << "calling munmap(" << mMappedAddress << ", " << mSize << ")" << LL_ENDL;
283 if(::munmap(mMappedAddress, mSize) == -1)
284 {
285 // TODO: Is this a fatal error? I think not...
286 }
287
288 mMappedAddress = NULL;
289 }
290
291 return true;
292}
293
294bool LLPluginSharedMemory::close(void)
295{
296 if(mImpl->mSharedMemoryFD != -1)
297 {
298 LL_DEBUGS("Plugin") << "calling close(" << mImpl->mSharedMemoryFD << ")" << LL_ENDL;
299 if(::close(mImpl->mSharedMemoryFD) == -1)
300 {
301 // TODO: Is this a fatal error? I think not...
302 }
303
304 mImpl->mSharedMemoryFD = -1;
305 }
306 return true;
307}
308
309bool LLPluginSharedMemory::unlink(void)
310{
311 if(!mName.empty())
312 {
313 if(::shm_unlink(mName.c_str()) == -1)
314 {
315 return false;
316 }
317 }
318
319 return true;
320}
321
322
323bool LLPluginSharedMemory::create(size_t size)
324{
325 mName = SHM_OPEN_SHARED_MEMORY_PREFIX_STRING;
326 mName += createName();
327 mSize = size;
328
329 // Preemptive unlink, just in case something didn't get cleaned up.
330 unlink();
331
332 mImpl->mSharedMemoryFD = ::shm_open(mName.c_str(), O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
333 if(mImpl->mSharedMemoryFD == -1)
334 {
335 return false;
336 }
337
338 mNeedsDestroy = true;
339
340 if(::ftruncate(mImpl->mSharedMemoryFD, mSize) == -1)
341 {
342 return false;
343 }
344
345
346 return map();
347}
348
349bool LLPluginSharedMemory::destroy(void)
350{
351 unmap();
352 close();
353
354 return true;
355}
356
357
358bool LLPluginSharedMemory::attach(const std::string &name, size_t size)
359{
360 mName = name;
361 mSize = size;
362
363 mImpl->mSharedMemoryFD = ::shm_open(mName.c_str(), O_RDWR, S_IRUSR | S_IWUSR);
364 if(mImpl->mSharedMemoryFD == -1)
365 {
366 return false;
367 }
368
369 // unlink here so the segment will be cleaned up automatically after the last close.
370 unlink();
371
372 return map();
373}
374
375bool LLPluginSharedMemory::detach(void)
376{
377 unmap();
378 close();
379 return true;
380}
381
382#elif USE_WIN32_SHARED_MEMORY
383// MARK: Win32 CreateFileMapping-based implementation
384
385// Reference: http://msdn.microsoft.com/en-us/library/aa366551(VS.85).aspx
386
387LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl()
388{
389 mMapFile = NULL;
390}
391
392LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl()
393{
394
395}
396
397bool LLPluginSharedMemory::map(void)
398{
399 mMappedAddress = MapViewOfFile(
400 mImpl->mMapFile, // handle to map object
401 FILE_MAP_ALL_ACCESS, // read/write permission
402 0,
403 0,
404 mSize);
405
406 if(mMappedAddress == NULL)
407 {
408 LL_WARNS("Plugin") << "MapViewOfFile failed: " << GetLastError() << LL_ENDL;
409 return false;
410 }
411
412 LL_DEBUGS("Plugin") << "memory mapped at " << mMappedAddress << LL_ENDL;
413
414 return true;
415}
416
417bool LLPluginSharedMemory::unmap(void)
418{
419 if(mMappedAddress != NULL)
420 {
421 UnmapViewOfFile(mMappedAddress);
422 mMappedAddress = NULL;
423 }
424
425 return true;
426}
427
428bool LLPluginSharedMemory::close(void)
429{
430 if(mImpl->mMapFile != NULL)
431 {
432 CloseHandle(mImpl->mMapFile);
433 mImpl->mMapFile = NULL;
434 }
435
436 return true;
437}
438
439bool LLPluginSharedMemory::unlink(void)
440{
441 // This is a no-op on Windows.
442 return true;
443}
444
445
446bool LLPluginSharedMemory::create(size_t size)
447{
448 mName = WIN32_SHARED_MEMORY_PREFIX_STRING;
449 mName += createName();
450 mSize = size;
451
452 mImpl->mMapFile = CreateFileMappingA(
453 INVALID_HANDLE_VALUE, // use paging file
454 NULL, // default security
455 PAGE_READWRITE, // read/write access
456 0, // max. object size
457 mSize, // buffer size
458 mName.c_str()); // name of mapping object
459
460 if(mImpl->mMapFile == NULL)
461 {
462 LL_WARNS("Plugin") << "CreateFileMapping failed: " << GetLastError() << LL_ENDL;
463 return false;
464 }
465
466 mNeedsDestroy = true;
467
468 return map();
469}
470
471bool LLPluginSharedMemory::destroy(void)
472{
473 unmap();
474 close();
475 return true;
476}
477
478bool LLPluginSharedMemory::attach(const std::string &name, size_t size)
479{
480 mName = name;
481 mSize = size;
482
483 mImpl->mMapFile = OpenFileMappingA(
484 FILE_MAP_ALL_ACCESS, // read/write access
485 FALSE, // do not inherit the name
486 mName.c_str()); // name of mapping object
487
488 if(mImpl->mMapFile == NULL)
489 {
490 LL_WARNS("Plugin") << "OpenFileMapping failed: " << GetLastError() << LL_ENDL;
491 return false;
492 }
493
494 return map();
495}
496
497bool LLPluginSharedMemory::detach(void)
498{
499 unmap();
500 close();
501 return true;
502}
503
504
505
506#endif
diff --git a/linden/indra/llplugin/llpluginsharedmemory.h b/linden/indra/llplugin/llpluginsharedmemory.h
new file mode 100644
index 0000000..2dc550e
--- /dev/null
+++ b/linden/indra/llplugin/llpluginsharedmemory.h
@@ -0,0 +1,130 @@
1/**
2 * @file llpluginsharedmemory.h
3 * @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API.
4 *
5 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33#ifndef LL_LLPLUGINSHAREDMEMORY_H
34#define LL_LLPLUGINSHAREDMEMORY_H
35
36class LLPluginSharedMemoryPlatformImpl;
37
38/**
39 * @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API.
40 *
41 */
42class LLPluginSharedMemory
43{
44 LOG_CLASS(LLPluginSharedMemory);
45public:
46 LLPluginSharedMemory();
47 ~LLPluginSharedMemory();
48
49 // Parent will use create/destroy, child will use attach/detach.
50 // Message transactions will ensure child attaches after parent creates and detaches before parent destroys.
51
52 /**
53 * Creates a shared memory segment, with a name which is guaranteed to be unique on the host at the current time. Used by parent.
54 * Message transactions will (? TODO:DOC - should? must?) ensure child attaches after parent creates and detaches before parent destroys.
55 *
56 * @param[in] size Shared memory size in TODO:DOC units = bytes?.
57 *
58 * @return False for failure, true for success.
59 */
60 bool create(size_t size);
61 /**
62 * Destroys a shared memory segment. Used by parent.
63 * Message transactions will (? TODO:DOC - should? must?) ensure child attaches after parent creates and detaches before parent destroys.
64 *
65 * @return True. TODO:DOC - always returns true. Is this the intended behavior?
66 */
67 bool destroy(void);
68
69 /**
70 * Creates and attaches a name to a shared memory segment. TODO:DOC what's the difference between attach() and create()?
71 *
72 * @param[in] name Name to attach to memory segment
73 * @param[in] size Size of memory segment TODO:DOC in bytes?
74 *
75 * @return False on failure, true otherwise.
76 */
77 bool attach(const std::string &name, size_t size);
78 /**
79 * Detaches shared memory segment.
80 *
81 * @return False on failure, true otherwise.
82 */
83 bool detach(void);
84
85 /**
86 * Checks if shared memory is mapped to a non-null address.
87 *
88 * @return True if memory address is non-null, false otherwise.
89 */
90 bool isMapped(void) const { return (mMappedAddress != NULL); };
91 /**
92 * Get pointer to shared memory.
93 *
94 * @return Pointer to shared memory.
95 */
96 void *getMappedAddress(void) const { return mMappedAddress; };
97 /**
98 * Get size of shared memory.
99 *
100 * @return Size of shared memory in bytes. TODO:DOC are bytes the correct unit?
101 */
102 size_t getSize(void) const { return mSize; };
103 /**
104 * Get name of shared memory.
105 *
106 * @return Name of shared memory.
107 */
108 std::string getName() const { return mName; };
109
110private:
111 bool map(void);
112 bool unmap(void);
113 bool close(void);
114 bool unlink(void);
115
116 std::string mName;
117 size_t mSize;
118 void *mMappedAddress;
119 bool mNeedsDestroy;
120
121 LLPluginSharedMemoryPlatformImpl *mImpl;
122
123 static int sSegmentNumber;
124 static std::string createName();
125
126};
127
128
129
130#endif // LL_LLPLUGINSHAREDMEMORY_H
diff --git a/linden/indra/llplugin/slplugin/CMakeLists.txt b/linden/indra/llplugin/slplugin/CMakeLists.txt
new file mode 100644
index 0000000..4a7d670
--- /dev/null
+++ b/linden/indra/llplugin/slplugin/CMakeLists.txt
@@ -0,0 +1,55 @@
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)
19endif (DARWIN)
20
21
22### SLPlugin
23
24set(SLPlugin_SOURCE_FILES
25 slplugin.cpp
26 )
27
28add_executable(SLPlugin
29 WIN32
30 ${SLPlugin_SOURCE_FILES}
31)
32
33target_link_libraries(SLPlugin
34 ${LLPLUGIN_LIBRARIES}
35 ${LLMESSAGE_LIBRARIES}
36 ${LLCOMMON_LIBRARIES}
37 ${PLUGIN_API_WINDOWS_LIBRARIES}
38)
39
40add_dependencies(SLPlugin
41 ${LLPLUGIN_LIBRARIES}
42 ${LLMESSAGE_LIBRARIES}
43 ${LLCOMMON_LIBRARIES}
44)
45
46if (DARWIN)
47 # Mac version needs to link against carbon, and also needs an embedded plist (to set LSBackgroundOnly)
48 target_link_libraries(SLPlugin ${CARBON_LIBRARY})
49 set_target_properties(
50 SLPlugin
51 PROPERTIES
52 LINK_FLAGS "-Wl,-sectcreate,__TEXT,__info_plist,${CMAKE_CURRENT_SOURCE_DIR}/slplugin_info.plist"
53 )
54endif (DARWIN)
55
diff --git a/linden/indra/llplugin/slplugin/slplugin.cpp b/linden/indra/llplugin/slplugin/slplugin.cpp
new file mode 100644
index 0000000..fa3924b
--- /dev/null
+++ b/linden/indra/llplugin/slplugin/slplugin.cpp
@@ -0,0 +1,288 @@
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 * $LicenseInfo:firstyear=2008&license=viewergpl$
6 *
7 * Copyright (c) 2008-2009, 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://secondlifegrid.net/programs/open_source/licensing/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://secondlifegrid.net/programs/open_source/licensing/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
33
34#include "linden_common.h"
35
36#include "llpluginprocesschild.h"
37#include "llpluginmessage.h"
38#include "llerrorcontrol.h"
39#include "llapr.h"
40#include "llstring.h"
41
42#if LL_DARWIN
43 #include <Carbon/Carbon.h>
44#endif
45
46#if LL_DARWIN || LL_LINUX
47 #include <signal.h>
48#endif
49
50/*
51 On Mac OS, since we call WaitNextEvent, this process will show up in the dock unless we set the LSBackgroundOnly flag in the Info.plist.
52
53 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:
54
55 -sectcreate __TEXT __info_plist /path/to/slplugin_info.plist
56
57 which means adding this to the gcc flags:
58
59 -Wl,-sectcreate,__TEXT,__info_plist,/path/to/slplugin_info.plist
60
61*/
62
63#if LL_DARWIN || LL_LINUX
64// Signal handlers to make crashes not show an OS dialog...
65static void crash_handler(int sig)
66{
67 // Just exit cleanly.
68 // TODO: add our own crash reporting
69 _exit(1);
70}
71#endif
72
73#if LL_WINDOWS
74#include <windows.h>
75////////////////////////////////////////////////////////////////////////////////
76// Our exception handler - will probably just exit and the host application
77// will miss the heartbeat and log the error in the usual fashion.
78LONG WINAPI myWin32ExceptionHandler( struct _EXCEPTION_POINTERS* exception_infop )
79{
80 //std::cerr << "This plugin (" << __FILE__ << ") - ";
81 //std::cerr << "intercepted an unhandled exception and will exit immediately." << std::endl;
82
83 // TODO: replace exception handler before we exit?
84 return EXCEPTION_EXECUTE_HANDLER;
85}
86
87// Taken from : http://blog.kalmbachnet.de/?postid=75
88// The MSVC 2005 CRT forces the call of the default-debugger (normally Dr.Watson)
89// even with the other exception handling code. This (terrifying) piece of code
90// patches things so that doesn't happen.
91LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter(
92 LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter )
93{
94 return NULL;
95}
96
97BOOL PreventSetUnhandledExceptionFilter()
98{
99// WARNING: This won't work on 64-bit Windows systems so we turn it off it.
100// It should work for any flavor of 32-bit Windows we care about.
101// If it's off, sometimes you will see an OS message when a plugin crashes
102#ifndef _WIN64
103 HMODULE hKernel32 = LoadLibraryA( "kernel32.dll" );
104 if ( NULL == hKernel32 )
105 return FALSE;
106
107 void *pOrgEntry = GetProcAddress( hKernel32, "SetUnhandledExceptionFilter" );
108 if( NULL == pOrgEntry )
109 return FALSE;
110
111 unsigned char newJump[ 100 ];
112 DWORD dwOrgEntryAddr = (DWORD)pOrgEntry;
113 dwOrgEntryAddr += 5; // add 5 for 5 op-codes for jmp far
114 void *pNewFunc = &MyDummySetUnhandledExceptionFilter;
115 DWORD dwNewEntryAddr = (DWORD) pNewFunc;
116 DWORD dwRelativeAddr = dwNewEntryAddr - dwOrgEntryAddr;
117
118 newJump[ 0 ] = 0xE9; // JMP absolute
119 memcpy( &newJump[ 1 ], &dwRelativeAddr, sizeof( pNewFunc ) );
120 SIZE_T bytesWritten;
121 BOOL bRet = WriteProcessMemory( GetCurrentProcess(), pOrgEntry, newJump, sizeof( pNewFunc ) + 1, &bytesWritten );
122 return bRet;
123#else
124 return FALSE;
125#endif
126}
127
128////////////////////////////////////////////////////////////////////////////////
129// Hook our exception handler and replace the system one
130void initExceptionHandler()
131{
132 LPTOP_LEVEL_EXCEPTION_FILTER prev_filter;
133
134 // save old exception handler in case we need to restore it at the end
135 prev_filter = SetUnhandledExceptionFilter( myWin32ExceptionHandler );
136 PreventSetUnhandledExceptionFilter();
137}
138
139bool checkExceptionHandler()
140{
141 bool ok = true;
142 LPTOP_LEVEL_EXCEPTION_FILTER prev_filter;
143 prev_filter = SetUnhandledExceptionFilter(myWin32ExceptionHandler);
144
145 PreventSetUnhandledExceptionFilter();
146
147 if (prev_filter != myWin32ExceptionHandler)
148 {
149 LL_WARNS("AppInit") << "Our exception handler (" << (void *)myWin32ExceptionHandler << ") replaced with " << prev_filter << "!" << LL_ENDL;
150 ok = false;
151 }
152
153 if (prev_filter == NULL)
154 {
155 ok = FALSE;
156 if (myWin32ExceptionHandler == NULL)
157 {
158 LL_WARNS("AppInit") << "Exception handler uninitialized." << LL_ENDL;
159 }
160 else
161 {
162 LL_WARNS("AppInit") << "Our exception handler (" << (void *)myWin32ExceptionHandler << ") replaced with NULL!" << LL_ENDL;
163 }
164 }
165
166 return ok;
167}
168#endif
169
170// If this application on Windows platform is a console application, a console is always
171// created which is bad. Making it a Windows "application" via CMake settings but not
172// adding any code to explicitly create windows does the right thing.
173#if LL_WINDOWS
174int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow )
175#else
176int main(int argc, char **argv)
177#endif
178{
179 ll_init_apr();
180
181 // Set up llerror logging
182 {
183 LLError::initForApplication(".");
184 LLError::setDefaultLevel(LLError::LEVEL_INFO);
185// LLError::setTagLevel("Plugin", LLError::LEVEL_DEBUG);
186// LLError::logToFile("slplugin.log");
187 }
188
189#if LL_WINDOWS
190 if( strlen( lpCmdLine ) == 0 )
191 {
192 LL_ERRS("slplugin") << "usage: " << "SLPlugin" << " launcher_port" << LL_ENDL;
193 };
194
195 U32 port = 0;
196 if(!LLStringUtil::convertToU32(lpCmdLine, port))
197 {
198 LL_ERRS("slplugin") << "port number must be numeric" << LL_ENDL;
199 };
200
201 // Insert our exception handler into the system so this plugin doesn't
202 // display a crash message if something bad happens. The host app will
203 // see the missing heartbeat and log appropriately.
204 initExceptionHandler();
205#elif LL_DARWIN || LL_LINUX
206 if(argc < 2)
207 {
208 LL_ERRS("slplugin") << "usage: " << argv[0] << " launcher_port" << LL_ENDL;
209 }
210
211 U32 port = 0;
212 if(!LLStringUtil::convertToU32(argv[1], port))
213 {
214 LL_ERRS("slplugin") << "port number must be numeric" << LL_ENDL;
215 }
216
217 // Catch signals that most kinds of crashes will generate, and exit cleanly so the system crash dialog isn't shown.
218 signal(SIGILL, &crash_handler); // illegal instruction
219# if LL_DARWIN
220 signal(SIGEMT, &crash_handler); // emulate instruction executed
221# endif // LL_DARWIN
222 signal(SIGFPE, &crash_handler); // floating-point exception
223 signal(SIGBUS, &crash_handler); // bus error
224 signal(SIGSEGV, &crash_handler); // segmentation violation
225 signal(SIGSYS, &crash_handler); // non-existent system call invoked
226#endif
227
228 LLPluginProcessChild *plugin = new LLPluginProcessChild();
229
230 plugin->init(port);
231
232 LLTimer timer;
233 timer.start();
234
235#if LL_WINDOWS
236 checkExceptionHandler();
237#endif
238
239 while(!plugin->isDone())
240 {
241 timer.reset();
242 plugin->idle();
243#if LL_DARWIN
244 {
245 // Some plugins (webkit at least) will want an event loop. This qualifies.
246 EventRecord evt;
247 WaitNextEvent(0, &evt, 0, NULL);
248 }
249#endif
250 F64 elapsed = timer.getElapsedTimeF64();
251 F64 remaining = plugin->getSleepTime() - elapsed;
252
253 if(remaining <= 0.0f)
254 {
255 // We've already used our full allotment.
256// LL_INFOS("slplugin") << "elapsed = " << elapsed * 1000.0f << " ms, remaining = " << remaining * 1000.0f << " ms, not sleeping" << LL_ENDL;
257
258 // Still need to service the network...
259 plugin->pump();
260 }
261 else
262 {
263
264// LL_INFOS("slplugin") << "elapsed = " << elapsed * 1000.0f << " ms, remaining = " << remaining * 1000.0f << " ms, sleeping for " << remaining * 1000.0f << " ms" << LL_ENDL;
265// timer.reset();
266
267 // This also services the network as needed.
268 plugin->sleep(remaining);
269
270// LL_INFOS("slplugin") << "slept for "<< timer.getElapsedTimeF64() * 1000.0f << " ms" << LL_ENDL;
271 }
272
273#if LL_WINDOWS
274 // More agressive checking of interfering exception handlers.
275 // Doesn't appear to be required so far - even for plugins
276 // that do crash with a single call to the intercept
277 // exception handler such as QuickTime.
278 //checkExceptionHandler();
279#endif
280 }
281
282 delete plugin;
283
284 ll_cleanup_apr();
285
286 return 0;
287}
288
diff --git a/linden/indra/llplugin/slplugin/slplugin_info.plist b/linden/indra/llplugin/slplugin/slplugin_info.plist
new file mode 100644
index 0000000..b1daf87
--- /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>LSBackgroundOnly</key>
10 <true/>
11</dict>
12</plist>