diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 1c6b356f6a..470ffff2c3 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -857,6 +857,7 @@ set(viewer_SOURCE_FILES llxmlrpctransaction.cpp noise.cpp particleeditor.cpp + permissionstracker.cpp piemenu.cpp pieseparator.cpp pieslice.cpp @@ -1630,6 +1631,7 @@ set(viewer_HEADER_FILES macmain.h noise.h particleeditor.h + permissionstracker.h piemenu.h pieseparator.h pieslice.h diff --git a/indra/newview/llfollowcam.cpp b/indra/newview/llfollowcam.cpp index c2ea3b07c1..4a65e8c2dc 100644 --- a/indra/newview/llfollowcam.cpp +++ b/indra/newview/llfollowcam.cpp @@ -29,6 +29,8 @@ #include "llfollowcam.h" #include "llagent.h" +#include "permissionstracker.h" + //------------------------------------------------------- // constants //------------------------------------------------------- @@ -829,11 +831,13 @@ void LLFollowCamMgr::setCameraActive( const LLUUID& source, bool active ) if (found_it != mParamStack.end()) { mParamStack.erase(found_it); + PermissionsTracker::instance().removePermissionsEntry(source, PermissionsTracker::PERM_FOLLOWCAM); } // put on top of stack if(active) { mParamStack.push_back(params); + PermissionsTracker::instance().addPermissionsEntry(source, PermissionsTracker::PERM_FOLLOWCAM); } } @@ -843,6 +847,8 @@ void LLFollowCamMgr::removeFollowCamParams(const LLUUID& source) LLFollowCamParams* params = getParamsForID(source); mParamMap.erase(source); delete params; + + PermissionsTracker::instance().removePermissionsEntry(source, PermissionsTracker::PERM_FOLLOWCAM); } bool LLFollowCamMgr::isScriptedCameraSource(const LLUUID& source) diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 916712ecb8..f022779a09 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -166,6 +166,7 @@ #include "lltexturecache.h" #include "llvovolume.h" #include "particleeditor.h" +#include "permissionstracker.h" using namespace LLAvatarAppearanceDefines; @@ -5424,6 +5425,9 @@ void handle_reset_camera_angles() // Camera focus and offset with CTRL/SHIFT + Scroll wheel gSavedSettings.getControl("FocusOffsetRearView")->resetToDefault(); gSavedSettings.getControl("CameraOffsetRearView")->resetToDefault(); + + // warn the user if there is a scripted followcam active that might stop a camera reset + PermissionsTracker::instance().warnFollowcam(); } // diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index e4eea0a510..d7b64bc5c1 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -150,6 +150,7 @@ #include "llfloaterbump.h" #include "llfloaterreg.h" #include "llfriendcard.h" +#include "permissionstracker.h" // Permissions Tracker #include "tea.h" // #include "NACLantispam.h" #include "chatbar_as_cmdline.h" @@ -4733,6 +4734,8 @@ void process_object_properties(LLMessageSystem *msg, void**user_data) { explorer->requestNameCallback(msg); } + + PermissionsTracker::instance().objectPropertiesCallback(msg); } // area search diff --git a/indra/newview/permissionstracker.cpp b/indra/newview/permissionstracker.cpp new file mode 100644 index 0000000000..db01feee27 --- /dev/null +++ b/indra/newview/permissionstracker.cpp @@ -0,0 +1,274 @@ +/** + * @file permissionstracker.cpp + * @brief Permissions Tracker implementation - Initially it's only tracking + * camera control permissions, to warn users about attachments or seats that + * took camera control permissions and might interfere with resetting the + * camera view. + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2022, Zi Ree @ Second Life + * + * 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 "llagentdata.h" // for gAgentID anf gAgentSessionID +#include "llnotificationsutil.h" +#include "llslurl.h" +#include "lltrans.h" +#include "llviewermenu.h" // for handle_object_edit() +#include "llviewerregion.h" +#include "llviewerobject.h" +#include "llviewerobjectlist.h" // for gObjectList +#include "llvoavatarself.h" // for gAgentAvatarp +#include "llworld.h" + +#include "permissionstracker.h" + +#define PERMISSION_ENTRY_EXPIRY_TIME 3600.0 + +PermissionsTracker::PermissionsTracker() +: LLSingleton() +{ +} + +PermissionsTracker::~PermissionsTracker() +{ +} + +void PermissionsTracker::addPermissionsEntry(const LLUUID& source_id, PermissionsTracker::PERM_TYPE permission_type) +{ + // find out if this is a new entry in the list + if (mPermissionsList.find(source_id) == mPermissionsList.end()) + { + LL_DEBUGS("PermissionsTracker") << "Creating list entry for source " << source_id << LL_ENDL; + mPermissionsList[source_id].objectName = LLTrans::getString("LoadingData"); + + // find out if the object is still in reach + LLViewerObject* vo = gObjectList.findObject(source_id); + if (!vo) + { + mPermissionsList[source_id].objectName = LLTrans::getString("ObjectOutOfRange"); + } + else + { + mPermissionsList[source_id].attachmentID = vo->getAttachmentItemID(); + LL_DEBUGS("PermissionsTracker") << "Requesting ObjectProperties for source " << source_id << LL_ENDL; + + // remember which object names we already requested + mRequestedIDs.push_back(source_id); + + // send a request out to get this object's details + LLMessageSystem* msg = gMessageSystem; + + msg->newMessageFast(_PREHASH_ObjectSelect); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgentID); + msg->addUUIDFast(_PREHASH_SessionID, gAgentSessionID); + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addU32Fast(_PREHASH_ObjectLocalID, vo->getLocalID()); + msg->sendReliable(gAgentAvatarp->getRegion()->getHost()); + + msg->newMessageFast(_PREHASH_ObjectDeselect); + msg->nextBlockFast(_PREHASH_AgentData); + msg->addUUIDFast(_PREHASH_AgentID, gAgentID); + msg->addUUIDFast(_PREHASH_SessionID, gAgentSessionID); + msg->nextBlockFast(_PREHASH_ObjectData); + msg->addU32Fast(_PREHASH_ObjectLocalID, vo->getLocalID()); + msg->sendReliable(gAgentAvatarp->getRegion()->getHost()); + } + } + + LL_DEBUGS("PermissionsTracker") << "Adding permission type " << permission_type << " to source " << source_id << LL_ENDL; + mPermissionsList[source_id].type |= permission_type; + mPermissionsList[source_id].time = LLDate(LLTimer::getTotalSeconds()); + + purgePermissionsEntries(); +} + +void PermissionsTracker::removePermissionsEntry(const LLUUID& source_id, PermissionsTracker::PERM_TYPE permission_type) +{ + if (mPermissionsList.find(source_id) == mPermissionsList.end()) + { + LL_DEBUGS("PermissionsTracker") << "Could not find list entry for source " << source_id << LL_ENDL; + return; + } + + LL_DEBUGS("PermissionsTracker") << "Removing permissions type " << permission_type << " from source " << source_id << LL_ENDL; + mPermissionsList[source_id].type &= ~permission_type; + mPermissionsList[source_id].time = LLDate(LLTimer::getTotalSeconds()); + + purgePermissionsEntries(); +} + +void PermissionsTracker::purgePermissionsEntries() +{ + F64 expiry_time = LLDate(LLTimer::getTotalSeconds()).secondsSinceEpoch() - PERMISSION_ENTRY_EXPIRY_TIME; + + auto it = mPermissionsList.begin(); + while (it != mPermissionsList.end()) + { + if (it->second.type == PERM_TYPE::PERM_NONE && it->second.time.secondsSinceEpoch() < expiry_time) + { + LL_DEBUGS("PermissionsTracker") << "Erasing list entry for source " << it->first << LL_ENDL; + it = mPermissionsList.erase(it); + } + else + { + ++it; + } + } +} + +void PermissionsTracker::warnFollowcam() +{ + std::string followcamList; + for (auto entry : mPermissionsList) + { + if (entry.second.type & PermissionsTracker::PERM_FOLLOWCAM) + { + if (entry.second.attachmentID.notNull()) + { + std::string attachment_point = "???"; + gAgentAvatarp->getAttachedPointName(entry.second.attachmentID, attachment_point); + + LLSD args; + args["ATTACHMENT_POINT"] = attachment_point; + + std::string verb = "select?name=" + LLURI::escape(entry.second.objectName); + followcamList += LLSLURL("inventory", entry.second.attachmentID, verb.c_str()).getSLURLString() + " " + + LLTrans::getString("WornOnAttachmentPoint", args) + "\n"; + } + else + { + followcamList += LLSLURL("objectim", entry.first, "").getSLURLString() + + "?name=" + LLURI::escape(entry.second.objectName) + + "&owner=" + entry.second.ownerID.asString(); + + LLSD args; + std::string slurl = args["slurl"].asString(); + if (slurl.empty()) + { + LLViewerRegion* region = LLWorld::instance().getRegionFromPosAgent(gAgentAvatarp->getPositionAgent()); + if(region) + { + LLSLURL region_slurl(region->getName(), gAgentAvatarp->getPositionAgent()); + slurl = region_slurl.getLocationString(); + } + } + + followcamList += "&slurl=" + LLURI::escape(slurl) + "\n"; + } + } + } + + if (followcamList.empty()) + { + return; + } + + LLSD args; + args["SOURCES"] = followcamList; + LLNotificationsUtil::add("WarnScriptedCamera", args); +} + +void PermissionsTracker::objectPropertiesCallback(LLMessageSystem* msg) +{ + LL_DEBUGS("PermissionsTracker") << "Received ObjectProperties message." << LL_ENDL; + + // if we weren't looking for any IDs, ignore this callback + if (mRequestedIDs.empty()) + { + LL_DEBUGS("PermissionsTracker") << "No objects in request list." << LL_ENDL; + return; + } + + // we might have received more than one answer in one block + S32 num = msg->getNumberOfBlocksFast(_PREHASH_ObjectData); + for (S32 index = 0; index < num; ++index) + { + LLUUID source_id; + msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_ObjectID, source_id, index); + + auto iter = std::find(mRequestedIDs.begin(), mRequestedIDs.end(), source_id); + // if this is one of the objects we were looking for, process the data + if (iter != mRequestedIDs.end()) + { + // get the name of the object + std::string object_name; + msg->getStringFast(_PREHASH_ObjectData, _PREHASH_Name, object_name, index); + + LLUUID object_owner; + msg->getUUIDFast(_PREHASH_ObjectData, _PREHASH_OwnerID, object_owner, index); + + LL_DEBUGS("PermissionsTracker") << "Received object name " << object_name + << " and owner " << object_owner.asString() << " for source " << source_id << LL_ENDL; + + // remove the object from the lookup list and add it to the known names list + mRequestedIDs.erase(iter); + + mPermissionsList[source_id].objectName = object_name; + mPermissionsList[source_id].ownerID = object_owner; + + LLAvatarName avatar_name; + if (LLAvatarNameCache::get(object_owner, &avatar_name)) + { + LL_DEBUGS("PermissionsTracker") << "Found cached entry for owner " << object_owner.asString() + << ": " << avatar_name.getCompleteName() << LL_ENDL; + mPermissionsList[source_id].ownerName = avatar_name.getCompleteName(); + } + else + { + if (mAvatarNameCacheConnections.find(object_owner) != mAvatarNameCacheConnections.end()) + { + boost::signals2::connection cb_connection = LLAvatarNameCache::get(object_owner, boost::bind(&PermissionsTracker::avatarNameCallback, this, _1, _2)); + mAvatarNameCacheConnections.insert(std::make_pair(object_owner, cb_connection)); + + LL_DEBUGS("PermissionsTracker") << "Requesting avatar name for owner " << object_owner.asString() << LL_ENDL; + } + } + } + } +} + +void PermissionsTracker::avatarNameCallback(const LLUUID& avatar_id, const LLAvatarName& avatar_name) +{ + LL_DEBUGS("PermissionsTracker") << "Received avatar name " << avatar_name.getCompleteName() << LL_ENDL; + + auto iter = mAvatarNameCacheConnections.find(avatar_id); + if (iter != mAvatarNameCacheConnections.end()) + { + if (iter->second.connected()) + { + iter->second.disconnect(); + } + mAvatarNameCacheConnections.erase(iter); + } + + for (auto entry : mPermissionsList) + { + if (entry.second.ownerID == avatar_id) + { + LL_DEBUGS("PermissionsTracker") << "Saved avatar name " << avatar_name.getCompleteName()<< " for source " << entry.first.asString() << LL_ENDL; + entry.second.ownerName = avatar_name.getCompleteName(); + } + } +} diff --git a/indra/newview/permissionstracker.h b/indra/newview/permissionstracker.h new file mode 100644 index 0000000000..0072d564e6 --- /dev/null +++ b/indra/newview/permissionstracker.h @@ -0,0 +1,86 @@ +/** + * @file permissionstracker.h + * @brief Permissions Tracker declaration + * + * $LicenseInfo:firstyear=2001&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2010, Linden Research, Inc. + * Copyright (C) 2022, Zi Ree @ Second Life + * + * 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$ + */ + +#ifndef PERMISSIONSTRACKER_H +#define PERMISSIONSTRACKER_H + +#include "llfloater.h" +#include "llsingleton.h" + +// -------------------------------------------------------------------------- +// PermissionsTracker: holds a list of requested script permissions to be +// referred to e.g. when trying to reset the camera view and a script is +// holding a follow cam which micht prevent it from working. +// -------------------------------------------------------------------------- + +class PermissionsTracker +: public LLSingleton +{ + LLSINGLETON(PermissionsTracker); + ~PermissionsTracker(); + + public: + enum PERM_TYPE + { + PERM_NONE = 0, + PERM_FOLLOWCAM = 1, + }; + + struct PermissionsEntry + { + LLUUID ownerID; // agent who owns the requesting object + LLUUID attachmentID; // attachment ID in inventory for attached objects + std::string ownerName; + std::string objectName; + LLDate time; // time when the permission was granted + U32 type; // what kind of permissions were granted + }; + + std::map mPermissionsList; // UUID of the requesting object + + void addPermissionsEntry( + const LLUUID& source_id, + PermissionsTracker::PERM_TYPE permission_type); // called in llviewermessage.cpp + + void removePermissionsEntry( + const LLUUID& source_id, + PermissionsTracker::PERM_TYPE permission_type); // called in llviewermessage.cpp + + void purgePermissionsEntries(); + + void objectPropertiesCallback(LLMessageSystem* msg); + void avatarNameCallback(const LLUUID& avatar_id, const LLAvatarName& av_name); + + void warnFollowcam(); // Warn the user if a script is holding followcam parameters + + std::vector mRequestedIDs; // list of object IDs we requested named for + + typedef std::map avatar_name_cache_connection_map_t; + avatar_name_cache_connection_map_t mAvatarNameCacheConnections; +}; + +#endif // PERMISSIONSTRACKER_H diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 77236200aa..230a5694ca 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -13790,4 +13790,14 @@ Reset the URL to default? notext="Remind me next time" yestext="Reset"/> + + +Camera reset might be inhibited by the following objects: + +[SOURCES] + +