From db8916454457e3e4856fddcbbafe66b3766e4ffa Mon Sep 17 00:00:00 2001
From: Kitty Barnett <develop@catznip.com>
Date: Tue, 3 Sep 2024 18:14:06 +0200
Subject: Add the RLVa console

---
 indra/newview/CMakeLists.txt                       |  2 +
 indra/newview/llviewerfloaterreg.cpp               |  2 +
 indra/newview/rlvdefines.h                         |  1 +
 indra/newview/rlvfloaters.cpp                      | 95 ++++++++++++++++++++++
 indra/newview/rlvfloaters.h                        | 40 +++++++++
 indra/newview/rlvhandler.cpp                       |  6 +-
 indra/newview/rlvhandler.h                         | 15 ++++
 indra/newview/rlvhelper.cpp                        | 38 ++++-----
 indra/newview/rlvhelper.h                          |  5 +-
 .../skins/default/xui/en/floater_rlv_console.xml   | 73 +++++++++++++++++
 indra/newview/skins/default/xui/en/strings.xml     |  5 ++
 11 files changed, 255 insertions(+), 27 deletions(-)
 create mode 100644 indra/newview/rlvfloaters.cpp
 create mode 100644 indra/newview/rlvfloaters.h
 create mode 100644 indra/newview/skins/default/xui/en/floater_rlv_console.xml

(limited to 'indra')

diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index a7bbadfd86..e3f675cb8e 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -728,6 +728,7 @@ set(viewer_SOURCE_FILES
     pipeline.cpp
     rlvactions.cpp
     rlvcommon.cpp
+    rlvfloaters.cpp
     rlvhelper.cpp
     rlvhandler.cpp
     )
@@ -1393,6 +1394,7 @@ set(viewer_HEADER_FILES
     rlvdefines.h
     rlvactions.h
     rlvcommon.h
+    rlvfloaters.h
     rlvhelper.h
     rlvhandler.h
     roles_constants.h
diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp
index 9bdd246129..e41d8c44fb 100644
--- a/indra/newview/llviewerfloaterreg.cpp
+++ b/indra/newview/llviewerfloaterreg.cpp
@@ -175,6 +175,7 @@
 #include "llpreviewtexture.h"
 #include "llscriptfloater.h"
 #include "llsyswellwindow.h"
+#include "rlvfloaters.h"
 
 // *NOTE: Please add files in alphabetical order to keep merges easy.
 
@@ -480,6 +481,7 @@ void LLViewerFloaterReg::registerFloaters()
     LLFloaterReg::add("region_debug_console", "floater_region_debug_console.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterRegionDebugConsole>);
     LLFloaterReg::add("region_info", "floater_region_info.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterRegionInfo>);
     LLFloaterReg::add("region_restarting", "floater_region_restarting.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterRegionRestarting>);
+    LLFloaterReg::add("rlv_console", "floater_rlv_console.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<Rlv::FloaterConsole>);
 
     LLFloaterReg::add("script_debug", "floater_script_debug.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterScriptDebug>);
     LLFloaterReg::add("script_debug_output", "floater_script_debug_panel.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterScriptDebugOutput>);
diff --git a/indra/newview/rlvdefines.h b/indra/newview/rlvdefines.h
index c36512007b..88dffa1127 100644
--- a/indra/newview/rlvdefines.h
+++ b/indra/newview/rlvdefines.h
@@ -52,6 +52,7 @@ namespace Rlv
     namespace Constants
     {
         constexpr char CmdPrefix = '@';
+        constexpr char ConsolePrompt[] = "> ";
         constexpr std::string_view OptionSeparator = ";";
     }
 }
diff --git a/indra/newview/rlvfloaters.cpp b/indra/newview/rlvfloaters.cpp
new file mode 100644
index 0000000000..8d107b2540
--- /dev/null
+++ b/indra/newview/rlvfloaters.cpp
@@ -0,0 +1,95 @@
+#include "llviewerprecompiledheaders.h"
+
+#include "llagentdata.h"
+#include "llchatentry.h"
+#include "lltexteditor.h"
+#include "lltrans.h"
+#include "llvoavatarself.h"
+
+#include "rlvfloaters.h"
+#include "rlvhandler.h"
+
+using namespace Rlv;
+
+// ============================================================================
+// FloaterConsole
+//
+
+bool FloaterConsole::postBuild()
+{
+    mInputEdit = getChild<LLChatEntry>("console_input");
+    mInputEdit->setCommitCallback(std::bind(&FloaterConsole::onInput, this));
+    mInputEdit->setTextExpandedCallback(std::bind(&FloaterConsole::reshapeLayoutPanel, this));
+    mInputEdit->setFocus(true);
+    mInputEdit->setCommitOnFocusLost(false);
+
+    mInputPanel = getChild<LLLayoutPanel>("input_panel");
+    mInputEditPad = mInputPanel->getRect().getHeight() - mInputEdit->getRect().getHeight();
+
+    mOutputText = getChild<LLTextEditor>("console_output");
+    mOutputText->appendText(Constants::ConsolePrompt, false);
+
+    if (RlvHandler::isEnabled())
+    {
+        mCommandOutputConn = RlvHandler::instance().setCommandOutputCallback([this](const RlvCommand& rlvCmd, S32, const std::string strText)
+        {
+            if (rlvCmd.getObjectID() == gAgentID)
+            {
+                mOutputText->appendText(rlvCmd.getBehaviour() + ": ", true);
+                mOutputText->appendText(strText, false);
+            }
+        });
+    }
+
+    return true;
+}
+
+void FloaterConsole::onClose(bool fQuitting)
+{
+    if (RlvHandler::isEnabled())
+    {
+        RlvHandler::instance().processCommand(gAgentID, "clear", true);
+    }
+}
+
+void FloaterConsole::onInput()
+{
+    if (!isAgentAvatarValid())
+    {
+        return;
+    }
+
+    std::string strText = mInputEdit->getText();
+    LLStringUtil::trim(strText);
+
+    mOutputText->appendText(strText, false);
+    mInputEdit->setText(LLStringUtil::null);
+
+    if (!RlvHandler::isEnabled())
+    {
+        mOutputText->appendText(LLTrans::getString("RlvConsoleDisable"), true);
+    }
+    else if (strText.length() <= 3 || Constants::CmdPrefix != strText[0])
+    {
+        mOutputText->appendText(LLTrans::getString("RlvConsoleInvalidCmd"), true);
+    }
+    else
+    {
+        LLChat chat;
+        chat.mFromID = gAgentID;
+        chat.mChatType = CHAT_TYPE_OWNER;
+
+        RlvHandler::instance().handleSimulatorChat(strText, chat, gAgentAvatarp);
+
+        mOutputText->appendText(strText, true);
+    }
+
+    mOutputText->appendText(Constants::ConsolePrompt, true);
+}
+
+void FloaterConsole::reshapeLayoutPanel()
+{
+    mInputPanel->reshape(mInputPanel->getRect().getWidth(), mInputEdit->getRect().getHeight() + mInputEditPad, false);
+}
+
+// ============================================================================
diff --git a/indra/newview/rlvfloaters.h b/indra/newview/rlvfloaters.h
new file mode 100644
index 0000000000..a599bb051a
--- /dev/null
+++ b/indra/newview/rlvfloaters.h
@@ -0,0 +1,40 @@
+#pragma once
+
+#include "llfloater.h"
+
+#include "rlvdefines.h"
+
+class LLChatEntry;
+class LLLayoutPanel;
+class LLTextEditor;
+class RlvCommand;
+class RlvHandler;
+
+namespace Rlv
+{
+    // ============================================================================
+    // FloaterConsole - debug console to allow command execution without the need for a script
+    //
+
+    class FloaterConsole : public LLFloater
+    {
+        friend class LLFloaterReg;
+        FloaterConsole(const LLSD& sdKey) : LLFloater(sdKey) {}
+
+    public:
+        bool postBuild() override;
+        void onClose(bool fQuitting) override;
+    protected:
+        void onInput();
+        void reshapeLayoutPanel();
+
+    private:
+        boost::signals2::scoped_connection mCommandOutputConn;
+        int mInputEditPad = 0;
+        LLLayoutPanel* mInputPanel = nullptr;
+        LLChatEntry* mInputEdit = nullptr;
+        LLTextEditor* mOutputText = nullptr;
+    };
+
+    // ============================================================================
+};
diff --git a/indra/newview/rlvhandler.cpp b/indra/newview/rlvhandler.cpp
index 8b2620cf48..0a0da0e25d 100644
--- a/indra/newview/rlvhandler.cpp
+++ b/indra/newview/rlvhandler.cpp
@@ -1,6 +1,5 @@
 #include "llviewerprecompiledheaders.h"
 #include "llagent.h"
-#include "llappviewer.h"
 #include "llstartup.h"
 #include "llviewercontrol.h"
 #include "llviewerobject.h"
@@ -38,7 +37,7 @@ bool RlvHandler::handleSimulatorChat(std::string& message, const LLChat& chat, c
 
     message.erase(0, 1);
     LLStringUtil::toLower(message);
-    CommandDbgOut cmdDbgOut(message);
+    CommandDbgOut cmdDbgOut(message, chatObj->getID() == gAgentID);
 
     boost_tokenizer tokens(message, boost::char_separator<char>(",", "", boost::drop_empty_tokens));
     for (const std::string& strCmd : tokens)
@@ -128,7 +127,7 @@ ECmdRet CommandHandlerBaseImpl<EParamType::Reply>::processCommand(const RlvComma
 {
     // Sanity check - <param> should specify a - valid - reply channel
     S32 nChannel;
-    if (!LLStringUtil::convertToS32(rlvCmd.getParam(), nChannel) || !Util::isValidReplyChannel(nChannel, rlvCmd.getObjectID() == gAgent.getID()))
+    if (!LLStringUtil::convertToS32(rlvCmd.getParam(), nChannel) || !Util::isValidReplyChannel(nChannel, rlvCmd.getObjectID() == gAgentID))
         return ECmdRet::FailedParam;
 
     std::string strReply;
@@ -141,6 +140,7 @@ ECmdRet CommandHandlerBaseImpl<EParamType::Reply>::processCommand(const RlvComma
     {
         Util::sendChatReply(nChannel, strReply);
     }
+    RlvHandler::instance().mOnCommandOutput(rlvCmd, nChannel, strReply);
 
     return eRet;
 }
diff --git a/indra/newview/rlvhandler.h b/indra/newview/rlvhandler.h
index a5e91548ef..7fa4da767a 100644
--- a/indra/newview/rlvhandler.h
+++ b/indra/newview/rlvhandler.h
@@ -13,6 +13,8 @@ class LLViewerObject;
 
 class RlvHandler : public LLSingleton<RlvHandler>
 {
+    template<Rlv::EParamType> friend struct Rlv::CommandHandlerBaseImpl;
+
     LLSINGLETON_EMPTY_CTOR(RlvHandler);
 
     /*
@@ -25,12 +27,25 @@ public:
 protected:
     Rlv::ECmdRet processCommand(std::reference_wrapper<const RlvCommand> rlvCmdRef, bool fromObj);
 
+    /*
+     * Helper functions
+     */
 public:
     // Initialization (deliberately static so they can safely be called in tight loops)
     static bool canEnable();
     static bool isEnabled() { return mIsEnabled; }
     static bool setEnabled(bool enable);
 
+    /*
+     * Event handling
+     */
+public:
+    // The command output signal is triggered whenever a command produces channel or debug output
+    using command_output_signal_t = boost::signals2::signal<void (const RlvCommand&, S32, const std::string&)>;
+    boost::signals2::connection setCommandOutputCallback(const command_output_signal_t::slot_type& cb) { return mOnCommandOutput.connect(cb); }
+
+protected:
+    command_output_signal_t mOnCommandOutput;
 private:
     static bool mIsEnabled;
 };
diff --git a/indra/newview/rlvhelper.cpp b/indra/newview/rlvhelper.cpp
index aad615d813..ecb76a1db3 100644
--- a/indra/newview/rlvhelper.cpp
+++ b/indra/newview/rlvhelper.cpp
@@ -250,21 +250,13 @@ namespace Rlv
 
     void CommandDbgOut::add(std::string strCmd, ECmdRet eRet)
     {
-        ECmdRet resultBucket;
-
-        // Successful and retained commands are added as-is
-        if (isReturnCodeSuccess(eRet))
-            resultBucket = ECmdRet::Success;
-        else if (ECmdRet::Retained == eRet)
-            resultBucket = ECmdRet::Retained;
-        else
-        {
-            // Failed commands get the failure reason appended to help troubleshooting
-            resultBucket = ECmdRet::Failed;
-            strCmd.append(llformat(" (%s)", getReturnCodeString(eRet).c_str()));
-        }
+        const std::string strSuffix = getReturnCodeString(eRet);
+        if (!strSuffix.empty())
+            strCmd.append(llformat(" (%s)", strSuffix.c_str()));
+        else if (mForConsole)
+            return; // Only show console feedback on successful commands when there's an informational notice
 
-        std::string& strResult = mCommandResults[resultBucket];
+        std::string& strResult = mCommandResults[isReturnCodeSuccess(eRet) ? ECmdRet::Success : (ECmdRet::Retained == eRet ? ECmdRet::Retained : ECmdRet::Failed)];
         if (!strResult.empty())
             strResult.append(", ");
         strResult.append(strCmd);
@@ -273,23 +265,25 @@ namespace Rlv
     std::string CommandDbgOut::get() const {
         std::ostringstream result;
 
-        if (1 == mCommandResults.size())
+        if (1 == mCommandResults.size() && !mForConsole)
         {
             auto it = mCommandResults.begin();
-            result << " " << getDebugVerbFromReturnCode(it->first) << ": @" << it->second;;
+            result << " " << getDebugVerbFromReturnCode(it->first) << ": @" << it->second;
         }
-        else if (mCommandResults.size() > 1)
+        else if (!mCommandResults.empty())
         {
             auto appendResult = [&](ECmdRet eRet, const std::string& name)
                 {
                     auto it = mCommandResults.find(eRet);
                     if (it == mCommandResults.end()) return;
-                    result << "\n    - " << LLTrans::getString(name) << ": @" << it->second;
+                    if (!mForConsole) result << "\n    - ";
+                    result << LLTrans::getString(name) << ": @" << it->second;
                 };
-            result << ": @" << mOrigCmd;
-            appendResult(ECmdRet::Success, "RlvDebugExecuted");
-            appendResult(ECmdRet::Failed, "RlvDebugFailed");
-            appendResult(ECmdRet::Retained, "RlvDebugRetained");
+            if (!mForConsole)
+                result << ": @" << mOrigCmd;
+            appendResult(ECmdRet::Success, !mForConsole ? "RlvDebugExecuted" : "RlvConsoleExecuted");
+            appendResult(ECmdRet::Failed, !mForConsole ? "RlvDebugFailed" : "RlvConsoleFailed");
+            appendResult(ECmdRet::Retained, !mForConsole ? "RlvDebugRetained" : "RlvConsoleRetained");
         }
 
         return result.str();
diff --git a/indra/newview/rlvhelper.h b/indra/newview/rlvhelper.h
index 802b1cbadd..644cd0ee22 100644
--- a/indra/newview/rlvhelper.h
+++ b/indra/newview/rlvhelper.h
@@ -141,7 +141,7 @@ namespace Rlv
     template<EBehaviour templBhvr> using ForceHandler = CommandHandler<EParamType::Force, templBhvr>;
     template<EBehaviour templBhvr> using ReplyHandler = CommandHandler<EParamType::Reply, templBhvr>;
 
-	// List of shared handlers
+    // List of shared handlers
     using VersionReplyHandler = ReplyHandler<EBehaviour::Version>;				// Shared between @version and @versionnew
 
     //
@@ -192,7 +192,7 @@ namespace Rlv
 
     struct CommandDbgOut
     {
-        CommandDbgOut(const std::string& orig_cmd) : mOrigCmd(orig_cmd) {}
+        CommandDbgOut(const std::string& orig_cmd, bool for_console) : mOrigCmd(orig_cmd), mForConsole(for_console) {}
         void add(std::string strCmd, ECmdRet eRet);
         std::string get() const;
         static std::string getDebugVerbFromReturnCode(ECmdRet eRet);
@@ -200,6 +200,7 @@ namespace Rlv
     private:
         std::string mOrigCmd;
         std::map<ECmdRet, std::string> mCommandResults;
+        bool mForConsole = false;
     };
 }
 
diff --git a/indra/newview/skins/default/xui/en/floater_rlv_console.xml b/indra/newview/skins/default/xui/en/floater_rlv_console.xml
new file mode 100644
index 0000000000..928d50cb41
--- /dev/null
+++ b/indra/newview/skins/default/xui/en/floater_rlv_console.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<floater
+ can_resize="true"
+ height="400"
+ layout="topleft"
+ min_height="300"
+ min_width="300"
+ name="rlv_console"
+ title="RLVa console"
+ width="600"
+ >
+    <layout_stack
+     animate="false"
+     bottom="-1"
+     default_tab_group="2"
+     follows="all"
+     left="5"
+     layout="topleft"
+     mouse_opaque="false"
+     name="main_stack"
+     right="-5"
+     orientation="vertical"
+     tab_group="1"
+     top="1"
+     >
+        <layout_panel
+         name="body_panel"
+         height="235">
+            <text_editor
+             follows="all"
+             left="1"
+			 right="-1"
+			 top="0"
+             length="1"
+             font="Monospace"
+             bottom="-1"
+             ignore_tab="false"
+             layout="topleft"
+             max_length="65536"
+             name="console_output"
+             read_only="true"
+             track_end="true"
+             type="string"
+             word_wrap="true"
+            >
+            </text_editor>
+        </layout_panel>
+
+        <layout_panel
+         height="26"
+         auto_resize="false"
+         name="input_panel">
+            <chat_editor
+             layout="topleft"
+             expand_lines_count="5"
+             follows="left|right|bottom"
+             font="SansSerifSmall"
+             height="20"
+             is_expandable="true"
+             text_tentative_color="TextFgTentativeColor"
+             name="console_input"
+             max_length="1023"
+             spellcheck="true"
+             tab_group="3"
+             bottom_delta="20"
+             left="1"
+			 top="1"
+			 right="-1"
+             wrap="true"
+            />
+        </layout_panel>
+    </layout_stack>
+</floater>
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index 39a663298a..cebe1ff6c3 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -4377,6 +4377,11 @@ and report the problem.
   </string>
 
   <!-- RLVa -->
+  <string name="RlvConsoleDisable">RLVa is disabled</string>
+  <string name="RlvConsoleInvalidCmd">Invalid command</string>
+  <string name="RlvConsoleExecuted">INFO</string>
+  <string name="RlvConsoleFailed">ERR</string>
+  <string name="RlvConsoleRetained">RET</string>
   <string name="RlvDebugExecuted">executed</string>
   <string name="RlvDebugFailed">failed</string>
   <string name="RlvDebugRetained">retained</string>
-- 
cgit v1.2.3