From 1398a68a874b3796d8d381b3be296cd2942b3739 Mon Sep 17 00:00:00 2001 From: Matti Date: Sun, 5 Jul 2026 13:25:03 +0100 Subject: [PATCH 1/5] feat: core RLV extensions, startup lock, and visual UI lock states --- indra/lib/python/indra/util/llmanifest.py | 10 +- indra/llui/llflatlistview.h | 4 - indra/llui/llfloaterreg.cpp | 12 +- indra/llui/lltoolbar.cpp | 19 +- indra/llui/llui.cpp | 4 +- indra/llxml/llcontrol.cpp | 27 +- indra/llxml/tests/llcontrol_test.cpp | 44 ++ indra/newview/CMakeLists.txt | 24 +- indra/newview/alavataractions.cpp | 7 + indra/newview/alfloaterprofilelegacy.cpp | 9 + indra/newview/alfloatersceneexplorer.cpp | 11 - indra/newview/alfloaterwebprofile.cpp | 8 + indra/newview/app_settings/commands.xml | 25 + .../newview/app_settings/settings_alchemy.xml | 70 ++- indra/newview/llagentcamera.cpp | 16 + indra/newview/llappviewer.cpp | 23 +- indra/newview/llavataractions.cpp | 6 + indra/newview/llfloaterimsessiontab.h | 1 - indra/newview/llfloaterpreference.cpp | 2 +- indra/newview/llfloaterprofile.cpp | 8 + indra/newview/llpanelgrouproles.cpp | 4 + indra/newview/llpanelobjectinventory.cpp | 7 + indra/newview/llpanelpeople.cpp | 97 ++-- indra/newview/llpanelpeople.h | 9 +- indra/newview/llstartup.cpp | 23 +- indra/newview/lltoolmgr.cpp | 4 + indra/newview/llviewerdisplay.cpp | 1 + indra/newview/llviewerfloaterreg.cpp | 2 + indra/newview/llviewermenu.cpp | 16 +- indra/newview/llviewerwindow.cpp | 48 +- indra/newview/rlvactions.cpp | 10 +- indra/newview/rlvcommon.cpp | 17 +- indra/newview/rlvcommon.h | 4 +- indra/newview/rlvdefines.h | 25 +- indra/newview/rlvhandler.cpp | 462 +++++++++++++++++- indra/newview/rlvhandler.h | 8 + indra/newview/rlvhelper.cpp | 59 ++- indra/newview/rlvui.cpp | 33 +- indra/newview/rlvui.h | 3 + .../skins/default/xui/en/menu_viewer.xml | 16 + .../skins/default/xui/en/notifications.xml | 71 +++ .../skins/default/xui/en/rlva_strings.xml | 49 +- .../skins/default/xui/ja/notifications.xml | 48 ++ .../skins/default/xui/ja/rlva_strings.xml | 45 +- 44 files changed, 1211 insertions(+), 180 deletions(-) diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py index b651fd2f2a5..b5f6ca535c9 100644 --- a/indra/lib/python/indra/util/llmanifest.py +++ b/indra/lib/python/indra/util/llmanifest.py @@ -259,8 +259,8 @@ def main(extra=[]): # Store package file for later if making touched file. base_package_file = "" if touch: - print('================ Created base package ', wm.package_file) - base_package_file = "" + wm.package_file + print('================ Created base package ', wm.package_name) + base_package_file = "" + wm.package_name else: print('================ Finished base copy') @@ -298,9 +298,9 @@ def main(extra=[]): except Exception as err: sys.exit(str(err)) if touch: - print('================ Created additional package ', wm.package_file, ' for ', package_id) + print('================ Created additional package ', wm.package_name, ' for ', package_id) with open(base_touch_template.format(package_id), 'w') as fp: - fp.write('set package_file=%s\n' % wm.package_file) + fp.write('set package_file=%s\n' % wm.package_name) else: print('================ Finished additional copy "', package_id, '" in ', args['dest']) # Write out the package file in this format, so that it can easily be called @@ -339,7 +339,7 @@ def __init__(self, args): self.build_prefix = [args['build']] self.dst_prefix = [args['dest']] self.created_paths = [] - self.package_file = "Unknown" + self.package_name = "Unknown" self.missing = [] def default_channel(self): diff --git a/indra/llui/llflatlistview.h b/indra/llui/llflatlistview.h index 75356faf34e..02394033afc 100644 --- a/indra/llui/llflatlistview.h +++ b/indra/llui/llflatlistview.h @@ -559,11 +559,7 @@ class LLFlatListViewEx : public LLFlatListView Params(); }; - // *WORKAROUND: two methods to overload appropriate Params due to localization issue: - // no_items_msg & no_filtered_items_msg attributes are not defined as translatable in VLT. See EXT-5931 -// [RLVa:KB] - Checked: RLVa-2.0.3 const std::string& getNoItemsMsg() const { return mNoItemsMsg; } -// [/RLVa:KB] void setNoItemsMsg(const std::string& msg) { mNoItemsMsg = msg; } void setNoFilteredItemsMsg(const std::string& msg) { mNoFilteredItemsMsg = msg; } diff --git a/indra/llui/llfloaterreg.cpp b/indra/llui/llfloaterreg.cpp index c28a8cb8b11..bba1bd84b9b 100644 --- a/indra/llui/llfloaterreg.cpp +++ b/indra/llui/llfloaterreg.cpp @@ -492,6 +492,12 @@ void LLFloaterReg::registerControlVariables() //static void LLFloaterReg::toggleInstanceOrBringToFront(const LLSD& sdname, const LLSD& key) { + std::string name = sdname.asString(); + if (!canShowInstance(name, key)) + { + return; + } + // // Floaters controlled by the toolbar behave a bit differently from others. // Namely they have 3-4 states as defined in the design wiki page here: @@ -504,7 +510,6 @@ void LLFloaterReg::toggleInstanceOrBringToFront(const LLSD& sdname, const LLSD& // * Also, if it is not on top, bring it forward when focus is given. // * Else the target floater is open, close it. // - std::string name = sdname.asString(); LLFloater* instance = getInstance(name, key); if (!instance) @@ -564,6 +569,11 @@ void LLFloaterReg::toggleInstanceOrBringToFront(const LLSD& sdname, const LLSD& void LLFloaterReg::showInstanceOrBringToFront(const LLSD& sdname, const LLSD& key) { std::string name = sdname.asString(); + if (!canShowInstance(name, key)) + { + return; + } + LLFloater* instance = getInstance(name, key); diff --git a/indra/llui/lltoolbar.cpp b/indra/llui/lltoolbar.cpp index 0b181785e85..22ccb878da2 100644 --- a/indra/llui/lltoolbar.cpp +++ b/indra/llui/lltoolbar.cpp @@ -1091,11 +1091,24 @@ LLToolBarButton* LLToolBar::createButton(const LLCommandId& id) enable_callback_t isEnabledCB; const std::string& isEnabledFunction = commandp->isEnabledFunctionName(); - if (isEnabledFunction.length() > 0) + std::string resolvedEnabledFunction = isEnabledFunction; + LLSD resolvedEnabledParam = commandp->isEnabledParameters(); + + if (resolvedEnabledFunction.empty() && + (commandp->executeFunctionName() == "Floater.Toggle" || + commandp->executeFunctionName() == "Floater.ToggleOrBringToFront" || + commandp->executeFunctionName() == "Floater.Show" || + commandp->executeFunctionName() == "Floater.ShowOrBringToFront")) + { + resolvedEnabledFunction = "Floater.CanShow"; + resolvedEnabledParam = commandp->executeParameters(); + } + + if (resolvedEnabledFunction.length() > 0) { LLUICtrl::EnableCallbackParam isEnabledParam; - isEnabledParam.function_name = isEnabledFunction; - isEnabledParam.parameter = commandp->isEnabledParameters(); + isEnabledParam.function_name = resolvedEnabledFunction; + isEnabledParam.parameter = resolvedEnabledParam; isEnabledCB = initEnableCallback(isEnabledParam); if (NULL == button->mIsEnabledSignal) diff --git a/indra/llui/llui.cpp b/indra/llui/llui.cpp index 6fca1a0cbf3..97e84f80020 100644 --- a/indra/llui/llui.cpp +++ b/indra/llui/llui.cpp @@ -193,9 +193,7 @@ mHelpImpl(NULL) // Used by menus along with Floater.Toggle to display visibility as a check-mark LLUICtrl::EnableCallbackRegistry::defaultRegistrar().add("Floater.Visible", [](LLUICtrl* ctrl, const LLSD& param) -> bool { return LLFloaterReg::instanceVisible(param.asStringRef(), LLSD()); }); LLUICtrl::EnableCallbackRegistry::defaultRegistrar().add("Floater.IsOpen", [](LLUICtrl* ctrl, const LLSD& param) -> bool { return LLFloaterReg::instanceVisible(param.asStringRef(), LLSD()); }); -// [RLVa:KB] - Checked: 2012-02-07 (RLVa-1.4.5) | Added: RLVa-1.4.5 - LLUICtrl::EnableCallbackRegistry::defaultRegistrar().add("Floater.CanShow", boost::bind(&LLFloaterReg::canShowInstance, _2, LLSD())); -// [/RLVa:KB] + LLUICtrl::EnableCallbackRegistry::defaultRegistrar().add("Floater.CanShow", [](LLUICtrl* ctrl, const LLSD& param) -> bool { return LLFloaterReg::canShowInstance(param.asStringRef(), LLSD().with("silent", true)); }); // Parse the master list of commands LLCommandManager::load(); diff --git a/indra/llxml/llcontrol.cpp b/indra/llxml/llcontrol.cpp index cef0d888fc7..caf313c546f 100644 --- a/indra/llxml/llcontrol.cpp +++ b/indra/llxml/llcontrol.cpp @@ -1024,6 +1024,7 @@ U32 LLControlGroup::loadFromFile(const std::string& filename, bool set_default_v U32 validitems = 0; bool hidefromsettingseditor = false; + std::vector files_to_include; for(LLSD::map_const_iterator itr = settings.beginMap(); itr != settings.endMap(); ++itr) { @@ -1035,17 +1036,10 @@ U32 LLControlGroup::loadFromFile(const std::string& filename, bool set_default_v { if(control_map.isArray()) { -#if LL_WINDOWS - size_t pos = filename.find_last_of('\\'); -#else - size_t pos = filename.find_last_of('/'); -#endif - if(pos != std::string::npos) + for(LLSD::array_const_iterator array_itr = control_map.beginArray(), array_end = control_map.endArray(); + array_itr != array_end; ++array_itr) { - const std::string dir = filename.substr(0,++pos); - for(LLSD::array_const_iterator array_itr = control_map.beginArray(), array_end = control_map.endArray(); - array_itr != array_end; ++array_itr) - validitems+=loadFromFile(dir+(*array_itr).asString(),set_default_values); + files_to_include.push_back((*array_itr).asString()); } } continue; @@ -1171,6 +1165,19 @@ U32 LLControlGroup::loadFromFile(const std::string& filename, bool set_default_v ++validitems; } + if (!files_to_include.empty()) + { + size_t pos = filename.find_last_of("\\/"); + if(pos != std::string::npos) + { + const std::string dir = filename.substr(0,++pos); + for(const std::string& inc_file : files_to_include) + { + validitems += loadFromFile(dir + inc_file, set_default_values); + } + } + } + LL_DEBUGS("Settings") << "Loaded " << validitems << " settings from " << filename << LL_ENDL; return validitems; } diff --git a/indra/llxml/tests/llcontrol_test.cpp b/indra/llxml/tests/llcontrol_test.cpp index e15117fc29e..7b867a05210 100644 --- a/indra/llxml/tests/llcontrol_test.cpp +++ b/indra/llxml/tests/llcontrol_test.cpp @@ -35,6 +35,7 @@ #include "../test/lltut.h" #include #include +#include namespace tut { @@ -151,4 +152,47 @@ namespace tut ensure("listener fired on changed setting", mListenerFired); } + // include file handling + template<> template<> + void control_group_t::test<5>() + { + // Write included settings file + std::string included_file = mTestConfigDir + "included_settings.xml"; + mCleanups.push_back(included_file); + + LLSD inc_config; + inc_config["IncludedSetting"]["Comment"] = "Dummy setting in included file"; + inc_config["IncludedSetting"]["Persist"] = 1; + inc_config["IncludedSetting"]["Type"] = "U32"; + inc_config["IncludedSetting"]["Value"] = 42; + + llofstream inc_file(included_file.c_str()); + if (inc_file.is_open()) + { + LLSDSerialize::toPrettyXML(inc_config, inc_file); + } + inc_file.close(); + + // Write master settings file that includes the other one + std::string master_file = mTestConfigDir + "master.xml"; + mCleanups.push_back(master_file); + + // Force the path of master_file to have forward slashes to test the fix + std::replace(master_file.begin(), master_file.end(), '\\', '/'); + + LLSD master_config; + master_config["Include"] = LLSD::emptyArray(); + master_config["Include"].append("included_settings.xml"); + + llofstream mast_file(master_file.c_str()); + if (mast_file.is_open()) + { + LLSDSerialize::toPrettyXML(master_config, mast_file); + } + mast_file.close(); + + int results = mCG->loadFromFile(master_file, true); + ensure("loaded include setting successfully", (mCG->getU32("IncludedSetting") == 42)); + } + } diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 42c23a6fc66..3f0064eb640 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -253,8 +253,6 @@ set(viewer_SOURCE_FILES llbrowsernotification.cpp llbuycurrencyhtml.cpp llcallingcard.cpp - llcefaccelinterop.cpp - llcefsurfacereceiver.cpp llchannelmanager.cpp #llchatbar.cpp llchathistory.cpp @@ -696,6 +694,9 @@ set(viewer_SOURCE_FILES llpreviewgesture.cpp llpreviewnotecard.cpp llpreviewscript.cpp + llestimwsmgr.cpp + llestimrlvhandler.cpp + llfloaterestim.cpp llpreviewsound.cpp llpreviewtexture.cpp llproductinforequest.cpp @@ -1023,8 +1024,6 @@ set(viewer_HEADER_FILES llbuycurrencyhtml.h llcallingcard.h llcapabilityprovider.h - llcefaccelinterop.h - llcefsurfacereceiver.h llchannelmanager.h #llchatbar.h llchathistory.h @@ -1455,6 +1454,9 @@ set(viewer_HEADER_FILES llpreviewgesture.h llpreviewnotecard.h llpreviewscript.h + llestimwsmgr.h + llestimrlvhandler.h + llfloaterestim.h llpreviewsound.h llpreviewtexture.h llproductinforequest.h @@ -2006,23 +2008,14 @@ if (NOT DISABLE_WEBRTC) endif() endif() -if (DARWIN) - find_library(IOSURFACE_FRAMEWORK IOSurface) - find_library(COREGRAPHICS_FRAMEWORK CoreGraphics) - target_link_libraries(${VIEWER_BINARY_NAME} - ${IOSURFACE_FRAMEWORK} - ${COREGRAPHICS_FRAMEWORK} - ) -endif() - # These are the generated targets that are copied for packaging that we do not directly link to # We special case for windows due to targets incompatible with Debug builds if (WINDOWS) - target_link_libraries(${VIEWER_BINARY_NAME} d3d11 dxgi ) add_custom_target(copy_input_dependencies DEPENDS stage_third_party_libs - $ + SLPlugin + $<$:$> $ $<$:$> $ @@ -2031,6 +2024,7 @@ else() add_custom_target(copy_input_dependencies DEPENDS stage_third_party_libs + SLPlugin $ $ $ diff --git a/indra/newview/alavataractions.cpp b/indra/newview/alavataractions.cpp index ed9e39fd8ac..7813c14b19e 100644 --- a/indra/newview/alavataractions.cpp +++ b/indra/newview/alavataractions.cpp @@ -43,6 +43,8 @@ #include "llfloaterreporter.h" #include "llfloaterwebcontent.h" #include "llslurl.h" +#include "rlvactions.h" +#include "rlvhandler.h" #include "llviewercontrol.h" #include "llviewermenu.h" #include "llviewerobjectlist.h" @@ -870,6 +872,11 @@ static void on_avatar_name_show_profile(const LLUUID& agent_id, const LLAvatarNa // static void ALAvatarActions::showWebProfile(const LLUUID& id) { + if (RlvActions::isRlvEnabled() && gRlvHandler.hasBehaviour(RLV_BHVR_SHOWPROFILES) && id != gAgent.getID()) + { + return; + } + if (id.notNull()) { LLAvatarNameCache::get(id, boost::bind(&on_avatar_name_show_profile, _1, _2)); diff --git a/indra/newview/alfloaterprofilelegacy.cpp b/indra/newview/alfloaterprofilelegacy.cpp index f8eba9a45e3..577b03cb612 100644 --- a/indra/newview/alfloaterprofilelegacy.cpp +++ b/indra/newview/alfloaterprofilelegacy.cpp @@ -34,6 +34,9 @@ #include "alpanelprofilelegacy.h" #include "llavatarname.h" #include "llavatarnamecache.h" +#include "rlvactions.h" +#include "rlvhandler.h" +#include "llagent.h" ALFloaterProfileLegacy::ALFloaterProfileLegacy(LLSD const& key) : LLFloater(key) @@ -57,6 +60,12 @@ void ALFloaterProfileLegacy::onOpen(const LLSD& key) if (!key.has("avatar_id")) return; const LLUUID av_id = key["avatar_id"].asUUID(); + if (RlvActions::isRlvEnabled() && gRlvHandler.hasBehaviour(RLV_BHVR_SHOWPROFILES) && av_id != gAgent.getID()) + { + closeFloater(); + return; + } + mAvatarNameCacheConnection = LLAvatarNameCache::get(av_id, boost::bind(&ALFloaterProfileLegacy::onAvatarNameCache, this, _1, _2)); diff --git a/indra/newview/alfloatersceneexplorer.cpp b/indra/newview/alfloatersceneexplorer.cpp index edc1e63f598..0353dea01c4 100644 --- a/indra/newview/alfloatersceneexplorer.cpp +++ b/indra/newview/alfloatersceneexplorer.cpp @@ -1259,17 +1259,6 @@ void ALFloaterSceneExplorer::reconcile() const bool is_attachment = obj->isAttachment(); if ((is_avatar || is_attachment) && !mShowAvatars) continue; - // RLVa: when nearby-agent presence is restricted, other avatars (and - // their attachments) must not be listed at all; self always stays. - // Re-evaluated every pass, so imposing/lifting the restriction prunes - // or restores the rows within a reconcile tick. - if ((is_avatar || is_attachment) && RlvActions::isRlvEnabled() - && !RlvActions::canShowNearbyAgents()) - { - const LLVOAvatar* wearer = is_avatar ? avatarp : obj->getAvatarAncestor(); - if (wearer && !wearer->isSelf()) - continue; - } // Unselectable objects (water/sky/ground, no-select prims) can neither be // fetched from the server nor edited, so leave them out entirely. if (!is_avatar && !obj->mbCanSelect) diff --git a/indra/newview/alfloaterwebprofile.cpp b/indra/newview/alfloaterwebprofile.cpp index 7742665a229..e71db08166c 100644 --- a/indra/newview/alfloaterwebprofile.cpp +++ b/indra/newview/alfloaterwebprofile.cpp @@ -32,6 +32,9 @@ #include "llcommandhandler.h" #include "llviewercontrol.h" #include "llweb.h" +#include "rlvactions.h" +#include "rlvhandler.h" +#include "llagent.h" ALFloaterWebProfile::ALFloaterWebProfile(const Params& key) : LLFloaterWebContent(key) @@ -41,6 +44,11 @@ ALFloaterWebProfile::ALFloaterWebProfile(const Params& key) : void ALFloaterWebProfile::onOpen(const LLSD& key) { Params p(key); + if (RlvActions::isRlvEnabled() && gRlvHandler.hasBehaviour(RLV_BHVR_SHOWPROFILES) && p.id() != gAgent.getID().asString()) + { + closeFloater(); + return; + } p.show_chrome(true); p.window_class("web_content"); p.allow_address_entry(false); diff --git a/indra/newview/app_settings/commands.xml b/indra/newview/app_settings/commands.xml index c7374b81d2a..9b01b18cf9b 100644 --- a/indra/newview/app_settings/commands.xml +++ b/indra/newview/app_settings/commands.xml @@ -29,6 +29,8 @@ tooltip_ref="Command_Appearance_Tooltip" execute_function="Floater.ToggleOrBringToFront" execute_parameters="appearance" + is_enabled_function="RLV.EnableIfNot" + is_enabled_parameters="showoutfits" is_running_function="Floater.IsOpen" is_running_parameters="appearance" /> @@ -62,6 +64,8 @@ tooltip_ref="Command_Conversations_Tooltip" execute_function="Floater.ToggleOrBringToFront" execute_parameters="im_container" + is_enabled_function="RLV.EnableIfNot" + is_enabled_parameters="showconversation" is_running_function="Floater.IsOpen" is_running_parameters="im_container" /> @@ -92,6 +96,8 @@ tooltip_ref="Command_Destinations_Tooltip" execute_function="Floater.ToggleOrBringToFront" execute_parameters="destinations" + is_enabled_function="RLV.EnableIfNot" + is_enabled_parameters="showdestinations" is_running_function="Floater.IsOpen" is_running_parameters="destinations" /> @@ -208,6 +214,8 @@ tooltip_ref="Command_People_Tooltip" execute_function="Floater.ToggleOrBringToFront" execute_parameters="people" + is_enabled_function="RLV.EnableIfNot" + is_enabled_parameters="showpeople" is_running_function="Floater.IsOpen" is_running_parameters="people" /> @@ -256,6 +264,8 @@ tooltip_ref="Command_Preferences_Tooltip" execute_function="Floater.ToggleOrBringToFront" execute_parameters="preferences" + is_enabled_function="RLV.EnableIfNot" + is_enabled_parameters="showpreferences" is_running_function="Floater.IsOpen" is_running_parameters="preferences" /> @@ -334,6 +344,8 @@ tooltip_ref="Command_Search_Tooltip" execute_function="Floater.ToggleOrBringToFront" execute_parameters="search" + is_enabled_function="RLV.EnableIfNot" + is_enabled_parameters="showsearch" is_running_function="Floater.IsOpen" is_running_parameters="search" /> @@ -384,6 +396,8 @@ tooltip_ref="Command_View_Tooltip" execute_function="Floater.ToggleOrBringToFront" execute_parameters="camera" + is_enabled_function="RLV.EnableIfNot" + is_enabled_parameters="showcamera" is_running_function="Floater.IsOpen" is_running_parameters="camera" /> @@ -452,4 +466,15 @@ is_running_function="Floater.IsOpen" is_running_parameters="web_content" /> + + diff --git a/indra/newview/app_settings/settings_alchemy.xml b/indra/newview/app_settings/settings_alchemy.xml index f0251c620fc..48e4cf2394e 100644 --- a/indra/newview/app_settings/settings_alchemy.xml +++ b/indra/newview/app_settings/settings_alchemy.xml @@ -2,49 +2,71 @@ - ALCefAcceleratedPaint + RLVStartupLock Comment - Use CEF GPU shared-texture (accelerated paint) zero-copy path for browser media instead of CPU pixel readback/upload. Requires a viewer restart. (CEF daemon rework, on by default; falls back to CPU paint where the GPU interop is unavailable.) + Enables RLV startup screen lock. Persist 1 Type Boolean Value - 0 + 1 - ALCefDaemonEnabled + RLVVersionViewerString Comment - Host all browser (CEF) media instances in a single shared tab-manager daemon process instead of one process per instance. Requires a viewer restart. (CEF daemon rework, on by default.) + Custom identification string returned by @versionviewer command. Persist 1 Type - Boolean + String Value - 1 + Alchemy Test - ALCefDedicatedHost + RLVStartupLockMinDelay Comment - Launch browser (CEF) media via the dedicated SLPluginCEF host, which statically links the CEF plugin instead of dlopen()ing it (avoids the Linux static-TLS crash; prerequisite for the sandbox). Requires a viewer restart. (CEF daemon rework, on by default.) + Minimum delay in seconds before RLV startup lock is released. Persist 1 Type - Boolean + F32 Value + 5.0 + + RLVStartupLockQuietPeriod + + Comment + Quiet period in seconds (no new RLV commands received) before startup lock is released. + Persist 1 + Type + F32 + Value + 2.0 - ALCefSandbox + RLVStartupLockTimeout Comment - Enable the Chromium sandbox for CEF browser sub-processes (renderer/GPU/utility). Requires a viewer restart. (CEF daemon rework, on by default.) + Maximum timeout in seconds to wait for RLV startup lock. Persist 1 Type - Boolean + F32 Value + 15.0 + + SkinCurrent + + Comment + The currently selected skin. + Persist 1 + Type + String + Value + alchemy ALSceneExplorerActivateAction @@ -2995,6 +3017,28 @@ Value 1 + EstimMaxIntensityCap + + Comment + Maximum intensity percentage cap for E-stim devices (0-255). + Persist + 1 + Type + U32 + Value + 255 + + EstimWebsocketPort + + Comment + Websocket server port for the e-stim daemon connection. + Persist + 1 + Type + U32 + Value + 9030 + diff --git a/indra/newview/llagentcamera.cpp b/indra/newview/llagentcamera.cpp index 41f349b148e..a860b4af52c 100644 --- a/indra/newview/llagentcamera.cpp +++ b/indra/newview/llagentcamera.cpp @@ -318,6 +318,12 @@ void LLAgentCamera::resetView(bool reset_camera, bool change_camera) gMenuHolder->hideMenus(); } + // [RLVa:KB] / Alchemy: If locked in mouselook, do not allow resetting/changing camera mode, toolset, or cursor. + if (RlvActions::isRlvEnabled() && gRlvHandler.hasBehaviour(RLV_BHVR_LOCKMOUSELOOK) && mCameraMode == CAMERA_MODE_MOUSELOOK) + { + return; + } + if (change_camera && !gSavedSettings.getBOOL("FreezeTime")) { changeCameraToDefault(); @@ -2475,6 +2481,11 @@ void LLAgentCamera::changeCameraToDefault() //----------------------------------------------------------------------------- void LLAgentCamera::changeCameraToFollow(bool animate) { + if (RlvActions::isRlvEnabled() && gRlvHandler.hasBehaviour(RLV_BHVR_LOCKMOUSELOOK) && mCameraMode == CAMERA_MODE_MOUSELOOK) + { + return; + } + if (LLViewerJoystick::getInstance()->getOverrideCamera()) { return; @@ -2531,6 +2542,11 @@ void LLAgentCamera::changeCameraToFollow(bool animate) //----------------------------------------------------------------------------- void LLAgentCamera::changeCameraToThirdPerson(bool animate) { + if (RlvActions::isRlvEnabled() && gRlvHandler.hasBehaviour(RLV_BHVR_LOCKMOUSELOOK) && mCameraMode == CAMERA_MODE_MOUSELOOK) + { + return; + } + if (LLViewerJoystick::getInstance()->getOverrideCamera()) { return; diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index b635e6e4dce..2d0213c9026 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -187,6 +187,7 @@ #include "llpolymesh.h" #include "llproxy.h" #include "llaudioengine.h" +#include "llestimwsmgr.h" #include "llstreamingaudio.h" #include "llviewermenu.h" #include "llselectmgr.h" @@ -1276,6 +1277,16 @@ bool LLAppViewer::init() gDirUtilp->deleteDirAndContents(gDirUtilp->getDumpLogsDirPath()); } #endif + + // Instantiate and start e-stim WebSocket server + { + LLWebsocketMgr& wsmgr = LLWebsocketMgr::instance(); + U16 port = (U16)gSavedSettings.getU32("EstimWebsocketPort"); + auto estim_server = std::make_shared(LLEstimWSServer::DEFAULT_SERVER_NAME, port, true); + wsmgr.addServer(estim_server); + wsmgr.startServer(LLEstimWSServer::DEFAULT_SERVER_NAME); + } + LL_PROFILER_FRAME_END; return true; } @@ -1558,17 +1569,9 @@ bool LLAppViewer::doFrame() ms_sleep(non_interactive_ms_sleep_time); } - // A native picker steals key focus but is serviced by this loop, so - // don't background-yield while one is open or it turns laggy. - bool native_dialog_open = false; -#if LL_SDL_WINDOW - native_dialog_open = LLWindowSDL::dialogOpen(); -#endif - // yield cooperatively when not running as foreground window // and when not quiting (causes trouble at mac's cleanup stage) if (!LLApp::isExiting() - && !native_dialog_open && ((gViewerWindow && !gViewerWindow->getWindow()->getVisible()) || !gFocusMgr.getAppHasFocus())) { @@ -2834,11 +2837,9 @@ bool LLAppViewer::initConfiguration() gSavedSettings.setBOOL("RenderDebugGLSession", false); } - LLControlVariable* skinfolder = gSavedSettings.getControl("SkinCurrent"); + const LLControlVariable* skinfolder = gSavedSettings.getControl("SkinCurrent"); if (skinfolder && LLStringUtil::null != skinfolder->getValue().asString()) { - skinfolder->setValue("default", false); - // Examining "Language" may not suffice -- see LLUI::getLanguage() // logic. Unfortunately LLUI::getLanguage() doesn't yet do us much // good because we haven't yet called LLUI::initClass(). diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp index 384b1215595..da0c78cf3fc 100644 --- a/indra/newview/llavataractions.cpp +++ b/indra/newview/llavataractions.cpp @@ -82,6 +82,7 @@ // [RLVa:KB] - Checked: 2011-04-11 (RLVa-1.3.0) #include "rlvactions.h" #include "rlvcommon.h" +#include "rlvhandler.h" // [/RLVa:KB] #if 0 @@ -378,6 +379,11 @@ void LLAvatarActions::startConference(const uuid_vec_t& ids, const LLUUID& float // static void LLAvatarActions::showProfile(const LLUUID& avatar_id) { + if (RlvActions::isRlvEnabled() && gRlvHandler.hasBehaviour(RLV_BHVR_SHOWPROFILES) && avatar_id != gAgent.getID()) + { + return; + } + if (avatar_id.notNull()) { if (gSkinSettings.getBOOL("LegacyProfile")) diff --git a/indra/newview/llfloaterimsessiontab.h b/indra/newview/llfloaterimsessiontab.h index a89de3ceaf1..49e6cb9a6f2 100644 --- a/indra/newview/llfloaterimsessiontab.h +++ b/indra/newview/llfloaterimsessiontab.h @@ -52,7 +52,6 @@ class LLFloaterIMSessionTab using super = LLTransientDockableFloater; // [RLVa:KB] - @shownames friend struct RlvCommandHandler; - friend struct RlvCommandHandler; // [/RLVa:KB] public: diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 3b286af56b1..b1e81167750 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -704,7 +704,7 @@ skin_t manifestFromJson(const std::string& filename, const ESkinType type) void LLFloaterPreference::loadUserSkins() { mUserSkins.clear(); -#if 1 +#if 0 const std::string& fullpath = gDirUtilp->add(gDirUtilp->getSkinBaseDir(), "default"); if (LLFile::isdir(fullpath)) { diff --git a/indra/newview/llfloaterprofile.cpp b/indra/newview/llfloaterprofile.cpp index ee92785b6fc..e5d0dd34dcf 100644 --- a/indra/newview/llfloaterprofile.cpp +++ b/indra/newview/llfloaterprofile.cpp @@ -32,6 +32,8 @@ #include "llnotificationsutil.h" #include "llpanelavatar.h" #include "llpanelprofile.h" +#include "rlvactions.h" +#include "rlvhandler.h" static const std::string PANEL_PROFILE_VIEW = "panel_profile_view"; @@ -53,6 +55,12 @@ LLFloaterProfile::~LLFloaterProfile() void LLFloaterProfile::onOpen(const LLSD& key) { + if (RlvActions::isRlvEnabled() && gRlvHandler.hasBehaviour(RLV_BHVR_SHOWPROFILES) && mAvatarId != gAgent.getID()) + { + closeFloater(); + return; + } + mPanelProfile->onOpen(key); // Update the avatar name. diff --git a/indra/newview/llpanelgrouproles.cpp b/indra/newview/llpanelgrouproles.cpp index 61c0843506d..0c3320e7cfc 100644 --- a/indra/newview/llpanelgrouproles.cpp +++ b/indra/newview/llpanelgrouproles.cpp @@ -26,6 +26,10 @@ #include "llviewerprecompiledheaders.h" +// [RLVa:KB] - @blockcontacts +#include "rlvhandler.h" +// [/RLVa:KB] + #include "llcheckboxctrl.h" #include "llagent.h" diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp index ff79184dde8..7bfd4908b9f 100644 --- a/indra/newview/llpanelobjectinventory.cpp +++ b/indra/newview/llpanelobjectinventory.cpp @@ -72,6 +72,7 @@ #include "rlvhandler.h" #include "rlvlocks.h" // [/RLVa:KB] +#include "llpanelcontents.h" const LLColor4U DEFAULT_WHITE(255, 255, 255); @@ -1639,6 +1640,12 @@ void LLPanelObjectInventory::updateInventory() mFolders->requestArrange(); } + LLPanelContents* parent_panel = dynamic_cast(getParent()); + if (parent_panel) + { + parent_panel->updateButtons(); + } + mInventoryNeedsUpdate = false; // Edit menu handler is set in onFocusReceived } diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp index a4a19c518dd..8cdd8d0d900 100644 --- a/indra/newview/llpanelpeople.cpp +++ b/indra/newview/llpanelpeople.cpp @@ -78,6 +78,7 @@ #include "llslurl.h" // [RLVa:KB] - Checked: RLVa-1.2.2 #include "rlvactions.h" +#include "rlvhandler.h" // [/RLVa:KB] @@ -763,6 +764,8 @@ bool LLPanelPeople::postBuild() mOnlineFriendList->setRefreshCompleteCallback(boost::bind(&LLPanelPeople::onFriendListRefreshComplete, this, _1, _2)); mAllFriendList->setRefreshCompleteCallback(boost::bind(&LLPanelPeople::onFriendListRefreshComplete, this, _1, _2)); + rlvUpdateTabStates(); + return true; } @@ -814,10 +817,17 @@ void LLPanelPeople::updateFriendListHelpText() //update help text for empty lists const std::string& filter = mSavedOriginalFilters[mTabContainer->getCurrentPanelIndex()]; - std::string message_name = filter.empty() ? "no_friends_msg" : "no_filtered_friends_msg"; - LLStringUtil::format_map_t args; - args["[SEARCH_TERM]"] = LLURI::escape(filter); - no_friends_text->setText(getString(message_name, args)); + if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWFRIENDS)) + { + no_friends_text->setText(RlvStrings::getString("blocked_friends")); + } + else + { + std::string message_name = filter.empty() ? "no_friends_msg" : "no_filtered_friends_msg"; + LLStringUtil::format_map_t args; + args["[SEARCH_TERM]"] = LLURI::escape(filter); + no_friends_text->setText(getString(message_name, args)); + } } else { @@ -832,6 +842,11 @@ void LLPanelPeople::updateFriendList() if (!mOnlineFriendList || !mAllFriendList) return; + if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWFRIENDS)) + { + return; + } + // get all buddies we know about const LLAvatarTracker& av_tracker = LLAvatarTracker::instance(); LLAvatarTracker::buddy_map_t all_buddies; @@ -890,42 +905,7 @@ void LLPanelPeople::updateNearbyList() std::vector positions; -// [RLVa:KB] - Checked: RLVa-2.0.3 - if (RlvActions::canShowNearbyAgents()) - { -// [/RLVa:KB] - LLWorld::getInstance()->getAvatars(&mNearbyList->getIDs(), &positions, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange")); - int count_in_region = 0; - LLViewerRegion* cur_region = gAgent.getRegion(); - - if (!cur_region) - { - LL_WARNS() << "Current region is null" << LL_ENDL; - return; - } - - // Iterate through avatars in the region. - // The nearby list reports the avatars in 4096m range (`ALControlCache::NearMeRange`) - // Reported UUIDs may not be in same region. - // Also the TOTAL changes based on your filter results -- Fallen - for (size_t i = 0; i < positions.size(); ++i) - { - if (cur_region->pointInRegionGlobal(positions[i])) - { - count_in_region++; - } - } - - mNearbyCountText->setTextArg("[TOTAL]", std::to_string(mNearbyList->size())); - mNearbyCountText->setTextArg("[COUNT]", std::to_string(count_in_region)); - mNearbyCountText->setTextArg("[REGION]", RlvActions::canShowLocation() ? cur_region->getName() : "[REDACTED]"); -// [RLVa:KB] - Checked: RLVa-2.0.3 - } - else - { - mNearbyList->getIDs().clear(); - } -// [/RLVa:KB] + LLWorld::getInstance()->getAvatars(&mNearbyList->getIDs(), &positions, gAgent.getPositionGlobal(), gSavedSettings.getF32("NearMeRange")); mNearbyList->setDirty(); #ifdef LL_DISCORD if (gSavedSettings.getBOOL("EnableDiscord")) @@ -941,6 +921,11 @@ void LLPanelPeople::updateRecentList() if (!mRecentList) return; + if (gRlvHandler.hasBehaviour(RLV_BHVR_SHOWFRIENDS)) + { + return; + } + LLRecentPeople::instance().get(mRecentList->getIDs()); mRecentList->setDirty(); } @@ -1637,6 +1622,38 @@ void LLPanelPeople::onOpen(const LLSD& key) // } //} } + rlvUpdateTabStates(); +} + +void LLPanelPeople::rlvUpdateTabStates() +{ + if (!mTabContainer) + return; + + bool enable = !(RlvActions::isRlvEnabled() && gRlvHandler.hasBehaviour(RLV_BHVR_SHOWFRIENDS)); + + LLPanel* pFriendsPanel = mTabContainer->getPanelByName(FRIENDS_TAB_NAME); + if (pFriendsPanel) + { + S32 idx = mTabContainer->getIndexForPanel(pFriendsPanel); + mTabContainer->enableTabButton(idx, enable); + } + + LLPanel* pRecentPanel = mTabContainer->getPanelByName(RECENT_TAB_NAME); + if (pRecentPanel) + { + S32 idx = mTabContainer->getIndexForPanel(pRecentPanel); + mTabContainer->enableTabButton(idx, enable); + } + + if (!enable) + { + const std::string& active_tab = getActiveTabName(); + if (active_tab == FRIENDS_TAB_NAME || active_tab == RECENT_TAB_NAME) + { + mTabContainer->selectTabByName(NEARBY_TAB_NAME); + } + } } bool LLPanelPeople::notifyChildren(const LLSD& info) diff --git a/indra/newview/llpanelpeople.h b/indra/newview/llpanelpeople.h index 4665e6986a5..0c8fcdac64f 100644 --- a/indra/newview/llpanelpeople.h +++ b/indra/newview/llpanelpeople.h @@ -61,7 +61,12 @@ class LLPanelPeople // [RLVa:KB] - Checked: RLVa-1.2.0 LLAvatarList* getNearbyList() { return mNearbyList; } - void updateNearbyList(); + void rlvUpdateTabStates(); + LLAvatarList* getOnlineFriendList() { return mOnlineFriendList; } + LLAvatarList* getAllFriendList() { return mAllFriendList; } + LLAvatarList* getRecentList() { return mRecentList; } + void rlvUpdateFriendList() { updateFriendList(); } + void rlvUpdateRecentList() { updateRecentList(); } // [/RLVa:KB] // internals @@ -93,7 +98,7 @@ class LLPanelPeople void updateFriendListHelpText(); void updateFriendList(); void updateFriendAccordionTitles(); -// void updateNearbyList(); + void updateNearbyList(); void updateRecentList(); bool isItemsFreeOfFriends(const uuid_vec_t& uuids); diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index bc3e6e8806b..cb6ed9148e1 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -2509,6 +2509,8 @@ bool idle_startup() const F32 wearables_time = wearables_timer.getElapsedTimeF32(); const F32 MAX_WEARABLES_TIME = 10.f; + bool transition_to_cleanup = false; + if (!gAgent.isOutfitChosen() && isAgentAvatarValid()) { // No point in waiting for clothing, we don't even know @@ -2521,7 +2523,7 @@ bool idle_startup() // to generic outfits. JC LLNotificationsUtil::add("WelcomeChooseSex", LLSD(), LLSD(), callback_choose_gender); - LLStartUp::setStartupState( STATE_CLEANUP ); + transition_to_cleanup = true; } do_startup_frame(); @@ -2533,7 +2535,7 @@ bool idle_startup() LLNotificationsUtil::add("ClothingLoading"); } record(LLStatViewer::LOADING_WEARABLES_LONG_DELAY, wearables_time); - LLStartUp::setStartupState( STATE_CLEANUP ); + transition_to_cleanup = true; } else if (gAgent.isFirstLogin() && isAgentAvatarValid() @@ -2544,8 +2546,7 @@ bool idle_startup() && gAgentAvatarp->isFullyLoaded()) { LL_DEBUGS("Avatar") << "avatar fully loaded" << LL_ENDL; - LLStartUp::setStartupState( STATE_CLEANUP ); - return true; + transition_to_cleanup = true; } } else @@ -2555,11 +2556,19 @@ bool idle_startup() { // We have our clothing, proceed. LL_DEBUGS("Avatar") << "wearables loaded" << LL_ENDL; - LLStartUp::setStartupState( STATE_CLEANUP ); - return true; + transition_to_cleanup = true; + } + } + + if (transition_to_cleanup) + { + if (rlv_handler_t::isEnabled() && gSavedSettings.getBOOL("RLVStartupLock")) + { + gRlvHandler.startStartupLock(); } + LLStartUp::setStartupState(STATE_CLEANUP); + return true; } - //fall through this frame to STATE_CLEANUP } if (STATE_CLEANUP == LLStartUp::getStartupState()) diff --git a/indra/newview/lltoolmgr.cpp b/indra/newview/lltoolmgr.cpp index ce9a595621b..fbaf696a4b7 100644 --- a/indra/newview/lltoolmgr.cpp +++ b/indra/newview/lltoolmgr.cpp @@ -280,6 +280,10 @@ bool LLToolMgr::canEdit() bool LLToolMgr::buildEnabledOrActive() { + if (RlvActions::isRlvEnabled() && RlvActions::hasBehaviour(RLV_BHVR_SHOWBUILD)) + { + return false; + } return LLFloaterReg::instanceVisible("build") || canEdit(); } diff --git a/indra/newview/llviewerdisplay.cpp b/indra/newview/llviewerdisplay.cpp index 677cdd495a0..3efc82a2b88 100644 --- a/indra/newview/llviewerdisplay.cpp +++ b/indra/newview/llviewerdisplay.cpp @@ -27,6 +27,7 @@ #include "llviewerprecompiledheaders.h" #include "llviewerdisplay.h" +#include "rlvhandler.h" #include "alfloaterprogressview.h" #include "fsyspath.h" diff --git a/indra/newview/llviewerfloaterreg.cpp b/indra/newview/llviewerfloaterreg.cpp index e4b68b67334..23416664453 100644 --- a/indra/newview/llviewerfloaterreg.cpp +++ b/indra/newview/llviewerfloaterreg.cpp @@ -98,6 +98,7 @@ #include "llfloaterexperienceprofile.h" #include "llfloaterexperiences.h" #include "llfloaterexperiencepicker.h" +#include "llfloaterestim.h" #include "llfloaterevent.h" #include "llfloaterfixedenvironment.h" #include "llfloaterfonttest.h" @@ -415,6 +416,7 @@ void LLViewerFloaterReg::registerFloaters() } LLFloaterReg::add("experiences", "floater_experiences.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("local_assets", "floater_local_assets.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + LLFloaterReg::add("estim", "llfloaterestim.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("experience_profile", "floater_experienceprofile.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); LLFloaterReg::add("experience_search", "floater_experience_search.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 65880c511c8..d431253729a 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -3587,7 +3587,7 @@ class LLLandEnableBuyPass : public view_listener_t bool enable_object_edit() { - if (!isAgentAvatarValid()) return false; + if (!isAgentAvatarValid() || !LLToolMgr::getInstance()->buildEnabledOrActive()) return false; // *HACK: The new "prelude" Help Islands have a build sandbox area, // so users need the Edit and Create pie menu options when they are @@ -4689,7 +4689,7 @@ class LLEditEnableCustomizeAvatar : public view_listener_t { // bool new_value = gAgentWearables.areWearablesLoaded(); // [RLVa:KB] - Checked: 2010-04-01 (RLVa-1.2.0c) | Modified: RLVa-1.0.0g - bool new_value = gAgentWearables.areWearablesLoaded() && ((!rlv_handler_t::isEnabled()) || (RlvActions::canStand())); + bool new_value = gAgentWearables.areWearablesLoaded() && ((!rlv_handler_t::isEnabled()) || (RlvActions::canStand() && !gRlvHandler.hasBehaviour(RLV_BHVR_SHOWOUTFITS))); // [/RLVa:KB] return new_value; } @@ -4699,6 +4699,7 @@ class LLEnableEditShape : public view_listener_t { bool handleEvent(const LLSD& userdata) { + if (RlvActions::isRlvEnabled() && gRlvHandler.hasBehaviour(RLV_BHVR_SHOWOUTFITS)) return false; return gAgentWearables.isWearableModifiable(LLWearableType::WT_SHAPE, 0); } }; @@ -4707,6 +4708,7 @@ class LLEnableHoverHeight : public view_listener_t { bool handleEvent(const LLSD& userdata) { + if (RlvActions::isRlvEnabled() && gRlvHandler.hasBehaviour(RLV_BHVR_SHOWOUTFITS)) return false; return gAgent.getRegion() && gAgent.getRegion()->avatarHoverHeightEnabled(); } }; @@ -6629,6 +6631,12 @@ void show_debug_menus() bool debug = gSavedSettings.getBOOL("UseDebugMenus"); bool qamode = gSavedSettings.getBOOL("QAMode"); + if (RlvActions::isRlvEnabled() && gRlvHandler.hasBehaviour(RLV_BHVR_SHOWDEVELOP)) + { + debug = false; + qamode = false; + } + gMenuBarView->setItemVisible("Advanced", debug); // gMenuBarView->setItemEnabled("Advanced", debug); // Don't disable Advanced keyboard shortcuts when hidden @@ -9950,6 +9958,9 @@ class LLToolsSelectTool : public view_listener_t { bool handleEvent(const LLSD& userdata) { + if (!LLToolMgr::getInstance()->buildEnabledOrActive()) + return true; + std::string tool_name = userdata.asString(); if (tool_name == "focus") { @@ -10901,6 +10912,7 @@ void initialize_menus() // [RLVa:KB] - Checked: RLVa-2.0.0 enable.add("RLV.MainToggleVisible", boost::bind(&rlvMenuMainToggleVisible, _1)); + enable.add("RLV.MainToggleEnabled", boost::bind(&rlvMenuMainToggleEnabled, _1)); enable.add("RLV.CanShowName", boost::bind(&rlvMenuCanShowName)); enable.add("RLV.EnableIfNot", boost::bind(&rlvMenuEnableIfNot, _2)); // [/RLVa:KB] diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 2d30f514118..5c8846d1798 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -1186,6 +1186,10 @@ bool LLViewerWindow::handleAnyMouseClick(LLWindow *window, LLCoordGL pos, MASK m bool LLViewerWindow::handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask) { + if (rlv_handler_t::isEnabled() && gSavedSettings.getBOOL("RLVStartupLock") && gRlvHandler.isStartupLockActive()) + { + return true; + } mAllowMouseDragging = false; if (!mMouseDownTimer.getStarted()) { @@ -1202,6 +1206,10 @@ bool LLViewerWindow::handleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask bool LLViewerWindow::handleDoubleClick(LLWindow *window, LLCoordGL pos, MASK mask) { + if (rlv_handler_t::isEnabled() && gSavedSettings.getBOOL("RLVStartupLock") && gRlvHandler.isStartupLockActive()) + { + return true; + } // try handling as a double-click first, then a single-click if that // wasn't handled. bool down = true; @@ -1214,6 +1222,10 @@ bool LLViewerWindow::handleDoubleClick(LLWindow *window, LLCoordGL pos, MASK ma bool LLViewerWindow::handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask) { + if (rlv_handler_t::isEnabled() && gSavedSettings.getBOOL("RLVStartupLock") && gRlvHandler.isStartupLockActive()) + { + return true; + } if (mMouseDownTimer.getStarted()) { mMouseDownTimer.stop(); @@ -1223,18 +1235,30 @@ bool LLViewerWindow::handleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask) } bool LLViewerWindow::handleRightMouseDown(LLWindow *window, LLCoordGL pos, MASK mask) { + if (rlv_handler_t::isEnabled() && gSavedSettings.getBOOL("RLVStartupLock") && gRlvHandler.isStartupLockActive()) + { + return true; + } bool down = true; return gViewerInput.handleMouse(window, pos, mask, CLICK_RIGHT, down); } bool LLViewerWindow::handleRightMouseUp(LLWindow *window, LLCoordGL pos, MASK mask) { + if (rlv_handler_t::isEnabled() && gSavedSettings.getBOOL("RLVStartupLock") && gRlvHandler.isStartupLockActive()) + { + return true; + } bool down = false; return gViewerInput.handleMouse(window, pos, mask, CLICK_RIGHT, down); } bool LLViewerWindow::handleMiddleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask) { + if (rlv_handler_t::isEnabled() && gSavedSettings.getBOOL("RLVStartupLock") && gRlvHandler.isStartupLockActive()) + { + return true; + } bool down = true; gViewerInput.handleMouse(window, pos, mask, CLICK_MIDDLE, down); @@ -3080,6 +3104,12 @@ void LLViewerWindow::draw() LLFontGL::HCENTER, LLFontGL::TOP); } + // Draw RLV Startup Lock Overlay on top of everything + if (rlv_handler_t::isEnabled() && gSavedSettings.getBOOL("RLVStartupLock") && gRlvHandler.isStartupLockActive()) + { + gRlvHandler.drawStartupLockOverlay(); + } + LLUI::setScaleFactor(old_scale_factor); } LLUI::popMatrix(); @@ -3093,6 +3123,10 @@ void LLViewerWindow::draw() // Takes a single keyup event, usually when UI is visible bool LLViewerWindow::handleKeyUp(KEY key, MASK mask) { + if (rlv_handler_t::isEnabled() && gSavedSettings.getBOOL("RLVStartupLock") && gRlvHandler.isStartupLockActive()) + { + return true; + } if (LLSetKeyBindDialog::recordKey(key, mask, false)) { LL_DEBUGS() << "KeyUp handled by LLSetKeyBindDialog" << LL_ENDL; @@ -3149,6 +3183,10 @@ bool LLViewerWindow::handleKeyUp(KEY key, MASK mask) // Takes a single keydown event, usually when UI is visible bool LLViewerWindow::handleKey(KEY key, MASK mask) { + if (rlv_handler_t::isEnabled() && gSavedSettings.getBOOL("RLVStartupLock") && gRlvHandler.isStartupLockActive()) + { + return true; + } // hide tooltips on keypress LLToolTipMgr::instance().blockToolTips(); @@ -3496,6 +3534,10 @@ bool LLViewerWindow::handleUnicodeChar(llwchar uni_char, MASK mask) void LLViewerWindow::handleScrollWheel(LLScrollDelta delta) { + if (rlv_handler_t::isEnabled() && gSavedSettings.getBOOL("RLVStartupLock") && gRlvHandler.isStartupLockActive()) + { + return; + } LLUI::getInstance()->resetMouseIdleTimer(); // Scale the precise (smooth) component by the user's sensitivity multiplier. @@ -3704,6 +3746,10 @@ void append_xui_tooltip(LLView* viewp, LLToolTip::Params& params) // event processing. void LLViewerWindow::updateUI() { + if (rlv_handler_t::isEnabled() && gSavedSettings.getBOOL("RLVStartupLock") && gRlvHandler.isStartupLockActive()) + { + return; + } LL_PROFILE_ZONE_SCOPED_CATEGORY_UI; static std::string last_handle_msg; @@ -4873,7 +4919,7 @@ LLViewerObject* LLViewerWindow::cursorIntersect(S32 mouse_x, S32 mouse_y, F32 de { found = gPipeline.lineSegmentIntersectInWorld(mw_start, mw_end, pick_transparent, pick_rigged, pick_unselectable, pick_reflection_probe, face_hit, intersection, uv, normal, tangent); - if (found && !pick_transparent && intersection) + if (found && !pick_transparent) { gDebugRaycastIntersection = *intersection; } diff --git a/indra/newview/rlvactions.cpp b/indra/newview/rlvactions.cpp index 3a507ff0dcb..1cad033b54c 100644 --- a/indra/newview/rlvactions.cpp +++ b/indra/newview/rlvactions.cpp @@ -215,6 +215,7 @@ bool RlvActions::canSendTypingStart() bool RlvActions::canStartIM(const LLUUID& idRecipient, bool fIgnoreOpen) { + // User can start an IM session with "recipient" (could be an agent or a group) if: // - not generally restricted from starting IM sessions (or the recipient is an exception or inside the exclusion range) // - not specifically restricted from starting an IM session with the recipient @@ -228,6 +229,7 @@ bool RlvActions::canStartIM(const LLUUID& idRecipient, bool fIgnoreOpen) bool RlvActions::canShowName(EShowNamesContext eContext, const LLUUID& idAgent) { + // Handle most common case upfront if (!s_BlockNamesContexts[eContext]) return true; @@ -260,11 +262,6 @@ bool RlvActions::canShowNameTag(const LLVOAvatar* pAvatar) return (nShowNameTagsDist != 0.f) && (dist_vec_squared(pAvatar->getPositionGlobal(), gAgent.getPositionGlobal()) < nShowNameTagsDist * nShowNameTagsDist); } -bool RlvActions::canShowNearbyAgents() -{ - return !gRlvHandler.hasBehaviour(RLV_BHVR_SHOWNEARBY); -} - // Handles: @chatwhisper, @chatnormal and @chatshout EChatType RlvActions::checkChatVolume(EChatType chatType) { @@ -308,10 +305,9 @@ bool RlvActions::canPreviewTextures() { return (!gRlvHandler.hasBehaviour(RLV_BHVR_VIEWTEXTURE)); } - // ============================================================================ // Movement -// +// bool RlvActions::canAcceptTpOffer(const LLUUID& idSender) { diff --git a/indra/newview/rlvcommon.cpp b/indra/newview/rlvcommon.cpp index 38109751909..3e54eb4a26c 100644 --- a/indra/newview/rlvcommon.cpp +++ b/indra/newview/rlvcommon.cpp @@ -457,6 +457,12 @@ std::string RlvStrings::getVersionAbout() RLV_VERSION_MAJOR, RLV_VERSION_MINOR, RLV_VERSION_PATCH, RLVa_VERSION_MAJOR, RLVa_VERSION_MINOR, RLVa_VERSION_PATCH, LLVersionInfo::instance().getBuild()); } +std::string RlvStrings::getViewerName() +{ + std::string custom_string = gSavedSettings.getString("RLVVersionViewerString"); + return custom_string.empty() ? LLVersionInfo::instance().getChannel() : custom_string; +} + std::string RlvStrings::getVersionNum(const LLUUID& idRlvObject) { bool fCompatMode = RlvSettings::isCompatibilityModeObject(idRlvObject); @@ -518,7 +524,7 @@ void RlvUtil::filterLocation(std::string& strUTF8Text) } // Checked: 2010-12-08 (RLVa-1.2.2c) | Modified: RLVa-1.2.2c -void RlvUtil::filterNames(std::string& strUTF8Text, bool fFilterLegacy, bool fClearMatches) +void RlvUtil::filterNames(std::string& strUTF8Text, bool fFilterLegacy) { uuid_vec_t idAgents; LLWorld::getInstance()->getAvatars(&idAgents, NULL); @@ -526,13 +532,13 @@ void RlvUtil::filterNames(std::string& strUTF8Text, bool fFilterLegacy, bool fCl { LLAvatarName avName; // NOTE: if we're agressively culling nearby names then ignore exceptions - if ( (LLAvatarNameCache::get(idAgents[idxAgent], &avName)) && ((fClearMatches) || (!RlvActions::canShowName(RlvActions::SNC_DEFAULT, idAgents[idxAgent]))) ) + if ( (LLAvatarNameCache::get(idAgents[idxAgent], &avName)) && (!RlvActions::canShowName(RlvActions::SNC_DEFAULT, idAgents[idxAgent])) ) { const std::string& strDisplayName = escape_for_regex(avName.getDisplayName()); bool fFilterDisplay = (strDisplayName.length() > 2); const std::string& strLegacyName = avName.getLegacyName(); fFilterLegacy &= (strLegacyName.length() > 2); - const std::string& strAnonym = (!fClearMatches) ? RlvStrings::getAnonym(avName) : LLStringUtil::null; + const std::string& strAnonym = RlvStrings::getAnonym(avName); // If the display name is a subset of the legacy name we need to filter that first, otherwise it's the other way around if (boost::icontains(strLegacyName, strDisplayName)) @@ -804,6 +810,11 @@ bool rlvMenuMainToggleVisible(LLUICtrl* pMenuCtrl) return true; } +bool rlvMenuMainToggleEnabled(LLUICtrl* pMenuCtrl) +{ + return !gRlvHandler.hasBehaviour(RLV_BHVR_LOCKRLV); +} + // Checked: 2011-08-16 (RLVa-1.4.0b) | Added: RLVa-1.4.0b void rlvMenuToggleVisible() { diff --git a/indra/newview/rlvcommon.h b/indra/newview/rlvcommon.h index e74e9ed5f3d..a9da2cf8283 100644 --- a/indra/newview/rlvcommon.h +++ b/indra/newview/rlvcommon.h @@ -145,6 +145,7 @@ class RlvStrings static const std::string& getStringMapPath() { return m_StringMapPath; } static std::string getVersion(const LLUUID& idRlvObject, bool fLegacy = false); static std::string getVersionAbout(); + static std::string getViewerName(); static std::string getVersionImplNum(); static std::string getVersionNum(const LLUUID& idRlvObject); static bool hasString(const std::string& strStringName, bool fCheckCustom = false); @@ -169,7 +170,7 @@ class RlvUtil static bool isNearbyRegion(const std::string& strRegion); // @showloc static void filterLocation(std::string& strUTF8Text); // @showloc - static void filterNames(std::string& strUTF8Text, bool fFilterLegacy = true, bool fClearMatches = false); // @shownames + static void filterNames(std::string& strUTF8Text, bool fFilterLegacy = true); // @shownames static void filterMentions(std::string& strUTF8Text); // @shownames (mention URIs) static void filterScriptQuestions(S32& nQuestions, LLSD& sdPayload); @@ -211,6 +212,7 @@ typedef bool (RlvExtCommandHandler::*rlvExtCommandHandler)(const RlvCommand& rlv // bool rlvMenuMainToggleVisible(LLUICtrl* pMenuItem); +bool rlvMenuMainToggleEnabled(LLUICtrl* pMenuItem); void rlvMenuToggleVisible(); bool rlvMenuCanShowName(); bool rlvMenuEnableIfNot(const LLSD& sdParam); diff --git a/indra/newview/rlvdefines.h b/indra/newview/rlvdefines.h index 2d31f1261a2..8da84196bd2 100644 --- a/indra/newview/rlvdefines.h +++ b/indra/newview/rlvdefines.h @@ -139,7 +139,22 @@ enum ERlvBehaviour { RLV_BHVR_SHOWLOC, // "showloc" RLV_BHVR_SHOWNAMES, // "shownames" RLV_BHVR_SHOWNAMETAGS, // "shownametags" - RLV_BHVR_SHOWNEARBY, + RLV_BHVR_SHOWFRIENDS, + RLV_BHVR_SHOWPROFILES, + RLV_BHVR_SHOWDEVELOP, + RLV_BHVR_LOCKMOUSELOOK, + RLV_BHVR_LOCKRLV, + RLV_BHVR_VERSIONVIEWER, + RLV_BHVR_SHUTDOWN, + RLV_BHVR_HIDEUI, + RLV_BHVR_SHOWSEARCH, + RLV_BHVR_SHOWPEOPLE, + RLV_BHVR_SHOWCONVERSATION, + RLV_BHVR_SHOWOUTFITS, + RLV_BHVR_SHOWBUILD, + RLV_BHVR_SHOWDESTINATIONS, + RLV_BHVR_SHOWCAMERA, + RLV_BHVR_SHOWPREFERENCES, RLV_BHVR_SHOWHOVERTEXT, // "showhovertext" RLV_BHVR_SHOWHOVERTEXTHUD, // "showhovertexthud" RLV_BHVR_SHOWHOVERTEXTWORLD, // "showhovertextworld" @@ -448,6 +463,14 @@ namespace RlvStringKeys /*inline*/ constexpr std::string_view TpRequestRemote = "blocked_tprequest_remote"sv; /*inline*/ constexpr std::string_view ViewXxx = "blocked_viewxxx"sv; /*inline*/ constexpr std::string_view Wireframe = "blocked_wireframe"sv; + /*inline*/ constexpr std::string_view ShowSearch = "blocked_showsearch"sv; + /*inline*/ constexpr std::string_view ShowPeople = "blocked_showpeople"sv; + /*inline*/ constexpr std::string_view ShowConversation = "blocked_showconversation"sv; + /*inline*/ constexpr std::string_view ShowOutfits = "blocked_showoutfits"sv; + /*inline*/ constexpr std::string_view ShowBuild = "blocked_showbuild"sv; + /*inline*/ constexpr std::string_view ShowDestinations = "blocked_showdestinations"sv; + /*inline*/ constexpr std::string_view ShowCamera = "blocked_showcamera"sv; + /*inline*/ constexpr std::string_view ShowPreferences = "blocked_showpreferences"sv; } namespace Hidden diff --git a/indra/newview/rlvhandler.cpp b/indra/newview/rlvhandler.cpp index 9aba1801df8..0d6757b3602 100644 --- a/indra/newview/rlvhandler.cpp +++ b/indra/newview/rlvhandler.cpp @@ -17,9 +17,13 @@ // Generic includes #include "llviewerprecompiledheaders.h" +#include "llviewerwindow.h" +#include "llfontgl.h" +#include "llrender2dutils.h" #include "llagent.h" #include "llappearancemgr.h" #include "llappviewer.h" +#include "lleventtimer.h" #include "llexperiencecache.h" #include "llexperiencelog.h" #include "llgroupactions.h" @@ -27,6 +31,7 @@ #include "llmoveview.h" #include "llslurl.h" #include "llstartup.h" +#include "llframetimer.h" #include "llviewermessage.h" #include "llviewermenu.h" #include "llviewerobjectlist.h" @@ -57,6 +62,7 @@ #include "llworldmapmessage.h" // @tpto #include "llviewertexturelist.h" // @setcam_texture #include "pipeline.h" // @setsphere +#include "lltoolbarview.h" // RLVa includes #include "rlvactions.h" @@ -67,6 +73,7 @@ #include "rlvhandler.h" #include "rlvhelper.h" #include "rlvinventory.h" +#include "llestimrlvhandler.h" #include "rlvlocks.h" #include "rlvmodifiers.h" #include "rlvui.h" @@ -142,7 +149,7 @@ static bool rlvParseGetStatusOption(const std::string& strOption, std::string& s // // Checked: 2010-04-07 (RLVa-1.2.0d) | Modified: RLVa-1.0.1d -RlvHandler::RlvHandler() : m_fCanCancelTp(true), m_posSitSource(), m_pGCTimer(NULL) +RlvHandler::RlvHandler() : m_fCanCancelTp(true), m_posSitSource(), m_pGCTimer(NULL), m_LastCommandTime(0.0), m_StartupLockStarted(false) { // Array auto-initialization to 0 is still not supported in VS2013 memset(m_Behaviours, 0, sizeof(S16) * RLV_BHVR_COUNT); @@ -582,8 +589,18 @@ ERlvCmdRet RlvHandler::processCommand(std::reference_wrapper r // Checked: 2009-11-25 (RLVa-1.1.0f) | Modified: RLVa-1.1.0f ERlvCmdRet RlvHandler::processCommand(const LLUUID& idObj, const std::string& strCommand, bool fFromObj) { + m_LastCommandTime = LLFrameTimer::getElapsedSeconds(); const RlvCommand rlvCmd(idObj, strCommand); - if (STATE_STARTED != LLStartUp::getStartupState()) + bool fAllowProcess = (STATE_STARTED == LLStartUp::getStartupState()); + if (!fAllowProcess && gSavedSettings.getBOOL("RLVStartupLock")) + { + if (LLStartUp::getStartupState() == STATE_WEARABLES_WAIT && gAgentWearables.areWearablesLoaded()) + { + fAllowProcess = true; + } + } + + if (!fAllowProcess) { m_Retained.push_back(rlvCmd); return RLV_RET_RETAINED; @@ -626,7 +643,14 @@ ERlvCmdRet RlvHandler::processClearCommand(const RlvCommand& rlvCmd) if ( (strFilter.empty()) || (std::string::npos != strCmdRem.find(strFilter)) ) { fContinue = (rlvObj.m_Commands.size() > 1); // rlvObj will become invalid once we remove the last command - processCommand(rlvCmd.getObjectID(), strCmdRem.append("=y"), false); + if (strCmdRem.rfind("lockmouselook", 0) == 0 || strCmdRem.rfind("lockrlv", 0) == 0) + { + processCommand(rlvCmd.getObjectID(), strCmdRem.append("=n"), false); + } + else + { + processCommand(rlvCmd.getObjectID(), strCmdRem.append("=y"), false); + } } } } @@ -1548,6 +1572,7 @@ bool RlvHandler::setEnabled(bool fEnable) RlvHandler::instance().addCommandHandler(std::make_unique()); RlvHandler::instance().addCommandHandler(std::make_unique()); + RlvHandler::instance().addCommandHandler(std::make_unique()); // Make sure we get notified when login is successful if (LLStartUp::getStartupState() < STATE_STARTED) @@ -2693,42 +2718,155 @@ ERlvCmdRet RlvBehaviourHandler::onCommand(const RlvComman return RlvBehaviourGenericHandler::onCommand(rlvCmd, fRefCount); } -// Handles: @shownearby=n|y toggles + +// Handles: @showfriends=n|y toggles template<> template<> -void RlvBehaviourToggleHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) +void RlvBehaviourToggleHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) { if (LLApp::isExiting()) return; // Nothing to do if the viewer is shutting down - // Refresh the nearby people list LLPanelPeople* pPeoplePanel = LLFloaterSidePanelContainer::getPanel("people", "panel_people"); - LLAvatarList* pNearbyList = (pPeoplePanel) ? pPeoplePanel->getNearbyList() : NULL; - RLV_ASSERT( (pPeoplePanel) && (pNearbyList) ); - if (pNearbyList) + LLAvatarList* pOnlineFriendsList = (pPeoplePanel) ? pPeoplePanel->getOnlineFriendList() : NULL; + LLAvatarList* pAllFriendsList = (pPeoplePanel) ? pPeoplePanel->getAllFriendList() : NULL; + LLAvatarList* pRecentList = (pPeoplePanel) ? pPeoplePanel->getRecentList() : NULL; + + if (pOnlineFriendsList && pAllFriendsList) { - static std::string s_strNoItemsMsg = pNearbyList->getNoItemsMsg(); - pNearbyList->setNoItemsMsg( (fHasBhvr) ? RlvStrings::getString("blocked_nearby") : s_strNoItemsMsg ); - pNearbyList->clear(); + static std::string s_strNoOnlineFriendsMsg = pOnlineFriendsList->getNoItemsMsg(); + static std::string s_strNoAllFriendsMsg = pAllFriendsList->getNoItemsMsg(); + pOnlineFriendsList->setNoItemsMsg( (fHasBhvr) ? RlvStrings::getString("blocked_friends") : s_strNoOnlineFriendsMsg ); + pAllFriendsList->setNoItemsMsg( (fHasBhvr) ? RlvStrings::getString("blocked_friends") : s_strNoAllFriendsMsg ); + pOnlineFriendsList->clear(); + pAllFriendsList->clear(); + } - if (pNearbyList->isInVisibleChain()) - pPeoplePanel->onCommit(); + if (pRecentList) + { + static std::string s_strNoRecentMsg = pRecentList->getNoItemsMsg(); + pRecentList->setNoItemsMsg( (fHasBhvr) ? RlvStrings::getString("blocked_recent") : s_strNoRecentMsg ); + pRecentList->clear(); + } + + if (pPeoplePanel) + { + pPeoplePanel->rlvUpdateTabStates(); if (!fHasBhvr) - pPeoplePanel->updateNearbyList(); + { + pPeoplePanel->rlvUpdateFriendList(); + pPeoplePanel->rlvUpdateRecentList(); + } } +} -#ifdef CATZNIP - // Refresh the nearby participant list - if (LLFloaterIMNearbyChat* pNearbyChatFloater = LLFloaterReg::findTypedInstance("nearby_chat")) +// Handles: @showprofiles=n|y toggles +template<> template<> +void RlvBehaviourToggleHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) +{ + if (LLApp::isExiting()) + return; + if (fHasBhvr) { + // Close "profile" (new profiles) + LLFloaterReg::const_instance_list_t floaters1 = LLFloaterReg::getFloaterList("profile"); + for (LLFloater* pFloater : floaters1) + { + if (pFloater->getKey()["id"].asUUID() != gAgent.getID()) + pFloater->closeFloater(); + } + + // Close "legacy_profile" (legacy profiles) + LLFloaterReg::const_instance_list_t floaters2 = LLFloaterReg::getFloaterList("legacy_profile"); + for (LLFloater* pFloater : floaters2) + { + if (pFloater->getKey()["avatar_id"].asUUID() != gAgent.getID()) + pFloater->closeFloater(); + } + + // Close "webprofile" (web profiles) + LLFloaterReg::const_instance_list_t floaters3 = LLFloaterReg::getFloaterList("webprofile"); + for (LLFloater* pFloater : floaters3) + { + if (pFloater->getKey()["id"].asUUID() != gAgent.getID()) + pFloater->closeFloater(); + } + } +} + +// Handles: @showdevelop=n|y toggles +template<> template<> +void RlvBehaviourToggleHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) +{ + if (LLApp::isExiting()) + return; + show_debug_menus(); +} - pNearbyChatFloater->updateShowParticipantList(); - pNearbyChatFloater->updateExpandCollapseBtn(); - // *TODO - Solution for CHUI +// Handles: @lockmouselook=y|n toggles +template<> template<> +void RlvBehaviourToggleHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) +{ + if (LLApp::isExiting()) + return; + if (fHasBhvr) + { + gAgentCamera.changeCameraToMouselook(); } -#endif // CATZNIP +} - // Refresh that avatar's name tag and all HUD text - LLHUDText::refreshAllObjectText(); +// Handles: @lockrlv=y|n toggles +template<> template<> +void RlvBehaviourToggleHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) +{ +} + +// Handles: @hideui=n|y toggles +template<> template<> +void RlvBehaviourToggleHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) +{ + if (LLApp::isExiting()) + return; + + LLFloaterView::skip_list_t skip_list; + if (LLFloater* pIMContainer = LLFloaterReg::findInstance("im_container")) + { + skip_list.insert(pIMContainer); + } + if (LLFloater* pNearbyChat = LLFloaterReg::findInstance("nearby_chat")) + { + skip_list.insert(pNearbyChat); + } + + if (fHasBhvr) + { + if (gFloaterView) + { + gFloaterView->pushVisibleAll(false, skip_list); + } + if (gMenuBarView) + { + gMenuBarView->setVisible(false); + } + if (gToolBarView) + { + gToolBarView->setVisible(false); + } + } + else + { + if (gFloaterView) + { + gFloaterView->popVisibleAll(skip_list); + } + if (gMenuBarView) + { + gMenuBarView->setVisible(true); + } + if (gToolBarView) + { + gToolBarView->setVisible(true); + } + } } // Handles: @showself=n|y and @showselfhead=n|y toggles @@ -2759,6 +2897,168 @@ void RlvBehaviourToggleHandler::onCommandToggle(ERlvBeha } } +// Handles: @showsearch=n|y toggles +template<> template<> +void RlvBehaviourToggleHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) +{ + if (LLApp::isExiting()) + return; + if (fHasBhvr) + { + LLFloaterReg::const_instance_list_t floaters = LLFloaterReg::getFloaterList("search"); + for (LLFloater* pFloater : floaters) + pFloater->closeFloater(); + RlvUIEnabler::instance().addGenericFloaterFilter("search", []() { LLNotificationsUtil::add("RLVSearchBlocked"); }); + } + else + { + RlvUIEnabler::instance().removeGenericFloaterFilter("search"); + } +} + +// Handles: @showpeople=n|y toggles +template<> template<> +void RlvBehaviourToggleHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) +{ + if (LLApp::isExiting()) + return; + if (fHasBhvr) + { + LLFloaterReg::const_instance_list_t floaters = LLFloaterReg::getFloaterList("people"); + for (LLFloater* pFloater : floaters) + pFloater->closeFloater(); + RlvUIEnabler::instance().addGenericFloaterFilter("people", []() { LLNotificationsUtil::add("RLVPeopleBlocked"); }); + } + else + { + RlvUIEnabler::instance().removeGenericFloaterFilter("people"); + } +} + +// Handles: @showconversation=n|y toggles +template<> template<> +void RlvBehaviourToggleHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) +{ + if (LLApp::isExiting()) + return; + if (fHasBhvr) + { + LLFloaterReg::const_instance_list_t floaters1 = LLFloaterReg::getFloaterList("conversation"); + for (LLFloater* pFloater : floaters1) + pFloater->closeFloater(); + LLFloaterReg::const_instance_list_t floaters2 = LLFloaterReg::getFloaterList("im_container"); + for (LLFloater* pFloater : floaters2) + pFloater->closeFloater(); + RlvUIEnabler::instance().addGenericFloaterFilter("conversation", []() { LLNotificationsUtil::add("RLVConversationBlocked"); }); + RlvUIEnabler::instance().addGenericFloaterFilter("im_container", []() { LLNotificationsUtil::add("RLVConversationBlocked"); }); + } + else + { + RlvUIEnabler::instance().removeGenericFloaterFilter("conversation"); + RlvUIEnabler::instance().removeGenericFloaterFilter("im_container"); + } +} + +// Handles: @showoutfits=n|y toggles +template<> template<> +void RlvBehaviourToggleHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) +{ + if (LLApp::isExiting()) + return; + if (fHasBhvr) + { + LLFloaterReg::const_instance_list_t floaters = LLFloaterReg::getFloaterList("appearance"); + for (LLFloater* pFloater : floaters) + pFloater->closeFloater(); + RlvUIEnabler::instance().addGenericFloaterFilter("appearance", []() { LLNotificationsUtil::add("RLVAppearanceBlocked"); }); + } + else + { + RlvUIEnabler::instance().removeGenericFloaterFilter("appearance"); + } +} + +// Handles: @showbuild=n|y toggles +template<> template<> +void RlvBehaviourToggleHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) +{ + if (LLApp::isExiting()) + return; + if (fHasBhvr) + { + LLFloaterReg::const_instance_list_t floaters = LLFloaterReg::getFloaterList("build"); + for (LLFloater* pFloater : floaters) + pFloater->closeFloater(); + RlvUIEnabler::instance().addGenericFloaterFilter("build", []() { LLNotificationsUtil::add("RLVBuildBlocked"); }); + } + else + { + RlvUIEnabler::instance().removeGenericFloaterFilter("build"); + } +} + +// Handles: @showdestinations=n|y toggles +template<> template<> +void RlvBehaviourToggleHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) +{ + if (LLApp::isExiting()) + return; + if (fHasBhvr) + { + LLFloaterReg::const_instance_list_t floaters = LLFloaterReg::getFloaterList("destinations"); + for (LLFloater* pFloater : floaters) + pFloater->closeFloater(); + RlvUIEnabler::instance().addGenericFloaterFilter("destinations", []() { LLNotificationsUtil::add("RLVDestinationsBlocked"); }); + } + else + { + RlvUIEnabler::instance().removeGenericFloaterFilter("destinations"); + } +} + +// Handles: @showcamera=n|y toggles +template<> template<> +void RlvBehaviourToggleHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) +{ + if (LLApp::isExiting()) + return; + if (fHasBhvr) + { + LLFloaterReg::const_instance_list_t floaters1 = LLFloaterReg::getFloaterList("camera"); + for (LLFloater* pFloater : floaters1) + pFloater->closeFloater(); + LLFloaterReg::const_instance_list_t floaters2 = LLFloaterReg::getFloaterList("camera_presets"); + for (LLFloater* pFloater : floaters2) + pFloater->closeFloater(); + RlvUIEnabler::instance().addGenericFloaterFilter("camera", []() { LLNotificationsUtil::add("RLVCameraBlocked"); }); + RlvUIEnabler::instance().addGenericFloaterFilter("camera_presets", []() { LLNotificationsUtil::add("RLVCameraBlocked"); }); + } + else + { + RlvUIEnabler::instance().removeGenericFloaterFilter("camera"); + RlvUIEnabler::instance().removeGenericFloaterFilter("camera_presets"); + } +} + +// Handles: @showpreferences=n|y toggles +template<> template<> +void RlvBehaviourToggleHandler::onCommandToggle(ERlvBehaviour eBhvr, bool fHasBhvr) +{ + if (LLApp::isExiting()) + return; + if (fHasBhvr) + { + LLFloaterReg::const_instance_list_t floaters = LLFloaterReg::getFloaterList("preferences"); + for (LLFloater* pFloater : floaters) + pFloater->closeFloater(); + RlvUIEnabler::instance().addGenericFloaterFilter("preferences", []() { LLNotificationsUtil::add("RLVPreferencesBlocked"); }); + } + else + { + RlvUIEnabler::instance().removeGenericFloaterFilter("preferences"); + } +} + // ============================================================================ // Command handlers (RLV_TYPE_FORCE) // @@ -3427,6 +3727,9 @@ ERlvCmdRet RlvHandler::processReplyCommand(const RlvCommand& rlvCmd) const // NOTE: RLV will respond even if there's an option strReply = RlvStrings::getVersion(rlvCmd.getObjectID(), RLV_BHVR_VERSION == rlvCmd.getBehaviourType()); break; + case RLV_BHVR_VERSIONVIEWER: // @versionviewer= + strReply = RlvStrings::getViewerName(); + break; case RLV_BHVR_VERSIONNUM: // @versionnum= - Checked: 2010-03-27 (RLVa-1.4.0a) | Added: RLVa-1.0.4b // NOTE: RLV will respond even if there's an option if (!rlvCmd.hasOption()) @@ -3979,3 +4282,114 @@ ERlvCmdRet RlvHandler::onGetPath(const RlvCommand& rlvCmd, std::string& strReply // // ============================================================================ + +class RlvShutdownTimer : public LLEventTimer +{ +public: + RlvShutdownTimer(S32 seconds) : LLEventTimer(1.0f), mSeconds(seconds) + { + if (mSeconds <= 0) + { + LLAppViewer::instance()->requestQuit(); + } + else + { + LLSD args; + args["SECONDS"] = mSeconds; + LLNotificationsUtil::add("RLVShutdownCountdown", args); + } + } + + bool tick() override + { + if (mSeconds <= 0) + { + return true; + } + mSeconds--; + LLSD args; + args["SECONDS"] = mSeconds; + LLNotificationsUtil::add("RLVShutdownCountdown", args); + if (mSeconds <= 0) + { + LLAppViewer::instance()->requestQuit(); + return true; + } + return false; + } + +private: + S32 mSeconds; +}; + +// Handles: @shutdown[:]=force +template<> template<> +ERlvCmdRet RlvForceHandler::onCommand(const RlvCommand& rlvCmd) +{ + S32 seconds = 10; + if (rlvCmd.hasOption()) + { + if (!RlvCommandOptionHelper::parseOption(rlvCmd.getOption(), seconds)) + { + seconds = 10; + } + } + + new RlvShutdownTimer(seconds); + return RLV_RET_SUCCESS; +} + +bool RlvHandler::isStartupLockActive() +{ + if (!m_StartupLockStarted) + { + return false; + } + + F32 min_delay = gSavedSettings.getF32("RLVStartupLockMinDelay"); + F32 quiet_period = gSavedSettings.getF32("RLVStartupLockQuietPeriod"); + F32 timeout = gSavedSettings.getF32("RLVStartupLockTimeout"); + + F32 elapsed = m_StartupLockTimer.getElapsedTimeF32(); + F64 last_cmd_time = getLastCommandTime(); + F64 time_since_last_cmd = LLFrameTimer::getElapsedSeconds() - last_cmd_time; + + if ((elapsed >= min_delay && (last_cmd_time == 0.0 || time_since_last_cmd >= quiet_period)) || (elapsed >= timeout)) + { + m_StartupLockStarted = false; + return false; + } + + return true; +} + +void RlvHandler::startStartupLock() +{ + m_StartupLockTimer.start(); + m_StartupLockStarted = true; + processRetainedCommands(); +} + +void RlvHandler::drawStartupLockOverlay() +{ + if (!gViewerWindow) + return; + + S32 width = gViewerWindow->getWindowWidthScaled(); + S32 height = gViewerWindow->getWindowHeightScaled(); + + // Draw full screen black quad + gl_rect_2d(0, height, width, 0, LLColor4::black, true); + + // Draw centering text + LLFontGL::getFontSansSerifBig()->renderUTF8( + "Initializing RLV restrictions...", + 0, + width / 2, + height / 2, + LLColor4::white, + LLFontGL::HCENTER, + LLFontGL::VCENTER + ); +} + diff --git a/indra/newview/rlvhandler.h b/indra/newview/rlvhandler.h index e06d879abe6..20c27f38a5c 100644 --- a/indra/newview/rlvhandler.h +++ b/indra/newview/rlvhandler.h @@ -24,6 +24,7 @@ #include "rlvcommon.h" #include "rlvhelper.h" +#include "llframetimer.h" // ============================================================================ // RlvHandler class @@ -118,6 +119,7 @@ class RlvHandler : public LLOldEvents::LLSimpleListener, public LLParticularGrou const LLUUID& getAgentGroup() const { return m_idAgentGroup; } // @setgroup bool getCanCancelTp() const { return m_fCanCancelTp; } // @accepttp and @tpto void setCanCancelTp(bool fAllow) { m_fCanCancelTp = fAllow; } // @accepttp and @tpto + F64 getLastCommandTime() const { return m_LastCommandTime; } const LLVector3d& getSitSource() const { return m_posSitSource; } // @standtp void setSitSource(const LLVector3d& posSource) { m_posSitSource = posSource; } // @standtp @@ -139,6 +141,9 @@ class RlvHandler : public LLOldEvents::LLSimpleListener, public LLParticularGrou static bool canEnable(); static bool isEnabled() { return m_fEnabled; } static bool setEnabled(bool fEnable); + bool isStartupLockActive(); + void startStartupLock(); + void drawStartupLockOverlay(); protected: // Command specific helper functions (NOTE: these generally do not perform safety checks) bool checkActiveGroupThrottle(const LLUUID& idRlvObj); // @setgroup=force @@ -277,6 +282,9 @@ class RlvHandler : public LLOldEvents::LLSimpleListener, public LLParticularGrou std::pair m_GroupChangeExpiration; // @setgroup=force std::string m_strCameraPresetRestore; // @setcam_eyeoffset, @setcam_eyeoffsetscale and @setcam_focusoffset + F64 m_LastCommandTime; + LLFrameTimer m_StartupLockTimer; + bool m_StartupLockStarted; friend class RlvSharedRootFetcher; // Fetcher needs access to m_fFetchComplete friend class RlvGCTimer; // Timer clear its own point at destruction diff --git a/indra/newview/rlvhelper.cpp b/indra/newview/rlvhelper.cpp index 85dadf83122..27c6385c36f 100644 --- a/indra/newview/rlvhelper.cpp +++ b/indra/newview/rlvhelper.cpp @@ -74,6 +74,44 @@ * Implement : void RlvBehaviourModifierHandler::onValueChanged() * */ +class RlvLockMouselookProcessor : public RlvBehaviourGenericToggleProcessor +{ +public: + RlvLockMouselookProcessor() : RlvBehaviourGenericToggleProcessor("lockmouselook") {} + ERlvCmdRet processCommand(const RlvCommand& rlvCmd) const override + { + ERlvParamType eInvertedType = RLV_TYPE_UNKNOWN; + if (rlvCmd.getParamType() == RLV_TYPE_ADD) + eInvertedType = RLV_TYPE_REMOVE; + else if (rlvCmd.getParamType() == RLV_TYPE_REMOVE) + eInvertedType = RLV_TYPE_ADD; + else + eInvertedType = rlvCmd.getParamType(); + + RlvCommand invertedCmd(rlvCmd, eInvertedType); + return RlvBehaviourGenericToggleProcessor::processCommand(invertedCmd); + } +}; + +class RlvLockRlvProcessor : public RlvBehaviourGenericToggleProcessor +{ +public: + RlvLockRlvProcessor() : RlvBehaviourGenericToggleProcessor("lockrlv") {} + ERlvCmdRet processCommand(const RlvCommand& rlvCmd) const override + { + ERlvParamType eInvertedType = RLV_TYPE_UNKNOWN; + if (rlvCmd.getParamType() == RLV_TYPE_ADD) + eInvertedType = RLV_TYPE_REMOVE; + else if (rlvCmd.getParamType() == RLV_TYPE_REMOVE) + eInvertedType = RLV_TYPE_ADD; + else + eInvertedType = rlvCmd.getParamType(); + + RlvCommand invertedCmd(rlvCmd, eInvertedType); + return RlvBehaviourGenericToggleProcessor::processCommand(invertedCmd); + } +}; + RlvBehaviourDictionary::RlvBehaviourDictionary() { // Array auto-initialization to 0 is still not supported in VS2013 @@ -154,7 +192,20 @@ RlvBehaviourDictionary::RlvBehaviourDictionary() addEntry(new RlvBehaviourToggleProcessor("shownames", RlvBehaviourInfo::BHVR_STRICT)); addEntry(new RlvBehaviourProcessor("shownametags", RlvBehaviourInfo::BHVR_STRICT)); addModifier(RLV_BHVR_SHOWNAMETAGS, RLV_MODIFIER_SHOWNAMETAGSDIST, new RlvBehaviourModifierHandler("Name Tags - Visible Distance", 0.0f, true, new RlvBehaviourModifierCompMin)); - addEntry(new RlvBehaviourGenericToggleProcessor("shownearby", RlvBehaviourInfo::BHVR_EXPERIMENTAL)); + addEntry(new RlvBehaviourGenericToggleProcessor("showfriends")); + addEntry(new RlvBehaviourGenericToggleProcessor("showprofiles")); + addEntry(new RlvBehaviourGenericToggleProcessor("showdevelop")); + addEntry(new RlvLockMouselookProcessor()); + addEntry(new RlvLockRlvProcessor()); + addEntry(new RlvBehaviourGenericToggleProcessor("hideui", RlvBehaviourInfo::BHVR_EXPERIMENTAL)); + addEntry(new RlvBehaviourGenericToggleProcessor("showsearch")); + addEntry(new RlvBehaviourGenericToggleProcessor("showpeople")); + addEntry(new RlvBehaviourGenericToggleProcessor("showconversation")); + addEntry(new RlvBehaviourGenericToggleProcessor("showoutfits")); + addEntry(new RlvBehaviourGenericToggleProcessor("showbuild")); + addEntry(new RlvBehaviourGenericToggleProcessor("showdestinations")); + addEntry(new RlvBehaviourGenericToggleProcessor("showcamera")); + addEntry(new RlvBehaviourGenericToggleProcessor("showpreferences")); addEntry(new RlvBehaviourGenericToggleProcessor("showself", RlvBehaviourInfo::BHVR_EXPERIMENTAL)); addEntry(new RlvBehaviourGenericToggleProcessor("showselfhead", RlvBehaviourInfo::BHVR_EXPERIMENTAL)); addEntry(new RlvBehaviourGenericProcessor("showworldmap", RLV_BHVR_SHOWWORLDMAP)); @@ -295,6 +346,7 @@ RlvBehaviourDictionary::RlvBehaviourDictionary() addEntry(new RlvForceProcessor("sit")); addEntry(new RlvForceProcessor("sitground")); addEntry(new RlvForceProcessor("tpto")); + addEntry(new RlvForceProcessor("shutdown", RlvBehaviourInfo::BHVR_EXPERIMENTAL)); addEntry(new RlvBehaviourInfo("unsit", RLV_BHVR_UNSIT, RLV_TYPE_FORCE)); // @@ -330,6 +382,7 @@ RlvBehaviourDictionary::RlvBehaviourDictionary() addEntry(new RlvBehaviourInfo("version", RLV_BHVR_VERSION, RLV_TYPE_REPLY)); addEntry(new RlvBehaviourInfo("versionnew", RLV_BHVR_VERSIONNEW, RLV_TYPE_REPLY)); addEntry(new RlvBehaviourInfo("versionnum", RLV_BHVR_VERSIONNUM, RLV_TYPE_REPLY)); + addEntry(new RlvBehaviourInfo("versionviewer", RLV_BHVR_VERSIONVIEWER, RLV_TYPE_REPLY)); // Populate m_String2InfoMap (the tuple should be unique) for (const RlvBehaviourInfo* pBhvrInfo : m_BhvrInfoList) @@ -731,6 +784,10 @@ RlvCommand::RlvCommand(const LLUUID& idObj, const std::string& strCommand) m_eParamType = RLV_TYPE_FORCE; else if (LLStringUtil::convertToS32(m_strParam, nTemp)) // Assume it's a reply command if we can convert to an S32 m_eParamType = RLV_TYPE_REPLY; + else if (m_strBehaviour.rfind("estim_", 0) == 0) + { + m_eParamType = RLV_TYPE_FORCE; + } else { m_eParamType = RLV_TYPE_UNKNOWN; diff --git a/indra/newview/rlvui.cpp b/indra/newview/rlvui.cpp index 721cca6cbb4..44cfb515113 100644 --- a/indra/newview/rlvui.cpp +++ b/indra/newview/rlvui.cpp @@ -309,6 +309,10 @@ bool RlvUIEnabler::addGenericFloaterFilter(const std::string& strFloaterName, co { m_ConnFloaterGeneric = LLFloaterReg::setValidateCallback(boost::bind(&RlvUIEnabler::filterFloaterGeneric, this, _1, _2)); } + if (!m_ConnPanelGeneric.connected()) + { + m_ConnPanelGeneric = LLFloaterSidePanelContainer::setValidateCallback(boost::bind(&RlvUIEnabler::filterPanelGeneric, this, _1, _2, _3)); + } return true; } @@ -323,22 +327,36 @@ bool RlvUIEnabler::removeGenericFloaterFilter(const std::string& strFloaterName) RLV_ASSERT_DBG(m_ConnFloaterGeneric.connected()); if (m_FilteredFloaterMap.empty()) + { m_ConnFloaterGeneric.disconnect(); + m_ConnPanelGeneric.disconnect(); + } return true; } -bool RlvUIEnabler::filterFloaterGeneric(const std::string& strFloaterName, const LLSD&) +bool RlvUIEnabler::filterFloaterGeneric(const std::string& strFloaterName, const LLSD& sdKey) { auto itFloater = m_FilteredFloaterMap.find(strFloaterName); if (m_FilteredFloaterMap.end() != itFloater) { - if (itFloater->second) + if (itFloater->second && !sdKey["silent"].asBoolean()) itFloater->second(); return false; } return true; +} +bool RlvUIEnabler::filterPanelGeneric(const std::string& strFloaterName, const std::string&, const LLSD& sdKey) +{ + auto itFloater = m_FilteredFloaterMap.find(strFloaterName); + if (m_FilteredFloaterMap.end() != itFloater) + { + if (itFloater->second && !sdKey["silent"].asBoolean()) + itFloater->second(); + return false; + } + return true; } // Checked: 2010-04-22 (RLVa-1.4.5) | Added: RLVa-1.2.0 @@ -368,21 +386,24 @@ bool RlvUIEnabler::filterPanelShowLoc(const std::string& strFloater, const std:: } // Checked: 2010-03-01 (RLVa-1.2.0b) | Added: RLVa-1.2.0a -bool RlvUIEnabler::filterFloaterViewXXX(const std::string& strName, const LLSD&) +bool RlvUIEnabler::filterFloaterViewXXX(const std::string& strName, const LLSD& sdKey) { if ( (gRlvHandler.hasBehaviour(RLV_BHVR_VIEWNOTE)) && ("preview_notecard" == strName) ) { - RlvUtil::notifyBlockedViewXXX(LLAssetType::AT_NOTECARD); + if (!sdKey["silent"].asBoolean()) + RlvUtil::notifyBlockedViewXXX(LLAssetType::AT_NOTECARD); return false; } else if ( (gRlvHandler.hasBehaviour(RLV_BHVR_VIEWSCRIPT)) && (("preview_script" == strName) || ("preview_scriptedit" == strName)) ) { - RlvUtil::notifyBlockedViewXXX(LLAssetType::AT_SCRIPT); + if (!sdKey["silent"].asBoolean()) + RlvUtil::notifyBlockedViewXXX(LLAssetType::AT_SCRIPT); return false; } else if ( (gRlvHandler.hasBehaviour(RLV_BHVR_VIEWTEXTURE)) && ("preview_texture" == strName) ) { - RlvUtil::notifyBlockedViewXXX(LLAssetType::AT_TEXTURE); + if (!sdKey["silent"].asBoolean()) + RlvUtil::notifyBlockedViewXXX(LLAssetType::AT_TEXTURE); return false; } return true; diff --git a/indra/newview/rlvui.h b/indra/newview/rlvui.h index a248c132246..9f2caa85229 100644 --- a/indra/newview/rlvui.h +++ b/indra/newview/rlvui.h @@ -74,6 +74,9 @@ class RlvUIEnabler : public LLSingleton bool filterPanelShowLoc(const std::string&, const std::string&, const LLSD& ); boost::signals2::connection m_ConnPanelShowLoc; // showloc + bool filterPanelGeneric(const std::string&, const std::string&, const LLSD&); + boost::signals2::connection m_ConnPanelGeneric; + /* * Helper functions */ diff --git a/indra/newview/skins/default/xui/en/menu_viewer.xml b/indra/newview/skins/default/xui/en/menu_viewer.xml index aa3074c90aa..4418a1f3e78 100644 --- a/indra/newview/skins/default/xui/en/menu_viewer.xml +++ b/indra/newview/skins/default/xui/en/menu_viewer.xml @@ -166,6 +166,20 @@ function="Floater.Toggle" parameter="preferences" /> + + + + + + @@ -3033,6 +3047,8 @@ function="World.EnvPreset" + diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index e0e767bc8d4..bc1c41251c4 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -1,5 +1,66 @@ + + + Profile viewing is blocked by your RLV restrictions + + + Viewer will shut down in [SECONDS] seconds due to RLV request. + + + Search is blocked by your RLV restrictions + + + People is blocked by your RLV restrictions + + + Conversation history is blocked by your RLV restrictions + + + Appearance is blocked by your RLV restrictions + + + Edit Tools is blocked by your RLV restrictions + + + Destinations Guide is blocked by your RLV restrictions + + + Camera Controls are blocked by your RLV restrictions + + + Preferences are blocked by your RLV restrictions + Don't show me this again @@ -9265,6 +9326,16 @@ Are you sure you want to close all IMs? name="AttachmentSaved" type="notifytip"> Attachment has been saved. + + +E-Stim device load disconnected! Stimulation has been cut off for safety. + + + +E-Stim device or sensor battery is low! Please check your devices. + diff --git a/indra/newview/skins/default/xui/en/rlva_strings.xml b/indra/newview/skins/default/xui/en/rlva_strings.xml index f97767ba668..32d7031e658 100644 --- a/indra/newview/skins/default/xui/en/rlva_strings.xml +++ b/indra/newview/skins/default/xui/en/rlva_strings.xml @@ -126,10 +126,15 @@ value Unable to perform the requested inventory action due to an RLV locked folder. - blocked_nearby + blocked_friends value - Unable to see the presence of nearby avatars due to RLV restrictions + Unable to see friends due to RLV restrictions + + blocked_recent + + value + Unable to see recent avatars due to RLV restrictions blocked_permattach @@ -186,6 +191,46 @@ value Unable to enable wireframe mode due to RLV restrictions + blocked_showsearch + + value + Search is blocked by your RLV restrictions + + blocked_showpeople + + value + People is blocked by your RLV restrictions + + blocked_showconversation + + value + Conversation history is blocked by your RLV restrictions + + blocked_showoutfits + + value + Appearance is blocked by your RLV restrictions + + blocked_showbuild + + value + Edit Tools is blocked by your RLV restrictions + + blocked_showdestinations + + value + Destinations Guide is blocked by your RLV restrictions + + blocked_showcamera + + value + Camera Controls are blocked by your RLV restrictions + + blocked_showpreferences + + value + Preferences are blocked by your RLV restrictions + blocked_recvim_remote diff --git a/indra/newview/skins/default/xui/ja/notifications.xml b/indra/newview/skins/default/xui/ja/notifications.xml index 0c5688e4857..1fdc8c7246a 100644 --- a/indra/newview/skins/default/xui/ja/notifications.xml +++ b/indra/newview/skins/default/xui/ja/notifications.xml @@ -5084,4 +5084,52 @@ UDP:3478, 3479, 5060, 5062, 6250, 12000-32000 フレンド以外の人からインスタントメッセージを受信したときに、設定した自動応答で応答されるようになります。 + + 検索はRLVの制限によってブロックされています。 + + + ピープルはRLVの制限によってブロックされています。 + + + 会話履歴はRLVの制限によってブロックされています。 + + + 外観はRLVの制限によってブロックされています。 + + + 編集ツールはRLVの制限によってブロックされています。 + + + 行き先ガイドはRLVの制限によってブロックされています。 + + + カメラコントロールはRLVの制限によってブロックされています。 + + + 設定はRLVの制限によってブロックされています。 + diff --git a/indra/newview/skins/default/xui/ja/rlva_strings.xml b/indra/newview/skins/default/xui/ja/rlva_strings.xml index 250837f8074..3fec2e68339 100644 --- a/indra/newview/skins/default/xui/ja/rlva_strings.xml +++ b/indra/newview/skins/default/xui/ja/rlva_strings.xml @@ -120,11 +120,6 @@ value RLVによってフォルダがロックされているため、インベントリに対するアクションを実行できません。 - blocked_nearby - - value - RLVの制限により、近くにいるアバターの存在を確認できません。 - blocked_permattach value @@ -180,6 +175,46 @@ value RLVの制限により、ワイヤーフレームモードを有効にできません。 + blocked_showsearch + + value + RLVの制限により、検索はブロックされています。 + + blocked_showpeople + + value + RLVの制限により、ピープルはブロックされています。 + + blocked_showconversation + + value + RLVの制限により、会話履歴はブロックされています。 + + blocked_showoutfits + + value + RLVの制限により、外観はブロックされています。 + + blocked_showbuild + + value + RLVの制限により、編集ツールはブロックされています。 + + blocked_showdestinations + + value + RLVの制限により、行き先ガイドはブロックされています。 + + blocked_showcamera + + value + RLVの制限により、カメラコントロールはブロックされています。 + + blocked_showpreferences + + value + RLVの制限により、設定はブロックされています。 + blocked_recvim_remote From b257f40c2cd7b5c0a9a618a4735c334183d6d1d8 Mon Sep 17 00:00:00 2001 From: Matti Date: Sun, 5 Jul 2026 13:25:12 +0100 Subject: [PATCH 2/5] feat: native estim and telemetry WebSocket integration --- indra/llaudio/llaudioengine_fmodstudio.cpp | 46 ++ indra/llaudio/llaudioengine_fmodstudio.h | 5 + indra/newview/llestimrlvhandler.cpp | 293 +++++++++ indra/newview/llestimrlvhandler.h | 20 + indra/newview/llestimwsmgr.cpp | 580 ++++++++++++++++++ indra/newview/llestimwsmgr.h | 137 +++++ indra/newview/llfloaterestim.cpp | 219 +++++++ indra/newview/llfloaterestim.h | 32 + .../skins/default/xui/en/llfloaterestim.xml | 64 ++ 9 files changed, 1396 insertions(+) create mode 100644 indra/newview/llestimrlvhandler.cpp create mode 100644 indra/newview/llestimrlvhandler.h create mode 100644 indra/newview/llestimwsmgr.cpp create mode 100644 indra/newview/llestimwsmgr.h create mode 100644 indra/newview/llfloaterestim.cpp create mode 100644 indra/newview/llfloaterestim.h create mode 100644 indra/newview/skins/default/xui/en/llfloaterestim.xml diff --git a/indra/llaudio/llaudioengine_fmodstudio.cpp b/indra/llaudio/llaudioengine_fmodstudio.cpp index bc9109c97b2..6976c2ae08a 100644 --- a/indra/llaudio/llaudioengine_fmodstudio.cpp +++ b/indra/llaudio/llaudioengine_fmodstudio.cpp @@ -50,6 +50,7 @@ FMOD_RESULT F_CALL windCallback(FMOD_DSP_STATE *dsp_state, float *inbuffer, floa FMOD::ChannelGroup *LLAudioEngine_FMODSTUDIO::mChannelGroups[LLAudioEngine::AUDIO_TYPE_COUNT] = {0}; float LLAudioEngine_FMODSTUDIO::sReverbSendScale = 0.0f; +LLAudioEngine_FMODSTUDIO::FFTCallback LLAudioEngine_FMODSTUDIO::sFFTCallback = nullptr; namespace { @@ -363,6 +364,19 @@ bool LLAudioEngine_FMODSTUDIO::init(void* userdata, const std::string &app_title "FMOD::System::setReverbProperties (init seed)"); } + // Create and attach FMOD FFT DSP to master channel group + if (mSystem) + { + FMOD::ChannelGroup* master_group = nullptr; + if (mSystem->getMasterChannelGroup(&master_group) == FMOD_OK && master_group) + { + if (mSystem->createDSPByType(FMOD_DSP_TYPE_FFT, &mFFtDSP) == FMOD_OK && mFFtDSP) + { + master_group->addDSP(FMOD_CHANNELCONTROL_DSP_TAIL, mFFtDSP); + } + } + } + LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): initialization complete." << LL_ENDL; return true; @@ -547,6 +561,16 @@ void LLAudioEngine_FMODSTUDIO::shutdown() LLAudioEngine::shutdown(); LL_INFOS("FMOD") << "LLAudioEngine_FMODSTUDIO::shutdown() closing FMOD Studio" << LL_ENDL; + if (mFFtDSP) + { + FMOD::ChannelGroup* master_group = nullptr; + if (mSystem && mSystem->getMasterChannelGroup(&master_group) == FMOD_OK && master_group) + { + master_group->removeDSP(mFFtDSP); + } + mFFtDSP->release(); + mFFtDSP = nullptr; + } if (mSystem) { mSystem->close(); @@ -559,6 +583,28 @@ void LLAudioEngine_FMODSTUDIO::shutdown() } +void LLAudioEngine_FMODSTUDIO::idle() +{ + LLAudioEngine::idle(); + + if (mInited && mSystem && mFFtDSP && sFFTCallback) + { + FMOD_DSP_PARAMETER_FFT* fft_param = nullptr; + unsigned int length = 0; + FMOD_RESULT r = mFFtDSP->getParameterData(FMOD_DSP_FFT_SPECTRUMDATA, (void**)&fft_param, &length, nullptr, 0); + if (r == FMOD_OK && fft_param) + { + int num_channels = fft_param->numchannels; + int fft_length = fft_param->length; + if (num_channels > 0 && fft_length > 0 && fft_param->spectrum[0]) + { + sFFTCallback(fft_param->spectrum[0], fft_length); + } + } + } +} + + LLAudioBuffer * LLAudioEngine_FMODSTUDIO::createBuffer() { return new LLAudioBufferFMODSTUDIO(mSystem); diff --git a/indra/llaudio/llaudioengine_fmodstudio.h b/indra/llaudio/llaudioengine_fmodstudio.h index 83a94a0cacc..6d23d94739b 100644 --- a/indra/llaudio/llaudioengine_fmodstudio.h +++ b/indra/llaudio/llaudioengine_fmodstudio.h @@ -124,8 +124,13 @@ class LLAudioEngine_FMODSTUDIO : public LLAudioEngine std::string mActiveDeviceName; bool mReverbActive = false; + FMOD::DSP *mFFtDSP = nullptr; public: + void idle() override; + typedef void (*FFTCallback)(const float* spectrum_data, int length); + static FFTCallback sFFTCallback; + static FMOD::ChannelGroup *mChannelGroups[LLAudioEngine::AUDIO_TYPE_COUNT]; // Cached wet-send level applied to every new channel in play(). // Static so the channel class can reach it without an engine back- diff --git a/indra/newview/llestimrlvhandler.cpp b/indra/newview/llestimrlvhandler.cpp new file mode 100644 index 00000000000..244f7b522a3 --- /dev/null +++ b/indra/newview/llestimrlvhandler.cpp @@ -0,0 +1,293 @@ +/** + * @file llestimrlvhandler.cpp + * @brief RLV handler implementation for native e-stim commands + */ + +#include "llviewerprecompiledheaders.h" +#include "llestimrlvhandler.h" +#include "llestimwsmgr.h" +#include "llerror.h" +#include "rlvhelper.h" +#include +#include + +bool LLEstimRLVHandler::onForceCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet) +{ + const std::string& behaviour = rlvCmd.getBehaviour(); + + if (behaviour.rfind("estim_", 0) != 0) + { + return false; + } + + auto server = LLEstimWSServer::getInstance(); + if (!server) + { + LL_WARNS("EstimRLV") << "E-Stim WebSocket server instance not available" << LL_ENDL; + cmdRet = RLV_RET_FAILED; + return true; + } + + if (behaviour == "estim_shock") + { + std::string opt = rlvCmd.getOption(); + std::vector parts; + size_t pos = 0; + std::string token; + while ((pos = opt.find(':')) != std::string::npos) + { + token = opt.substr(0, pos); + parts.push_back(token); + opt.erase(0, pos + 1); + } + parts.push_back(opt); + + if (parts.size() >= 4) + { + std::string channel = parts[0]; + U32 intensity = std::stoul(parts[1]); + U32 freq = std::stoul(parts[2]); + U32 duration = std::stoul(parts[3]); + server->sendShockCommand(channel, intensity, freq, duration); + cmdRet = RLV_RET_SUCCESS; + return true; + } + } + else if (behaviour == "estim_intensity") + { + std::string opt = rlvCmd.getOption(); + size_t pos = opt.find(':'); + if (pos != std::string::npos) + { + std::string channel = opt.substr(0, pos); + U32 intensity = std::stoul(opt.substr(pos + 1)); + server->sendStimCommand(channel, intensity); + cmdRet = RLV_RET_SUCCESS; + return true; + } + } + else if (behaviour == "estim_freq") + { + std::string opt = rlvCmd.getOption(); + size_t pos = opt.find(':'); + if (pos != std::string::npos) + { + std::string channel = opt.substr(0, pos); + U32 freq = std::stoul(opt.substr(pos + 1)); + server->sendStimCommand(channel, 0, freq); + cmdRet = RLV_RET_SUCCESS; + return true; + } + } + else if (behaviour == "estim_pulse_width") + { + std::string opt = rlvCmd.getOption(); + size_t pos = opt.find(':'); + if (pos != std::string::npos) + { + std::string channel = opt.substr(0, pos); + U32 pw = std::stoul(opt.substr(pos + 1)); + server->sendPulseConfig(channel, pw, ""); + cmdRet = RLV_RET_SUCCESS; + return true; + } + } + else if (behaviour == "estim_waveform") + { + std::string opt = rlvCmd.getOption(); + size_t pos = opt.find(':'); + if (pos != std::string::npos) + { + std::string channel = opt.substr(0, pos); + std::string wf = opt.substr(pos + 1); + server->sendPulseConfig(channel, 0, wf); + cmdRet = RLV_RET_SUCCESS; + return true; + } + } + else if (behaviour == "estim_pattern") + { + std::string opt = rlvCmd.getOption(); + size_t pos = opt.find(':'); + if (pos != std::string::npos) + { + std::string channel = opt.substr(0, pos); + U32 pat = std::stoul(opt.substr(pos + 1)); + server->sendStimCommand(channel, 0, 0, pat); + cmdRet = RLV_RET_SUCCESS; + return true; + } + } + else if (behaviour == "estim_sensor_mode") + { + std::string opt = rlvCmd.getOption(); + size_t pos = opt.find(':'); + if (pos != std::string::npos) + { + std::string sensor = opt.substr(0, pos); + std::string mode = opt.substr(pos + 1); + server->sendSensorMode(sensor, mode); + cmdRet = RLV_RET_SUCCESS; + return true; + } + } + else if (behaviour == "estim_audio_sync") + { + std::string opt = rlvCmd.getOption(); + bool enable = (opt == "on" || opt == "1" || opt == "true"); + server->setAudioSyncEnabled(enable); + cmdRet = RLV_RET_SUCCESS; + return true; + } + else if (behaviour == "estim_stop") + { + server->panicStop(); + cmdRet = RLV_RET_SUCCESS; + return true; + } + else if (behaviour == "estim_trigger") + { + std::string opt = rlvCmd.getOption(); + std::vector parts; + size_t pos = 0; + std::string token; + while ((pos = opt.find(':')) != std::string::npos) + { + token = opt.substr(0, pos); + parts.push_back(token); + opt.erase(0, pos + 1); + } + parts.push_back(opt); + + if (!parts.empty() && parts[0] == "clear") + { + server->clearTriggers(); + cmdRet = RLV_RET_SUCCESS; + return true; + } + else if (parts.size() >= 8 && parts[0] == "add") + { + EstimTriggerRule rule; + rule.sensor = parts[1]; + rule.axis = parts[2]; + rule.op = parts[3]; + rule.threshold = std::stof(parts[4]); + rule.action = parts[5] + ":" + parts[6] + ":" + parts[7]; + server->registerTrigger(rule); + cmdRet = RLV_RET_SUCCESS; + return true; + } + } + + cmdRet = RLV_RET_FAILED_UNKNOWN; + return true; +} + +bool LLEstimRLVHandler::onAddRemCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet) +{ + const std::string& behaviour = rlvCmd.getBehaviour(); + + if (behaviour.rfind("estim_", 0) != 0) + { + return false; + } + + auto server = LLEstimWSServer::getInstance(); + if (!server) + { + cmdRet = RLV_RET_FAILED; + return true; + } + + if (behaviour == "estim_notify") + { + std::string opt = rlvCmd.getOption(); + std::vector parts; + size_t pos = 0; + std::string token; + while ((pos = opt.find(':')) != std::string::npos) + { + token = opt.substr(0, pos); + parts.push_back(token); + opt.erase(0, pos + 1); + } + parts.push_back(opt); + + if (parts.size() == 3) + { + std::string sensor = parts[0]; + std::string axis = parts[1]; + S32 channel = 0; + if (LLStringUtil::convertToS32(parts[2], channel)) + { + if (rlvCmd.getParamType() == RLV_TYPE_ADD) + { + server->registerNotification(sensor, axis, channel, rlvCmd.getObjectID()); + } + else if (rlvCmd.getParamType() == RLV_TYPE_REMOVE) + { + server->removeNotification(sensor, axis, channel, rlvCmd.getObjectID()); + } + cmdRet = RLV_RET_SUCCESS; + return true; + } + } + } + + cmdRet = RLV_RET_SUCCESS; + return true; +} + +bool LLEstimRLVHandler::onReplyCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet) +{ + const std::string& behaviour = rlvCmd.getBehaviour(); + + if (behaviour != "estim_getval") + { + return false; + } + + auto server = LLEstimWSServer::getInstance(); + if (!server) + { + cmdRet = RLV_RET_FAILED; + return true; + } + + std::string opt = rlvCmd.getOption(); + size_t pos = opt.find(':'); + if (pos != std::string::npos) + { + std::string sensor = opt.substr(0, pos); + std::string axis = opt.substr(pos + 1); + F32 value = server->getSensorValue(sensor, axis); + + std::string reply; + if (axis == "button") + { + reply = std::to_string((int)value); + } + else + { + reply = llformat("%.6f", value); + } + + RlvUtil::sendChatReply(rlvCmd.getParam(), reply); + cmdRet = RLV_RET_SUCCESS; + return true; + } + + cmdRet = RLV_RET_FAILED_PARAM; + return true; +} + +bool LLEstimRLVHandler::onClearCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet) +{ + auto server = LLEstimWSServer::getInstance(); + if (server) + { + server->clearNotificationsForObject(rlvCmd.getObjectID()); + } + cmdRet = RLV_RET_SUCCESS; + return true; +} diff --git a/indra/newview/llestimrlvhandler.h b/indra/newview/llestimrlvhandler.h new file mode 100644 index 00000000000..61e7892bc4d --- /dev/null +++ b/indra/newview/llestimrlvhandler.h @@ -0,0 +1,20 @@ +/** + * @file llestimrlvhandler.h + * @brief RLV handler for native e-stim commands + */ + +#pragma once + +#include "rlvcommon.h" + +class LLEstimRLVHandler : public RlvExtCommandHandler +{ +public: + LLEstimRLVHandler() = default; + ~LLEstimRLVHandler() override = default; + + bool onForceCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet) override; + bool onAddRemCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet) override; + bool onReplyCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet) override; + bool onClearCommand(const RlvCommand& rlvCmd, ERlvCmdRet& cmdRet) override; +}; diff --git a/indra/newview/llestimwsmgr.cpp b/indra/newview/llestimwsmgr.cpp new file mode 100644 index 00000000000..f3d3ae17038 --- /dev/null +++ b/indra/newview/llestimwsmgr.cpp @@ -0,0 +1,580 @@ +/** + * @file llestimwsmgr.cpp + * @brief Native e-stim WebSocket manager and server implementation + */ + +#include "llviewerprecompiledheaders.h" +#include "llestimwsmgr.h" +#include "rlvcommon.h" +#include "llwebsocketmgr.h" +#include "llviewercontrol.h" +#include "llnotificationsutil.h" +#ifdef LL_FMODSTUDIO +#include "llaudioengine_fmodstudio.h" +#endif +#include "llerror.h" +#include "lluictrl.h" +#include +#include + +//======================================================================== +// LLEstimWSConnection +//======================================================================== + +void LLEstimWSConnection::onOpen() +{ + LLJSONRPCConnection::onOpen(); + LL_INFOS("EstimWS") << "E-Stim companion daemon connection opened" << LL_ENDL; +} + +void LLEstimWSConnection::onClose() +{ + LLJSONRPCConnection::onClose(); + LL_INFOS("EstimWS") << "E-Stim companion daemon connection closed" << LL_ENDL; +} + +//======================================================================== +// LLEstimWSServer +//======================================================================== + +LLEstimWSServer::LLEstimWSServer(const std::string& name, U16 port, bool local_only) + : LLJSONRPCServer(name, port, local_only) +{ + LL_INFOS("EstimWS") << "Created E-Stim WebSocket server on port " << port << LL_ENDL; +} + +LLEstimWSServer::ptr_t LLEstimWSServer::getInstance() +{ + if (!LLWebsocketMgr::instanceExists()) + { + return nullptr; + } + LLWebsocketMgr& wsmgr = LLWebsocketMgr::instance(); + return std::static_pointer_cast( + wsmgr.findServerByName(LLEstimWSServer::DEFAULT_SERVER_NAME)); +} + +void LLEstimWSServer::onStarted() +{ + LL_INFOS("EstimWS") << "E-Stim WebSocket server started" << LL_ENDL; + LLUICtrl::CommitCallbackRegistry::currentRegistrar().add("Estim.Panic", + boost::bind(&LLEstimWSServer::panicStop, this)); +} + +void LLEstimWSServer::onStopped() +{ + LL_INFOS("EstimWS") << "E-Stim WebSocket server stopped" << LL_ENDL; +#ifdef LL_FMODSTUDIO + LLAudioEngine_FMODSTUDIO::sFFTCallback = nullptr; +#endif +} + +void LLEstimWSServer::onConnectionOpened(const LLWebsocketMgr::WSConnection::ptr_t& connection) +{ + LLJSONRPCServer::onConnectionOpened(connection); + mActiveConnection = std::dynamic_pointer_cast(connection); + mConnected = true; + mSafetyCutoff = false; + mNotifiedLoadDisconnect = false; + mNotifiedLowBattery = false; + LL_INFOS("EstimWS") << "E-Stim daemon connection active" << LL_ENDL; +} + +void LLEstimWSServer::onConnectionClosed(const LLWebsocketMgr::WSConnection::ptr_t& connection) +{ + LLJSONRPCServer::onConnectionClosed(connection); + mActiveConnection.reset(); + mConnected = false; + mSafetyCutoff = true; + panicStop(); + + LL_WARNS("EstimWS") << "E-Stim daemon connection lost! Triggering safety cutoff." << LL_ENDL; + LLNotificationsUtil::add("EstimLoadDisconnect"); +} + +LLWebsocketMgr::WSConnection::ptr_t LLEstimWSServer::connectionFactory(LLWebsocketMgr::WSServer::ptr_t server, + LLWebsocketMgr::connection_h handle) +{ + auto connection = std::make_shared(server, handle); + setupConnectionMethods(connection); + return connection; +} + +void LLEstimWSServer::setupConnectionMethods(LLJSONRPCConnection::ptr_t connection) +{ + LLJSONRPCServer::setupConnectionMethods(connection); + + auto estim_conn = std::dynamic_pointer_cast(connection); + if (estim_conn) + { + std::weak_ptr that = std::static_pointer_cast(LLJSONRPCServer::shared_from_this()); + + estim_conn->registerMethod("sensor_update", + [that](const std::string&, const LLSD&, const LLSD& params) -> LLSD + { + if (auto server = that.lock()) + { + return server->handleSensorUpdate(params); + } + return LLSD(); + }); + + estim_conn->registerMethod("telemetry", + [that](const std::string&, const LLSD&, const LLSD& params) -> LLSD + { + if (auto server = that.lock()) + { + return server->handleTelemetry(params); + } + return LLSD(); + }); + + estim_conn->registerMethod("get_triggers", + [that](const std::string&, const LLSD&, const LLSD& params) -> LLSD + { + if (auto server = that.lock()) + { + LLSD list = LLSD::emptyArray(); + for (const auto& rule : server->getTriggers()) + { + LLSD r = LLSD::emptyMap(); + r["sensor"] = rule.sensor; + r["axis"] = rule.axis; + r["operator"] = rule.op; + r["threshold"] = (F32)rule.threshold; + r["rule_action"] = rule.action; + list.append(r); + } + return list; + } + return LLSD(); + }); + } +} + +LLSD LLEstimWSServer::handleSensorUpdate(const LLSD& params) +{ + std::string sensor = params["sensor"].asString(); + std::string axis = params["axis"].asString(); + F32 value = (F32)params["value"].asReal(); + + bool is_new = (mSensorValues.find(sensor) == mSensorValues.end() || + mSensorValues[sensor].find(axis) == mSensorValues[sensor].end()); + F32 old_value = is_new ? -999999.0f : mSensorValues[sensor][axis]; + mSensorValues[sensor][axis] = value; + + evaluateSensorInput(sensor, axis, value); + + if (is_new || value != old_value) + { + notifyLslSubscribers(sensor, axis, value); + } + + broadcastNotification("sensor_update", params); + + LLSD response = LLSD::emptyMap(); + response["status"] = "success"; + return response; +} + +LLSD LLEstimWSServer::handleTelemetry(const LLSD& params) +{ + if (params.has("battery")) + { + mCoyoteBattery = (F32)params["battery"].asReal(); + } + else if (params.has("battery_coyote")) + { + mCoyoteBattery = (F32)params["battery_coyote"].asReal(); + } + + if (params.has("load_a")) + { + mLoadA = params["load_a"].asBoolean(); + } + if (params.has("load_b")) + { + mLoadB = params["load_b"].asBoolean(); + } + + // Safety cutoff check: + // Temporarily disabled due to dodgy detection + /* + if (!mLoadA || !mLoadB) + { + if (!mSafetyCutoff) + { + mSafetyCutoff = true; + panicStop(); + if (!mNotifiedLoadDisconnect) + { + LLNotificationsUtil::add("EstimLoadDisconnect"); + mNotifiedLoadDisconnect = true; + } + } + } + else + { + mSafetyCutoff = false; + mNotifiedLoadDisconnect = false; + } + */ + + // Battery low check: + if (mCoyoteBattery < 10.0f) + { + if (!mNotifiedLowBattery) + { + LLNotificationsUtil::add("EstimLowBattery"); + mNotifiedLowBattery = true; + } + } + else + { + mNotifiedLowBattery = false; + } + + broadcastNotification("telemetry", params); + + LLSD response = LLSD::emptyMap(); + response["status"] = "success"; + return response; +} + +void LLEstimWSServer::sendStimCommand(const std::string& channel, U32 intensity, U32 frequency, U32 pattern) +{ + U32 max_cap = gSavedSettings.getU32("EstimMaxIntensityCap"); + U32 channel_cap = (channel == "b") ? mMaxIntensityB : mMaxIntensityA; + U32 capped_intensity = std::min(intensity, std::min(max_cap, channel_cap)); + + if (mSafetyCutoff) + { + capped_intensity = 0; + } + + if (channel == "a") + { + mChannelAIntensity = capped_intensity; + } + else if (channel == "b") + { + mChannelBIntensity = capped_intensity; + } + + if (mActiveConnection && mActiveConnection->isConnected()) + { + LLSD params = LLSD::emptyMap(); + params["command"] = "stimulate"; + params["channel"] = channel; + params["intensity"] = (S32)capped_intensity; + if (frequency > 0) + { + params["frequency"] = (S32)frequency; + } + if (pattern > 0) + { + params["pattern"] = (S32)pattern; + } + mActiveConnection->notify("stimulate", params); + } +} + +void LLEstimWSServer::sendShockCommand(const std::string& channel, U32 intensity, U32 frequency, U32 duration_ms) +{ + U32 max_cap = gSavedSettings.getU32("EstimMaxIntensityCap"); + U32 channel_cap = (channel == "b") ? mMaxIntensityB : mMaxIntensityA; + U32 capped_intensity = std::min(intensity, std::min(max_cap, channel_cap)); + + if (mSafetyCutoff) + { + capped_intensity = 0; + } + + if (mActiveConnection && mActiveConnection->isConnected()) + { + LLSD params = LLSD::emptyMap(); + params["command"] = "shock"; + params["channel"] = channel; + params["intensity"] = (S32)capped_intensity; + params["frequency"] = (S32)frequency; + params["duration"] = (S32)duration_ms; + mActiveConnection->notify("shock", params); + } +} + +void LLEstimWSServer::sendPulseConfig(const std::string& channel, U32 pulse_width, const std::string& waveform) +{ + if (mActiveConnection && mActiveConnection->isConnected()) + { + LLSD params = LLSD::emptyMap(); + params["command"] = "pulse_config"; + params["channel"] = channel; + params["pulse_width"] = (S32)pulse_width; + params["waveform"] = waveform; + mActiveConnection->notify("pulse_config", params); + } +} + +void LLEstimWSServer::sendSensorMode(const std::string& sensor, const std::string& mode) +{ + if (mActiveConnection && mActiveConnection->isConnected()) + { + LLSD params = LLSD::emptyMap(); + params["command"] = "sensor_mode"; + params["sensor"] = sensor; + params["mode"] = mode; + mActiveConnection->notify("sensor_mode", params); + } +} + +void LLEstimWSServer::panicStop() +{ + mChannelAIntensity = 0; + mChannelBIntensity = 0; + + if (mActiveConnection && mActiveConnection->isConnected()) + { + LLSD params = LLSD::emptyMap(); + params["command"] = "stop"; + mActiveConnection->notify("stop", params); + } +} + +void LLEstimWSServer::registerTrigger(const EstimTriggerRule& rule) +{ + mTriggerRules.push_back(rule); + + LLSD params = LLSD::emptyMap(); + params["action"] = "add"; + params["sensor"] = rule.sensor; + params["axis"] = rule.axis; + params["operator"] = rule.op; + params["threshold"] = (F32)rule.threshold; + params["rule_action"] = rule.action; + broadcastNotification("trigger_update", params); +} + +void LLEstimWSServer::clearTriggers() +{ + mTriggerRules.clear(); + + LLSD params = LLSD::emptyMap(); + params["action"] = "clear"; + broadcastNotification("trigger_update", params); +} + +void LLEstimWSServer::evaluateSensorInput(const std::string& sensor, const std::string& axis, F32 value) +{ + for (const auto& rule : mTriggerRules) + { + if (rule.sensor == sensor && rule.axis == axis) + { + bool condition_met = false; + if (rule.op == ">") + { + condition_met = (value > rule.threshold); + } + else if (rule.op == "<") + { + condition_met = (value < rule.threshold); + } + else if (rule.op == "==") + { + condition_met = (std::abs(value - rule.threshold) < 0.001f); + } + + if (condition_met) + { + if (!rule.last_state) + { + rule.last_state = true; + + std::vector parts; + size_t pos = 0; + std::string token; + std::string s = rule.action; + while ((pos = s.find(':')) != std::string::npos) + { + token = s.substr(0, pos); + parts.push_back(token); + s.erase(0, pos + 1); + } + parts.push_back(s); + + if (parts.size() >= 3) + { + std::string act = parts[0]; + if (act == "intensity") + { + std::string chan = parts[1]; + U32 val = std::stoul(parts[2]); + sendStimCommand(chan, val); + } + else if (act == "freq") + { + std::string chan = parts[1]; + U32 val = std::stoul(parts[2]); + sendStimCommand(chan, 0, val); + } + else if (act == "pattern") + { + std::string chan = parts[1]; + U32 val = std::stoul(parts[2]); + sendStimCommand(chan, 0, 0, val); + } + else if (act == "lsl") + { + std::string reply_chan = parts[1]; + std::string message = parts[2]; + RlvUtil::sendChatReply(reply_chan, message); + } + } + } + } + else + { + rule.last_state = false; + } + } + } +} + +F32 LLEstimWSServer::getSensorValue(const std::string& sensor, const std::string& axis) const +{ + auto it_s = mSensorValues.find(sensor); + if (it_s != mSensorValues.end()) + { + auto it_a = it_s->second.find(axis); + if (it_a != it_s->second.end()) + { + return it_a->second; + } + } + return 0.0f; +} + +void LLEstimWSServer::registerNotification(const std::string& sensor, const std::string& axis, S32 channel, const LLUUID& object_id) +{ + for (const auto& rule : mNotifications) + { + if (rule.sensor == sensor && rule.axis == axis && rule.channel == channel && rule.object_id == object_id) + { + return; + } + } + mNotifications.push_back({sensor, axis, channel, object_id}); +} + +void LLEstimWSServer::removeNotification(const std::string& sensor, const std::string& axis, S32 channel, const LLUUID& object_id) +{ + mNotifications.erase( + std::remove_if(mNotifications.begin(), mNotifications.end(), + [&](const EstimNotificationRule& rule) { + return rule.sensor == sensor && rule.axis == axis && rule.channel == channel && rule.object_id == object_id; + }), + mNotifications.end()); +} + +void LLEstimWSServer::clearNotificationsForObject(const LLUUID& object_id) +{ + mNotifications.erase( + std::remove_if(mNotifications.begin(), mNotifications.end(), + [&](const EstimNotificationRule& rule) { + return rule.object_id == object_id; + }), + mNotifications.end()); +} + +void LLEstimWSServer::notifyLslSubscribers(const std::string& sensor, const std::string& axis, F32 value) +{ + for (const auto& rule : mNotifications) + { + if (rule.sensor == sensor && rule.axis == axis) + { + std::string val_str; + if (axis == "button") + { + val_str = std::to_string((int)value); + } + else + { + val_str = llformat("%.6f", value); + } + std::string reply = sensor + ":" + axis + "=" + val_str; + RlvUtil::sendChatReply(rule.channel, reply); + } + } +} + +void LLEstimWSServer::setAudioSyncEnabled(bool enabled) +{ + mAudioSyncEnabled = enabled; +#ifdef LL_FMODSTUDIO + if (enabled) + { + LLAudioEngine_FMODSTUDIO::sFFTCallback = &LLEstimWSServer::processFFTData; + } + else + { + if (LLAudioEngine_FMODSTUDIO::sFFTCallback == &LLEstimWSServer::processFFTData) + { + LLAudioEngine_FMODSTUDIO::sFFTCallback = nullptr; + } + } +#endif +} + +void LLEstimWSServer::processFFTData(const float* spectrum_data, int length) +{ + if (getInstance() && getInstance()->isAudioSyncEnabled()) + { + getInstance()->handleFFT(spectrum_data, length); + } +} + +void LLEstimWSServer::handleFFT(const float* spectrum_data, int length) +{ + static LLTimer throttle_timer; + if (throttle_timer.getElapsedTimeF32() < 0.050f) + { + return; + } + throttle_timer.reset(); + + float bass = 0.0f; + int bass_bins = std::min(10, length); + for (int i = 0; i < bass_bins; ++i) + { + bass += spectrum_data[i]; + } + if (bass_bins > 0) bass /= bass_bins; + + float mid_high = 0.0f; + int mid_high_start = bass_bins; + int mid_high_end = std::min(length / 2, length); + int mid_high_bins = mid_high_end - mid_high_start; + for (int i = mid_high_start; i < mid_high_end; ++i) + { + mid_high += spectrum_data[i]; + } + if (mid_high_bins > 0) mid_high /= mid_high_bins; + + U32 intensity_a = static_cast(bass * 1000.0f); + intensity_a = std::min(intensity_a, (U32)255); + + U32 freq_b = 10 + static_cast(mid_high * 1000.0f); + freq_b = std::min(std::max(freq_b, (U32)10), (U32)150); + + sendStimCommand("a", intensity_a); + sendStimCommand("b", mChannelBIntensity, freq_b); +} + +void LLEstimWSServer::testChannelA() +{ + sendStimCommand("a", std::min(10u, mMaxIntensityA), 50, 0); +} + +void LLEstimWSServer::testChannelB() +{ + sendStimCommand("b", std::min(10u, mMaxIntensityB), 50, 0); +} diff --git a/indra/newview/llestimwsmgr.h b/indra/newview/llestimwsmgr.h new file mode 100644 index 00000000000..9ff7a79657b --- /dev/null +++ b/indra/newview/llestimwsmgr.h @@ -0,0 +1,137 @@ +/** + * @file llestimwsmgr.h + * @brief Native e-stim WebSocket manager and server interface + */ + +#pragma once + +#include "lljsonrpcws.h" +#include "llsd.h" +#include "lluuid.h" +#include +#include +#include +#include + +struct EstimNotificationRule +{ + std::string sensor; + std::string axis; + S32 channel; + LLUUID object_id; +}; + +struct EstimTriggerRule +{ + std::string sensor; + std::string axis; + std::string op; + F32 threshold{0.0f}; + std::string action; + mutable bool last_state{false}; +}; + + +class LLEstimWSConnection : public LLJSONRPCConnection, public std::enable_shared_from_this +{ +public: + using ptr_t = std::shared_ptr; + using wptr_t = std::weak_ptr; + + LLEstimWSConnection(const LLWebsocketMgr::WSServer::ptr_t server, const LLWebsocketMgr::connection_h& handle) + : LLJSONRPCConnection(server, handle) {} + + ~LLEstimWSConnection() override = default; + + void onOpen() override; + void onClose() override; +}; + +class LLEstimWSServer : public LLJSONRPCServer +{ +public: + static constexpr char const* DEFAULT_SERVER_NAME = "estim_server"; + static constexpr U16 DEFAULT_SERVER_PORT = 9030; + + using ptr_t = std::shared_ptr; + using wptr_t = std::weak_ptr; + + LLEstimWSServer(const std::string& name, U16 port, bool local_only = true); + ~LLEstimWSServer() override = default; + + static LLEstimWSServer::ptr_t getInstance(); + + void onStarted() override; + void onStopped() override; + void onConnectionOpened(const LLWebsocketMgr::WSConnection::ptr_t& connection) override; + void onConnectionClosed(const LLWebsocketMgr::WSConnection::ptr_t& connection) override; + + void sendStimCommand(const std::string& channel, U32 intensity, U32 frequency = 0, U32 pattern = 0); + void sendShockCommand(const std::string& channel, U32 intensity, U32 frequency, U32 duration_ms); + void sendPulseConfig(const std::string& channel, U32 pulse_width, const std::string& waveform); + void sendSensorMode(const std::string& sensor, const std::string& mode); + void panicStop(); + + void registerTrigger(const EstimTriggerRule& rule); + void clearTriggers(); + void evaluateSensorInput(const std::string& sensor, const std::string& axis, F32 value); + + F32 getSensorValue(const std::string& sensor, const std::string& axis) const; + const std::map>& getSensorValues() const { return mSensorValues; } + + void registerNotification(const std::string& sensor, const std::string& axis, S32 channel, const LLUUID& object_id); + void removeNotification(const std::string& sensor, const std::string& axis, S32 channel, const LLUUID& object_id); + void clearNotificationsForObject(const LLUUID& object_id); + void notifyLslSubscribers(const std::string& sensor, const std::string& axis, F32 value); + + void setMaxIntensityA(U32 intensity) { mMaxIntensityA = intensity; } + void setMaxIntensityB(U32 intensity) { mMaxIntensityB = intensity; } + U32 getMaxIntensityA() const { return mMaxIntensityA; } + U32 getMaxIntensityB() const { return mMaxIntensityB; } + + void testChannelA(); + void testChannelB(); + + void setAudioSyncEnabled(bool enabled); + bool isAudioSyncEnabled() const { return mAudioSyncEnabled; } + + static void processFFTData(const float* spectrum_data, int length); + void handleFFT(const float* spectrum_data, int length); + + // Telemetry getters + F32 getCoyoteBattery() const { return mCoyoteBattery; } + bool getLoadA() const { return mLoadA; } + bool getLoadB() const { return mLoadB; } + bool isConnected() const { return mConnected; } + const std::vector& getTriggers() const { return mTriggerRules; } + +protected: + LLWebsocketMgr::WSConnection::ptr_t connectionFactory(LLWebsocketMgr::WSServer::ptr_t server, + LLWebsocketMgr::connection_h handle) override; + + void setupConnectionMethods(LLJSONRPCConnection::ptr_t connection) override; + +private: + LLSD handleSensorUpdate(const LLSD& params); + LLSD handleTelemetry(const LLSD& params); + + std::vector mTriggerRules; + std::vector mNotifications; + std::map> mSensorValues; + bool mAudioSyncEnabled{ false }; + bool mSafetyCutoff{ false }; + bool mNotifiedLoadDisconnect{ false }; + bool mNotifiedLowBattery{ false }; + + LLEstimWSConnection::ptr_t mActiveConnection; + + F32 mCoyoteBattery{ 100.0f }; + bool mLoadA{ true }; + bool mLoadB{ true }; + bool mConnected{ false }; + + U32 mMaxIntensityA{ 255 }; + U32 mMaxIntensityB{ 255 }; + U32 mChannelAIntensity{ 0 }; + U32 mChannelBIntensity{ 0 }; +}; diff --git a/indra/newview/llfloaterestim.cpp b/indra/newview/llfloaterestim.cpp new file mode 100644 index 00000000000..84c43f1f8cc --- /dev/null +++ b/indra/newview/llfloaterestim.cpp @@ -0,0 +1,219 @@ +/** + * @file llfloaterestim.cpp + * @brief E-Stim Device Controls UI Floater implementation + */ + +#include "llviewerprecompiledheaders.h" +#include "llfloaterestim.h" +#include "llestimwsmgr.h" +#include "llbutton.h" +#include "lltextbox.h" +#include "llscrolllistctrl.h" +#include "llsliderctrl.h" + +LLFloaterEstim::LLFloaterEstim(const LLSD& key) + : LLFloater(key) +{ +} + +bool LLFloaterEstim::postBuild() +{ + mTriggerList = getChild("trigger_list"); + mSensorList = getChild("sensor_list"); + + LLButton* panic_btn = getChild("panic_button"); + if (panic_btn) panic_btn->setCommitCallback(boost::bind(&LLFloaterEstim::onPanicPressed, this)); + + LLButton* test_a_btn = getChild("test_a_btn"); + if (test_a_btn) test_a_btn->setCommitCallback(boost::bind(&LLFloaterEstim::onTestAPressed, this)); + + LLButton* test_b_btn = getChild("test_b_btn"); + if (test_b_btn) test_b_btn->setCommitCallback(boost::bind(&LLFloaterEstim::onTestBPressed, this)); + + LLButton* clear_btn = getChild("clear_btn"); + if (clear_btn) clear_btn->setCommitCallback(boost::bind(&LLFloaterEstim::onClearTriggersPressed, this)); + + LLSliderCtrl* slider_a = getChild("max_a_slider"); + if (slider_a) slider_a->setCommitCallback(boost::bind(&LLFloaterEstim::onSliderAModified, this, _1)); + + LLSliderCtrl* slider_b = getChild("max_b_slider"); + if (slider_b) slider_b->setCommitCallback(boost::bind(&LLFloaterEstim::onSliderBModified, this, _1)); + + if (auto server = LLEstimWSServer::getInstance()) + { + if (slider_a) slider_a->setValue((F32)server->getMaxIntensityA()); + if (slider_b) slider_b->setValue((F32)server->getMaxIntensityB()); + } + + return true; +} + +void LLFloaterEstim::onPanicPressed() +{ + if (auto server = LLEstimWSServer::getInstance()) + { + server->panicStop(); + } +} + +void LLFloaterEstim::onTestAPressed() +{ + if (auto server = LLEstimWSServer::getInstance()) + { + server->testChannelA(); + } +} + +void LLFloaterEstim::onTestBPressed() +{ + if (auto server = LLEstimWSServer::getInstance()) + { + server->testChannelB(); + } +} + +void LLFloaterEstim::onClearTriggersPressed() +{ + if (auto server = LLEstimWSServer::getInstance()) + { + server->clearTriggers(); + updateTriggerList(); + } +} + +void LLFloaterEstim::onSliderAModified(LLUICtrl* ctrl) +{ + if (auto server = LLEstimWSServer::getInstance()) + { + server->setMaxIntensityA((U32)ctrl->getValue().asInteger()); + } +} + +void LLFloaterEstim::onSliderBModified(LLUICtrl* ctrl) +{ + if (auto server = LLEstimWSServer::getInstance()) + { + server->setMaxIntensityB((U32)ctrl->getValue().asInteger()); + } +} + +void LLFloaterEstim::draw() +{ + LLFloater::draw(); + + auto server = LLEstimWSServer::getInstance(); + if (!server) + { + return; + } + + LLTextBox* status_txt = getChild("status_label"); + if (status_txt) + { + status_txt->setText(LLStringExplicit(server->isConnected() ? "Connection: Connected" : "Connection: Disconnected")); + } + + LLTextBox* battery_txt = getChild("battery_label"); + if (battery_txt) + { + battery_txt->setText(LLStringExplicit("Coyote Battery: " + std::to_string((int)server->getCoyoteBattery()) + "%")); + } + + LLTextBox* load_a_txt = getChild("load_a_label"); + if (load_a_txt) + { + load_a_txt->setText(LLStringExplicit(server->getLoadA() ? "Channel A Load: Connected" : "Channel A Load: Disconnected")); + } + + LLTextBox* load_b_txt = getChild("load_b_label"); + if (load_b_txt) + { + load_b_txt->setText(LLStringExplicit(server->getLoadB() ? "Channel B Load: Connected" : "Channel B Load: Disconnected")); + } + + updateTriggerList(); + updateSensorList(); +} + +void LLFloaterEstim::updateTriggerList() +{ + if (!mTriggerList) + { + return; + } + + auto server = LLEstimWSServer::getInstance(); + if (!server) + { + return; + } + + mTriggerList->clearRows(); + + const auto& triggers = server->getTriggers(); + for (const auto& rule : triggers) + { + LLSD element; + element["columns"][0]["column"] = "rule"; + element["columns"][0]["type"] = "text"; + element["columns"][0]["value"] = rule.sensor + "." + rule.axis + " " + rule.op + " " + std::to_string((int)rule.threshold) + " -> " + rule.action; + mTriggerList->addElement(element); + } +} + +void LLFloaterEstim::updateSensorList() +{ + if (!mSensorList) + { + return; + } + + auto server = LLEstimWSServer::getInstance(); + if (!server) + { + return; + } + + mSensorList->clearRows(); + + const auto& sensor_values = server->getSensorValues(); + for (const auto& sensor_pair : sensor_values) + { + const std::string& sensor_name = sensor_pair.first; + const auto& axes = sensor_pair.second; + + std::string val_str = ""; + for (const auto& axis_pair : axes) + { + if (!val_str.empty()) + { + val_str += ", "; + } + if (axis_pair.first == "button") + { + val_str += "Button: " + std::string(axis_pair.second > 0 ? "Pressed" : "Released"); + } + else if (axis_pair.first == "accel") + { + val_str += "Accel: " + llformat("%.1f", axis_pair.second); + } + else + { + std::string axis_name = axis_pair.first; + if (!axis_name.empty()) axis_name[0] = std::toupper(axis_name[0]); + val_str += axis_name + ": " + llformat("%.1f", axis_pair.second); + } + } + + LLSD element; + element["columns"][0]["column"] = "sensor"; + element["columns"][0]["type"] = "text"; + element["columns"][0]["value"] = sensor_name; + + element["columns"][1]["column"] = "values"; + element["columns"][1]["type"] = "text"; + element["columns"][1]["value"] = val_str; + + mSensorList->addElement(element); + } +} diff --git a/indra/newview/llfloaterestim.h b/indra/newview/llfloaterestim.h new file mode 100644 index 00000000000..a047938cf61 --- /dev/null +++ b/indra/newview/llfloaterestim.h @@ -0,0 +1,32 @@ +/** + * @file llfloaterestim.h + * @brief E-Stim Device Controls UI Floater header + */ + +#pragma once + +#include "llfloater.h" +#include "llscrolllistctrl.h" + +class LLFloaterEstim : public LLFloater +{ +public: + LLFloaterEstim(const LLSD& key); + ~LLFloaterEstim() override = default; + + bool postBuild() override; + void draw() override; + +private: + void onPanicPressed(); + void onTestAPressed(); + void onTestBPressed(); + void onClearTriggersPressed(); + void onSliderAModified(LLUICtrl* ctrl); + void onSliderBModified(LLUICtrl* ctrl); + void updateTriggerList(); + void updateSensorList(); + + LLScrollListCtrl* mTriggerList{ nullptr }; + LLScrollListCtrl* mSensorList{ nullptr }; +}; diff --git a/indra/newview/skins/default/xui/en/llfloaterestim.xml b/indra/newview/skins/default/xui/en/llfloaterestim.xml new file mode 100644 index 00000000000..e4db3a32940 --- /dev/null +++ b/indra/newview/skins/default/xui/en/llfloaterestim.xml @@ -0,0 +1,64 @@ + + + + + Coyote Battery: Unknown + + + Channel A Load: Connected + + + Channel B Load: Connected + + + Connection: Disconnected + + + + Max Output A: + + + + + Max Output B: + + + + + Active Trigger Rules: + + + + + + + Connected Sensors: + + + + + + +