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