mirror of
https://github.com/FirestormViewer/phoenix-firestorm.git
synced 2026-08-14 00:48:30 +00:00
Merge branch 'release/2026.02' of https://github.com/secondlife/viewer
# Conflicts: # indra/newview/app_settings/settings.xml # indra/newview/llfloaterimcontainer.cpp # indra/newview/llinventorygallerymenu.cpp # indra/newview/lllandmarkactions.cpp # indra/newview/llviewermenu.cpp # indra/newview/llvoicewebrtc.cpp
This commit is contained in:
@@ -1764,6 +1764,57 @@ void LLWebRTCPeerConnectionImpl::unsetDataObserver(LLWebRTCDataObserver* observe
|
||||
}
|
||||
}
|
||||
|
||||
class LLStatsCollectorCallback : public webrtc::RTCStatsCollectorCallback
|
||||
{
|
||||
public:
|
||||
typedef std::function<void(const LLWebRTCStatsMap&)> StatsCallback;
|
||||
|
||||
LLStatsCollectorCallback(StatsCallback callback) : callback_(callback) {}
|
||||
|
||||
void OnStatsDelivered(const webrtc::scoped_refptr<const webrtc::RTCStatsReport>& report) override
|
||||
{
|
||||
if (callback_)
|
||||
{
|
||||
// Transform RTCStatsReport stats to simple map
|
||||
LLWebRTCStatsMap stats_map;
|
||||
for (const auto& stats : *report)
|
||||
{
|
||||
std::map<std::string, std::string> stat_attributes;
|
||||
|
||||
// Convert each attribute to string format
|
||||
for (const auto& attribute : stats.Attributes())
|
||||
{
|
||||
stat_attributes[attribute.name()] = attribute.ToString();
|
||||
}
|
||||
stats_map[stats.id()] = stat_attributes;
|
||||
}
|
||||
callback_(stats_map);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
StatsCallback callback_;
|
||||
};
|
||||
|
||||
void LLWebRTCPeerConnectionImpl::gatherConnectionStats()
|
||||
{
|
||||
if (!mPeerConnection)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto stats_callback = webrtc::make_ref_counted<LLStatsCollectorCallback>(
|
||||
[this](const LLWebRTCStatsMap& generic_stats)
|
||||
{
|
||||
for (auto& observer : mSignalingObserverList)
|
||||
{
|
||||
observer->OnStatsDelivered(generic_stats);
|
||||
}
|
||||
});
|
||||
|
||||
mPeerConnection->GetStats(stats_callback.get());
|
||||
}
|
||||
|
||||
LLWebRTCImpl * gWebRTCImpl = nullptr;
|
||||
LLWebRTCDeviceInterface * getDeviceInterface()
|
||||
{
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#ifndef LLWEBRTC_H
|
||||
#define LLWEBRTC_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
|
||||
@@ -65,6 +66,7 @@ inline std::atomic<bool> gWebRTCUpdateDevices = false;
|
||||
// </FS:minerjr> [FIRE-36022]
|
||||
namespace llwebrtc
|
||||
{
|
||||
typedef std::map<std::string, std::map<std::string, std::string>> LLWebRTCStatsMap;
|
||||
|
||||
class LLWebRTCLogCallback
|
||||
{
|
||||
@@ -250,6 +252,8 @@ class LLWebRTCSignalingObserver
|
||||
// Called when the data channel has been established and data
|
||||
// transfer can begin.
|
||||
virtual void OnDataChannelReady(LLWebRTCDataInterface *data_interface) = 0;
|
||||
|
||||
virtual void OnStatsDelivered(const LLWebRTCStatsMap& stats_data) {}
|
||||
};
|
||||
|
||||
// LLWebRTCPeerConnectionInterface representsd a connection to a peer,
|
||||
@@ -283,6 +287,8 @@ class LLWebRTCPeerConnectionInterface
|
||||
virtual void unsetSignalingObserver(LLWebRTCSignalingObserver* observer) = 0;
|
||||
|
||||
virtual void AnswerAvailable(const std::string &sdp) = 0;
|
||||
|
||||
virtual void gatherConnectionStats() = 0;
|
||||
};
|
||||
|
||||
// The following define the dynamic linked library
|
||||
|
||||
@@ -648,6 +648,8 @@ class LLWebRTCPeerConnectionImpl : public LLWebRTCPeerConnectionInterface,
|
||||
void enableSenderTracks(bool enable);
|
||||
void enableReceiverTracks(bool enable);
|
||||
|
||||
void gatherConnectionStats() override;
|
||||
|
||||
protected:
|
||||
|
||||
LLWebRTCImpl * mWebRTCImpl;
|
||||
|
||||
@@ -546,6 +546,7 @@ set(viewer_SOURCE_FILES
|
||||
llfloaterimnearbychat.cpp
|
||||
llfloaterimnearbychathandler.cpp
|
||||
llfloaterimnearbychatlistener.cpp
|
||||
llnearbyvoicemoderation.cpp
|
||||
llnetmap.cpp
|
||||
llnotificationalerthandler.cpp
|
||||
llnotificationgrouphandler.cpp
|
||||
@@ -1403,6 +1404,7 @@ set(viewer_HEADER_FILES
|
||||
llnameeditor.h
|
||||
llnamelistctrl.h
|
||||
llnavigationbar.h
|
||||
llnearbyvoicemoderation.h
|
||||
llnetmap.h
|
||||
llnotificationhandler.h
|
||||
llnotificationlistitem.h
|
||||
|
||||
@@ -1 +1 @@
|
||||
26.1.0
|
||||
26.2.0
|
||||
|
||||
@@ -2217,7 +2217,7 @@
|
||||
<key>Type</key>
|
||||
<string>String</string>
|
||||
<key>Value</key>
|
||||
<string>http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/vawp/index.html</string>
|
||||
<string>https://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/vawp/index.html</string>
|
||||
</map>
|
||||
<!--AvatarBakedTextureUploadTimeout is in use by QA-->
|
||||
<key>AvatarBakedTextureUploadTimeout</key>
|
||||
@@ -4858,7 +4858,7 @@
|
||||
<key>Type</key>
|
||||
<string>String</string>
|
||||
<key>Value</key>
|
||||
<string>http://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/guide.html</string>
|
||||
<string>https://lecs-viewer-web-components.s3.amazonaws.com/v3.0/[GRID_LOWERCASE]/guide.html</string>
|
||||
<key>Backup</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
@@ -5211,7 +5211,7 @@
|
||||
<key>Type</key>
|
||||
<string>String</string>
|
||||
<key>Value</key>
|
||||
<string>http://events.[GRID]/viewer/embed/event/[EVENT_ID]</string>
|
||||
<string>https://events.[GRID]/viewer/embed/event/[EVENT_ID]</string>
|
||||
</map>
|
||||
<key>FastCacheFetchEnabled</key>
|
||||
<map>
|
||||
@@ -6720,7 +6720,7 @@
|
||||
<key>Type</key>
|
||||
<string>String</string>
|
||||
<key>Value</key>
|
||||
<string>http://wiki.secondlife.com/wiki/[LSL_STRING]</string>
|
||||
<string>https://wiki.secondlife.com/wiki/[LSL_STRING]</string>
|
||||
<key>Backup</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
@@ -7323,7 +7323,7 @@
|
||||
<key>Type</key>
|
||||
<string>F32</string>
|
||||
<key>Value</key>
|
||||
<real>30.0</real>
|
||||
<real>60.0</real>
|
||||
</map>
|
||||
<key>MapScale</key>
|
||||
<map>
|
||||
@@ -9454,6 +9454,39 @@ Change of this parameter will affect the layout of buttons in notification toast
|
||||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
<key>OpenDebugStatVoice</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Expand Voice (WebRTC) stats display</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
<key>OpenDebugStatVoiceOutgoing</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Expand Outgoing audio (Voice) stats display</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
<key>OpenDebugStatVoiceIncoming</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Expand Incoming audio (Voice) stats display</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
<key>OutBandwidth</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
@@ -19714,7 +19747,7 @@ Change of this parameter will affect the layout of buttons in notification toast
|
||||
<key>Type</key>
|
||||
<string>String</string>
|
||||
<key>Value</key>
|
||||
<string>http://common-flash-secondlife-com.s3.amazonaws.com/viewer/v2.6/agni/404.html</string>
|
||||
<string>https://common-flash-secondlife-com.s3.amazonaws.com/viewer/v2.6/agni/404.html</string>
|
||||
<key>Backup</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
|
||||
@@ -1574,6 +1574,7 @@ bool LLAppViewer::frame()
|
||||
|
||||
bool LLAppViewer::doFrame()
|
||||
{
|
||||
resumeMainloopTimeout("Main:doFrameStart");
|
||||
#ifdef LL_DISCORD
|
||||
{
|
||||
LL_PROFILE_ZONE_NAMED("discord_callbacks");
|
||||
@@ -1958,6 +1959,11 @@ bool LLAppViewer::doFrame()
|
||||
LL_INFOS() << "Exiting main_loop" << LL_ENDL;
|
||||
}
|
||||
}LLPerfStats::StatsRecorder::endFrame();
|
||||
|
||||
// Not viewer's fault if something outside frame
|
||||
// pauses viewer (ex: macOS doesn't call oneFrame),
|
||||
// so stop tracking on exit.
|
||||
pauseMainloopTimeout();
|
||||
LL_PROFILER_FRAME_END;
|
||||
|
||||
return ! LLApp::isRunning();
|
||||
@@ -6860,7 +6866,8 @@ F32 LLAppViewer::getMainloopTimeoutSec() const
|
||||
if (LLStartUp::getStartupState() == STATE_STARTED
|
||||
&& gAgent.getTeleportState() == LLAgent::TELEPORT_NONE)
|
||||
{
|
||||
static LLCachedControl<F32> mainloop_started(gSavedSettings, "MainloopTimeoutStarted", 30.f);
|
||||
// consider making this value match 'disconnected' timout.
|
||||
static LLCachedControl<F32> mainloop_started(gSavedSettings, "MainloopTimeoutStarted", 60.f);
|
||||
return mainloop_started();
|
||||
}
|
||||
else
|
||||
|
||||
@@ -505,7 +505,9 @@ bool LLFeatureManager::loadGPUClass()
|
||||
{
|
||||
mGPUClass = GPU_CLASS_2;
|
||||
}
|
||||
else if (gbps <= class1_gbps*4.f)
|
||||
else if ((gbps <= class1_gbps*4.f)
|
||||
// Cap silicon's GPUs at med+ as they have high throughput, low capability
|
||||
|| gGLManager.mIsApple)
|
||||
{
|
||||
mGPUClass = GPU_CLASS_3;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@
|
||||
#include "llsdserialize.h"
|
||||
#include "llviewermenu.h" // is_agent_mappable
|
||||
#include "llviewerobjectlist.h"
|
||||
#include "llvoavatar.h"
|
||||
#include "llnearbyvoicemoderation.h"
|
||||
// [RLVa:KB] - @pay
|
||||
#include "rlvactions.h"
|
||||
// [/RLVa:KB]
|
||||
@@ -94,6 +96,7 @@ LLFloaterIMContainer::LLFloaterIMContainer(const LLSD& seed, const Params& param
|
||||
|
||||
mAutoResize = false;
|
||||
LLTransientFloaterMgr::getInstance()->addControlView(LLTransientFloaterMgr::IM, this);
|
||||
LLNearbyVoiceModeration::getInstance();
|
||||
}
|
||||
|
||||
LLFloaterIMContainer::~LLFloaterIMContainer()
|
||||
@@ -534,6 +537,33 @@ void LLFloaterIMContainer::idleUpdate()
|
||||
mGeneralTitleInUse = !needs_override;
|
||||
setTitle(needs_override ? conversation_floaterp->getTitle() : mGeneralTitle);
|
||||
}
|
||||
const LLConversationItem* nearby_session = getSessionModel(LLUUID());
|
||||
if (nearby_session)
|
||||
{
|
||||
LLSpeakerMgr* speaker_mgr = (LLSpeakerMgr*)(LLLocalSpeakerMgr::getInstance());
|
||||
|
||||
LLFolderViewModelItemCommon::child_list_t::const_iterator current_participant_model = nearby_session->getChildrenBegin();
|
||||
LLFolderViewModelItemCommon::child_list_t::const_iterator end_participant_model = nearby_session->getChildrenEnd();
|
||||
while (current_participant_model != end_participant_model)
|
||||
{
|
||||
LLConversationItemParticipant* participant_model =
|
||||
dynamic_cast<LLConversationItemParticipant*>((*current_participant_model).get());
|
||||
if (participant_model)
|
||||
{
|
||||
bool show_moderator_options = LLNearbyVoiceModeration::getInstance()->isNearbyChatModerator();
|
||||
LLUUID participant_id = participant_model->getUUID();
|
||||
if (participant_id != gAgentID)
|
||||
{
|
||||
// Don't show moderator options if participant is not connected to the same spatial channel
|
||||
LLSpeaker* speakerp = speaker_mgr->findSpeaker(participant_id).get();
|
||||
show_moderator_options &= speakerp && speakerp->isInVoiceChannel();
|
||||
}
|
||||
participant_model->setModeratorOptionsVisible(show_moderator_options);
|
||||
}
|
||||
|
||||
current_participant_model++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mParticipantRefreshTimer.setTimerExpirySec(1.0f);
|
||||
@@ -1697,6 +1727,10 @@ bool LLFloaterIMContainer::visibleContextMenuItem(const LLSD& userdata)
|
||||
{
|
||||
return isMuted(conversation_item->getUUID());
|
||||
}
|
||||
else if ("can_allow_text_chat" == item)
|
||||
{
|
||||
return !isNearbyChatSpeakerSelected();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -2026,9 +2060,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 (LLNearbyVoiceModeration::getInstance()->isNearbyChatModerator() && isNearbyChatSpeakerSelected())
|
||||
{
|
||||
// Determine here which actions are allowed
|
||||
if ("can_moderate_voice" == userdata)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (("can_mute" == userdata))
|
||||
{
|
||||
return !is_self;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -2161,7 +2213,35 @@ 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)
|
||||
{
|
||||
// Request a mute/unmute using a capability request via the simulator
|
||||
LLNearbyVoiceModeration::getInstance()->requestMuteIndividual(userID, !isMuted(userID));
|
||||
}
|
||||
else
|
||||
if ("mute_all" == command)
|
||||
{
|
||||
// Send the mute_all request to the server
|
||||
const bool mute_state = true;
|
||||
LLNearbyVoiceModeration::getInstance()->requestMuteAll(mute_state);
|
||||
}
|
||||
else
|
||||
if ("unmute_all" == command)
|
||||
{
|
||||
// Send the unmute_all request to the server
|
||||
const bool mute_state = false;
|
||||
LLNearbyVoiceModeration::getInstance()->requestMuteAll(mute_state);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (command.compare("selected"))
|
||||
{
|
||||
@@ -2279,6 +2359,31 @@ LLSpeaker * LLFloaterIMContainer::getSpeakerOfSelectedParticipant(LLSpeakerMgr *
|
||||
return speaker_managerp->findSpeaker(participant_itemp->getUUID());
|
||||
}
|
||||
|
||||
bool LLFloaterIMContainer::isNearbyChatSpeakerSelected()
|
||||
{
|
||||
LLFolderViewItem *selectedItem = mConversationsRoot->getCurSelectedItem();
|
||||
if (!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();
|
||||
}
|
||||
|
||||
void LLFloaterIMContainer::toggleAllowTextChat(const LLUUID& participant_uuid)
|
||||
{
|
||||
LLIMSpeakerMgr * speaker_managerp = dynamic_cast<LLIMSpeakerMgr*>(getSpeakerMgrForSelectedParticipant());
|
||||
|
||||
@@ -180,6 +180,7 @@ private:
|
||||
void banSelectedMember(const LLUUID& participant_uuid);
|
||||
void openNearbyChat();
|
||||
bool isParticipantListExpanded();
|
||||
bool isNearbyChatSpeakerSelected();
|
||||
|
||||
void idleUpdate(); // for convenience (self) from static idle
|
||||
void idleProcessEvents();
|
||||
|
||||
@@ -2935,6 +2935,22 @@ void LLFloaterPreference::onChangeMaturity()
|
||||
|| sim_access == SIM_ACCESS_ADULT);
|
||||
|
||||
getChild<LLIconCtrl>("rating_icon_adult")->setVisible(sim_access == SIM_ACCESS_ADULT);
|
||||
|
||||
// Update Legacy Search maturity settings
|
||||
bool can_access_mature = gAgent.canAccessMature();
|
||||
bool can_access_adult = gAgent.canAccessAdult();
|
||||
if (!can_access_mature)
|
||||
{
|
||||
gSavedSettings.setBOOL("ShowMatureSims", false);
|
||||
gSavedSettings.setBOOL("ShowMatureLand", false);
|
||||
gSavedSettings.setBOOL("ShowMatureClassifieds", false);
|
||||
}
|
||||
if (!can_access_adult)
|
||||
{
|
||||
gSavedSettings.setBOOL("ShowAdultSims", false);
|
||||
gSavedSettings.setBOOL("ShowAdultLand", false);
|
||||
gSavedSettings.setBOOL("ShowAdultClassifieds", false);
|
||||
}
|
||||
}
|
||||
|
||||
void LLFloaterPreference::onChangeComplexityMode(const LLSD& newvalue)
|
||||
|
||||
@@ -2015,7 +2015,14 @@ void LLItemBridge::performAction(LLInventoryModel* model, std::string action)
|
||||
{
|
||||
LLVector3d global_pos;
|
||||
landmark->getGlobalPos(global_pos);
|
||||
LLLandmarkActions::getSLURLfromPosGlobal(global_pos, ©_slurl_to_clipboard_callback_inv, true);
|
||||
if (!global_pos.isExactlyZero())
|
||||
{
|
||||
LLLandmarkActions::getSLURLfromPosGlobal(global_pos, ©_slurl_to_clipboard_callback_inv, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
LLNotificationsUtil::add("LandmarkLocationUnknown");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -338,26 +338,21 @@ void LLInventoryGalleryContextMenu::doToSelected(const LLSD& userdata)
|
||||
{
|
||||
LLVector3d global_pos;
|
||||
landmark->getGlobalPos(global_pos);
|
||||
boost::function<void(std::string& slurl)> copy_slurl_to_clipboard_cb = [](const std::string& slurl)
|
||||
if (!global_pos.isExactlyZero())
|
||||
{
|
||||
gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl));
|
||||
LLSD args;
|
||||
args["SLURL"] = slurl;
|
||||
LLNotificationsUtil::add("CopySLURL", args);
|
||||
};
|
||||
// <FS:Zi> GCC12 warning: maybe-uninitialized - probably bogus
|
||||
#if defined(__GNUC__) && (__GNUC__ >= 12)
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wuninitialized"
|
||||
#endif
|
||||
// </FS:Zi>
|
||||
LLLandmarkActions::getSLURLfromPosGlobal(global_pos, copy_slurl_to_clipboard_cb, true);
|
||||
// <FS:Zi> GCC12 warning: maybe-uninitialized - probably bogus
|
||||
#if defined(__GNUC__) && (__GNUC__ >= 12)
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
// </FS:Zi>
|
||||
|
||||
boost::function<void(std::string& slurl)> copy_slurl_to_clipboard_cb = [](const std::string& slurl)
|
||||
{
|
||||
gViewerWindow->getWindow()->copyTextToClipboard(utf8str_to_wstring(slurl));
|
||||
LLSD args;
|
||||
args["SLURL"] = slurl;
|
||||
LLNotificationsUtil::add("CopySLURL", args);
|
||||
};
|
||||
LLLandmarkActions::getSLURLfromPosGlobal(global_pos, copy_slurl_to_clipboard_cb, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
LLNotificationsUtil::add("LandmarkLocationUnknown");
|
||||
}
|
||||
};
|
||||
LLLandmark* landmark = LLLandmarkActions::getLandmark(mUUIDs.front(), copy_slurl_cb);
|
||||
if (landmark)
|
||||
|
||||
@@ -80,6 +80,40 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class LLFetchFirstLandmarkByPos : public LLInventoryCollectFunctor
|
||||
{
|
||||
private:
|
||||
LLVector3d mPos;
|
||||
bool mFound = false;
|
||||
public:
|
||||
LLFetchFirstLandmarkByPos(const LLVector3d& pos) :
|
||||
mPos(pos), mFound(false)
|
||||
{
|
||||
}
|
||||
|
||||
/*virtual*/ bool operator()(LLInventoryCategory* cat, LLInventoryItem* item)
|
||||
{
|
||||
if (mFound || !item || item->getType() != LLAssetType::AT_LANDMARK)
|
||||
return false;
|
||||
|
||||
LLLandmark* landmark = gLandmarkList.getAsset(item->getAssetUUID());
|
||||
if (!landmark) // the landmark not been loaded yet
|
||||
return false;
|
||||
|
||||
LLVector3d landmark_global_pos;
|
||||
if (!landmark->getGlobalPos(landmark_global_pos))
|
||||
return false;
|
||||
//we have to round off each coordinates to compare positions properly
|
||||
mFound = ll_round(mPos.mdV[VX]) == ll_round(landmark_global_pos.mdV[VX])
|
||||
&& ll_round(mPos.mdV[VY]) == ll_round(landmark_global_pos.mdV[VY])
|
||||
&& ll_round(mPos.mdV[VZ]) == ll_round(landmark_global_pos.mdV[VZ]);
|
||||
return mFound;
|
||||
}
|
||||
|
||||
// only care about first found landmark, so stop when found
|
||||
/*virtual*/ bool exceedsLimit() { return mFound; }
|
||||
};
|
||||
|
||||
class LLFetchLandmarksByName : public LLInventoryCollectFunctor
|
||||
{
|
||||
private:
|
||||
@@ -155,6 +189,9 @@ public:
|
||||
mFounded = LLViewerParcelMgr::getInstance()->inAgentParcel(landmark_global_pos);
|
||||
return mFounded;
|
||||
}
|
||||
|
||||
// only care about first found landmark, so stop when found
|
||||
/*virtual*/ bool exceedsLimit() { return mFounded; }
|
||||
};
|
||||
|
||||
static void fetch_landmarks(LLInventoryModel::cat_array_t& cats,
|
||||
@@ -197,23 +234,61 @@ bool LLLandmarkActions::landmarkAlreadyExists()
|
||||
//static
|
||||
bool LLLandmarkActions::hasParcelLandmark()
|
||||
{
|
||||
static LLUUID sLastItemID;
|
||||
static S32 sLastFrame = -1;
|
||||
if (sLastItemID.notNull())
|
||||
{
|
||||
LLInventoryItem* item = gInventory.getItem(sLastItemID);
|
||||
if (item)
|
||||
{
|
||||
LLLandmark* landmark = gLandmarkList.getAsset(item->getAssetUUID());
|
||||
if (landmark)
|
||||
{
|
||||
LLVector3d landmark_global_pos;
|
||||
if (landmark->getGlobalPos(landmark_global_pos)
|
||||
&& LLViewerParcelMgr::getInstance()->inAgentParcel(landmark_global_pos))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Cached landmark does not match current parcel anymore,
|
||||
// repeat inventory search to find a replacement landmark
|
||||
// or to make sure there are none.
|
||||
sLastItemID.setNull();
|
||||
sLastFrame = -1;
|
||||
}
|
||||
|
||||
if (sLastFrame == LLFrameTimer::getFrameCount())
|
||||
{
|
||||
// Ideally this should also check parcel change and landmark additions,
|
||||
// not just frame change.
|
||||
// But should be sufficient to check only frame as this is used
|
||||
// after inventory and parcel operations.
|
||||
return false;
|
||||
}
|
||||
sLastFrame = LLFrameTimer::getFrameCount();
|
||||
|
||||
LLFirstAgentParcelLandmark get_first_agent_landmark;
|
||||
LLInventoryModel::cat_array_t cats;
|
||||
LLInventoryModel::item_array_t items;
|
||||
fetch_landmarks(cats, items, get_first_agent_landmark);
|
||||
return !items.empty();
|
||||
|
||||
if (!items.empty())
|
||||
{
|
||||
sLastItemID = items[0]->getUUID();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// *TODO: This could be made more efficient by only fetching the FIRST
|
||||
// landmark that meets the criteria
|
||||
LLViewerInventoryItem* LLLandmarkActions::findLandmarkForGlobalPos(const LLVector3d &pos)
|
||||
{
|
||||
// Determine whether there are landmarks pointing to the current parcel.
|
||||
// Will stop after first found landmark.
|
||||
LLInventoryModel::cat_array_t cats;
|
||||
LLInventoryModel::item_array_t items;
|
||||
LLFetchlLandmarkByPos is_current_pos_landmark(pos);
|
||||
fetch_landmarks(cats, items, is_current_pos_landmark);
|
||||
LLFetchFirstLandmarkByPos get_landmark_from_pos(pos);
|
||||
fetch_landmarks(cats, items, get_landmark_from_pos);
|
||||
|
||||
if(items.empty())
|
||||
{
|
||||
@@ -462,12 +537,17 @@ void LLLandmarkActions::copySLURLtoClipboard(const LLUUID& landmarkInventoryItem
|
||||
{
|
||||
LLVector3d global_pos;
|
||||
landmark->getGlobalPos(global_pos);
|
||||
// <FS:Beq> FIRE-30534 - changes related to var regions in opensim
|
||||
// LLLandmarkActions::getSLURLfromPosGlobal(global_pos,©_slurl_to_clipboard_callback,true);
|
||||
LLVector3 region_pos;
|
||||
region_pos = landmark->getRegionPos();
|
||||
LLLandmarkActions::getSLURLfromPosGlobalAndLocal(global_pos, region_pos, ©_slurl_to_clipboard_callback,true);
|
||||
// </FS:Beq>
|
||||
if (!global_pos.isExactlyZero())
|
||||
{
|
||||
// <FS:Beq> FIRE-30534 - changes related to var regions in opensim
|
||||
// LLLandmarkActions::getSLURLfromPosGlobal(global_pos, ©_slurl_to_clipboard_callback, true);
|
||||
LLLandmarkActions::getSLURLfromPosGlobalAndLocal(global_pos, landmark->getRegionPos(), ©_slurl_to_clipboard_callback, true);
|
||||
// </FS:Beq>
|
||||
}
|
||||
else
|
||||
{
|
||||
LLNotificationsUtil::add("LandmarkLocationUnknown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -866,6 +866,10 @@ void LLMuteList::processMuteListUpdate(LLMessageSystem* msg, void**)
|
||||
std::string unclean_filename;
|
||||
msg->getStringFast(_PREHASH_MuteData, _PREHASH_Filename, unclean_filename);
|
||||
std::string filename = LLDir::getScrubbedFileName(unclean_filename);
|
||||
if (filename.empty())
|
||||
{
|
||||
LL_WARNS() << "Received empty mute list filename." << LL_ENDL;
|
||||
}
|
||||
|
||||
LLMuteList* mute_list = getInstance();
|
||||
mute_list->mLoadState = ML_REQUESTED;
|
||||
@@ -898,16 +902,16 @@ void LLMuteList::processUseCachedMuteList(LLMessageSystem* msg, void**)
|
||||
|
||||
void LLMuteList::onFileMuteList(void** user_data, S32 error_code, LLExtStat ext_status)
|
||||
{
|
||||
LL_INFOS() << "LLMuteList::processMuteListFile()" << LL_ENDL;
|
||||
|
||||
std::string* local_filename_and_path = (std::string*)user_data;
|
||||
if(local_filename_and_path && !local_filename_and_path->empty() && (error_code == 0))
|
||||
{
|
||||
LL_INFOS() << "Received mute list from server" << LL_ENDL;
|
||||
LLMuteList::getInstance()->loadFromFile(*local_filename_and_path);
|
||||
LLFile::remove(*local_filename_and_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_INFOS() << "LLMuteList xfer failed with code " << error_code << LL_ENDL;
|
||||
LLMuteList::getInstance()->mLoadState = ML_FAILED;
|
||||
}
|
||||
delete local_filename_and_path;
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
/**
|
||||
* @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 "llnotificationsutil.h"
|
||||
#include "llviewerregion.h"
|
||||
#include "llvoavatar.h"
|
||||
#include "llvoicechannel.h"
|
||||
#include "llvoiceclient.h"
|
||||
#include "llviewerobjectlist.h"
|
||||
#include "llviewerparcelmgr.h"
|
||||
#include "roles_constants.h"
|
||||
|
||||
#include "llnearbyvoicemoderation.h"
|
||||
|
||||
LLNearbyVoiceModeration::LLNearbyVoiceModeration()
|
||||
{
|
||||
}
|
||||
|
||||
LLNearbyVoiceModeration::~LLNearbyVoiceModeration()
|
||||
{
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string LLNearbyVoiceModeration::getCapUrlFromRegion(LLViewerRegion* region)
|
||||
{
|
||||
if (! region || ! region->capabilitiesReceived())
|
||||
{
|
||||
return std::string();
|
||||
}
|
||||
|
||||
std::string url = region->getCapability("SpatialVoiceModerationRequest");
|
||||
if (url.empty())
|
||||
{
|
||||
LL_INFOS() << "Capability URL for region " << region->getName() << " is empty" << LL_ENDL;
|
||||
return std::string();
|
||||
}
|
||||
LL_INFOS() << "Capability URL for region " << region->getName() << " is " << url << LL_ENDL;
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
void LLNearbyVoiceModeration::requestMuteIndividual(const LLUUID& agent_id, bool mute)
|
||||
{
|
||||
LLVOAvatar* avatar = getVOAvatarFromId(agent_id);
|
||||
if (avatar)
|
||||
{
|
||||
const std::string cap_url = getCapUrlFromRegion(avatar->getRegion());
|
||||
if (cap_url.length())
|
||||
{
|
||||
const std::string operand = mute ? "mute" : "unmute";
|
||||
|
||||
LLSD body;
|
||||
body["operand"] = operand;
|
||||
body["agent_id"] = agent_id;
|
||||
|
||||
const std::string agent_name = avatar->getFullname();
|
||||
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(
|
||||
cap_url,
|
||||
body,
|
||||
success_msg,
|
||||
failure_msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LLNearbyVoiceModeration::requestMuteAll(bool mute)
|
||||
{
|
||||
// Use our own avatar to get the region name
|
||||
LLViewerRegion* region = gAgent.getRegion();
|
||||
|
||||
const std::string cap_url = getCapUrlFromRegion(region);
|
||||
if (cap_url.length())
|
||||
{
|
||||
const std::string operand = mute ? "mute_all" : "unmute_all";
|
||||
|
||||
LLSD body;
|
||||
body["operand"] = operand;
|
||||
|
||||
LL_INFOS() << "For all residents in this region, applying: " << operand << LL_ENDL;
|
||||
|
||||
std::string success_msg =
|
||||
STRINGIZE("Nearby voice for all residents was set to: " << operand);
|
||||
|
||||
std::string failure_msg =
|
||||
STRINGIZE("Unable to set nearby voice for all residents to: " << operand);
|
||||
|
||||
LLCoreHttpUtil::HttpCoroutineAdapter::messageHttpPost(
|
||||
cap_url,
|
||||
body,
|
||||
success_msg,
|
||||
failure_msg);
|
||||
}
|
||||
}
|
||||
|
||||
void LLNearbyVoiceModeration::setMutedInfo(const std::string& channelID, bool mute)
|
||||
{
|
||||
auto it = mChannelMuteMap.find(channelID);
|
||||
if (it == mChannelMuteMap.end())
|
||||
{
|
||||
if (mute)
|
||||
{
|
||||
// Channel is new and being muted
|
||||
showMutedNotification(true);
|
||||
}
|
||||
mChannelMuteMap[channelID] = mute;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (it->second != mute)
|
||||
{
|
||||
// Flag changed
|
||||
showMutedNotification(mute);
|
||||
it->second = mute;
|
||||
}
|
||||
}
|
||||
if (mute && LLVoiceClient::getInstance()->getUserPTTState())
|
||||
{
|
||||
LLVoiceClient::getInstance()->setUserPTTState(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool LLNearbyVoiceModeration::showNotificationIfNeeded()
|
||||
{
|
||||
if (LLVoiceClient::getInstance()->inProximalChannel() &&
|
||||
LLVoiceClient::getInstance()->getIsModeratorMuted(gAgentID))
|
||||
{
|
||||
return showMutedNotification(true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LLNearbyVoiceModeration::showMutedNotification(bool is_muted)
|
||||
{
|
||||
// Check if the current voice channel is nearby chat
|
||||
if (LLVoiceClient::getInstance()->inProximalChannel())
|
||||
{
|
||||
LLNotificationsUtil::add(is_muted ? "NearbyVoiceMutedByModerator" : "NearbyVoiceUnmutedByModerator");
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool LLNearbyVoiceModeration::isNearbyChatModerator()
|
||||
{
|
||||
// Region doesn't support WebRTC voice
|
||||
if (!gAgent.getRegion() || !gAgent.getRegion()->isRegionWebRTCEnabled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only show moderator options when connected to spatial voice chat
|
||||
LLVoiceChannel* channel = LLVoiceChannel::getCurrentVoiceChannel();
|
||||
if (!channel || channel->getSessionID().notNull() || !LLAgent::isActionAllowed("speak"))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (LLViewerParcelMgr::getInstance()->isVoiceRestricted())
|
||||
{
|
||||
// Only the parcel owner should have access to moderate parcel voice space
|
||||
return LLViewerParcelMgr::getInstance()->allowVoiceModeration();
|
||||
}
|
||||
else
|
||||
{
|
||||
return gAgent.canManageEstate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* @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:
|
||||
void requestMuteIndividual(const LLUUID& userID, bool mute);
|
||||
void requestMuteAll(bool mute);
|
||||
|
||||
void setMutedInfo(const std::string& channelID, bool mute);
|
||||
bool showMutedNotification(bool is_muted);
|
||||
bool showNotificationIfNeeded();
|
||||
|
||||
bool isNearbyChatModerator();
|
||||
|
||||
private:
|
||||
LLVOAvatar* getVOAvatarFromId(const LLUUID& id);
|
||||
const std::string getCapUrlFromRegion(LLViewerRegion* region);
|
||||
|
||||
boost::signals2::connection mParcelCallbackConnection;
|
||||
std::map<std::string, bool> mChannelMuteMap;
|
||||
};
|
||||
@@ -567,9 +567,6 @@ void LLPanelDirBrowser::processDirEventsReply(LLMessageSystem* msg, void**)
|
||||
LLUUID owner_id;
|
||||
std::string name;
|
||||
std::string date;
|
||||
bool show_pg = gSavedSettings.getBOOL("ShowPGEvents");
|
||||
bool show_mature = gSavedSettings.getBOOL("ShowMatureEvents");
|
||||
bool show_adult = gSavedSettings.getBOOL("ShowAdultEvents");
|
||||
|
||||
msg->getUUID("AgentData", "AgentID", agent_id);
|
||||
msg->getUUID("QueryData", "QueryID", query_id );
|
||||
@@ -626,27 +623,6 @@ void LLPanelDirBrowser::processDirEventsReply(LLMessageSystem* msg, void**)
|
||||
LL_WARNS() << "skipped event due to owner_id null, event_id " << event_id << LL_ENDL;
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip events that don't match the flags
|
||||
// there's no PG flag, so we make sure neither adult nor mature is set
|
||||
if (((event_flags & (EVENT_FLAG_ADULT | EVENT_FLAG_MATURE)) == EVENT_FLAG_NONE) && !show_pg)
|
||||
{
|
||||
//llwarns << "Skipped pg event because we're not showing pg, event_id " << event_id << llendl;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((event_flags & EVENT_FLAG_MATURE) && !show_mature)
|
||||
{
|
||||
//llwarns << "Skipped mature event because we're not showing mature, event_id " << event_id << llendl;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((event_flags & EVENT_FLAG_ADULT) && !show_adult)
|
||||
{
|
||||
//llwarns << "Skipped adult event because we're not showing adult, event_id " << event_id << llendl;
|
||||
continue;
|
||||
}
|
||||
|
||||
LLSD content;
|
||||
|
||||
content["type"] = EVENT_CODE;
|
||||
|
||||
@@ -143,18 +143,17 @@ void LLPanelDirEvents::performQueryOrDelete(U32 event_id)
|
||||
static LLUICachedControl<bool> incpg("ShowPGEvents", true);
|
||||
static LLUICachedControl<bool> incmature("ShowMatureEvents", false);
|
||||
static LLUICachedControl<bool> incadult("ShowAdultEvents", false);
|
||||
if (!(incpg || incmature || incadult))
|
||||
{
|
||||
LLNotificationsUtil::add("NoContentToSearch");
|
||||
return;
|
||||
}
|
||||
|
||||
U32 scope = DFQ_DATE_EVENTS;
|
||||
if (incpg) scope |= DFQ_INC_PG;
|
||||
if (incmature && gAgent.canAccessMature()) scope |= DFQ_INC_MATURE;
|
||||
if (incadult && gAgent.canAccessAdult()) scope |= DFQ_INC_ADULT;
|
||||
|
||||
if ( !( scope & (DFQ_INC_PG | DFQ_INC_MATURE | DFQ_INC_ADULT )))
|
||||
{
|
||||
LLNotificationsUtil::add("NoContentToSearch");
|
||||
return;
|
||||
}
|
||||
|
||||
setupNewSearch();
|
||||
|
||||
std::ostringstream params;
|
||||
|
||||
@@ -62,20 +62,6 @@ bool LLPanelDirLand::postBuild()
|
||||
|
||||
childSetValue("type", gSavedSettings.getString("FindLandType"));
|
||||
|
||||
bool adult_enabled = gAgent.canAccessAdult();
|
||||
bool mature_enabled = gAgent.canAccessMature();
|
||||
childSetVisible("incpg", true);
|
||||
if (!mature_enabled)
|
||||
{
|
||||
childSetValue("incmature", false);
|
||||
childDisable("incmature");
|
||||
}
|
||||
if (!adult_enabled)
|
||||
{
|
||||
childSetValue("incadult", false);
|
||||
childDisable("incadult");
|
||||
}
|
||||
|
||||
childSetCommitCallback("pricecheck", onCommitPrice, this);
|
||||
childSetCommitCallback("areacheck", onCommitArea, this);
|
||||
|
||||
|
||||
@@ -121,6 +121,11 @@ void LLPanelDirPlaces::performQuery()
|
||||
static LLUICachedControl<bool> inc_pg("ShowPGSims", true);
|
||||
static LLUICachedControl<bool> inc_mature("ShowMatureSims", false);
|
||||
static LLUICachedControl<bool> inc_adult("ShowAdultSims", false);
|
||||
if (!(inc_pg || inc_mature || inc_adult))
|
||||
{
|
||||
LLNotificationsUtil::add("NoContentToSearch");
|
||||
return;
|
||||
}
|
||||
|
||||
if (inc_pg)
|
||||
{
|
||||
@@ -137,12 +142,6 @@ void LLPanelDirPlaces::performQuery()
|
||||
flags |= DFQ_INC_ADULT;
|
||||
}
|
||||
|
||||
if (0x0 == flags)
|
||||
{
|
||||
LLNotificationsUtil::add("NoContentToSearch");
|
||||
return;
|
||||
}
|
||||
|
||||
queryCore(query_string, category, flags);
|
||||
}
|
||||
|
||||
|
||||
@@ -8352,11 +8352,8 @@ class LLAvatarEnableResetSkeleton : public view_listener_t
|
||||
{
|
||||
bool handleEvent(const LLSD& userdata)
|
||||
{
|
||||
if (find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject())) // <FS:Beq/> set but unused.
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
LLViewerObject* obj = LLSelectMgr::getInstance()->getSelection()->getPrimaryObject();
|
||||
return obj && obj->getAvatar();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -11237,6 +11234,12 @@ LLVOAvatar* find_avatar_from_object(LLViewerObject* object)
|
||||
}
|
||||
else if( !object->isAvatar() )
|
||||
{
|
||||
// Check for animesh objects (animated objects with a control avatar)
|
||||
LLVOAvatar* avatar = object->getAvatar();
|
||||
if (avatar)
|
||||
{
|
||||
return avatar;
|
||||
}
|
||||
object = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -750,6 +750,16 @@ bool LLViewerParcelMgr::allowAgentVoice() const
|
||||
return allowAgentVoice(gAgent.getRegion(), mAgentParcel);
|
||||
}
|
||||
|
||||
bool LLViewerParcelMgr::isVoiceRestricted() const
|
||||
{
|
||||
return mAgentParcel && !mAgentParcel->getParcelFlagUseEstateVoiceChannel();
|
||||
}
|
||||
|
||||
bool LLViewerParcelMgr::allowVoiceModeration() const
|
||||
{
|
||||
return isVoiceRestricted() && isParcelOwnedByAgent(mAgentParcel, GP_SESSION_MODERATOR);
|
||||
}
|
||||
|
||||
bool LLViewerParcelMgr::allowAgentVoice(const LLViewerRegion* region, const LLParcel* parcel) const
|
||||
{
|
||||
return region && region->isVoiceEnabled()
|
||||
|
||||
@@ -185,6 +185,12 @@ public:
|
||||
bool allowAgentVoice() const;
|
||||
bool allowAgentVoice(const LLViewerRegion* region, const LLParcel* parcel) const;
|
||||
|
||||
// Returns true if this parcel is using private voice channel
|
||||
bool isVoiceRestricted() const;
|
||||
|
||||
// Can this agent moderate Nearby voice chat on this parcel?
|
||||
bool allowVoiceModeration() const;
|
||||
|
||||
// Can this agent start flying on this parcel?
|
||||
// Used for parcel property icons in nav bar.
|
||||
bool allowAgentFly(const LLViewerRegion* region, const LLParcel* parcel) const;
|
||||
|
||||
@@ -3552,6 +3552,7 @@ void LLViewerRegionImpl::buildCapabilityNames(LLSD& capabilityNames)
|
||||
capabilityNames.append("SetDisplayName");
|
||||
capabilityNames.append("SimConsoleAsync");
|
||||
capabilityNames.append("SimulatorFeatures");
|
||||
capabilityNames.append("SpatialVoiceModerationRequest");
|
||||
capabilityNames.append("StartGroupProposal");
|
||||
capabilityNames.append("TerrainNavMeshProperties");
|
||||
capabilityNames.append("TextureStats");
|
||||
@@ -4079,6 +4080,16 @@ std::string LLViewerRegion::getSimHostName()
|
||||
return std::string("...");
|
||||
}
|
||||
|
||||
|
||||
bool LLViewerRegion::isRegionWebRTCEnabled()
|
||||
{
|
||||
if (mSimulatorFeaturesReceived && mSimulatorFeatures.has("VoiceServerType"))
|
||||
{
|
||||
return mSimulatorFeatures["VoiceServerType"].asString() == "webrtc";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void LLViewerRegion::applyCacheMiscExtras(LLViewerObject* obj)
|
||||
{
|
||||
LL_PROFILE_ZONE_SCOPED_CATEGORY_DISPLAY;
|
||||
|
||||
@@ -457,6 +457,8 @@ public:
|
||||
|
||||
std::string getSimHostName();
|
||||
|
||||
bool isRegionWebRTCEnabled();
|
||||
|
||||
static bool isNewObjectCreationThrottleDisabled() {return sNewObjectCreationThrottle < 0;}
|
||||
|
||||
// rebuild reflection probe list
|
||||
|
||||
@@ -269,6 +269,20 @@ LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> > HUDS_FRAME_PCT("huds_
|
||||
LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> > UI_FRAME_PCT("ui_frame_pct");
|
||||
LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> > SWAP_FRAME_PCT("swap_frame_pct");
|
||||
LLTrace::SampleStatHandle<LLUnit<F32, LLUnits::Percent> > IDLE_FRAME_PCT("idle_frame_pct");
|
||||
|
||||
|
||||
|
||||
LLTrace::SampleStatHandle<U32> WEBRTC_PACKETS_IN_LOST("webrtc_packets_in_lost", "Lost incoming packets"),
|
||||
WEBRTC_PACKETS_IN_RECEIVED("webrtc_packets_in_recv", "Incoming packets received"),
|
||||
WEBRTC_PACKETS_OUT_SENT("webrtc_packets_out_sent", "Outgoing packets sent"),
|
||||
WEBRTC_PACKETS_OUT_LOST("webrtc_packets_out_lost", "Lost outgoing packets");
|
||||
|
||||
LLTrace::SampleStatHandle<F32> WEBRTC_JITTER_OUT("webrtc_jitter_out", "Timing variation of outgoing audio"),
|
||||
WEBRTC_JITTER_IN("webrtc_jitter_in", "Timing variation of incoming audio"),
|
||||
WEBRTC_LATENCY("webrtc_latency", "Round-trip audio delay"),
|
||||
WEBRTC_UPLOAD_BANDWIDTH("webrtc_upload_bandwidth", "Estimated upload bandwidth"),
|
||||
WEBRTC_JITTER_BUFFER("webrtc_jitter_buffer", "Average delay added to smooth incoming audio");
|
||||
|
||||
}
|
||||
|
||||
LLViewerStats::LLViewerStats()
|
||||
|
||||
@@ -237,6 +237,9 @@ extern LLTrace::EventStatHandle<LLUnit<F32, LLUnits::Percent> > OBJECT_CACHE_HIT
|
||||
extern LLTrace::SampleStatHandle<F64> NOTRMALIZED_FRAMETIME_JITTER_SESSION;
|
||||
extern LLTrace::SampleStatHandle<F64> NORMALIZED_FRAMTIME_JITTER_PERIOD;
|
||||
|
||||
extern LLTrace::SampleStatHandle<U32> WEBRTC_PACKETS_IN_LOST, WEBRTC_PACKETS_IN_RECEIVED, WEBRTC_PACKETS_OUT_SENT, WEBRTC_PACKETS_OUT_LOST;
|
||||
extern LLTrace::SampleStatHandle<F32> WEBRTC_JITTER_OUT, WEBRTC_JITTER_IN, WEBRTC_LATENCY, WEBRTC_UPLOAD_BANDWIDTH, WEBRTC_JITTER_BUFFER;
|
||||
|
||||
}
|
||||
|
||||
class LLViewerStats : public LLSingleton<LLViewerStats>
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include "llagent.h"
|
||||
#include "lltrans.h"
|
||||
#include "lluiusage.h"
|
||||
#include "llnearbyvoicemoderation.h"
|
||||
|
||||
const F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f;
|
||||
|
||||
@@ -733,6 +734,13 @@ bool LLVoiceClient::getPTTIsToggle()
|
||||
|
||||
void LLVoiceClient::inputUserControlState(bool down)
|
||||
{
|
||||
if (down && !getUserPTTState())
|
||||
{
|
||||
// Nearby chat is muted by moderator, don't toggle PTT
|
||||
if (LLNearbyVoiceModeration::getInstance()->showNotificationIfNeeded())
|
||||
return;
|
||||
}
|
||||
|
||||
if(mPTTIsToggle)
|
||||
{
|
||||
if(down) // toggle open-mic state on 'down'
|
||||
|
||||
@@ -61,10 +61,12 @@
|
||||
#include "llrand.h"
|
||||
#include "llviewerwindow.h"
|
||||
#include "llviewercamera.h"
|
||||
#include "llviewerstats.h"
|
||||
#include "llversioninfo.h"
|
||||
|
||||
#include "llviewernetwork.h"
|
||||
#include "llnotificationsutil.h"
|
||||
#include "llnearbyvoicemoderation.h"
|
||||
|
||||
#include "llcorehttputil.h"
|
||||
#include "lleventfilter.h"
|
||||
@@ -80,6 +82,8 @@
|
||||
|
||||
const std::string WEBRTC_VOICE_SERVER_TYPE = "webrtc";
|
||||
|
||||
const F32 STATS_TIMER_DELAY = 2.0;
|
||||
|
||||
// <FS:minerjr> [FIRE-36022] - Removing my USB headset crashes entire viewer
|
||||
using namespace std::chrono_literals; // Needed for shared timed mutex to use time
|
||||
// </FS:minerjr> [FIRE-36022]
|
||||
@@ -2985,6 +2989,7 @@ bool LLVoiceWebRTCConnection::connectionStateMachine()
|
||||
}
|
||||
mWebRTCAudioInterface->setReceiveVolume(mSpeakerVolume);
|
||||
LLWebRTCVoiceClient::getInstance()->OnConnectionEstablished(mChannelID, mRegionID);
|
||||
resetConnectionStats();
|
||||
setVoiceConnectionState(VOICE_STATE_WAIT_FOR_DATA_CHANNEL);
|
||||
break;
|
||||
}
|
||||
@@ -3038,6 +3043,13 @@ bool LLVoiceWebRTCConnection::connectionStateMachine()
|
||||
sendJoin();
|
||||
}
|
||||
}
|
||||
|
||||
static LLTimer stats_timer;
|
||||
if (stats_timer.getElapsedTimeF32() > STATS_TIMER_DELAY)
|
||||
{
|
||||
mWebRTCPeerConnectionInterface->gatherConnectionStats();
|
||||
stats_timer.reset();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3254,12 +3266,54 @@ void LLVoiceWebRTCConnection::OnDataReceivedImpl(const std::string &data, bool b
|
||||
|
||||
if (participant_obj.contains("m") && participant_obj["m"].is_bool())
|
||||
{
|
||||
participant->mIsModeratorMuted = participant_obj["m"].as_bool();
|
||||
bool is_moderator_muted = participant_obj["m"].as_bool();
|
||||
if (isSpatial())
|
||||
{
|
||||
// ignore muted flags from non-primary server
|
||||
if (mPrimary || primary)
|
||||
{
|
||||
participant->mIsModeratorMuted = is_moderator_muted;
|
||||
if (gAgentID == agent_id)
|
||||
{
|
||||
LLNearbyVoiceModeration::getInstance()->setMutedInfo(mChannelID, is_moderator_muted);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
participant->mIsModeratorMuted = is_moderator_muted;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (isSpatial() && (mPrimary || primary))
|
||||
{
|
||||
// mute info message can be received before join message, so try to mute again later
|
||||
if (participant_obj.contains("m") && participant_obj["m"].is_bool())
|
||||
{
|
||||
bool is_moderator_muted = participant_obj["m"].as_bool();
|
||||
std::string channel_id = mChannelID;
|
||||
F32 delay { 1.5f };
|
||||
doAfterInterval(
|
||||
[channel_id, agent_id, is_moderator_muted]()
|
||||
{
|
||||
LLWebRTCVoiceClient::participantStatePtr_t participant =
|
||||
LLWebRTCVoiceClient::getInstance()->findParticipantByID(channel_id, agent_id);
|
||||
if (participant)
|
||||
{
|
||||
participant->mIsModeratorMuted = is_moderator_muted;
|
||||
if (gAgentID == agent_id)
|
||||
{
|
||||
LLNearbyVoiceModeration::getInstance()->setMutedInfo(channel_id, is_moderator_muted);
|
||||
}
|
||||
}
|
||||
}, delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// tell the simulator to set the mute and volume data for this
|
||||
// participant, if there are any updates.
|
||||
boost::json::object root;
|
||||
@@ -3331,6 +3385,112 @@ void LLVoiceWebRTCConnection::sendJoin()
|
||||
mWebRTCDataInterface->sendData(json_data, false);
|
||||
}
|
||||
|
||||
void LLVoiceWebRTCConnection::OnStatsDelivered(const llwebrtc::LLWebRTCStatsMap& stats_data)
|
||||
{
|
||||
LL::WorkQueue::postMaybe(mMainQueue, [=, this]
|
||||
{
|
||||
if (mShutDown)
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (const auto& [stats_id, attributes] : stats_data)
|
||||
{
|
||||
if (attributes.contains("currentRoundTripTime"))
|
||||
{
|
||||
F32 rtt_seconds = 0.0f;
|
||||
LLStringUtil::convertToF32(attributes.at("currentRoundTripTime"), rtt_seconds);
|
||||
sample(LLStatViewer::WEBRTC_LATENCY, rtt_seconds * 1000.0f);
|
||||
}
|
||||
if (attributes.contains("availableOutgoingBitrate"))
|
||||
{
|
||||
F32 bitrate_bps = 0.0f;
|
||||
LLStringUtil::convertToF32(attributes.at("availableOutgoingBitrate"), bitrate_bps);
|
||||
sample(LLStatViewer::WEBRTC_UPLOAD_BANDWIDTH, bitrate_bps / 1000.0f);
|
||||
}
|
||||
|
||||
// Stat type detection below is heuristic-based.
|
||||
// It's relied on specific fields to distinguish outbound-rtp, remote-inbound-rtp, and inbound-rtp.
|
||||
// This approach works with current WebRTC stats but may need updating later.
|
||||
|
||||
// Outbound RTP
|
||||
if (attributes.contains("mediaSourceId"))
|
||||
{
|
||||
U32 out_packets_sent = 0;
|
||||
LLStringUtil::convertToU32(attributes.at("packetsSent"), out_packets_sent);
|
||||
sample(LLStatViewer::WEBRTC_PACKETS_OUT_SENT, out_packets_sent);
|
||||
}
|
||||
// Remote-Inbound RTP
|
||||
else if (attributes.contains("localId"))
|
||||
{
|
||||
if (attributes.contains("packetsLost"))
|
||||
{
|
||||
U32 out_packets_lost = 0;
|
||||
LLStringUtil::convertToU32(attributes.at("packetsLost"), out_packets_lost);
|
||||
sample(LLStatViewer::WEBRTC_PACKETS_OUT_LOST, out_packets_lost);
|
||||
}
|
||||
if (attributes.contains("jitter"))
|
||||
{
|
||||
F32 jitter_seconds = 0.0f;
|
||||
LLStringUtil::convertToF32(attributes.at("jitter"), jitter_seconds);
|
||||
sample(LLStatViewer::WEBRTC_JITTER_OUT, jitter_seconds * 1000.0f);
|
||||
}
|
||||
}
|
||||
// Inbound RTP
|
||||
else if (attributes.contains("jitterBufferDelay"))
|
||||
{
|
||||
if (attributes.contains("packetsLost"))
|
||||
{
|
||||
U32 in_packets_lost = 0;
|
||||
LLStringUtil::convertToU32(attributes.at("packetsLost"), in_packets_lost);
|
||||
sample(LLStatViewer::WEBRTC_PACKETS_IN_LOST, in_packets_lost);
|
||||
}
|
||||
if (attributes.contains("packetsReceived"))
|
||||
{
|
||||
U32 in_packets_recv = 0;
|
||||
LLStringUtil::convertToU32(attributes.at("packetsReceived"), in_packets_recv);
|
||||
sample(LLStatViewer::WEBRTC_PACKETS_IN_RECEIVED, in_packets_recv);
|
||||
}
|
||||
if (attributes.contains("jitter"))
|
||||
{
|
||||
F32 jitter_seconds = 0.0f;
|
||||
LLStringUtil::convertToF32(attributes.at("jitter"), jitter_seconds);
|
||||
sample(LLStatViewer::WEBRTC_JITTER_IN, jitter_seconds * 1000.0f);
|
||||
}
|
||||
if (attributes.contains("jitterBufferDelay") && attributes.contains("jitterBufferEmittedCount"))
|
||||
{
|
||||
F32 total_delay_seconds = 0.0f;
|
||||
F32 emitted_count_f = 0.0f;
|
||||
|
||||
// total delay in seconds
|
||||
LLStringUtil::convertToF32(attributes.at("jitterBufferDelay"), total_delay_seconds);
|
||||
|
||||
// number of packets played out
|
||||
LLStringUtil::convertToF32(attributes.at("jitterBufferEmittedCount"), emitted_count_f);
|
||||
if (emitted_count_f > 0.0f)
|
||||
{
|
||||
F32 avg_delay_seconds = total_delay_seconds / emitted_count_f;
|
||||
F32 avg_delay_ms = avg_delay_seconds * 1000.0f;
|
||||
sample(LLStatViewer::WEBRTC_JITTER_BUFFER, avg_delay_seconds * 1000.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void LLVoiceWebRTCConnection::resetConnectionStats()
|
||||
{
|
||||
sample(LLStatViewer::WEBRTC_JITTER_BUFFER, 0);
|
||||
sample(LLStatViewer::WEBRTC_JITTER_IN, 0);
|
||||
sample(LLStatViewer::WEBRTC_JITTER_OUT, 0);
|
||||
sample(LLStatViewer::WEBRTC_LATENCY, 0);
|
||||
sample(LLStatViewer::WEBRTC_PACKETS_IN_LOST, 0);
|
||||
sample(LLStatViewer::WEBRTC_PACKETS_IN_RECEIVED, 0);
|
||||
sample(LLStatViewer::WEBRTC_PACKETS_OUT_SENT, 0);
|
||||
sample(LLStatViewer::WEBRTC_PACKETS_OUT_LOST, 0);
|
||||
sample(LLStatViewer::WEBRTC_UPLOAD_BANDWIDTH, 0);
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
// WebRTC Spatial Connection
|
||||
|
||||
|
||||
@@ -540,6 +540,8 @@ private:
|
||||
static bool sShuttingDown;
|
||||
|
||||
LLEventMailDrop mWebRTCPump;
|
||||
|
||||
LLSD mLastWebRTCStats;
|
||||
};
|
||||
|
||||
|
||||
@@ -603,6 +605,8 @@ class LLVoiceWebRTCConnection :
|
||||
//@{
|
||||
void OnDataReceived(const std::string &data, bool binary) override;
|
||||
void OnDataChannelReady(llwebrtc::LLWebRTCDataInterface *data_interface) override;
|
||||
|
||||
void OnStatsDelivered(const llwebrtc::LLWebRTCStatsMap& stats_data) override;
|
||||
//@}
|
||||
|
||||
void OnDataReceivedImpl(const std::string &data, bool binary);
|
||||
@@ -638,6 +642,8 @@ class LLVoiceWebRTCConnection :
|
||||
|
||||
void OnVoiceConnectionRequestSuccess(const LLSD &body);
|
||||
|
||||
void resetConnectionStats();
|
||||
|
||||
protected:
|
||||
typedef enum e_voice_connection_state
|
||||
{
|
||||
|
||||
@@ -440,6 +440,64 @@
|
||||
</stat_view>
|
||||
</stat_view>
|
||||
</stat_view>
|
||||
<stat_view name="voice"
|
||||
label="Voice"
|
||||
setting="OpenDebugStatVoice">
|
||||
<stat_bar
|
||||
name="webrtc_latency"
|
||||
label="Latency"
|
||||
stat="webrtc_latency"
|
||||
unit_label="ms"
|
||||
decimal_digits="1"/>
|
||||
<stat_bar
|
||||
name="webrtc_upload_bandwidth"
|
||||
label="Upload bandwidth"
|
||||
stat="webrtc_upload_bandwidth"
|
||||
unit_label="kbps"
|
||||
decimal_digits="0"/>
|
||||
<stat_view name="incoming_audio"
|
||||
label="Incoming audio"
|
||||
setting="OpenDebugStatVoiceIncoming">
|
||||
<stat_bar
|
||||
name="incoming_packet_recv"
|
||||
label="Packets received"
|
||||
stat="webrtc_packets_in_recv"/>
|
||||
<stat_bar
|
||||
name="packets_in_lost"
|
||||
label="Packets lost"
|
||||
stat="webrtc_packets_in_lost"/>
|
||||
<stat_bar
|
||||
name="jitter_in"
|
||||
label="Jitter"
|
||||
stat="webrtc_jitter_in"
|
||||
unit_label="ms"
|
||||
decimal_digits="1"/>
|
||||
<stat_bar
|
||||
name="jitter_buffer"
|
||||
label="Jitter buffer"
|
||||
stat="webrtc_jitter_buffer"
|
||||
unit_label="ms"
|
||||
decimal_digits="1"/>
|
||||
</stat_view>
|
||||
<stat_view name="outgoing_audio"
|
||||
label="Outgoing audio"
|
||||
setting="OpenDebugStatVoiceOutgoing">
|
||||
<stat_bar
|
||||
name="packets_out_sent"
|
||||
label="Packets sent"
|
||||
stat="webrtc_packets_out_sent"/>
|
||||
<stat_bar
|
||||
name="packets_out_lost"
|
||||
label="Packets lost"
|
||||
stat="webrtc_packets_out_lost"/>
|
||||
<stat_bar
|
||||
name="jitter_out"
|
||||
label="Jitter"
|
||||
stat="webrtc_jitter_out"
|
||||
unit_label="ms"
|
||||
decimal_digits="1"/>
|
||||
</stat_view>
|
||||
</stat_view>
|
||||
</container_view>
|
||||
</scroll_container>
|
||||
</floater>
|
||||
|
||||
@@ -176,57 +176,60 @@
|
||||
<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">
|
||||
<on_visible function="Avatar.VisibleItem" parameter="can_allow_text_chat" />
|
||||
</menu_item_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>
|
||||
|
||||
@@ -9871,6 +9871,23 @@ Your voice has been muted by a moderator.
|
||||
yestext="OK"/>
|
||||
</notification>
|
||||
|
||||
<notification
|
||||
icon="notifytip.tga"
|
||||
name="NearbyVoiceMutedByModerator"
|
||||
type="notifytip">
|
||||
The moderator has muted your voice chat.
|
||||
People in this location will not hear you if you speak.
|
||||
<tag>voice</tag>
|
||||
</notification>
|
||||
|
||||
<notification
|
||||
icon="notifytip.tga"
|
||||
name="NearbyVoiceUnmutedByModerator"
|
||||
type="notifytip">
|
||||
Your voice chat can now be heard by people in this location.
|
||||
<tag>voice</tag>
|
||||
</notification>
|
||||
|
||||
<notification
|
||||
icon="alertmodal.tga"
|
||||
name="FailedToGetBenefits"
|
||||
|
||||
Reference in New Issue
Block a user