summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--indra/llcommon/fsyspath.h5
-rw-r--r--indra/llcommon/llsdutil.h132
-rw-r--r--indra/llcommon/lua_function.cpp46
-rw-r--r--indra/llcommon/lua_function.h269
-rw-r--r--indra/llui/llluafloater.cpp31
-rw-r--r--indra/llxml/tests/llcontrol_test.cpp12
-rw-r--r--indra/newview/CMakeLists.txt2
-rw-r--r--indra/newview/app_settings/settings.xml634
-rw-r--r--indra/newview/llappearancelistener.cpp146
-rw-r--r--indra/newview/llappearancelistener.h46
-rw-r--r--indra/newview/llappearancemgr.cpp51
-rw-r--r--indra/newview/llappearancemgr.h8
-rw-r--r--indra/newview/llappviewer.cpp19
-rw-r--r--indra/newview/llappviewerlistener.cpp9
-rw-r--r--indra/newview/llappviewerlistener.h5
-rw-r--r--indra/newview/llfloaterluadebug.cpp13
-rw-r--r--indra/newview/llfloaterluadebug.h2
-rw-r--r--indra/newview/llluamanager.cpp187
-rw-r--r--indra/newview/llluamanager.h22
-rw-r--r--indra/newview/llstartup.cpp3
-rw-r--r--indra/newview/llviewermenu.cpp4
-rw-r--r--indra/newview/llviewermessage.cpp5
-rw-r--r--indra/newview/llviewerwindow.cpp2
-rw-r--r--indra/newview/llviewerwindowlistener.cpp54
-rw-r--r--indra/newview/llvoavatar.cpp30
-rw-r--r--indra/newview/llwearableitemslist.cpp13
-rw-r--r--indra/newview/llwearableitemslist.h8
-rw-r--r--indra/newview/scripts/lua/luafloater_outfits_list.xml51
-rw-r--r--indra/newview/scripts/lua/luafloater_speedometer.xml35
-rw-r--r--indra/newview/scripts/lua/popup.lua32
-rw-r--r--indra/newview/scripts/lua/require/LLAppearance.lua31
-rw-r--r--indra/newview/scripts/lua/require/LLChatListener.lua48
-rw-r--r--indra/newview/scripts/lua/require/UI.lua13
-rw-r--r--indra/newview/scripts/lua/require/login.lua (renamed from indra/newview/scripts/lua/login.lua)0
-rw-r--r--indra/newview/scripts/lua/require/logout.lua7
-rw-r--r--indra/newview/scripts/lua/require/mapargs.lua (renamed from indra/newview/scripts/lua/mapargs.lua)0
-rw-r--r--indra/newview/scripts/lua/require/popup.lua53
-rw-r--r--indra/newview/scripts/lua/test_LLAppearance.lua114
-rw-r--r--indra/newview/scripts/lua/test_LLChatListener.lua28
-rw-r--r--indra/newview/scripts/lua/test_atexit.lua3
-rw-r--r--indra/newview/scripts/lua/test_login.lua2
-rw-r--r--indra/newview/scripts/lua/test_logout.lua3
-rw-r--r--indra/newview/scripts/lua/test_luafloater_demo.lua77
-rw-r--r--indra/newview/scripts/lua/test_luafloater_gesture_list.lua75
-rw-r--r--indra/newview/scripts/lua/test_luafloater_speedometer.lua31
-rw-r--r--indra/newview/scripts/lua/test_popup.lua6
-rw-r--r--indra/newview/scripts/lua/test_snapshot.lua15
-rw-r--r--indra/newview/skins/default/xui/en/floater_lua_debug.xml9
-rw-r--r--indra/newview/skins/default/xui/en/strings.xml4
-rw-r--r--indra/newview/tests/llluamanager_test.cpp102
-rw-r--r--indra/test/test.cpp32
51 files changed, 1604 insertions, 925 deletions
diff --git a/indra/llcommon/fsyspath.h b/indra/llcommon/fsyspath.h
index aa4e0132bc..3c749d84de 100644
--- a/indra/llcommon/fsyspath.h
+++ b/indra/llcommon/fsyspath.h
@@ -69,6 +69,11 @@ public:
// shadow base-class string() method with UTF-8 aware method
std::string string() const { return super::u8string(); }
+ // On Posix systems, where value_type is already char, this operator
+ // std::string() method shadows the base class operator string_type()
+ // method. But on Windows, where value_type is wchar_t, the base class
+ // doesn't have operator std::string(). Provide it.
+ operator std::string() const { return string(); }
};
#endif /* ! defined(LL_FSYSPATH_H) */
diff --git a/indra/llcommon/llsdutil.h b/indra/llcommon/llsdutil.h
index aa497c53c7..3f59222e9b 100644
--- a/indra/llcommon/llsdutil.h
+++ b/indra/llcommon/llsdutil.h
@@ -365,15 +365,14 @@ private:
// subject function has returned, so we must ensure that any constructed
// LLSDParam<T> lives just as long as this LLSDParam<LLSD> does. Putting
// each LLSDParam<T> on the heap and capturing a smart pointer in a vector
- // works. We would have liked to use std::unique_ptr, but vector entries
- // must be copyable.
+ // works.
// (Alternatively we could assume that every instance of LLSDParam<LLSD>
// will be asked for at most ONE conversion. We could store a scalar
// std::unique_ptr and, when constructing an new LLSDParam<T>, assert that
// the unique_ptr is empty. But some future change in usage patterns, and
// consequent failure of that assertion, would be very mysterious. Instead
// of explaining how to fix it, just fix it now.)
- mutable std::vector<std::shared_ptr<LLSDParamBase>> converters_;
+ mutable std::vector<std::unique_ptr<LLSDParamBase>> converters_;
public:
LLSDParam(const LLSD& value): value_(value) {}
@@ -389,9 +388,9 @@ public:
{
// capture 'ptr' with the specific subclass type because converters_
// only stores LLSDParamBase pointers
- auto ptr{ std::make_shared<LLSDParam<std::decay_t<T>>>(value_) };
+ auto ptr{ new LLSDParam<std::decay_t<T>>(value_) };
// keep the new converter alive until we ourselves are destroyed
- converters_.push_back(ptr);
+ converters_.emplace_back(ptr);
return *ptr;
}
};
@@ -474,12 +473,12 @@ public:
}
};
-namespace llsd
-{
-
/*****************************************************************************
* range-based for-loop helpers for LLSD
*****************************************************************************/
+namespace llsd
+{
+
/// Usage: for (LLSD item : inArray(someLLSDarray)) { ... }
class inArray
{
@@ -525,7 +524,70 @@ private:
} // namespace llsd
+/*****************************************************************************
+* LLSDParam<std::vector<T>>
+*****************************************************************************/
+// Given an LLSD array, return a const std::vector<T>&, where T is a type
+// supported by LLSDParam. Bonus: if the LLSD value is actually a scalar,
+// return a single-element vector containing the converted value.
+template <typename T>
+class LLSDParam<std::vector<T>>: public LLSDParamBase
+{
+public:
+ LLSDParam(const LLSD& array)
+ {
+ // treat undefined "array" as empty vector
+ if (array.isDefined())
+ {
+ // what if it's a scalar?
+ if (! array.isArray())
+ {
+ v.push_back(LLSDParam<T>(array));
+ }
+ else // really is an array
+ {
+ // reserve space for the array entries
+ v.reserve(array.size());
+ for (const auto& item : llsd::inArray(array))
+ {
+ v.push_back(LLSDParam<T>(item));
+ }
+ }
+ }
+ }
+
+ operator const std::vector<T>&() const { return v; }
+
+private:
+ std::vector<T> v;
+};
+
+/*****************************************************************************
+* LLSDParam<std::map<std::string, T>>
+*****************************************************************************/
+// Given an LLSD map, return a const std::map<std::string, T>&, where T is a
+// type supported by LLSDParam.
+template <typename T>
+class LLSDParam<std::map<std::string, T>>: public LLSDParamBase
+{
+public:
+ LLSDParam(const LLSD& map)
+ {
+ for (const auto& pair : llsd::inMap(map))
+ {
+ m[pair.first] = LLSDParam<T>(pair.second);
+ }
+ }
+
+ operator const std::map<std::string, T>&() const { return m; }
+
+private:
+ std::map<std::string, T> m;
+};
+/*****************************************************************************
+* deep and shallow clone
+*****************************************************************************/
// Creates a deep clone of an LLSD object. Maps, Arrays and binary objects
// are duplicated, atomic primitives (Boolean, Integer, Real, etc) simply
// use a shared reference.
@@ -553,6 +615,60 @@ LLSD shallow(LLSD value, LLSD filter=LLSD()) { return llsd_shallow(value, filter
} // namespace llsd
+/*****************************************************************************
+* toArray(), toMap()
+*****************************************************************************/
+namespace llsd
+{
+
+// For some T convertible to LLSD, given std::vector<T> myVec,
+// toArray(myVec) returns an LLSD array whose entries correspond to the
+// items in myVec.
+// For some U convertible to LLSD, given function U xform(const T&),
+// toArray(myVec, xform) returns an LLSD array whose every entry is
+// xform(item) of the corresponding item in myVec.
+// toArray() actually works with any container<C> usable with range
+// 'for', not just std::vector.
+// (Once we get C++20 we can use std::identity instead of this default lambda.)
+template <typename C, typename FUNC>
+LLSD toArray(const C& container, FUNC&& func=[](const auto& arg){ return arg; })
+{
+ LLSD array;
+ for (const auto& item : container)
+ {
+ array.append(std::forward<FUNC>(func)(item));
+ }
+ return array;
+}
+
+// For some T convertible to LLSD, given std::map<std::string, T> myMap,
+// toMap(myMap) returns an LLSD map whose entries correspond to the
+// (key, value) pairs in myMap.
+// For some U convertible to LLSD, given function
+// std::pair<std::string, U> xform(const std::pair<std::string, T>&),
+// toMap(myMap, xform) returns an LLSD map whose every entry is
+// xform(pair) of the corresponding (key, value) pair in myMap.
+// toMap() actually works with any container usable with range 'for', not
+// just std::map. It need not even be an associative container, as long as
+// you pass an xform function that returns std::pair<std::string, U>.
+// (Once we get C++20 we can use std::identity instead of this default lambda.)
+template <typename C, typename FUNC>
+LLSD toMap(const C& container, FUNC&& func=[](const auto& arg){ return arg; })
+{
+ LLSD map;
+ for (const auto& pair : container)
+ {
+ const auto& [key, value] = std::forward<FUNC>(func)(pair);
+ map[key] = value;
+ }
+ return map;
+}
+
+} // namespace llsd
+
+/*****************************************************************************
+* boost::hash<LLSD>
+*****************************************************************************/
// Specialization for generating a hash value from an LLSD block.
namespace boost
{
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp
index 7c5a939d8a..42bba80ed5 100644
--- a/indra/llcommon/lua_function.cpp
+++ b/indra/llcommon/lua_function.cpp
@@ -28,6 +28,7 @@
#include "lleventcoro.h"
#include "llsd.h"
#include "llsdutil.h"
+#include "llstring.h"
#include "lualistener.h"
#include "stringize.h"
@@ -37,6 +38,8 @@ const S32 INTERRUPTS_SUSPEND_LIMIT = 100;
#define lua_register(L, n, f) (lua_pushcfunction(L, (f), n), lua_setglobal(L, (n)))
#define lua_rawlen lua_objlen
+int DistinctInt::mValues{0};
+
/*****************************************************************************
* luau namespace
*****************************************************************************/
@@ -78,8 +81,13 @@ fsyspath lluau::source_path(lua_State* L)
// In particular:
// passing level=1 gets you info about the deepest function call
// passing level=lua_stackdepth() gets you info about the topmost script
- lua_Debug ar;
- lua_getinfo(L, lua_stackdepth(L), "s", &ar);
+ // Empirically, lua_getinfo(level > 1) behaves strangely (including
+ // crashing the program) unless you iterate from 1 to desired level.
+ lua_Debug ar{};
+ for (int i(0), depth(lua_stackdepth(L)); i <= depth; ++i)
+ {
+ lua_getinfo(L, i, "s", &ar);
+ }
return ar.source;
}
@@ -470,19 +478,11 @@ void lua_pushllsd(lua_State* L, const LLSD& data)
*****************************************************************************/
LuaState::LuaState(script_finished_fn cb):
mCallback(cb),
- mState(nullptr)
-{
- initLuaState();
-}
-
-void LuaState::initLuaState()
+ mState(luaL_newstate())
{
- if (mState)
- {
- lua_close(mState);
- }
- mState = luaL_newstate();
luaL_openlibs(mState);
+ // publish to this new lua_State all the LL entry points we defined using
+ // the lua_function() macro
LuaFunction::init(mState);
// Try to make print() write to our log.
lua_register(mState, "print", LuaFunction::get("print_info"));
@@ -599,8 +599,7 @@ std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string&
// we instead of the Lua runtime catch it, our lua_State retains
// its internal error status. Any subsequent lua_pcall() calls
// with this lua_State will report error regardless of whether the
- // chunk runs successfully. Get a new lua_State().
- initLuaState();
+ // chunk runs successfully.
return { -1, stringize(LLError::Log::classname(error), ": ", error.what()) };
}
}
@@ -620,7 +619,6 @@ std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string&
LL_WARNS("Lua") << desc << " error converting result " << index << ": "
<< error.what() << LL_ENDL;
// see above comments regarding lua_State's error status
- initLuaState();
return { -1, stringize(LLError::Log::classname(error), ": ", error.what()) };
}
}
@@ -659,7 +657,8 @@ LuaListener& LuaState::obtainListener(lua_State* L)
/*****************************************************************************
* atexit()
*****************************************************************************/
-lua_function(atexit, "register a Lua function to be called at script termination")
+lua_function(atexit, "atexit(function): "
+ "register Lua function to be called at script termination")
{
luaL_checkstack(L, 4, nullptr);
// look up the global name "table"
@@ -747,7 +746,7 @@ std::pair<LuaFunction::Registry&, LuaFunction::Lookup&> LuaFunction::getState()
/*****************************************************************************
* source_path()
*****************************************************************************/
-lua_function(source_path, "return the source path of the running Lua script")
+lua_function(source_path, "source_path(): return the source path of the running Lua script")
{
luaL_checkstack(L, 1, nullptr);
lua_pushstdstring(L, lluau::source_path(L).u8string());
@@ -757,7 +756,7 @@ lua_function(source_path, "return the source path of the running Lua script")
/*****************************************************************************
* source_dir()
*****************************************************************************/
-lua_function(source_dir, "return the source directory of the running Lua script")
+lua_function(source_dir, "source_dir(): return the source directory of the running Lua script")
{
luaL_checkstack(L, 1, nullptr);
lua_pushstdstring(L, lluau::source_path(L).parent_path().u8string());
@@ -767,7 +766,7 @@ lua_function(source_dir, "return the source directory of the running Lua script"
/*****************************************************************************
* abspath()
*****************************************************************************/
-lua_function(abspath,
+lua_function(abspath, "abspath(path): "
"for given filesystem path relative to running script, return absolute path")
{
auto path{ lua_tostdstring(L, 1) };
@@ -779,7 +778,7 @@ lua_function(abspath,
/*****************************************************************************
* check_stop()
*****************************************************************************/
-lua_function(check_stop, "ensure that a Lua script responds to viewer shutdown")
+lua_function(check_stop, "check_stop(): ensure that a Lua script responds to viewer shutdown")
{
LLCoros::checkStop();
return 0;
@@ -800,7 +799,7 @@ lua_function(help,
for (const auto& [name, pair] : registry)
{
const auto& [fptr, helptext] = pair;
- luapump.post(helptext);
+ luapump.post("LL." + helptext);
}
}
else
@@ -812,6 +811,7 @@ lua_function(help,
if (lua_type(L, idx) == LUA_TSTRING)
{
arg = lua_tostdstring(L, idx);
+ LLStringUtil::removePrefix(arg, "LL.");
}
else if (lua_type(L, idx) == LUA_TFUNCTION)
{
@@ -830,7 +830,7 @@ lua_function(help,
if (auto found = registry.find(arg); found != registry.end())
{
- luapump.post(found->second.second);
+ luapump.post("LL." + found->second.second);
}
else
{
diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h
index 7a3d9e7dd7..7f7a7566f3 100644
--- a/indra/llcommon/lua_function.h
+++ b/indra/llcommon/lua_function.h
@@ -21,8 +21,9 @@
#include "stringize.h"
#include <exception> // std::uncaught_exceptions()
#include <memory> // std::shared_ptr
-#include <optional>
+#include <typeindex>
#include <typeinfo>
+#include <unordered_map>
#include <utility> // std::pair
class LuaListener;
@@ -86,8 +87,6 @@ public:
~LuaState();
- void initLuaState();
-
bool checkLua(const std::string& desc, int r);
// expr() is for when we want to capture any results left on the stack
@@ -117,11 +116,12 @@ private:
* LuaPopper
*****************************************************************************/
/**
- * LuaPopper is an RAII struct whose role is to pop some number of entries
+ * LuaPopper is an RAII class whose role is to pop some number of entries
* from the Lua stack if the calling function exits early.
*/
-struct LuaPopper
+class LuaPopper
{
+public:
LuaPopper(lua_State* L, int count):
mState(L),
mCount(count)
@@ -135,11 +135,40 @@ struct LuaPopper
void disarm() { set(0); }
void set(int count) { mCount = count; }
+private:
lua_State* mState;
int mCount;
};
/*****************************************************************************
+* LuaRemover
+*****************************************************************************/
+/**
+ * Remove a particular stack index on exit from enclosing scope.
+ * If you pass a negative index (meaning relative to the current stack top),
+ * converts to an absolute index. The point of LuaRemover is to remove the
+ * entry at the specified index regardless of subsequent pushes to the stack.
+ */
+class LuaRemover
+{
+public:
+ LuaRemover(lua_State* L, int index):
+ mState(L),
+ mIndex(lua_absindex(L, index))
+ {}
+ LuaRemover(const LuaRemover&) = delete;
+ LuaRemover& operator=(const LuaRemover&) = delete;
+ ~LuaRemover()
+ {
+ lua_remove(mState, mIndex);
+ }
+
+private:
+ lua_State* mState;
+ int mIndex;
+};
+
+/*****************************************************************************
* lua_function (and helper class LuaFunction)
*****************************************************************************/
/**
@@ -194,30 +223,43 @@ int name##_luasub::call(lua_State* L)
/*****************************************************************************
* lua_emplace<T>(), lua_toclass<T>()
*****************************************************************************/
+// Every instance of DistinctInt has a different int value, barring int
+// wraparound.
+class DistinctInt
+{
+public:
+ DistinctInt(): mValue(++mValues) {}
+ int get() const { return mValue; }
+ operator int() const { return mValue; }
+private:
+ static int mValues;
+ int mValue;
+};
+
namespace {
-// this closure function retrieves its bound argument to pass to
-// lua_emplace_gc<T>()
-template <class T>
-int lua_emplace_call_gc(lua_State* L);
-// this will be the function called by the new userdata's metatable's __gc()
-template <class T>
-int lua_emplace_gc(lua_State* L);
-// name by which we'll store the new userdata's metatable in the Registry
-template <class T>
-std::string lua_emplace_metaname(const std::string& Tname = LLError::Log::classname<T>());
+template <typename T>
+struct TypeTag
+{
+ // For (std::is_same<T, U>), &TypeTag<T>::value == &TypeTag<U>::value.
+ // For (! std::is_same<T, U>), &TypeTag<T>::value != &TypeTag<U>::value.
+ // And every distinct instance of DistinctInt has a distinct value.
+ // Therefore, TypeTag<T>::value is an int uniquely associated with each
+ // distinct T.
+ static DistinctInt value;
+};
+
+template <typename T>
+DistinctInt TypeTag<T>::value;
} // anonymous namespace
/**
* On the stack belonging to the passed lua_State, push a Lua userdata object
- * with a newly-constructed C++ object std::optional<T>(args...). The new
- * userdata has a metadata table with a __gc() function to ensure that when
- * the userdata instance is garbage-collected, ~T() is called. Also call
- * LL.atexit(lua_emplace_call_gc<T>(object)) to make ~LuaState() call ~T().
- *
- * We wrap the userdata object as std::optional<T> so we can explicitly
- * destroy the contained T, and detect that we've done so.
+ * containing a newly-constructed C++ object T(args...). The userdata has a
+ * Luau destructor guaranteeing that the new T instance is destroyed when the
+ * userdata is garbage-collected, no later than when the LuaState is
+ * destroyed.
*
* Usage:
* lua_emplace<T>(L, T constructor args...);
@@ -226,178 +268,45 @@ std::string lua_emplace_metaname(const std::string& Tname = LLError::Log::classn
template <class T, typename... ARGS>
void lua_emplace(lua_State* L, ARGS&&... args)
{
- using optT = std::optional<T>;
- luaL_checkstack(L, 5, nullptr);
- auto ptr = lua_newuserdata(L, sizeof(optT));
+ luaL_checkstack(L, 1, nullptr);
+ int tag{ TypeTag<T>::value };
+ if (! lua_getuserdatadtor(L, tag))
+ {
+ // We haven't yet told THIS lua_State the destructor to use for this tag.
+ lua_setuserdatadtor(
+ L, tag,
+ [](lua_State*, void* ptr)
+ {
+ // destroy the contained T instance
+ static_cast<T*>(ptr)->~T();
+ });
+ }
+ auto ptr = lua_newuserdatatagged(L, sizeof(T), tag);
// stack is uninitialized userdata
// For now, assume (but verify) that lua_newuserdata() returns a
// conservatively-aligned ptr. If that turns out not to be the case, we
// might have to discard the new userdata, overallocate its successor and
// perform manual alignment -- but only if we must.
- llassert((uintptr_t(ptr) % alignof(optT)) == 0);
+ llassert((uintptr_t(ptr) % alignof(T)) == 0);
// Construct our T there using placement new
- new (ptr) optT(std::in_place, std::forward<ARGS>(args)...);
- // stack is now initialized userdata containing our T instance
-
- // Find or create the metatable shared by all userdata instances holding
- // C++ type T. We want it to be shared across instances, but it must be
- // type-specific because its __gc field is lua_emplace_gc<T>.
- auto Tname{ LLError::Log::classname<T>() };
- auto metaname{ lua_emplace_metaname<T>(Tname) };
- if (luaL_newmetatable(L, metaname.c_str()))
- {
- // just created it: populate it
- auto gcname{ stringize("lua_emplace_gc<", Tname, ">") };
- lua_pushcfunction(L, lua_emplace_gc<T>, gcname.c_str());
- // stack is userdata, metatable, lua_emplace_gc<T>
- lua_setfield(L, -2, "__gc");
- }
- // stack is userdata, metatable
- lua_setmetatable(L, -2);
- // Stack is now userdata, initialized with T(args),
- // with metatable.__gc pointing to lua_emplace_gc<T>.
-
- // But wait, there's more! Use our atexit() function to ensure that this
- // C++ object is eventually destroyed even if the garbage collector never
- // gets around to it.
- lua_getglobal(L, "LL");
- // stack contains userdata, LL
- lua_getfield(L, -1, "atexit");
- // stack contains userdata, LL, LL.atexit
- // ditch LL
- lua_replace(L, -2);
- // stack contains userdata, LL.atexit
-
- // We have a bit of a problem here. We want to allow the garbage collector
- // to collect the userdata if it must; but we also want to register a
- // cleanup function to destroy the value if (usual case) it has NOT been
- // garbage-collected. The problem is that if we bind into atexit()'s queue
- // a strong reference to the userdata, we ensure that the garbage
- // collector cannot collect it, making our metatable with __gc function
- // completely moot. And we must assume that lua_pushcclosure() binds a
- // strong reference to each value passed as a closure.
-
- // The solution is to use one more indirection: create a weak table whose
- // sole entry is the userdata. If all other references to the new userdata
- // are forgotten, so the only remaining reference is the weak table, the
- // userdata can be collected. Then we can bind that weak table as the
- // closure value for our cleanup function.
- // The new weak table will have at most 1 array value, 0 other keys.
- lua_createtable(L, 1, 0);
- // stack contains userdata, LL.atexit, weak_table
- if (luaL_newmetatable(L, "weak_values"))
- {
- // stack contains userdata, LL.atexit, weak_table, weak_values
- // just created "weak_values" metatable: populate it
- // Registry.weak_values = {__mode="v"}
- lua_pushliteral(L, "v");
- // stack contains userdata, LL.atexit, weak_table, weak_values, "v"
- lua_setfield(L, -2, "__mode");
- }
- // stack contains userdata, LL.atexit, weak_table, weak_values
- // setmetatable(weak_table, weak_values)
- lua_setmetatable(L, -2);
- // stack contains userdata, LL.atexit, weak_table
- lua_pushinteger(L, 1);
- // stack contains userdata, LL.atexit, weak_table, 1
- // duplicate userdata
- lua_pushvalue(L, -4);
- // stack contains userdata, LL.atexit, weak_table, 1, userdata
- // weak_table[1] = userdata
- lua_settable(L, -3);
- // stack contains userdata, LL.atexit, weak_table
-
- // push a closure binding (lua_emplace_call_gc<T>, weak_table)
- auto callgcname{ stringize("lua_emplace_call_gc<", Tname, ">") };
- lua_pushcclosure(L, lua_emplace_call_gc<T>, callgcname.c_str(), 1);
- // stack contains userdata, LL.atexit, closure
- // Call LL.atexit(closure)
- lua_call(L, 1, 0);
- // stack contains userdata -- return that
+ new (ptr) T(std::forward<ARGS>(args)...);
+ // stack is now initialized userdata containing our T instance -- return
+ // that
}
-namespace {
-
-// passed to LL.atexit(closure(lua_emplace_call_gc<T>, weak_table{userdata}));
-// retrieves bound userdata to pass to lua_emplace_gc<T>()
-template <class T>
-int lua_emplace_call_gc(lua_State* L)
-{
- luaL_checkstack(L, 2, nullptr);
- // retrieve the first (only) bound upvalue and push to stack top
- lua_pushvalue(L, lua_upvalueindex(1));
- // This is the weak_table bound by lua_emplace<T>(). Its one and only
- // entry should be the lua_emplace<T>() userdata -- unless userdata has
- // been garbage collected. Retrieve weak_table[1].
- lua_pushinteger(L, 1);
- // stack contains weak_table, 1
- lua_gettable(L, -2);
- // stack contains weak_table, weak_table[1]
- // If our userdata was garbage-collected, there is no weak_table[1],
- // and we just retrieved nil.
- if (lua_isnil(L, -1))
- {
- lua_pop(L, 2);
- return 0;
- }
- // stack contains weak_table, userdata
- // ditch weak_table
- lua_replace(L, -2);
- // pass userdata to lua_emplace_gc<T>()
- return lua_emplace_gc<T>(L);
-}
-
-// set as metatable(userdata).__gc to be called by the garbage collector
-template <class T>
-int lua_emplace_gc(lua_State* L)
-{
- using optT = std::optional<T>;
- // We're called with userdata on the stack holding an instance of type T.
- auto ptr = lua_touserdata(L, -1);
- llassert(ptr);
- // Destroy the T object contained in optT at the void* address ptr. If
- // in future lua_emplace() must manually align our optT* within the
- // Lua-provided void*, derive optT* from ptr.
- static_cast<optT*>(ptr)->reset();
- // pop the userdata
- lua_pop(L, 1);
- return 0;
-}
-
-template <class T>
-std::string lua_emplace_metaname(const std::string& Tname)
-{
- return stringize("lua_emplace_", Tname, "_meta");
-}
-
-} // anonymous namespace
-
/**
* If the value at the passed acceptable index is a full userdata created by
- * lua_emplace<T>() -- that is, the userdata contains a non-empty
- * std::optional<T> -- return a pointer to the contained T instance. Otherwise
- * (index is not a full userdata; userdata is not of type std::optional<T>;
- * std::optional<T> is empty) return nullptr.
+ * lua_emplace<T>(), return a pointer to the contained T instance. Otherwise
+ * (index is not a full userdata; userdata is not of type T) return nullptr.
*/
template <class T>
T* lua_toclass(lua_State* L, int index)
{
- using optT = std::optional<T>;
- // recreate the name lua_emplace<T>() uses for its metatable
- auto metaname{ lua_emplace_metaname<T>() };
// get void* pointer to userdata (if that's what it is)
- void* ptr{ luaL_checkudata(L, index, metaname.c_str()) };
- if (! ptr)
- return nullptr;
- // Derive the optT* from ptr. If in future lua_emplace() must manually
- // align our optT* within the Lua-provided void*, adjust accordingly.
- optT* tptr(static_cast<optT*>(ptr));
- // make sure our optT isn't empty
- if (! *tptr)
- return nullptr;
- // looks like we still have a non-empty optT: return the *address* of the
- // value() reference
- return &tptr->value();
+ void* ptr{ lua_touserdatatagged(L, index, TypeTag<T>::value) };
+ // Derive the T* from ptr. If in future lua_emplace() must manually
+ // align our T* within the Lua-provided void*, adjust accordingly.
+ return static_cast<T*>(ptr);
}
/*****************************************************************************
@@ -473,7 +382,7 @@ public:
template <typename... ARGS>
void operator()(ARGS&&... args)
{
- LL_INFOS("Lua") << mBlock << ' ';
+ LL_DEBUGS("Lua") << mBlock << ' ';
stream_to(LL_CONT, std::forward<ARGS>(args)...);
LL_ENDL;
}
diff --git a/indra/llui/llluafloater.cpp b/indra/llui/llluafloater.cpp
index e584a67a00..b08508bf9b 100644
--- a/indra/llui/llluafloater.cpp
+++ b/indra/llui/llluafloater.cpp
@@ -101,6 +101,15 @@ LLLuaFloater::LLLuaFloater(const LLSD &key) :
}
}, requiredParams);
+ mDispatchListener.add("clear_list", "", [this](const LLSD &event)
+ {
+ LLScrollListCtrl *ctrl = getChild<LLScrollListCtrl>(event["ctrl_name"].asString());
+ if(ctrl)
+ {
+ ctrl->deleteAllItems();
+ }
+ }, llsd::map("ctrl_name", LLSD()));
+
mDispatchListener.add("add_text", "", [this](const LLSD &event)
{
LLTextEditor *editor = getChild<LLTextEditor>(event["ctrl_name"].asString());
@@ -111,15 +120,35 @@ LLLuaFloater::LLLuaFloater(const LLSD &key) :
}
}, requiredParams);
+ mDispatchListener.add("set_label", "", [this](const LLSD &event)
+ {
+ LLButton *btn = getChild<LLButton>(event["ctrl_name"].asString());
+ if (btn)
+ {
+ btn->setLabel((event["value"]).asString());
+ }
+ }, requiredParams);
+
mDispatchListener.add("set_title", "", [this](const LLSD &event)
{
setTitle(event["value"].asString());
}, llsd::map("value", LLSD()));
-
+
mDispatchListener.add("get_value", "", [ctrl_lookup](const LLSD &event)
{
return ctrl_lookup(event, [](LLUICtrl *ctrl, const LLSD &event) { return llsd::map("value", ctrl->getValue()); });
}, llsd::map("ctrl_name", LLSD(), "reqid", LLSD()));
+
+ mDispatchListener.add("get_selected_id", "", [this](const LLSD &event)
+ {
+ LLScrollListCtrl *ctrl = getChild<LLScrollListCtrl>(event["ctrl_name"].asString());
+ if (!ctrl)
+ {
+ LL_WARNS("LuaFloater") << "Control not found: " << event["ctrl_name"] << LL_ENDL;
+ return LLSD();
+ }
+ return llsd::map("value", ctrl->getCurrentID());
+ }, llsd::map("ctrl_name", LLSD(), "reqid", LLSD()));
}
LLLuaFloater::~LLLuaFloater()
diff --git a/indra/llxml/tests/llcontrol_test.cpp b/indra/llxml/tests/llcontrol_test.cpp
index f5f8b285f7..595c6a600b 100644
--- a/indra/llxml/tests/llcontrol_test.cpp
+++ b/indra/llxml/tests/llcontrol_test.cpp
@@ -97,7 +97,7 @@ namespace tut
template<> template<>
void control_group_t::test<1>()
{
- int results = mCG->loadFromFile(mTestConfigFile.c_str());
+ int results = mCG->loadFromFile(mTestConfigFile);
ensure("number of settings", (results == 1));
ensure("value of setting", (mCG->getU32("TestSetting") == 12));
}
@@ -106,14 +106,14 @@ namespace tut
template<> template<>
void control_group_t::test<2>()
{
- int results = mCG->loadFromFile(mTestConfigFile.c_str());
+ int results = mCG->loadFromFile(mTestConfigFile);
mCG->setU32("TestSetting", 13);
ensure_equals("value of changed setting", mCG->getU32("TestSetting"), 13);
LLControlGroup test_cg("foo2");
std::string temp_test_file = (mTestConfigDir + "setting_llsd_temp.xml");
mCleanups.push_back(temp_test_file);
mCG->saveToFile(temp_test_file.c_str(), TRUE);
- results = test_cg.loadFromFile(temp_test_file.c_str());
+ results = test_cg.loadFromFile(temp_test_file);
ensure("number of changed settings loaded", (results == 1));
ensure("value of changed settings loaded", (test_cg.getU32("TestSetting") == 13));
}
@@ -126,7 +126,7 @@ namespace tut
// a default settings file that declares variables, rather than a user
// settings file. When loadFromFile() encounters an unrecognized user
// settings variable, it forcibly preserves it (CHOP-962).
- int results = mCG->loadFromFile(mTestConfigFile.c_str(), true);
+ int results = mCG->loadFromFile(mTestConfigFile, true);
LLControlVariable* control = mCG->getControl("TestSetting");
LLSD new_value = 13;
control->setValue(new_value, FALSE);
@@ -135,7 +135,7 @@ namespace tut
std::string temp_test_file = (mTestConfigDir + "setting_llsd_persist_temp.xml");
mCleanups.push_back(temp_test_file);
mCG->saveToFile(temp_test_file.c_str(), TRUE);
- results = test_cg.loadFromFile(temp_test_file.c_str());
+ results = test_cg.loadFromFile(temp_test_file);
//If we haven't changed any settings, then we shouldn't have any settings to load
ensure("number of non-persisted changed settings loaded", (results == 0));
}
@@ -144,7 +144,7 @@ namespace tut
template<> template<>
void control_group_t::test<4>()
{
- int results = mCG->loadFromFile(mTestConfigFile.c_str());
+ int results = mCG->loadFromFile(mTestConfigFile);
ensure("number of settings", (results == 1));
mCG->getControl("TestSetting")->getSignal()->connect(boost::bind(&this->handleListenerTest));
mCG->setU32("TestSetting", 13);
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index ab65d71916..4e7e072289 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -95,6 +95,7 @@ set(viewer_SOURCE_FILES
llagentwearables.cpp
llanimstatelabels.cpp
llappcorehttp.cpp
+ llappearancelistener.cpp
llappearancemgr.cpp
llappviewer.cpp
llappviewerlistener.cpp
@@ -760,6 +761,7 @@ set(viewer_HEADER_FILES
llanimstatelabels.h
llappcorehttp.h
llappearance.h
+ llappearancelistener.h
llappearancemgr.h
llappviewer.h
llappviewerlistener.h
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 2463d56eae..cdaf9a47dd 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -346,7 +346,7 @@
<key>Value</key>
<real>0.5</real>
</map>
- <key>AudioStreamingMedia</key>
+ <key>AudioStreamingMedia</key>
<map>
<key>Comment</key>
<string>Enable streaming</string>
@@ -1291,7 +1291,7 @@
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
- <integer>0</integer>
+ <integer>0</integer>
</map>
<key>CameraPositionSmoothing</key>
<map>
@@ -1907,17 +1907,17 @@
<key>Value</key>
<string />
</map>
- <key>DebugAvatarRezTime</key>
- <map>
- <key>Comment</key>
- <string>Display times for avatars to resolve.</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>Boolean</string>
- <key>Value</key>
- <integer>0</integer>
- </map>
+ <key>DebugAvatarRezTime</key>
+ <map>
+ <key>Comment</key>
+ <string>Display times for avatars to resolve.</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
<key>DebugAvatarLocalTexLoadedTime</key>
<map>
<key>Comment</key>
@@ -2292,39 +2292,39 @@
<key>Value</key>
<integer>0</integer>
</map>
- <key>DefaultFemaleAvatar</key>
- <map>
- <key>Comment</key>
- <string>Default Female Avatar</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>String</string>
- <key>Value</key>
- <string>Female Shape &amp; Outfit</string>
- </map>
- <key>DefaultLoginLocation</key>
- <map>
- <key>Comment</key>
- <string>Startup destination default (if not specified on command line)</string>
- <key>Persist</key>
- <integer>0</integer>
- <key>Type</key>
- <string>String</string>
- <key>Value</key>
- <string/>
- </map>
- <key>DefaultMaleAvatar</key>
- <map>
- <key>Comment</key>
- <string>Default Male Avatar</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>String</string>
- <key>Value</key>
- <string>Male Shape &amp; Outfit</string>
- </map>
+ <key>DefaultFemaleAvatar</key>
+ <map>
+ <key>Comment</key>
+ <string>Default Female Avatar</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>Female Shape &amp; Outfit</string>
+ </map>
+ <key>DefaultLoginLocation</key>
+ <map>
+ <key>Comment</key>
+ <string>Startup destination default (if not specified on command line)</string>
+ <key>Persist</key>
+ <integer>0</integer>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string/>
+ </map>
+ <key>DefaultMaleAvatar</key>
+ <map>
+ <key>Comment</key>
+ <string>Default Male Avatar</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>Male Shape &amp; Outfit</string>
+ </map>
<key>DestinationGuideURL</key>
<map>
<key>Comment</key>
@@ -2699,7 +2699,7 @@
<key>Value</key>
<integer>0</integer>
</map>
- <key>FirstSelectedEnabledPopups</key>
+ <key>FirstSelectedEnabledPopups</key>
<map>
<key>Comment</key>
<string>Return false if there is not enable popup selected in the list of floater preferences popups</string>
@@ -3521,39 +3521,39 @@
<key>Value</key>
<integer>0</integer>
</map>
- <key>InventoryLinking</key>
- <map>
- <key>Comment</key>
- <string>Enable ability to create links to folders and items via "Paste as link".</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>Boolean</string>
- <key>Value</key>
- <integer>1</integer>
- </map>
- <key>InventoryOutboxLogging</key>
- <map>
- <key>Comment</key>
- <string>Enable debug output associated with the Merchant Outbox.</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>Boolean</string>
- <key>Value</key>
- <integer>0</integer>
- </map>
- <key>InventoryOutboxMakeVisible</key>
- <map>
- <key>Comment</key>
- <string>Enable making the Merchant Outbox and Inbox visible in the inventory for debug purposes.</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>Boolean</string>
- <key>Value</key>
- <integer>0</integer>
- </map>
+ <key>InventoryLinking</key>
+ <map>
+ <key>Comment</key>
+ <string>Enable ability to create links to folders and items via "Paste as link".</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
+ <key>InventoryOutboxLogging</key>
+ <map>
+ <key>Comment</key>
+ <string>Enable debug output associated with the Merchant Outbox.</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
+ <key>InventoryOutboxMakeVisible</key>
+ <map>
+ <key>Comment</key>
+ <string>Enable making the Merchant Outbox and Inbox visible in the inventory for debug purposes.</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
<key>InventoryOutboxMaxFolderCount</key>
<map>
<key>Comment</key>
@@ -3807,8 +3807,8 @@
<key>Value</key>
<real>0.25</real>
</map>
- <key>Jpeg2000AdvancedCompression</key>
- <map>
+ <key>Jpeg2000AdvancedCompression</key>
+ <map>
<key>Comment</key>
<string>Use advanced Jpeg2000 compression options (precincts, blocks, ordering, markers)</string>
<key>Persist</key>
@@ -3817,9 +3817,9 @@
<string>Boolean</string>
<key>Value</key>
<integer>0</integer>
- </map>
- <key>Jpeg2000PrecinctsSize</key>
- <map>
+ </map>
+ <key>Jpeg2000PrecinctsSize</key>
+ <map>
<key>Comment</key>
<string>Size of image precincts. Assumed square and same for all levels. Must be power of 2.</string>
<key>Persist</key>
@@ -3828,9 +3828,9 @@
<string>S32</string>
<key>Value</key>
<integer>256</integer>
- </map>
- <key>Jpeg2000BlocksSize</key>
- <map>
+ </map>
+ <key>Jpeg2000BlocksSize</key>
+ <map>
<key>Comment</key>
<string>Size of encoding blocks. Assumed square and same for all levels. Must be power of 2. Max 64, Min 4.</string>
<key>Persist</key>
@@ -3839,7 +3839,7 @@
<string>S32</string>
<key>Value</key>
<integer>64</integer>
- </map>
+ </map>
<key>KeepAspectForSnapshot</key>
<map>
<key>Comment</key>
@@ -3917,10 +3917,23 @@
<key>Value</key>
<string>Monospace</string>
</map>
+ <key>LuaAutorunPath</key>
+ <map>
+ <key>Comment</key>
+ <string>Directories containing scripts to autorun at viewer startup</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>LLSD</string>
+ <key>Value</key>
+ <array>
+ <string>scripts/lua/auto</string>
+ </array>
+ </map>
<key>LuaChunk</key>
<map>
<key>Comment</key>
- <string>Zero or more Lua chunks to run</string>
+ <string>Zero or more Lua chunks to run from command line</string>
<key>Persist</key>
<integer>0</integer>
<key>Type</key>
@@ -3928,10 +3941,36 @@
<key>Value</key>
<array />
</map>
+ <key>LuaCommandPath</key>
+ <map>
+ <key>Comment</key>
+ <string>Directories containing scripts recognized as chat slash commands</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>LLSD</string>
+ <key>Value</key>
+ <array>
+ <string>scripts/lua</string>
+ </array>
+ </map>
+ <key>LuaRequirePath</key>
+ <map>
+ <key>Comment</key>
+ <string>Directories containing Lua modules loadable by require()</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>LLSD</string>
+ <key>Value</key>
+ <array>
+ <string>scripts/lua/require</string>
+ </array>
+ </map>
<key>LuaScript</key>
<map>
<key>Comment</key>
- <string>Zero or more Lua script files to run</string>
+ <string>Zero or more Lua script files to run from command line</string>
<key>Persist</key>
<integer>0</integer>
<key>Type</key>
@@ -4469,17 +4508,17 @@
<key>Value</key>
<string />
</map>
- <key>MarketplaceListingsLogging</key>
- <map>
- <key>Comment</key>
- <string>Enable debug output associated with the Marketplace Listings (SLM) API.</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>Boolean</string>
- <key>Value</key>
- <integer>0</integer>
- </map>
+ <key>MarketplaceListingsLogging</key>
+ <map>
+ <key>Comment</key>
+ <string>Enable debug output associated with the Marketplace Listings (SLM) API.</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
<key>MarketplaceURL</key>
<map>
<key>Comment</key>
@@ -5365,28 +5404,28 @@
<key>Value</key>
<integer>1000</integer>
</map>
- <key>FakeInitialOutfitName</key>
- <map>
- <key>Comment</key>
- <string>Pretend that this is first time login and specified name was chosen</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
+ <key>FakeInitialOutfitName</key>
+ <map>
+ <key>Comment</key>
+ <string>Pretend that this is first time login and specified name was chosen</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
<string>String</string>
<key>Value</key>
<string />
- </map>
- <key>MyOutfitsAutofill</key>
- <map>
- <key>Comment</key>
- <string>Always autofill My Outfits from library when empty (else happens just once).</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>Boolean</string>
- <key>Value</key>
- <integer>0</integer>
- </map>
+ </map>
+ <key>MyOutfitsAutofill</key>
+ <map>
+ <key>Comment</key>
+ <string>Always autofill My Outfits from library when empty (else happens just once).</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
<key>NearMeRange</key>
<map>
<key>Comment</key>
@@ -5506,7 +5545,7 @@
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
- <integer>0</integer>
+ <integer>0</integer>
</map>
<key>NonvisibleObjectsInMemoryTime</key>
<map>
@@ -5517,7 +5556,7 @@
<key>Type</key>
<string>U32</string>
<key>Value</key>
- <integer>64</integer>
+ <integer>64</integer>
</map>
<key>NoPreload</key>
<map>
@@ -6429,7 +6468,7 @@
<key>Value</key>
<real>6.0</real>
</map>
- <key>PreferredMaturity</key>
+ <key>PreferredMaturity</key>
<map>
<key>Comment</key>
<string>Setting for the user's preferred maturity level (consts in indra_constants.h)</string>
@@ -6438,7 +6477,7 @@
<key>Type</key>
<string>U32</string>
<key>Value</key>
- <integer>13</integer>
+ <integer>13</integer>
</map>
<key>PreviewAmbientColor</key>
<map>
@@ -6608,8 +6647,8 @@
</map>
<key>PrimMediaMasterEnabled</key>
- <map>
- <key>Comment</key>
+ <map>
+ <key>Comment</key>
<string>Whether or not Media on a Prim is enabled.</string>
<key>Persist</key>
<integer>1</integer>
@@ -6618,9 +6657,9 @@
<key>Value</key>
<integer>1</integer>
</map>
- <key>PrimMediaControlsUseHoverControlSet</key>
- <map>
- <key>Comment</key>
+ <key>PrimMediaControlsUseHoverControlSet</key>
+ <map>
+ <key>Comment</key>
<string>Whether or not hovering over prim media uses minimal "hover" controls or the authored control set.</string>
<key>Persist</key>
<integer>1</integer>
@@ -6629,17 +6668,17 @@
<key>Value</key>
<integer>0</integer>
</map>
- <key>PrimMediaDragNDrop</key>
- <map>
- <key>Comment</key>
- <string>Enable drag and drop of URLs onto prim faces</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>Boolean</string>
- <key>Value</key>
- <integer>1</integer>
- </map>
+ <key>PrimMediaDragNDrop</key>
+ <map>
+ <key>Comment</key>
+ <string>Enable drag and drop of URLs onto prim faces</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
<key>PrimMediaMaxRetries</key>
<map>
<key>Comment</key>
@@ -6673,7 +6712,7 @@
<key>Value</key>
<real>5.0</real>
</map>
- <key>PrimMediaMaxSortedQueueSize</key>
+ <key>PrimMediaMaxSortedQueueSize</key>
<map>
<key>Comment</key>
<string>Maximum number of objects the viewer will load media for initially</string>
@@ -6684,7 +6723,7 @@
<key>Value</key>
<integer>100000</integer>
</map>
- <key>PrimMediaMaxRoundRobinQueueSize</key>
+ <key>PrimMediaMaxRoundRobinQueueSize</key>
<map>
<key>Comment</key>
<string>Maximum number of objects the viewer will continuously update media for</string>
@@ -8788,17 +8827,17 @@
<key>Value</key>
<integer>1024</integer>
</map>
- <key>RenderHeroProbeDistance</key>
- <map>
- <key>Comment</key>
- <string>Distance in meters for hero probes to render out to.</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>F32</string>
- <key>Value</key>
- <real>8</real>
- </map>
+ <key>RenderHeroProbeDistance</key>
+ <map>
+ <key>Comment</key>
+ <string>Distance in meters for hero probes to render out to.</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>8</real>
+ </map>
<key>RenderHeroProbeUpdateRate</key>
<map>
<key>Comment</key>
@@ -9363,17 +9402,17 @@
<key>Value</key>
<integer>1</integer>
</map>
- <key>RenderTransparentWater</key>
- <map>
- <key>Comment</key>
- <string>Render water as transparent. Setting to false renders water as opaque with a simple texture applied.</string>
+ <key>RenderTransparentWater</key>
+ <map>
+ <key>Comment</key>
+ <string>Render water as transparent. Setting to false renders water as opaque with a simple texture applied.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
- </map>
+ </map>
<key>RenderTreeLODFactor</key>
<map>
<key>Comment</key>
@@ -9655,18 +9694,18 @@
<key>Value</key>
<integer>1</integer>
</map>
- <key>RenderPreferStreamDraw</key>
- <map>
- <key>Comment</key>
- <string>Use GL_STREAM_DRAW in place of GL_DYNAMIC_DRAW</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>Boolean</string>
- <key>Value</key>
- <integer>0</integer>
- </map>
- <key>RenderVolumeLODFactor</key>
+ <key>RenderPreferStreamDraw</key>
+ <map>
+ <key>Comment</key>
+ <string>Use GL_STREAM_DRAW in place of GL_DYNAMIC_DRAW</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
+ <key>RenderVolumeLODFactor</key>
<map>
<key>Comment</key>
<string>Controls level of detail of primitives (multiplier for current screen area when calculated level of detail)</string>
@@ -9765,18 +9804,18 @@
<key>Value</key>
<integer>0</integer>
</map>
- <key>ReportBugURL</key>
- <map>
- <key>Comment</key>
- <string>URL used for filing bugs from viewer</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>String</string>
- <key>Value</key>
- <string>https://feedback.secondlife.com/</string>
- </map>
- <key>RevokePermsOnStopAnimation</key>
+ <key>ReportBugURL</key>
+ <map>
+ <key>Comment</key>
+ <string>URL used for filing bugs from viewer</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>String</string>
+ <key>Value</key>
+ <string>https://feedback.secondlife.com/</string>
+ </map>
+ <key>RevokePermsOnStopAnimation</key>
<map>
<key>Comment</key>
<string>Clear animation permssions when choosing "Stop Animating Me"</string>
@@ -10029,39 +10068,39 @@
<key>Value</key>
<real>400.0</real>
</map>
- <key>SceneLoadingMonitorEnabled</key>
- <map>
- <key>Comment</key>
- <string>Enabled scene loading monitor if set</string>
- <key>Persist</key>
- <integer>0</integer>
- <key>Type</key>
- <string>Boolean</string>
- <key>Value</key>
- <integer>0</integer>
- </map>
- <key>SceneLoadingMonitorSampleTime</key>
- <map>
- <key>Comment</key>
- <string>Time between screen samples when monitor scene load (seconds)</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>F32</string>
- <key>Value</key>
- <real>0.25</real>
- </map>
- <key>SceneLoadingMonitorPixelDiffThreshold</key>
- <map>
- <key>Comment</key>
- <string>Amount of pixels changed required to consider the scene as still loading (square root of fraction of pixels on screen)</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>F32</string>
- <key>Value</key>
- <real>0.02</real>
- </map>
+ <key>SceneLoadingMonitorEnabled</key>
+ <map>
+ <key>Comment</key>
+ <string>Enabled scene loading monitor if set</string>
+ <key>Persist</key>
+ <integer>0</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
+ <key>SceneLoadingMonitorSampleTime</key>
+ <map>
+ <key>Comment</key>
+ <string>Time between screen samples when monitor scene load (seconds)</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.25</real>
+ </map>
+ <key>SceneLoadingMonitorPixelDiffThreshold</key>
+ <map>
+ <key>Comment</key>
+ <string>Amount of pixels changed required to consider the scene as still loading (square root of fraction of pixels on screen)</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.02</real>
+ </map>
<key>ScriptHelpFollowsCursor</key>
<map>
<key>Comment</key>
@@ -10238,7 +10277,7 @@
<key>Value</key>
<integer>0</integer>
</map>
- <key>AvatarNameTagMode</key>
+ <key>AvatarNameTagMode</key>
<map>
<key>Comment</key>
<string>Select Avatar Name Tag Mode</string>
@@ -10447,7 +10486,7 @@
<key>Value</key>
<integer>1</integer>
</map>
- <key>ShowScriptErrors</key>
+ <key>ShowScriptErrors</key>
<map>
<key>Comment</key>
<string>Show script errors</string>
@@ -10458,7 +10497,7 @@
<key>Value</key>
<integer>1</integer>
</map>
- <key>ShowScriptErrorsLocation</key>
+ <key>ShowScriptErrorsLocation</key>
<map>
<key>Comment</key>
<string>Show script error in chat (0) or window (1).</string>
@@ -10717,8 +10756,8 @@
<string>Display results of find events that are flagged as moderate</string>
<key>Persist</key>
<integer>1</integer>
- <key>HideFromEditor</key>
- <integer>1</integer>
+ <key>HideFromEditor</key>
+ <integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
@@ -10730,8 +10769,8 @@
<string>Display results of find events that are flagged as adult</string>
<key>Persist</key>
<integer>1</integer>
- <key>HideFromEditor</key>
- <integer>1</integer>
+ <key>HideFromEditor</key>
+ <integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
@@ -10743,8 +10782,8 @@
<string>Display results of find land sales that are flagged as general</string>
<key>Persist</key>
<integer>1</integer>
- <key>HideFromEditor</key>
- <integer>1</integer>
+ <key>HideFromEditor</key>
+ <integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
@@ -10756,8 +10795,8 @@
<string>Display results of find land sales that are flagged as moderate</string>
<key>Persist</key>
<integer>1</integer>
- <key>HideFromEditor</key>
- <integer>1</integer>
+ <key>HideFromEditor</key>
+ <integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
@@ -10769,8 +10808,8 @@
<string>Display results of find land sales that are flagged as adult</string>
<key>Persist</key>
<integer>1</integer>
- <key>HideFromEditor</key>
- <integer>1</integer>
+ <key>HideFromEditor</key>
+ <integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
@@ -10782,8 +10821,8 @@
<string>Display results of find places or find popular that are in general sims</string>
<key>Persist</key>
<integer>1</integer>
- <key>HideFromEditor</key>
- <integer>1</integer>
+ <key>HideFromEditor</key>
+ <integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
@@ -10795,8 +10834,8 @@
<string>Display results of find places or find popular that are in moderate sims</string>
<key>Persist</key>
<integer>1</integer>
- <key>HideFromEditor</key>
- <integer>1</integer>
+ <key>HideFromEditor</key>
+ <integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
@@ -10808,8 +10847,8 @@
<string>Display results of find places or find popular that are in adult sims</string>
<key>Persist</key>
<integer>1</integer>
- <key>HideFromEditor</key>
- <integer>1</integer>
+ <key>HideFromEditor</key>
+ <integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
@@ -10947,17 +10986,17 @@
<key>Value</key>
<integer>0</integer>
</map>
- <key>ShowTutorial</key>
- <map>
- <key>Comment</key>
- <string>Show tutorial window on login</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>Boolean</string>
- <key>Value</key>
- <integer>0</integer>
- </map>
+ <key>ShowTutorial</key>
+ <map>
+ <key>Comment</key>
+ <string>Show tutorial window on login</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
<key>ShowVoiceVisualizersInCalls</key>
<map>
<key>Comment</key>
@@ -13529,17 +13568,17 @@
<key>Value</key>
<real>0.40000000596</real>
</map>
- <key>moapbeacon</key>
- <map>
- <key>Comment</key>
- <string>Beacon / Highlight media on a prim sources</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>Boolean</string>
- <key>Value</key>
- <integer>0</integer>
- </map>
+ <key>moapbeacon</key>
+ <map>
+ <key>Comment</key>
+ <string>Beacon / Highlight media on a prim sources</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>0</integer>
+ </map>
<key>particlesbeacon</key>
<map>
<key>Comment</key>
@@ -13639,17 +13678,17 @@
<key>Value</key>
<integer>0</integer>
</map>
- <key>SLURLDragNDrop</key>
- <map>
- <key>Comment</key>
- <string>Enable drag and drop of SLURLs onto the viewer</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>Boolean</string>
- <key>Value</key>
- <integer>1</integer>
- </map>
+ <key>SLURLDragNDrop</key>
+ <map>
+ <key>Comment</key>
+ <string>Enable drag and drop of SLURLs onto the viewer</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>Boolean</string>
+ <key>Value</key>
+ <integer>1</integer>
+ </map>
<key>SLURLPassToOtherInstance</key>
<map>
<key>Comment</key>
@@ -13837,10 +13876,10 @@
<string>LLSD</string>
<key>Value</key>
<array>
- <string>snapshot</string>
- <string>postcard</string>
- <string>mini_map</string>
- <string>beacons</string>
+ <string>snapshot</string>
+ <string>postcard</string>
+ <string>mini_map</string>
+ <string>beacons</string>
</array>
</map>
<key>LandmarksSortedByDate</key>
@@ -14590,7 +14629,7 @@
<key>Value</key>
<integer>0</integer>
</map>
- <key>LocalTerrainAsset1</key>
+ <key>LocalTerrainAsset1</key>
<map>
<key>Comment</key>
<string>If set to a non-null UUID, overrides the terrain asset locally for all regions with material assets. Local terrain assets are not visible to others. Please keep in mind that this debug setting may be temporary. Do not rely on this setting existing in future viewer builds.</string>
@@ -14601,7 +14640,7 @@
<key>Value</key>
<string>00000000-0000-0000-0000-000000000000</string>
</map>
- <key>LocalTerrainAsset2</key>
+ <key>LocalTerrainAsset2</key>
<map>
<key>Comment</key>
<string>If set to a non-null UUID, overrides the terrain asset locally for all regions with material assets. Local terrain assets are not visible to others. Please keep in mind that this debug setting may be temporary. Do not rely on this setting existing in future viewer builds.</string>
@@ -14612,7 +14651,7 @@
<key>Value</key>
<string>00000000-0000-0000-0000-000000000000</string>
</map>
- <key>LocalTerrainAsset3</key>
+ <key>LocalTerrainAsset3</key>
<map>
<key>Comment</key>
<string>If set to a non-null UUID, overrides the terrain asset locally for all regions with material assets. Local terrain assets are not visible to others. Please keep in mind that this debug setting may be temporary. Do not rely on this setting existing in future viewer builds.</string>
@@ -14623,7 +14662,7 @@
<key>Value</key>
<string>00000000-0000-0000-0000-000000000000</string>
</map>
- <key>LocalTerrainAsset4</key>
+ <key>LocalTerrainAsset4</key>
<map>
<key>Comment</key>
<string>If set to a non-null UUID, overrides the terrain asset locally for all regions with material assets. Local terrain assets are not visible to others. Please keep in mind that this debug setting may be temporary. Do not rely on this setting existing in future viewer builds.</string>
@@ -14634,7 +14673,7 @@
<key>Value</key>
<string>00000000-0000-0000-0000-000000000000</string>
</map>
- <key>PathfindingRetrieveNeighboringRegion</key>
+ <key>PathfindingRetrieveNeighboringRegion</key>
<map>
<key>Comment</key>
<string>Download a neighboring region when visualizing a pathfinding navmesh (default val 99 means do not download neighbors).</string>
@@ -14643,9 +14682,9 @@
<key>Type</key>
<string>U32</string>
<key>Value</key>
- <integer>99</integer>
+ <integer>99</integer>
</map>
- <key>PathfindingNavMeshClear</key>
+ <key>PathfindingNavMeshClear</key>
<map>
<key>Comment</key>
<string>Background color when displaying pathfinding navmesh.</string>
@@ -14805,7 +14844,7 @@
<real>1.0</real>
</array>
</map>
- <key>PathfindingTestPathValidEndColor</key>
+ <key>PathfindingTestPathValidEndColor</key>
<map>
<key>Comment</key>
<string>Color of the pathfinding test-pathing tool end-point when the path is valid.</string>
@@ -14837,7 +14876,7 @@
<real>1.0</real>
</array>
</map>
- <key>PathfindingTestPathColor</key>
+ <key>PathfindingTestPathColor</key>
<map>
<key>Comment</key>
<string>Color of the pathfinding test-path when the path is valid.</string>
@@ -15397,17 +15436,6 @@
<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>
@@ -15485,17 +15513,17 @@
<key>Value</key>
<real>300</real>
</map>
- <key>StatsReportFileInterval</key>
- <map>
- <key>Comment</key>
- <string>Interval to save viewer stats file data</string>
- <key>Persist</key>
- <integer>1</integer>
- <key>Type</key>
- <string>F32</string>
- <key>Value</key>
- <real>0.2</real>
- </map>
+ <key>StatsReportFileInterval</key>
+ <map>
+ <key>Comment</key>
+ <string>Interval to save viewer stats file data</string>
+ <key>Persist</key>
+ <integer>1</integer>
+ <key>Type</key>
+ <string>F32</string>
+ <key>Value</key>
+ <real>0.2</real>
+ </map>
<key>StatsReportSkipZeroDataSaves</key>
<map>
<key>Comment</key>
diff --git a/indra/newview/llappearancelistener.cpp b/indra/newview/llappearancelistener.cpp
new file mode 100644
index 0000000000..0dab352311
--- /dev/null
+++ b/indra/newview/llappearancelistener.cpp
@@ -0,0 +1,146 @@
+/**
+ * @file llappearancelistener.cpp
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, 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 "llappearancelistener.h"
+
+#include "llappearancemgr.h"
+#include "llinventoryfunctions.h"
+#include "lltransutil.h"
+#include "llwearableitemslist.h"
+#include "stringize.h"
+
+LLAppearanceListener::LLAppearanceListener()
+ : LLEventAPI("LLAppearance",
+ "API to wear a specified outfit and wear/remove individual items")
+{
+ add("wearOutfit",
+ "Wear outfit by folder id: [\"folder_id\"] OR by folder name: [\"folder_name\"]\n"
+ "When [\"append\"] is true, outfit will be added to COF\n"
+ "otherwise it will replace current oufit",
+ &LLAppearanceListener::wearOutfit);
+
+ add("wearItems",
+ "Wear items by id: [items_id]",
+ &LLAppearanceListener::wearItems,
+ llsd::map("items_id", LLSD(), "replace", LLSD()));
+
+ add("detachItems",
+ "Detach items by id: [items_id]",
+ &LLAppearanceListener::detachItems,
+ llsd::map("items_id", LLSD()));
+
+ add("getOutfitsList",
+ "Return the table with Outfits info(id and name)",
+ &LLAppearanceListener::getOutfitsList);
+
+ add("getOutfitItems",
+ "Return the table of items with info(id : name, wearable_type, is_worn) inside specified outfit folder",
+ &LLAppearanceListener::getOutfitItems);
+}
+
+
+void LLAppearanceListener::wearOutfit(LLSD const &data)
+{
+ Response response(LLSD(), data);
+ if (!data.has("folder_id") && !data.has("folder_name"))
+ {
+ return response.error("Either [folder_id] or [folder_name] is required");
+ }
+
+ std::string error_msg;
+ bool result(false);
+ bool append = data.has("append") ? data["append"].asBoolean() : false;
+ if (data.has("folder_id"))
+ {
+ result = LLAppearanceMgr::instance().wearOutfit(data["folder_id"].asUUID(), error_msg, append);
+ }
+ else
+ {
+ result = LLAppearanceMgr::instance().wearOutfitByName(data["folder_name"].asString(), error_msg, append);
+ }
+
+ if (!result)
+ {
+ response.error(error_msg);
+ }
+}
+
+void LLAppearanceListener::wearItems(LLSD const &data)
+{
+ LLAppearanceMgr::instance().wearItemsOnAvatar(
+ LLSDParam<uuid_vec_t>(data["items_id"]),
+ true, data["replace"].asBoolean());
+}
+
+void LLAppearanceListener::detachItems(LLSD const &data)
+{
+ LLAppearanceMgr::instance().removeItemsFromAvatar(
+ LLSDParam<uuid_vec_t>(data["items_id"]));
+}
+
+void LLAppearanceListener::getOutfitsList(LLSD const &data)
+{
+ Response response(LLSD(), data);
+ const LLUUID outfits_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS);
+
+ LLInventoryModel::cat_array_t cat_array;
+ LLInventoryModel::item_array_t item_array;
+
+ LLIsType is_category(LLAssetType::AT_CATEGORY);
+ gInventory.collectDescendentsIf(outfits_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, is_category);
+
+ response["outfits"] = llsd::toMap(cat_array,
+ [](const LLPointer<LLViewerInventoryCategory> &cat)
+ { return std::make_pair(cat->getUUID().asString(), cat->getName()); });
+}
+
+void LLAppearanceListener::getOutfitItems(LLSD const &data)
+{
+ Response response(LLSD(), data);
+ LLUUID outfit_id(data["outfit_id"].asUUID());
+ LLViewerInventoryCategory *cat = gInventory.getCategory(outfit_id);
+ if (!cat || cat->getPreferredType() != LLFolderType::FT_OUTFIT)
+ {
+ return response.error(stringize(LLTrans::getString("OutfitNotFound"), outfit_id.asString()));
+ }
+ LLInventoryModel::cat_array_t cat_array;
+ LLInventoryModel::item_array_t item_array;
+
+ LLFindOutfitItems collector = LLFindOutfitItems();
+ gInventory.collectDescendentsIf(outfit_id, cat_array, item_array, LLInventoryModel::EXCLUDE_TRASH, collector);
+
+ response["items"] = llsd::toMap(item_array,
+ [](const LLPointer<LLViewerInventoryItem> &it)
+ {
+ return std::make_pair(
+ it->getUUID().asString(),
+ llsd::map(
+ "name", it->getName(),
+ "wearable_type", LLWearableType::getInstance()->getTypeName(it->isWearableType() ? it->getWearableType() : LLWearableType::WT_NONE),
+ "is_worn", get_is_item_worn(it)));
+ });
+}
diff --git a/indra/newview/llappearancelistener.h b/indra/newview/llappearancelistener.h
new file mode 100644
index 0000000000..04c5eac2eb
--- /dev/null
+++ b/indra/newview/llappearancelistener.h
@@ -0,0 +1,46 @@
+/**
+ * @file llappearancelistener.h
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2024, 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_LLAPPEARANCELISTENER_H
+#define LL_LLAPPEARANCELISTENER_H
+
+#include "lleventapi.h"
+
+class LLAppearanceListener : public LLEventAPI
+{
+public:
+ LLAppearanceListener();
+
+private:
+ void wearOutfit(LLSD const &data);
+ void wearItems(LLSD const &data);
+ void detachItems(LLSD const &data);
+ void getOutfitsList(LLSD const &data);
+ void getOutfitItems(LLSD const &data);
+};
+
+#endif // LL_LLAPPEARANCELISTENER_H
+
diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp
index 30f07a873b..26c41b19ed 100644
--- a/indra/newview/llappearancemgr.cpp
+++ b/indra/newview/llappearancemgr.cpp
@@ -31,6 +31,7 @@
#include "llagent.h"
#include "llagentcamera.h"
#include "llagentwearables.h"
+#include "llappearancelistener.h"
#include "llappearancemgr.h"
#include "llattachmentsmgr.h"
#include "llcommandhandler.h"
@@ -48,6 +49,7 @@
#include "lloutfitslist.h"
#include "llselectmgr.h"
#include "llsidepanelappearance.h"
+#include "lltransutil.h"
#include "llviewerobjectlist.h"
#include "llvoavatar.h"
#include "llvoavatarself.h"
@@ -71,6 +73,8 @@
#pragma warning (disable:4702)
#endif
+LLAppearanceListener sAppearanceListener;
+
namespace
{
const S32 BAKE_RETRY_MAX_COUNT = 5;
@@ -2900,8 +2904,18 @@ void LLAppearanceMgr::wearInventoryCategoryOnAvatar( LLInventoryCategory* catego
LLAppearanceMgr::changeOutfit(TRUE, category->getUUID(), append);
}
-// FIXME do we really want to search entire inventory for matching name?
-void LLAppearanceMgr::wearOutfitByName(const std::string& name)
+bool LLAppearanceMgr::wearOutfitByName(const std::string& name, bool append)
+{
+ std::string error_msg;
+ if(!wearOutfitByName(name, error_msg, append))
+ {
+ LL_WARNS() << error_msg << LL_ENDL;
+ return false;
+ }
+ return true;
+}
+
+bool LLAppearanceMgr::wearOutfitByName(const std::string& name, std::string& error_msg, bool append)
{
LL_INFOS("Avatar") << self_av_string() << "Wearing category " << name << LL_ENDL;
@@ -2934,15 +2948,38 @@ void LLAppearanceMgr::wearOutfitByName(const std::string& name)
}
}
- if(cat)
+ return wearOutfit(stringize(std::quoted(name)), cat, error_msg, copy_items, append);
+}
+
+bool LLAppearanceMgr::wearOutfit(const LLUUID &cat_id, std::string &error_msg, bool append)
+{
+ LLViewerInventoryCategory *cat = gInventory.getCategory(cat_id);
+ return wearOutfit(stringize(cat_id), cat, error_msg, false, append);
+}
+
+bool LLAppearanceMgr::wearOutfit(const std::string &desc, LLInventoryCategory* cat,
+ std::string &error_msg, bool copy_items, bool append)
+{
+ if (!cat)
{
- LLAppearanceMgr::wearInventoryCategory(cat, copy_items, false);
+ error_msg = stringize(LLTrans::getString("OutfitNotFound"), desc);
+ return false;
}
- else
+ // don't allow wearing a system folder
+ if (LLFolderType::lookupIsProtectedType(cat->getPreferredType()))
{
- LL_WARNS() << "Couldn't find outfit " <<name<< " in wearOutfitByName()"
- << LL_ENDL;
+ error_msg = stringize(LLTrans::getString("SystemFolderNotWorn"), std::quoted(cat->getName()));
+ return false;
}
+ bool can_wear = append ? getCanAddToCOF(cat->getUUID()) : getCanReplaceCOF(cat->getUUID());
+ if (!can_wear)
+ {
+ std::string msg = append ? LLTrans::getString("OutfitNotAdded") : LLTrans::getString("OutfitNotReplaced");
+ error_msg = stringize(msg, std::quoted(cat->getName()), " , id: ", cat->getUUID());
+ return false;
+ }
+ wearInventoryCategory(cat, copy_items, append);
+ return true;
}
bool areMatchingWearables(const LLViewerInventoryItem *a, const LLViewerInventoryItem *b)
diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h
index e5de92b653..11d209e6b5 100644
--- a/indra/newview/llappearancemgr.h
+++ b/indra/newview/llappearancemgr.h
@@ -59,7 +59,9 @@ public:
void wearInventoryCategory(LLInventoryCategory* category, bool copy, bool append);
void wearInventoryCategoryOnAvatar(LLInventoryCategory* category, bool append);
void wearCategoryFinal(const LLUUID& cat_id, bool copy_items, bool append);
- void wearOutfitByName(const std::string& name);
+ bool wearOutfit(const LLUUID &cat_id, std::string &error_msg, bool append = false);
+ bool wearOutfitByName(const std::string &name, std::string &error_msg, bool append = false);
+ bool wearOutfitByName(const std::string &name, bool append = false);
void changeOutfit(bool proceed, const LLUUID& category, bool append);
void replaceCurrentOutfit(const LLUUID& new_outfit);
void renameOutfit(const LLUUID& outfit_id);
@@ -261,6 +263,10 @@ private:
static void onOutfitRename(const LLSD& notification, const LLSD& response);
+ // used by both wearOutfit(LLUUID) and wearOutfitByName(std::string)
+ bool wearOutfit(const std::string &desc, LLInventoryCategory* cat,
+ std::string &error_msg, bool copy_items, bool append);
+
bool mAttachmentInvLinkEnabled;
bool mOutfitIsDirty;
bool mIsInUpdateAppearanceFromCOF; // to detect recursive calls.
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 65e5e4f783..ef12fe0bd3 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -117,6 +117,7 @@
#include "lldiriterator.h"
#include "llexperiencecache.h"
#include "llimagej2c.h"
+#include "llluamanager.h"
#include "llmemory.h"
#include "llprimitive.h"
#include "llurlaction.h"
@@ -1218,6 +1219,24 @@ bool LLAppViewer::init()
// no completion callback: we don't need to know
LLLUAmanager::runScriptFile(script);
});
+ processComposeSwitch(
+ "LuaAutorunPath", "LuaAutorunPath",
+ [](const LLSD& directory)
+ {
+ // each directory can be relative to the viewer's install
+ // directory -- if directory is already absolute, operator/()
+ // preserves it
+ auto abspath(fsyspath(gDirUtilp->getAppRODataDir()) / directory.asString());
+ std::string absdir(abspath.string());
+ LL_DEBUGS("InitInfo") << "LuaAutorunPath: " << absdir << LL_ENDL;
+ LLDirIterator scripts(absdir, "*.lua");
+ std::string script;
+ while (scripts.next(script))
+ {
+ LL_DEBUGS("InitInfo") << "LuaAutorunPath: " << absdir << ": " << script << LL_ENDL;
+ LLLUAmanager::runScriptFile((abspath / script).string());
+ }
+ });
if (gSavedSettings.getBOOL("QAMode") && gSavedSettings.getS32("QAModeEventHostPort") > 0)
{
diff --git a/indra/newview/llappviewerlistener.cpp b/indra/newview/llappviewerlistener.cpp
index 6d519b6fef..d02b1b4a79 100644
--- a/indra/newview/llappviewerlistener.cpp
+++ b/indra/newview/llappviewerlistener.cpp
@@ -42,6 +42,9 @@ LLAppViewerListener::LLAppViewerListener(const LLAppViewerGetter& getter):
mAppViewerGetter(getter)
{
// add() every method we want to be able to invoke via this event API.
+ add("userQuit",
+ "Ask to quit with user confirmation prompt",
+ &LLAppViewerListener::userQuit);
add("requestQuit",
"Ask to quit nicely",
&LLAppViewerListener::requestQuit);
@@ -50,6 +53,12 @@ LLAppViewerListener::LLAppViewerListener(const LLAppViewerGetter& getter):
&LLAppViewerListener::forceQuit);
}
+void LLAppViewerListener::userQuit(const LLSD& event)
+{
+ LL_INFOS() << "Listener requested user quit" << LL_ENDL;
+ mAppViewerGetter()->userQuit();
+}
+
void LLAppViewerListener::requestQuit(const LLSD& event)
{
LL_INFOS() << "Listener requested quit" << LL_ENDL;
diff --git a/indra/newview/llappviewerlistener.h b/indra/newview/llappviewerlistener.h
index 5ade3d3e04..e116175eb7 100644
--- a/indra/newview/llappviewerlistener.h
+++ b/indra/newview/llappviewerlistener.h
@@ -30,7 +30,7 @@
#define LL_LLAPPVIEWERLISTENER_H
#include "lleventapi.h"
-#include <boost/function.hpp>
+#include <functional>
class LLAppViewer;
class LLSD;
@@ -39,11 +39,12 @@ class LLSD;
class LLAppViewerListener: public LLEventAPI
{
public:
- typedef boost::function<LLAppViewer*(void)> LLAppViewerGetter;
+ typedef std::function<LLAppViewer*(void)> LLAppViewerGetter;
/// Bind the LLAppViewer instance to use (e.g. LLAppViewer::instance()).
LLAppViewerListener(const LLAppViewerGetter& getter);
private:
+ void userQuit(const LLSD& event);
void requestQuit(const LLSD& event);
void forceQuit(const LLSD& event);
diff --git a/indra/newview/llfloaterluadebug.cpp b/indra/newview/llfloaterluadebug.cpp
index f715327ec8..9981126e4f 100644
--- a/indra/newview/llfloaterluadebug.cpp
+++ b/indra/newview/llfloaterluadebug.cpp
@@ -27,7 +27,6 @@
#include "llfloaterluadebug.h"
-#include "llcheckboxctrl.h"
#include "lllineeditor.h"
#include "lltexteditor.h"
#include "llviewermenufile.h" // LLFilePickerReplyThread
@@ -92,8 +91,7 @@ void LLFloaterLUADebug::onExecuteClicked()
mResultOutput->setValue("");
std::string cmd = mLineInput->getText();
- cleanLuaState();
- LLLUAmanager::runScriptLine(mState, cmd, [this](int count, const LLSD& result)
+ LLLUAmanager::runScriptLine(cmd, [this](int count, const LLSD& result)
{
completion(count, result);
});
@@ -174,12 +172,3 @@ void LLFloaterLUADebug::completion(int count, const LLSD& result)
sep = ", ";
}
}
-
-void LLFloaterLUADebug::cleanLuaState()
-{
- if(getChild<LLCheckBoxCtrl>("clean_lua_state")->get())
- {
- //Reinit to clean lua_State
- mState.initLuaState();
- }
-}
diff --git a/indra/newview/llfloaterluadebug.h b/indra/newview/llfloaterluadebug.h
index ae30b7cf25..e513d9a095 100644
--- a/indra/newview/llfloaterluadebug.h
+++ b/indra/newview/llfloaterluadebug.h
@@ -58,14 +58,12 @@ class LLFloaterLUADebug :
private:
void completion(int count, const LLSD& result);
- void cleanLuaState();
LLTempBoundListener mOutConnection;
LLTextEditor* mResultOutput;
LLLineEditor* mLineInput;
LLLineEditor* mScriptPath;
- LuaState mState;
U32 mAck{ 0 };
bool mExecuting{ false };
};
diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp
index 8b6f7a4698..7014c59e4e 100644
--- a/indra/newview/llluamanager.cpp
+++ b/indra/newview/llluamanager.cpp
@@ -32,6 +32,7 @@
#include "llcoros.h"
#include "llerror.h"
#include "lleventcoro.h"
+#include "llsdutil.h"
#include "llviewercontrol.h"
#include "lua_function.h"
#include "lualistener.h"
@@ -161,24 +162,25 @@ lua_function(get_event_next,
return 2;
}
-LLCoros::Future<std::pair<int, LLSD>>
+LLCoros::Future<LLLUAmanager::script_result>
LLLUAmanager::startScriptFile(const std::string& filename)
{
// Despite returning from startScriptFile(), we need this Promise to
// remain alive until the callback has fired.
- auto promise{ std::make_shared<LLCoros::Promise<std::pair<int, LLSD>>>() };
+ auto promise{ std::make_shared<LLCoros::Promise<script_result>>() };
runScriptFile(filename,
[promise](int count, LLSD result)
{ promise->set_value({ count, result }); });
return LLCoros::getFuture(*promise);
}
-std::pair<int, LLSD> LLLUAmanager::waitScriptFile(const std::string& filename)
+LLLUAmanager::script_result LLLUAmanager::waitScriptFile(const std::string& filename)
{
return startScriptFile(filename).get();
}
-void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn result_cb, script_finished_fn finished_cb)
+void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn result_cb,
+ script_finished_fn finished_cb)
{
// A script_result_fn will be called when LuaState::expr() completes.
LLCoros::instance().launch(filename, [filename, result_cb, finished_cb]()
@@ -211,39 +213,25 @@ void LLLUAmanager::runScriptFile(const std::string &filename, script_result_fn r
});
}
-void LLLUAmanager::runScriptLine(const std::string& chunk, script_finished_fn cb)
-{
- // A script_finished_fn is used to initialize the LuaState.
- // It will be called when the LuaState is destroyed.
- LuaState L(cb);
- runScriptLine(L, chunk);
-}
-
-void LLLUAmanager::runScriptLine(const std::string& chunk, script_result_fn cb)
-{
- LuaState L;
- // A script_result_fn will be called when LuaState::expr() completes.
- runScriptLine(L, chunk, cb);
-}
-
-LLCoros::Future<std::pair<int, LLSD>>
-LLLUAmanager::startScriptLine(LuaState& L, const std::string& chunk)
+LLCoros::Future<LLLUAmanager::script_result>
+LLLUAmanager::startScriptLine(const std::string& chunk)
{
// Despite returning from startScriptLine(), we need this Promise to
// remain alive until the callback has fired.
- auto promise{ std::make_shared<LLCoros::Promise<std::pair<int, LLSD>>>() };
- runScriptLine(L, chunk,
+ auto promise{ std::make_shared<LLCoros::Promise<script_result>>() };
+ runScriptLine(chunk,
[promise](int count, LLSD result)
{ promise->set_value({ count, result }); });
return LLCoros::getFuture(*promise);
}
-std::pair<int, LLSD> LLLUAmanager::waitScriptLine(LuaState& L, const std::string& chunk)
+LLLUAmanager::script_result LLLUAmanager::waitScriptLine(const std::string& chunk)
{
- return startScriptLine(L, chunk).get();
+ return startScriptLine(chunk).get();
}
-void LLLUAmanager::runScriptLine(LuaState& L, const std::string& chunk, script_result_fn cb)
+void LLLUAmanager::runScriptLine(const std::string& chunk, script_result_fn result_cb,
+ script_finished_fn finished_cb)
{
// find a suitable abbreviation for the chunk string
std::string shortchunk{ chunk };
@@ -255,37 +243,19 @@ void LLLUAmanager::runScriptLine(LuaState& L, const std::string& chunk, script_r
shortchunk = stringize(shortchunk.substr(0, shortlen), "...");
std::string desc{ "lua: " + shortchunk };
- LLCoros::instance().launch(desc, [&L, desc, chunk, cb]()
+ LLCoros::instance().launch(desc, [desc, chunk, result_cb, finished_cb]()
{
+ // A script_finished_fn is used to initialize the LuaState.
+ // It will be called when the LuaState is destroyed.
+ LuaState L(finished_cb);
auto [count, result] = L.expr(desc, chunk);
- if (cb)
+ if (result_cb)
{
- cb(count, result);
+ result_cb(count, result);
}
});
}
-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 read_file(const std::string &name)
{
llifstream in_file;
@@ -330,31 +300,6 @@ LLRequireResolver::LLRequireResolver(lua_State *L, const std::string& path) :
luaL_argerrorL(L, 1, "cannot require a full path");
}
-/**
- * Remove a particular stack index on exit from enclosing scope.
- * If you pass a negative index (meaning relative to the current stack top),
- * converts to an absolute index. The point of LuaRemover is to remove the
- * entry at the specified index regardless of subsequent pushes to the stack.
- */
-class LuaRemover
-{
-public:
- LuaRemover(lua_State* L, int index):
- mState(L),
- mIndex(lua_absindex(L, index))
- {}
- LuaRemover(const LuaRemover&) = delete;
- LuaRemover& operator=(const LuaRemover&) = delete;
- ~LuaRemover()
- {
- lua_remove(mState, mIndex);
- }
-
-private:
- lua_State* mState;
- int mIndex;
-};
-
// push the loaded module or throw a Lua error
void LLRequireResolver::findModule()
{
@@ -384,18 +329,13 @@ void LLRequireResolver::findModule()
fail();
}
- std::vector<fsyspath> lib_paths
+ LLSD lib_paths(gSavedSettings.getLLSD("LuaRequirePath"));
+ LL_DEBUGS("Lua") << "LuaRequirePath = " << lib_paths << LL_ENDL;
+ for (const auto& path : llsd::inArray(lib_paths))
{
- gDirUtilp->getExpandedFilename(LL_PATH_SCRIPTS, "lua", "require"),
-#ifdef LL_TEST
- // Build-time tests don't have the app bundle - use source tree.
- fsyspath(__FILE__).parent_path() / "scripts" / "lua" / "require",
-#endif
- };
-
- for (const auto& path : lib_paths)
- {
- std::string absolutePathOpt = (path / mPathToResolve).u8string();
+ // if path is already absolute, operator/() preserves it
+ auto abspath(fsyspath(gDirUtilp->getAppRODataDir()) / path.asString());
+ std::string absolutePathOpt = (abspath / mPathToResolve).u8string();
if (absolutePathOpt.empty())
luaL_error(L, "error requiring module '%s'", mPathToResolve.u8string().data());
@@ -454,45 +394,70 @@ bool LLRequireResolver::findModuleImpl(const std::string& absolutePath)
void LLRequireResolver::runModule(const std::string& desc, const std::string& code)
{
// Here we just loaded a new module 'code', need to run it and get its result.
- // Module needs to run in a new thread, isolated from the rest.
- // Note: we create ML on main thread so that it doesn't inherit environment of L.
- lua_State *GL = lua_mainthread(L);
-// lua_State *ML = lua_newthread(GL);
- // Try loading modules on Lua's main thread instead.
- lua_State *ML = GL;
- // lua_newthread() pushed the new thread object on GL's stack. Move to L's.
-// lua_xmove(GL, L, 1);
-
- // new thread needs to have the globals sandboxed
-// luaL_sandboxthread(ML);
+ lua_State *ML = lua_mainthread(L);
{
// If loadstring() returns (! LUA_OK) then there's an error message on
// the stack. If it returns LUA_OK then the newly-loaded module code
// is on the stack.
- if (lluau::loadstring(ML, desc, code) == LUA_OK)
+ LL_DEBUGS("Lua") << "Loading module " << desc << LL_ENDL;
+ if (lluau::loadstring(ML, desc, code) != LUA_OK)
+ {
+ // error message on stack top
+ LL_DEBUGS("Lua") << "Error loading module " << desc << ": "
+ << lua_tostring(ML, -1) << LL_ENDL;
+ lua_pushliteral(ML, "loadstring: ");
+ // stack contains error, "loadstring: "
+ // swap: insert stack top at position -2
+ lua_insert(ML, -2);
+ // stack contains "loadstring: ", error
+ lua_concat(ML, 2);
+ // stack contains "loadstring: " + error
+ }
+ else // module code on stack top
{
- // luau uses Lua 5.3's version of lua_resume():
- // run the coroutine on ML, "from" L, passing no arguments.
-// int status = lua_resume(ML, L, 0);
- // we expect one return value
- int status = lua_pcall(ML, 0, 1, 0);
+ // push debug module
+ lua_getglobal(ML, "debug");
+ // push debug.traceback
+ lua_getfield(ML, -1, "traceback");
+ // stack contains module code, debug, debug.traceback
+ // ditch debug
+ lua_replace(ML, -2);
+ // stack contains module code, debug.traceback
+ // swap: insert stack top at position -2
+ lua_insert(ML, -2);
+ // stack contains debug.traceback, module code
+ LL_DEBUGS("Lua") << "Loaded module " << desc << ", running" << LL_ENDL;
+ // no arguments, one return value
+ // pass debug.traceback as the error function
+ int status = lua_pcall(ML, 0, 1, -2);
+ // lua_pcall() has popped the module code and replaced it with its
+ // return value. Regardless of status or the type of the stack
+ // top, get rid of debug.traceback on the stack.
+ lua_remove(ML, -2);
if (status == LUA_OK)
{
- if (lua_gettop(ML) == 0)
- lua_pushfstring(ML, "module %s must return a value", desc.data());
- else if (!lua_istable(ML, -1) && !lua_isfunction(ML, -1))
+ auto top{ lua_gettop(ML) };
+ std::string type{ (top == 0)? "nothing"
+ : lua_typename(ML, lua_type(ML, -1)) };
+ LL_DEBUGS("Lua") << "Module " << desc << " returned " << type << LL_ENDL;
+ if ((top == 0) || ! (lua_istable(ML, -1) || lua_isfunction(ML, -1)))
+ {
lua_pushfstring(ML, "module %s must return a table or function, not %s",
- desc.data(), lua_typename(ML, lua_type(ML, -1)));
+ desc.data(), type.data());
+ }
}
else if (status == LUA_YIELD)
{
+ LL_DEBUGS("Lua") << "Module " << desc << " yielded" << LL_ENDL;
lua_pushfstring(ML, "module %s can not yield", desc.data());
}
- else if (!lua_isstring(ML, -1))
+ else
{
- lua_pushfstring(ML, "unknown error while running module %s", desc.data());
+ llassert(lua_isstring(ML, -1));
+ LL_DEBUGS("Lua") << "Module " << desc << " error: "
+ << lua_tostring(ML, -1) << LL_ENDL;
}
}
}
@@ -502,8 +467,4 @@ void LLRequireResolver::runModule(const std::string& desc, const std::string& co
{
lua_xmove(ML, L, 1);
}
- // remove ML from L's stack
-// lua_remove(L, -2);
-// // DON'T call lua_close(ML)! Since ML is only a thread of L, corrupts L too!
-// lua_close(ML);
}
diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h
index af9dcf70c2..50f922a80f 100644
--- a/indra/newview/llluamanager.h
+++ b/indra/newview/llluamanager.h
@@ -55,34 +55,32 @@ public:
// count > 1 with result.isArray() means the script returned multiple
// results, represented as the entries of the result array.
typedef std::function<void(int count, const LLSD& result)> script_result_fn;
+ // same semantics as script_result_fn parameters
+ typedef std::pair<int, LLSD> script_result;
- static void runScriptFile(const std::string &filename, script_result_fn result_cb = {}, script_finished_fn finished_cb = {});
+ static void runScriptFile(const std::string &filename, script_result_fn result_cb = {},
+ script_finished_fn finished_cb = {});
// Start running a Lua script file, returning an LLCoros::Future whose
// get() method will pause the calling coroutine until it can deliver the
// (count, result) pair described above. Between startScriptFile() and
// Future::get(), the caller and the Lua script coroutine will run
// concurrently.
- static LLCoros::Future<std::pair<int, LLSD>>
- startScriptFile(const std::string& filename);
+ static LLCoros::Future<script_result> startScriptFile(const std::string& filename);
// Run a Lua script file, and pause the calling coroutine until it completes.
// The return value is the (count, result) pair described above.
- static std::pair<int, LLSD> waitScriptFile(const std::string& filename);
+ static script_result waitScriptFile(const std::string& filename);
- static void runScriptLine(const std::string &chunk, script_finished_fn cb = {});
- static void runScriptLine(const std::string &chunk, script_result_fn cb);
- static void runScriptLine(LuaState& L, const std::string &chunk, script_result_fn cb = {});
+ static void runScriptLine(const std::string &chunk, script_result_fn result_cb = {},
+ script_finished_fn finished_cb = {});
// Start running a Lua chunk, returning an LLCoros::Future whose
// get() method will pause the calling coroutine until it can deliver the
// (count, result) pair described above. Between startScriptLine() and
// Future::get(), the caller and the Lua script coroutine will run
// concurrently.
- static LLCoros::Future<std::pair<int, LLSD>>
- startScriptLine(LuaState& L, const std::string& chunk);
+ static LLCoros::Future<script_result> startScriptLine(const std::string& chunk);
// Run a Lua chunk, and pause the calling coroutine until it completes.
// The return value is the (count, result) pair described above.
- static std::pair<int, LLSD> waitScriptLine(LuaState& L, const std::string& chunk);
-
- static void runScriptOnLogin();
+ static script_result waitScriptLine(const std::string& chunk);
static const std::map<std::string, std::string> getScriptNames() { return sScriptNames; }
diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp
index 470e512694..3cf0def66e 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -208,7 +208,6 @@
#include "llstacktrace.h"
#include "threadpool.h"
-#include "llluamanager.h"
#include "llperfstats.h"
@@ -2422,8 +2421,6 @@ bool idle_startup()
LLPerfStats::StatsRecorder::setAutotuneInit();
- LLLUAmanager::runScriptOnLogin();
-
return TRUE;
}
diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp
index 90b7c43047..cea7180187 100644
--- a/indra/newview/llviewermenu.cpp
+++ b/indra/newview/llviewermenu.cpp
@@ -9745,8 +9745,8 @@ void initialize_menus()
view_listener_t::addMenu(new LLAdvancedEnableAppearanceToXML(), "Advanced.EnableAppearanceToXML");
view_listener_t::addMenu(new LLAdvancedToggleCharacterGeometry(), "Advanced.ToggleCharacterGeometry");
- view_listener_t::addMenu(new LLAdvancedTestMale(), "Advanced.TestMale");
- view_listener_t::addMenu(new LLAdvancedTestFemale(), "Advanced.TestFemale");
+ view_listener_t::addMenu(new LLAdvancedTestMale(), "Advanced.TestMale", cb_info::UNTRUSTED_THROTTLE);
+ view_listener_t::addMenu(new LLAdvancedTestFemale(), "Advanced.TestFemale", cb_info::UNTRUSTED_THROTTLE);
// Advanced > Character > Animation Speed
view_listener_t::addMenu(new LLAdvancedAnimTenFaster(), "Advanced.AnimTenFaster");
diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp
index 01d4695eda..26b088e390 100644
--- a/indra/newview/llviewermessage.cpp
+++ b/indra/newview/llviewermessage.cpp
@@ -2749,6 +2749,11 @@ void process_chat_from_simulator(LLMessageSystem *msg, void **user_data)
msg_notify["from_id"] = chat.mFromID;
msg_notify["source_type"] = chat.mSourceType;
on_new_message(msg_notify);
+
+
+ msg_notify["chat_type"] = chat.mChatType;
+ msg_notify["message"] = mesg;
+ LLEventPumps::instance().obtain("LLNearbyChat").post(msg_notify);
}
}
diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp
index b637bcbdac..9f60f71d28 100644
--- a/indra/newview/llviewerwindow.cpp
+++ b/indra/newview/llviewerwindow.cpp
@@ -4840,7 +4840,7 @@ BOOL LLViewerWindow::saveSnapshot(const std::string& filepath, S32 image_width,
LL_INFOS() << "Saving snapshot to: " << filepath << LL_ENDL;
LLPointer<LLImageRaw> raw = new LLImageRaw;
- BOOL success = rawSnapshot(raw, image_width, image_height, TRUE, FALSE, show_ui, show_hud, do_rebuild);
+ BOOL success = rawSnapshot(raw, image_width, image_height, TRUE, FALSE, show_ui, show_hud, do_rebuild, 0, type);
if (success)
{
diff --git a/indra/newview/llviewerwindowlistener.cpp b/indra/newview/llviewerwindowlistener.cpp
index da7e18af5c..52f413792a 100644
--- a/indra/newview/llviewerwindowlistener.cpp
+++ b/indra/newview/llviewerwindowlistener.cpp
@@ -43,22 +43,13 @@ LLViewerWindowListener::LLViewerWindowListener(LLViewerWindow* llviewerwindow):
mViewerWindow(llviewerwindow)
{
// add() every method we want to be able to invoke via this event API.
- LLSD saveSnapshotArgs;
- saveSnapshotArgs["filename"] = LLSD::String();
- saveSnapshotArgs["reply"] = LLSD::String();
- // The following are optional, so don't build them into required prototype.
-// saveSnapshotArgs["width"] = LLSD::Integer();
-// saveSnapshotArgs["height"] = LLSD::Integer();
-// saveSnapshotArgs["showui"] = LLSD::Boolean();
-// saveSnapshotArgs["showhud"] = LLSD::Boolean();
-// saveSnapshotArgs["rebuild"] = LLSD::Boolean();
-// saveSnapshotArgs["type"] = LLSD::String();
add("saveSnapshot",
- "Save screenshot: [\"filename\"], [\"width\"], [\"height\"], [\"showui\"], [\"showhud\"], [\"rebuild\"], [\"type\"]\n"
+ "Save screenshot: [\"filename\"] (extension may be specified: bmp, jpeg, png)\n"
+ "[\"width\"], [\"height\"], [\"showui\"], [\"showhud\"], [\"rebuild\"], [\"type\"]\n"
"type: \"COLOR\", \"DEPTH\"\n"
- "Post on [\"reply\"] an event containing [\"ok\"]",
+ "Post on [\"reply\"] an event containing [\"result\"]",
&LLViewerWindowListener::saveSnapshot,
- saveSnapshotArgs);
+ llsd::map("filename", LLSD::String(), "reply", LLSD()));
add("requestReshape",
"Resize the window: [\"w\"], [\"h\"]",
&LLViewerWindowListener::requestReshape);
@@ -66,12 +57,15 @@ LLViewerWindowListener::LLViewerWindowListener(LLViewerWindow* llviewerwindow):
void LLViewerWindowListener::saveSnapshot(const LLSD& event) const
{
+ Response response(LLSD(), event);
+
typedef std::map<LLSD::String, LLSnapshotModel::ESnapshotLayerType> TypeMap;
TypeMap types;
#define tp(name) types[#name] = LLSnapshotModel::SNAPSHOT_TYPE_##name
tp(COLOR);
tp(DEPTH);
-#undef tp
+#undef tp
+
// Our add() call should ensure that the incoming LLSD does in fact
// contain our required arguments. Deal with the optional ones.
S32 width (mViewerWindow->getWindowWidthRaw());
@@ -94,14 +88,36 @@ void LLViewerWindowListener::saveSnapshot(const LLSD& event) const
TypeMap::const_iterator found = types.find(event["type"]);
if (found == types.end())
{
- LL_ERRS("LLViewerWindowListener") << "LLViewerWindowListener::saveSnapshot(): "
- << "unrecognized type " << event["type"] << LL_ENDL;
- return;
+ return response.error(stringize("Unrecognized type ", std::quoted(event["type"].asString()), " [\"COLOR\"] or [\"DEPTH\"] is expected."));
}
type = found->second;
}
- bool ok = mViewerWindow->saveSnapshot(event["filename"], width, height, showui, showhud, rebuild, type);
- sendReply(LLSDMap("ok", ok), event);
+
+ std::string filename(event["filename"]);
+ if (filename.empty())
+ {
+ return response.error(stringize("File path is empty."));
+ }
+
+ LLSnapshotModel::ESnapshotFormat format(LLSnapshotModel::SNAPSHOT_FORMAT_BMP);
+ std::string ext = gDirUtilp->getExtension(filename);
+ if (ext.empty())
+ {
+ filename.append(".bmp");
+ }
+ else if (ext == "png")
+ {
+ format = LLSnapshotModel::SNAPSHOT_FORMAT_PNG;
+ }
+ else if (ext == "jpeg" || ext == "jpg")
+ {
+ format = LLSnapshotModel::SNAPSHOT_FORMAT_JPEG;
+ }
+ else if (ext != "bmp")
+ {
+ return response.error(stringize("Unrecognized format. [\"png\"], [\"jpeg\"] or [\"bmp\"] is expected."));
+ }
+ response["result"] = mViewerWindow->saveSnapshot(filename, width, height, showui, showhud, rebuild, type, format);
}
void LLViewerWindowListener::requestReshape(LLSD const & event_data) const
diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp
index e5d0eda766..63bbac8b9c 100644
--- a/indra/newview/llvoavatar.cpp
+++ b/indra/newview/llvoavatar.cpp
@@ -114,6 +114,7 @@
#include "llskinningutil.h"
#include "llperfstats.h"
+#include "lleventapi.h"
#include <boost/lexical_cast.hpp>
@@ -11596,4 +11597,33 @@ F32 LLVOAvatar::getAverageGPURenderTime()
return ret;
}
+class LLVOAvatarListener : public LLEventAPI
+{
+ public:
+ LLVOAvatarListener() : LLEventAPI("LLVOAvatar", "LLVOAvatar listener to retrieve avatar info")
+ {
+ add("getSpeed",
+ "Return the avatar movement speed in the XY plane",
+ &LLVOAvatarListener::getSpeed,
+ LLSD().with("reply", LLSD()));
+ add("isInAir",
+ "Return the info whether avatar is in the air, and if so the time in the air",
+ &LLVOAvatarListener::isInAir,
+ LLSD().with("reply", LLSD()));
+ }
+
+ private:
+ void getSpeed(const LLSD &request) const
+ {
+ LLVector3 avatar_velocity = gAgentAvatarp->getCharacterVelocity() * gAgentAvatarp->getTimeDilation();
+ avatar_velocity.mV[VZ] = 0.f;
+ Response response(llsd::map("value", avatar_velocity.magVec()), request);
+ }
+ void isInAir(const LLSD &request) const
+ {
+ Response response(llsd::map("value", gAgentAvatarp->mInAir,
+ "duration", gAgentAvatarp->mInAir ? gAgentAvatarp->mTimeInAir.getElapsedTimeF32() : 0), request);
+ }
+};
+static LLVOAvatarListener VOAvatarListener;
diff --git a/indra/newview/llwearableitemslist.cpp b/indra/newview/llwearableitemslist.cpp
index 676164fcc6..e122cc0360 100644
--- a/indra/newview/llwearableitemslist.cpp
+++ b/indra/newview/llwearableitemslist.cpp
@@ -33,7 +33,6 @@
#include "llagentwearables.h"
#include "llappearancemgr.h"
-#include "llinventoryfunctions.h"
#include "llinventoryicon.h"
#include "llgesturemgr.h"
#include "lltransutil.h"
@@ -41,14 +40,6 @@
#include "llviewermenu.h"
#include "llvoavatarself.h"
-class LLFindOutfitItems : public LLInventoryCollectFunctor
-{
-public:
- LLFindOutfitItems() {}
- virtual ~LLFindOutfitItems() {}
- virtual bool operator()(LLInventoryCategory* cat,
- LLInventoryItem* item);
-};
bool LLFindOutfitItems::operator()(LLInventoryCategory* cat,
LLInventoryItem* item)
@@ -60,10 +51,10 @@ bool LLFindOutfitItems::operator()(LLInventoryCategory* cat,
|| (item->getType() == LLAssetType::AT_OBJECT)
|| (item->getType() == LLAssetType::AT_GESTURE))
{
- return TRUE;
+ return true;
}
}
- return FALSE;
+ return false;
}
//////////////////////////////////////////////////////////////////////////
diff --git a/indra/newview/llwearableitemslist.h b/indra/newview/llwearableitemslist.h
index 80e211ad6b..15033f5e9a 100644
--- a/indra/newview/llwearableitemslist.h
+++ b/indra/newview/llwearableitemslist.h
@@ -32,6 +32,7 @@
#include "llsingleton.h"
// newview
+#include "llinventoryfunctions.h"
#include "llinventoryitemslist.h"
#include "llinventorylistitem.h"
#include "lllistcontextmenu.h"
@@ -505,4 +506,11 @@ protected:
LLWearableType::EType mMenuWearableType;
};
+struct LLFindOutfitItems : public LLInventoryCollectFunctor
+{
+ LLFindOutfitItems() {}
+ virtual ~LLFindOutfitItems() {}
+ virtual bool operator()(LLInventoryCategory *cat, LLInventoryItem *item);
+};
+
#endif //LL_LLWEARABLEITEMSLIST_H
diff --git a/indra/newview/scripts/lua/luafloater_outfits_list.xml b/indra/newview/scripts/lua/luafloater_outfits_list.xml
new file mode 100644
index 0000000000..8cab864308
--- /dev/null
+++ b/indra/newview/scripts/lua/luafloater_outfits_list.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ legacy_header_height="18"
+ height="205"
+ layout="topleft"
+ name="lua_outfits"
+ title="Outfits"
+ width="325">
+ <scroll_list
+ draw_heading="false"
+ left="5"
+ width="315"
+ height="150"
+ top_pad ="25"
+ follows="all"
+ name="outfits_list">
+ <scroll_list.columns
+ name="outfit_name"
+ label="Name"/>
+ </scroll_list>
+ <button
+ follows="left|bottom"
+ height="23"
+ label="Replace COF"
+ layout="topleft"
+ name="replace_btn"
+ enabled="false"
+ top_pad="5"
+ width="95" >
+ </button>
+ <button
+ follows="left|bottom"
+ height="23"
+ label="Add to COF"
+ left_pad="7"
+ layout="topleft"
+ name="add_btn"
+ enabled="false"
+ width="95" >
+ </button>
+ <button
+ follows="left|bottom"
+ height="23"
+ label="Show wearables"
+ left_pad="7"
+ layout="topleft"
+ name="select_btn"
+ enabled="false"
+ width="111" >
+ </button>
+</floater>
diff --git a/indra/newview/scripts/lua/luafloater_speedometer.xml b/indra/newview/scripts/lua/luafloater_speedometer.xml
new file mode 100644
index 0000000000..54b99c7d48
--- /dev/null
+++ b/indra/newview/scripts/lua/luafloater_speedometer.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ legacy_header_height="18"
+ height="60"
+ layout="topleft"
+ name="lua_speedometer"
+ title="Speedometer"
+ can_minimize="false"
+ width="170">
+ <text
+ type="string"
+ follows="left|top"
+ font="SansSerifBold"
+ font.size="Huge"
+ text_color="White"
+ layout="topleft"
+ top="25"
+ left_delta="30"
+ width="70"
+ height="30"
+ name="speed_lbl"/>
+ <text
+ type="string"
+ follows="left|top"
+ width="55"
+ height="30"
+ font="SansSerifBold"
+ font.size="Huge"
+ text_color="White"
+ layout="topleft"
+ left_pad="2"
+ name="mps_lbl">
+ m/s
+ </text>
+</floater>
diff --git a/indra/newview/scripts/lua/popup.lua b/indra/newview/scripts/lua/popup.lua
deleted file mode 100644
index 8a01ab7836..0000000000
--- a/indra/newview/scripts/lua/popup.lua
+++ /dev/null
@@ -1,32 +0,0 @@
-local leap = require 'leap'
-
--- notification is any name defined in notifications.xml as
--- <notification name=>
--- vars is a table providing values for [VAR] substitution keys in the
--- notification body.
-local popup_meta = {
- -- setting this function as getmetatable(popup).__call() means this gets
- -- called when a consumer calls popup(notification, vars, payload)
- __call = function(self, notification, vars, payload)
- return leap.request('LLNotifications',
- {op='requestAdd', name=notification,
- substitutions=vars,
- payload=payload})
- end
-}
-
-local popup = setmetatable({}, popup_meta)
-
-function popup:alert(message)
- return self('GenericAlert', {MESSAGE=message})
-end
-
-function popup:alertOK(message)
- return self('GenericAlertOK', {MESSAGE=message})
-end
-
-function popup:alertYesCancel(message)
- return self('GenericAlertYesCancel', {MESSAGE=message})
-end
-
-return popup
diff --git a/indra/newview/scripts/lua/require/LLAppearance.lua b/indra/newview/scripts/lua/require/LLAppearance.lua
new file mode 100644
index 0000000000..f533d22daf
--- /dev/null
+++ b/indra/newview/scripts/lua/require/LLAppearance.lua
@@ -0,0 +1,31 @@
+local leap = require 'leap'
+
+local LLAppearance = {}
+
+function LLAppearance.wearOutfit(folder, action)
+ action = action or 'add'
+ leap.request('LLAppearance', {op='wearOutfit', append = (action == 'add'), folder_id=folder})
+end
+
+function LLAppearance.wearOutfitByName(folder, action)
+ action = action or 'add'
+ leap.request('LLAppearance', {op='wearOutfit', append = (action == 'add'), folder_name=folder})
+end
+
+function LLAppearance.wearItems(items_id, replace)
+ leap.send('LLAppearance', {op='wearItems', replace = replace, items_id=items_id})
+end
+
+function LLAppearance.detachItems(items_id)
+ leap.send('LLAppearance', {op='detachItems', items_id=items_id})
+end
+
+function LLAppearance.getOutfitsList()
+ return leap.request('LLAppearance', {op='getOutfitsList'})['outfits']
+end
+
+function LLAppearance.getOutfitItems(id)
+ return leap.request('LLAppearance', {op='getOutfitItems', outfit_id = id})['items']
+end
+
+return LLAppearance
diff --git a/indra/newview/scripts/lua/require/LLChatListener.lua b/indra/newview/scripts/lua/require/LLChatListener.lua
new file mode 100644
index 0000000000..428dca881e
--- /dev/null
+++ b/indra/newview/scripts/lua/require/LLChatListener.lua
@@ -0,0 +1,48 @@
+local fiber = require 'fiber'
+local inspect = require 'inspect'
+local leap = require 'leap'
+local util = require 'util'
+
+local LLChatListener = {}
+local waitfor = {}
+local listener_name = {}
+
+function LLChatListener:new()
+ local obj = setmetatable({}, self)
+ self.__index = self
+ obj.name = 'Chat_listener'
+
+ return obj
+end
+
+util.classctor(LLChatListener)
+
+function LLChatListener:handleMessages(event_data)
+ print(inspect(event_data))
+ return true
+end
+
+function LLChatListener:start()
+ waitfor = leap.WaitFor:new(-1, self.name)
+ function waitfor:filter(pump, data)
+ if pump == "LLNearbyChat" then
+ return data
+ end
+ end
+
+ fiber.launch(self.name, function()
+ event = waitfor:wait()
+ while event and self:handleMessages(event) do
+ event = waitfor:wait()
+ end
+ end)
+
+ listener_name = leap.request(leap.cmdpump(), {op='listen', source='LLNearbyChat', listener="ChatListener", tweak=true}).listener
+end
+
+function LLChatListener:stop()
+ leap.send(leap.cmdpump(), {op='stoplistening', source='LLNearbyChat', listener=listener_name})
+ waitfor:close()
+end
+
+return LLChatListener
diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua
index eb1a4017c7..1eee4657f4 100644
--- a/indra/newview/scripts/lua/require/UI.lua
+++ b/indra/newview/scripts/lua/require/UI.lua
@@ -122,4 +122,17 @@ function UI.type(...)
end
end
+-- ***************************************************************************
+-- Snapshot
+-- ***************************************************************************
+-- UI.snapshot{filename=filename -- extension may be specified: bmp, jpeg, png
+-- [, type='COLOR' | 'DEPTH']
+-- [, width=width][, height=height] -- uses current window size if not specified
+-- [, showui=true][, showhud=true]
+-- [, rebuild=false]}
+function UI.snapshot(...)
+ local args = mapargs('filename,width,height,showui,showhud,rebuild,type', ...)
+ args.op = 'saveSnapshot'
+ return leap.request('LLViewerWindow', args).result
+end
return UI
diff --git a/indra/newview/scripts/lua/login.lua b/indra/newview/scripts/lua/require/login.lua
index 0d8591cace..0d8591cace 100644
--- a/indra/newview/scripts/lua/login.lua
+++ b/indra/newview/scripts/lua/require/login.lua
diff --git a/indra/newview/scripts/lua/require/logout.lua b/indra/newview/scripts/lua/require/logout.lua
new file mode 100644
index 0000000000..63dcd7f01f
--- /dev/null
+++ b/indra/newview/scripts/lua/require/logout.lua
@@ -0,0 +1,7 @@
+local leap = require 'leap'
+
+local function logout()
+ leap.send('LLAppViewer', {op='userQuit'});
+end
+
+return logout
diff --git a/indra/newview/scripts/lua/mapargs.lua b/indra/newview/scripts/lua/require/mapargs.lua
index 45f5a9c556..45f5a9c556 100644
--- a/indra/newview/scripts/lua/mapargs.lua
+++ b/indra/newview/scripts/lua/require/mapargs.lua
diff --git a/indra/newview/scripts/lua/require/popup.lua b/indra/newview/scripts/lua/require/popup.lua
new file mode 100644
index 0000000000..3aaadf85ba
--- /dev/null
+++ b/indra/newview/scripts/lua/require/popup.lua
@@ -0,0 +1,53 @@
+local leap = require 'leap'
+local mapargs = require 'mapargs'
+
+-- notification is any name defined in notifications.xml as
+-- <notification name=>
+-- vars is a table providing values for [VAR] substitution keys in the
+-- notification body
+-- payload prepopulates the response table
+-- wait=false means fire and forget, otherwise wait for user response
+local popup_meta = {
+ -- setting this function as getmetatable(popup).__call() means this gets
+ -- called when a consumer calls popup(notification, vars, payload)
+ __call = function(self, ...)
+ local args = mapargs('notification,vars,payload,wait', ...)
+ -- we use convenience argument names different from 'LLNotifications'
+ -- listener
+ args.name = args.notification
+ args.notification = nil
+ args.substitutions = args.vars
+ args.vars = nil
+ local wait = args.wait
+ args.wait = nil
+ args.op = 'requestAdd'
+ -- Specifically test (wait == false), NOT (not wait), because we treat
+ -- nil (omitted, default true) differently than false (explicitly
+ -- DON'T wait).
+ if wait == false then
+ leap.send('LLNotifications', args)
+ else
+ return leap.request('LLNotifications', args).response
+ end
+ end
+}
+
+local popup = setmetatable({}, popup_meta)
+
+function popup:alert(message)
+ return self('GenericAlert', {MESSAGE=message})
+end
+
+function popup:alertOK(message)
+ return self('GenericAlertOK', {MESSAGE=message})
+end
+
+function popup:alertYesCancel(message)
+ return self('GenericAlertYesCancel', {MESSAGE=message})
+end
+
+function popup:tip(message)
+ self{'SystemMessageTip', {MESSAGE=message}, wait=false}
+end
+
+return popup
diff --git a/indra/newview/scripts/lua/test_LLAppearance.lua b/indra/newview/scripts/lua/test_LLAppearance.lua
new file mode 100644
index 0000000000..5ddd9f15ff
--- /dev/null
+++ b/indra/newview/scripts/lua/test_LLAppearance.lua
@@ -0,0 +1,114 @@
+local Floater = require 'Floater'
+local LLAppearance = require 'LLAppearance'
+local startup = require 'startup'
+local inspect = require 'inspect'
+
+local SHOW_OUTFITS = true
+local SELECTED_OUTFIT_ID = {}
+local DATA_MAP = {}
+
+local wearables_lbl = 'Show wearables'
+local outfits_lbl = 'Show outfits'
+local replace_cof_lbl = 'Replace COF'
+local add_cof_lbl = 'Add to COF'
+local outfits_title = 'Outfits'
+local wear_lbl = 'Wear item'
+local detach_lbl = 'Detach item'
+
+local flt = Floater:new(
+ "luafloater_outfits_list.xml",
+ {outfits_list = {"double_click"}})
+
+function get_selected_id()
+ return flt:request({action="get_selected_id", ctrl_name='outfits_list'}).value
+end
+
+function populate_list()
+ if SHOW_OUTFITS then
+ DATA_MAP = LLAppearance.getOutfitsList()
+ else
+ DATA_MAP = LLAppearance.getOutfitItems(SELECTED_OUTFIT_ID)
+ end
+
+ local action_data = {}
+ action_data.action = "add_list_element"
+ action_data.ctrl_name = "outfits_list"
+ local outfits = {}
+ for uuid, info in pairs(DATA_MAP) do
+ name = {}
+ if SHOW_OUTFITS then
+ name = info
+ else
+ name = info.name
+ end
+ table.insert(outfits, {value = uuid, columns={column = "outfit_name", value = name}})
+ end
+ action_data.value = outfits
+ flt:post(action_data)
+end
+
+function set_label(btn_name, value)
+ flt:post({action="set_label", ctrl_name=btn_name, value=value})
+end
+
+function set_enabled(btn_name, value)
+ flt:post({action="set_enabled", ctrl_name=btn_name, value=value})
+end
+
+function update_labels()
+ if SHOW_OUTFITS then
+ set_label('select_btn', wearables_lbl)
+ set_label('replace_btn', replace_cof_lbl)
+ set_label('add_btn', add_cof_lbl)
+
+ set_enabled('select_btn', false)
+ flt:post({action="set_title", value=outfits_title})
+ else
+ set_label('select_btn', outfits_lbl)
+ set_label('replace_btn', wear_lbl)
+ set_label('add_btn', detach_lbl)
+
+ set_enabled('select_btn', true)
+ flt:post({action="set_title", value=DATA_MAP[SELECTED_OUTFIT_ID]})
+ end
+
+ set_enabled('replace_btn', false)
+ set_enabled('add_btn', false)
+end
+
+function flt:post_build(event_data)
+ populate_list()
+end
+
+function flt:commit_replace_btn(event_data)
+ if SHOW_OUTFITS then
+ LLAppearance.wearOutfit(get_selected_id(), 'replace')
+ else
+ LLAppearance.wearItems(get_selected_id(), false)
+ end
+end
+
+function flt:commit_add_btn(event_data)
+ if SHOW_OUTFITS then
+ LLAppearance.wearOutfit(get_selected_id(), 'add')
+ else
+ LLAppearance.detachItems(get_selected_id())
+ end
+end
+
+function flt:commit_select_btn(event_data)
+ SHOW_OUTFITS = not SHOW_OUTFITS
+ SELECTED_OUTFIT_ID = get_selected_id()
+ update_labels()
+ self:post({action="clear_list", ctrl_name='outfits_list'})
+ populate_list()
+end
+
+function flt:commit_outfits_list(event_data)
+ set_enabled('replace_btn', true)
+ set_enabled('add_btn', true)
+ set_enabled('select_btn', true)
+end
+
+startup.wait('STATE_STARTED')
+flt:show()
diff --git a/indra/newview/scripts/lua/test_LLChatListener.lua b/indra/newview/scripts/lua/test_LLChatListener.lua
new file mode 100644
index 0000000000..18363ed43b
--- /dev/null
+++ b/indra/newview/scripts/lua/test_LLChatListener.lua
@@ -0,0 +1,28 @@
+local LLChatListener = require 'LLChatListener'
+local LLChat = require 'LLChat'
+local leap = require 'leap'
+
+function openOrEcho(message)
+ local floater_name = string.match(message, "^open%s+(%w+)")
+ if floater_name then
+ leap.send("LLFloaterReg", {name = floater_name, op = "showInstance"})
+ else
+ LLChat.sendNearby('Echo: ' .. message)
+ end
+end
+
+local listener = LLChatListener()
+
+function listener:handleMessages(event_data)
+ if string.find(event_data.message, '[LUA]') then
+ return true
+ elseif event_data.message == 'stop' then
+ LLChat.sendNearby('Closing echo script.')
+ return false
+ else
+ openOrEcho(event_data.message)
+ end
+ return true
+end
+
+listener:start()
diff --git a/indra/newview/scripts/lua/test_atexit.lua b/indra/newview/scripts/lua/test_atexit.lua
new file mode 100644
index 0000000000..6fbc0f3eb1
--- /dev/null
+++ b/indra/newview/scripts/lua/test_atexit.lua
@@ -0,0 +1,3 @@
+LL.atexit(function() print('Third') end)
+LL.atexit(function() print('Second') end)
+LL.atexit(function() print('First') end)
diff --git a/indra/newview/scripts/lua/test_login.lua b/indra/newview/scripts/lua/test_login.lua
index 6df52b08c2..a8c31807bc 100644
--- a/indra/newview/scripts/lua/test_login.lua
+++ b/indra/newview/scripts/lua/test_login.lua
@@ -3,5 +3,5 @@ login = require 'login'
startup.wait('STATE_LOGIN_WAIT')
login()
--- WIP: not working as of 2024-06-11
+-- Fill in valid credentials as they would be entered on the login screen
-- login('My Username', 'password')
diff --git a/indra/newview/scripts/lua/test_logout.lua b/indra/newview/scripts/lua/test_logout.lua
new file mode 100644
index 0000000000..b1ac59e38c
--- /dev/null
+++ b/indra/newview/scripts/lua/test_logout.lua
@@ -0,0 +1,3 @@
+logout = require 'logout'
+
+logout()
diff --git a/indra/newview/scripts/lua/test_luafloater_demo.lua b/indra/newview/scripts/lua/test_luafloater_demo.lua
deleted file mode 100644
index 65a31670c8..0000000000
--- a/indra/newview/scripts/lua/test_luafloater_demo.lua
+++ /dev/null
@@ -1,77 +0,0 @@
-XML_FILE_PATH = LL.abspath("luafloater_demo.xml")
-
-scriptparts = string.split(LL.source_path(), '/')
-scriptname = scriptparts[#scriptparts]
-print('Running ' .. scriptname)
-
-leap = require 'leap'
-fiber = require 'fiber'
-startup = require 'startup'
-
---event pump for sending actions to the floater
-local COMMAND_PUMP_NAME = ""
-local reqid
---table of floater UI events
-event_list=leap.request("LLFloaterReg", {op="getFloaterEvents"}).events
-
-local function _event(event_name)
- if not table.find(event_list, event_name) then
- LL.print_warning("Incorrect event name: " .. event_name)
- end
- return event_name
-end
-
-function post(action)
- leap.send(COMMAND_PUMP_NAME, action)
-end
-
-function getCurrentTime()
- local currentTime = os.date("*t")
- return string.format("%02d:%02d:%02d", currentTime.hour, currentTime.min, currentTime.sec)
-end
-
-function handleEvents(event_data)
- post({action="add_text", ctrl_name="events_editor", value = event_data})
- if event_data.event == _event("commit") then
- if event_data.ctrl_name == "disable_ctrl" then
- post({action="set_enabled", ctrl_name="open_btn", value = (1 - event_data.value)})
- elseif event_data.ctrl_name == "title_cmb" then
- post({action="set_title", value= event_data.value})
- elseif event_data.ctrl_name == "open_btn" then
- floater_name = leap.request(COMMAND_PUMP_NAME, {action="get_value", ctrl_name='openfloater_cmd'})['value']
- leap.send("LLFloaterReg", {name = floater_name, op = "showInstance"})
- end
- elseif event_data.event == _event("double_click") then
- if event_data.ctrl_name == "show_time_lbl" then
- post({action="set_value", ctrl_name="time_lbl", value= getCurrentTime()})
- end
- elseif event_data.event == _event("floater_close") then
- LL.print_warning("Floater was closed")
- return false
- end
- return true
-end
-
-startup.wait('STATE_LOGIN_WAIT')
-local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"}
---sign for additional events for defined control {<control_name>= {action1, action2, ...}}
-key.extra_events={show_time_lbl = {_event("right_mouse_down"), _event("double_click")}}
-local resp = leap.request("LLFloaterReg", key)
-COMMAND_PUMP_NAME = resp.command_name
-reqid = resp.reqid
-
-catch_events = leap.WaitFor(-1, "all_events")
-function catch_events:filter(pump, data)
- if data.reqid == reqid then
- return data
- end
-end
-
-function process_events(waitfor)
- event_data = waitfor:wait()
- while event_data and handleEvents(event_data) do
- event_data = waitfor:wait()
- end
-end
-
-fiber.launch("catch_events", process_events, catch_events)
diff --git a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua b/indra/newview/scripts/lua/test_luafloater_gesture_list.lua
deleted file mode 100644
index a5fd325430..0000000000
--- a/indra/newview/scripts/lua/test_luafloater_gesture_list.lua
+++ /dev/null
@@ -1,75 +0,0 @@
-XML_FILE_PATH = LL.abspath("luafloater_gesture_list.xml")
-
-scriptparts = string.split(LL.source_path(), '/')
-scriptname = scriptparts[#scriptparts]
-print('Running ' .. scriptname)
-
-leap = require 'leap'
-fiber = require 'fiber'
-LLGesture = require 'LLGesture'
-startup = require 'startup'
-
---event pump for sending actions to the floater
-local COMMAND_PUMP_NAME = ""
-local reqid
---table of floater UI events
-event_list=leap.request("LLFloaterReg", {op="getFloaterEvents"}).events
-
-local function _event(event_name)
- if not table.find(event_list, event_name) then
- LL.print_warning("Incorrect event name: " .. event_name)
- end
- return event_name
-end
-
-function post(action)
- leap.send(COMMAND_PUMP_NAME, action)
-end
-
-function handleEvents(event_data)
- if event_data.event == _event("floater_close") then
- return false
- end
-
- if event_data.event == _event("post_build") then
- COMMAND_PUMP_NAME = event_data.command_name
- reqid = event_data.reqid
- gestures_uuid = LLGesture.getActiveGestures()
- local action_data = {}
- action_data.action = "add_list_element"
- action_data.ctrl_name = "gesture_list"
- gestures = {}
- for uuid, info in pairs(gestures_uuid) do
- table.insert(gestures, {value = uuid, columns ={column = "gesture_name", value = info.name}})
- end
- action_data.value = gestures
- post(action_data)
- elseif event_data.event == _event("double_click") then
- if event_data.ctrl_name == "gesture_list" then
- LLGesture.startGesture(event_data.value)
- end
- end
- return true
-end
-
-startup.wait('STATE_STARTED')
-local key = {xml_path = XML_FILE_PATH, op = "showLuaFloater"}
---receive additional events for defined control {<control_name>= {action1, action2, ...}}
-key.extra_events={gesture_list = {_event("double_click")}}
-handleEvents(leap.request("LLFloaterReg", key))
-
-catch_events = leap.WaitFor(-1, "all_events")
-function catch_events:filter(pump, data)
- if data.reqid == reqid then
- return data
- end
-end
-
-function process_events(waitfor)
- event_data = waitfor:wait()
- while event_data and handleEvents(event_data) do
- event_data = waitfor:wait()
- end
-end
-
-fiber.launch("catch_events", process_events, catch_events)
diff --git a/indra/newview/scripts/lua/test_luafloater_speedometer.lua b/indra/newview/scripts/lua/test_luafloater_speedometer.lua
new file mode 100644
index 0000000000..af7189a2cb
--- /dev/null
+++ b/indra/newview/scripts/lua/test_luafloater_speedometer.lua
@@ -0,0 +1,31 @@
+local Floater = require 'Floater'
+local leap = require 'leap'
+local popup = require 'popup'
+local startup = require 'startup'
+local Timer = (require 'timers').Timer
+local max_speed = 0
+local flt = Floater("luafloater_speedometer.xml")
+startup.wait('STATE_STARTED')
+
+local timer
+
+function flt:floater_close(event_data)
+ if timer then
+ timer:cancel()
+ end
+ popup:tip(string.format("Registered max speed: %.2f m/s", max_speed))
+end
+
+local function idle(event_data)
+ local speed = leap.request('LLVOAvatar', {op='getSpeed'})['value']
+ flt:post({action="set_value", ctrl_name="speed_lbl", value = string.format("%.2f", speed)})
+ max_speed=math.max(max_speed, speed)
+end
+
+msg = 'Are you sure you want to run this "speedometer" script?'
+response = popup:alertYesCancel(msg)
+
+if response.OK_okcancelbuttons then
+ flt:show()
+ timer = Timer(1, idle, true) -- iterate
+end
diff --git a/indra/newview/scripts/lua/test_popup.lua b/indra/newview/scripts/lua/test_popup.lua
new file mode 100644
index 0000000000..e48f89c3a7
--- /dev/null
+++ b/indra/newview/scripts/lua/test_popup.lua
@@ -0,0 +1,6 @@
+popup = require 'popup'
+
+response = popup:alert('This just has a Close button')
+response = popup:alertOK(string.format('You said "%s", is that OK?', next(response)))
+response = popup:alertYesCancel(string.format('You said "%s"', next(response)))
+popup:tip(string.format('You said "%s"', next(response)))
diff --git a/indra/newview/scripts/lua/test_snapshot.lua b/indra/newview/scripts/lua/test_snapshot.lua
new file mode 100644
index 0000000000..d7c878833b
--- /dev/null
+++ b/indra/newview/scripts/lua/test_snapshot.lua
@@ -0,0 +1,15 @@
+local UI = require 'UI'
+
+PATH = 'E:\\'
+-- 'png', 'jpeg' or 'bmp'
+EXT = '.png'
+
+NAME_SIMPLE = 'Snapshot_simple' .. '_' .. os.date("%Y-%m-%d_%H-%M-%S")
+UI.snapshot(PATH .. NAME_SIMPLE .. EXT)
+
+NAME_ARGS = 'Snapshot_args' .. '_' .. os.date("%Y-%m-%d_%H-%M-%S")
+
+-- 'COLOR' or 'DEPTH'
+TYPE = 'COLOR'
+UI.snapshot{PATH .. NAME_ARGS .. EXT, width = 700, height = 400,
+ type = TYPE, showui = false, showhud = false}
diff --git a/indra/newview/skins/default/xui/en/floater_lua_debug.xml b/indra/newview/skins/default/xui/en/floater_lua_debug.xml
index 012ea6f254..15027f1647 100644
--- a/indra/newview/skins/default/xui/en/floater_lua_debug.xml
+++ b/indra/newview/skins/default/xui/en/floater_lua_debug.xml
@@ -25,15 +25,6 @@
width="100">
LUA string:
</text>
- <check_box
- follows="right|top"
- height="15"
- label="Use clean lua_State"
- layout="topleft"
- top="10"
- right ="-70"
- name="clean_lua_state"
- width="70"/>
<line_editor
border_style="line"
border_thickness="1"
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index 76a2660dbb..492b29fbc7 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -4028,6 +4028,10 @@ Please check http://status.secondlifegrid.net to see if there is a known problem
<string name="DeleteItem">Delete selected item?</string>
<string name="EmptyOutfitText">There are no items in this outfit</string>
+ <string name="OutfitNotFound" value="Couldn't find outfit "/>
+ <string name="OutfitNotAdded" value="Can't add to COF outfit "/>
+ <string name="OutfitNotReplaced" value="Can't replace COF with outfit "/>
+ <string name="SystemFolderNotWorn" value="Can't wear system folder "/>
<!-- External editor status codes -->
<string name="ExternalEditorNotSet">Select an editor by setting the environment variable LL_SCRIPT_EDITOR or the ExternalEditor setting.
diff --git a/indra/newview/tests/llluamanager_test.cpp b/indra/newview/tests/llluamanager_test.cpp
index 206dbd9e71..26a4ac95e3 100644
--- a/indra/newview/tests/llluamanager_test.cpp
+++ b/indra/newview/tests/llluamanager_test.cpp
@@ -21,6 +21,7 @@
#include "../llcommon/tests/StringVec.h"
#include "../test/lltut.h"
#include "llapp.h"
+#include "llcontrol.h"
#include "lldate.h"
#include "llevents.h"
#include "lleventcoro.h"
@@ -39,6 +40,8 @@ public:
bool frame() override { return true; }
};
+LLControlGroup gSavedSettings("Global");
+
template <typename CALLABLE>
auto listener(CALLABLE&& callable)
{
@@ -57,6 +60,36 @@ namespace tut
{
struct llluamanager_data
{
+ llluamanager_data()
+ {
+ // Load gSavedSettings from source tree
+ // indra/newview/tests/llluamanager_test.cpp =>
+ // indra/newview
+ auto newview{ fsyspath(__FILE__).parent_path().parent_path() };
+ auto settings{ newview / "app_settings" / "settings.xml" };
+ // true suppresses implicit declare; implicit declare requires
+ // that every variable in settings.xml has a Comment, which many don't.
+ gSavedSettings.loadFromFile(settings.u8string(), true);
+ // At test time, since we don't have the app bundle available,
+ // extend LuaRequirePath to include the require directory in the
+ // source tree.
+ auto require{ (newview / "scripts" / "lua" / "require").u8string() };
+ auto paths{ gSavedSettings.getLLSD("LuaRequirePath") };
+ bool found = false;
+ for (const auto& path : llsd::inArray(paths))
+ {
+ if (path.asString() == require)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (! found)
+ {
+ paths.append(require);
+ gSavedSettings.setLLSD("LuaRequirePath", paths);
+ }
+ }
// 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.
@@ -90,11 +123,10 @@ namespace tut
void object::test<1>()
{
set_test_name("test Lua results");
- LuaState L;
for (auto& luax : lua_expressions)
{
auto [count, result] =
- LLLUAmanager::waitScriptLine(L, "return " + luax.expr);
+ LLLUAmanager::waitScriptLine("return " + luax.expr);
auto desc{ stringize("waitScriptLine(", luax.desc, "): ") };
// if count < 0, report Lua error message
ensure_equals(desc + result.asString(), count, 1);
@@ -111,8 +143,7 @@ namespace tut
"data = ", construct, "\n"
"LL.post_on('testpump', data)\n"
));
- LuaState L;
- auto [count, result] = LLLUAmanager::waitScriptLine(L, lua);
+ auto [count, result] = LLLUAmanager::waitScriptLine(lua);
// We woke up again ourselves because the coroutine running Lua has
// finished. But our Lua chunk didn't actually return anything, so we
// expect count to be 0 and result to be undefined.
@@ -149,11 +180,10 @@ namespace tut
"LL.post_on('testpump', data)\n"
"LL.post_on('testpump', 'exit')\n"
);
- LuaState L;
// It's important to let the startScriptLine() coroutine run
// concurrently with ours until we've had a chance to post() our
// reply.
- auto future = LLLUAmanager::startScriptLine(L, lua);
+ auto future = LLLUAmanager::startScriptLine(lua);
StringVec expected{
"entry",
"get_event_pumps()",
@@ -184,8 +214,7 @@ namespace tut
"pump, data = LL.get_event_next()\n"
"return data\n"
);
- LuaState L;
- auto future = LLLUAmanager::startScriptLine(L, lua);
+ auto future = LLLUAmanager::startScriptLine(lua);
// We woke up again ourselves because the coroutine running Lua has
// reached the get_event_next() call, which suspends the calling C++
// coroutine (including the Lua code running on it) until we post
@@ -323,8 +352,7 @@ namespace tut
sendReply(data, data);
}));
- LuaState L;
- auto [count, result] = LLLUAmanager::waitScriptLine(L, lua);
+ auto [count, result] = LLLUAmanager::waitScriptLine(lua);
ensure_equals("Lua script didn't return item", count, 1);
ensure_equals("echo failed", result, llsd::map("a", "a", "b", "b"));
}
@@ -338,18 +366,18 @@ namespace tut
"\n"
"fiber = require('fiber')\n"
"leap = require('leap')\n"
- "-- debug = require('printf')\n"
"local function debug(...) end\n"
+ "-- debug = require('printf')\n"
"\n"
"-- negative priority ensures catchall is always last\n"
- "catchall = leap.WaitFor:new(-1, 'catchall')\n"
+ "catchall = leap.WaitFor(-1, 'catchall')\n"
"function catchall:filter(pump, data)\n"
" debug('catchall:filter(%s, %s)', pump, data)\n"
" return data\n"
"end\n"
"\n"
"-- but first, catch events with 'special' key\n"
- "catch_special = leap.WaitFor:new(2, 'catch_special')\n"
+ "catch_special = leap.WaitFor(2, 'catch_special')\n"
"function catch_special:filter(pump, data)\n"
" debug('catch_special:filter(%s, %s)', pump, data)\n"
" return if data['special'] ~= nil then data else nil\n"
@@ -402,10 +430,7 @@ namespace tut
requests.append(data);
}));
- LuaState L;
- auto future = LLLUAmanager::startScriptLine(L, lua);
- auto replyname{ L.obtainListener().getReplyName() };
- auto& replypump{ LLEventPumps::instance().obtain(replyname) };
+ auto future = LLLUAmanager::startScriptLine(lua);
// LuaState::expr() periodically interrupts a running chunk to ensure
// the rest of our coroutines get cycles. Nonetheless, for this test
// we have to wait until both requester() coroutines have posted and
@@ -417,6 +442,8 @@ namespace tut
llcoro::suspend();
}
ensure_equals("didn't get both requests", requests.size(), 2);
+ auto replyname{ requests[0]["reply"].asString() };
+ auto& replypump{ LLEventPumps::instance().obtain(replyname) };
// moreover, we expect they arrived in the order they were created
ensure_equals("a wasn't first", requests[0]["name"].asString(), "a");
ensure_equals("b wasn't second", requests[1]["name"].asString(), "b");
@@ -441,8 +468,7 @@ namespace tut
"\n"
"LL.get_event_next()\n"
);
- LuaState L;
- auto future = LLLUAmanager::startScriptLine(L, lua);
+ auto future = LLLUAmanager::startScriptLine(lua);
// Poke LLTestApp to send its preliminary shutdown message.
mApp.setQuitting();
// but now we have to give the startScriptLine() coroutine a chance to run
@@ -464,11 +490,45 @@ namespace tut
" x = 1\n"
"end\n"
);
- LuaState L;
- auto [count, result] = LLLUAmanager::waitScriptLine(L, lua);
+ auto [count, result] = LLLUAmanager::waitScriptLine(lua);
// We expect the above erroneous script has been forcibly terminated
// because it ran too long without doing any actual work.
ensure_equals(desc + " count: " + result.asString(), count, -1);
ensure_contains(desc + " result", result.asString(), "terminated");
}
+
+ template <typename T>
+ struct Visible
+ {
+ Visible(T name): name(name)
+ {
+ LL_INFOS() << "Visible<" << LLError::Log::classname<T>() << ">('" << name << "')" << LL_ENDL;
+ }
+ Visible(const Visible&) = delete;
+ Visible& operator=(const Visible&) = delete;
+ ~Visible()
+ {
+ LL_INFOS() << "~Visible<" << LLError::Log::classname<T>() << ">('" << name << "')" << LL_ENDL;
+ }
+ T name;
+ };
+
+ template<> template<>
+ void object::test<9>()
+ {
+ set_test_name("track distinct lua_emplace<T>() types");
+ LuaState L;
+ lua_emplace<Visible<std::string>>(L, "std::string 0");
+ int st0tag = lua_userdatatag(L, -1);
+ lua_emplace<Visible<const char*>>(L, "const char* 0");
+ int cp0tag = lua_userdatatag(L, -1);
+ lua_emplace<Visible<std::string>>(L, "std::string 1");
+ int st1tag = lua_userdatatag(L, -1);
+ lua_emplace<Visible<const char*>>(L, "const char* 1");
+ int cp1tag = lua_userdatatag(L, -1);
+ lua_settop(L, 0);
+ ensure_equals("lua_emplace<std::string>() tags diverge", st0tag, st1tag);
+ ensure_equals("lua_emplace<const char*>() tags diverge", cp0tag, cp1tag);
+ ensure_not_equals("lua_emplace<>() tags collide", st0tag, cp0tag);
+ }
} // namespace tut
diff --git a/indra/test/test.cpp b/indra/test/test.cpp
index 61a4eb07c5..0e863d8084 100644
--- a/indra/test/test.cpp
+++ b/indra/test/test.cpp
@@ -35,13 +35,14 @@
*/
#include "linden_common.h"
-#include "llerrorcontrol.h"
-#include "lltut.h"
#include "chained_callback.h"
-#include "stringize.h"
-#include "namedtempfile.h"
+#include "fsyspath.h"
+#include "llerrorcontrol.h"
#include "lltrace.h"
#include "lltracethreadrecorder.h"
+#include "lltut.h"
+#include "namedtempfile.h"
+#include "stringize.h"
#include "apr_pools.h"
#include "apr_getopt.h"
@@ -545,6 +546,29 @@ int main(int argc, char **argv)
// LOGTEST overrides default, but can be overridden by --debug.
const char* LOGTEST = getenv("LOGTEST");
+ // Sometimes we must rebuild much of the viewer before we get to the
+ // specific test we want to monitor, and some viewer integration tests are
+ // quite verbose. In addition to noticing plain LOGTEST= (for all tests),
+ // also notice LOGTEST_progname= (for a specific test).
+ // (Why doesn't MSVC notice fsyspath::operator std::string()?
+ // Why must we explicitly call fsyspath::string()?)
+ std::string basename(fsyspath(argv[0]).stem().string());
+ // don't make user set LOGTEST_INTEGRATION_TEST_progname or (worse)
+ // LOGTEST_PROJECT_foo_TEST_bar -- only LOGTEST_progname or LOGTEST_bar
+ auto _TEST_ = basename.find("_TEST_");
+ if (_TEST_ != std::string::npos)
+ {
+ basename.erase(0, _TEST_+6);
+ }
+ std::string LOGTEST_prog_key("LOGTEST_" + basename);
+ const char* LOGTEST_prog = getenv(LOGTEST_prog_key.c_str());
+// std::cout << LOGTEST_prog_key << "='" << (LOGTEST_prog? LOGTEST_prog : "") << "'" << std::endl;
+ if (LOGTEST_prog && *LOGTEST_prog)
+ {
+ LOGTEST = LOGTEST_prog;
+ std::cout << "LOGTEST='" << LOGTEST << "' from " << LOGTEST_prog_key << std::endl;
+ }
+
// values used for options parsing
apr_status_t apr_err;
const char* opt_arg = NULL;