diff options
author | Armin Weatherwax | 2010-06-14 12:04:49 +0200 |
---|---|---|
committer | Armin Weatherwax | 2010-09-23 15:38:25 +0200 |
commit | 35df5441d3e2789663532c948731aff3a1e04728 (patch) | |
tree | ac7674289784a5f96106ea507637055a8dada78a /linden/indra/llplugin | |
parent | Changed version to Experimental 2010.09.18 (diff) | |
download | meta-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')
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 | |||
3 | project(llplugin) | ||
4 | |||
5 | include(00-Common) | ||
6 | include(LLCommon) | ||
7 | include(LLImage) | ||
8 | include(LLMath) | ||
9 | include(LLMessage) | ||
10 | include(LLRender) | ||
11 | include(LLXML) | ||
12 | include(LLWindow) | ||
13 | |||
14 | include_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 | |||
24 | set(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 | |||
34 | set(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 | |||
48 | set_source_files_properties(${llplugin_HEADER_FILES} | ||
49 | PROPERTIES HEADER_FILE_ONLY TRUE) | ||
50 | |||
51 | list(APPEND llplugin_SOURCE_FILES ${llplugin_HEADER_FILES}) | ||
52 | |||
53 | add_library (llplugin ${llplugin_SOURCE_FILES}) | ||
54 | |||
55 | add_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 | |||
39 | static int LOW_PRIORITY_TEXTURE_SIZE_DEFAULT = 256; | ||
40 | |||
41 | static 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 | |||
52 | LLPluginClassMedia::LLPluginClassMedia(LLPluginClassMediaOwner *owner) | ||
53 | { | ||
54 | mOwner = owner; | ||
55 | mPlugin = NULL; | ||
56 | reset(); | ||
57 | } | ||
58 | |||
59 | |||
60 | LLPluginClassMedia::~LLPluginClassMedia() | ||
61 | { | ||
62 | reset(); | ||
63 | } | ||
64 | |||
65 | bool 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 | |||
79 | void 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 | |||
143 | void 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 | |||
254 | int LLPluginClassMedia::getTextureWidth() const | ||
255 | { | ||
256 | return nextPowerOf2(mTextureWidth); | ||
257 | } | ||
258 | |||
259 | int LLPluginClassMedia::getTextureHeight() const | ||
260 | { | ||
261 | return nextPowerOf2(mTextureHeight); | ||
262 | } | ||
263 | |||
264 | unsigned 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 | |||
274 | void 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 | |||
290 | void 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 | |||
345 | void LLPluginClassMedia::setAutoScale(bool auto_scale) | ||
346 | { | ||
347 | if(auto_scale != mAutoScaleMedia) | ||
348 | { | ||
349 | mAutoScaleMedia = auto_scale; | ||
350 | setSizeInternal(); | ||
351 | } | ||
352 | } | ||
353 | |||
354 | bool 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 | |||
371 | bool 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 | |||
383 | void LLPluginClassMedia::resetDirty(void) | ||
384 | { | ||
385 | mDirtyRect = LLRect::null; | ||
386 | } | ||
387 | |||
388 | std::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 | |||
419 | void 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 | |||
461 | bool 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 | |||
525 | void 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 | |||
536 | bool 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 | |||
548 | void 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 | |||
557 | const 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 | |||
574 | void 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 | |||
624 | void 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 | |||
636 | F64 LLPluginClassMedia::getCPUUsage() | ||
637 | { | ||
638 | F64 result = 0.0f; | ||
639 | |||
640 | if(mPlugin) | ||
641 | { | ||
642 | result = mPlugin->getCPUUsage(); | ||
643 | } | ||
644 | |||
645 | return result; | ||
646 | } | ||
647 | |||
648 | void LLPluginClassMedia::cut() | ||
649 | { | ||
650 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_cut"); | ||
651 | sendMessage(message); | ||
652 | } | ||
653 | |||
654 | void LLPluginClassMedia::copy() | ||
655 | { | ||
656 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_copy"); | ||
657 | sendMessage(message); | ||
658 | } | ||
659 | |||
660 | void LLPluginClassMedia::paste() | ||
661 | { | ||
662 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA, "edit_paste"); | ||
663 | sendMessage(message); | ||
664 | } | ||
665 | |||
666 | /* virtual */ | ||
667 | void 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 */ | ||
948 | void LLPluginClassMedia::pluginLaunchFailed() | ||
949 | { | ||
950 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PLUGIN_FAILED_LAUNCH); | ||
951 | } | ||
952 | |||
953 | /* virtual */ | ||
954 | void LLPluginClassMedia::pluginDied() | ||
955 | { | ||
956 | mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_PLUGIN_FAILED); | ||
957 | } | ||
958 | |||
959 | void LLPluginClassMedia::mediaEvent(LLPluginClassMediaOwner::EMediaEvent event) | ||
960 | { | ||
961 | if(mOwner) | ||
962 | { | ||
963 | mOwner->handleMediaEvent(this, event); | ||
964 | } | ||
965 | } | ||
966 | |||
967 | void 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 | ||
982 | bool LLPluginClassMedia::pluginSupportsMediaBrowser(void) | ||
983 | { | ||
984 | std::string version = mPlugin->getMessageClassVersion(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER); | ||
985 | return !version.empty(); | ||
986 | } | ||
987 | |||
988 | void 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 | |||
997 | void LLPluginClassMedia::clear_cache() | ||
998 | { | ||
999 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "clear_cache"); | ||
1000 | sendMessage(message); | ||
1001 | } | ||
1002 | |||
1003 | void LLPluginClassMedia::clear_cookies() | ||
1004 | { | ||
1005 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "clear_cookies"); | ||
1006 | sendMessage(message); | ||
1007 | } | ||
1008 | |||
1009 | void LLPluginClassMedia::enable_cookies(bool enable) | ||
1010 | { | ||
1011 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "enable_cookies"); | ||
1012 | sendMessage(message); | ||
1013 | } | ||
1014 | |||
1015 | void 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 | |||
1026 | void LLPluginClassMedia::browse_stop() | ||
1027 | { | ||
1028 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_stop"); | ||
1029 | sendMessage(message); | ||
1030 | } | ||
1031 | |||
1032 | void 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 | |||
1041 | void LLPluginClassMedia::browse_forward() | ||
1042 | { | ||
1043 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_forward"); | ||
1044 | sendMessage(message); | ||
1045 | } | ||
1046 | |||
1047 | void LLPluginClassMedia::browse_back() | ||
1048 | { | ||
1049 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "browse_back"); | ||
1050 | sendMessage(message); | ||
1051 | } | ||
1052 | |||
1053 | void 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 | |||
1063 | void 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 | |||
1072 | void LLPluginClassMedia::crashPlugin() | ||
1073 | { | ||
1074 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_INTERNAL, "crash"); | ||
1075 | |||
1076 | sendMessage(message); | ||
1077 | } | ||
1078 | |||
1079 | void 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 | ||
1089 | bool LLPluginClassMedia::pluginSupportsMediaTime(void) | ||
1090 | { | ||
1091 | std::string version = mPlugin->getMessageClassVersion(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME); | ||
1092 | return !version.empty(); | ||
1093 | } | ||
1094 | |||
1095 | void LLPluginClassMedia::stop() | ||
1096 | { | ||
1097 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "stop"); | ||
1098 | sendMessage(message); | ||
1099 | } | ||
1100 | |||
1101 | void 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 | |||
1110 | void LLPluginClassMedia::pause() | ||
1111 | { | ||
1112 | LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_TIME, "pause"); | ||
1113 | sendMessage(message); | ||
1114 | } | ||
1115 | |||
1116 | void 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 | |||
1125 | void 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 | |||
1134 | void 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 | |||
1148 | float LLPluginClassMedia::getVolume() | ||
1149 | { | ||
1150 | return mRequestedVolume; | ||
1151 | } | ||
1152 | |||
1153 | void 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 | |||
43 | class LLPluginClassMedia : public LLPluginProcessParentOwner | ||
44 | { | ||
45 | LOG_CLASS(LLPluginClassMedia); | ||
46 | public: | ||
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 | |||
243 | protected: | ||
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 | |||
40 | class LLPluginClassMedia; | ||
41 | |||
42 | class LLPluginClassMediaOwner | ||
43 | { | ||
44 | public: | ||
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. */ | ||
40 | LLPluginInstanceMessageListener::~LLPluginInstanceMessageListener() | ||
41 | { | ||
42 | } | ||
43 | |||
44 | /** | ||
45 | * TODO:DOC describe how it's used | ||
46 | */ | ||
47 | const 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 | */ | ||
54 | LLPluginInstance::LLPluginInstance(LLPluginInstanceMessageListener *owner) : | ||
55 | mDSOHandle(NULL), | ||
56 | mPluginUserData(NULL), | ||
57 | mPluginSendMessageFunction(NULL) | ||
58 | { | ||
59 | mOwner = owner; | ||
60 | } | ||
61 | |||
62 | /** | ||
63 | * Destructor. | ||
64 | */ | ||
65 | LLPluginInstance::~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 | */ | ||
80 | int 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 | */ | ||
126 | void 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 | */ | ||
143 | void LLPluginInstance::idle(void) | ||
144 | { | ||
145 | } | ||
146 | |||
147 | // static | ||
148 | void 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 | */ | ||
161 | void 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 | */ | ||
44 | class LLPluginInstanceMessageListener | ||
45 | { | ||
46 | public: | ||
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 | */ | ||
55 | class LLPluginInstance | ||
56 | { | ||
57 | LOG_CLASS(LLPluginInstance); | ||
58 | public: | ||
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 | |||
92 | private: | ||
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 | */ | ||
42 | LLPluginMessage::LLPluginMessage() | ||
43 | { | ||
44 | } | ||
45 | |||
46 | /** | ||
47 | * Constructor. | ||
48 | * | ||
49 | * @param[in] p Existing message | ||
50 | */ | ||
51 | LLPluginMessage::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 | */ | ||
62 | LLPluginMessage::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 | */ | ||
71 | LLPluginMessage::~LLPluginMessage() | ||
72 | { | ||
73 | } | ||
74 | |||
75 | /** | ||
76 | * Reset all internal state. | ||
77 | */ | ||
78 | void 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 | */ | ||
90 | void 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 | */ | ||
103 | void 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 | */ | ||
114 | void 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 | */ | ||
125 | void 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 | */ | ||
136 | void 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 | */ | ||
149 | void 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 | */ | ||
160 | void 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 | */ | ||
171 | void 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 | */ | ||
184 | std::string LLPluginMessage::getClass(void) const | ||
185 | { | ||
186 | return mMessage["class"]; | ||
187 | } | ||
188 | |||
189 | /** | ||
190 | * Gets the message name. | ||
191 | * | ||
192 | * @return Message name | ||
193 | */ | ||
194 | std::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 | */ | ||
206 | bool 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 | */ | ||
225 | std::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 | */ | ||
244 | LLSD 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 | */ | ||
263 | S32 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 | */ | ||
282 | U32 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 | */ | ||
303 | bool 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 | */ | ||
322 | F64 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 | */ | ||
341 | void* 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 | */ | ||
360 | std::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 | */ | ||
376 | int 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 | */ | ||
392 | LLPluginMessageListener::~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 | */ | ||
401 | LLPluginMessageDispatcher::~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 | */ | ||
411 | void 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 | */ | ||
421 | void 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 | */ | ||
431 | void 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 | */ | ||
41 | class LLPluginMessage | ||
42 | { | ||
43 | LOG_CLASS(LLPluginMessage); | ||
44 | public: | ||
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 | |||
102 | private: | ||
103 | |||
104 | LLSD mMessage; | ||
105 | |||
106 | }; | ||
107 | |||
108 | /** | ||
109 | * @brief Listener for plugin messages. | ||
110 | */ | ||
111 | class LLPluginMessageListener | ||
112 | { | ||
113 | public: | ||
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 | */ | ||
125 | class LLPluginMessageDispatcher | ||
126 | { | ||
127 | public: | ||
128 | virtual ~LLPluginMessageDispatcher(); | ||
129 | |||
130 | void addPluginMessageListener(LLPluginMessageListener *); | ||
131 | void removePluginMessageListener(LLPluginMessageListener *); | ||
132 | protected: | ||
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 | |||
40 | static const char MESSAGE_DELIMITER = '\0'; | ||
41 | |||
42 | LLPluginMessagePipeOwner::LLPluginMessagePipeOwner() : | ||
43 | mMessagePipe(NULL), | ||
44 | mSocketError(APR_SUCCESS) | ||
45 | { | ||
46 | } | ||
47 | |||
48 | // virtual | ||
49 | LLPluginMessagePipeOwner::~LLPluginMessagePipeOwner() | ||
50 | { | ||
51 | killMessagePipe(); | ||
52 | } | ||
53 | |||
54 | // virtual | ||
55 | apr_status_t LLPluginMessagePipeOwner::socketError(apr_status_t error) | ||
56 | { | ||
57 | mSocketError = error; | ||
58 | return error; | ||
59 | }; | ||
60 | |||
61 | //virtual | ||
62 | void LLPluginMessagePipeOwner::setMessagePipe(LLPluginMessagePipe *read_pipe) | ||
63 | { | ||
64 | // Save a reference to this pipe | ||
65 | mMessagePipe = read_pipe; | ||
66 | } | ||
67 | |||
68 | bool LLPluginMessagePipeOwner::canSendMessage(void) | ||
69 | { | ||
70 | return (mMessagePipe != NULL); | ||
71 | } | ||
72 | |||
73 | bool 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 | |||
89 | void LLPluginMessagePipeOwner::killMessagePipe(void) | ||
90 | { | ||
91 | if(mMessagePipe != NULL) | ||
92 | { | ||
93 | delete mMessagePipe; | ||
94 | mMessagePipe = NULL; | ||
95 | } | ||
96 | } | ||
97 | |||
98 | LLPluginMessagePipe::LLPluginMessagePipe(LLPluginMessagePipeOwner *owner, LLSocket::ptr_t socket) | ||
99 | { | ||
100 | mOwner = owner; | ||
101 | mOwner->setMessagePipe(this); | ||
102 | mSocket = socket; | ||
103 | } | ||
104 | |||
105 | LLPluginMessagePipe::~LLPluginMessagePipe() | ||
106 | { | ||
107 | if(mOwner != NULL) | ||
108 | { | ||
109 | mOwner->setMessagePipe(NULL); | ||
110 | } | ||
111 | } | ||
112 | |||
113 | bool 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 | |||
122 | void 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 | |||
128 | void 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 | |||
149 | bool 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 | |||
298 | void 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 | |||
38 | class LLPluginMessagePipe; | ||
39 | |||
40 | // Inherit from this to be able to receive messages from the LLPluginMessagePipe | ||
41 | class LLPluginMessagePipeOwner | ||
42 | { | ||
43 | LOG_CLASS(LLPluginMessagePipeOwner); | ||
44 | public: | ||
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 | |||
55 | protected: | ||
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 | |||
67 | class LLPluginMessagePipe | ||
68 | { | ||
69 | LOG_CLASS(LLPluginMessagePipe); | ||
70 | public: | ||
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 | |||
79 | protected: | ||
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 | |||
40 | static const F32 HEARTBEAT_SECONDS = 1.0f; | ||
41 | static const F32 PLUGIN_IDLE_SECONDS = 1.0f / 100.0f; // Each call to idle will give the plugin this much time. | ||
42 | |||
43 | LLPluginProcessChild::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 | |||
51 | LLPluginProcessChild::~LLPluginProcessChild() | ||
52 | { | ||
53 | if(mInstance != NULL) | ||
54 | { | ||
55 | sendMessageToPlugin(LLPluginMessage("base", "cleanup")); | ||
56 | delete mInstance; | ||
57 | mInstance = NULL; | ||
58 | } | ||
59 | } | ||
60 | |||
61 | void LLPluginProcessChild::killSockets(void) | ||
62 | { | ||
63 | killMessagePipe(); | ||
64 | mSocket.reset(); | ||
65 | } | ||
66 | |||
67 | void LLPluginProcessChild::init(U32 launcher_port) | ||
68 | { | ||
69 | mLauncherHost = LLHost("127.0.0.1", launcher_port); | ||
70 | setState(STATE_INITIALIZED); | ||
71 | } | ||
72 | |||
73 | void 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 | |||
220 | void 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 | |||
232 | void 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 | |||
245 | bool LLPluginProcessChild::isRunning(void) | ||
246 | { | ||
247 | bool result = false; | ||
248 | |||
249 | if(mState == STATE_RUNNING) | ||
250 | result = true; | ||
251 | |||
252 | return result; | ||
253 | } | ||
254 | |||
255 | bool 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 | |||
271 | void 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 | |||
283 | void 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 | |||
292 | void 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 */ | ||
411 | void 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 | |||
486 | void 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 | |||
42 | class LLPluginInstance; | ||
43 | |||
44 | class LLPluginProcessChild: public LLPluginMessagePipeOwner, public LLPluginInstanceMessageListener | ||
45 | { | ||
46 | LOG_CLASS(LLPluginProcessChild); | ||
47 | public: | ||
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 | |||
75 | private: | ||
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 | ||
42 | LLPluginProcessParentOwner::~LLPluginProcessParentOwner() | ||
43 | { | ||
44 | |||
45 | } | ||
46 | |||
47 | LLPluginProcessParent::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 | |||
62 | LLPluginProcessParent::~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 | |||
83 | void LLPluginProcessParent::killSockets(void) | ||
84 | { | ||
85 | killMessagePipe(); | ||
86 | mListenSocket.reset(); | ||
87 | mSocket.reset(); | ||
88 | } | ||
89 | |||
90 | void LLPluginProcessParent::errorState(void) | ||
91 | { | ||
92 | if(mState < STATE_RUNNING) | ||
93 | setState(STATE_LAUNCH_FAILURE); | ||
94 | else | ||
95 | setState(STATE_ERROR); | ||
96 | } | ||
97 | |||
98 | void 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 | |||
109 | bool 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 | |||
155 | void 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 | |||
429 | bool LLPluginProcessParent::isLoading(void) | ||
430 | { | ||
431 | bool result = false; | ||
432 | |||
433 | if(mState <= STATE_LOADING) | ||
434 | result = true; | ||
435 | |||
436 | return result; | ||
437 | } | ||
438 | |||
439 | bool LLPluginProcessParent::isRunning(void) | ||
440 | { | ||
441 | bool result = false; | ||
442 | |||
443 | if(mState == STATE_RUNNING) | ||
444 | result = true; | ||
445 | |||
446 | return result; | ||
447 | } | ||
448 | |||
449 | bool LLPluginProcessParent::isDone(void) | ||
450 | { | ||
451 | bool result = false; | ||
452 | |||
453 | if(mState == STATE_DONE) | ||
454 | result = true; | ||
455 | |||
456 | return result; | ||
457 | } | ||
458 | |||
459 | void 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 | |||
480 | void 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 | |||
489 | void 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 | |||
502 | void 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 | |||
595 | std::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 | |||
624 | void 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 | } | ||
640 | size_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 | } | ||
652 | void *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 | |||
665 | std::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 | |||
677 | std::string LLPluginProcessParent::getPluginVersion(void) | ||
678 | { | ||
679 | return mPluginVersionString; | ||
680 | } | ||
681 | |||
682 | void LLPluginProcessParent::setState(EState state) | ||
683 | { | ||
684 | LL_DEBUGS("Plugin") << "setting state to " << state << LL_ENDL; | ||
685 | mState = state; | ||
686 | }; | ||
687 | |||
688 | bool 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 | |||
709 | bool 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 | |||
44 | class LLPluginProcessParentOwner | ||
45 | { | ||
46 | public: | ||
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 | |||
54 | class LLPluginProcessParent : public LLPluginMessagePipeOwner | ||
55 | { | ||
56 | LOG_CLASS(LLPluginProcessParent); | ||
57 | public: | ||
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 | |||
110 | private: | ||
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 | |||
81 | int LLPluginSharedMemory::sSegmentNumber = 0; | ||
82 | |||
83 | std::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 | */ | ||
102 | class LLPluginSharedMemoryPlatformImpl | ||
103 | { | ||
104 | public: | ||
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 | */ | ||
121 | LLPluginSharedMemory::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 | */ | ||
133 | LLPluginSharedMemory::~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 | |||
148 | LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl() | ||
149 | { | ||
150 | mAprSharedMemory = NULL; | ||
151 | } | ||
152 | |||
153 | LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl() | ||
154 | { | ||
155 | |||
156 | } | ||
157 | |||
158 | bool 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 | |||
169 | bool LLPluginSharedMemory::unmap(void) | ||
170 | { | ||
171 | // This is a no-op under apr. | ||
172 | return true; | ||
173 | } | ||
174 | |||
175 | bool LLPluginSharedMemory::close(void) | ||
176 | { | ||
177 | // This is a no-op under apr. | ||
178 | return true; | ||
179 | } | ||
180 | |||
181 | bool LLPluginSharedMemory::unlink(void) | ||
182 | { | ||
183 | // This is a no-op under apr. | ||
184 | return true; | ||
185 | } | ||
186 | |||
187 | |||
188 | bool 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 | |||
206 | bool 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 | |||
221 | bool 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 | |||
237 | bool 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 | |||
256 | LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl() | ||
257 | { | ||
258 | mSharedMemoryFD = -1; | ||
259 | } | ||
260 | |||
261 | LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl() | ||
262 | { | ||
263 | } | ||
264 | |||
265 | bool 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 | |||
278 | bool 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 | |||
294 | bool 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 | |||
309 | bool 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 | |||
323 | bool 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 | |||
349 | bool LLPluginSharedMemory::destroy(void) | ||
350 | { | ||
351 | unmap(); | ||
352 | close(); | ||
353 | |||
354 | return true; | ||
355 | } | ||
356 | |||
357 | |||
358 | bool 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 | |||
375 | bool 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 | |||
387 | LLPluginSharedMemoryPlatformImpl::LLPluginSharedMemoryPlatformImpl() | ||
388 | { | ||
389 | mMapFile = NULL; | ||
390 | } | ||
391 | |||
392 | LLPluginSharedMemoryPlatformImpl::~LLPluginSharedMemoryPlatformImpl() | ||
393 | { | ||
394 | |||
395 | } | ||
396 | |||
397 | bool 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 | |||
417 | bool LLPluginSharedMemory::unmap(void) | ||
418 | { | ||
419 | if(mMappedAddress != NULL) | ||
420 | { | ||
421 | UnmapViewOfFile(mMappedAddress); | ||
422 | mMappedAddress = NULL; | ||
423 | } | ||
424 | |||
425 | return true; | ||
426 | } | ||
427 | |||
428 | bool 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 | |||
439 | bool LLPluginSharedMemory::unlink(void) | ||
440 | { | ||
441 | // This is a no-op on Windows. | ||
442 | return true; | ||
443 | } | ||
444 | |||
445 | |||
446 | bool 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 | |||
471 | bool LLPluginSharedMemory::destroy(void) | ||
472 | { | ||
473 | unmap(); | ||
474 | close(); | ||
475 | return true; | ||
476 | } | ||
477 | |||
478 | bool 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 | |||
497 | bool 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 | |||
36 | class LLPluginSharedMemoryPlatformImpl; | ||
37 | |||
38 | /** | ||
39 | * @brief LLPluginSharedMemory manages a shared memory segment for use by the LLPlugin API. | ||
40 | * | ||
41 | */ | ||
42 | class LLPluginSharedMemory | ||
43 | { | ||
44 | LOG_CLASS(LLPluginSharedMemory); | ||
45 | public: | ||
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 | |||
110 | private: | ||
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 @@ | |||
1 | project(SLPlugin) | ||
2 | |||
3 | include(00-Common) | ||
4 | include(LLCommon) | ||
5 | include(LLPlugin) | ||
6 | include(Linking) | ||
7 | include(PluginAPI) | ||
8 | include(LLMessage) | ||
9 | |||
10 | include_directories( | ||
11 | ${LLPLUGIN_INCLUDE_DIRS} | ||
12 | ${LLMESSAGE_INCLUDE_DIRS} | ||
13 | ${LLCOMMON_INCLUDE_DIRS} | ||
14 | ) | ||
15 | |||
16 | if (DARWIN) | ||
17 | include(CMakeFindFrameworks) | ||
18 | find_library(CARBON_LIBRARY Carbon) | ||
19 | endif (DARWIN) | ||
20 | |||
21 | |||
22 | ### SLPlugin | ||
23 | |||
24 | set(SLPlugin_SOURCE_FILES | ||
25 | slplugin.cpp | ||
26 | ) | ||
27 | |||
28 | add_executable(SLPlugin | ||
29 | WIN32 | ||
30 | ${SLPlugin_SOURCE_FILES} | ||
31 | ) | ||
32 | |||
33 | target_link_libraries(SLPlugin | ||
34 | ${LLPLUGIN_LIBRARIES} | ||
35 | ${LLMESSAGE_LIBRARIES} | ||
36 | ${LLCOMMON_LIBRARIES} | ||
37 | ${PLUGIN_API_WINDOWS_LIBRARIES} | ||
38 | ) | ||
39 | |||
40 | add_dependencies(SLPlugin | ||
41 | ${LLPLUGIN_LIBRARIES} | ||
42 | ${LLMESSAGE_LIBRARIES} | ||
43 | ${LLCOMMON_LIBRARIES} | ||
44 | ) | ||
45 | |||
46 | if (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 | ) | ||
54 | endif (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... | ||
65 | static 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. | ||
78 | LONG 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. | ||
91 | LPTOP_LEVEL_EXCEPTION_FILTER WINAPI MyDummySetUnhandledExceptionFilter( | ||
92 | LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter ) | ||
93 | { | ||
94 | return NULL; | ||
95 | } | ||
96 | |||
97 | BOOL 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 | ||
130 | void 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 | |||
139 | bool 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 | ||
174 | int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) | ||
175 | #else | ||
176 | int 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> | ||