summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--autobuild.xml44
-rw-r--r--indra/cmake/CMakeLists.txt1
-rw-r--r--indra/cmake/Copy3rdPartyLibs.cmake1
-rw-r--r--indra/cmake/Lualibs.cmake15
-rw-r--r--indra/llcommon/CMakeLists.txt1
-rwxr-xr-x[-rw-r--r--]indra/llcommon/hexdump.h (renamed from indra/test/hexdump.h)13
-rw-r--r--indra/llcommon/llevents.cpp9
-rw-r--r--indra/llcommon/tests/llsdserialize_test.cpp8
-rw-r--r--indra/llcommon/tests/threadsafeschedule_test.cpp4
-rw-r--r--indra/llui/llmenugl.h16
-rw-r--r--indra/llui/lltexteditor.h2
-rw-r--r--indra/newview/CMakeLists.txt11
-rw-r--r--indra/newview/app_settings/settings.xml11
-rw-r--r--indra/newview/llfilepicker.cpp5
-rw-r--r--indra/newview/llfilepicker.h3
-rw-r--r--indra/newview/llfloaterluadebug.cpp122
-rw-r--r--indra/newview/llfloaterluadebug.h66
-rw-r--r--indra/newview/llinventoryfunctions.cpp13
-rw-r--r--indra/newview/llinventoryfunctions.h16
-rw-r--r--indra/newview/llinventorygallery.cpp4
-rw-r--r--indra/newview/llinventorymodel.cpp11
-rw-r--r--indra/newview/llluamanager.cpp1530
-rw-r--r--indra/newview/llluamanager.h45
-rw-r--r--indra/newview/llstartup.cpp3
-rw-r--r--indra/newview/lltoolplacer.cpp8
-rw-r--r--indra/newview/lltoolplacer.h8
-rw-r--r--indra/newview/lluilistener.cpp2
-rw-r--r--indra/newview/lluilistener.h3
-rw-r--r--indra/newview/llviewerfloaterreg.cpp3
-rw-r--r--indra/newview/llviewermenu.cpp129
-rw-r--r--indra/newview/llviewermenu.h1
-rw-r--r--indra/newview/llviewermenufile.cpp18
-rw-r--r--indra/newview/llviewermenufile.h2
-rw-r--r--indra/newview/skins/default/xui/en/floater_lua_debug.xml108
-rw-r--r--indra/newview/skins/default/xui/en/menu_viewer.xml11
-rw-r--r--indra/newview/tests/llluamanager_test.cpp301
-rwxr-xr-xindra/newview/viewer_manifest.py3
-rw-r--r--indra/test/io.cpp40
-rw-r--r--scripts/lua/avatar.lua14
-rw-r--r--scripts/lua/demo.lua147
40 files changed, 2649 insertions, 103 deletions
diff --git a/autobuild.xml b/autobuild.xml
index 569b9f7c28..d802d4b4a5 100644
--- a/autobuild.xml
+++ b/autobuild.xml
@@ -1658,6 +1658,48 @@
<key>name</key>
<string>llphysicsextensions_tpv</string>
</map>
+ <key>lualibs</key>
+ <map>
+ <key>copyright</key>
+ <string>Lua by Lua.org, PUC-Rio.</string>
+ <key>description</key>
+ <string>LUA libs API</string>
+ <key>license</key>
+ <string>lua</string>
+ <key>license_file</key>
+ <string>LICENSES/lua.txt</string>
+ <key>name</key>
+ <string>lualibs</string>
+ <key>platforms</key>
+ <map>
+ <key>windows</key>
+ <map>
+ <key>archive</key>
+ <map>
+ <key>hash</key>
+ <string>55bd833166d03f1467e2c7f24fa9143e</string>
+ <key>url</key>
+ <string>https://automated-builds-secondlife-com.s3.amazonaws.com/ct2/87775/805841/openssl-1.1.1l.563846-windows-563846.tar.bz2</string>
+ </map>
+ <key>name</key>
+ <string>windows</string>
+ </map>
+ <key>windows64</key>
+ <map>
+ <key>archive</key>
+ <map>
+ <key>hash</key>
+ <string>6dc51cd8cad422ab1dcd67cc59af119d</string>
+ <key>url</key>
+ <string>https://s3-proxy.lindenlab.com/private-builds-secondlife-com/ct2/118146/1013526/lualibs-5.4.581683-windows64-581683.tar.bz2</string>
+ </map>
+ <key>name</key>
+ <string>windows64</string>
+ </map>
+ </map>
+ <key>version</key>
+ <string>5.4.581683</string>
+ </map>
<key>mesa</key>
<map>
<key>platforms</key>
@@ -3217,4 +3259,4 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
<string>Second Life Viewer</string>
</map>
</map>
-</llsd> \ No newline at end of file
+</llsd>
diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt
index f0b35c08f3..af01666875 100644
--- a/indra/cmake/CMakeLists.txt
+++ b/indra/cmake/CMakeLists.txt
@@ -43,6 +43,7 @@ set(cmake_SOURCE_FILES
LLTestCommand.cmake
LLWindow.cmake
Linking.cmake
+ Lualibs.cmake
Meshoptimizer.cmake
NDOF.cmake
OPENAL.cmake
diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake
index a3db02372d..0da0f89cbd 100644
--- a/indra/cmake/Copy3rdPartyLibs.cmake
+++ b/indra/cmake/Copy3rdPartyLibs.cmake
@@ -60,6 +60,7 @@ if(WINDOWS)
nghttp2.dll
libhunspell.dll
uriparser.dll
+ lua54.dll
)
# OpenSSL
diff --git a/indra/cmake/Lualibs.cmake b/indra/cmake/Lualibs.cmake
new file mode 100644
index 0000000000..ec40d0f41c
--- /dev/null
+++ b/indra/cmake/Lualibs.cmake
@@ -0,0 +1,15 @@
+# -*- cmake -*-
+
+include_guard()
+
+include(Prebuilt)
+
+add_library( ll::lualibs INTERFACE IMPORTED )
+
+use_system_binary( lualibs )
+
+use_prebuilt_binary(lualibs)
+
+target_link_libraries(ll::lualibs INTERFACE ${lualibs})
+
+target_include_directories( ll::lualibs SYSTEM INTERFACE ${LIBS_PREBUILT_DIR}/include/lualibs)
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index ef4899978e..5dd4321330 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -13,6 +13,7 @@ include(Copy3rdPartyLibs)
include(ZLIBNG)
include(URIPARSER)
include(Tracy)
+include(lualibs)
set(llcommon_SOURCE_FILES
diff --git a/indra/test/hexdump.h b/indra/llcommon/hexdump.h
index dd7cbaaa3c..234168cd61 100644..100755
--- a/indra/test/hexdump.h
+++ b/indra/llcommon/hexdump.h
@@ -1,8 +1,8 @@
/**
* @file hexdump.h
* @author Nat Goodspeed
- * @date 2023-09-08
- * @brief Provide hexdump() and hexmix() ostream formatters
+ * @date 2023-10-03
+ * @brief iostream manipulators to stream hex, or string with nonprinting chars
*
* $LicenseInfo:firstyear=2023&license=viewerlgpl$
* Copyright (c) 2023, Linden Research, Inc.
@@ -17,6 +17,9 @@
#include <iostream>
#include <string_view>
+namespace LL
+{
+
// Format a given byte string as 2-digit hex values, no separators
// Usage: std::cout << hexdump(somestring) << ...
class hexdump
@@ -30,6 +33,10 @@ public:
hexdump(reinterpret_cast<const unsigned char*>(data), len)
{}
+ hexdump(const std::vector<unsigned char>& data):
+ hexdump(data.data(), data.size())
+ {}
+
hexdump(const unsigned char* data, size_t len):
mData(data, data + len)
{}
@@ -94,4 +101,6 @@ private:
std::string mData;
};
+} // namespace LL
+
#endif /* ! defined(LL_HEXDUMP_H) */
diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp
index 0a213bddef..241b8cf463 100644
--- a/indra/llcommon/llevents.cpp
+++ b/indra/llcommon/llevents.cpp
@@ -54,10 +54,11 @@
#pragma warning (pop)
#endif
// other Linden headers
-#include "stringize.h"
#include "llerror.h"
-#include "llsdutil.h"
+#include "lleventfilter.h"
#include "llexception.h"
+#include "llsdutil.h"
+#include "stringize.h"
#if LL_MSVC
#pragma warning (disable : 4702)
#endif
@@ -71,7 +72,9 @@ LLEventPumps::LLEventPumps():
{ "LLEventStream", [](const std::string& name, bool tweak)
{ return new LLEventStream(name, tweak); } },
{ "LLEventMailDrop", [](const std::string& name, bool tweak)
- { return new LLEventMailDrop(name, tweak); } }
+ { return new LLEventMailDrop(name, tweak); } },
+ { "LLEventLogProxy", [](const std::string& name, bool tweak)
+ { return new LLEventLogProxyFor<LLEventStream>(name, tweak); } }
},
mTypes
{
diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp
index ac40125f75..ae3a94c55d 100644
--- a/indra/llcommon/tests/llsdserialize_test.cpp
+++ b/indra/llcommon/tests/llsdserialize_test.cpp
@@ -52,7 +52,7 @@ typedef U32 uint32_t;
#include "llformat.h"
#include "llmemorystream.h"
-#include "../test/hexdump.h"
+#include "hexdump.h"
#include "../test/lltut.h"
#include "../test/namedtempfile.h"
#include "stringize.h"
@@ -1921,12 +1921,12 @@ namespace tut
int bufflen{ static_cast<int>(buffstr.length()) };
out.write(reinterpret_cast<const char*>(&bufflen), sizeof(bufflen));
LL_DEBUGS() << "Wrote length: "
- << hexdump(reinterpret_cast<const char*>(&bufflen),
- sizeof(bufflen))
+ << LL::hexdump(reinterpret_cast<const char*>(&bufflen),
+ sizeof(bufflen))
<< LL_ENDL;
out.write(buffstr.c_str(), buffstr.length());
LL_DEBUGS() << "Wrote data: "
- << hexmix(buffstr.c_str(), buffstr.length())
+ << LL::hexmix(buffstr.c_str(), buffstr.length())
<< LL_ENDL;
}
}
diff --git a/indra/llcommon/tests/threadsafeschedule_test.cpp b/indra/llcommon/tests/threadsafeschedule_test.cpp
index c421cc7b1c..876281eae0 100644
--- a/indra/llcommon/tests/threadsafeschedule_test.cpp
+++ b/indra/llcommon/tests/threadsafeschedule_test.cpp
@@ -46,11 +46,11 @@ namespace tut
// the real time required for each push() call. Explicitly increment
// the timestamp for each one -- but since we're passing explicit
// timestamps, make the queue reorder them.
- queue.push(Queue::TimeTuple(Queue::Clock::now() + 200ms, "ghi"));
+ queue.push(Queue::TimeTuple(Queue::Clock::now() + 2000ms, "ghi"));
// Given the various push() overloads, you have to match the type
// exactly: conversions are ambiguous.
queue.push("abc"s);
- queue.push(Queue::Clock::now() + 100ms, "def");
+ queue.push(Queue::Clock::now() + 1000ms, "def");
queue.close();
auto entry = queue.pop();
ensure_equals("failed to pop first", std::get<0>(entry), "abc"s);
diff --git a/indra/llui/llmenugl.h b/indra/llui/llmenugl.h
index 87e3f18ebc..d606fef625 100644
--- a/indra/llui/llmenugl.h
+++ b/indra/llui/llmenugl.h
@@ -562,14 +562,15 @@ public:
// add a context menu branch
BOOL appendContextSubMenu(LLMenuGL *menu);
+ // Add the menu item to this menu.
+ virtual BOOL append( LLMenuItemGL* item );
+
+ // add a menu - this will create a cascading menu
+ virtual BOOL appendMenu(LLMenuGL *menu);
+
protected:
void createSpilloverBranch();
void cleanupSpilloverBranch();
- // Add the menu item to this menu.
- virtual BOOL append( LLMenuItemGL* item );
-
- // add a menu - this will create a cascading menu
- virtual BOOL appendMenu( LLMenuGL* menu );
// Used in LLContextMenu and in LLTogleableMenu
// to add an item of context menu branch
@@ -805,9 +806,10 @@ public:
void resetMenuTrigger() { mAltKeyTrigger = FALSE; }
+ // add a menu - this will create a drop down menu.
+ virtual BOOL appendMenu(LLMenuGL *menu);
+
private:
- // add a menu - this will create a drop down menu.
- virtual BOOL appendMenu( LLMenuGL* menu );
// rearrange the child rects so they fit the shape of the menu
// bar.
virtual void arrange( void );
diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h
index f3939248c2..ec2b9a4b7d 100644
--- a/indra/llui/lltexteditor.h
+++ b/indra/llui/lltexteditor.h
@@ -241,7 +241,9 @@ protected:
// Undoable operations
void addChar(llwchar c); // at mCursorPos
S32 addChar(S32 pos, llwchar wc);
+public:
void addLineBreakChar(BOOL group_together = FALSE);
+protected:
S32 overwriteChar(S32 pos, llwchar wc);
void removeChar();
S32 removeChar(S32 pos);
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 2c45e0713c..6fc0f28a89 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -46,6 +46,7 @@ include(VisualLeakDetector)
include(ZLIBNG)
include(URIPARSER)
include(LLPrimitive)
+include(lualibs)
if (NOT HAVOK_TPV)
# When using HAVOK_TPV, the library is precompiled, so no need for this
@@ -237,6 +238,7 @@ set(viewer_SOURCE_FILES
llfloaterlandholdings.cpp
llfloaterlinkreplace.cpp
llfloaterloadprefpreset.cpp
+ llfloaterluadebug.cpp
llfloatermarketplacelistings.cpp
llfloatermap.cpp
llfloatermediasettings.cpp
@@ -365,6 +367,7 @@ set(viewer_SOURCE_FILES
lllogchat.cpp
llloginhandler.cpp
lllogininstance.cpp
+ llluamanager.cpp
llmachineid.cpp
llmanip.cpp
llmaniprotate.cpp
@@ -886,6 +889,7 @@ set(viewer_HEADER_FILES
llfloaterlandholdings.h
llfloaterlinkreplace.h
llfloaterloadprefpreset.h
+ llfloaterluadebug.h
llfloatermap.h
llfloatermarketplacelistings.h
llfloatermediasettings.h
@@ -1012,6 +1016,7 @@ set(viewer_HEADER_FILES
lllogchat.h
llloginhandler.h
lllogininstance.h
+ llluamanager.h
llmachineid.h
llmanip.h
llmaniprotate.h
@@ -1708,6 +1713,7 @@ if (WINDOWS)
${SHARED_LIB_STAGING_DIR}/openjp2.dll
${SHARED_LIB_STAGING_DIR}/libhunspell.dll
${SHARED_LIB_STAGING_DIR}/uriparser.dll
+ ${SHARED_LIB_STAGING_DIR}/lua54.dll
#${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/SLVoice.exe
#${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/libsndfile-1.dll
#${SHARED_LIB_STAGING_DIR}/${LL_INTDIR}/vivoxoal.dll
@@ -2279,6 +2285,11 @@ if (LL_TESTS)
"${test_libs}"
)
+ LL_ADD_INTEGRATION_TEST(llluamanager
+ "llluamanager.cpp"
+ "${test_libs}"
+ )
+
LL_ADD_INTEGRATION_TEST(llsechandler_basic
llsechandler_basic.cpp
"${test_libs}"
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 28d84aba21..bf45d18ea8 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -17074,6 +17074,17 @@
<key>Value</key>
<integer>3</integer>
</map>
+ <key>AutorunLuaScriptName</key>
+ <map>
+ <key>Comment</key>
+ <string>Script name to autorun after login.</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>default.lua</string>
+ </map>
<key>ResetUIScaleOnFirstRun</key>
<map>
<key>Comment</key>
diff --git a/indra/newview/llfilepicker.cpp b/indra/newview/llfilepicker.cpp
index f1f156c2e0..b9435dc9df 100644
--- a/indra/newview/llfilepicker.cpp
+++ b/indra/newview/llfilepicker.cpp
@@ -61,6 +61,7 @@ LLFilePicker LLFilePicker::sInstance;
#define MODEL_FILTER L"Model files (*.dae)\0*.dae\0"
#define SCRIPT_FILTER L"Script files (*.lsl)\0*.lsl\0"
#define DICTIONARY_FILTER L"Dictionary files (*.dic; *.xcu)\0*.dic;*.xcu\0"
+#define LUA_FILTER L"Script files (*.lua)\0*.lua\0"
#endif
#ifdef LL_DARWIN
@@ -218,6 +219,10 @@ BOOL LLFilePicker::setupFilter(ELoadFilter filter)
mOFN.lpstrFilter = DICTIONARY_FILTER \
L"\0";
break;
+ case FFLOAD_LUA:
+ mOFN.lpstrFilter = LUA_FILTER \
+ L"\0";
+ break;
default:
res = FALSE;
break;
diff --git a/indra/newview/llfilepicker.h b/indra/newview/llfilepicker.h
index 692b908fff..cb453ea87e 100644
--- a/indra/newview/llfilepicker.h
+++ b/indra/newview/llfilepicker.h
@@ -85,7 +85,8 @@ public:
FFLOAD_SCRIPT = 11,
FFLOAD_DICTIONARY = 12,
FFLOAD_DIRECTORY = 13, // To call from lldirpicker.
- FFLOAD_EXE = 14 // Note: EXE will be treated as ALL on Windows and Linux but not on Darwin
+ FFLOAD_EXE = 14, // Note: EXE will be treated as ALL on Windows and Linux but not on Darwin
+ FFLOAD_LUA = 15
};
enum ESaveFilter
diff --git a/indra/newview/llfloaterluadebug.cpp b/indra/newview/llfloaterluadebug.cpp
new file mode 100644
index 0000000000..44454d7be0
--- /dev/null
+++ b/indra/newview/llfloaterluadebug.cpp
@@ -0,0 +1,122 @@
+/**
+ * @file llfloaterluadebug.cpp
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llfloaterluadebug.h"
+
+#include "lllineeditor.h"
+#include "lltexteditor.h"
+#include "llviewermenufile.h" // LLFilePickerReplyThread
+
+#include "llagent.h"
+#include "llappearancemgr.h"
+#include "llfloaterreg.h"
+#include "llfloaterimnearbychat.h"
+
+#include "llluamanager.h"
+
+#if LL_WINDOWS
+#pragma comment(lib, "liblua54.a")
+#endif
+
+
+LLFloaterLUADebug::LLFloaterLUADebug(const LLSD &key)
+ : LLFloater(key)
+{
+}
+
+
+BOOL LLFloaterLUADebug::postBuild()
+{
+ mResultOutput = getChild<LLTextEditor>("result_text");
+ mLineInput = getChild<LLLineEditor>("lua_cmd");
+ mScriptPath = getChild<LLLineEditor>("script_path");
+ mOutConnection = LLEventPumps::instance().obtain("lua output")
+ .listen("LLFloaterLUADebug",
+ [mResultOutput=mResultOutput](const LLSD& data)
+ {
+ mResultOutput->insertText(data.asString());
+ mResultOutput->addLineBreakChar(true);
+ return false;
+ });
+
+ getChild<LLButton>("execute_btn")->setClickedCallback(boost::bind(&LLFloaterLUADebug::onExecuteClicked, this));
+ getChild<LLButton>("browse_btn")->setClickedCallback(boost::bind(&LLFloaterLUADebug::onBtnBrowse, this));
+ getChild<LLButton>("run_btn")->setClickedCallback(boost::bind(&LLFloaterLUADebug::onBtnRun, this));
+
+#if !LL_WINDOWS
+ getChild<LLButton>("execute_btn")->setEnabled(false);
+ getChild<LLButton>("browse_btn")->setEnabled(false);
+#endif
+
+ return TRUE;
+}
+
+LLFloaterLUADebug::~LLFloaterLUADebug()
+{}
+
+void LLFloaterLUADebug::onExecuteClicked()
+{
+ mResultOutput->setValue("");
+
+ std::string cmd = mLineInput->getText();
+ LLLUAmanager::runScriptLine(cmd, [this](std::string msg)
+ {
+ mResultOutput->insertText(msg);
+ });
+}
+
+void LLFloaterLUADebug::onBtnBrowse()
+{
+ (new LLFilePickerReplyThread(boost::bind(&LLFloaterLUADebug::runSelectedScript, this, _1), LLFilePicker::FFLOAD_LUA, false))->getFile();
+}
+
+void LLFloaterLUADebug::onBtnRun()
+{
+ std::vector<std::string> filenames;
+ std::string filepath = mScriptPath->getText();
+ if (!filepath.empty())
+ {
+ filenames.push_back(filepath);
+ runSelectedScript(filenames);
+ }
+}
+
+void LLFloaterLUADebug::runSelectedScript(const std::vector<std::string> &filenames)
+{
+ mResultOutput->setValue("");
+
+ std::string filepath = filenames[0];
+ if (!filepath.empty())
+ {
+ mScriptPath->setText(filepath);
+ LLLUAmanager::runScriptFile(filepath, [this](std::string msg)
+ {
+ mResultOutput->insertText(msg);
+ });
+ }
+}
+
diff --git a/indra/newview/llfloaterluadebug.h b/indra/newview/llfloaterluadebug.h
new file mode 100644
index 0000000000..fd61e29ebe
--- /dev/null
+++ b/indra/newview/llfloaterluadebug.h
@@ -0,0 +1,66 @@
+/**
+ * @file llfloaterluadebug.h
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLFLOATERLUADEBUG_H
+#define LL_LLFLOATERLUADEBUG_H
+
+#include "llevents.h"
+#include "llfloater.h"
+
+extern "C"
+{
+#include "lua/lua.h"
+#include "lua/lauxlib.h"
+#include "lua/lualib.h"
+}
+
+class LLLineEditor;
+class LLTextEditor;
+
+class LLFloaterLUADebug :
+ public LLFloater
+{
+ public:
+ LLFloaterLUADebug(const LLSD& key);
+ virtual ~LLFloaterLUADebug();
+
+ /*virtual*/ BOOL postBuild();
+
+ void onExecuteClicked();
+ void onBtnBrowse();
+ void onBtnRun();
+
+ void runSelectedScript(const std::vector<std::string> &filenames);
+
+private:
+ LLTempBoundListener mOutConnection;
+
+ LLTextEditor* mResultOutput;
+ LLLineEditor* mLineInput;
+ LLLineEditor* mScriptPath;
+};
+
+#endif // LL_LLFLOATERLUADEBUG_H
+
diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp
index 4aeacae6ed..56e680a602 100644
--- a/indra/newview/llinventoryfunctions.cpp
+++ b/indra/newview/llinventoryfunctions.cpp
@@ -2645,6 +2645,19 @@ bool LLNameCategoryCollector::operator()(
return false;
}
+bool LLNameItemCollector::operator()(
+ LLInventoryCategory* cat, LLInventoryItem* item)
+{
+ if(item)
+ {
+ if (!LLStringUtil::compareInsensitive(mName, item->getName()))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
bool LLFindCOFValidItems::operator()(LLInventoryCategory* cat,
LLInventoryItem* item)
{
diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h
index 925217dda3..d63d4179cf 100644
--- a/indra/newview/llinventoryfunctions.h
+++ b/indra/newview/llinventoryfunctions.h
@@ -367,6 +367,22 @@ protected:
};
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+// Class LLNameItemCollector
+//
+// Collects items based on case-insensitive match of prefix
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+class LLNameItemCollector : public LLInventoryCollectFunctor
+{
+public:
+ LLNameItemCollector(const std::string& name) : mName(name) {}
+ virtual ~LLNameItemCollector() {}
+ virtual bool operator()(LLInventoryCategory* cat,
+ LLInventoryItem* item);
+protected:
+ std::string mName;
+};
+
+//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Class LLFindCOFValidItems
//
// Collects items that can be legitimately linked to in the COF.
diff --git a/indra/newview/llinventorygallery.cpp b/indra/newview/llinventorygallery.cpp
index 845ea01f56..0e4c64a641 100644
--- a/indra/newview/llinventorygallery.cpp
+++ b/indra/newview/llinventorygallery.cpp
@@ -2420,9 +2420,7 @@ void LLInventoryGallery::startDrag()
ids.push_back(selected_id);
}
}
- // We must have set this for some reason, but it's causing compile errors
- (void)src;
- LLToolDragAndDrop::getInstance()->beginMultiDrag(types, ids, LLToolDragAndDrop::SOURCE_AGENT);
+ LLToolDragAndDrop::getInstance()->beginMultiDrag(types, ids, src);
}
bool LLInventoryGallery::areViewsInitialized()
diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp
index ea771661ec..2849ae8858 100644
--- a/indra/newview/llinventorymodel.cpp
+++ b/indra/newview/llinventorymodel.cpp
@@ -4805,6 +4805,17 @@ std::string LLInventoryModel::getFullPath(const LLInventoryObject *obj) const
return result;
}
+/*
+const LLInventoryObject* LLInventoryModel::findByFullPath(const std::string& path)
+{
+ vector<std::string> path_elts;
+ boost::algorithm::split(path_elts, path, boost::is_any_of("/"));
+ for(path_elts, auto e)
+ {
+ }
+}
+*/
+
///----------------------------------------------------------------------------
/// Local function definitions
///----------------------------------------------------------------------------
diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp
new file mode 100644
index 0000000000..0475122832
--- /dev/null
+++ b/indra/newview/llluamanager.cpp
@@ -0,0 +1,1530 @@
+/**
+ * @file llluamanager.cpp
+ * @brief classes and functions for interfacing with LUA.
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ *
+ */
+
+#include "llviewerprecompiledheaders.h"
+#include "llluamanager.h"
+
+#include "hexdump.h"
+#include "llerror.h"
+#include "lleventcoro.h"
+#include "lleventfilter.h"
+#include "llevents.h"
+#include "llinstancetracker.h"
+#include "llleaplistener.h"
+#include "lluuid.h"
+#include "stringize.h"
+
+// skip all these link dependencies for integration testing
+#ifndef LL_TEST
+#include "llagent.h"
+#include "llappearancemgr.h"
+#include "llcallbacklist.h"
+#include "llfloaterreg.h"
+#include "llfloaterimnearbychat.h"
+#include "llfloatersidepanelcontainer.h"
+#include "llnotificationsutil.h"
+#include "llvoavatarself.h"
+#include "llviewermenu.h"
+#include "llviewermenufile.h"
+#include "llviewerwindow.h"
+#include "lluilistener.h"
+#include "llanimationstates.h"
+#include "llinventoryfunctions.h"
+#include "lltoolplacer.h"
+#include "llviewerregion.h"
+
+// FIXME extremely hacky way to get to the UI Listener framework. There's
+// a cleaner way.
+extern LLUIListener sUIListener;
+#endif // ! LL_TEST
+
+#include <boost/algorithm/string/replace.hpp>
+
+extern "C"
+{
+#include "lua/lua.h"
+#include "lua/lauxlib.h"
+#include "lua/lualib.h"
+}
+
+#include <algorithm>
+#include <cstdlib> // std::rand()
+#include <cstring> // std::memcpy()
+#include <map>
+#include <memory> // std::unique_ptr
+#include <sstream>
+#include <string_view>
+#include <vector>
+
+#if LL_WINDOWS
+#pragma comment(lib, "liblua54.a")
+#endif
+
+std::string lua_tostdstring(lua_State* L, int index);
+void lua_pushstdstring(lua_State* L, const std::string& str);
+LLSD lua_tollsd(lua_State* L, int index);
+void lua_pushllsd(lua_State* L, const LLSD& data);
+
+/**
+ * LuaListener is based on LLLeap. It serves an analogous function.
+ *
+ * Each LuaListener instance has an int key, generated randomly to
+ * inconvenience malicious Lua scripts wanting to mess with others. The idea
+ * is that a given lua_State stores in its Registry:
+ * - "event.listener": the int key of the corresponding LuaListener, if any
+ * - "event.function": the Lua function to be called with incoming events
+ * The original thought was that LuaListener would itself store the Lua
+ * function -- but surprisingly, there is no C/C++ type in the API that stores
+ * a Lua function.
+ *
+ * (We considered storing in "event.listener" the LuaListener pointer itself
+ * as a light userdata, but the problem would be if Lua code overwrote that.
+ * We want to prevent any Lua script from crashing the viewer, intentionally
+ * or otherwise. Safer to use a key lookup.)
+ *
+ * Like LLLeap, each LuaListener instance also has an associated
+ * LLLeapListener to respond to LLEventPump management commands.
+ */
+class LuaListener: public LLInstanceTracker<LuaListener, int>
+{
+ using super = LLInstanceTracker<LuaListener, int>;
+public:
+ LuaListener(lua_State* L):
+ super(getUniqueKey()),
+ mListener(
+ new LLLeapListener(
+ [L](LLEventPump& pump, const std::string& listener)
+ { return connect(L, pump, listener); }))
+ {
+ mReplyConnection = connect(L, mReplyPump, "LuaListener");
+ }
+
+ LuaListener(const LuaListener&) = delete;
+ LuaListener& operator=(const LuaListener&) = delete;
+
+ ~LuaListener()
+ {
+ LL_DEBUGS("Lua") << "~LuaListener('" << mReplyPump.getName() << "')" << LL_ENDL;
+ }
+
+ std::string getReplyName() const { return mReplyPump.getName(); }
+ std::string getCommandName() const { return mListener->getPumpName(); }
+
+private:
+ static int getUniqueKey()
+ {
+ // Find a random key that does NOT already correspond to a LuaListener
+ // instance. Passing a duplicate key to LLInstanceTracker would do Bad
+ // Things.
+ int key;
+ do
+ {
+ key = std::rand();
+ } while (LuaListener::getInstance(key));
+ // This is theoretically racy, if we were instantiating new
+ // LuaListeners on multiple threads. Don't.
+ return key;
+ }
+
+ static LLBoundListener connect(lua_State* L, LLEventPump& pump, const std::string& listener)
+ {
+ return pump.listen(
+ listener,
+ [L, pumpname=pump.getName()](const LLSD& data)
+ { return call_lua(L, pumpname, data); });
+ }
+
+ static bool call_lua(lua_State* L, const std::string& pump, const LLSD& data)
+ {
+ LL_INFOS("Lua") << "LuaListener::call_lua('" << pump << "', " << data << ")" << LL_ENDL;
+ if (! lua_checkstack(L, 3))
+ {
+ LL_WARNS("Lua") << "Cannot extend Lua stack to call listen_events() callback"
+ << LL_ENDL;
+ return false;
+ }
+ // push the registered Lua callback function stored in our registry as
+ // "event.function"
+ lua_getfield(L, LUA_REGISTRYINDEX, "event.function");
+ llassert(lua_isfunction(L, -1));
+ // pass pump name
+ lua_pushstdstring(L, pump);
+ // then the data blob
+ lua_pushllsd(L, data);
+ // call the registered Lua listener function; allow it to return bool;
+ // no message handler
+ auto status = lua_pcall(L, 2, 1, 0);
+ bool result{ false };
+ if (status != LUA_OK)
+ {
+ LL_WARNS("Lua") << "Error in listen_events() callback: "
+ << lua_tostdstring(L, -1) << LL_ENDL;
+ }
+ else
+ {
+ result = lua_toboolean(L, -1);
+ }
+ // discard either the error message or the bool return value
+ lua_pop(L, 1);
+ return result;
+ }
+
+#ifndef LL_TEST
+ LLEventStream mReplyPump{ LLUUID::generateNewID().asString() };
+#else
+ LLEventLogProxyFor<LLEventStream> mReplyPump{ "luapump", false };
+#endif
+ LLTempBoundListener mReplyConnection;
+ std::unique_ptr<LLLeapListener> mListener;
+};
+
+/**
+ * LuaPopper is an RAII struct whose role is to pop some number of entries
+ * from the Lua stack if the calling function exits early.
+ */
+struct LuaPopper
+{
+ LuaPopper(lua_State* L, int count):
+ mState(L),
+ mCount(count)
+ {}
+
+ LuaPopper(const LuaPopper&) = delete;
+ LuaPopper& operator=(const LuaPopper&) = delete;
+
+ ~LuaPopper()
+ {
+ if (mCount)
+ {
+ lua_pop(mState, mCount);
+ }
+ }
+
+ void disarm() { set(0); }
+ void set(int count) { mCount = count; }
+
+ lua_State* mState;
+ int mCount;
+};
+
+/**
+ * LuaFunction is a base class containing a static registry of its static
+ * subclass call() methods. call() is NOT virtual: instead, each subclass
+ * constructor passes a pointer to its distinct call() method to the base-
+ * class constructor, along with a name by which to register that method.
+ *
+ * The init() method walks the registry and registers each such name with the
+ * passed lua_State.
+ */
+class LuaFunction
+{
+public:
+ LuaFunction(const std::string_view& name, lua_CFunction function)
+ {
+ getRegistry().emplace(name, function);
+ }
+
+ static void init(lua_State* L)
+ {
+ for (const auto& pair: getRegistry())
+ {
+ lua_register(L, pair.first.c_str(), pair.second);
+ }
+ }
+
+ static lua_CFunction get(const std::string& key)
+ {
+ // use find() instead of subscripting to avoid creating an entry for
+ // unknown key
+ const auto& registry{ getRegistry() };
+ auto found{ registry.find(key) };
+ return (found == registry.end())? nullptr : found->second;
+ }
+
+private:
+ using Registry = std::map<std::string, lua_CFunction>;
+ static Registry& getRegistry()
+ {
+ // use a function-local static to ensure it's initialized
+ static Registry registry;
+ return registry;
+ }
+};
+
+/**
+ * lua_function(name) is a macro to facilitate defining C++ functions
+ * available to Lua. It defines a subclass of LuaFunction and declares a
+ * static instance of that subclass, thereby forcing the compiler to call its
+ * constructor at module initialization time. The constructor passes the
+ * stringized instance name to its LuaFunction base-class constructor, along
+ * with a pointer to the static subclass call() method. It then emits the
+ * call() method definition header, to be followed by a method body enclosed
+ * in curly braces as usual.
+ */
+#define lua_function(name) \
+static struct name##_ : public LuaFunction \
+{ \
+ name##_(): LuaFunction(#name, &call) {} \
+ static int call(lua_State* L); \
+} name; \
+int name##_::call(lua_State* L)
+// {
+// ... supply method body here, referencing 'L' ...
+// }
+
+// This function consumes ALL Lua stack arguments and returns concatenated
+// message string
+std::string lua_print_msg(lua_State* L, const std::string_view& level)
+{
+ // On top of existing Lua arguments, push 'where' info
+ luaL_checkstack(L, 1, nullptr);
+ luaL_where(L, 1);
+ // start with the 'where' info at the top of the stack
+ std::ostringstream out;
+ out << lua_tostring(L, -1);
+ lua_pop(L, 1);
+ const char* sep = ""; // 'where' info ends with ": "
+ // now iterate over arbitrary args, calling Lua tostring() on each and
+ // concatenating with separators
+ for (int p = 1; p <= lua_gettop(L); ++p)
+ {
+ out << sep;
+ sep = " ";
+ // push Lua tostring() function -- note, semantically different from
+ // lua_tostring()!
+ lua_getglobal(L, "tostring");
+ // Now the stack is arguments 1 .. N, plus tostring().
+ // Rotate downwards, producing stack args 2 .. N, tostring(), arg1.
+ lua_rotate(L, 1, -1);
+ // pop tostring() and arg1, pushing tostring(arg1)
+ // (ignore potential error code from lua_pcall() because, if there was
+ // an error, we expect the stack top to be an error message -- which
+ // we'll print)
+ lua_pcall(L, 1, 1, 0);
+ // stack now holds args 2 .. N, tostring(arg1)
+ out << lua_tostring(L, -1);
+ }
+ // pop everything
+ lua_settop(L, 0);
+ // capture message string
+ std::string msg{ out.str() };
+ // put message out there for any interested party (*koff* LLFloaterLUADebug *koff*)
+ LLEventPumps::instance().obtain("lua output").post(stringize(level, ": ", msg));
+ return msg;
+}
+
+lua_function(print_debug)
+{
+ LL_DEBUGS("Lua") << lua_print_msg(L, "DEBUG") << LL_ENDL;
+ return 0;
+}
+
+// also used for print(); see LuaState constructor
+lua_function(print_info)
+{
+ LL_INFOS("Lua") << lua_print_msg(L, "INFO") << LL_ENDL;
+ return 0;
+}
+
+lua_function(print_warning)
+{
+ LL_WARNS("Lua") << lua_print_msg(L, "WARN") << LL_ENDL;
+ return 0;
+}
+
+#ifndef LL_TEST
+lua_function(avatar_sit)
+{
+ gAgent.sitDown();
+ return 0;
+}
+
+lua_function(avatar_stand)
+{
+ gAgent.standUp();
+ return 0;
+}
+
+lua_function(nearby_chat_send)
+{
+ std::string msg(lua_tostring(L, 1));
+ LLFloaterIMNearbyChat *nearby_chat = LLFloaterReg::findTypedInstance<LLFloaterIMNearbyChat>("nearby_chat");
+ nearby_chat->sendChatFromViewer(msg, CHAT_TYPE_NORMAL, gSavedSettings.getBOOL("PlayChatAnim"));
+
+ lua_pop(L, 1);
+ return 0;
+}
+
+lua_function(wear_by_name)
+{
+ std::string folder_name(lua_tostring(L, 1));
+ LLAppearanceMgr::instance().wearOutfitByName(folder_name);
+
+ lua_pop(L, 1);
+ return 0;
+}
+
+lua_function(open_floater)
+{
+ std::string floater_name(lua_tostring(L, 1));
+
+ LLSD key;
+ if (floater_name == "profile")
+ {
+ key["id"] = gAgentID;
+ }
+ LLFloaterReg::showInstance(floater_name, key);
+
+ lua_pop(L, 1);
+ return 0;
+}
+
+lua_function(close_floater)
+{
+ std::string floater_name(lua_tostring(L, 1));
+
+ LLSD key;
+ if (floater_name == "profile")
+ {
+ key["id"] = gAgentID;
+ }
+ LLFloaterReg::hideInstance(floater_name, key);
+
+ lua_pop(L, 1);
+ return 0;
+}
+
+lua_function(close_all_floaters)
+{
+ close_all_windows();
+ return 0;
+}
+
+lua_function(click_child)
+{
+ std::string parent_name(lua_tostring(L, 1));
+ std::string child_name(lua_tostring(L, 2));
+
+ LLFloater *floater = LLFloaterReg::findInstance(parent_name);
+ LLUICtrl *child = floater->getChild<LLUICtrl>(child_name, true);
+ child->onCommit();
+
+ lua_pop(L, 2);
+ return 0;
+}
+
+lua_function(snapshot_to_file)
+{
+ std::string filename(lua_tostring(L, 1));
+
+ //don't take snapshot from coroutine
+ doOnIdleOneTime([filename]()
+ {
+ gViewerWindow->saveSnapshot(filename,
+ gViewerWindow->getWindowWidthRaw(),
+ gViewerWindow->getWindowHeightRaw(),
+ gSavedSettings.getBOOL("RenderUIInSnapshot"),
+ gSavedSettings.getBOOL("RenderHUDInSnapshot"),
+ FALSE,
+ LLSnapshotModel::SNAPSHOT_TYPE_COLOR,
+ LLSnapshotModel::SNAPSHOT_FORMAT_PNG);
+ });
+
+ lua_pop(L, 1);
+ return 0;
+}
+
+lua_function(open_wearing_tab)
+{
+ LLFloaterSidePanelContainer::showPanel("appearance", LLSD().with("type", "now_wearing"));
+ return 0;
+}
+
+lua_function(set_debug_setting_bool)
+{
+ std::string setting_name(lua_tostring(L, 1));
+ bool value(lua_toboolean(L, 2));
+
+ gSavedSettings.setBOOL(setting_name, value);
+ lua_pop(L, 2);
+ return 0;
+}
+
+lua_function(get_avatar_name)
+{
+ std::string name = gAgentAvatarp->getFullname();
+ luaL_checkstack(L, 1, nullptr);
+ lua_pushstdstring(L, name);
+ return 1;
+}
+
+lua_function(is_avatar_flying)
+{
+ luaL_checkstack(L, 1, nullptr);
+ lua_pushboolean(L, gAgent.getFlying());
+ return 1;
+}
+
+lua_function(play_animation)
+{
+ // on exit, pop all passed arguments, so always return 0
+ LuaPopper popper(L, lua_gettop(L));
+
+ std::string anim_name = lua_tostring(L,1);
+
+ EAnimRequest req = ANIM_REQUEST_START;
+ if (lua_gettop(L) > 1)
+ {
+ req = (EAnimRequest) (int) lua_tonumber(L, 2);
+ }
+
+ LLInventoryModel::cat_array_t cat_array;
+ LLInventoryModel::item_array_t item_array;
+ LLNameItemCollector has_name(anim_name);
+ gInventory.collectDescendentsIf(gInventory.getRootFolderID(),
+ cat_array,
+ item_array,
+ LLInventoryModel::EXCLUDE_TRASH,
+ has_name);
+ for (auto& item: item_array)
+ {
+ if (item->getType() == LLAssetType::AT_ANIMATION)
+ {
+ LLUUID anim_id = item->getAssetUUID();
+ LL_INFOS() << "Playing animation " << anim_id << LL_ENDL;
+ gAgent.sendAnimationRequest(anim_id, req);
+ return 0;
+ }
+ }
+ LL_WARNS() << "No animation found for name " << anim_name << LL_ENDL;
+
+ return 0;
+}
+
+lua_function(env_setting_event)
+{
+ handle_env_setting_event(lua_tostring(L, 1));
+ lua_pop(L, 1);
+ return 0;
+}
+
+void handle_notification_dialog(const LLSD &notification, const LLSD &response, lua_State *L, std::string response_cb)
+{
+ if (!response_cb.empty())
+ {
+ S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
+
+ luaL_checkstack(L, 1, nullptr);
+ lua_pushinteger(L, option);
+ lua_setglobal(L, response_cb.c_str());
+ }
+}
+
+lua_function(show_notification)
+{
+ std::string notification(lua_tostring(L, 1));
+
+ if (lua_type(L, 2) == LUA_TTABLE)
+ {
+ LLSD args = lua_tollsd(L, 2);
+
+ std::string response_cb;
+ if (lua_type(L, 3) == LUA_TSTRING)
+ {
+ response_cb = lua_tostring(L, 3);
+ }
+
+ LLNotificationsUtil::add(notification, args, LLSD(), boost::bind(handle_notification_dialog, _1, _2, L, response_cb));
+ }
+ else if (lua_type(L, 2) == LUA_TSTRING)
+ {
+ std::string response_cb = lua_tostring(L, 2);
+ LLNotificationsUtil::add(notification, LLSD(), LLSD(), boost::bind(handle_notification_dialog, _1, _2, L, response_cb));
+ }
+ else
+ {
+ LLNotificationsUtil::add(notification);
+ }
+
+ lua_settop(L, 0);
+ return 0;
+}
+
+lua_function(add_menu_item)
+{
+ std::string menu(lua_tostring(L, 1));
+ if (lua_type(L, 2) == LUA_TTABLE)
+ {
+ LLSD args = lua_tollsd(L, 2);
+
+ LLMenuItemCallGL::Params item_params;
+ item_params.name = args["name"];
+ item_params.label = args["label"];
+
+ LLUICtrl::CommitCallbackParam item_func;
+ item_func.function_name = args["function"];
+ if (args.has("parameter"))
+ {
+ item_func.parameter = args["parameter"];
+ }
+ item_params.on_click = item_func;
+
+ LLMenuItemCallGL *menu_item = LLUICtrlFactory::create<LLMenuItemCallGL>(item_params);
+ gMenuBarView->findChildMenuByName(menu, true)->append(menu_item);
+ }
+
+ lua_settop(L, 0);
+ return 0;
+}
+
+lua_function(add_menu_separator)
+{
+ std::string menu(lua_tostring(L, 1));
+ gMenuBarView->findChildMenuByName(menu, true)->addSeparator();
+
+ lua_pop(L, 1);
+ return 0;
+}
+
+lua_function(add_menu)
+{
+ if (lua_type(L, 1) == LUA_TTABLE)
+ {
+ LLSD args = lua_tollsd(L, 1);
+
+ LLMenuGL::Params item_params;
+ item_params.name = args["name"];
+ item_params.label = args["label"];
+ item_params.can_tear_off = args["tear_off"];
+
+ LLMenuGL *menu = LLUICtrlFactory::create<LLMenuGL>(item_params);
+ gMenuBarView->appendMenu(menu);
+ }
+
+ lua_settop(L, 0);
+ return 0;
+}
+
+lua_function(add_branch)
+{
+ std::string menu(lua_tostring(L, 1));
+ if (lua_type(L, 2) == LUA_TTABLE)
+ {
+ LLSD args = lua_tollsd(L, 2);
+
+ LLMenuGL::Params item_params;
+ item_params.name = args["name"];
+ item_params.label = args["label"];
+ item_params.can_tear_off = args["tear_off"];
+
+ LLMenuGL *branch = LLUICtrlFactory::create<LLMenuGL>(item_params);
+ gMenuBarView->findChildMenuByName(menu, true)->appendMenu(branch);
+ }
+
+ lua_settop(L, 0);
+ return 0;
+}
+
+// rez_prim({x, y}, prim_type)
+// avatar is the reference point
+lua_function(rez_prim)
+{
+ lua_rawgeti(L, 1, 1);
+ F32 x = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+ lua_rawgeti(L, 1, 2);
+ F32 y = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+
+ S32 type(lua_tonumber(L, 2)); // primitive shapes 1-8
+
+ LLVector3 obj_pos = gAgent.getPositionAgent() + LLVector3(x, y, -0.5);
+ bool res = LLToolPlacer::rezNewObject(type, NULL, 0, TRUE, gAgent.getPositionAgent(), obj_pos, gAgent.getRegion(), 0);
+
+ LL_INFOS() << "Rezing a prim: type " << LLPrimitive::pCodeToString(type) << ", coordinates: " << obj_pos << " Success: " << res << LL_ENDL;
+
+ lua_pop(L, lua_gettop(L));
+ return 0;
+}
+
+// rez_prim2({x, y,z}, prim_type)
+// avatar is the reference point
+lua_function(rez_prim2)
+{
+ luaL_checktype(L, 1, LUA_TTABLE);
+ S32 type(lua_tonumber(L,2));
+ lua_pop(L,1);
+
+ lua_pushinteger(L, 1);
+ lua_gettable(L, -2);
+ F32 x = lua_tonumber(L,-1);
+ lua_pop(L,1);
+
+ lua_pushinteger(L, 2);
+ lua_gettable(L, -2);
+ F32 y = lua_tonumber(L,-1);
+ lua_pop(L,1);
+
+ lua_pushinteger(L, 3);
+ lua_gettable(L, -2);
+ F32 z = lua_tonumber(L,-1);
+ lua_pop(L,1);
+
+ LL_INFOS() << "x " << x << " y " << y << " z " << z << " type " << type << LL_ENDL;
+
+ LLVector3 obj_pos = gAgent.getPositionAgent() + LLVector3(x, y, z);
+ bool res = LLToolPlacer::rezNewObject(type, NULL, 0, TRUE, gAgent.getPositionAgent(), obj_pos, gAgent.getRegion(), 0);
+
+ LL_INFOS() << "Rezing a prim: type " << LLPrimitive::pCodeToString(type) << ", coordinates: " << obj_pos << " Success: " << res << LL_ENDL;
+
+ lua_settop(L, 0);
+ return 0;
+}
+
+
+void move_to_dest(const LLVector3d &target_global, lua_State *L, std::string response_cb)
+{
+ struct Data
+ {
+ lua_State *L;
+ std::string response_cb;
+ };
+
+ Data *data = new Data();
+ data->L = L;
+ if (!response_cb.empty())
+ {
+ data->response_cb = response_cb;
+ }
+
+ auto handle_dest_reached = [](BOOL success, void *user_data)
+ {
+ Data *cb_data = static_cast<Data *>(user_data);
+ if (!cb_data->response_cb.empty())
+ {
+ S32 result = success ? 1 : -1;
+ lua_pushinteger(cb_data->L, result);
+ lua_setglobal(cb_data->L, cb_data->response_cb.c_str());
+ }
+ };
+
+ gAgent.startAutoPilotGlobal(target_global, std::string(), NULL, handle_dest_reached, data, 0.f, 0.03f, FALSE);
+}
+
+// move_by({x,y}, "lua_cb_func")
+// avatar is the reference point
+lua_function(move_by)
+{
+ lua_rawgeti(L, 1, 1);
+ F32 x = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+ lua_rawgeti(L, 1, 2);
+ F32 y = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+
+ LLVector3d dest = gAgent.getRegion()->getPosGlobalFromRegion(gAgent.getPositionAgent() + LLVector3(x, y, 0));
+
+ std::string response_cb;
+ if (lua_type(L, 2) == LUA_TSTRING)
+ {
+ response_cb = lua_tostring(L, 2);
+ }
+ move_to_dest(dest, L, response_cb);
+
+ lua_settop(L, 0);
+ return 0;
+}
+
+// move_to({x,y,z}, "lua_cb_func")
+// region coordinates are used
+lua_function(move_to)
+{
+ lua_rawgeti(L, 1, 1);
+ F32 x = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+ lua_rawgeti(L, 1, 2);
+ F32 y = lua_tonumber(L, -1);
+ lua_rawgeti(L, 1, 3);
+ F32 z = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+
+ LLVector3d dest = gAgent.getRegion()->getPosGlobalFromRegion(LLVector3(x, y, z));
+
+ std::string response_cb;
+ if (lua_type(L, 2) == LUA_TSTRING)
+ {
+ response_cb = lua_tostring(L, 2);
+ }
+ move_to_dest(dest, L, response_cb);
+
+ lua_settop(L, 0);
+ return 0;
+}
+
+lua_function(run_ui_command)
+{
+ int top = lua_gettop(L);
+ std::string func_name;
+ if (top >= 1)
+ {
+ func_name = lua_tostring(L,1);
+ }
+ std::string parameter;
+ if (top >= 2)
+ {
+ parameter = lua_tostring(L,2);
+ }
+ LL_WARNS("LUA") << "running ui func " << func_name << " parameter " << parameter << LL_ENDL;
+ LLSD event;
+ event["function"] = func_name;
+ if (!parameter.empty())
+ {
+ event["parameter"] = parameter;
+ }
+ sUIListener.call(event);
+
+ lua_settop(L, 0);
+ return 0;
+}
+#endif // ! LL_TEST
+
+lua_function(post_on)
+{
+ std::string pumpname{ lua_tostdstring(L, 1) };
+ LLSD data{ lua_tollsd(L, 2) };
+ lua_pop(L, 2);
+ LL_INFOS("Lua") << "post_on('" << pumpname << "', " << data << ")" << LL_ENDL;
+ LLEventPumps::instance().obtain(pumpname).post(data);
+ return 0;
+}
+
+lua_function(listen_events)
+{
+ if (! lua_isfunction(L, 1))
+ {
+ return luaL_typeerror(L, 1, "function");
+ }
+ luaL_checkstack(L, 2, nullptr);
+
+ // Get the lua_State* for the main thread of this state, in case we were
+ // called from a coroutine thread. We're going to make callbacks into Lua
+ // code, and we want to do it on the main thread rather than a (possibly
+ // suspended) coroutine thread.
+ // Registry table is at pseudo-index LUA_REGISTRYINDEX
+ // Main thread is at registry key LUA_RIDX_MAINTHREAD
+ auto regtype{ lua_geti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD) };
+ // Not finding the main thread at the documented place isn't a user error,
+ // it's a Problem
+ llassert_always(regtype == LUA_TTHREAD);
+ lua_State* mainthread{ lua_tothread(L, -1) };
+ // pop the main thread
+ lua_pop(L, 1);
+
+ luaL_checkstack(mainthread, 1, nullptr);
+ LuaListener::ptr_t listener;
+ // Does the main thread already have a LuaListener stored in the registry?
+ // That is, has this Lua chunk already called listen_events()?
+ auto keytype{ lua_getfield(mainthread, LUA_REGISTRYINDEX, "event.listener") };
+ llassert(keytype == LUA_TNIL || keytype == LUA_TNUMBER);
+ if (keytype == LUA_TNUMBER)
+ {
+ // We do already have a LuaListener. Retrieve it.
+ int isint;
+ listener = LuaListener::getInstance(lua_tointegerx(mainthread, -1, &isint));
+ // pop the int "event.listener" key
+ lua_pop(mainthread, 1);
+ // Nobody should have destroyed this LuaListener instance!
+ llassert(isint && listener);
+ }
+ else
+ {
+ // pop the nil "event.listener" key
+ lua_pop(mainthread, 1);
+ // instantiate a new LuaListener, binding the mainthread state -- but
+ // use a no-op deleter: we do NOT want to delete this new LuaListener
+ // on return from listen_events()!
+ listener.reset(new LuaListener(mainthread), [](LuaListener*){});
+ // set its key in the field where we'll look for it later
+ lua_pushinteger(mainthread, listener->getKey());
+ lua_setfield(mainthread, LUA_REGISTRYINDEX, "event.listener");
+ }
+
+ // Now that we've found or created our LuaListener, store the passed Lua
+ // function as the callback. Beware: our caller passed the function on L's
+ // stack, but we want to store it on the mainthread registry.
+ if (L != mainthread)
+ {
+ // push 1 value (the Lua function) from L's stack to mainthread's
+ lua_xmove(L, mainthread, 1);
+ }
+ lua_setfield(mainthread, LUA_REGISTRYINDEX, "event.function");
+
+ // return the reply pump name and the command pump name on caller's lua_State
+ lua_pushstdstring(L, listener->getReplyName());
+ lua_pushstdstring(L, listener->getCommandName());
+ return 2;
+}
+
+lua_function(await_event)
+{
+ // await_event(pumpname [, timeout [, value to return if timeout (default nil)]])
+ auto pumpname{ lua_tostdstring(L, 1) };
+ LLSD result;
+ if (lua_gettop(L) > 1)
+ {
+ auto timeout{ lua_tonumber(L, 2) };
+ // with no 3rd argument, should be LLSD()
+ auto dftval{ lua_tollsd(L, 3) };
+ lua_settop(L, 0);
+ result = llcoro::suspendUntilEventOnWithTimeout(pumpname, timeout, dftval);
+ }
+ else
+ {
+ // no timeout
+ lua_pop(L, 1);
+ result = llcoro::suspendUntilEventOn(pumpname);
+ }
+ lua_pushllsd(L, result);
+ return 1;
+}
+
+/**
+ * RAII class to manage the lifespan of a lua_State
+ */
+class LuaState
+{
+public:
+ LuaState(const std::string_view& desc, LLLUAmanager::script_finished_fn cb):
+ mDesc(desc),
+ mCallback(cb),
+ mState(luaL_newstate())
+ {
+ luaL_openlibs(mState);
+ LuaFunction::init(mState);
+ // Try to make print() write to our log.
+ lua_register(mState, "print", LuaFunction::get("print_info"));
+ }
+
+ LuaState(const LuaState&) = delete;
+ LuaState& operator=(const LuaState&) = delete;
+
+ ~LuaState()
+ {
+ // Did somebody call listen_events() on this LuaState?
+ // That is, is there a LuaListener key in its registry?
+ auto keytype{ lua_getfield(mState, LUA_REGISTRYINDEX, "event.listener") };
+ if (keytype == LUA_TNUMBER)
+ {
+ // We do have a LuaListener. Retrieve it.
+ int isint;
+ auto listener{ LuaListener::getInstance(lua_tointegerx(mState, -1, &isint)) };
+ // pop the int "event.listener" key
+ lua_pop(mState, 1);
+ // if we got a LuaListener instance, destroy it
+ // (if (! isint), lua_tointegerx() returned 0, but key 0 might
+ // validly designate someone ELSE's LuaListener)
+ if (isint && listener)
+ {
+ auto lptr{ listener.get() };
+ listener.reset();
+ delete lptr;
+ }
+ }
+
+ lua_close(mState);
+
+ if (mCallback)
+ {
+ // mError potentially set by previous checkLua() call(s)
+ mCallback(mError);
+ }
+ }
+
+ bool checkLua(int r)
+ {
+ if (r != LUA_OK)
+ {
+ mError = lua_tostring(mState, -1);
+ lua_pop(mState, 1);
+
+ LL_WARNS() << mDesc << ": " << mError << LL_ENDL;
+ return false;
+ }
+ return true;
+ }
+
+ operator lua_State*() const { return mState; }
+
+private:
+ std::string mDesc;
+ LLLUAmanager::script_finished_fn mCallback;
+ lua_State* mState;
+ std::string mError;
+};
+
+void LLLUAmanager::runScriptFile(const std::string& filename, script_finished_fn cb)
+{
+ std::string desc{ stringize("runScriptFile('", filename, "')") };
+ LLCoros::instance().launch(desc, [desc, filename, cb]()
+ {
+ LuaState L(desc, cb);
+
+ auto LUA_sleep_func = [](lua_State *L)
+ {
+ F32 seconds = lua_tonumber(L, -1);
+ lua_pop(L, 1);
+ llcoro::suspendUntilTimeout(seconds);
+ return 0;
+ };
+
+ lua_register(L, "sleep", LUA_sleep_func);
+
+ if (L.checkLua(luaL_dofile(L, filename.c_str())))
+ {
+ lua_getglobal(L, "call_once_func");
+ if (lua_isfunction(L, -1))
+ {
+ // call call_once_func(), setting internal error message if
+ // error
+ L.checkLua(lua_pcall(L, 0, 0, 0));
+ }
+ }
+ });
+}
+
+void LLLUAmanager::runScriptLine(const std::string& cmd, script_finished_fn cb)
+{
+ // find a suitable abbreviation for the cmd string
+ std::string_view shortcmd{ cmd };
+ const size_t shortlen = 40;
+ std::string::size_type eol = shortcmd.find_first_of("\r\n");
+ if (eol != std::string::npos)
+ shortcmd = shortcmd.substr(0, eol);
+ if (shortcmd.length() > shortlen)
+ shortcmd = stringize(shortcmd.substr(0, shortlen), "...");
+
+ std::string desc{ stringize("runScriptLine('", shortcmd, "')") };
+ LLCoros::instance().launch(desc, [desc, cmd, cb]()
+ {
+ LuaState L(desc, cb);
+ L.checkLua(luaL_dostring(L, cmd.c_str()));
+ });
+}
+
+void LLLUAmanager::runScriptOnLogin()
+{
+#ifndef LL_TEST
+ std::string filename = gSavedSettings.getString("AutorunLuaScriptName");
+ if (filename.empty())
+ {
+ LL_INFOS() << "Script name wasn't set." << LL_ENDL;
+ return;
+ }
+
+ filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, filename);
+ if (!gDirUtilp->fileExists(filename))
+ {
+ LL_INFOS() << filename << " was not found." << LL_ENDL;
+ return;
+ }
+
+ runScriptFile(filename);
+#endif // ! LL_TEST
+}
+
+std::string lua_tostdstring(lua_State* L, int index)
+{
+ size_t len;
+ const char* strval{ lua_tolstring(L, index, &len) };
+ return { strval, len };
+}
+
+void lua_pushstdstring(lua_State* L, const std::string& str)
+{
+ luaL_checkstack(L, 1, nullptr);
+ lua_pushlstring(L, str.c_str(), str.length());
+}
+
+// Usage: std::cout << lua_what(L, stackindex) << ...;
+// Reports on the Lua value found at the passed stackindex.
+// If cast to std::string, returns the corresponding string value.
+class lua_what
+{
+public:
+ lua_what(lua_State* state, int idx):
+ L(state),
+ index(idx)
+ {}
+
+ friend std::ostream& operator<<(std::ostream& out, const lua_what& self)
+ {
+ switch (lua_type(self.L, self.index))
+ {
+ case LUA_TNONE:
+ // distinguish acceptable but non-valid index
+ out << "none";
+ break;
+
+ case LUA_TNIL:
+ out << "nil";
+ break;
+
+ case LUA_TBOOLEAN:
+ {
+ auto oldflags { out.flags() };
+ out << std::boolalpha << lua_toboolean(self.L, self.index);
+ out.flags(oldflags);
+ break;
+ }
+
+ case LUA_TNUMBER:
+ out << lua_tonumber(self.L, self.index);
+ break;
+
+ case LUA_TSTRING:
+ out << std::quoted(lua_tostdstring(self.L, self.index));
+ break;
+
+ case LUA_TUSERDATA:
+ {
+ const size_t maxlen = 20;
+ size_t binlen{ lua_rawlen(self.L, self.index) };
+ LLSD::Binary binary(std::min(maxlen, binlen));
+ std::memcpy(binary.data(), lua_touserdata(self.L, self.index), binary.size());
+ out << LL::hexdump(binary);
+ if (binlen > maxlen)
+ {
+ out << "...(" << (binlen - maxlen) << " more)";
+ }
+ break;
+ }
+
+ case LUA_TLIGHTUSERDATA:
+ out << lua_touserdata(self.L, self.index);
+ break;
+
+ default:
+ // anything else, don't bother trying to report value, just type
+ out << lua_typename(self.L, lua_type(self.L, self.index));
+ break;
+ }
+ return out;
+ }
+
+ operator std::string() const { return stringize(*this); }
+
+private:
+ lua_State* L;
+ int index;
+};
+
+// Usage: std::cout << lua_stack(L) << ...;
+// Reports on the contents of the Lua stack.
+// If cast to std::string, returns the corresponding string value.
+class lua_stack
+{
+public:
+ lua_stack(lua_State* state):
+ L(state)
+ {}
+
+ friend std::ostream& operator<<(std::ostream& out, const lua_stack& self)
+ {
+ const char* sep = "stack: [";
+ for (int index = 1; index <= lua_gettop(self.L); ++index)
+ {
+ out << sep << lua_what(self.L, index);
+ sep = ", ";
+ }
+ out << ']';
+ return out;
+ }
+
+ operator std::string() const { return stringize(*this); }
+
+private:
+ lua_State* L;
+};
+
+// log exit from any block declaring an instance of DebugExit, regardless of
+// how control leaves that block
+struct DebugExit
+{
+ DebugExit(const std::string& name): mName(name) {}
+ DebugExit(const DebugExit&) = delete;
+ DebugExit& operator=(const DebugExit&) = delete;
+ ~DebugExit()
+ {
+ LL_DEBUGS("Lua") << "exit " << mName << LL_ENDL;
+ }
+
+ std::string mName;
+};
+
+// By analogy with existing lua_tomumble() functions, return an LLSD object
+// corresponding to the Lua object at stack index 'index' in state L.
+// This function assumes that a Lua caller is fully aware that they're trying
+// to call a viewer function. In other words, the caller must specifically
+// construct Lua data convertible to LLSD.
+//
+// For proper error handling, we REQUIRE that the Lua runtime be compiled as
+// C++ so errors are raised as C++ exceptions rather than as longjmp() calls:
+// http://www.lua.org/manual/5.4/manual.html#4.4
+// "Internally, Lua uses the C longjmp facility to handle errors. (Lua will
+// use exceptions if you compile it as C++; search for LUAI_THROW in the
+// source code for details.)"
+// Some blocks within this function construct temporary C++ objects in the
+// expectation that these objects will be properly destroyed even if code
+// reached by that block raises a Lua error.
+LLSD lua_tollsd(lua_State* L, int index)
+{
+ LL_DEBUGS("Lua") << "lua_tollsd(" << index << ") of " << lua_gettop(L) << " stack entries: "
+ << lua_what(L, index) << LL_ENDL;
+ DebugExit log_exit("lua_tollsd()");
+ switch (lua_type(L, index))
+ {
+ case LUA_TNONE:
+ // Should LUA_TNONE be an error instead of returning isUndefined()?
+ case LUA_TNIL:
+ return {};
+
+ case LUA_TBOOLEAN:
+ return bool(lua_toboolean(L, index));
+
+ case LUA_TNUMBER:
+ {
+ // check if integer truncation leaves the number intact
+ int isint;
+ lua_Integer intval{ lua_tointegerx(L, index, &isint) };
+ if (isint)
+ {
+ return LLSD::Integer(intval);
+ }
+ else
+ {
+ return lua_tonumber(L, index);
+ }
+ }
+
+ case LUA_TSTRING:
+ return lua_tostdstring(L, index);
+
+ case LUA_TUSERDATA:
+ {
+ LLSD::Binary binary(lua_rawlen(L, index));
+ std::memcpy(binary.data(), lua_touserdata(L, index), binary.size());
+ return binary;
+ }
+
+ case LUA_TTABLE:
+ {
+ // A Lua table correctly constructed to convert to LLSD will have
+ // either consecutive integer keys starting at 1, which we represent
+ // as an LLSD array (with Lua key 1 at C++ index 0), or will have
+ // all string keys.
+ //
+ // In the belief that Lua table traversal skips "holes," that is, it
+ // doesn't report any key/value pair whose value is nil, we allow a
+ // table with integer keys >= 1 but with "holes." This produces an
+ // LLSD array with isUndefined() entries at unspecified keys. There
+ // would be no other way for a Lua caller to construct an
+ // isUndefined() LLSD array entry. However, to guard against crazy int
+ // keys, we forbid gaps larger than a certain size: crazy int keys
+ // could result in a crazy large contiguous LLSD array.
+ //
+ // Possible looseness could include:
+ // - A mix of integer and string keys could produce an LLSD map in
+ // which the integer keys are converted to string. (Key conversion
+ // must be performed in C++, not Lua, to avoid confusing
+ // lua_next().)
+ // - However, since in Lua t[0] and t["0"] are distinct table entries,
+ // do not consider converting numeric string keys to int to return
+ // an LLSD array.
+ // But until we get more experience with actual Lua scripts in
+ // practice, let's say that any deviation is a Lua coding error.
+ // An important property of the strict definition above is that most
+ // conforming data blobs can make a round trip across the language
+ // boundary and still compare equal. A non-conforming data blob would
+ // lose that property.
+ // Known exceptions to round trip identity:
+ // - Empty LLSD map and empty LLSD array convert to empty Lua table.
+ // But empty Lua table converts to isUndefined() LLSD object.
+ // - LLSD::Real with integer value returns as LLSD::Integer.
+ // - LLSD::UUID, LLSD::Date and LLSD::URI all convert to Lua string,
+ // and so return as LLSD::String.
+ // - Lua does not store any table key whose value is nil. An LLSD
+ // array with isUndefined() entries produces a Lua table with
+ // "holes" in the int key sequence; this converts back to an LLSD
+ // array containing corresponding isUndefined() entries -- except
+ // when one or more of the final entries isUndefined(). These are
+ // simply dropped, producing a shorter LLSD array than the original.
+ // - For the same reason, any keys in an LLSD map whose value
+ // isUndefined() are simply discarded in the converted Lua table.
+ // This converts back to an LLSD map lacking those keys.
+ // - If it's important to preserve the original length of an LLSD
+ // array whose final entries are undefined, or the full set of keys
+ // for an LLSD map some of whose values are undefined, store an
+ // LLSD::emptyArray() or emptyMap() instead. These will be
+ // represented in Lua as empty table, which should convert back to
+ // undefined LLSD. Naturally, though, those won't survive a second
+ // round trip.
+
+ // This is the most important of the luaL_checkstack() calls because a
+ // deeply nested Lua structure will enter this case at each level, and
+ // we'll need another 2 stack slots to traverse each nested table.
+ luaL_checkstack(L, 2, nullptr);
+ // BEFORE we push nil to initialize the lua_next() traversal, convert
+ // 'index' to absolute! Our caller might have passed a relative index;
+ // we do, below: lua_tollsd(L, -1). If 'index' is -1, then when we
+ // push nil, what we find at index -1 is nil, not the table!
+ index = lua_absindex(L, index);
+ LL_DEBUGS("Lua") << "checking for empty table" << LL_ENDL;
+ lua_pushnil(L); // first key
+ LL_DEBUGS("Lua") << lua_stack(L) << LL_ENDL;
+ if (! lua_next(L, index))
+ {
+ // it's a table, but the table is empty -- no idea if it should be
+ // modeled as empty array or empty map -- return isUndefined(),
+ // which can be consumed as either
+ LL_DEBUGS("Lua") << "empty table" << LL_ENDL;
+ return {};
+ }
+ // key is at stack index -2, value at index -1
+ // from here until lua_next() returns 0, have to lua_pop(2) if we
+ // return early
+ LuaPopper popper(L, 2);
+ // Remember the type of the first key
+ auto firstkeytype{ lua_type(L, -2) };
+ LL_DEBUGS("Lua") << "table not empty, first key type " << lua_typename(L, firstkeytype)
+ << LL_ENDL;
+ switch (firstkeytype)
+ {
+ case LUA_TNUMBER:
+ {
+ // First Lua key is a number: try to convert table to LLSD array.
+ // This is tricky because we don't know in advance the size of the
+ // array. The Lua reference manual says that lua_rawlen() is the
+ // same as the length operator '#'; but the length operator states
+ // that it might stop at any "hole" in the subject table.
+ // Moreover, the Lua next() function (and presumably lua_next())
+ // traverses a table in unspecified order, even for numeric keys
+ // (emphasized in the doc).
+ // Make a preliminary pass over the whole table to validate and to
+ // collect keys.
+ std::vector<LLSD::Integer> keys;
+ // Try to determine the length of the table. If the length
+ // operator is truthful, avoid allocations while we grow the keys
+ // vector. Even if it's not, we can still grow the vector, albeit
+ // a little less efficiently.
+ keys.reserve(luaL_len(L, index));
+ do
+ {
+ auto arraykeytype{ lua_type(L, -2) };
+ switch (arraykeytype)
+ {
+ case LUA_TNUMBER:
+ {
+ int isint;
+ lua_Integer intkey{ lua_tointegerx(L, -2, &isint) };
+ if (! isint)
+ {
+ // key isn't an integer - this doesn't fit our LLSD
+ // array constraints
+ return luaL_error(L, "Expected integer array key, got %f instead",
+ lua_tonumber(L, -2));
+ }
+ if (intkey < 1)
+ {
+ return luaL_error(L, "array key %d out of bounds", int(intkey));
+ }
+
+ keys.push_back(LLSD::Integer(intkey));
+ break;
+ }
+
+ case LUA_TSTRING:
+ // break out strings specially to report the value
+ return luaL_error(L, "Cannot convert string array key '%s' to LLSD",
+ lua_tostring(L, -2));
+
+ default:
+ return luaL_error(L, "Cannot convert %s array key to LLSD",
+ lua_typename(L, arraykeytype));
+ }
+
+ // remove value, keep key for next iteration
+ lua_pop(L, 1);
+ } while (lua_next(L, index) != 0);
+ popper.disarm();
+ // Table keys are all integers: are they reasonable integers?
+ // Arbitrary max: may bite us, but more likely to protect us
+ size_t array_max{ 10000 };
+ if (keys.size() > array_max)
+ {
+ return luaL_error(L, "Conversion from Lua to LLSD array limited to %d entries",
+ int(array_max));
+ }
+ // We know the smallest key is >= 1. Check the largest. We also
+ // know the vector is NOT empty, else we wouldn't have gotten here.
+ std::sort(keys.begin(), keys.end());
+ LLSD::Integer highkey = *keys.rbegin();
+ if ((highkey - LLSD::Integer(keys.size())) > 100)
+ {
+ // Looks like we've gone beyond intentional array gaps into
+ // crazy key territory.
+ return luaL_error(L, "Gaps in Lua table too large for conversion to LLSD array");
+ }
+ LL_DEBUGS("Lua") << "collected " << keys.size() << " keys, max " << highkey << LL_ENDL;
+ // right away expand the result array to the size we'll need
+ LLSD result{ LLSD::emptyArray() };
+ result[highkey - 1] = LLSD();
+ // Traverse the table again, and this time populate result array.
+ lua_pushnil(L); // first key
+ while (lua_next(L, index))
+ {
+ // key at stack index -2, value at index -1
+ // We've already validated lua_tointegerx() for each key.
+ auto key{ lua_tointeger(L, -2) };
+ LL_DEBUGS("Lua") << "key " << key << ':' << LL_ENDL;
+ // Don't forget to subtract 1 from Lua key for LLSD subscript!
+ result[LLSD::Integer(key) - 1] = lua_tollsd(L, -1);
+ // remove value, keep key for next iteration
+ lua_pop(L, 1);
+ }
+ return result;
+ }
+
+ case LUA_TSTRING:
+ {
+ // First Lua key is a string: try to convert table to LLSD map
+ LLSD result{ LLSD::emptyMap() };
+ do
+ {
+ auto mapkeytype{ lua_type(L, -2) };
+ if (mapkeytype != LUA_TSTRING)
+ {
+ return luaL_error(L, "Cannot convert %s map key to LLSD",
+ lua_typename(L, mapkeytype));
+ }
+
+ auto key{ lua_tostdstring(L, -2) };
+ LL_DEBUGS("Lua") << "map key " << std::quoted(key) << ':' << LL_ENDL;
+ result[key] = lua_tollsd(L, -1);
+ // remove value, keep key for next iteration
+ lua_pop(L, 1);
+ } while (lua_next(L, index) != 0);
+ popper.disarm();
+ return result;
+ }
+
+ default:
+ // First Lua key isn't number or string: sorry
+ return luaL_error(L, "Cannot convert %s table key to LLSD",
+ lua_typename(L, firstkeytype));
+ }
+ }
+
+ default:
+ // Other Lua entities (e.g. function, C function, light userdata,
+ // thread, userdata) are not convertible to LLSD, indicating a coding
+ // error in the caller.
+ return luaL_error(L, "Cannot convert type %s to LLSD", luaL_typename(L, index));
+ }
+}
+
+// By analogy with existing lua_pushmumble() functions, push onto state L's
+// stack a Lua object corresponding to the passed LLSD object.
+void lua_pushllsd(lua_State* L, const LLSD& data)
+{
+ // might need 2 slots for array or map
+ luaL_checkstack(L, 2, nullptr);
+ switch (data.type())
+ {
+ case LLSD::TypeUndefined:
+ lua_pushnil(L);
+ break;
+
+ case LLSD::TypeBoolean:
+ lua_pushboolean(L, data.asBoolean());
+ break;
+
+ case LLSD::TypeInteger:
+ lua_pushinteger(L, data.asInteger());
+ break;
+
+ case LLSD::TypeReal:
+ lua_pushnumber(L, data.asReal());
+ break;
+
+ case LLSD::TypeBinary:
+ {
+ auto binary{ data.asBinary() };
+ std::memcpy(lua_newuserdata(L, binary.size()),
+ binary.data(), binary.size());
+ break;
+ }
+
+ case LLSD::TypeMap:
+ {
+ // push a new table with space for our non-array keys
+ lua_createtable(L, 0, data.size());
+ for (const auto& pair: llsd::inMap(data))
+ {
+ // push value -- so now table is at -2, value at -1
+ lua_pushllsd(L, pair.second);
+ // pop value, assign to table[key]
+ lua_setfield(L, -2, pair.first.c_str());
+ }
+ break;
+ }
+
+ case LLSD::TypeArray:
+ {
+ // push a new table with space for array entries
+ lua_createtable(L, data.size(), 0);
+ lua_Integer key{ 0 };
+ for (const auto& item: llsd::inArray(data))
+ {
+ // push new array value: table at -2, value at -1
+ lua_pushllsd(L, item);
+ // pop value, assign table[key] = value
+ lua_seti(L, -2, ++key);
+ }
+ break;
+ }
+
+ case LLSD::TypeString:
+ case LLSD::TypeUUID:
+ case LLSD::TypeDate:
+ case LLSD::TypeURI:
+ default:
+ {
+ lua_pushstdstring(L, data.asString());
+ break;
+ }
+ }
+}
diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h
new file mode 100644
index 0000000000..08d9876ce2
--- /dev/null
+++ b/indra/newview/llluamanager.h
@@ -0,0 +1,45 @@
+/**
+ * @file llluamanager.h
+ * @brief classes and functions for interfacing with LUA.
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2023, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#ifndef LL_LLLUAMANAGER_H
+#define LL_LLLUAMANAGER_H
+
+#include <functional>
+#include <string>
+
+class LLLUAmanager
+{
+public:
+ typedef std::function<void(std::string msg)> script_finished_fn;
+
+ static void runScriptFile(const std::string &filename, script_finished_fn cb = script_finished_fn());
+ static void runScriptLine(const std::string &cmd, script_finished_fn cb = script_finished_fn());
+
+ static void runScriptOnLogin();
+};
+
+
+#endif
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index ad87fca25b..9edbeaf464 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -207,6 +207,7 @@
#include "llstacktrace.h"
#include "threadpool.h"
+#include "llluamanager.h"
#include "llperfstats.h"
@@ -2396,6 +2397,8 @@ bool idle_startup()
LLPerfStats::StatsRecorder::setAutotuneInit();
+ LLLUAmanager::runScriptOnLogin();
+
return TRUE;
}
diff --git a/indra/newview/lltoolplacer.cpp b/indra/newview/lltoolplacer.cpp
index 7cdd7cc5c8..a4e6d20181 100644
--- a/indra/newview/lltoolplacer.cpp
+++ b/indra/newview/lltoolplacer.cpp
@@ -164,13 +164,17 @@ BOOL LLToolPlacer::addObject( LLPCode pcode, S32 x, S32 y, U8 use_physics )
BOOL b_hit_land = FALSE;
S32 hit_face = -1;
LLViewerObject* hit_obj = NULL;
- U8 state = 0;
BOOL success = raycastForNewObjPos( x, y, &hit_obj, &hit_face, &b_hit_land, &ray_start_region, &ray_end_region, &regionp );
if( !success )
{
return FALSE;
}
+ return rezNewObject(pcode,hit_obj, hit_face, b_hit_land, ray_start_region, ray_end_region, regionp, use_physics);
+}
+BOOL LLToolPlacer::rezNewObject(LLPCode pcode, LLViewerObject * hit_obj, S32 hit_face, BOOL b_hit_land, LLVector3 ray_start_region,
+ LLVector3 ray_end_region, LLViewerRegion* regionp, U8 use_physics)
+{
if( hit_obj && (hit_obj->isAvatar() || hit_obj->isAttachment()) )
{
// Can't create objects on avatars or attachments
@@ -194,6 +198,8 @@ BOOL LLToolPlacer::addObject( LLPCode pcode, S32 x, S32 y, U8 use_physics )
U8 material = LL_MCODE_WOOD;
BOOL create_selected = FALSE;
LLVolumeParams volume_params;
+
+ U8 state = 0;
switch (pcode)
{
diff --git a/indra/newview/lltoolplacer.h b/indra/newview/lltoolplacer.h
index ad59cb0daa..d20d682710 100644
--- a/indra/newview/lltoolplacer.h
+++ b/indra/newview/lltoolplacer.h
@@ -50,12 +50,16 @@ public:
static void setObjectType( LLPCode type ) { sObjectType = type; }
static LLPCode getObjectType() { return sObjectType; }
+ static BOOL addObject(LLPCode pcode, S32 x, S32 y, U8 use_physics);
+ static BOOL rezNewObject(LLPCode pcode, LLViewerObject* hit_obj, S32 hit_face, BOOL b_hit_land, LLVector3 ray_start_region,
+ LLVector3 ray_end_region, LLViewerRegion *regionp, U8 use_physics);
+
protected:
static LLPCode sObjectType;
private:
- BOOL addObject( LLPCode pcode, S32 x, S32 y, U8 use_physics );
- BOOL raycastForNewObjPos( S32 x, S32 y, LLViewerObject** hit_obj, S32* hit_face,
+
+ static BOOL raycastForNewObjPos(S32 x, S32 y, LLViewerObject **hit_obj, S32 *hit_face,
BOOL* b_hit_land, LLVector3* ray_start_region, LLVector3* ray_end_region, LLViewerRegion** region );
BOOL addDuplicate(S32 x, S32 y);
};
diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp
index 956f5cf187..97a2be6aa3 100644
--- a/indra/newview/lluilistener.cpp
+++ b/indra/newview/lluilistener.cpp
@@ -67,7 +67,7 @@ void LLUIListener::call(const LLSD& event) const
// API: we provide no reply. Therefore, a typo in the script will
// provide no feedback whatsoever to that script. To rub the coder's
// nose in such an error, crump rather than quietly ignoring it.
- LL_ERRS("LLUIListener") << "function '" << event["function"] << "' not found" << LL_ENDL;
+ LL_WARNS("LLUIListener") << "function '" << event["function"] << "' not found" << LL_ENDL;
}
else
{
diff --git a/indra/newview/lluilistener.h b/indra/newview/lluilistener.h
index 08724024dc..e53984dae2 100644
--- a/indra/newview/lluilistener.h
+++ b/indra/newview/lluilistener.h
@@ -39,7 +39,8 @@ class LLUIListener: public LLEventAPI
public:
LLUIListener();
-private:
+// FIXME These fields are intended to be private, changed here to support very hacky code in llluamanager.cpp
+public:
void call(const LLSD& event) const;
void getValue(const LLSD&event) const;
};
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 15b95d70a9..1cc584b36f 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -92,6 +92,7 @@
#include "llfloaterlandholdings.h"
#include "llfloaterlinkreplace.h"
#include "llfloaterloadprefpreset.h"
+#include "llfloaterluadebug.h"
#include "llfloatermap.h"
#include "llfloatermarketplacelistings.h"
#include "llfloatermediasettings.h"
@@ -391,6 +392,8 @@ void LLViewerFloaterReg::registerFloaters()
LLFloaterReg::add("land_holdings", "floater_land_holdings.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLandHoldings>);
LLFloaterReg::add("linkreplace", "floater_linkreplace.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLinkReplace>);
LLFloaterReg::add("load_pref_preset", "floater_load_pref_preset.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterLoadPrefPreset>);
+
+ LLFloaterReg::add("lua_debug", "floater_lua_debug.xml", (LLFloaterBuildFunc) &LLFloaterReg::build<LLFloaterLUADebug>);
LLFloaterReg::add("mem_leaking", "floater_mem_leaking.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMemLeak>);
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index 8686fad3e6..04c0c1eb17 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -171,6 +171,8 @@ extern BOOL gShaderProfileFrame;
// Globals
//
+LLUIListener sUIListener;
+
LLMenuBarGL *gMenuBarView = NULL;
LLViewerMenuHolderGL *gMenuHolder = NULL;
LLMenuGL *gPopupMenuView = NULL;
@@ -348,8 +350,6 @@ public:
static LLMenuParcelObserver* gMenuParcelObserver = NULL;
-static LLUIListener sUIListener;
-
LLMenuParcelObserver::LLMenuParcelObserver()
{
LLViewerParcelMgr::getInstance()->addObserver(this);
@@ -8893,71 +8893,80 @@ class LLToolsSelectTool : public view_listener_t
};
/// WINDLIGHT callbacks
-class LLWorldEnvSettings : public view_listener_t
-{
- void defocusEnvFloaters()
+void defocusEnvFloaters()
+{
+ // currently there is only one instance of each floater
+ std::vector<std::string> env_floaters_names = {"env_edit_extdaycycle", "env_fixed_environmentent_water",
+ "env_fixed_environmentent_sky"};
+ for (std::vector<std::string>::const_iterator it = env_floaters_names.begin(); it != env_floaters_names.end(); ++it)
{
- //currently there is only one instance of each floater
- std::vector<std::string> env_floaters_names = { "env_edit_extdaycycle", "env_fixed_environmentent_water", "env_fixed_environmentent_sky" };
- for (std::vector<std::string>::const_iterator it = env_floaters_names.begin(); it != env_floaters_names.end(); ++it)
+ LLFloater *env_floater = LLFloaterReg::findTypedInstance<LLFloater>(*it);
+ if (env_floater)
{
- LLFloater* env_floater = LLFloaterReg::findTypedInstance<LLFloater>(*it);
- if (env_floater)
- {
- env_floater->setFocus(FALSE);
- }
+ env_floater->setFocus(FALSE);
}
}
+}
+bool handle_env_setting_event(std::string event_name)
+{
+ if (event_name == "sunrise")
+ {
+ LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNRISE,
+ LLEnvironment::TRANSITION_INSTANT);
+ LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
+ defocusEnvFloaters();
+ }
+ else if (event_name == "noon")
+ {
+ LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDDAY,
+ LLEnvironment::TRANSITION_INSTANT);
+ LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
+ defocusEnvFloaters();
+ }
+ else if (event_name == "sunset")
+ {
+ LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNSET,
+ LLEnvironment::TRANSITION_INSTANT);
+ LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
+ defocusEnvFloaters();
+ }
+ else if (event_name == "midnight")
+ {
+ LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDNIGHT,
+ LLEnvironment::TRANSITION_INSTANT);
+ LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
+ defocusEnvFloaters();
+ }
+ else if (event_name == "region")
+ {
+ LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_LOCAL);
+ LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
+ defocusEnvFloaters();
+ }
+ else if (event_name == "pause_clouds")
+ {
+ if (LLEnvironment::instance().isCloudScrollPaused())
+ LLEnvironment::instance().resumeCloudScroll();
+ else
+ LLEnvironment::instance().pauseCloudScroll();
+ }
+ else if (event_name == "adjust_tool")
+ {
+ LLFloaterReg::showInstance("env_adjust_snapshot");
+ }
+ else if (event_name == "my_environs")
+ {
+ LLFloaterReg::showInstance("my_environments");
+ }
+ return true;
+}
+
+class LLWorldEnvSettings : public view_listener_t
+{
bool handleEvent(const LLSD& userdata)
{
- std::string event_name = userdata.asString();
-
- if (event_name == "sunrise")
- {
- LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNRISE, LLEnvironment::TRANSITION_INSTANT);
- LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
- defocusEnvFloaters();
- }
- else if (event_name == "noon")
- {
- LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDDAY, LLEnvironment::TRANSITION_INSTANT);
- LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
- defocusEnvFloaters();
- }
- else if (event_name == "sunset")
- {
- LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_SUNSET, LLEnvironment::TRANSITION_INSTANT);
- LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
- defocusEnvFloaters();
- }
- else if (event_name == "midnight")
- {
- LLEnvironment::instance().setEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::KNOWN_SKY_MIDNIGHT, LLEnvironment::TRANSITION_INSTANT);
- LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
- defocusEnvFloaters();
- }
- else if (event_name == "region")
- {
- LLEnvironment::instance().clearEnvironment(LLEnvironment::ENV_LOCAL);
- LLEnvironment::instance().setSelectedEnvironment(LLEnvironment::ENV_LOCAL, LLEnvironment::TRANSITION_INSTANT);
- defocusEnvFloaters();
- }
- else if (event_name == "pause_clouds")
- {
- if (LLEnvironment::instance().isCloudScrollPaused())
- LLEnvironment::instance().resumeCloudScroll();
- else
- LLEnvironment::instance().pauseCloudScroll();
- }
- else if (event_name == "adjust_tool")
- {
- LLFloaterReg::showInstance("env_adjust_snapshot");
- }
- else if (event_name == "my_environs")
- {
- LLFloaterReg::showInstance("my_environments");
- }
+ handle_env_setting_event(userdata.asString());
return true;
}
diff --git a/indra/newview/llviewermenu.h b/indra/newview/llviewermenu.h
index a90b32c984..b17a5d8b96 100644
--- a/indra/newview/llviewermenu.h
+++ b/indra/newview/llviewermenu.h
@@ -140,6 +140,7 @@ void handle_give_money_dialog();
bool enable_pay_object();
bool enable_buy_object();
bool handle_go_to();
+bool handle_env_setting_event(std::string event_name);
// Export to XML or Collada
void handle_export_selected( void * );
diff --git a/indra/newview/llviewermenufile.cpp b/indra/newview/llviewermenufile.cpp
index 17e89e1850..724f19c7d6 100644
--- a/indra/newview/llviewermenufile.cpp
+++ b/indra/newview/llviewermenufile.cpp
@@ -741,16 +741,22 @@ class LLFileEnableCloseAllWindows : public view_listener_t
}
};
+void close_all_windows()
+{
+ bool app_quitting = false;
+ gFloaterView->closeAllChildren(app_quitting);
+ LLFloaterSnapshot *floater_snapshot = LLFloaterSnapshot::findInstance();
+ if (floater_snapshot)
+ floater_snapshot->closeFloater(app_quitting);
+ if (gMenuHolder)
+ gMenuHolder->hideMenus();
+}
+
class LLFileCloseAllWindows : public view_listener_t
{
bool handleEvent(const LLSD& userdata)
{
- bool app_quitting = false;
- gFloaterView->closeAllChildren(app_quitting);
- LLFloaterSnapshot* floater_snapshot = LLFloaterSnapshot::findInstance();
- if (floater_snapshot)
- floater_snapshot->closeFloater(app_quitting);
- if (gMenuHolder) gMenuHolder->hideMenus();
+ close_all_windows();
return true;
}
};
diff --git a/indra/newview/llviewermenufile.h b/indra/newview/llviewermenufile.h
index 61572b9996..fd13eef8b7 100644
--- a/indra/newview/llviewermenufile.h
+++ b/indra/newview/llviewermenufile.h
@@ -72,6 +72,8 @@ void assign_defaults_and_show_upload_message(
const std::string& display_name,
std::string& description);
+void close_all_windows();
+
//consider moving all file pickers below to more suitable place
class LLFilePickerThread : public LLThread
{ //multi-threaded file picker (runs system specific file picker in background and calls "notify" from main thread)
diff --git a/indra/newview/skins/default/xui/en/floater_lua_debug.xml b/indra/newview/skins/default/xui/en/floater_lua_debug.xml
new file mode 100644
index 0000000000..f03739f7c2
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_lua_debug.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ can_minimize="false"
+ can_resize="false"
+ can_close="true"
+ bevel_style="in"
+ height="220"
+ layout="topleft"
+ name="LUA debug"
+ save_rect="true"
+ title="LUA DEBUG"
+ single_instance="true"
+ width="535">
+ <text
+ type="string"
+ length="1"
+ follows="left|bottom"
+ font="SansSerif"
+ height="30"
+ layout="topleft"
+ left="10"
+ left_delta="10"
+ name="editor_path_label"
+ top="10"
+ width="100">
+ LUA string:
+ </text>
+ <line_editor
+ border_style="line"
+ border_thickness="1"
+ follows="left|bottom"
+ font="SansSerif"
+ height="20"
+ layout="topleft"
+ left="10"
+ max_length_bytes="300"
+ name="lua_cmd"
+ select_on_focus="true"
+ top_delta="30"
+ width="435" />
+ <button
+ follows="left|bottom"
+ height="25"
+ label="Execute"
+ layout="topleft"
+ left_pad="5"
+ name="execute_btn"
+ top_delta="-2"
+ width="75" />
+
+ <text_editor
+ enabled="false"
+ left="10"
+ height="95"
+ layout="topleft"
+ name="result_text"
+ follows="left|top"
+ max_length="65536"
+ width="515"
+ top_delta="40"
+ word_wrap="true" />
+ <text
+ type="string"
+ length="1"
+ follows="left|bottom"
+ font="SansSerif"
+ height="30"
+ layout="topleft"
+ left="10"
+ name="path_label"
+ top_pad="15"
+ width="100">
+ File Path:
+ </text>
+ <line_editor
+ border_style="line"
+ enabled="false"
+ border_thickness="1"
+ follows="left|bottom"
+ font="SansSerif"
+ height="20"
+ layout="topleft"
+ left_delta="65"
+ max_length_bytes="300"
+ name="script_path"
+ select_on_focus="true"
+ top_delta="-2"
+ width="320" />
+ <button
+ follows="left|bottom"
+ height="25"
+ label="Browse..."
+ label_selected="Browse..."
+ layout="topleft"
+ left_pad="5"
+ name="browse_btn"
+ top_delta="-2"
+ width="70" />
+ <button
+ follows="left|bottom"
+ height="25"
+ label="Run"
+ label_selected="Run"
+ layout="topleft"
+ left_pad="5"
+ name="run_btn"
+ width="50" />
+</floater>
diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml
index 810d3fddd5..89643189b5 100644
--- a/indra/newview/skins/default/xui/en/menu_viewer.xml
+++ b/indra/newview/skins/default/xui/en/menu_viewer.xml
@@ -2485,6 +2485,17 @@ function="World.EnvPreset"
</menu_item_call>
<menu_item_separator/>
+ <menu_item_check
+ label="LUA Debug Console"
+ name="LUA Debug Console">
+ <menu_item_check.on_check
+ function="Floater.Visible"
+ parameter="lua_debug" />
+ <menu_item_check.on_click
+ function="Floater.Toggle"
+ parameter="lua_debug" />
+ </menu_item_check>
+ <menu_item_separator/>
<menu_item_call
label="Region Info to Debug Console"
diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp
new file mode 100644
index 0000000000..6433ff1118
--- /dev/null
+++ b/indra/newview/tests/llluamanager_test.cpp
@@ -0,0 +1,301 @@
+/**
+ * @file llluamanager_test.cpp
+ * @author Nat Goodspeed
+ * @date 2023-09-28
+ * @brief Test for llluamanager.
+ *
+ * $LicenseInfo:firstyear=2023&license=viewerlgpl$
+ * Copyright (c) 2023, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+//#include "llviewerprecompiledheaders.h"
+// associated header
+#include "../newview/llluamanager.h"
+// STL headers
+// std headers
+#include <vector>
+// external library headers
+// other Linden headers
+#include "../test/lltut.h"
+#include "llapp.h"
+#include "lldate.h"
+#include "llevents.h"
+#include "lleventcoro.h"
+#include "llsdutil.h"
+#include "lluri.h"
+#include "lluuid.h"
+#include "stringize.h"
+#include "../llcommon/tests/StringVec.h"
+
+class LLTestApp : public LLApp
+{
+public:
+ bool init() override { return true; }
+ bool cleanup() override { return true; }
+ bool frame() override { return true; }
+};
+
+template <typename CALLABLE>
+auto listener(CALLABLE&& callable)
+{
+ return [callable=std::forward<CALLABLE>(callable)]
+ (const LLSD& data)
+ {
+ callable(data);
+ return false;
+ };
+}
+
+/*****************************************************************************
+* TUT
+*****************************************************************************/
+namespace tut
+{
+ struct llluamanager_data
+ {
+ // We need an LLApp instance because LLLUAmanager uses coroutines,
+ // which suspend, and when a coroutine suspends it checks LLApp state,
+ // and if it's not APP_STATUS_RUNNING the coroutine terminates.
+ LLTestApp mApp;
+ };
+ typedef test_group<llluamanager_data> llluamanager_group;
+ typedef llluamanager_group::object object;
+ llluamanager_group llluamanagergrp("llluamanager");
+
+ template<> template<>
+ void object::test<1>()
+ {
+ set_test_name("test post_on(), listen_events(), await_event()");
+ StringVec posts;
+ LLEventStream replypump("testpump");
+ LLTempBoundListener conn(
+ replypump.listen("test<1>",
+ listener([&posts](const LLSD& data)
+ { posts.push_back(data.asString()); })));
+ const std::string lua(
+ "-- test post_on,listen_events,await_event\n"
+ "post_on('testpump', 'entry')\n"
+ "callback = function(pump, data)\n"
+ " -- just echo the data we received\n"
+ " post_on('testpump', data)\n"
+ "end\n"
+ "post_on('testpump', 'listen_events()')\n"
+ "replypump, cmdpump = listen_events(callback)\n"
+ "post_on('testpump', replypump)\n"
+ "post_on('testpump', 'await_event()')\n"
+ "await_event(replypump)\n"
+ "post_on('testpump', 'exit')\n"
+ );
+ LLLUAmanager::runScriptLine(lua);
+ StringVec expected{
+ "entry",
+ "listen_events()",
+ "",
+ "await_event()",
+ "message",
+ "exit"
+ };
+ for (int i = 0; i < 10 && posts.size() <= 2 && posts[2].empty(); ++i)
+ {
+ llcoro::suspend();
+ }
+ expected[2] = posts.at(2);
+ LL_DEBUGS() << "Found pumpname '" << expected[2] << "'" << LL_ENDL;
+ LLEventPump& luapump{ LLEventPumps::instance().obtain(expected[2]) };
+ LL_DEBUGS() << "Found pump '" << luapump.getName() << "', type '"
+ << LLError::Log::classname(luapump)
+ << "': post('" << expected[4] << "')" << LL_ENDL;
+ luapump.post(expected[4]);
+ llcoro::suspend();
+ ensure_equals("post_on() sequence", posts, expected);
+ }
+
+ void from_lua(const std::string& desc, const std::string_view& construct, const LLSD& expect)
+ {
+ LLSD fromlua;
+ LLEventStream replypump("testpump");
+ LLTempBoundListener conn(
+ replypump.listen("llluamanager_test",
+ listener([&fromlua](const LLSD& data){ fromlua = data; })));
+ const std::string lua(stringize(
+ "-- test LLSD synthesized by Lua\n",
+ // we expect the caller's Lua snippet to construct a Lua object
+ // called 'data'
+ construct, "\n"
+ "post_on('testpump', data)\n"
+ ));
+ LLLUAmanager::runScriptLine(lua);
+ // At this point LLLUAmanager::runScriptLine() has launched a new C++
+ // coroutine to run the passed Lua snippet, but that coroutine hasn't
+ // yet had a chance to run. Poke the coroutine scheduler until the Lua
+ // script has sent its data.
+ for (int i = 0; i < 10 && fromlua.isUndefined(); ++i)
+ {
+ llcoro::suspend();
+ }
+ // We woke up again ourselves because the coroutine running Lua has
+ // finished.
+ ensure_equals(desc, fromlua, expect);
+ }
+
+ template<> template<>
+ void object::test<2>()
+ {
+ set_test_name("LLSD from Lua");
+ from_lua("nil", "data = nil", LLSD());
+ from_lua("true", "data = true", true);
+ from_lua("false", "data = false", false);
+ from_lua("int", "data = 17", 17);
+ from_lua("real", "data = 3.14", 3.14);
+ from_lua("string", "data = 'string'", "string");
+ // can't synthesize Lua userdata in Lua code: that can only be
+ // constructed by a C function
+ from_lua("empty table", "data = {}", LLSD());
+ from_lua("nested empty table", "data = { 1, 2, 3, {}, 5 }",
+ llsd::array(1, 2, 3, LLSD(), 5));
+ from_lua("nested non-empty table", "data = { 1, 2, 3, {a=0, b=1}, 5 }",
+ llsd::array(1, 2, 3, llsd::map("a", 0, "b", 1), 5));
+ }
+
+ void round_trip(const std::string& desc, const LLSD& send, const LLSD& expect)
+ {
+ LLSD reply;
+ LLEventStream replypump("testpump");
+ LLTempBoundListener conn(
+ replypump.listen("llluamanager_test",
+ listener([&reply](const LLSD& post){ reply = post; })));
+ const std::string lua(
+ "-- test LLSD round trip\n"
+ "callback = function(pump, data)\n"
+ " -- just echo the data we received\n"
+ " post_on('testpump', data)\n"
+ "end\n"
+ "replypump, cmdpump = listen_events(callback)\n"
+ "post_on('testpump', replypump)\n"
+ "await_event(replypump)\n"
+ );
+ LLLUAmanager::runScriptLine(lua);
+ // At this point LLLUAmanager::runScriptLine() has launched a new C++
+ // coroutine to run the passed Lua snippet, but that coroutine hasn't
+ // yet had a chance to run. Poke the coroutine scheduler until the Lua
+ // script has sent its reply pump name.
+ for (int i = 0; i < 10 && reply.isUndefined(); ++i)
+ {
+ llcoro::suspend();
+ }
+ // We woke up again ourselves because the coroutine running Lua has
+ // reached the await_event() call, which suspends the calling C++
+ // coroutine (including the Lua code running on it) until we post
+ // something to that reply pump.
+ auto luapump{ reply.asString() };
+ reply.clear();
+ LLEventPumps::instance().post(luapump, send);
+ // The C++ coroutine running the Lua script is now ready to run. Run
+ // it so it will echo the LLSD back to us.
+ llcoro::suspend();
+ ensure_equals(desc, reply, expect);
+ }
+
+ // Define an RTItem to be used for round-trip LLSD testing: what it is,
+ // what we send to Lua, what we expect to get back. They could be the
+ // same.
+ struct RTItem
+ {
+ RTItem(const std::string& name, const LLSD& send, const LLSD& expect):
+ mName(name),
+ mSend(send),
+ mExpect(expect)
+ {}
+ RTItem(const std::string& name, const LLSD& both):
+ mName(name),
+ mSend(both),
+ mExpect(both)
+ {}
+
+ std::string mName;
+ LLSD mSend, mExpect;
+ };
+
+ template<> template<>
+ void object::test<3>()
+ {
+ set_test_name("LLSD round trip");
+ LLSD::Binary binary{ 3, 1, 4, 1, 5, 9, 2, 6, 5 };
+ const char* uuid{ "01234567-abcd-0123-4567-0123456789ab" };
+ const char* date{ "2023-10-04T21:06:00Z" };
+ const char* uri{ "https://secondlife.com/index.html" };
+ std::vector<RTItem> items{
+ RTItem("undefined", LLSD()),
+ RTItem("true", true),
+ RTItem("false", false),
+ RTItem("int", 17),
+ RTItem("real", 3.14),
+ RTItem("int real", 27.0, 27),
+ RTItem("string", "string"),
+ RTItem("binary", binary),
+ RTItem("empty array", LLSD::emptyArray(), LLSD()),
+ RTItem("empty map", LLSD::emptyMap(), LLSD()),
+ RTItem("UUID", LLUUID(uuid), uuid),
+ RTItem("date", LLDate(date), date),
+ RTItem("uri", LLURI(uri), uri)
+ };
+ // scalars
+ for (const auto& item: items)
+ {
+ round_trip(item.mName, item.mSend, item.mExpect);
+ }
+
+ // array
+ LLSD send_array{ LLSD::emptyArray() }, expect_array{ LLSD::emptyArray() };
+ for (const auto& item: items)
+ {
+ send_array.append(item.mSend);
+ expect_array.append(item.mExpect);
+ }
+ // exercise the array tail trimming below
+ send_array.append(items[0].mSend);
+ expect_array.append(items[0].mExpect);
+ // Lua takes a table value of nil to mean: don't store this key. An
+ // LLSD array containing undefined entries (converted to nil) leaves
+ // "holes" in the Lua table. These will be converted back to undefined
+ // LLSD entries -- except at the end. Trailing undefined entries are
+ // simply omitted from the table -- so the table converts back to a
+ // shorter LLSD array. We've constructed send_array and expect_array
+ // according to 'items' above -- but truncate from expect_array any
+ // trailing entries whose mSend will map to Lua nil.
+ while (expect_array.size() > 0 &&
+ send_array[expect_array.size() - 1].isUndefined())
+ {
+ expect_array.erase(expect_array.size() - 1);
+ }
+ round_trip("array", send_array, expect_array);
+
+ // map
+ LLSD send_map{ LLSD::emptyMap() }, expect_map{ LLSD::emptyMap() };
+ for (const auto& item: items)
+ {
+ send_map[item.mName] = item.mSend;
+ // see comment in the expect_array truncation loop above --
+ // Lua never stores table entries with nil values
+ if (item.mSend.isDefined())
+ {
+ expect_map[item.mName] = item.mExpect;
+ }
+ }
+ round_trip("map", send_map, expect_map);
+
+ // deeply nested map: exceed Lua's default stack space (20),
+ // i.e. verify that we have the right checkstack() calls
+ for (int i = 0; i < 20; ++i)
+ {
+ LLSD new_send_map{ send_map }, new_expect_map{ expect_map };
+ new_send_map["nested map"] = send_map;
+ new_expect_map["nested map"] = expect_map;
+ send_map = new_send_map;
+ expect_map = new_expect_map;
+ }
+ round_trip("nested map", send_map, expect_map);
+ }
+} // namespace tut
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index 1fa4df1682..98240d3a78 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -565,6 +565,9 @@ class Windows_x86_64_Manifest(ViewerManifest):
# Uriparser
self.path("uriparser.dll")
+ # lua libs
+ self.path("lua54.dll")
+
# These need to be installed as a SxS assembly, currently a 'private' assembly.
# See http://msdn.microsoft.com/en-us/library/ms235291(VS.80).aspx
self.path("msvcp140.dll")
diff --git a/indra/test/io.cpp b/indra/test/io.cpp
index 40243a8ad6..027db50693 100644
--- a/indra/test/io.cpp
+++ b/indra/test/io.cpp
@@ -45,6 +45,7 @@
#include "llcommon.h"
#include "lluuid.h"
#include "llinstantmessage.h"
+#include "stringize.h"
namespace tut
{
@@ -1116,6 +1117,9 @@ namespace tut
template<> template<>
void fitness_test_object::test<5>()
{
+ skip("Test is strongly timing dependent, "
+ "and on slow CI machines it fails way too often.");
+ const int retries = 100;
// Set up the server
LLPumpIO::chain_t chain;
typedef LLCloneIOFactory<LLIOSleeper> sleeper_t;
@@ -1129,9 +1133,12 @@ namespace tut
chain.push_back(LLIOPipe::ptr_t(server));
mPump->addChain(chain, NEVER_CHAIN_EXPIRY_SECS);
// We need to tickle the pump a little to set up the listen()
- pump_loop(mPump, 0.1f);
+ for (int retry = 0; mPump->runningChains() < 1 && retry < retries; ++retry)
+ {
+ pump_loop(mPump, 0.1f);
+ }
U32 count = mPump->runningChains();
- ensure_equals("server chain onboard", count, 1);
+ ensure_equals("server chain 1 onboard", count, 1);
LL_DEBUGS() << "** Server is up." << LL_ENDL;
// Set up the client
@@ -1140,9 +1147,12 @@ namespace tut
bool connected = client->blockingConnect(server_host);
ensure("Connected to server", connected);
LL_DEBUGS() << "connected" << LL_ENDL;
- pump_loop(mPump,0.1f);
+ for (int retry = 0; mPump->runningChains() < 2 && retry < retries; ++retry)
+ {
+ pump_loop(mPump,0.1f);
+ }
count = mPump->runningChains();
- ensure_equals("server chain onboard", count, 2);
+ ensure_equals("server chain 2 onboard", count, 2);
LL_DEBUGS() << "** Client is connected." << LL_ENDL;
// We have connected, since the socket reader does not block,
@@ -1156,20 +1166,32 @@ namespace tut
chain.clear();
// pump for a bit and make sure all 3 chains are running
- pump_loop(mPump,0.1f);
+ for (int retry = 0; mPump->runningChains() < 3 && retry < retries; ++retry)
+ {
+ pump_loop(mPump, 0.1f);
+ }
count = mPump->runningChains();
- // ensure_equals("client chain onboard", count, 3); commented out because it fails frequently - appears to be timing sensitive
+ ensure_equals("client chain onboard", count, 3);
LL_DEBUGS() << "** request should have been sent." << LL_ENDL;
// pump for long enough the the client socket closes, and the
// server socket should not be closed yet.
- pump_loop(mPump,0.2f);
+ for (int retry = 0; mPump->runningChains() == 3 && retry < retries; ++retry)
+ {
+ pump_loop(mPump, 0.1f);
+ }
+ // We used to test for count == 2 here, but on a slow test machine it
+ // can happen that not just one but two chains close before we reach
+ // this point.
count = mPump->runningChains();
- ensure_equals("client chain timed out ", count, 2);
+ ensure(stringize("client chain timed out: count ", count), count < 3);
LL_DEBUGS() << "** client chain should be closed." << LL_ENDL;
// At this point, the socket should be closed by the timeout
- pump_loop(mPump,1.0f);
+ for (int retry = 0; mPump->runningChains() > 1 && retry < retries; ++retry)
+ {
+ pump_loop(mPump, 0.1f);
+ }
count = mPump->runningChains();
ensure_equals("accepted socked close", count, 1);
LL_DEBUGS() << "** Sleeper should have timed out.." << LL_ENDL;
diff --git a/scripts/lua/avatar.lua b/scripts/lua/avatar.lua
new file mode 100644
index 0000000000..7c419a740c
--- /dev/null
+++ b/scripts/lua/avatar.lua
@@ -0,0 +1,14 @@
+function call_once_func()
+ run_ui_command("World.EnvSettings", "midnight")
+ sleep(1)
+ run_ui_command("World.EnvSettings", "noon")
+ sleep(1)
+ wear_by_name("* AVL")
+ run_ui_command("Avatar.ResetSelfSkeletonAndAnimations")
+ sleep(5)
+ wear_by_name("* Elephant")
+ sleep(5)
+ play_animation("Elephant_Fly");
+ sleep(5)
+ play_animation("Elephant_Fly",1);
+end \ No newline at end of file
diff --git a/scripts/lua/demo.lua b/scripts/lua/demo.lua
new file mode 100644
index 0000000000..3442c18b0d
--- /dev/null
+++ b/scripts/lua/demo.lua
@@ -0,0 +1,147 @@
+function popup_and_wait_ok(message)
+ args = {MESSAGE=message}
+ notif_response = nil
+ show_notification("GenericAlertOK", args, "notif_response")
+ while not notif_response do
+ sleep(0.2)
+ end
+
+ local response = notif_response
+ return response
+end
+
+function demo_environment()
+ popup_and_wait_ok("Change Environment")
+ run_ui_command("World.EnvSettings", "midnight")
+ sleep(2)
+ run_ui_command("World.EnvSettings", "sunrise")
+ sleep(2)
+ run_ui_command("World.EnvSettings", "noon")
+ sleep(2)
+end
+
+function demo_rez()
+ for x=-1,1,1 do
+ for y=-1,1,1 do
+ rez_prim2({x,y,-1},1)
+ end
+ end
+end
+
+function demo_avatar()
+ popup_and_wait_ok("Change Avatar")
+
+ local dest = {10,10,0}
+ move_by(dest, "autopilot_response")
+ while not autopilot_response do
+ sleep(0.2)
+ end
+
+ local response = autopilot_response
+
+ if response == 1 then
+ sleep(1)
+ demo_rez()
+ sleep(2)
+ end
+
+ wear_by_name("Greg")
+ run_ui_command("Avatar.ResetSelfSkeletonAndAnimations")
+ sleep(8)
+
+ wear_by_name("Petrol Sue")
+ sleep(8)
+
+ run_ui_command("Self.ToggleSitStand")
+ sleep(2)
+ run_ui_command("Self.ToggleSitStand")
+ sleep(2)
+
+ --run_ui_command("View.ZoomOut")
+ run_ui_command("EditShape")
+ sleep(6)
+ close_floater("appearance")
+
+end
+
+function demo_ui()
+
+
+ -- adding items to 'Build' menu
+ -- popup_and_wait_ok("Extend UI")
+
+ popup_and_wait_ok("UI interaction")
+ open_floater("inventory")
+ open_floater("preferences")
+ open_floater("nearby_chat")
+ nearby_chat_send("Hello World!")
+
+ sleep(5)
+ close_all_floaters()
+
+
+ notif_response = nil
+ args = {MESSAGE="Customize the UI now?"}
+ show_notification("GenericAlertYesCancel", args, "notif_response")
+ while not notif_response do
+ sleep(0.2)
+ end
+ if notif_response ~= 0 then
+ popup_and_wait_ok("Exiting")
+ return
+ end
+
+ menu_name = "BuildTools"
+ add_menu_separator(menu_name)
+
+ params = {name="user_sit",
+ label="Sit!"}
+ params["function"]="Self.ToggleSitStand"
+
+ add_menu_item(menu_name, params)
+
+ params = {name="user_midnight",label="Set night",parameter="midnight"}
+ params["function"] = "World.EnvSettings"
+
+ add_menu_item(menu_name, params)
+
+ -- adding new custom menu
+ new_menu_name = "user_menu"
+ params = {name=new_menu_name,label="My Secret Menu",tear_off="true"}
+ add_menu(params)
+
+ -- adding new item to the new menu
+ params = {name="user_debug",label="Console",
+ parameter="debug"}
+ params["function"] = "Advanced.ToggleConsole"
+
+ add_menu_item(new_menu_name, params)
+
+ -- adding new branch
+ new_branch = "user_floaters"
+ params = {name=new_branch, label="Open Floater",tear_off="true"}
+ add_branch(new_menu_name, params)
+
+ -- adding items to the branch
+ params = {name="user_permissions",label="Default permissions",
+ parameter="perms_default"}
+ params["function"] = "Floater.ToggleOrBringToFront"
+
+
+ add_menu_item(new_branch, params)
+
+ params = {name="user_beacons",label="Beacons",
+ parameter="beacons"}
+ params["function"] = "Floater.ToggleOrBringToFront"
+
+ add_menu_item(new_branch, params)
+ sleep(5)
+
+end
+
+function call_once_func()
+
+ demo_environment()
+ demo_avatar()
+ demo_ui()
+end