summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMaxim Nikolenko <maximnproductengine@lindenlab.com>2024-08-02 18:47:44 +0300
committerGitHub <noreply@github.com>2024-08-02 18:47:44 +0300
commitd6abce3968925c5cb58c11f1c6fc936605f55c57 (patch)
tree16142170d61f2ecf0ad8a4d6194a152658527589
parent2390b0d623a09c0ec4f46fa10567bb02b7d49cfe (diff)
parent365030aa33d991006e093557cb7ce93d18ea2e4e (diff)
Merge pull request #2047 from secondlife/lua-top-menu
Lua api for adding new menu items to the Top menu
-rw-r--r--indra/llcommon/lua_function.cpp17
-rw-r--r--indra/llcommon/lua_function.h162
-rw-r--r--indra/newview/lluilistener.cpp162
-rw-r--r--indra/newview/lluilistener.h13
-rw-r--r--indra/newview/scripts/lua/require/UI.lua34
-rw-r--r--indra/newview/scripts/lua/test_callables.lua6
-rw-r--r--indra/newview/scripts/lua/test_top_menu.lua34
7 files changed, 412 insertions, 16 deletions
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp
index 42bba80ed5..b173d17ede 100644
--- a/indra/llcommon/lua_function.cpp
+++ b/indra/llcommon/lua_function.cpp
@@ -32,6 +32,8 @@
#include "lualistener.h"
#include "stringize.h"
+using namespace std::literals; // e.g. std::string_view literals: "this"sv
+
const S32 INTERRUPTS_MAX_LIMIT = 20000;
const S32 INTERRUPTS_SUSPEND_LIMIT = 100;
@@ -41,7 +43,7 @@ const S32 INTERRUPTS_SUSPEND_LIMIT = 100;
int DistinctInt::mValues{0};
/*****************************************************************************
-* luau namespace
+* lluau namespace
*****************************************************************************/
namespace
{
@@ -93,21 +95,14 @@ fsyspath lluau::source_path(lua_State* L)
void lluau::set_interrupts_counter(lua_State *L, S32 counter)
{
- luaL_checkstack(L, 2, nullptr);
- lua_pushstring(L, "_INTERRUPTS");
- lua_pushinteger(L, counter);
- lua_rawset(L, LUA_REGISTRYINDEX);
+ lua_rawsetfield(L, LUA_REGISTRYINDEX, "_INTERRUPTS"sv, lua_Integer(counter));
}
void lluau::check_interrupts_counter(lua_State* L)
{
- luaL_checkstack(L, 1, nullptr);
- lua_pushstring(L, "_INTERRUPTS");
- lua_rawget(L, LUA_REGISTRYINDEX);
- S32 counter = lua_tointeger(L, -1);
- lua_pop(L, 1);
+ auto counter = lua_rawgetfield<lua_Integer>(L, LUA_REGISTRYINDEX, "_INTERRUPTS"sv);
- lluau::set_interrupts_counter(L, ++counter);
+ set_interrupts_counter(L, ++counter);
if (counter > INTERRUPTS_MAX_LIMIT)
{
lluau::error(L, "Possible infinite loop, terminated.");
diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h
index 7f7a7566f3..7b59af30f5 100644
--- a/indra/llcommon/lua_function.h
+++ b/indra/llcommon/lua_function.h
@@ -18,6 +18,7 @@
#include "luau/lualib.h"
#include "fsyspath.h"
#include "llerror.h"
+#include "llsd.h"
#include "stringize.h"
#include <exception> // std::uncaught_exceptions()
#include <memory> // std::shared_ptr
@@ -169,6 +170,167 @@ private:
};
/*****************************************************************************
+* lua_push() wrappers for generic code
+*****************************************************************************/
+inline
+void lua_push(lua_State* L, bool b)
+{
+ lua_pushboolean(L, int(b));
+}
+
+inline
+void lua_push(lua_State* L, lua_CFunction fn)
+{
+ lua_pushcfunction(L, fn, "");
+}
+
+inline
+void lua_push(lua_State* L, lua_Integer n)
+{
+ lua_pushinteger(L, n);
+}
+
+inline
+void lua_push(lua_State* L, void* p)
+{
+ lua_pushlightuserdata(L, p);
+}
+
+inline
+void lua_push(lua_State* L, const LLSD& data)
+{
+ lua_pushllsd(L, data);
+}
+
+inline
+void lua_push(lua_State* L, const char* s, size_t len)
+{
+ lua_pushlstring(L, s, len);
+}
+
+inline
+void lua_push(lua_State* L)
+{
+ lua_pushnil(L);
+}
+
+inline
+void lua_push(lua_State* L, lua_Number n)
+{
+ lua_pushnumber(L, n);
+}
+
+inline
+void lua_push(lua_State* L, const std::string& s)
+{
+ lua_pushstdstring(L, s);
+}
+
+inline
+void lua_push(lua_State* L, const char* s)
+{
+ lua_pushstring(L, s);
+}
+
+/*****************************************************************************
+* lua_to() wrappers for generic code
+*****************************************************************************/
+template <typename T>
+auto lua_to(lua_State* L, int index);
+
+template <>
+inline
+auto lua_to<bool>(lua_State* L, int index)
+{
+ return lua_toboolean(L, index);
+}
+
+template <>
+inline
+auto lua_to<lua_CFunction>(lua_State* L, int index)
+{
+ return lua_tocfunction(L, index);
+}
+
+template <>
+inline
+auto lua_to<lua_Integer>(lua_State* L, int index)
+{
+ return lua_tointeger(L, index);
+}
+
+template <>
+inline
+auto lua_to<LLSD>(lua_State* L, int index)
+{
+ return lua_tollsd(L, index);
+}
+
+template <>
+inline
+auto lua_to<lua_Number>(lua_State* L, int index)
+{
+ return lua_tonumber(L, index);
+}
+
+template <>
+inline
+auto lua_to<std::string>(lua_State* L, int index)
+{
+ return lua_tostdstring(L, index);
+}
+
+template <>
+inline
+auto lua_to<void*>(lua_State* L, int index)
+{
+ return lua_touserdata(L, index);
+}
+
+/*****************************************************************************
+* field operations
+*****************************************************************************/
+// return to C++, from table at index, the value of field k
+template <typename T>
+auto lua_getfieldv(lua_State* L, int index, const char* k)
+{
+ luaL_checkstack(L, 1, nullptr);
+ lua_getfield(L, index, k);
+ LuaPopper pop(L, 1);
+ return lua_to<T>(L, -1);
+}
+
+// set in table at index, as field k, the specified C++ value
+template <typename T>
+auto lua_setfieldv(lua_State* L, int index, const char* k, const T& value)
+{
+ luaL_checkstack(L, 1, nullptr);
+ lua_push(L, value);
+ lua_setfield(L, index, k);
+}
+
+// return to C++, from table at index, the value of field k (without metamethods)
+template <typename T>
+auto lua_rawgetfield(lua_State* L, int index, const std::string_view& k)
+{
+ luaL_checkstack(L, 1, nullptr);
+ lua_pushlstring(L, k.data(), k.length());
+ lua_rawget(L, index);
+ LuaPopper pop(L, 1);
+ return lua_to<T>(L, -1);
+}
+
+// set in table at index, as field k, the specified C++ value (without metamethods)
+template <typename T>
+void lua_rawsetfield(lua_State* L, int index, const std::string_view& k, const T& value)
+{
+ luaL_checkstack(L, 2, nullptr);
+ lua_pushlstring(L, k.data(), k.length());
+ lua_push(L, value);
+ lua_rawset(L, index);
+}
+
+/*****************************************************************************
* lua_function (and helper class LuaFunction)
*****************************************************************************/
/**
diff --git a/indra/newview/lluilistener.cpp b/indra/newview/lluilistener.cpp
index 4afd7f1766..b81859a764 100644
--- a/indra/newview/lluilistener.cpp
+++ b/indra/newview/lluilistener.cpp
@@ -34,10 +34,13 @@
// std headers
// external library headers
// other Linden headers
+#include "llmenugl.h"
#include "llui.h" // getRootView(), resolvePath()
#include "lluictrl.h"
#include "llerror.h"
+extern LLMenuBarGL* gMenuBarView;
+
#define THROTTLE_PERIOD 1.5 // required seconds between throttled functions
#define MIN_THROTTLE 0.5
@@ -52,11 +55,48 @@ LLUIListener::LLUIListener():
&LLUIListener::call,
llsd::map("function", LLSD(), "reply", LLSD()));
+ add("callables",
+ "Return a list [\"callables\"] of dicts {name, access} of functions registered to\n"
+ "invoke with \"call\".\n"
+ "access has values \"allow\", \"block\" or \"throttle\".",
+ &LLUIListener::callables,
+ llsd::map("reply", LLSD::String()));
+
add("getValue",
"For the UI control identified by the path in [\"path\"], return the control's\n"
"current value as [\"value\"] reply.",
&LLUIListener::getValue,
llsd::map("path", LLSD(), "reply", LLSD()));
+
+ add("getParents",
+ "List names of Top menus suitable for passing as \"parent_menu\"",
+ &LLUIListener::getParents,
+ llsd::map("reply", LLSD::String()));
+
+ LLSD required_args = llsd::map("name", LLSD(), "label", LLSD(), "reply", LLSD());
+ add("addMenu",
+ "Add new drop-down menu [\"name\"] with displayed [\"label\"] to the Top menu.",
+ &LLUIListener::addMenu,
+ required_args);
+
+ required_args.insert("parent_menu", LLSD());
+ add("addMenuBranch",
+ "Add new menu branch [\"name\"] with displayed [\"label\"]\n"
+ "to the [\"parent_menu\"] within the Top menu.",
+ &LLUIListener::addMenuBranch,
+ required_args);
+
+ add("addMenuItem",
+ "Add new menu item [\"name\"] with displayed [\"label\"]\n"
+ "and call-on-click UI function [\"func\"] with optional [\"param\"]\n"
+ "to the [\"parent_menu\"] within the Top menu.",
+ &LLUIListener::addMenuItem,
+ required_args.with("func", LLSD()));
+
+ add("addMenuSeparator",
+ "Add menu separator to the [\"parent_menu\"] within the Top menu.",
+ &LLUIListener::addMenuSeparator,
+ llsd::map("parent_menu", LLSD(), "reply", LLSD()));
}
typedef LLUICtrl::CommitCallbackInfo cb_info;
@@ -103,7 +143,43 @@ void LLUIListener::call(const LLSD& event)
(info->callback_func)(NULL, event["parameter"]);
}
-void LLUIListener::getValue(const LLSD&event) const
+void LLUIListener::callables(const LLSD& event) const
+{
+ Response response(LLSD(), event);
+
+ using Registry = LLUICtrl::CommitCallbackRegistry;
+ using Method = Registry::Registrar& (*)();
+ static Method registrars[] =
+ {
+ &Registry::defaultRegistrar,
+ &Registry::currentRegistrar,
+ };
+ LLSD list;
+ for (auto method : registrars)
+ {
+ auto& registrar{ (*method)() };
+ for (auto it = registrar.beginItems(), end = registrar.endItems(); it != end; ++it)
+ {
+ LLSD entry{ llsd::map("name", it->first) };
+ switch (it->second.handle_untrusted)
+ {
+ case LLUICtrl::CommitCallbackInfo::UNTRUSTED_ALLOW:
+ entry["access"] = "allow";
+ break;
+ case LLUICtrl::CommitCallbackInfo::UNTRUSTED_BLOCK:
+ entry["access"] = "block";
+ break;
+ case LLUICtrl::CommitCallbackInfo::UNTRUSTED_THROTTLE:
+ entry["access"] = "throttle";
+ break;
+ }
+ list.append(entry);
+ }
+ }
+ response["callables"] = list;
+}
+
+void LLUIListener::getValue(const LLSD& event) const
{
Response response(LLSD(), event);
@@ -120,3 +196,87 @@ void LLUIListener::getValue(const LLSD&event) const
response.error(stringize("UI control ", std::quoted(event["path"].asString()), " was not found"));
}
}
+
+void LLUIListener::getParents(const LLSD& event) const
+{
+ Response response(LLSD(), event);
+ response["parents"] = llsd::toArray(
+ *gMenuBarView->getChildList(),
+ [](auto childp) {return childp->getName(); });
+}
+
+LLMenuGL::Params get_params(const LLSD&event)
+{
+ LLMenuGL::Params item_params;
+ item_params.name = event["name"];
+ item_params.label = event["label"];
+ item_params.can_tear_off = true;
+ return item_params;
+}
+
+LLMenuGL* get_parent_menu(LLEventAPI::Response& response, const LLSD&event)
+{
+ LLMenuGL* parent_menu = gMenuBarView->findChildMenuByName(event["parent_menu"], true);
+ if(!parent_menu)
+ {
+ response.error(stringize("Parent menu ", std::quoted(event["parent_menu"].asString()), " was not found"));
+ }
+ return parent_menu;
+}
+
+void LLUIListener::addMenu(const LLSD&event) const
+{
+ Response response(LLSD(), event);
+ LLMenuGL::Params item_params = get_params(event);
+ if(!gMenuBarView->appendMenu(LLUICtrlFactory::create<LLMenuGL>(item_params)))
+ {
+ response.error(stringize("Menu ", std::quoted(event["name"].asString()), " was not added"));
+ }
+}
+
+void LLUIListener::addMenuBranch(const LLSD&event) const
+{
+ Response response(LLSD(), event);
+ if(LLMenuGL* parent_menu = get_parent_menu(response, event))
+ {
+ LLMenuGL::Params item_params = get_params(event);
+ if(!parent_menu->appendMenu(LLUICtrlFactory::create<LLMenuGL>(item_params)))
+ {
+ response.error(stringize("Menu branch ", std::quoted(event["name"].asString()), " was not added"));
+ }
+ }
+}
+
+void LLUIListener::addMenuItem(const LLSD&event) const
+{
+ Response response(LLSD(), event);
+ LLMenuItemCallGL::Params item_params;
+ item_params.name = event["name"];
+ item_params.label = event["label"];
+ LLUICtrl::CommitCallbackParam item_func;
+ item_func.function_name = event["func"];
+ if (event.has("param"))
+ {
+ item_func.parameter = event["param"];
+ }
+ item_params.on_click = item_func;
+ if(LLMenuGL* parent_menu = get_parent_menu(response, event))
+ {
+ if(!parent_menu->append(LLUICtrlFactory::create<LLMenuItemCallGL>(item_params)))
+ {
+ response.error(stringize("Menu item ", std::quoted(event["name"].asString()), " was not added"));
+ }
+ }
+}
+
+void LLUIListener::addMenuSeparator(const LLSD&event) const
+{
+ Response response(LLSD(), event);
+ if(LLMenuGL* parent_menu = get_parent_menu(response, event))
+ {
+ if(!parent_menu->addSeparator())
+ {
+ response.error("Separator was not added");
+ }
+ }
+}
diff --git a/indra/newview/lluilistener.h b/indra/newview/lluilistener.h
index 0df2afb3fe..671eb5f29b 100644
--- a/indra/newview/lluilistener.h
+++ b/indra/newview/lluilistener.h
@@ -39,12 +39,17 @@ class LLUIListener: public LLEventAPI
public:
LLUIListener();
-// FIXME These fields are intended to be private, changed here to support very hacky code in llluamanager.cpp
-public:
+private:
void call(const LLSD& event);
- void getValue(const LLSD&event) const;
+ void callables(const LLSD& event) const;
+ void getValue(const LLSD& event) const;
+ void getParents(const LLSD& event) const;
+
+ void addMenu(const LLSD&event) const;
+ void addMenuBranch(const LLSD&event) const;
+ void addMenuItem(const LLSD&event) const;
+ void addMenuSeparator(const LLSD&event) const;
- private:
F64 mLastUntrustedThrottle {0};
F64 mLastMinThrottle {0};
};
diff --git a/indra/newview/scripts/lua/require/UI.lua b/indra/newview/scripts/lua/require/UI.lua
index 1eee4657f4..06b49c6269 100644
--- a/indra/newview/scripts/lua/require/UI.lua
+++ b/indra/newview/scripts/lua/require/UI.lua
@@ -14,6 +14,10 @@ function UI.call(func, parameter)
leap.request('UI', {op='call', ['function']=func, parameter=parameter})
end
+function UI.callables()
+ return leap.request('UI', {op='callables'}).callables
+end
+
function UI.getValue(path)
return leap.request('UI', {op='getValue', path=path})['value']
end
@@ -135,4 +139,34 @@ function UI.snapshot(...)
args.op = 'saveSnapshot'
return leap.request('LLViewerWindow', args).result
end
+
+-- ***************************************************************************
+-- Top menu
+-- ***************************************************************************
+
+function UI.addMenu(...)
+ local args = mapargs('name,label', ...)
+ args.op = 'addMenu'
+ return leap.request('UI', args)
+end
+
+function UI.addMenuBranch(...)
+ local args = mapargs('name,label,parent_menu', ...)
+ args.op = 'addMenuBranch'
+ return leap.request('UI', args)
+end
+
+-- see UI.callables() for valid values of 'func'
+function UI.addMenuItem(...)
+ local args = mapargs('name,label,parent_menu,func,param', ...)
+ args.op = 'addMenuItem'
+ return leap.request('UI', args)
+end
+
+function UI.addMenuSeparator(...)
+ local args = mapargs('parent_menu', ...)
+ args.op = 'addMenuSeparator'
+ return leap.request('UI', args)
+end
+
return UI
diff --git a/indra/newview/scripts/lua/test_callables.lua b/indra/newview/scripts/lua/test_callables.lua
new file mode 100644
index 0000000000..1bee062db8
--- /dev/null
+++ b/indra/newview/scripts/lua/test_callables.lua
@@ -0,0 +1,6 @@
+startup=require 'startup'
+UI=require 'UI'
+startup.wait('STATE_LOGIN_WAIT')
+for _, cbl in pairs(UI.callables()) do
+ print(`{cbl.name} ({cbl.access})`)
+end
diff --git a/indra/newview/scripts/lua/test_top_menu.lua b/indra/newview/scripts/lua/test_top_menu.lua
new file mode 100644
index 0000000000..780a384c92
--- /dev/null
+++ b/indra/newview/scripts/lua/test_top_menu.lua
@@ -0,0 +1,34 @@
+UI = require 'UI'
+
+--Add new drop-down 'LUA Menu' to the Top menu.
+local MENU_NAME = "lua_menu"
+UI.addMenu{name=MENU_NAME,label="LUA Menu"}
+
+--Add two new menu items to the 'LUA Menu': 'Debug console' and 'Scripts'
+UI.addMenuItem{name="lua_debug",label="Debug console",
+ param="lua_debug",
+ func="Floater.ToggleOrBringToFront",
+ parent_menu=MENU_NAME}
+
+UI.addMenuItem{name="lua_scripts",label="Scripts",
+ param="lua_scripts",
+ func="Floater.ToggleOrBringToFront",
+ parent_menu=MENU_NAME}
+
+--Add menu separator to the 'LUA Menu' under added menu items
+UI.addMenuSeparator{parent_menu=MENU_NAME}
+
+--Add two new menu branch 'About...' to the 'LUA Menu'
+local BRANCH_NAME = "about_branch"
+UI.addMenuBranch{name="about_branch",label="About...",parent_menu=MENU_NAME}
+
+--Add two new menu items to the 'About...' branch
+UI.addMenuItem{name="lua_info",label="Lua...",
+ param="https://www.lua.org/about.html",
+ func="Advanced.ShowURL",
+ parent_menu=BRANCH_NAME}
+
+UI.addMenuItem{name="lua_info",label="Luau...",
+ param="https://luau-lang.org/",
+ func="Advanced.ShowURL",
+ parent_menu=BRANCH_NAME}