summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorCallum Prentice <callum@lindenlab.com>2025-10-08 18:58:27 -0700
committerCallum Prentice <callum@lindenlab.com>2025-10-08 18:58:27 -0700
commit0a14318dd6cb9e2eae142956126cfd000c3027d9 (patch)
tree5eb7390e3e23460cb39e8055226bfbca00bce545 /indra
parentc257304ae73ee1760b505c87e6d8d1a394fcd02b (diff)
First commit for a second experiment, investigating how nearby voice/chat moderation might work
Diffstat (limited to 'indra')
-rw-r--r--indra/newview/CMakeLists.txt2
-rw-r--r--indra/newview/llfloaterimcontainer.cpp96
-rw-r--r--indra/newview/llfloaterimcontainer.h2
-rw-r--r--indra/newview/llnearbyvoicemoderation.cpp102
-rw-r--r--indra/newview/llnearbyvoicemoderation.h41
-rw-r--r--indra/newview/skins/default/xui/en/menu_conversation.xml105
6 files changed, 291 insertions, 57 deletions
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index c727d5ae57..239193001c 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -414,6 +414,7 @@ set(viewer_SOURCE_FILES
llfloaterimnearbychat.cpp
llfloaterimnearbychathandler.cpp
llfloaterimnearbychatlistener.cpp
+ llnearbyvoicemoderation.cpp
llnetmap.cpp
llnotificationalerthandler.cpp
llnotificationgrouphandler.cpp
@@ -1087,6 +1088,7 @@ set(viewer_HEADER_FILES
llnameeditor.h
llnamelistctrl.h
llnavigationbar.h
+ llnearbyvoicemoderation.h
llnetmap.h
llnotificationhandler.h
llnotificationlistitem.h
diff --git a/indra/newview/llfloaterimcontainer.cpp b/indra/newview/llfloaterimcontainer.cpp
index d821d9a4a5..9a016f6286 100644
--- a/indra/newview/llfloaterimcontainer.cpp
+++ b/indra/newview/llfloaterimcontainer.cpp
@@ -57,6 +57,8 @@
#include "llsdserialize.h"
#include "llviewermenu.h" // is_agent_mappable
#include "llviewerobjectlist.h"
+#include "llvoavatar.h"
+#include "llnearbyvoicemoderation.h"
const S32 EVENTS_PER_IDLE_LOOP_CURRENT_SESSION = 80;
@@ -502,12 +504,13 @@ void LLFloaterIMContainer::idleUpdate()
const LLConversationItem *current_session = getCurSelectedViewModelItem();
if (current_session)
{
- if (current_session->getType() == LLConversationItem::CONV_SESSION_GROUP)
+ bool is_nearby_chat = current_session->getType() == LLConversationItem::CONV_SESSION_NEARBY;
+ if (current_session->getType() == LLConversationItem::CONV_SESSION_GROUP || is_nearby_chat)
{
// Update moderator options visibility
LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = current_session->getChildrenBegin();
LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = current_session->getChildrenEnd();
- bool is_moderator = isGroupModerator();
+ bool is_moderator = isGroupModerator() || (is_nearby_chat && isNearbyChatModerator());
bool can_ban = haveAbilityToBan();
while (current_participant_model != end_participant_model)
{
@@ -1685,6 +1688,10 @@ bool LLFloaterIMContainer::visibleContextMenuItem(const LLSD& userdata)
{
return isMuted(conversation_item->getUUID());
}
+ else if ("can_allow_text_chat" == item)
+ {
+ return !isNearbyChatSpeakerSelected();
+ }
return true;
}
@@ -2009,9 +2016,27 @@ LLConversationViewParticipant* LLFloaterIMContainer::createConversationViewParti
bool LLFloaterIMContainer::enableModerateContextMenuItem(const std::string& userdata, bool is_self)
{
- // only group moderators can perform actions related to this "enable callback"
- if (!isGroupModerator())
+ if (isNearbyChatModerator() && isNearbyChatSpeakerSelected())
+ {
+ // Determine here which actions are allowed
+ if ("can_moderate_voice" == userdata)
+ {
+ return true;
+ }
+ else if (("can_mute" == userdata))
+ {
+ return true;
+ }
+ else if ("can_unmute" == userdata)
+ {
+ return true;
+ }
+
+ return false;
+ }
+ else if (!isGroupModerator())
{
+ // only group moderators can perform actions related to this "enable callback"
return false;
}
@@ -2144,7 +2169,37 @@ void LLFloaterIMContainer::banSelectedMember(const LLUUID& participant_uuid)
void LLFloaterIMContainer::moderateVoice(const std::string& command, const LLUUID& userID)
{
- if (!gAgent.getRegion()) return;
+ if (!gAgent.getRegion())
+ {
+ return;
+ }
+
+ if (isNearbyChatSpeakerSelected())
+ {
+ if ("selected" == command)
+ {
+ // Toggle the voice icon display
+ LLAvatarActions::toggleMuteVoice(userID);
+
+ // Request a mute/unmute using a capability request via the simulator
+ const bool mute_state = LLAvatarActions::isVoiceMuted(userID);
+ LLNearbyVoiceModeration::getInstance()->requestMuteChange(userID, mute_state);
+ }
+ else
+ if ("mute_all" == command)
+ {
+ // TODO: the SpatialVoiceModerationRequest has an mute_all/unmute_all
+ // verb but we do not have an equivalent of LLAvatarActions::toggleMuteVoice(userID);
+ // to visually mute all the speaker icons in the conversation floater
+ }
+ else
+ if ("unmute_all" == command)
+ {
+ // TODO: same idea as "mute_all" above
+ }
+
+ return;
+ }
if (command.compare("selected"))
{
@@ -2262,6 +2317,37 @@ LLSpeaker * LLFloaterIMContainer::getSpeakerOfSelectedParticipant(LLSpeakerMgr *
return speaker_managerp->findSpeaker(participant_itemp->getUUID());
}
+bool LLFloaterIMContainer::isNearbyChatSpeakerSelected()
+{
+ LLFolderViewItem *selectedItem = mConversationsRoot->getCurSelectedItem();
+ if (NULL == selectedItem)
+ {
+ LL_WARNS() << "Current selected item is null" << LL_ENDL;
+ return NULL;
+ }
+
+ conversations_widgets_map::const_iterator iter = mConversationsWidgets.begin();
+ conversations_widgets_map::const_iterator end = mConversationsWidgets.end();
+ const LLUUID * conversation_uuidp = NULL;
+ while(iter != end)
+ {
+ if (iter->second == selectedItem || iter->second == selectedItem->getParentFolder())
+ {
+ conversation_uuidp = &iter->first;
+ break;
+ }
+ ++iter;
+ }
+ // Nearby chat ID is LLUUID::null
+ return conversation_uuidp->isNull();
+}
+
+bool LLFloaterIMContainer::isNearbyChatModerator()
+{
+ // TODO: Need a better heurestic for determining if this person is a moderator :)
+ return true;
+}
+
void LLFloaterIMContainer::toggleAllowTextChat(const LLUUID& participant_uuid)
{
LLIMSpeakerMgr * speaker_managerp = dynamic_cast<LLIMSpeakerMgr*>(getSpeakerMgrForSelectedParticipant());
diff --git a/indra/newview/llfloaterimcontainer.h b/indra/newview/llfloaterimcontainer.h
index 30eed8be36..c91093c107 100644
--- a/indra/newview/llfloaterimcontainer.h
+++ b/indra/newview/llfloaterimcontainer.h
@@ -178,6 +178,8 @@ private:
void banSelectedMember(const LLUUID& participant_uuid);
void openNearbyChat();
bool isParticipantListExpanded();
+ bool isNearbyChatSpeakerSelected();
+ bool isNearbyChatModerator();
void idleUpdate(); // for convenience (self) from static idle
void idleProcessEvents();
diff --git a/indra/newview/llnearbyvoicemoderation.cpp b/indra/newview/llnearbyvoicemoderation.cpp
new file mode 100644
index 0000000000..d714fc36b4
--- /dev/null
+++ b/indra/newview/llnearbyvoicemoderation.cpp
@@ -0,0 +1,102 @@
+/**
+ * @file llnearbyvoicemoderation.cpp
+ *
+ * $LicenseInfo:firstyear=2008&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#include "llviewerprecompiledheaders.h"
+
+#include "llagent.h"
+#include "llviewerregion.h"
+#include "llvoavatar.h"
+#include "llviewerobjectlist.h"
+
+#include "llnearbyvoicemoderation.h"
+
+LLVOAvatar* LLNearbyVoiceModeration::getVOAvatarFromId(const LLUUID& agent_id)
+{
+ LLViewerObject *obj = gObjectList.findObject(agent_id);
+ while (obj && obj->isAttachment())
+ {
+ obj = (LLViewerObject*)obj->getParent();
+ }
+
+ if (obj && obj->isAvatar())
+ {
+ return (LLVOAvatar*)obj;
+ }
+ else
+ {
+ return NULL;
+ }
+}
+
+void LLNearbyVoiceModeration::requestMuteChange(const LLUUID& agent_id, bool mute)
+{
+ LLVOAvatar* avatar = getVOAvatarFromId(agent_id);
+ if (avatar)
+ {
+ LLViewerRegion* region = avatar->getRegion();
+ if (! region || ! region->capabilitiesReceived())
+ {
+ // TODO: Retry if fails since the capabilities may not have been received
+ // if this is called early into a region entry
+ LL_INFOS() << "Region or region capabilities unavailable" << LL_ENDL;
+ return;
+ }
+ LL_INFOS() << "Region name is " << region->getName() << LL_ENDL;
+
+ std::string url = region->getCapability("SpatialVoiceModerationRequest");
+ if (url.empty())
+ {
+ // TODO: Retry if fails since URL may not have not be available
+ // if this is called early into a region entry
+ LL_INFOS() << "Capability URL is empty" << LL_ENDL;
+ return;
+ }
+ LL_INFOS() << "Capability URL is " << url << LL_ENDL;
+
+ const std::string agent_name = avatar->getFullname();
+
+ const std::string operand = mute ? "mute" : "unmute";
+
+ LLSD body;
+ body["operand"] = operand;
+ body["agent_id"] = agent_id;
+ body["moderator_id"] = gAgent.getID();
+
+ LL_INFOS() << "Resident " << agent_name
+ << " (" << agent_id << ")" << " applying " << operand << LL_ENDL;
+
+ std::string success_msg =
+ STRINGIZE("Resident " << agent_name
+ << " (" << agent_id << ")" << " nearby voice was set to " << operand);
+
+ std::string failure_msg =
+ STRINGIZE("Unable to change voice muting for resident "
+ << agent_name << " (" << agent_id << ")");
+
+ LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(url, body,
+ success_msg,
+ failure_msg);
+ }
+}
diff --git a/indra/newview/llnearbyvoicemoderation.h b/indra/newview/llnearbyvoicemoderation.h
new file mode 100644
index 0000000000..4275754dbf
--- /dev/null
+++ b/indra/newview/llnearbyvoicemoderation.h
@@ -0,0 +1,41 @@
+/**
+ * @file llnearbyvoicemoderation.h
+ *
+ * $LicenseInfo:firstyear=2008&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2010, Linden Research, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License only.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
+ * $/LicenseInfo$
+ */
+
+#pragma once
+
+class LLVOAvatar;
+
+class LLNearbyVoiceModeration :
+ public LLSingleton <LLNearbyVoiceModeration> {
+ LLSINGLETON(LLNearbyVoiceModeration) {
+ };
+
+ ~LLNearbyVoiceModeration() {
+ };
+
+ public:
+ LLVOAvatar* getVOAvatarFromId(const LLUUID& id);
+ void requestMuteChange(const LLUUID& userID, bool mute);
+};
diff --git a/indra/newview/skins/default/xui/en/menu_conversation.xml b/indra/newview/skins/default/xui/en/menu_conversation.xml
index 62cdaa5886..cf9b8959ce 100644
--- a/indra/newview/skins/default/xui/en/menu_conversation.xml
+++ b/indra/newview/skins/default/xui/en/menu_conversation.xml
@@ -176,57 +176,58 @@
<on_click function="Group.DoToSelected" parameter="leave_group"/>
<on_enable function="Avatar.EnableItem" parameter="can_leave_group" />
</menu_item_call>
- <menu_item_separator layout="topleft" name="Moderator Options Separator"/>
- <context_menu
- label="Moderator Options"
- layout="topleft"
- name="Moderator Options">
- <menu_item_check
- label="Allow text chat"
- layout="topleft"
- name="AllowTextChat">
- <on_check function="Avatar.CheckItem" parameter="is_allowed_text_chat" />
- <on_click function="Avatar.DoToSelected" parameter="toggle_allow_text_chat" />
- <on_enable function="Avatar.EnableItem" parameter="can_allow_text_chat" />
- </menu_item_check>
- <menu_item_separator layout="topleft" name="moderate_voice_separator" />
- <menu_item_call
- label="Mute this participant"
- layout="topleft"
- name="ModerateVoiceMuteSelected">
- <on_click function="Avatar.DoToSelected" parameter="selected" />
- <on_enable function="Avatar.EnableItem" parameter="can_mute" />
- <on_visible function="Avatar.VisibleItem" parameter="show_mute" />
- </menu_item_call>
+ <menu_item_separator layout="topleft" name="Moderator Options Separator"/>
+ <context_menu
+ label="Moderator Options"
+ layout="topleft"
+ name="Moderator Options">
+ <menu_item_check
+ label="Allow text chat"
+ layout="topleft"
+ name="AllowTextChat">
+ <on_check function="Avatar.CheckItem" parameter="is_allowed_text_chat" />
+ <on_click function="Avatar.DoToSelected" parameter="toggle_allow_text_chat" />
+ <on_enable function="Avatar.EnableItem" parameter="can_allow_text_chat" />
+ <on_visible function="Avatar.VisibleItem" parameter="can_allow_text_chat" />
+ </menu_item_check>
+ <menu_item_separator layout="topleft" name="moderate_voice_separator" />
+ <menu_item_call
+ label="Mute this participant"
+ layout="topleft"
+ name="ModerateVoiceMuteSelected">
+ <on_click function="Avatar.DoToSelected" parameter="selected" />
+ <on_enable function="Avatar.EnableItem" parameter="can_mute" />
+ <on_visible function="Avatar.VisibleItem" parameter="show_mute" />
+ </menu_item_call>
+ <menu_item_call
+ label="Unmute this participant"
+ layout="topleft"
+ name="ModerateVoiceUnMuteSelected">
+ <on_click function="Avatar.DoToSelected" parameter="selected" />
+ <on_enable function="Avatar.EnableItem" parameter="can_unmute" />
+ <on_visible function="Avatar.VisibleItem" parameter="show_unmute" />
+ </menu_item_call>
+ <menu_item_call
+ label="Mute everyone"
+ layout="topleft"
+ name="ModerateVoiceMute">
+ <on_click function="Avatar.DoToSelected" parameter="mute_all" />
+ <on_enable function="Avatar.EnableItem" parameter="can_moderate_voice" />
+ </menu_item_call>
+ <menu_item_call
+ label="Unmute everyone"
+ layout="topleft"
+ name="ModerateVoiceUnmute">
+ <on_click function="Avatar.DoToSelected" parameter="unmute_all" />
+ <on_enable function="Avatar.EnableItem" parameter="can_moderate_voice" />
+ </menu_item_call>
+ </context_menu>
+ <menu_item_separator layout="topleft" name="Group Ban Separator"/>
<menu_item_call
- label="Unmute this participant"
- layout="topleft"
- name="ModerateVoiceUnMuteSelected">
- <on_click function="Avatar.DoToSelected" parameter="selected" />
- <on_enable function="Avatar.EnableItem" parameter="can_unmute" />
- <on_visible function="Avatar.VisibleItem" parameter="show_unmute" />
- </menu_item_call>
- <menu_item_call
- label="Mute everyone"
- layout="topleft"
- name="ModerateVoiceMute">
- <on_click function="Avatar.DoToSelected" parameter="mute_all" />
- <on_enable function="Avatar.EnableItem" parameter="can_moderate_voice" />
- </menu_item_call>
- <menu_item_call
- label="Unmute everyone"
- layout="topleft"
- name="ModerateVoiceUnmute">
- <on_click function="Avatar.DoToSelected" parameter="unmute_all" />
- <on_enable function="Avatar.EnableItem" parameter="can_moderate_voice" />
- </menu_item_call>
- </context_menu>
- <menu_item_separator layout="topleft" name="Group Ban Separator"/>
- <menu_item_call
- label="Ban member"
- layout="topleft"
- name="BanMember">
- <on_click function="Avatar.DoToSelected" parameter="ban_member" />
- <on_enable function="Avatar.EnableItem" parameter="can_ban_member" />
- </menu_item_call>
+ label="Ban member"
+ layout="topleft"
+ name="BanMember">
+ <on_click function="Avatar.DoToSelected" parameter="ban_member" />
+ <on_enable function="Avatar.EnableItem" parameter="can_ban_member" />
+ </menu_item_call>
</toggleable_menu>