From 0fec01385d3c09490457aa1069f2985d5ec793f9 Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Tue, 6 Apr 2010 22:02:49 -0700 Subject: DEV-48542 - CID-444/CID-445: missing break in switch, llpanellogin.cpp Added missing breaks. CR: Josh --- indra/newview/llpanellogin.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index b620a005d2..548f97b9f1 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -785,11 +785,20 @@ void LLPanelLogin::getLocation(LLSLURL& slurl) switch(combo->getCurrentIndex()) { case 0: + { slurl = LLSLURL(LLSLURL::SIM_LOCATION_HOME); + break; + } case 1: + { slurl = LLSLURL(LLSLURL::SIM_LOCATION_LAST); + break; + } default: + { slurl = LLSLURL(combo->getValue().asString()); + break; + } } } -- cgit v1.2.3 From 658ccc3e85487f9f24ff3b5926e60d6cce7f42e0 Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Wed, 7 Apr 2010 11:08:04 -0700 Subject: Re-insert backed out SLE checkin so we can fix it --- indra/llcommon/lluri.cpp | 3 +- indra/llcommon/lluuid.cpp | 162 +- indra/llmessage/llcurl.cpp | 29 +- indra/llmessage/llcurl.h | 12 +- indra/llmessage/llhttpclient.cpp | 16 +- indra/llmessage/llhttpclient.h | 11 +- indra/llmessage/llurlrequest.cpp | 51 +- indra/llmessage/llurlrequest.h | 68 +- indra/llui/llurlentry.cpp | 29 +- indra/llui/tests/llurlentry_test.cpp | 26 +- indra/llvfs/lldir.cpp | 39 +- indra/llvfs/lldir.h | 4 +- indra/newview/CMakeLists.txt | 55 +- indra/newview/Info-SecondLife.plist | 28 + indra/newview/app_settings/settings.xml | 115 +- .../installers/windows/installer_template.nsi | 6 + indra/newview/llagent.cpp | 4 +- indra/newview/llagent.h | 5 +- indra/newview/llagentlistener.cpp | 5 +- indra/newview/llagentui.cpp | 17 +- indra/newview/llagentui.h | 4 +- indra/newview/llappviewer.cpp | 124 +- indra/newview/llappviewer.h | 1 - indra/newview/llappviewerlinux.cpp | 2 +- indra/newview/llappviewermacosx.cpp | 1 - indra/newview/llavataractions.cpp | 2 +- indra/newview/llbottomtray.cpp | 4 +- indra/newview/llcallfloater.cpp | 33 +- indra/newview/llcallingcard.cpp | 1 + indra/newview/llchathistory.cpp | 15 +- indra/newview/llcurrencyuimanager.cpp | 2 +- indra/newview/llfloaterabout.cpp | 14 +- indra/newview/llfloaterbuyland.cpp | 4 +- indra/newview/llfloaterchat.cpp | 2 +- indra/newview/llfloaterchatterbox.cpp | 2 +- indra/newview/llfloaterevent.cpp | 2 +- indra/newview/llfloaterland.cpp | 11 +- indra/newview/llfloaterpreference.cpp | 4 +- indra/newview/llfloaterregioninfo.cpp | 3 +- indra/newview/llfloaterreporter.cpp | 12 +- indra/newview/llfloatersnapshot.cpp | 2 +- indra/newview/llfloatervoicedevicesettings.cpp | 41 +- indra/newview/llfloaterworldmap.cpp | 23 +- indra/newview/llfloaterworldmap.h | 3 +- indra/newview/llgrouplist.cpp | 2 +- indra/newview/llimfloater.cpp | 1 - indra/newview/llimpanel.cpp | 14 +- indra/newview/llimview.cpp | 27 +- indra/newview/llinspectavatar.cpp | 8 +- indra/newview/llinspectobject.cpp | 6 +- indra/newview/llinspectremoteobject.cpp | 4 +- indra/newview/llinventorymodel.cpp | 1 - indra/newview/lllandmarkactions.cpp | 4 +- indra/newview/lllocationinputctrl.cpp | 9 +- indra/newview/llloginhandler.cpp | 200 +- indra/newview/llloginhandler.h | 11 +- indra/newview/lllogininstance.cpp | 77 +- indra/newview/lllogininstance.h | 7 +- indra/newview/llnavigationbar.cpp | 34 +- indra/newview/lloutputmonitorctrl.cpp | 8 +- indra/newview/lloutputmonitorctrl.h | 2 +- indra/newview/lloverlaybar.cpp | 2 +- indra/newview/llpanelavatar.cpp | 8 +- indra/newview/llpanelgroup.cpp | 4 +- indra/newview/llpanelimcontrolpanel.cpp | 5 +- indra/newview/llpanellogin.cpp | 542 +- indra/newview/llpanellogin.h | 29 +- indra/newview/llpanelpeople.cpp | 4 +- indra/newview/llpanelplacestab.cpp | 5 +- indra/newview/llparticipantlist.cpp | 3 +- indra/newview/llsecapi.cpp | 161 + indra/newview/llsecapi.h | 493 ++ indra/newview/llsechandler_basic.cpp | 1586 +++++ indra/newview/llsechandler_basic.h | 285 + indra/newview/llselectmgr.cpp | 8 +- indra/newview/llslurl.cpp | 514 +- indra/newview/llslurl.h | 159 +- indra/newview/llspeakbutton.cpp | 10 +- indra/newview/llspeakers.cpp | 40 +- indra/newview/llspeakingindicatormanager.cpp | 4 +- indra/newview/llstartup.cpp | 722 +- indra/newview/llstartup.h | 17 +- indra/newview/llstylemap.cpp | 2 +- indra/newview/llurl.cpp | 6 + indra/newview/llurl.h | 1 + indra/newview/llurldispatcher.cpp | 201 +- indra/newview/llurldispatcher.h | 19 +- indra/newview/llurllineeditorctrl.cpp | 2 +- indra/newview/llvieweraudio.cpp | 10 +- indra/newview/llviewercontrol.cpp | 6 +- indra/newview/llviewerinventory.cpp | 13 +- indra/newview/llviewermenu.cpp | 16 +- indra/newview/llviewermessage.cpp | 23 +- indra/newview/llviewernetwork.cpp | 694 +- indra/newview/llviewernetwork.h | 182 +- indra/newview/llviewerobject.cpp | 12 +- indra/newview/llviewerstats.cpp | 6 +- indra/newview/llviewerwindow.cpp | 33 +- indra/newview/llvoavatar.cpp | 12 +- indra/newview/llvoicechannel.cpp | 64 +- indra/newview/llvoicechannel.h | 3 +- indra/newview/llvoiceclient.cpp | 7434 ++------------------ indra/newview/llvoiceclient.h | 1022 +-- indra/newview/llvoicevivox.cpp | 6967 ++++++++++++++++++ indra/newview/llvoicevivox.h | 914 +++ indra/newview/llweb.cpp | 2 +- indra/newview/llworld.cpp | 3 +- indra/newview/llxmlrpclistener.cpp | 18 +- indra/newview/llxmlrpctransaction.cpp | 106 +- indra/newview/llxmlrpctransaction.h | 3 + .../newview/skins/default/xui/en/floater_about.xml | 2 +- .../newview/skins/default/xui/en/notifications.xml | 63 + indra/newview/skins/default/xui/en/panel_login.xml | 88 +- indra/newview/skins/default/xui/en/strings.xml | 9 + indra/newview/tests/lllogininstance_test.cpp | 115 +- indra/newview/tests/llsecapi_test.cpp | 188 + indra/newview/tests/llsechandler_basic_test.cpp | 964 +++ indra/newview/tests/llslurl_test.cpp | 258 + indra/newview/tests/llviewernetwork_test.cpp | 486 ++ indra/newview/viewer_manifest.py | 17 + indra/viewer_components/login/lllogin.cpp | 43 +- 121 files changed, 16325 insertions(+), 9695 deletions(-) create mode 100644 indra/newview/llsecapi.cpp create mode 100644 indra/newview/llsecapi.h create mode 100644 indra/newview/llsechandler_basic.cpp create mode 100644 indra/newview/llsechandler_basic.h create mode 100644 indra/newview/llvoicevivox.cpp create mode 100644 indra/newview/llvoicevivox.h create mode 100644 indra/newview/tests/llsecapi_test.cpp create mode 100644 indra/newview/tests/llsechandler_basic_test.cpp create mode 100644 indra/newview/tests/llslurl_test.cpp create mode 100644 indra/newview/tests/llviewernetwork_test.cpp diff --git a/indra/llcommon/lluri.cpp b/indra/llcommon/lluri.cpp index 9d4f3a98f0..0e8f3f0f73 100644 --- a/indra/llcommon/lluri.cpp +++ b/indra/llcommon/lluri.cpp @@ -231,7 +231,8 @@ static BOOL isDefault(const std::string& scheme, U16 port) void LLURI::parseAuthorityAndPathUsingOpaque() { if (mScheme == "http" || mScheme == "https" || - mScheme == "ftp" || mScheme == "secondlife" ) + mScheme == "ftp" || mScheme == "secondlife" || + mScheme == "x-grid-location-info") { if (mEscapedOpaque.substr(0,2) != "//") { diff --git a/indra/llcommon/lluuid.cpp b/indra/llcommon/lluuid.cpp index bcbae06ec5..583c1e589b 100644 --- a/indra/llcommon/lluuid.cpp +++ b/indra/llcommon/lluuid.cpp @@ -33,9 +33,12 @@ // We can't use WIN32_LEAN_AND_MEAN here, needs lots of includes. #if LL_WINDOWS -# undef WIN32_LEAN_AND_MEAN -# include -# include +#undef WIN32_LEAN_AND_MEAN +#include +#include +// ugh, this is ugly. We need to straighten out our linking for this library +#pragma comment(lib, "IPHLPAPI.lib") +#include #endif #include "lldefs.h" @@ -452,67 +455,102 @@ static void get_random_bytes(void *buf, int nbytes) return; } -#if LL_WINDOWS -typedef struct _ASTAT_ -{ - ADAPTER_STATUS adapt; - NAME_BUFFER NameBuff [30]; -}ASTAT, * PASTAT; +#if LL_WINDOWS +// Code copied from http://msdn.microsoft.com/en-us/library/aa365939(VS.85).aspx +// This code grabs the first hardware address, rather than the first interface. +// Using a VPN can cause the first returned interface to be changed. + +const S32 MAC_ADDRESS_BYTES=6; + // static -S32 LLUUID::getNodeID(unsigned char * node_id) -{ - ASTAT Adapter; - NCB Ncb; - UCHAR uRetCode; - LANA_ENUM lenum; - int i; - int retval = 0; - - memset( &Ncb, 0, sizeof(Ncb) ); - Ncb.ncb_command = NCBENUM; - Ncb.ncb_buffer = (UCHAR *)&lenum; - Ncb.ncb_length = sizeof(lenum); - uRetCode = Netbios( &Ncb ); - // printf( "The NCBENUM return code is: 0x%x \n", uRetCode ); - - for(i=0; i < lenum.length ;i++) - { - memset( &Ncb, 0, sizeof(Ncb) ); - Ncb.ncb_command = NCBRESET; - Ncb.ncb_lana_num = lenum.lana[i]; - - uRetCode = Netbios( &Ncb ); - // printf( "The NCBRESET on LANA %d return code is: 0x%x \n", - // lenum.lana[i], uRetCode ); - - memset( &Ncb, 0, sizeof (Ncb) ); - Ncb.ncb_command = NCBASTAT; - Ncb.ncb_lana_num = lenum.lana[i]; - - strcpy( (char *)Ncb.ncb_callname, "* " ); /* Flawfinder: ignore */ - Ncb.ncb_buffer = (unsigned char *)&Adapter; - Ncb.ncb_length = sizeof(Adapter); - - uRetCode = Netbios( &Ncb ); -// printf( "The NCBASTAT on LANA %d return code is: 0x%x \n", -// lenum.lana[i], uRetCode ); - if ( uRetCode == 0 ) - { -// printf( "The Ethernet Number on LANA %d is: %02x%02x%02x%02x%02x%02x\n", -// lenum.lana[i], -// Adapter.adapt.adapter_address[0], -// Adapter.adapt.adapter_address[1], -// Adapter.adapt.adapter_address[2], -// Adapter.adapt.adapter_address[3], -// Adapter.adapt.adapter_address[4], -// Adapter.adapt.adapter_address[5] ); - memcpy(node_id,Adapter.adapt.adapter_address,6); /* Flawfinder: ignore */ - retval = 1; - - } - } - return retval; +S32 LLUUID::getNodeID(unsigned char *node_id) +{ + + // Declare and initialize variables. + DWORD dwSize = 0; + DWORD dwRetVal = 0; + int i; + +/* variables used for GetIfTable and GetIfEntry */ + MIB_IFTABLE *pIfTable; + MIB_IFROW *pIfRow; + + // Allocate memory for our pointers. + pIfTable = (MIB_IFTABLE *) malloc(sizeof (MIB_IFTABLE)); + if (pIfTable == NULL) + { + printf("Error allocating memory needed to call GetIfTable\n"); + return 0; + } + + // Before calling GetIfEntry, we call GetIfTable to make + // sure there are entries to get and retrieve the interface index. + + // Make an initial call to GetIfTable to get the + // necessary size into dwSize + if (GetIfTable(pIfTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER) { + free(pIfTable); + pIfTable = (MIB_IFTABLE *) malloc(dwSize); + if (pIfTable == NULL) + { + printf("Error allocating memory\n"); + return 0; + } + } + // Make a second call to GetIfTable to get the actual + // data we want. + if ((dwRetVal = GetIfTable(pIfTable, &dwSize, 0)) == NO_ERROR) + { + if (pIfTable->dwNumEntries > 0) + { + pIfRow = (MIB_IFROW *) malloc(sizeof (MIB_IFROW)); + if (pIfRow == NULL) + { + printf("Error allocating memory\n"); + if (pIfTable != NULL) + { + free(pIfTable); + pIfTable = NULL; + } + return 0; + } + + int limit = MAC_ADDRESS_BYTES; + memcpy(node_id, "\0\0\0\0\0\0", limit); // zero out array of bytes + for (i = 0; i < (int) pIfTable->dwNumEntries; i++) + { + pIfRow->dwIndex = pIfTable->table[i].dwIndex; + if ((dwRetVal = GetIfEntry(pIfRow)) == NO_ERROR) + { + switch (pIfRow->dwType) + { + case IF_TYPE_ETHERNET_CSMACD: + case IF_TYPE_IEEE80211: + limit = min((int) pIfRow->dwPhysAddrLen, limit); + if (pIfRow->dwPhysAddrLen == 0) + break; + memcpy(node_id, (UCHAR *)&pIfRow->bPhysAddr[0], limit); // just incase the PhysAddr is not the expected MAC_Address size + free(pIfTable); + return 1; //return first hardware device found. + break; + + case IF_TYPE_OTHER: + case IF_TYPE_PPP: + case IF_TYPE_SOFTWARE_LOOPBACK: + case IF_TYPE_ISO88025_TOKENRING: + case IF_TYPE_IEEE1394: + case IF_TYPE_ATM: + case IF_TYPE_TUNNEL: + default: + break; + } + } + } + } + } + free(pIfTable); + return 0; } #elif LL_DARWIN diff --git a/indra/llmessage/llcurl.cpp b/indra/llmessage/llcurl.cpp index 024e17a777..91e11b8c0d 100644 --- a/indra/llmessage/llcurl.cpp +++ b/indra/llmessage/llcurl.cpp @@ -89,10 +89,6 @@ S32 gCurlMultiCount = 0; std::vector LLCurl::sSSLMutex; std::string LLCurl::sCAPath; std::string LLCurl::sCAFile; -// Verify SSL certificates by default (matches libcurl default). The ability -// to alter this flag is only to allow us to suppress verification if it's -// broken for some reason. -bool LLCurl::sSSLVerify = true; //static void LLCurl::setCAPath(const std::string& path) @@ -106,18 +102,6 @@ void LLCurl::setCAFile(const std::string& file) sCAFile = file; } -//static -void LLCurl::setSSLVerify(bool verify) -{ - sSSLVerify = verify; -} - -//static -bool LLCurl::getSSLVerify() -{ - return sSSLVerify; -} - //static std::string LLCurl::getVersionString() { @@ -481,8 +465,7 @@ void LLCurl::Easy::prepRequest(const std::string& url, setErrorBuffer(); setCA(); - setopt(CURLOPT_SSL_VERIFYPEER, LLCurl::getSSLVerify()); - setopt(CURLOPT_SSL_VERIFYHOST, LLCurl::getSSLVerify()? 2 : 0); + setopt(CURLOPT_SSL_VERIFYPEER, true); setopt(CURLOPT_TIMEOUT, CURL_REQUEST_TIMEOUT); setoptString(CURLOPT_URL, url); @@ -912,6 +895,15 @@ void LLCurlEasyRequest::setReadCallback(curl_read_callback callback, void* userd } } +void LLCurlEasyRequest::setSSLCtxCallback(curl_ssl_ctx_callback callback, void* userdata) +{ + if (mEasy) + { + mEasy->setopt(CURLOPT_SSL_CTX_FUNCTION, (void*)callback); + mEasy->setopt(CURLOPT_SSL_CTX_DATA, userdata); + } +} + void LLCurlEasyRequest::slist_append(const char* str) { if (mEasy) @@ -1061,3 +1053,4 @@ void LLCurl::cleanupClass() #endif curl_global_cleanup(); } + diff --git a/indra/llmessage/llcurl.h b/indra/llmessage/llcurl.h index caf02cccd9..b6a637ae5b 100644 --- a/indra/llmessage/llcurl.h +++ b/indra/llmessage/llcurl.h @@ -157,16 +157,6 @@ public: */ static const std::string& getCAPath() { return sCAPath; } - /** - * @ brief Set flag controlling whether to verify HTTPS certs. - */ - static void setSSLVerify(bool verify); - - /** - * @ brief Get flag controlling whether to verify HTTPS certs. - */ - static bool getSSLVerify(); - /** * @ brief Initialize LLCurl class */ @@ -192,7 +182,6 @@ public: private: static std::string sCAPath; static std::string sCAFile; - static bool sSSLVerify; }; namespace boost @@ -240,6 +229,7 @@ public: void setHeaderCallback(curl_header_callback callback, void* userdata); void setWriteCallback(curl_write_callback callback, void* userdata); void setReadCallback(curl_read_callback callback, void* userdata); + void setSSLCtxCallback(curl_ssl_ctx_callback callback, void* userdata); void slist_append(const char* str); void sendRequest(const std::string& url); void requestComplete(); diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp index dd56e18caf..345b76d1a1 100644 --- a/indra/llmessage/llhttpclient.cpp +++ b/indra/llmessage/llhttpclient.cpp @@ -31,7 +31,7 @@ */ #include "linden_common.h" - +#include #include "llhttpclient.h" #include "llassetstorage.h" @@ -46,7 +46,10 @@ #include "message.h" #include + const F32 HTTP_REQUEST_EXPIRY_SECS = 60.0f; +LLURLRequest::SSLCertVerifyCallback LLHTTPClient::mCertVerifyCallback = NULL; + //////////////////////////////////////////////////////////////////////////// // Responder class moved to LLCurl @@ -206,13 +209,19 @@ namespace LLPumpIO* theClientPump = NULL; } +void LLHTTPClient::setCertVerifyCallback(LLURLRequest::SSLCertVerifyCallback callback) +{ + LLHTTPClient::mCertVerifyCallback = callback; +} + static void request( const std::string& url, LLURLRequest::ERequestAction method, Injector* body_injector, LLCurl::ResponderPtr responder, const F32 timeout = HTTP_REQUEST_EXPIRY_SECS, - const LLSD& headers = LLSD()) + const LLSD& headers = LLSD() + ) { if (!LLHTTPClient::hasPump()) { @@ -222,7 +231,7 @@ static void request( LLPumpIO::chain_t chain; LLURLRequest* req = new LLURLRequest(method, url); - req->checkRootCertificate(LLCurl::getSSLVerify()); + req->setSSLVerifyCallback(LLHTTPClient::getCertVerifyCallback(), (void *)req); lldebugs << LLURLRequest::actionAsVerb(method) << " " << url << " " @@ -417,7 +426,6 @@ static LLSD blocking_request( std::string body_str; // other request method checks root cert first, we skip? - //req->checkRootCertificate(true); // * Set curl handle options curl_easy_setopt(curlp, CURLOPT_NOSIGNAL, 1); // don't use SIGALRM for timeouts diff --git a/indra/llmessage/llhttpclient.h b/indra/llmessage/llhttpclient.h index 3d0646e5fe..8afbc9e0fc 100644 --- a/indra/llmessage/llhttpclient.h +++ b/indra/llmessage/llhttpclient.h @@ -40,7 +40,8 @@ #include #include - +#include +#include "llurlrequest.h" #include "llassettype.h" #include "llcurl.h" #include "lliopipe.h" @@ -61,6 +62,7 @@ public: typedef LLCurl::Responder Responder; typedef LLCurl::ResponderPtr ResponderPtr; + /** @name non-blocking API */ //@{ static void head( @@ -155,7 +157,12 @@ public: static void setPump(LLPumpIO& pump); ///< must be called before any of the above calls are made static bool hasPump(); - ///< for testing + + static void setCertVerifyCallback(LLURLRequest::SSLCertVerifyCallback callback); + static LLURLRequest::SSLCertVerifyCallback getCertVerifyCallback() { return mCertVerifyCallback; } + +protected: + static LLURLRequest::SSLCertVerifyCallback mCertVerifyCallback; }; #endif // LL_LLHTTPCLIENT_H diff --git a/indra/llmessage/llurlrequest.cpp b/indra/llmessage/llurlrequest.cpp index 4e7ceff984..1e76d10828 100644 --- a/indra/llmessage/llurlrequest.cpp +++ b/indra/llmessage/llurlrequest.cpp @@ -36,7 +36,8 @@ #include "llurlrequest.h" #include - +#include +#include #include "llcurl.h" #include "llioutil.h" #include "llmemtype.h" @@ -56,6 +57,8 @@ const std::string CONTEXT_TRANSFERED_BYTES("transfered_bytes"); static size_t headerCallback(void* data, size_t size, size_t nmemb, void* user); + + /** * class LLURLRequestDetail */ @@ -72,6 +75,7 @@ public: U32 mBodyLimit; S32 mByteAccumulator; bool mIsBodyLimitSet; + LLURLRequest::SSLCertVerifyCallback mSSLVerifyCallback; }; LLURLRequestDetail::LLURLRequestDetail() : @@ -80,7 +84,8 @@ LLURLRequestDetail::LLURLRequestDetail() : mLastRead(NULL), mBodyLimit(0), mByteAccumulator(0), - mIsBodyLimitSet(false) + mIsBodyLimitSet(false), + mSSLVerifyCallback(NULL) { LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); mCurlRequest = new LLCurlEasyRequest(); @@ -94,6 +99,36 @@ LLURLRequestDetail::~LLURLRequestDetail() mLastRead = NULL; } +void LLURLRequest::setSSLVerifyCallback(SSLCertVerifyCallback callback, void *param) +{ + mDetail->mSSLVerifyCallback = callback; + mDetail->mCurlRequest->setSSLCtxCallback(LLURLRequest::_sslCtxCallback, (void *)this); + mDetail->mCurlRequest->setopt(CURLOPT_SSL_VERIFYPEER, true); + mDetail->mCurlRequest->setopt(CURLOPT_SSL_VERIFYHOST, 2); +} + + +// _sslCtxFunction +// Callback function called when an SSL Context is created via CURL +// used to configure the context for custom cert validation + +CURLcode LLURLRequest::_sslCtxCallback(CURL * curl, void *sslctx, void *param) +{ + LLURLRequest *req = (LLURLRequest *)param; + if(req == NULL || req->mDetail->mSSLVerifyCallback == NULL) + { + SSL_CTX_set_cert_verify_callback((SSL_CTX *)sslctx, NULL, NULL); + return CURLE_OK; + } + SSL_CTX * ctx = (SSL_CTX *) sslctx; + // disable any default verification for server certs + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); + // set the verification callback. + SSL_CTX_set_cert_verify_callback(ctx, req->mDetail->mSSLVerifyCallback, (void *)req); + // the calls are void + return CURLE_OK; + +} /** * class LLURLRequest @@ -148,6 +183,11 @@ void LLURLRequest::setURL(const std::string& url) mDetail->mURL = url; } +std::string LLURLRequest::getURL() const +{ + return mDetail->mURL; +} + void LLURLRequest::addHeader(const char* header) { LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); @@ -160,13 +200,6 @@ void LLURLRequest::setBodyLimit(U32 size) mDetail->mIsBodyLimitSet = true; } -void LLURLRequest::checkRootCertificate(bool check) -{ - mDetail->mCurlRequest->setopt(CURLOPT_SSL_VERIFYPEER, (check? TRUE : FALSE)); - mDetail->mCurlRequest->setopt(CURLOPT_SSL_VERIFYHOST, (check? 2 : 0)); - mDetail->mCurlRequest->setoptString(CURLOPT_ENCODING, ""); -} - void LLURLRequest::setCallback(LLURLRequestComplete* callback) { LLMemType m1(LLMemType::MTYPE_IO_URL_REQUEST); diff --git a/indra/llmessage/llurlrequest.h b/indra/llmessage/llurlrequest.h index cb3c466440..69fd22e592 100644 --- a/indra/llmessage/llurlrequest.h +++ b/indra/llmessage/llurlrequest.h @@ -44,6 +44,8 @@ #include "lliopipe.h" #include "llchainio.h" #include "llerror.h" +#include +#include "llcurl.h" extern const std::string CONTEXT_REQUEST; @@ -72,6 +74,8 @@ class LLURLRequest : public LLIOPipe { LOG_CLASS(LLURLRequest); public: + + typedef int (* SSLCertVerifyCallback)(X509_STORE_CTX *ctx, void *param); /** * @brief This enumeration is for specifying the type of request. */ @@ -125,7 +129,7 @@ public: * */ void setURL(const std::string& url); - + std::string getURL() const; /** * @brief Add a header to the http post. * @@ -143,8 +147,9 @@ public: * Set whether request will check that remote server * certificates are signed by a known root CA when using HTTPS. */ - void checkRootCertificate(bool check); + void setSSLVerifyCallback(SSLCertVerifyCallback callback, void * param); + /** * @brief Return at most size bytes of body. * @@ -189,6 +194,7 @@ public: * @brief Give this pipe a chance to handle a generated error */ virtual EStatus handleError(EStatus status, LLPumpIO* pump); + protected: /** @@ -217,6 +223,8 @@ protected: S32 mRequestTransferedBytes; S32 mResponseTransferedBytes; + static CURLcode _sslCtxCallback(CURL * curl, void *sslctx, void *param); + private: /** * @brief Initialize the object. Called during construction. @@ -364,62 +372,6 @@ protected: }; -/** - * @class LLURLRequestClientFactory - * @brief Template class to build url request based client chains - * - * This class eases construction of a basic sd rpc client. Here is an - * example of it's use: - * - * class LLUsefulService : public LLService { ... }
- * LLService::registerCreator(
- * "useful",
- * LLService::creator_t(new LLURLRequestClientFactory))
- *
- * - * This class should work, but I never got around to using/testing it. - * - */ -#if 0 -template -class LLURLRequestClientFactory : public LLChainIOFactory -{ -public: - LLURLRequestClientFactory(LLURLRequest::ERequestAction action) {} - LLURLRequestClientFactory( - LLURLRequest::ERequestAction action, - const std::string& fixed_url) : - mAction(action), - mURL(fixed_url) - { - } - virtual bool build(LLPumpIO::chain_t& chain, LLSD context) const - { - lldebugs << "LLURLRequestClientFactory::build" << llendl; - LLIOPipe::ptr_t service(new Client); - chain.push_back(service); - LLURLRequest* http(new LLURLRequest(mAction)); - LLIOPipe::ptr_t http_pipe(http); - // *FIX: how do we know the content type? - //http->addHeader("Content-Type: text/llsd"); - if(mURL.empty()) - { - chain.push_back(LLIOPipe::ptr_t(new LLContextURLExtractor(http))); - } - else - { - http->setURL(mURL); - } - chain.push_back(http_pipe); - chain.push_back(service); - return true; - } - -protected: - LLURLRequest::ERequestAction mAction; - std::string mURL; -}; -#endif /** * External constants diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index e8e3459673..c38e38c900 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -41,6 +41,9 @@ #include "lltrans.h" #include "lluicolortable.h" +#define APP_HEADER_REGEX "((x-grid-location-info://[-\\w\\.]+/app)|(secondlife:///app))" + + LLUrlEntryBase::LLUrlEntryBase() : mColor(LLUIColorTable::instance().getColor("HTMLLinkColor")), mDisabledLink(false) @@ -303,10 +306,11 @@ std::string LLUrlEntrySLURL::getLocation(const std::string &url) const // // LLUrlEntryAgent Describes a Second Life agent Url, e.g., // secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about +// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about // LLUrlEntryAgent::LLUrlEntryAgent() { - mPattern = boost::regex("secondlife:///app/agent/[\\da-f-]+/\\w+", + mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/\\w+", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_agent.xml"; mIcon = "Generic_Person"; @@ -418,10 +422,11 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa // LLUrlEntryGroup Describes a Second Life group Url, e.g., // secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about // secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect +// x-grid-location-info://lincoln.lindenlab.com/app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect // LLUrlEntryGroup::LLUrlEntryGroup() { - mPattern = boost::regex("secondlife:///app/group/[\\da-f-]+/\\w+", + mPattern = boost::regex(APP_HEADER_REGEX "/group/[\\da-f-]+/\\w+", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_group.xml"; mIcon = "Generic_Group"; @@ -482,7 +487,8 @@ LLUrlEntryInventory::LLUrlEntryInventory() //*TODO: add supporting of inventory item names with whitespaces //this pattern cann't parse for example //secondlife:///app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select?name=name with spaces¶m2=value - mPattern = boost::regex("secondlife:///app/inventory/[\\da-f-]+/\\w+\\S*", + //x-grid-location-info://lincoln.lindenlab.com/app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select?name=name with spaces¶m2=value + mPattern = boost::regex(APP_HEADER_REGEX "/inventory/[\\da-f-]+/\\w+\\S*", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_inventory.xml"; } @@ -496,10 +502,11 @@ std::string LLUrlEntryInventory::getLabel(const std::string &url, const LLUrlLab /// /// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., /// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about +/// x-grid-location-info://lincoln.lindenlab.com/app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about /// LLUrlEntryParcel::LLUrlEntryParcel() { - mPattern = boost::regex("secondlife:///app/parcel/[\\da-f-]+/about", + mPattern = boost::regex(APP_HEADER_REGEX "/parcel/[\\da-f-]+/about", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_parcel.xml"; mTooltip = LLTrans::getString("TooltipParcelUrl"); @@ -515,7 +522,7 @@ std::string LLUrlEntryParcel::getLabel(const std::string &url, const LLUrlLabelC // LLUrlEntryPlace::LLUrlEntryPlace() { - mPattern = boost::regex("secondlife://\\S+/?(\\d+/\\d+/\\d+|\\d+/\\d+)/?", + mPattern = boost::regex("((x-grid-location-info://[-\\w\\.]+/region/)|(secondlife://))\\S+/?(\\d+/\\d+/\\d+|\\d+/\\d+)/?", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_slurl.xml"; mTooltip = LLTrans::getString("TooltipSLURL"); @@ -560,10 +567,11 @@ std::string LLUrlEntryPlace::getLocation(const std::string &url) const // // LLUrlEntryTeleport Describes a Second Life teleport Url, e.g., // secondlife:///app/teleport/Ahern/50/50/50/ +// x-grid-location-info://lincoln.lindenlab.com/app/teleport/Ahern/50/50/50/ // LLUrlEntryTeleport::LLUrlEntryTeleport() { - mPattern = boost::regex("secondlife:///app/teleport/\\S+(/\\d+)?(/\\d+)?(/\\d+)?/?\\S*", + mPattern = boost::regex(APP_HEADER_REGEX "/teleport/\\S+(/\\d+)?(/\\d+)?(/\\d+)?/?\\S*", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_teleport.xml"; mTooltip = LLTrans::getString("TooltipTeleportUrl"); @@ -581,7 +589,12 @@ std::string LLUrlEntryTeleport::getLabel(const std::string &url, const LLUrlLabe LLURI uri(url); LLSD path_array = uri.pathArray(); S32 path_parts = path_array.size(); - const std::string label = LLTrans::getString("SLurlLabelTeleport"); + std::string host = uri.hostName(); + std::string label = LLTrans::getString("SLurlLabelTeleport"); + if (!host.empty()) + { + label += " " + host; + } if (path_parts == 6) { // handle teleport url with (X,Y,Z) coordinates @@ -680,7 +693,7 @@ std::string LLUrlEntrySLLabel::getTooltip(const std::string &string) const // LLUrlEntryWorldMap::LLUrlEntryWorldMap() { - mPattern = boost::regex("secondlife:///app/worldmap/\\S+/?(\\d+)?/?(\\d+)?/?(\\d+)?/?\\S*", + mPattern = boost::regex(APP_HEADER_REGEX "/worldmap/\\S+/?(\\d+)?/?(\\d+)?/?(\\d+)?/?\\S*", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_map.xml"; mTooltip = LLTrans::getString("TooltipMapUrl"); diff --git a/indra/llui/tests/llurlentry_test.cpp b/indra/llui/tests/llurlentry_test.cpp index cbb303a059..4463b6cc6f 100644 --- a/indra/llui/tests/llurlentry_test.cpp +++ b/indra/llui/tests/llurlentry_test.cpp @@ -286,6 +286,13 @@ namespace tut "XXX secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar", "secondlife:///App/AGENT/0E346D8B-4433-4d66-a6b0-fd37083abc4c/foobar"); + testRegex("Standalone Agent Url ", url, + "x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about", + "x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + + testRegex("Standalone Agent Url Multicase with Text", url, + "M x-grid-location-info://lincoln.lindenlab.com/app/AGENT/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about M", + "x-grid-location-info://lincoln.lindenlab.com/app/AGENT/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); } template<> template<> @@ -315,6 +322,15 @@ namespace tut testRegex("Group Url multicase", url, "XXX secondlife:///APP/Group/00005FF3-4044-c79f-9de8-fb28ae0df991/About XXX", "secondlife:///APP/Group/00005FF3-4044-c79f-9de8-fb28ae0df991/About"); + + testRegex("Standalone Group Url ", url, + "x-grid-location-info://lincoln.lindenlab.com/app/group/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about", + "x-grid-location-info://lincoln.lindenlab.com/app/group/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + + testRegex("Standalone Group Url Multicase ith Text", url, + "M x-grid-location-info://lincoln.lindenlab.com/app/GROUP/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about M", + "x-grid-location-info://lincoln.lindenlab.com/app/GROUP/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about"); + } template<> template<> @@ -361,7 +377,11 @@ namespace tut // DEV-35459: SLURLs and teleport Links not parsed properly testRegex("SLURL with quote", url, "XXX secondlife://A'ksha%20Oasis/41/166/701 XXX", - "secondlife://A%27ksha%20Oasis/41/166/701"); + "secondlife://A%27ksha%20Oasis/41/166/701"); + + testRegex("Standalone All Hands (50,50) [2] with text", url, + "XXX x-grid-location-info://lincoln.lindenlab.com/region/All%20Hands/50/50/50 XXX", + "x-grid-location-info://lincoln.lindenlab.com/region/All%20Hands/50/50/50"); } template<> template<> @@ -461,6 +481,10 @@ namespace tut testRegex("Teleport url with quote", url, "XXX secondlife:///app/teleport/A'ksha%20Oasis/41/166/701 XXX", "secondlife:///app/teleport/A%27ksha%20Oasis/41/166/701"); + + testRegex("Standalone All Hands", url, + "XXX x-grid-location-info://lincoln.lindenlab.com/app/teleport/All%20Hands/50/50/50 XXX", + "x-grid-location-info://lincoln.lindenlab.com/app/teleport/All%20Hands/50/50/50"); } template<> template<> diff --git a/indra/llvfs/lldir.cpp b/indra/llvfs/lldir.cpp index da4abde451..b4ee42ef3a 100644 --- a/indra/llvfs/lldir.cpp +++ b/indra/llvfs/lldir.cpp @@ -459,7 +459,6 @@ std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subd } //llinfos << "*** EXPANDED FILENAME: <" << expanded_filename << ">" << llendl; - return expanded_filename; } @@ -565,27 +564,23 @@ std::string LLDir::getForbiddenFileChars() return "\\/:*?\"<>|"; } -void LLDir::setLindenUserDir(const std::string &first, const std::string &last) +void LLDir::setLindenUserDir(const std::string &username) { - // if both first and last aren't set, that's bad. - if (!first.empty() && !last.empty()) + // if the username isn't set, that's bad + if (!username.empty()) { // some platforms have case-sensitive filesystems, so be // utterly consistent with our firstname/lastname case. - std::string firstlower(first); - LLStringUtil::toLower(firstlower); - std::string lastlower(last); - LLStringUtil::toLower(lastlower); + std::string userlower(username); + LLStringUtil::toLower(userlower); + LLStringUtil::replaceChar(userlower, ' ', '_'); mLindenUserDir = getOSUserAppDir(); mLindenUserDir += mDirDelimiter; - mLindenUserDir += firstlower; - mLindenUserDir += "_"; - mLindenUserDir += lastlower; - llinfos << "Got name for LLDir::setLindenUserDir(first='" << first << "', last='" << last << "')" << llendl; + mLindenUserDir += userlower; } else { - llerrs << "Invalid name for LLDir::setLindenUserDir(first='" << first << "', last='" << last << "')" << llendl; + llerrs << "NULL name for LLDir::setLindenUserDir" << llendl; } dumpCurrentDirectories(); @@ -603,27 +598,25 @@ void LLDir::setChatLogsDir(const std::string &path) } } -void LLDir::setPerAccountChatLogsDir(const std::string &first, const std::string &last) +void LLDir::setPerAccountChatLogsDir(const std::string &username) { // if both first and last aren't set, assume we're grabbing the cached dir - if (!first.empty() && !last.empty()) + if (!username.empty()) { // some platforms have case-sensitive filesystems, so be // utterly consistent with our firstname/lastname case. - std::string firstlower(first); - LLStringUtil::toLower(firstlower); - std::string lastlower(last); - LLStringUtil::toLower(lastlower); + std::string userlower(username); + LLStringUtil::toLower(userlower); + LLStringUtil::replaceChar(userlower, ' ', '_'); mPerAccountChatLogsDir = getChatLogsDir(); mPerAccountChatLogsDir += mDirDelimiter; - mPerAccountChatLogsDir += firstlower; - mPerAccountChatLogsDir += "_"; - mPerAccountChatLogsDir += lastlower; + mPerAccountChatLogsDir += userlower; } else { - llwarns << "Invalid name for LLDir::setPerAccountChatLogsDir" << llendl; + llerrs << "NULL name for LLDir::setPerAccountChatLogsDir" << llendl; } + } void LLDir::setSkinFolder(const std::string &skin_folder) diff --git a/indra/llvfs/lldir.h b/indra/llvfs/lldir.h index 9067d75bac..05d5efc66f 100644 --- a/indra/llvfs/lldir.h +++ b/indra/llvfs/lldir.h @@ -137,8 +137,8 @@ class LLDir static std::string getForbiddenFileChars(); virtual void setChatLogsDir(const std::string &path); // Set the chat logs dir to this user's dir - virtual void setPerAccountChatLogsDir(const std::string &first, const std::string &last); // Set the per user chat log directory. - virtual void setLindenUserDir(const std::string &first, const std::string &last); // Set the linden user dir to this user's dir + virtual void setPerAccountChatLogsDir(const std::string &username); // Set the per user chat log directory. + virtual void setLindenUserDir(const std::string &username); // Set the linden user dir to this user's dir virtual void setSkinFolder(const std::string &skin_folder); virtual bool setCacheDir(const std::string &path); diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 47fde08a9d..e0a31875c8 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -7,6 +7,7 @@ include(Boost) include(BuildVersion) include(DBusGlib) include(DirectX) +include(OpenSSL) include(DragDrop) include(ELFIO) include(FMOD) @@ -371,6 +372,8 @@ set(viewer_SOURCE_FILES llscrollingpanelparam.cpp llsearchcombobox.cpp llsearchhistory.cpp + llsecapi.cpp + llsechandler_basic.cpp llselectmgr.cpp llsidepanelappearance.cpp llsidepanelinventory.cpp @@ -445,7 +448,6 @@ set(viewer_SOURCE_FILES llurldispatcherlistener.cpp llurlhistory.cpp llurllineeditorctrl.cpp - llurlsimstring.cpp llurlwhitelist.cpp llvectorperfoptions.cpp llversioninfo.cpp @@ -512,7 +514,9 @@ set(viewer_SOURCE_FILES llvoground.cpp llvoicechannel.cpp llvoiceclient.cpp + llvoicedw.cpp llvoicevisualizer.cpp + llvoicevivox.cpp llvoinventorylistener.cpp llvopartgroup.cpp llvosky.cpp @@ -872,6 +876,8 @@ set(viewer_HEADER_FILES llscrollingpanelparam.h llsearchcombobox.h llsearchhistory.h + llsecapi.h + llsechandler_basic.h llselectmgr.h llsidepanelappearance.h llsidepanelinventory.h @@ -948,7 +954,6 @@ set(viewer_HEADER_FILES llurldispatcherlistener.h llurlhistory.h llurllineeditorctrl.h - llurlsimstring.h llurlwhitelist.h llvectorperfoptions.h llversioninfo.h @@ -1012,7 +1017,9 @@ set(viewer_HEADER_FILES llvoground.h llvoicechannel.h llvoiceclient.h + llvoicedw.h llvoicevisualizer.h + llvoicevivox.h llvoinventorylistener.h llvopartgroup.h llvosky.h @@ -1403,8 +1410,8 @@ if (WINDOWS) ${SHARED_LIB_STAGING_DIR}/RelWithDebInfo/libtcmalloc_minimal.dll ${SHARED_LIB_STAGING_DIR}/Debug/libtcmalloc_minimal-debug.dll ) - endif(USE_GOOGLE_PERFTOOLS) - + endif(USE_GOOGLE_PERFTOOLS) + set(COPY_INPUT_DEPENDECIES # The following commented dependencies are determined at variably at build time. Can't do this here. @@ -1621,6 +1628,8 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${WINDOWS_LIBRARIES} ${XMLRPCEPI_LIBRARIES} ${ELFIO_LIBRARIES} + ${OPENSSL_LIBRARIES} + ${CRYPTO_LIBRARIES} ${LLLOGIN_LIBRARIES} ${GOOGLE_PERFTOOLS_LIBRARIES} ) @@ -1797,6 +1806,43 @@ if (LL_TESTS) "${CMAKE_SOURCE_DIR}/llmessage/tests/test_llsdmessage_peer.py" ) + set(test_libs + ${LLMESSAGE_LIBRARIES} + ${WINDOWS_LIBRARIES} + ${LLVFS_LIBRARIES} + ${LLMATH_LIBRARIES} + ${LLCOMMON_LIBRARIES} + ${GOOGLEMOCK_LIBRARIES} + ${OPENSSL_LIBRARIES} + ${CRYPTO_LIBRARIES} + ) + + LL_ADD_INTEGRATION_TEST(llsechandler_basic + llsechandler_basic.cpp + "${test_libs}" + ) + + LL_ADD_INTEGRATION_TEST(llsecapi + llsecapi.cpp + "${test_libs}" + ) + + set(llslurl_test_sources + llslurl.cpp + llviewernetwork.cpp + ) + + + LL_ADD_INTEGRATION_TEST(llslurl + "${llslurl_test_sources}" + "${test_libs}" + ) + + LL_ADD_INTEGRATION_TEST(llviewernetwork + llviewernetwork.cpp + "${test_libs}" + ) + #ADD_VIEWER_BUILD_TEST(llmemoryview viewer) #ADD_VIEWER_BUILD_TEST(llagentaccess viewer) #ADD_VIEWER_BUILD_TEST(llworldmap viewer) @@ -1804,6 +1850,7 @@ if (LL_TESTS) #ADD_VIEWER_BUILD_TEST(lltextureinfo viewer) #ADD_VIEWER_BUILD_TEST(lltextureinfodetails viewer) #ADD_VIEWER_BUILD_TEST(lltexturestatsuploader viewer) + endif (LL_TESTS) diff --git a/indra/newview/Info-SecondLife.plist b/indra/newview/Info-SecondLife.plist index 4cb01a0f33..7436c5642e 100644 --- a/indra/newview/Info-SecondLife.plist +++ b/indra/newview/Info-SecondLife.plist @@ -18,6 +18,33 @@ APPL CFBundleSignature ???? + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + slurl + + CFBundleTypeIconFile + seconlife + CFBundleTypeMIMETypes + + application/x-grid-location-info + + CFBundleTypeName + Secondlife SLURL + CFBundleTypeOSTypes + + SLRL + + CFBundleTypeRole + Viewer + LSTypeIsPackage + + NSDocumentClass + SecondLifeSLURL + + CFBundleURLTypes @@ -26,6 +53,7 @@ CFBundleURLSchemes secondlife + x-grid-location-info LSIsAppleDefaultForScheme diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 30049b73ea..442cd5d31e 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -1264,6 +1264,17 @@ Value 0 + CertStore + + Comment + Specifies the Certificate Store for certificate trust verification + Persist + 1 + Type + String + Value + default + ChatBarStealsFocus Comment @@ -1640,6 +1651,17 @@ Value 1 + CurrentGrid + + Comment + Currently Selected Grid + Persist + 1 + Type + String + Value + + CustomServer Comment @@ -2366,6 +2388,29 @@ Value 0 + DefaultFemaleAvatar + + Comment + Default Female Avatar + Persist + 1 + Type + String + Value + Female Shape & Outfit + + DefaultMaleAvatar + + Comment + Default Male Avatar + Persist + 1 + Type + String + Value + Male Shape & Outfit + + DefaultObjectTexture Comment @@ -3442,7 +3487,7 @@ Type Boolean Value - 0 + 1 ForceMandatoryUpdate @@ -7696,6 +7741,17 @@ Value 0 + SecondLifeEnterprise + + Comment + Enables Second Life Enterprise features + Persist + 1 + Type + Boolean + Value + 0 + SelectMovableOnly Comment @@ -8500,7 +8556,7 @@ Type Boolean Value - 0 + 1 ShowTangentBasis @@ -10393,6 +10449,17 @@ Value + VivoxDebugSIPURIHostName + + Comment + Hostname portion of vivox SIP URIs (empty string for the default). + Persist + 1 + Type + String + Value + + VivoxDebugVoiceAccountServerURI Comment @@ -10404,6 +10471,28 @@ Value + VivoxVoiceHost + + Comment + Client SLVoice host to connect to + Persist + 1 + Type + String + Value + 127.0.0.1 + + VivoxVoicePort + + Comment + Client SLVoice port to connect to + Persist + 1 + Type + U32 + Value + 44125 + VoiceCallsFriendsOnly Comment @@ -10536,6 +10625,17 @@ Value Default + VoiceLogFile + + Comment + Log file to use when launching the voice daemon + Persist + 1 + Type + String + Value + + VoiceOutputAudioDevice Comment @@ -10580,6 +10680,17 @@ Value 0 + VoiceServerType + + Comment + The type of voice server to connect to. + Persist + 0 + Type + String + Value + vivox + WLSkyDetail Comment diff --git a/indra/newview/installers/windows/installer_template.nsi b/indra/newview/installers/windows/installer_template.nsi index a7322749ca..b7b4c54001 100644 --- a/indra/newview/installers/windows/installer_template.nsi +++ b/indra/newview/installers/windows/installer_template.nsi @@ -797,6 +797,12 @@ WriteRegStr HKEY_CLASSES_ROOT "${URLNAME}\DefaultIcon" "" '"$INSTDIR\$INSTEXE"' ;; URL param must be last item passed to viewer, it ignores subsequent params ;; to avoid parameter injection attacks. WriteRegExpandStr HKEY_CLASSES_ROOT "${URLNAME}\shell\open\command" "" '"$INSTDIR\$INSTEXE" $INSTFLAGS -url "%1"' +WriteRegStr HKEY_CLASSES_ROOT "x-grid-location-info"(default)" "URL:Second Life" +WriteRegStr HKEY_CLASSES_ROOT "x-grid-location-info" "URL Protocol" "" +WriteRegStr HKEY_CLASSES_ROOT "x-grid-location-info\DefaultIcon" "" '"$INSTDIR\$INSTEXE"' +;; URL param must be last item passed to viewer, it ignores subsequent params +;; to avoid parameter injection attacks. +WriteRegExpandStr HKEY_CLASSES_ROOT "x-grid-location-info\shell\open\command" "" '"$INSTDIR\$INSTEXE" $INSTFLAGS -url "%1"' ; write out uninstaller WriteUninstaller "$INSTDIR\uninst.exe" diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index f434782977..37d1bd15e1 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -3231,7 +3231,7 @@ bool LLAgent::teleportCore(bool is_local) // MBW -- Let the voice client know a teleport has begun so it can leave the existing channel. // This was breaking the case of teleporting within a single sim. Backing it out for now. -// gVoiceClient->leaveChannel(); +// LLVoiceClient::getInstance()->leaveChannel(); return true; } @@ -3375,7 +3375,7 @@ void LLAgent::setTeleportState(ETeleportState state) if (mTeleportState == TELEPORT_MOVING) { // We're outa here. Save "back" slurl. - mTeleportSourceSLURL = LLAgentUI::buildSLURL(); + LLAgentUI::buildSLURL(mTeleportSourceSLURL); } else if(mTeleportState == TELEPORT_ARRIVING) { diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h index a460077b7e..32f9b00135 100644 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -42,6 +42,7 @@ #include "llpointer.h" #include "lluicolor.h" #include "llvoavatardefines.h" +#include "llslurl.h" extern const BOOL ANIMATE; extern const U8 AGENT_STATE_TYPING; // Typing indication @@ -514,13 +515,13 @@ public: public: static void parseTeleportMessages(const std::string& xml_filename); - const std::string getTeleportSourceSLURL() const { return mTeleportSourceSLURL; } + const void getTeleportSourceSLURL(LLSLURL& slurl) const { slurl = mTeleportSourceSLURL; } public: // ! TODO ! Define ERROR and PROGRESS enums here instead of exposing the mappings. static std::map sTeleportErrorMessages; static std::map sTeleportProgressMessages; private: - std::string mTeleportSourceSLURL; // SLURL where last TP began + LLSLURL mTeleportSourceSLURL; // SLURL where last TP began //-------------------------------------------------------------------- // Teleport Actions diff --git a/indra/newview/llagentlistener.cpp b/indra/newview/llagentlistener.cpp index b3ed7c353e..7a8205acb5 100644 --- a/indra/newview/llagentlistener.cpp +++ b/indra/newview/llagentlistener.cpp @@ -53,7 +53,10 @@ void LLAgentListener::requestTeleport(LLSD const & event_data) const } else { - std::string url = LLSLURL::buildSLURL(event_data["regionname"], event_data["x"], event_data["y"], event_data["z"]); + std::string url = LLSLURL(event_data["regionname"], + LLVector3(event_data["x"].asReal(), + event_data["y"].asReal(), + event_data["z"].asReal())).getSLURLString(); LLURLDispatcher::dispatch(url, NULL, false); } } diff --git a/indra/newview/llagentui.cpp b/indra/newview/llagentui.cpp index c4597ad6f8..15d9f36b74 100644 --- a/indra/newview/llagentui.cpp +++ b/indra/newview/llagentui.cpp @@ -76,16 +76,15 @@ void LLAgentUI::buildFullname(std::string& name) } //static -std::string LLAgentUI::buildSLURL(const bool escaped /*= true*/) +void LLAgentUI::buildSLURL(LLSLURL& slurl, const bool escaped /*= true*/) { - std::string slurl; - LLViewerRegion *regionp = gAgent.getRegion(); - if (regionp) - { - LLVector3d agentPos = gAgent.getPositionGlobal(); - slurl = LLSLURL::buildSLURLfromPosGlobal(regionp->getName(), agentPos, escaped); - } - return slurl; + LLSLURL return_slurl; + LLViewerRegion *regionp = gAgent.getRegion(); + if (regionp) + { + return_slurl = LLSLURL(regionp->getName(), gAgent.getPositionGlobal()); + } + slurl = return_slurl; } //static diff --git a/indra/newview/llagentui.h b/indra/newview/llagentui.h index 3478793e38..577b752fbe 100644 --- a/indra/newview/llagentui.h +++ b/indra/newview/llagentui.h @@ -33,6 +33,8 @@ #ifndef LLAGENTUI_H #define LLAGENTUI_H +class LLSLURL; + class LLAgentUI { public: @@ -48,7 +50,7 @@ public: static void buildName(std::string& name); static void buildFullname(std::string &name); - static std::string buildSLURL(const bool escaped = true); + static void buildSLURL(LLSLURL& slurl, const bool escaped = true); //build location string using the current position of gAgent. static BOOL buildLocationString(std::string& str, ELocationFormat fmt = LOCATION_FORMAT_LANDMARK); //build location string using a region position of the avatar. diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 43c8c679c6..2a355474b1 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -153,7 +153,7 @@ #include "llworld.h" #include "llhudeffecttrail.h" #include "llvectorperfoptions.h" -#include "llurlsimstring.h" +#include "llslurl.h" #include "llwatchdog.h" // Included so that constants/settings might be initialized @@ -193,6 +193,9 @@ #include "llparcel.h" #include "llavatariconctrl.h" +// Include for security api initialization +#include "llsecapi.h" + // *FIX: These extern globals should be cleaned up. // The globals either represent state/config/resource-storage of either // this app, or another 'component' of the viewer. App globals should be @@ -507,35 +510,6 @@ public: } }; -void LLAppViewer::initGridChoice() -{ - // Load up the initial grid choice from: - // - hard coded defaults... - // - command line settings... - // - if dev build, persisted settings... - - // Set the "grid choice", this is specified by command line. - std::string grid_choice = gSavedSettings.getString("CmdLineGridChoice"); - LLViewerLogin::getInstance()->setGridChoice(grid_choice); - - // Load last server choice by default - // ignored if the command line grid choice has been set - if(grid_choice.empty()) - { - S32 server = gSavedSettings.getS32("ServerChoice"); - server = llclamp(server, 0, (S32)GRID_INFO_COUNT - 1); - if(server == GRID_INFO_OTHER) - { - std::string custom_server = gSavedSettings.getString("CustomServer"); - LLViewerLogin::getInstance()->setGridChoice(custom_server); - } - else if(server != (S32)GRID_INFO_NONE) - { - LLViewerLogin::getInstance()->setGridChoice((EGridInfo)server); - } - } -} - //virtual bool LLAppViewer::initSLURLHandler() { @@ -647,7 +621,6 @@ bool LLAppViewer::init() LLCurl::initClass(); initThreads(); - writeSystemInfo(); // Build a string representing the current version number. @@ -777,10 +750,6 @@ bool LLAppViewer::init() return false; } - // Always fetch the Ethernet MAC address, needed both for login - // and password load. - LLUUID::getNodeID(gMACAddress); - // Prepare for out-of-memory situations, during which we will crash on // purpose and save a dump. #if LL_WINDOWS && LL_RELEASE_FOR_DOWNLOAD && LL_USE_SMARTHEAP @@ -892,6 +861,7 @@ bool LLAppViewer::init() } } + // save the graphics card gDebugInfo["GraphicsCard"] = LLFeatureManager::getInstance()->getGPUString(); @@ -902,6 +872,17 @@ bool LLAppViewer::init() gSimFrames = (F32)gFrameCount; LLViewerJoystick::getInstance()->init(false); + + try { + initializeSecHandler(); + } + catch (LLProtectedDataException ex) + { + LLNotificationsUtil::add("CorruptedProtectedDataStore"); + } + LLHTTPClient::setCertVerifyCallback(secapiSSLCertVerifyCallback); + + gGLActive = FALSE; if (gSavedSettings.getBOOL("QAMode") && gSavedSettings.getS32("QAModeEventHostPort") > 0) { @@ -936,13 +917,11 @@ bool LLAppViewer::mainLoop() gServicePump = new LLPumpIO(gAPRPoolp); LLHTTPClient::setPump(*gServicePump); LLCurl::setCAFile(gDirUtilp->getCAFile()); - LLCurl::setSSLVerify(! gSavedSettings.getBOOL("NoVerifySSLCert")); - + // Note: this is where gLocalSpeakerMgr and gActiveSpeakerMgr used to be instantiated. LLVoiceChannel::initClass(); - LLVoiceClient::init(gServicePump); - + LLVoiceClient::getInstance()->init(gServicePump); LLTimer frameTimer,idleTimer; LLTimer debugTime; LLViewerJoystick* joystick(LLViewerJoystick::getInstance()); @@ -1273,7 +1252,7 @@ bool LLAppViewer::cleanup() // to ensure shutdown order LLMortician::setZealous(TRUE); - LLVoiceClient::terminate(); + LLVoiceClient::getInstance()->terminate(); disconnectViewer(); @@ -1472,13 +1451,6 @@ bool LLAppViewer::cleanup() llinfos << "Saving Data" << llendflush; - // Quitting with "Remember Password" turned off should always stomp your - // saved password, whether or not you successfully logged in. JC - if (!gSavedSettings.getBOOL("RememberPassword")) - { - LLStartUp::deletePasswordFromDisk(); - } - // Store the time of our current logoff gSavedPerAccountSettings.setU32("LastLogoff", time_corrected()); @@ -2047,7 +2019,6 @@ bool LLAppViewer::initConfiguration() } } - initGridChoice(); // If we have specified crash on startup, set the global so we'll trigger the crash at the right time if(clp.hasOption("crashonstartup")) @@ -2141,30 +2112,17 @@ bool LLAppViewer::initConfiguration() // injection and steal passwords. Phoenix. SL-55321 if(clp.hasOption("url")) { - std::string slurl = clp.getOption("url")[0]; - if (LLSLURL::isSLURLCommand(slurl)) - { - LLStartUp::sSLURLCommand = slurl; - } - else - { - LLURLSimString::setString(slurl); - } + LLStartUp::setStartSLURL(LLSLURL(clp.getOption("url")[0])); + if(LLStartUp::getStartSLURL().getType() == LLSLURL::LOCATION) + { + LLGridManager::getInstance()->setGridChoice(LLStartUp::getStartSLURL().getGrid()); + + } } else if(clp.hasOption("slurl")) { - std::string slurl = clp.getOption("slurl")[0]; - if(LLSLURL::isSLURL(slurl)) - { - if (LLSLURL::isSLURLCommand(slurl)) - { - LLStartUp::sSLURLCommand = slurl; - } - else - { - LLURLSimString::setString(slurl); - } - } + LLSLURL start_slurl(clp.getOption("slurl")[0]); + LLStartUp::setStartSLURL(start_slurl); } const LLControlVariable* skinfolder = gSavedSettings.getControl("SkinCurrent"); @@ -2245,18 +2203,10 @@ bool LLAppViewer::initConfiguration() // don't call anotherInstanceRunning() when doing URL handoff, as // it relies on checking a marker file which will not work when running // out of different directories - std::string slurl; - if (!LLStartUp::sSLURLCommand.empty()) - { - slurl = LLStartUp::sSLURLCommand; - } - else if (LLURLSimString::parse()) - { - slurl = LLURLSimString::getURL(); - } - if (!slurl.empty()) + + if (LLStartUp::getStartSLURL().isValid()) { - if (sendURLToOtherInstance(slurl)) + if (sendURLToOtherInstance(LLStartUp::getStartSLURL().getSLURLString())) { // successfully handed off URL to existing instance, exit return false; @@ -2312,9 +2262,9 @@ bool LLAppViewer::initConfiguration() // need to do this here - need to have initialized global settings first std::string nextLoginLocation = gSavedSettings.getString( "NextLoginLocation" ); - if ( nextLoginLocation.length() ) + if ( !nextLoginLocation.empty() ) { - LLURLSimString::setString( nextLoginLocation ); + LLStartUp::setStartSLURL(LLSLURL(nextLoginLocation)); }; gLastRunVersion = gSavedSettings.getString("LastRunVersion"); @@ -2535,7 +2485,7 @@ void LLAppViewer::writeSystemInfo() // The user is not logged on yet, but record the current grid choice login url // which may have been the intended grid. This can b - gDebugInfo["GridName"] = LLViewerLogin::getInstance()->getGridLabel(); + gDebugInfo["GridName"] = LLGridManager::getInstance()->getGridLabel(); // *FIX:Mani - move this ddown in llappviewerwin32 #ifdef LL_WINDOWS @@ -3886,7 +3836,7 @@ void LLAppViewer::sendLogoutRequest() gLogoutMaxTime = LOGOUT_REQUEST_TIME; mLogoutRequestSent = TRUE; - gVoiceClient->leaveChannel(); + LLVoiceClient::getInstance()->leaveChannel(); //Set internal status variables and marker files gLogoutInProgress = TRUE; @@ -4306,7 +4256,7 @@ void LLAppViewer::launchUpdater() #endif // *TODO change userserver to be grid on both viewer and sim, since // userserver no longer exists. - query_map["userserver"] = LLViewerLogin::getInstance()->getGridLabel(); + query_map["userserver"] = LLGridManager::getInstance()->getGridLabel(); query_map["channel"] = gSavedSettings.getString("VersionChannelName"); // *TODO constantize this guy // *NOTE: This URL is also used in win_setup/lldownloader.cpp @@ -4319,10 +4269,10 @@ void LLAppViewer::launchUpdater() LLAppViewer::sUpdaterInfo = new LLAppViewer::LLUpdaterInfo() ; // if a sim name was passed in via command line parameter (typically through a SLURL) - if ( LLURLSimString::sInstance.mSimString.length() ) + if ( LLStartUp::getStartSLURL().getType() == LLSLURL::LOCATION ) { // record the location to start at next time - gSavedSettings.setString( "NextLoginLocation", LLURLSimString::sInstance.mSimString ); + gSavedSettings.setString( "NextLoginLocation", LLStartUp::getStartSLURL().getSLURLString()); }; #if LL_WINDOWS diff --git a/indra/newview/llappviewer.h b/indra/newview/llappviewer.h index a915b7fa50..27e8bec1d5 100644 --- a/indra/newview/llappviewer.h +++ b/indra/newview/llappviewer.h @@ -191,7 +191,6 @@ private: bool initThreads(); // Initialize viewer threads, return false on failure. bool initConfiguration(); // Initialize settings from the command line/config file. - void initGridChoice(); bool initCache(); // Initialize local client cache. diff --git a/indra/newview/llappviewerlinux.cpp b/indra/newview/llappviewerlinux.cpp index d34bcb4a68..78b0f7ba83 100644 --- a/indra/newview/llappviewerlinux.cpp +++ b/indra/newview/llappviewerlinux.cpp @@ -604,7 +604,7 @@ void LLAppViewerLinux::handleCrashReporting(bool reportFreeze) {cmd.c_str(), ask_dialog, "-user", - (char*)LLViewerLogin::getInstance()->getGridLabel().c_str(), + (char*)LLGridManager::getInstance()->getGridLabel().c_str(), "-name", LLAppViewer::instance()->getSecondLifeTitle().c_str(), NULL}; diff --git a/indra/newview/llappviewermacosx.cpp b/indra/newview/llappviewermacosx.cpp index 80d9b14345..58d28883c6 100644 --- a/indra/newview/llappviewermacosx.cpp +++ b/indra/newview/llappviewermacosx.cpp @@ -44,7 +44,6 @@ #include "llviewernetwork.h" #include "llviewercontrol.h" #include "llmd5.h" -#include "llurlsimstring.h" #include "llfloaterworldmap.h" #include "llurldispatcher.h" #include diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp index c85c72837c..48a85dc73f 100644 --- a/indra/newview/llavataractions.cpp +++ b/indra/newview/llavataractions.cpp @@ -282,7 +282,7 @@ bool LLAvatarActions::isCalling(const LLUUID &id) //static bool LLAvatarActions::canCall() { - return LLVoiceClient::voiceEnabled() && gVoiceClient->voiceWorking(); + return LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); } // static diff --git a/indra/newview/llbottomtray.cpp b/indra/newview/llbottomtray.cpp index 41bee540fc..9824f59358 100644 --- a/indra/newview/llbottomtray.cpp +++ b/indra/newview/llbottomtray.cpp @@ -319,7 +319,7 @@ void LLBottomTray::onChange(EStatusType status, const std::string &channelURI, b // skipped to avoid button blinking if (status != STATUS_JOINING && status!= STATUS_LEFT_CHANNEL) { - mSpeakBtn->setFlyoutBtnEnabled(LLVoiceClient::voiceEnabled() && gVoiceClient->voiceWorking()); + mSpeakBtn->setFlyoutBtnEnabled(LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking()); } } @@ -489,7 +489,7 @@ BOOL LLBottomTray::postBuild() mSpeakBtn->setShowToolTip( getString("VoiceControlBtnToolTip") ); // Registering Chat Bar to receive Voice client status change notifications. - gVoiceClient->addObserver(this); + LLVoiceClient::getInstance()->addObserver(this); mObjectDefaultWidthMap[RS_BUTTON_GESTURES] = mGesturePanel->getRect().getWidth(); mObjectDefaultWidthMap[RS_BUTTON_MOVEMENT] = mMovementPanel->getRect().getWidth(); diff --git a/indra/newview/llcallfloater.cpp b/indra/newview/llcallfloater.cpp index 4ea3c61ab2..215f1b95aa 100644 --- a/indra/newview/llcallfloater.cpp +++ b/indra/newview/llcallfloater.cpp @@ -133,9 +133,9 @@ LLCallFloater::~LLCallFloater() // Don't use LLVoiceClient::getInstance() here // singleton MAY have already been destroyed. - if(gVoiceClient) + if(LLVoiceClient::getInstance()) { - gVoiceClient->removeObserver(this); + LLVoiceClient::getInstance()->removeObserver(this); } LLTransientFloaterMgr::getInstance()->removeControlView(this); } @@ -191,7 +191,7 @@ void LLCallFloater::draw() // Seems this is a problem somewhere in Voice Client (LLVoiceClient::participantAddedEvent) // onChange(); - bool is_moderator_muted = gVoiceClient->getIsModeratorMuted(gAgentID); + bool is_moderator_muted = LLVoiceClient::getInstance()->getIsModeratorMuted(gAgentID); if (mIsModeratorMutedVoice != is_moderator_muted) { @@ -209,7 +209,6 @@ void LLCallFloater::draw() void LLCallFloater::onChange() { if (NULL == mParticipants) return; - updateParticipantsVoiceState(); // Add newly joined participants. @@ -239,11 +238,11 @@ void LLCallFloater::updateSession() LLVoiceChannel* voice_channel = LLVoiceChannel::getCurrentVoiceChannel(); if (voice_channel) { - lldebugs << "Current voice channel: " << voice_channel->getSessionID() << llendl; + LL_DEBUGS("Voice") << "Current voice channel: " << voice_channel->getSessionID() << LL_ENDL; if (mSpeakerManager && voice_channel->getSessionID() == mSpeakerManager->getSessionID()) { - lldebugs << "Speaker manager is already set for session: " << voice_channel->getSessionID() << llendl; + LL_DEBUGS("Voice") << "Speaker manager is already set for session: " << voice_channel->getSessionID() << LL_ENDL; return; } else @@ -253,7 +252,6 @@ void LLCallFloater::updateSession() } const LLUUID& session_id = voice_channel ? voice_channel->getSessionID() : LLUUID::null; - lldebugs << "Set speaker manager for session: " << session_id << llendl; LLIMModel::LLIMSession* im_session = LLIMModel::getInstance()->findIMSession(session_id); if (im_session) @@ -293,7 +291,7 @@ void LLCallFloater::updateSession() { // by default let show nearby chat participants mSpeakerManager = LLLocalSpeakerMgr::getInstance(); - lldebugs << "Set DEFAULT speaker manager" << llendl; + LL_DEBUGS("Voice") << "Set DEFAULT speaker manager" << LL_ENDL; mVoiceType = VC_LOCAL_CHAT; } @@ -472,16 +470,15 @@ void LLCallFloater::updateAgentModeratorState() static void get_voice_participants_uuids(uuid_vec_t& speakers_uuids) { // Get a list of participants from VoiceClient - LLVoiceClient::participantMap *voice_map = gVoiceClient->getParticipantList(); - if (voice_map) + std::set participants; + LLVoiceClient::getInstance()->getParticipantList(participants); + + for (std::set::const_iterator iter = participants.begin(); + iter != participants.end(); ++iter) { - for (LLVoiceClient::participantMap::const_iterator iter = voice_map->begin(); - iter != voice_map->end(); ++iter) - { - LLUUID id = (*iter).second->mAvatarID; - speakers_uuids.push_back(id); - } + speakers_uuids.push_back(*iter); } + } void LLCallFloater::initParticipantsVoiceState() @@ -557,7 +554,7 @@ void LLCallFloater::updateParticipantsVoiceState() uuid_vec_t::iterator speakers_iter = std::find(speakers_uuids.begin(), speakers_uuids.end(), participant_id); - lldebugs << "processing speaker: " << item->getAvatarName() << ", " << item->getAvatarId() << llendl; + LL_DEBUGS("Voice") << "processing speaker: " << item->getAvatarName() << ", " << item->getAvatarId() << LL_ENDL; // If an avatarID assigned to a panel is found in a speakers list // obtained from VoiceClient we assign the JOINED status to the owner @@ -727,7 +724,7 @@ void LLCallFloater::connectToChannel(LLVoiceChannel* channel) void LLCallFloater::onVoiceChannelStateChanged(const LLVoiceChannel::EState& old_state, const LLVoiceChannel::EState& new_state) { // check is voice operational and if it doesn't work hide VCP (EXT-4397) - if(LLVoiceClient::voiceEnabled() && gVoiceClient->voiceWorking()) + if(LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking()) { updateState(new_state); } diff --git a/indra/newview/llcallingcard.cpp b/indra/newview/llcallingcard.cpp index 79a2631c31..1a6c11fa73 100644 --- a/indra/newview/llcallingcard.cpp +++ b/indra/newview/llcallingcard.cpp @@ -700,6 +700,7 @@ void LLAvatarTracker::processNotify(LLMessageSystem* msg, bool online) args["FIRST"] = first; args["LAST"] = last; } + } } else diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index 71e7ae7061..1e404d611c 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -640,20 +640,19 @@ void LLChatHistory::appendMessage(const LLChat& chat, const LLSD &args, const LL if ( chat.mSourceType == CHAT_SOURCE_OBJECT && chat.mFromID.notNull()) { // for object IMs, create a secondlife:///app/objectim SLapp - std::string url = LLSLURL::buildCommand("objectim", chat.mFromID, ""); + std::string url = LLSLURL("objectim", chat.mFromID, "").getSLURLString(); url += "?name=" + chat.mFromName; url += "&owner=" + args["owner_id"].asString(); std::string slurl = args["slurl"].asString(); if (slurl.empty()) { - LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosAgent(chat.mPosAgent); - if (region) - { - S32 x, y, z; - LLSLURL::globalPosToXYZ(LLVector3d(chat.mPosAgent), x, y, z); - slurl = region->getName() + llformat("/%d/%d/%d", x, y, z); - } + LLViewerRegion *region = LLWorld::getInstance()->getRegionFromPosAgent(chat.mPosAgent); + if(region) + { + LLSLURL region_slurl(region->getName(), chat.mPosAgent); + slurl = region_slurl.getLocationString(); + } } url += "&slurl=" + slurl; diff --git a/indra/newview/llcurrencyuimanager.cpp b/indra/newview/llcurrencyuimanager.cpp index be6c15eab4..fd3df359bd 100644 --- a/indra/newview/llcurrencyuimanager.cpp +++ b/indra/newview/llcurrencyuimanager.cpp @@ -284,7 +284,7 @@ void LLCurrencyUIManager::Impl::startTransaction(TransactionType type, static std::string transactionURI; if (transactionURI.empty()) { - transactionURI = LLViewerLogin::getInstance()->getHelperURI() + "currency.php"; + transactionURI = LLGridManager::getInstance()->getHelperURI() + "currency.php"; } delete mTransaction; diff --git a/indra/newview/llfloaterabout.cpp b/indra/newview/llfloaterabout.cpp index ef69f39ad2..56bc4a7933 100644 --- a/indra/newview/llfloaterabout.cpp +++ b/indra/newview/llfloaterabout.cpp @@ -266,8 +266,18 @@ LLSD LLFloaterAbout::getInfo() info["J2C_VERSION"] = LLImageJ2C::getEngineInfo(); bool want_fullname = true; info["AUDIO_DRIVER_VERSION"] = gAudiop ? LLSD(gAudiop->getDriverName(want_fullname)) : LLSD(); - info["VIVOX_VERSION"] = gVoiceClient ? gVoiceClient->getAPIVersion() : LLTrans::getString("NotConnected"); - + if(LLVoiceClient::getInstance()->voiceEnabled()) + { + LLVoiceVersionInfo version = LLVoiceClient::getInstance()->getVersion(); + std::ostringstream version_string; + version_string << version.serverType << " " << version.serverVersion << std::endl; + info["VOICE_VERSION"] = version_string.str(); + } + else + { + info["VOICE_VERSION"] = LLTrans::getString("NotConnected"); + } + // TODO: Implement media plugin version query info["QT_WEBKIT_VERSION"] = "4.6 (version number hard-coded)"; diff --git a/indra/newview/llfloaterbuyland.cpp b/indra/newview/llfloaterbuyland.cpp index d37bc01885..464b3a7214 100644 --- a/indra/newview/llfloaterbuyland.cpp +++ b/indra/newview/llfloaterbuyland.cpp @@ -830,7 +830,7 @@ void LLFloaterBuyLandUI::updateNames() else { mParcelSellerName = - LLSLURL::buildCommand("agent", parcelp->getOwnerID(), "inspect"); + LLSLURL("agent", parcelp->getOwnerID(), "inspect").getSLURLString(); } } @@ -859,7 +859,7 @@ void LLFloaterBuyLandUI::startTransaction(TransactionType type, const LLXMLRPCVa static std::string transaction_uri; if (transaction_uri.empty()) { - transaction_uri = LLViewerLogin::getInstance()->getHelperURI() + "landtool.php"; + transaction_uri = LLGridManager::getInstance()->getHelperURI() + "landtool.php"; } const char* method; diff --git a/indra/newview/llfloaterchat.cpp b/indra/newview/llfloaterchat.cpp index cdb9b8edb8..882d12f68e 100644 --- a/indra/newview/llfloaterchat.cpp +++ b/indra/newview/llfloaterchat.cpp @@ -166,7 +166,7 @@ void add_timestamped_line(LLViewerTextEditor* edit, LLChat chat, const LLColor4& if (chat.mSourceType == CHAT_SOURCE_AGENT && chat.mFromID != LLUUID::null) { - chat.mURL = LLSLURL::buildCommand("agent", chat.mFromID, "inspect"); + chat.mURL = LLSLURL("agent", chat.mFromID, "inspect").getSLURLString(); } // If the chat line has an associated url, link it up to the name. diff --git a/indra/newview/llfloaterchatterbox.cpp b/indra/newview/llfloaterchatterbox.cpp index 774caaec90..a15cef7ea4 100644 --- a/indra/newview/llfloaterchatterbox.cpp +++ b/indra/newview/llfloaterchatterbox.cpp @@ -318,7 +318,7 @@ LLFloaterChatterBox* LLFloaterChatterBox::getInstance() //static LLFloater* LLFloaterChatterBox::getCurrentVoiceFloater() { - if (!LLVoiceClient::voiceEnabled()) + if (!LLVoiceClient::getInstance()->voiceEnabled()) { return NULL; } diff --git a/indra/newview/llfloaterevent.cpp b/indra/newview/llfloaterevent.cpp index 97ebab3425..84a5c3dc77 100644 --- a/indra/newview/llfloaterevent.cpp +++ b/indra/newview/llfloaterevent.cpp @@ -193,7 +193,7 @@ void LLFloaterEvent::processEventInfoReply(LLMessageSystem *msg, void **) floater->mTBCategory->setText(floater->mEventInfo.mCategoryStr); floater->mTBDate->setText(floater->mEventInfo.mTimeStr); floater->mTBDesc->setText(floater->mEventInfo.mDesc); - floater->mTBRunBy->setText(LLSLURL::buildCommand("agent", floater->mEventInfo.mRunByID, "inspect")); + floater->mTBRunBy->setText(LLSLURL("agent", floater->mEventInfo.mRunByID, "inspect").getSLURLString()); floater->mTBDuration->setText(llformat("%d:%.2d", floater->mEventInfo.mDuration / 60, floater->mEventInfo.mDuration % 60)); diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp index 02c83dcd09..25d3f971b5 100644 --- a/indra/newview/llfloaterland.cpp +++ b/indra/newview/llfloaterland.cpp @@ -42,7 +42,6 @@ #include "llnotificationsutil.h" #include "llparcel.h" #include "message.h" -#include "lluserauth.h" #include "llagent.h" #include "llbutton.h" @@ -804,7 +803,7 @@ void LLPanelLandGeneral::refreshNames() else { // Figure out the owner's name - owner = LLSLURL::buildCommand("agent", parcel->getOwnerID(), "inspect"); + owner = LLSLURL("agent", parcel->getOwnerID(), "inspect").getSLURLString(); } if(LLParcel::OS_LEASE_PENDING == parcel->getOwnershipStatus()) @@ -816,7 +815,7 @@ void LLPanelLandGeneral::refreshNames() std::string group; if (!parcel->getGroupID().isNull()) { - group = LLSLURL::buildCommand("group", parcel->getGroupID(), "inspect"); + group = LLSLURL("group", parcel->getGroupID(), "inspect").getSLURLString(); } mTextGroup->setText(group); @@ -825,9 +824,9 @@ void LLPanelLandGeneral::refreshNames() const LLUUID& auth_buyer_id = parcel->getAuthorizedBuyerID(); if(auth_buyer_id.notNull()) { - std::string name; - name = LLSLURL::buildCommand("agent", auth_buyer_id, "inspect"); - mSaleInfoForSale2->setTextArg("[BUYER]", name); + std::string name; + name = LLSLURL("agent", auth_buyer_id, "inspect").getSLURLString(); + mSaleInfoForSale2->setTextArg("[BUYER]", name); } else { diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 764a0dc954..3a73037ae8 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -595,7 +595,7 @@ void LLFloaterPreference::onBtnOK() llinfos << "Can't close preferences!" << llendl; } - LLPanelLogin::refreshLocation( false ); + LLPanelLogin::updateLocationCombo( false ); } // static @@ -612,7 +612,7 @@ void LLFloaterPreference::onBtnApply( ) apply(); saveSettings(); - LLPanelLogin::refreshLocation( false ); + LLPanelLogin::updateLocationCombo( false ); } // static diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index 3758cbe74f..8c219cb3fd 100644 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -2922,8 +2922,7 @@ bool LLDispatchEstateUpdateInfo::operator()( LLUUID owner_id(strings[1]); regionp->setOwner(owner_id); // Update estate owner name in UI - std::string owner_name = - LLSLURL::buildCommand("agent", owner_id, "inspect"); + std::string owner_name = LLSLURL("agent", owner_id, "inspect").getSLURLString(); panel->setOwnerName(owner_name); U32 estate_id = strtoul(strings[2].c_str(), NULL, 10); diff --git a/indra/newview/llfloaterreporter.cpp b/indra/newview/llfloaterreporter.cpp index b42b34835d..f7c8855bf6 100644 --- a/indra/newview/llfloaterreporter.cpp +++ b/indra/newview/llfloaterreporter.cpp @@ -126,7 +126,9 @@ void LLFloaterReporter::processRegionInfo(LLMessageSystem* msg) // virtual BOOL LLFloaterReporter::postBuild() { - childSetText("abuse_location_edit", LLAgentUI::buildSLURL()); + LLSLURL slurl; + LLAgentUI::buildSLURL(slurl); + childSetText("abuse_location_edit", slurl.getSLURLString()); enableControls(TRUE); @@ -280,7 +282,6 @@ void LLFloaterReporter::getObjectInfo(const LLUUID& object_id) { object_owner.append("Unknown"); } - setFromAvatar(mObjectID, object_owner); } else @@ -325,7 +326,8 @@ void LLFloaterReporter::setFromAvatar(const LLUUID& avatar_id, const std::string mAbuserID = mObjectID = avatar_id; mOwnerName = avatar_name; - std::string avatar_link = LLSLURL::buildCommand("agent", mObjectID, "inspect"); + std::string avatar_link = + LLSLURL("agent", mObjectID, "inspect").getSLURLString(); childSetText("owner_name", avatar_link); childSetText("object_name", avatar_name); childSetText("abuser_name_edit", avatar_name); @@ -504,7 +506,7 @@ void LLFloaterReporter::setPickedObjectProperties(const std::string& object_name { childSetText("object_name", object_name); std::string owner_link = - LLSLURL::buildCommand("agent", owner_id, "inspect"); + LLSLURL("agent", owner_id, "inspect").getSLURLString(); childSetText("owner_name", owner_link); childSetText("abuser_name_edit", owner_name); mAbuserID = owner_id; @@ -566,7 +568,7 @@ LLSD LLFloaterReporter::gatherReport() mCopyrightWarningSeen = FALSE; std::ostringstream summary; - if (!LLViewerLogin::getInstance()->isInProductionGrid()) + if (!LLGridManager::getInstance()->isInProductionGrid()) { summary << "Preview "; } diff --git a/indra/newview/llfloatersnapshot.cpp b/indra/newview/llfloatersnapshot.cpp index adac9861d4..aae379afe2 100644 --- a/indra/newview/llfloatersnapshot.cpp +++ b/indra/newview/llfloatersnapshot.cpp @@ -1123,7 +1123,7 @@ void LLSnapshotLivePreview::saveWeb(std::string url) void LLSnapshotLivePreview::regionNameCallback(std::string url, LLSD body, const std::string& name, S32 x, S32 y, S32 z) { - body["slurl"] = LLSLURL::buildSLURL(name, x, y, z); + body["slurl"] = LLSLURL(name, LLVector3d(x, y, z)).getSLURLString(); LLHTTPClient::post(url, body, new LLSendWebResponder()); diff --git a/indra/newview/llfloatervoicedevicesettings.cpp b/indra/newview/llfloatervoicedevicesettings.cpp index 638c9f1b8c..63365e3461 100644 --- a/indra/newview/llfloatervoicedevicesettings.cpp +++ b/indra/newview/llfloatervoicedevicesettings.cpp @@ -64,9 +64,6 @@ LLPanelVoiceDeviceSettings::LLPanelVoiceDeviceSettings() // grab "live" mic volume level mMicVolume = gSavedSettings.getF32("AudioLevelMic"); - // ask for new device enumeration - // now do this in onOpen() instead... - //gVoiceClient->refreshDeviceLists(); } LLPanelVoiceDeviceSettings::~LLPanelVoiceDeviceSettings() @@ -105,7 +102,7 @@ void LLPanelVoiceDeviceSettings::draw() refresh(); // let user know that volume indicator is not yet available - bool is_in_tuning_mode = gVoiceClient->inTuningMode(); + bool is_in_tuning_mode = LLVoiceClient::getInstance()->inTuningMode(); childSetVisible("wait_text", !is_in_tuning_mode); LLPanel::draw(); @@ -113,7 +110,7 @@ void LLPanelVoiceDeviceSettings::draw() if (is_in_tuning_mode) { const S32 num_bars = 5; - F32 voice_power = gVoiceClient->tuningGetEnergy() / LLVoiceClient::OVERDRIVEN_POWER_LEVEL; + F32 voice_power = LLVoiceClient::getInstance()->tuningGetEnergy() / LLVoiceClient::OVERDRIVEN_POWER_LEVEL; S32 discrete_power = llmin(num_bars, llfloor(voice_power * (F32)num_bars + 0.1f)); for(S32 power_bar_idx = 0; power_bar_idx < num_bars; power_bar_idx++) @@ -194,13 +191,13 @@ void LLPanelVoiceDeviceSettings::refresh() LLSlider* volume_slider = getChild("mic_volume_slider"); // set mic volume tuning slider based on last mic volume setting F32 current_volume = (F32)volume_slider->getValue().asReal(); - gVoiceClient->tuningSetMicVolume(current_volume); + LLVoiceClient::getInstance()->tuningSetMicVolume(current_volume); // Fill in popup menus mCtrlInputDevices = getChild("voice_input_device"); mCtrlOutputDevices = getChild("voice_output_device"); - if(!gVoiceClient->deviceSettingsAvailable()) + if(!LLVoiceClient::getInstance()->deviceSettingsAvailable()) { // The combo boxes are disabled, since we can't get the device settings from the daemon just now. // Put the currently set default (ONLY) in the box, and select it. @@ -219,17 +216,16 @@ void LLPanelVoiceDeviceSettings::refresh() } else if (!mDevicesUpdated) { - LLVoiceClient::deviceList *devices; - - LLVoiceClient::deviceList::iterator iter; + LLVoiceDeviceList::const_iterator iter; if(mCtrlInputDevices) { mCtrlInputDevices->removeall(); mCtrlInputDevices->add( getString("default_text"), ADD_BOTTOM ); - devices = gVoiceClient->getCaptureDevices(); - for(iter=devices->begin(); iter != devices->end(); iter++) + for(iter=LLVoiceClient::getInstance()->getCaptureDevices().begin(); + iter != LLVoiceClient::getInstance()->getCaptureDevices().end(); + iter++) { mCtrlInputDevices->add( *iter, ADD_BOTTOM ); } @@ -245,8 +241,8 @@ void LLPanelVoiceDeviceSettings::refresh() mCtrlOutputDevices->removeall(); mCtrlOutputDevices->add( getString("default_text"), ADD_BOTTOM ); - devices = gVoiceClient->getRenderDevices(); - for(iter=devices->begin(); iter != devices->end(); iter++) + for(iter= LLVoiceClient::getInstance()->getRenderDevices().begin(); + iter != LLVoiceClient::getInstance()->getRenderDevices().end(); iter++) { mCtrlOutputDevices->add( *iter, ADD_BOTTOM ); } @@ -268,37 +264,34 @@ void LLPanelVoiceDeviceSettings::initialize() mDevicesUpdated = FALSE; // ask for new device enumeration - gVoiceClient->refreshDeviceLists(); + LLVoiceClient::getInstance()->refreshDeviceLists(); // put voice client in "tuning" mode - gVoiceClient->tuningStart(); + LLVoiceClient::getInstance()->tuningStart(); LLVoiceChannel::suspend(); } void LLPanelVoiceDeviceSettings::cleanup() { - if (gVoiceClient) - { - gVoiceClient->tuningStop(); - } + LLVoiceClient::getInstance()->tuningStop(); LLVoiceChannel::resume(); } // static void LLPanelVoiceDeviceSettings::onCommitInputDevice(LLUICtrl* ctrl, void* user_data) { - if(gVoiceClient) + if(LLVoiceClient::getInstance()) { - gVoiceClient->setCaptureDevice(ctrl->getValue().asString()); + LLVoiceClient::getInstance()->setCaptureDevice(ctrl->getValue().asString()); } } // static void LLPanelVoiceDeviceSettings::onCommitOutputDevice(LLUICtrl* ctrl, void* user_data) { - if(gVoiceClient) + if(LLVoiceClient::getInstance()) { - gVoiceClient->setRenderDevice(ctrl->getValue().asString()); + LLVoiceClient::getInstance()->setRenderDevice(ctrl->getValue().asString()); } } diff --git a/indra/newview/llfloaterworldmap.cpp b/indra/newview/llfloaterworldmap.cpp index f17c9765b9..896c410e32 100644 --- a/indra/newview/llfloaterworldmap.cpp +++ b/indra/newview/llfloaterworldmap.cpp @@ -461,7 +461,7 @@ void LLFloaterWorldMap::draw() childSetEnabled("Teleport", (BOOL)tracking_status); // childSetEnabled("Clear", (BOOL)tracking_status); childSetEnabled("Show Destination", (BOOL)tracking_status || LLWorldMap::getInstance()->isTracking()); - childSetEnabled("copy_slurl", (mSLURL.size() > 0) ); + childSetEnabled("copy_slurl", (mSLURL.isValid()) ); setMouseOpaque(TRUE); getDragHandle()->setMouseOpaque(TRUE); @@ -660,14 +660,8 @@ void LLFloaterWorldMap::updateLocation() childSetValue("location", agent_sim_name); // Figure out where user is - LLVector3d agentPos = gAgent.getPositionGlobal(); - - S32 agent_x = llround( (F32)fmod( agentPos.mdV[VX], (F64)REGION_WIDTH_METERS ) ); - S32 agent_y = llround( (F32)fmod( agentPos.mdV[VY], (F64)REGION_WIDTH_METERS ) ); - S32 agent_z = llround( (F32)agentPos.mdV[VZ] ); - // Set the current SLURL - mSLURL = LLSLURL::buildSLURL(agent_sim_name, agent_x, agent_y, agent_z); + mSLURL = LLSLURL(agent_sim_name, gAgent.getPositionGlobal()); } } @@ -694,18 +688,15 @@ void LLFloaterWorldMap::updateLocation() } childSetValue("location", sim_name); - - F32 region_x = (F32)fmod( pos_global.mdV[VX], (F64)REGION_WIDTH_METERS ); - F32 region_y = (F32)fmod( pos_global.mdV[VY], (F64)REGION_WIDTH_METERS ); // simNameFromPosGlobal can fail, so don't give the user an invalid SLURL if ( gotSimName ) { - mSLURL = LLSLURL::buildSLURL(sim_name, llround(region_x), llround(region_y), llround((F32)pos_global.mdV[VZ])); + mSLURL = LLSLURL(sim_name, pos_global); } else { // Empty SLURL will disable the "Copy SLURL to clipboard" button - mSLURL = ""; + mSLURL = LLSLURL(); } } } @@ -1174,7 +1165,7 @@ void LLFloaterWorldMap::onClearBtn() mTrackedStatus = LLTracker::TRACKING_NOTHING; LLTracker::stopTracking((void *)(intptr_t)TRUE); LLWorldMap::getInstance()->cancelTracking(); - mSLURL = ""; // Clear the SLURL since it's invalid + mSLURL = LLSLURL(); // Clear the SLURL since it's invalid mSetToUserPosition = TRUE; // Revert back to the current user position } @@ -1197,10 +1188,10 @@ void LLFloaterWorldMap::onClickTeleportBtn() void LLFloaterWorldMap::onCopySLURL() { - getWindow()->copyTextToClipboard(utf8str_to_wstring(mSLURL)); + getWindow()->copyTextToClipboard(utf8str_to_wstring(mSLURL.getSLURLString())); LLSD args; - args["SLURL"] = mSLURL; + args["SLURL"] = mSLURL.getSLURLString(); LLNotificationsUtil::add("CopySLURL", args); } diff --git a/indra/newview/llfloaterworldmap.h b/indra/newview/llfloaterworldmap.h index 00f5e788fb..52809ff830 100644 --- a/indra/newview/llfloaterworldmap.h +++ b/indra/newview/llfloaterworldmap.h @@ -43,6 +43,7 @@ #include "llhudtext.h" #include "llmapimagetype.h" #include "lltracker.h" +#include "llslurl.h" class LLEventInfo; class LLFriendObserver; @@ -183,7 +184,7 @@ private: LLTracker::ETrackingStatus mTrackedStatus; std::string mTrackedSimName; std::string mTrackedAvatarName; - std::string mSLURL; + LLSLURL mSLURL; }; extern LLFloaterWorldMap* gFloaterWorldMap; diff --git a/indra/newview/llgrouplist.cpp b/indra/newview/llgrouplist.cpp index 8a056f836f..03a47b5983 100644 --- a/indra/newview/llgrouplist.cpp +++ b/indra/newview/llgrouplist.cpp @@ -287,7 +287,7 @@ bool LLGroupList::onContextMenuItemEnable(const LLSD& userdata) return gAgent.getGroupID() != selected_group_id; if (userdata.asString() == "call") - return real_group_selected && LLVoiceClient::voiceEnabled()&&gVoiceClient->voiceWorking(); + return real_group_selected && LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); return real_group_selected; } diff --git a/indra/newview/llimfloater.cpp b/indra/newview/llimfloater.cpp index 3ec8d11fb0..9704c7537a 100644 --- a/indra/newview/llimfloater.cpp +++ b/indra/newview/llimfloater.cpp @@ -58,7 +58,6 @@ #include "lltransientfloatermgr.h" #include "llinventorymodel.h" #include "llrootview.h" - #include "llspeakers.h" diff --git a/indra/newview/llimpanel.cpp b/indra/newview/llimpanel.cpp index 4bdf5f42dc..0e3b78df7f 100644 --- a/indra/newview/llimpanel.cpp +++ b/indra/newview/llimpanel.cpp @@ -300,7 +300,7 @@ void LLFloaterIMPanel::onVolumeChange(LLUICtrl* source, void* user_data) LLFloaterIMPanel* floaterp = (LLFloaterIMPanel*)user_data; if (floaterp) { - gVoiceClient->setUserVolume(floaterp->mOtherParticipantUUID, (F32)source->getValue().asReal()); + LLVoiceClient::getInstance()->setUserVolume(floaterp->mOtherParticipantUUID, (F32)source->getValue().asReal()); } } @@ -312,7 +312,7 @@ void LLFloaterIMPanel::draw() BOOL enable_connect = (region && region->getCapability("ChatSessionRequest") != "") && mSessionInitialized - && LLVoiceClient::voiceEnabled() + && LLVoiceClient::getInstance()->voiceEnabled() && mCallBackEnabled; // hide/show start call and end call buttons @@ -320,8 +320,8 @@ void LLFloaterIMPanel::draw() if (!voice_channel) return; - childSetVisible("end_call_btn", LLVoiceClient::voiceEnabled() && voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED); - childSetVisible("start_call_btn", LLVoiceClient::voiceEnabled() && voice_channel->getState() < LLVoiceChannel::STATE_CALL_STARTED); + childSetVisible("end_call_btn", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->getState() >= LLVoiceChannel::STATE_CALL_STARTED); + childSetVisible("start_call_btn", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->getState() < LLVoiceChannel::STATE_CALL_STARTED); childSetEnabled("start_call_btn", enable_connect); childSetEnabled("send_btn", !childGetValue("chat_editor").asString().empty()); @@ -384,11 +384,11 @@ void LLFloaterIMPanel::draw() else { // refresh volume and mute checkbox - childSetVisible("speaker_volume", LLVoiceClient::voiceEnabled() && voice_channel->isActive()); - childSetValue("speaker_volume", gVoiceClient->getUserVolume(mOtherParticipantUUID)); + childSetVisible("speaker_volume", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->isActive()); + childSetValue("speaker_volume", LLVoiceClient::getInstance()->getUserVolume(mOtherParticipantUUID)); childSetValue("mute_btn", LLMuteList::getInstance()->isMuted(mOtherParticipantUUID, LLMute::flagVoiceChat)); - childSetVisible("mute_btn", LLVoiceClient::voiceEnabled() && voice_channel->isActive()); + childSetVisible("mute_btn", LLVoiceClient::getInstance()->voiceEnabled() && voice_channel->isActive()); } LLFloater::draw(); } diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index e0f155a6a9..5201f92dbc 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -344,13 +344,13 @@ LLIMModel::LLIMSession::~LLIMSession() mSpeakers = NULL; // End the text IM session if necessary - if(gVoiceClient && mOtherParticipantID.notNull()) + if(LLVoiceClient::getInstance() && mOtherParticipantID.notNull()) { switch(mType) { case IM_NOTHING_SPECIAL: case IM_SESSION_P2P_INVITE: - gVoiceClient->endUserIMSession(mOtherParticipantID); + LLVoiceClient::getInstance()->endUserIMSession(mOtherParticipantID); break; default: @@ -925,7 +925,7 @@ void LLIMModel::sendMessage(const std::string& utf8_text, if((offline == IM_OFFLINE) && (LLVoiceClient::getInstance()->isOnlineSIP(other_participant_id))) { // User is online through the OOW connector, but not with a regular viewer. Try to send the message via SLVoice. - sent = gVoiceClient->sendTextMessage(other_participant_id, utf8_text); + sent = LLVoiceClient::getInstance()->sendTextMessage(other_participant_id, utf8_text); } if(!sent) @@ -1717,7 +1717,7 @@ void LLOutgoingCallDialog::show(const LLSD& key) // skipping "You will now be reconnected to nearby" in notification when call is ended by disabling voice, // so no reconnection to nearby chat happens (EXT-4397) - bool voice_works = LLVoiceClient::voiceEnabled() && gVoiceClient->voiceWorking(); + bool voice_works = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); std::string reconnect_nearby = voice_works ? LLTrans::getString("reconnect_nearby") : std::string(); childSetTextArg("nearby", "[RECONNECT_NEARBY]", reconnect_nearby); @@ -1843,7 +1843,11 @@ LLCallDialog(payload) void LLIncomingCallDialog::onLifetimeExpired() { // check whether a call is valid or not - if (LLVoiceClient::getInstance()->findSession(mPayload["caller_id"].asUUID())) + LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mPayload["session_id"].asUUID()); + if(channelp && + (channelp->getState() != LLVoiceChannel::STATE_NO_CHANNEL_INFO) && + (channelp->getState() != LLVoiceChannel::STATE_ERROR) && + (channelp->getState() != LLVoiceChannel::STATE_HUNG_UP)) { // restart notification's timer if call is still valid mLifetimeTimer.start(); @@ -2077,10 +2081,10 @@ void LLIncomingCallDialog::processCallResponse(S32 response) { if (type == IM_SESSION_P2P_INVITE) { - if(gVoiceClient) + if(LLVoiceClient::getInstance()) { std::string s = mPayload["session_handle"].asString(); - gVoiceClient->declineInvite(s); + LLVoiceClient::getInstance()->declineInvite(s); } } else @@ -2168,11 +2172,8 @@ bool inviteUserResponse(const LLSD& notification, const LLSD& response) { if (type == IM_SESSION_P2P_INVITE) { - if(gVoiceClient) - { - std::string s = payload["session_handle"].asString(); - gVoiceClient->declineInvite(s); - } + std::string s = payload["session_handle"].asString(); + LLVoiceClient::getInstance()->declineInvite(s); } else { @@ -3078,7 +3079,7 @@ public: return; } - if(!LLVoiceClient::voiceEnabled()) + if(!LLVoiceClient::getInstance()->voiceEnabled()) { // Don't display voice invites unless the user has voice enabled. return; diff --git a/indra/newview/llinspectavatar.cpp b/indra/newview/llinspectavatar.cpp index 94ea236757..1299324105 100644 --- a/indra/newview/llinspectavatar.cpp +++ b/indra/newview/llinspectavatar.cpp @@ -536,8 +536,7 @@ void LLInspectAvatar::toggleSelectedVoice(bool enabled) void LLInspectAvatar::updateVolumeSlider() { - - bool voice_enabled = gVoiceClient->getVoiceEnabled(mAvatarID); + bool voice_enabled = LLVoiceClient::getInstance()->getVoiceEnabled(mAvatarID); // Do not display volume slider and mute button if it // is ourself or we are not in a voice channel together @@ -567,6 +566,7 @@ void LLInspectAvatar::updateVolumeSlider() volume_slider->setEnabled( !is_muted ); F32 volume; + if (is_muted) { // it's clearer to display their volume as zero @@ -575,7 +575,7 @@ void LLInspectAvatar::updateVolumeSlider() else { // actual volume - volume = gVoiceClient->getUserVolume(mAvatarID); + volume = LLVoiceClient::getInstance()->getUserVolume(mAvatarID); } volume_slider->setValue( (F64)volume ); } @@ -604,7 +604,7 @@ void LLInspectAvatar::onClickMuteVolume() void LLInspectAvatar::onVolumeChange(const LLSD& data) { F32 volume = (F32)data.asReal(); - gVoiceClient->setUserVolume(mAvatarID, volume); + LLVoiceClient::getInstance()->setUserVolume(mAvatarID, volume); } void LLInspectAvatar::nameUpdatedCallback( diff --git a/indra/newview/llinspectobject.cpp b/indra/newview/llinspectobject.cpp index 91cbbbf430..a2b5ffbac4 100644 --- a/indra/newview/llinspectobject.cpp +++ b/indra/newview/llinspectobject.cpp @@ -480,7 +480,7 @@ void LLInspectObject::updateCreator(LLSelectNode* nodep) // Objects cannot be created by a group, so use agent URL format LLUUID creator_id = nodep->mPermissions->getCreator(); std::string creator_url = - LLSLURL::buildCommand("agent", creator_id, "about"); + LLSLURL("agent", creator_id, "about").getSLURLString(); args["[CREATOR]"] = creator_url; // created by one user but owned by another @@ -490,12 +490,12 @@ void LLInspectObject::updateCreator(LLSelectNode* nodep) if (group_owned) { owner_id = nodep->mPermissions->getGroup(); - owner_url = LLSLURL::buildCommand("group", owner_id, "about"); + owner_url = LLSLURL("group", owner_id, "about").getSLURLString(); } else { owner_id = nodep->mPermissions->getOwner(); - owner_url = LLSLURL::buildCommand("agent", owner_id, "about"); + owner_url = LLSLURL("agent", owner_id, "about").getSLURLString(); } args["[OWNER]"] = owner_url; diff --git a/indra/newview/llinspectremoteobject.cpp b/indra/newview/llinspectremoteobject.cpp index 66e4a1bf66..97ff771658 100644 --- a/indra/newview/llinspectremoteobject.cpp +++ b/indra/newview/llinspectremoteobject.cpp @@ -176,11 +176,11 @@ void LLInspectRemoteObject::update() { if (mGroupOwned) { - owner = LLSLURL::buildCommand("group", mOwnerID, "about"); + owner = LLSLURL("group", mOwnerID, "about").getSLURLString(); } else { - owner = LLSLURL::buildCommand("agent", mOwnerID, "about"); + owner = LLSLURL("agent", mOwnerID, "about").getSLURLString(); } } else diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index d1cc0ae936..b6202c6a8c 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -1329,7 +1329,6 @@ bool LLInventoryModel::fetchDescendentsOf(const LLUUID& folder_id) return cat->fetchDescendents(); } - void LLInventoryModel::cache( const LLUUID& parent_folder_id, const LLUUID& agent_id) diff --git a/indra/newview/lllandmarkactions.cpp b/indra/newview/lllandmarkactions.cpp index 7336efb62a..539ca97a93 100644 --- a/indra/newview/lllandmarkactions.cpp +++ b/indra/newview/lllandmarkactions.cpp @@ -299,7 +299,7 @@ void LLLandmarkActions::getSLURLfromPosGlobal(const LLVector3d& global_pos, slur bool gotSimName = LLWorldMap::getInstance()->simNameFromPosGlobal(global_pos, sim_name); if (gotSimName) { - std::string slurl = LLSLURL::buildSLURLfromPosGlobal(sim_name, global_pos, escaped); + std::string slurl = LLSLURL(sim_name, global_pos).getSLURLString(); cb(slurl); return; @@ -351,7 +351,7 @@ void LLLandmarkActions::onRegionResponseSLURL(slurl_callback_t cb, bool gotSimName = LLWorldMap::getInstance()->simNameFromPosGlobal(global_pos, sim_name); if (gotSimName) { - slurl = LLSLURL::buildSLURLfromPosGlobal(sim_name, global_pos, escaped); + slurl = LLSLURL(sim_name, global_pos).getSLURLString(); } else { diff --git a/indra/newview/lllocationinputctrl.cpp b/indra/newview/lllocationinputctrl.cpp index ad2e594b49..3b4a4a1344 100644 --- a/indra/newview/lllocationinputctrl.cpp +++ b/indra/newview/lllocationinputctrl.cpp @@ -668,9 +668,8 @@ void LLLocationInputCtrl::onLocationPrearrange(const LLSD& data) value["item_type"] = TELEPORT_HISTORY; value["global_pos"] = result->mGlobalPos.getValue(); std::string region_name = result->mTitle.substr(0, result->mTitle.find(',')); - //TODO*: add slurl to teleportitem or parse region name from title - value["tooltip"] = LLSLURL::buildSLURLfromPosGlobal(region_name, - result->mGlobalPos, false); + //TODO*: add Surl to teleportitem or parse region name from title + value["tooltip"] = LLSLURL(region_name, result->mGlobalPos).getSLURLString(); add(result->getTitle(), value); } result = std::find_if(result + 1, th_items.end(), boost::bind( @@ -1011,7 +1010,9 @@ void LLLocationInputCtrl::changeLocationPresentation() if(!mTextEntry->hasSelection() && text == mHumanReadableLocation) { //needs unescaped one - mTextEntry->setText(LLAgentUI::buildSLURL(false)); + LLSLURL slurl; + LLAgentUI::buildSLURL(slurl, false); + mTextEntry->setText(slurl.getSLURLString()); mTextEntry->selectAll(); mMaturityIcon->setVisible(FALSE); diff --git a/indra/newview/llloginhandler.cpp b/indra/newview/llloginhandler.cpp index e3817eecc4..4e0a7594ba 100644 --- a/indra/newview/llloginhandler.cpp +++ b/indra/newview/llloginhandler.cpp @@ -35,13 +35,14 @@ #include "llloginhandler.h" // viewer includes +#include "llsecapi.h" #include "lllogininstance.h" // to check if logged in yet #include "llpanellogin.h" // save_password_to_disk() #include "llstartup.h" // getStartupState() -#include "llurlsimstring.h" +#include "llslurl.h" #include "llviewercontrol.h" // gSavedSettings #include "llviewernetwork.h" // EGridInfo -#include "llviewerwindow.h" // getWindow() +#include "llviewerwindow.h" // getWindow() // library includes #include "llmd5.h" @@ -60,109 +61,33 @@ bool LLLoginHandler::parseDirectLogin(std::string url) LLURI uri(url); parse(uri.queryMap()); - if (/*mWebLoginKey.isNull() ||*/ - mFirstName.empty() || - mLastName.empty()) - { - return false; - } - else - { - return true; - } + // NOTE: Need to add direct login as per identity evolution + return true; } - void LLLoginHandler::parse(const LLSD& queryMap) { - //mWebLoginKey = queryMap["web_login_key"].asUUID(); - mFirstName = queryMap["first_name"].asString(); - mLastName = queryMap["last_name"].asString(); - EGridInfo grid_choice = GRID_INFO_NONE; - if (queryMap["grid"].asString() == "aditi") - { - grid_choice = GRID_INFO_ADITI; - } - else if (queryMap["grid"].asString() == "agni") - { - grid_choice = GRID_INFO_AGNI; - } - else if (queryMap["grid"].asString() == "siva") - { - grid_choice = GRID_INFO_SIVA; - } - else if (queryMap["grid"].asString() == "damballah") - { - grid_choice = GRID_INFO_DAMBALLAH; - } - else if (queryMap["grid"].asString() == "durga") - { - grid_choice = GRID_INFO_DURGA; - } - else if (queryMap["grid"].asString() == "shakti") - { - grid_choice = GRID_INFO_SHAKTI; - } - else if (queryMap["grid"].asString() == "soma") - { - grid_choice = GRID_INFO_SOMA; - } - else if (queryMap["grid"].asString() == "ganga") - { - grid_choice = GRID_INFO_GANGA; - } - else if (queryMap["grid"].asString() == "vaak") - { - grid_choice = GRID_INFO_VAAK; - } - else if (queryMap["grid"].asString() == "uma") - { - grid_choice = GRID_INFO_UMA; - } - else if (queryMap["grid"].asString() == "mohini") - { - grid_choice = GRID_INFO_MOHINI; - } - else if (queryMap["grid"].asString() == "yami") - { - grid_choice = GRID_INFO_YAMI; - } - else if (queryMap["grid"].asString() == "nandi") + if (queryMap.has("grid")) { - grid_choice = GRID_INFO_NANDI; + LLGridManager::getInstance()->setGridChoice(queryMap["grid"].asString()); } - else if (queryMap["grid"].asString() == "mitra") - { - grid_choice = GRID_INFO_MITRA; - } - else if (queryMap["grid"].asString() == "radha") - { - grid_choice = GRID_INFO_RADHA; - } - else if (queryMap["grid"].asString() == "ravi") - { - grid_choice = GRID_INFO_RAVI; - } - else if (queryMap["grid"].asString() == "aruna") - { - grid_choice = GRID_INFO_ARUNA; - } - - if(grid_choice != GRID_INFO_NONE) - { - LLViewerLogin::getInstance()->setGridChoice(grid_choice); - } - + + std::string startLocation = queryMap["location"].asString(); - + if (startLocation == "specify") { - LLURLSimString::setString(queryMap["region"].asString()); + LLStartUp::setStartSLURL(LLSLURL(LLGridManager::getInstance()->getGridLoginID(), + queryMap["region"].asString())); } - else if (!startLocation.empty()) // "last" or "home" or ??? (let LLURLSimString figure it out) + else if (startLocation == "home") { - LLURLSimString::setString(startLocation); + LLStartUp::setStartSLURL(LLSLURL(LLSLURL::SIM_LOCATION_HOME)); + } + else if (startLocation == "last") + { + LLStartUp::setStartSLURL(LLSLURL(LLSLURL::SIM_LOCATION_LAST)); } } @@ -220,40 +145,65 @@ bool LLLoginHandler::handle(const LLSD& tokens, return true; } - std::string password = query_map["password"].asString(); - - if (!password.empty()) + if (LLStartUp::getStartupState() < STATE_LOGIN_CLEANUP) //on splash page { - gSavedSettings.setBOOL("RememberPassword", TRUE); - - if (password.substr(0,3) != "$1$") - { - LLMD5 pass((unsigned char*)password.c_str()); - char md5pass[33]; /* Flawfinder: ignore */ - pass.hex_digest(md5pass); - std::string hashed_password = ll_safe_string(md5pass, 32); - LLStartUp::savePasswordToDisk(hashed_password); - } + // as the login page may change from grid to grid, as well as + // things like username/password/etc, we simply refresh the + // login page to make sure everything is set up correctly + LLPanelLogin::loadLoginPage(); + LLStartUp::setStartupState( STATE_LOGIN_CLEANUP ); } - + return true; +} - if (LLStartUp::getStartupState() < STATE_LOGIN_CLEANUP) //on splash page - { - if (!mFirstName.empty() || !mLastName.empty()) - { - // Fill in the name, and maybe the password - LLPanelLogin::setFields(mFirstName, mLastName, password); - } - //if (mWebLoginKey.isNull()) - //{ - // LLPanelLogin::loadLoginPage(); - //} - //else - //{ - // LLStartUp::setStartupState( STATE_LOGIN_CLEANUP ); - //} - LLStartUp::setStartupState( STATE_LOGIN_CLEANUP ); - } - return true; + +// Initialize the credentials +// If the passed in URL contains login info, parse +// that into a credential and web login key. Otherwise +// check the command line. If the command line +// does not contain any login creds, load the last saved +// ones from the protected credential store. +// This always returns with a credential structure set in the +// login handler +LLPointer LLLoginHandler::initializeLoginInfo() +{ + LLPointer result = NULL; + // so try to load it from the UserLoginInfo + result = loadSavedUserLoginInfo(); + if (result.isNull()) + { + result = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); + } + + return result; +} + + +LLPointer LLLoginHandler::loadSavedUserLoginInfo() +{ + // load the saved user login info into a LLCredential. + // perhaps this should be moved. + LLSD cmd_line_login = gSavedSettings.getLLSD("UserLoginInfo"); + if (cmd_line_login.size() == 3) + { + + LLMD5 pass((unsigned char*)cmd_line_login[2].asString().c_str()); + char md5pass[33]; /* Flawfinder: ignore */ + pass.hex_digest(md5pass); + LLSD identifier = LLSD::emptyMap(); + identifier["type"] = "agent"; + identifier["first_name"] = cmd_line_login[0]; + identifier["last_name"] = cmd_line_login[1]; + + LLSD authenticator = LLSD::emptyMap(); + authenticator["type"] = "hash"; + authenticator["algorithm"] = "md5"; + authenticator["secret"] = md5pass; + // yuck, we'll fix this with mani's changes. + gSavedSettings.setBOOL("AutoLogin", TRUE); + return gSecAPIHandler->createCredential(LLGridManager::getInstance()->getGrid(), + identifier, authenticator); + } + return NULL; } diff --git a/indra/newview/llloginhandler.h b/indra/newview/llloginhandler.h index ac4648761b..c15b998c91 100644 --- a/indra/newview/llloginhandler.h +++ b/indra/newview/llloginhandler.h @@ -34,6 +34,7 @@ #define LLLOGINHANDLER_H #include "llcommandhandler.h" +#include "llsecapi.h" class LLLoginHandler : public LLCommandHandler { @@ -46,19 +47,15 @@ class LLLoginHandler : public LLCommandHandler // secondlife:///app/login?first=Bob&last=Dobbs bool parseDirectLogin(std::string url); - std::string getFirstName() const { return mFirstName; } - std::string getLastName() const { return mLastName; } - // Web-based login unsupported //LLUUID getWebLoginKey() const { return mWebLoginKey; } + LLPointer loadSavedUserLoginInfo(); + LLPointer initializeLoginInfo(); + private: void parse(const LLSD& queryMap); -private: - std::string mFirstName; - std::string mLastName; - //LLUUID mWebLoginKey; }; extern LLLoginHandler gLoginHandler; diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp index 24c72c65ce..0459c85050 100644 --- a/indra/newview/lllogininstance.cpp +++ b/indra/newview/lllogininstance.cpp @@ -48,13 +48,16 @@ // newview #include "llviewernetwork.h" #include "llviewercontrol.h" -#include "llurlsimstring.h" +#include "llslurl.h" +#include "llstartup.h" #include "llfloaterreg.h" #include "llnotifications.h" #include "llwindow.h" #if LL_LINUX || LL_SOLARIS #include "lltrans.h" #endif +#include "llsecapi.h" +#include "llstartup.h" static const char * const TOS_REPLY_PUMP = "lllogininstance_tos_callback"; static const char * const TOS_LISTENER_NAME = "lllogininstance_tos"; @@ -83,14 +86,14 @@ LLLoginInstance::~LLLoginInstance() { } -void LLLoginInstance::connect(const LLSD& credentials) +void LLLoginInstance::connect(LLPointer credentials) { std::vector uris; - LLViewerLogin::getInstance()->getLoginURIs(uris); + LLGridManager::getInstance()->getLoginURIs(uris); connect(uris.front(), credentials); } -void LLLoginInstance::connect(const std::string& uri, const LLSD& credentials) +void LLLoginInstance::connect(const std::string& uri, LLPointer credentials) { mAttemptComplete = false; // Reset attempt complete at this point! constructAuthParams(credentials); @@ -102,7 +105,7 @@ void LLLoginInstance::reconnect() // Sort of like connect, only using the pre-existing // request params. std::vector uris; - LLViewerLogin::getInstance()->getLoginURIs(uris); + LLGridManager::getInstance()->getLoginURIs(uris); mLoginModule->connect(uris.front(), mRequestData); } @@ -118,7 +121,7 @@ LLSD LLLoginInstance::getResponse() return mResponseData; } -void LLLoginInstance::constructAuthParams(const LLSD& credentials) +void LLLoginInstance::constructAuthParams(LLPointer user_credential) { // Set up auth request options. //#define LL_MINIMIAL_REQUESTED_OPTIONS @@ -145,8 +148,10 @@ void LLLoginInstance::constructAuthParams(const LLSD& credentials) requested_options.append("adult_compliant"); //requested_options.append("inventory-targets"); requested_options.append("buddy-list"); + requested_options.append("newuser-config"); requested_options.append("ui-config"); #endif + requested_options.append("voice-config"); requested_options.append("tutorial_setting"); requested_options.append("login-flags"); requested_options.append("global-textures"); @@ -155,20 +160,18 @@ void LLLoginInstance::constructAuthParams(const LLSD& credentials) gSavedSettings.setBOOL("UseDebugMenus", TRUE); requested_options.append("god-connect"); } + + // (re)initialize the request params with creds. + LLSD request_params = user_credential->getLoginParams(); char hashed_mac_string[MD5HEX_STR_SIZE]; /* Flawfinder: ignore */ LLMD5 hashed_mac; - hashed_mac.update( gMACAddress, MAC_ADDRESS_BYTES ); + unsigned char MACAddress[MAC_ADDRESS_BYTES]; + LLUUID::getNodeID(MACAddress); + hashed_mac.update( MACAddress, MAC_ADDRESS_BYTES ); hashed_mac.finalize(); hashed_mac.hex_digest(hashed_mac_string); - - // prepend "$1$" to the password to indicate its the md5'd version. - std::string dpasswd("$1$"); - dpasswd.append(credentials["passwd"].asString()); - - // (re)initialize the request params with creds. - LLSD request_params(credentials); - request_params["passwd"] = dpasswd; + request_params["start"] = construct_start_string(); request_params["skipoptional"] = mSkipOptionalUpdate; request_params["agree_to_tos"] = false; // Always false here. Set true in @@ -247,6 +250,15 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event) LLSD data(LLSD::emptyMap()); data["message"] = message_response; data["reply_pump"] = TOS_REPLY_PUMP; + if(response.has("error_code")) + { + data["error_code"] = response["error_code"]; + } + if(response.has("certificate")) + { + data["certificate"] = response["certificate"]; + } + LLFloaterReg::showInstance("message_critical", data); LLEventPumps::instance().obtain(TOS_REPLY_PUMP) .listen(TOS_LISTENER_NAME, @@ -454,20 +466,31 @@ bool LLLoginInstance::updateDialogCallback(const LLSD& notification, const LLSD& std::string construct_start_string() { std::string start; - if (LLURLSimString::parse()) + LLSLURL start_slurl = LLStartUp::getStartSLURL(); + switch(start_slurl.getType()) { - // a startup URL was specified - std::string unescaped_start = + case LLSLURL::LOCATION: + { + // a startup URL was specified + LLVector3 position = start_slurl.getPosition(); + std::string unescaped_start = STRINGIZE( "uri:" - << LLURLSimString::sInstance.mSimName << "&" - << LLURLSimString::sInstance.mX << "&" - << LLURLSimString::sInstance.mY << "&" - << LLURLSimString::sInstance.mZ); - start = xml_escape_string(unescaped_start); - } - else - { - start = gSavedSettings.getString("LoginLocation"); + << start_slurl.getRegion() << "&" + << position[VX] << "&" + << position[VY] << "&" + << position[VZ]); + start = xml_escape_string(unescaped_start); + break; + } + case LLSLURL::HOME_LOCATION: + { + start = "home"; + break; + } + default: + { + start = "last"; + } } return start; } diff --git a/indra/newview/lllogininstance.h b/indra/newview/lllogininstance.h index c8704eddb4..44271bb75e 100644 --- a/indra/newview/lllogininstance.h +++ b/indra/newview/lllogininstance.h @@ -36,6 +36,7 @@ #include "lleventdispatcher.h" #include #include +#include "llsecapi.h" class LLLogin; class LLEventStream; class LLNotificationsInterface; @@ -48,8 +49,8 @@ public: LLLoginInstance(); ~LLLoginInstance(); - void connect(const LLSD& credential); // Connect to the current grid choice. - void connect(const std::string& uri, const LLSD& credential); // Connect to the given uri. + void connect(LLPointer credentials); // Connect to the current grid choice. + void connect(const std::string& uri, LLPointer credentials); // Connect to the given uri. void reconnect(); // reconnect using the current credentials. void disconnect(); @@ -81,7 +82,7 @@ public: void setUpdaterLauncher(const UpdaterLauncherCallback& ulc) { mUpdaterLauncher = ulc; } private: - void constructAuthParams(const LLSD& credentials); + void constructAuthParams(LLPointer user_credentials); void updateApp(bool mandatory, const std::string& message); bool updateDialogCallback(const LLSD& notification, const LLSD& response); diff --git a/indra/newview/llnavigationbar.cpp b/indra/newview/llnavigationbar.cpp index e11df06d86..c3d0f1bfc2 100644 --- a/indra/newview/llnavigationbar.cpp +++ b/indra/newview/llnavigationbar.cpp @@ -52,7 +52,6 @@ #include "llsearchcombobox.h" #include "llsidetray.h" #include "llslurl.h" -#include "llurlsimstring.h" #include "llurlregistry.h" #include "llurldispatcher.h" #include "llviewerinventory.h" @@ -508,29 +507,34 @@ void LLNavigationBar::onLocationSelection() std::string region_name; LLVector3 local_coords(128, 128, 0); - S32 x = 0, y = 0, z = 0; // Is the typed location a SLURL? - if (LLSLURL::isSLURL(typed_location)) + LLSLURL slurl = LLSLURL(typed_location); + if (slurl.getType() == LLSLURL::LOCATION) { - // Yes. Extract region name and local coordinates from it. - if (LLURLSimString::parse(LLSLURL::stripProtocol(typed_location), ®ion_name, &x, &y, &z)) - local_coords.set(x, y, z); - else - return; + region_name = slurl.getRegion(); + local_coords = slurl.getPosition(); } - // we have to do this check after previous, because LLUrlRegistry contains handlers for slurl too - //but we need to know whether typed_location is a simple http url. - else if (LLUrlRegistry::instance().isUrl(typed_location)) + else if(!slurl.isValid()) { + // we have to do this check after previous, because LLUrlRegistry contains handlers for slurl too + // but we need to know whether typed_location is a simple http url. + if (LLUrlRegistry::instance().isUrl(typed_location)) + { // display http:// URLs in the media browser, or // anything else is sent to the search floater LLWeb::loadURL(typed_location); return; + } + else + { + // assume that an user has typed the {region name} or possible {region_name, parcel} + region_name = typed_location.substr(0,typed_location.find(',')); + } } else { - // assume that an user has typed the {region name} or possible {region_name, parcel} - region_name = typed_location.substr(0,typed_location.find(',')); + // was an app slurl, home, whatever. Bail + return; } // Resolve the region name to its global coordinates. @@ -562,7 +566,7 @@ void LLNavigationBar::onTeleportFinished(const LLVector3d& global_agent_pos) */ LLAgentUI::buildLocationString(location, LLAgentUI::LOCATION_FORMAT_NO_MATURITY, gAgent.getPosAgentFromGlobal(global_agent_pos)); - std::string tooltip (LLSLURL::buildSLURLfromPosGlobal(gAgent.getRegion()->getName(), global_agent_pos, false)); + std::string tooltip (LLSLURL(gAgent.getRegion()->getName(), global_agent_pos).getSLURLString()); LLLocationHistoryItem item (location, global_agent_pos, tooltip,TYPED_REGION_SLURL);// we can add into history only TYPED location @@ -651,7 +655,7 @@ void LLNavigationBar::onRegionNameResponse( LLVector3d region_pos = from_region_handle(region_handle); LLVector3d global_pos = region_pos + (LLVector3d) local_coords; - llinfos << "Teleporting to: " << LLSLURL::buildSLURLfromPosGlobal(region_name, global_pos, false) << llendl; + llinfos << "Teleporting to: " << LLSLURL(region_name, global_pos).getSLURLString() << llendl; gAgent.teleportViaLocation(global_pos); } diff --git a/indra/newview/lloutputmonitorctrl.cpp b/indra/newview/lloutputmonitorctrl.cpp index d6d48a4ead..197a0ef728 100644 --- a/indra/newview/lloutputmonitorctrl.cpp +++ b/indra/newview/lloutputmonitorctrl.cpp @@ -142,7 +142,7 @@ void LLOutputMonitorCtrl::draw() // Copied from llmediaremotectrl.cpp // *TODO: Give the LLOutputMonitorCtrl an agent-id to monitor, then - // call directly into gVoiceClient to ask if that agent-id is muted, is + // call directly into LLVoiceClient::getInstance() to ask if that agent-id is muted, is // speaking, and what power. This avoids duplicating data, which can get // out of sync. const F32 LEVEL_0 = LLVoiceClient::OVERDRIVEN_POWER_LEVEL / 3.f; @@ -151,14 +151,14 @@ void LLOutputMonitorCtrl::draw() if (getVisible() && mAutoUpdate && !mIsMuted && mSpeakerId.notNull()) { - setPower(gVoiceClient->getCurrentPower(mSpeakerId)); + setPower(LLVoiceClient::getInstance()->getCurrentPower(mSpeakerId)); if(mIsAgentControl) { - setIsTalking(gVoiceClient->getUserPTTState()); + setIsTalking(LLVoiceClient::getInstance()->getUserPTTState()); } else { - setIsTalking(gVoiceClient->getIsSpeaking(mSpeakerId)); + setIsTalking(LLVoiceClient::getInstance()->getIsSpeaking(mSpeakerId)); } } diff --git a/indra/newview/lloutputmonitorctrl.h b/indra/newview/lloutputmonitorctrl.h index b7454a5066..3a83da67e2 100644 --- a/indra/newview/lloutputmonitorctrl.h +++ b/indra/newview/lloutputmonitorctrl.h @@ -143,7 +143,7 @@ private: LLPointer mImageLevel2; LLPointer mImageLevel3; - /** whether to deal with gVoiceClient directly */ + /** whether to deal with LLVoiceClient::getInstance() directly */ bool mAutoUpdate; /** uuid of a speaker being monitored */ diff --git a/indra/newview/lloverlaybar.cpp b/indra/newview/lloverlaybar.cpp index 67e048885f..3f1b23ba14 100644 --- a/indra/newview/lloverlaybar.cpp +++ b/indra/newview/lloverlaybar.cpp @@ -258,7 +258,7 @@ void LLOverlayBar::refresh() { // update "remotes" childSetVisible("media_remote_container", TRUE); - childSetVisible("voice_remote_container", LLVoiceClient::voiceEnabled()); + childSetVisible("voice_remote_container", LLVoiceClient::getInstance()->voiceEnabled()); childSetVisible("state_buttons", TRUE); } diff --git a/indra/newview/llpanelavatar.cpp b/indra/newview/llpanelavatar.cpp index a0ba2f739b..b554af66f0 100644 --- a/indra/newview/llpanelavatar.cpp +++ b/indra/newview/llpanelavatar.cpp @@ -163,7 +163,7 @@ BOOL LLPanelAvatarNotes::postBuild() resetControls(); resetData(); - gVoiceClient->addObserver((LLVoiceClientStatusObserver*)this); + LLVoiceClient::getInstance()->addObserver((LLVoiceClientStatusObserver*)this); return TRUE; } @@ -374,7 +374,7 @@ void LLPanelAvatarNotes::onChange(EStatusType status, const std::string &channel return; } - childSetEnabled("call", LLVoiceClient::voiceEnabled() && gVoiceClient->voiceWorking()); + childSetEnabled("call", LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking()); } void LLPanelAvatarNotes::setAvatarId(const LLUUID& id) @@ -518,7 +518,7 @@ BOOL LLPanelAvatarProfile::postBuild() pic = getChild("real_world_pic"); pic->setFallbackImageName("default_profile_picture.j2c"); - gVoiceClient->addObserver((LLVoiceClientStatusObserver*)this); + LLVoiceClient::getInstance()->addObserver((LLVoiceClientStatusObserver*)this); resetControls(); resetData(); @@ -809,7 +809,7 @@ void LLPanelAvatarProfile::onChange(EStatusType status, const std::string &chann return; } - childSetEnabled("call", LLVoiceClient::voiceEnabled() && gVoiceClient->voiceWorking()); + childSetEnabled("call", LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking()); } void LLPanelAvatarProfile::setAvatarId(const LLUUID& id) diff --git a/indra/newview/llpanelgroup.cpp b/indra/newview/llpanelgroup.cpp index c00b6a4147..716166a945 100644 --- a/indra/newview/llpanelgroup.cpp +++ b/indra/newview/llpanelgroup.cpp @@ -201,7 +201,7 @@ BOOL LLPanelGroup::postBuild() mJoinText = panel_general->getChild("join_cost_text"); } - gVoiceClient->addObserver(this); + LLVoiceClient::getInstance()->addObserver(this); return TRUE; } @@ -322,7 +322,7 @@ void LLPanelGroup::onChange(EStatusType status, const std::string &channelURI, b return; } - childSetEnabled("btn_call", LLVoiceClient::voiceEnabled() && gVoiceClient->voiceWorking()); + childSetEnabled("btn_call", LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking()); } void LLPanelGroup::notifyObservers() diff --git a/indra/newview/llpanelimcontrolpanel.cpp b/indra/newview/llpanelimcontrolpanel.cpp index c34f0633b9..709bb83fe4 100644 --- a/indra/newview/llpanelimcontrolpanel.cpp +++ b/indra/newview/llpanelimcontrolpanel.cpp @@ -81,7 +81,8 @@ void LLPanelChatControlPanel::onVoiceChannelStateChanged(const LLVoiceChannel::E void LLPanelChatControlPanel::updateCallButton() { - bool voice_enabled = LLVoiceClient::voiceEnabled() && gVoiceClient->voiceWorking(); + // hide/show call button + bool voice_enabled = LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); LLIMModel::LLIMSession* session = LLIMModel::getInstance()->findIMSession(mSessionId); @@ -124,7 +125,7 @@ BOOL LLPanelChatControlPanel::postBuild() childSetAction("end_call_btn", boost::bind(&LLPanelChatControlPanel::onEndCallButtonClicked, this)); childSetAction("voice_ctrls_btn", boost::bind(&LLPanelChatControlPanel::onOpenVoiceControlsClicked, this)); - gVoiceClient->addObserver(this); + LLVoiceClient::getInstance()->addObserver(this); return TRUE; } diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp index ee4dcc44fe..42e4b397db 100644 --- a/indra/newview/llpanellogin.cpp +++ b/indra/newview/llpanellogin.cpp @@ -51,11 +51,12 @@ #include "llfocusmgr.h" #include "lllineeditor.h" #include "llnotificationsutil.h" +#include "llsecapi.h" #include "llstartup.h" #include "lltextbox.h" #include "llui.h" #include "lluiconstants.h" -#include "llurlsimstring.h" +#include "llslurl.h" #include "llversioninfo.h" #include "llviewerhelp.h" #include "llviewertexturelist.h" @@ -77,6 +78,7 @@ #pragma warning(disable: 4355) // 'this' used in initializer list #endif // LL_WINDOWS +#include "llsdserialize.h" #define USE_VIEWER_AUTH 0 const S32 BLACK_BORDER_HEIGHT = 160; @@ -104,7 +106,6 @@ public: LLLoginRefreshHandler gLoginRefreshHandler; - // helper class that trys to download a URL from a web site and calls a method // on parent class indicating if the web server is working or not class LLIamHereLogin : public LLHTTPClient::Responder @@ -153,10 +154,6 @@ namespace { boost::intrusive_ptr< LLIamHereLogin > gResponsePtr = 0; }; -void set_start_location(LLUICtrl* ctrl, void* data) -{ - LLURLSimString::setString(ctrl->getValue().asString()); -} //--------------------------------------------------------------------------- // Public methods @@ -187,6 +184,7 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, delete LLPanelLogin::sInstance; } + mPasswordModified = FALSE; LLPanelLogin::sInstance = this; // add to front so we are the bottom-most child @@ -213,10 +211,7 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, } #if !USE_VIEWER_AUTH - childSetPrevalidate("first_name_edit", LLTextValidate::validateASCIIPrintableNoSpace); - childSetPrevalidate("last_name_edit", LLTextValidate::validateASCIIPrintableNoSpace); - - childSetCommitCallback("password_edit", mungePassword, this); + childSetPrevalidate("username_edit", LLTextValidate::validateASCIIPrintableNoPipe); getChild("password_edit")->setKeystrokeCallback(onPassKey, this); // change z sort of clickable text to be behind buttons @@ -228,27 +223,19 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, LLComboBox* combo = getChild("start_location_combo"); - std::string sim_string = LLURLSimString::sInstance.mSimString; - if(sim_string.empty()) - { - LLURLSimString::setString(gSavedSettings.getString("LoginLocation")); - sim_string = LLURLSimString::sInstance.mSimString; - } - - if (!sim_string.empty()) + if(LLStartUp::getStartSLURL().getType() != LLSLURL::LOCATION) { - // Replace "" with this region name - combo->remove(2); - combo->add( sim_string ); - combo->setTextEntry(sim_string); - combo->setCurrentByIndex( 2 ); + LLSLURL slurl(gSavedSettings.getString("LoginLocation")); + LLStartUp::setStartSLURL(slurl); } - - combo->setCommitCallback( &set_start_location, NULL ); + updateLocationCombo(false); + + combo->setCommitCallback(onSelectLocation, NULL); LLComboBox* server_choice_combo = sInstance->getChild("server_combo"); server_choice_combo->setCommitCallback(onSelectServer, NULL); server_choice_combo->setFocusLostCallback(boost::bind(onServerComboLostFocus, _1)); + updateServerCombo(); childSetAction("connect_btn", onClickConnect, this); @@ -304,17 +291,10 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect, // kick off a request to grab the url manually gResponsePtr = LLIamHereLogin::build( this ); - std::string login_page = gSavedSettings.getString("LoginPage"); - if (login_page.empty()) - { - login_page = getString( "real_url" ); - } - LLHTTPClient::head( login_page, gResponsePtr ); -#if !USE_VIEWER_AUTH - // Initialize visibility (and don't force visibility - use prefs) - refreshLocation( false ); -#endif + LLHTTPClient::head( LLGridManager::getInstance()->getLoginPage(), gResponsePtr ); + + updateLocationCombo(false); } @@ -378,21 +358,6 @@ void LLPanelLogin::setSiteIsAlive( bool alive ) } } -void LLPanelLogin::mungePassword(LLUICtrl* caller, void* user_data) -{ - LLPanelLogin* self = (LLPanelLogin*)user_data; - LLLineEditor* editor = (LLLineEditor*)caller; - std::string password = editor->getText(); - - // Re-md5 if we've changed at all - if (password != self->mIncomingPassword) - { - LLMD5 pass((unsigned char *)password.c_str()); - char munged_password[MD5HEX_STR_SIZE]; - pass.hex_digest(munged_password); - self->mMungedPassword = munged_password; - } -} LLPanelLogin::~LLPanelLogin() { @@ -499,14 +464,14 @@ void LLPanelLogin::giveFocus() if( sInstance ) { // Grab focus and move cursor to first blank input field - std::string first = sInstance->childGetText("first_name_edit"); + std::string username = sInstance->childGetText("username_edit"); std::string pass = sInstance->childGetText("password_edit"); - BOOL have_first = !first.empty(); + BOOL have_username = !username.empty(); BOOL have_pass = !pass.empty(); LLLineEditor* edit = NULL; - if (have_first && !have_pass) + if (have_username && !have_pass) { // User saved his name but not his password. Move // focus to password field. @@ -515,7 +480,7 @@ void LLPanelLogin::giveFocus() else { // User doesn't have a name, so start there. - edit = sInstance->getChild("first_name_edit"); + edit = sInstance->getChild("username_edit"); } if (edit) @@ -537,8 +502,8 @@ void LLPanelLogin::showLoginWidgets() // *TODO: Append all the usual login parameters, like first_login=Y etc. std::string splash_screen_url = sInstance->getString("real_url"); web_browser->navigateTo( splash_screen_url, "text/html" ); - LLUICtrl* first_name_edit = sInstance->getChild("first_name_edit"); - first_name_edit->setFocus(TRUE); + LLUICtrl* username_edit = sInstance->getChild("username_edit"); + username_edit->setFocus(TRUE); } // static @@ -560,77 +525,120 @@ void LLPanelLogin::show(const LLRect &rect, } // static -void LLPanelLogin::setFields(const std::string& firstname, - const std::string& lastname, - const std::string& password) +void LLPanelLogin::setFields(LLPointer credential, + BOOL remember) { if (!sInstance) { llwarns << "Attempted fillFields with no login view shown" << llendl; return; } + LL_INFOS("Credentials") << "Setting login fields to " << *credential << LL_ENDL; - sInstance->childSetText("first_name_edit", firstname); - sInstance->childSetText("last_name_edit", lastname); - - // Max "actual" password length is 16 characters. - // Hex digests are always 32 characters. - if (password.length() == 32) + LLSD identifier = credential->getIdentifier(); + if((std::string)identifier["type"] == "agent") + { + sInstance->childSetText("username_edit", (std::string)identifier["first_name"] + " " + + (std::string)identifier["last_name"]); + } + else if((std::string)identifier["type"] == "account") + { + sInstance->childSetText("username_edit", (std::string)identifier["account_name"]); + } + else + { + sInstance->childSetText("username_edit", std::string()); + } + // if the password exists in the credential, set the password field with + // a filler to get some stars + LLSD authenticator = credential->getAuthenticator(); + LL_INFOS("Credentials") << "Setting authenticator field " << authenticator["type"].asString() << LL_ENDL; + if(authenticator.isMap() && + authenticator.has("secret") && + (authenticator["secret"].asString().size() > 0)) { + // This is a MD5 hex digest of a password. // We don't actually use the password input field, // fill it with MAX_PASSWORD characters so we get a // nice row of asterixes. const std::string filler("123456789!123456"); - sInstance->childSetText("password_edit", filler); - sInstance->mIncomingPassword = filler; - sInstance->mMungedPassword = password; + sInstance->childSetText("password_edit", std::string("123456789!123456")); } else { - // this is a normal text password - sInstance->childSetText("password_edit", password); - sInstance->mIncomingPassword = password; - LLMD5 pass((unsigned char *)password.c_str()); - char munged_password[MD5HEX_STR_SIZE]; - pass.hex_digest(munged_password); - sInstance->mMungedPassword = munged_password; + sInstance->childSetText("password_edit", std::string()); } + sInstance->childSetValue("remember_check", remember); } // static -void LLPanelLogin::addServer(const std::string& server, S32 domain_name) +void LLPanelLogin::getFields(LLPointer& credential, + BOOL remember) { if (!sInstance) { - llwarns << "Attempted addServer with no login view shown" << llendl; + llwarns << "Attempted getFields with no login view shown" << llendl; return; } + + // load the credential so we can pass back the stored password or hash if the user did + // not modify the password field. + + credential = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); - LLComboBox* combo = sInstance->getChild("server_combo"); - combo->add(server, LLSD(domain_name) ); - combo->setCurrentByIndex(0); -} - -// static -void LLPanelLogin::getFields(std::string *firstname, - std::string *lastname, - std::string *password) -{ - if (!sInstance) + LLSD identifier = LLSD::emptyMap(); + LLSD authenticator = LLSD::emptyMap(); + + if(credential.notNull()) { - llwarns << "Attempted getFields with no login view shown" << llendl; - return; + authenticator = credential->getAuthenticator(); } - *firstname = sInstance->childGetText("first_name_edit"); - LLStringUtil::trim(*firstname); - - *lastname = sInstance->childGetText("last_name_edit"); - LLStringUtil::trim(*lastname); + std::string username = sInstance->childGetText("username_edit"); + LLStringUtil::trim(username); + std::string password = sInstance->childGetText("password_edit"); - *password = sInstance->mMungedPassword; + LL_INFOS2("Credentials", "Authentication") << "retrieving username:" << username << LL_ENDL; + // determine if the username is a first/last form or not. + size_t separator_index = username.find_first_of(' '); + if (separator_index == username.npos) + { + LL_INFOS2("Credentials", "Authentication") << "account: " << username << LL_ENDL; + // single username, so this is a 'clear' identifier + identifier["type"] = "account"; + identifier["account_name"] = username; + + if (LLPanelLogin::sInstance->mPasswordModified) + { + authenticator = LLSD::emptyMap(); + // password is plaintext + authenticator["type"] = "clear"; + authenticator["secret"] = password; + } + } + else if (separator_index == username.find_last_of(' ')) + { + LL_INFOS2("Credentials", "Authentication") << "agent: " << username << LL_ENDL; + // traditional firstname / lastname + identifier["type"] = "agent"; + identifier["first_name"] = username.substr(0, separator_index); + identifier["last_name"] = username.substr(separator_index+1, username.npos); + + if (LLPanelLogin::sInstance->mPasswordModified) + { + authenticator = LLSD::emptyMap(); + authenticator["type"] = "hash"; + authenticator["algorithm"] = "md5"; + LLMD5 pass((const U8 *)password.c_str()); + char md5pass[33]; /* Flawfinder: ignore */ + pass.hex_digest(md5pass); + authenticator["secret"] = md5pass; + } + } + credential = gSecAPIHandler->createCredential(LLGridManager::getInstance()->getGrid(), identifier, authenticator); + remember = sInstance->childGetValue("remember_check"); } // static @@ -650,64 +658,147 @@ BOOL LLPanelLogin::isGridComboDirty() } // static -void LLPanelLogin::getLocation(std::string &location) +BOOL LLPanelLogin::areCredentialFieldsDirty() { if (!sInstance) { - llwarns << "Attempted getLocation with no login view shown" << llendl; - return; + llwarns << "Attempted getServer with no login view shown" << llendl; } - - LLComboBox* combo = sInstance->getChild("start_location_combo"); - location = combo->getValue().asString(); + else + { + std::string username = sInstance->childGetText("username_edit"); + LLStringUtil::trim(username); + std::string password = sInstance->childGetText("password_edit"); + LLLineEditor* ctrl = sInstance->getChild("username_edit"); + if(ctrl && ctrl->isDirty()) + { + return true; + } + ctrl = sInstance->getChild("password_edit"); + if(ctrl && ctrl->isDirty()) + { + return true; + } + } + return false; } + // static -void LLPanelLogin::refreshLocation( bool force_visible ) +void LLPanelLogin::updateLocationCombo( bool force_visible ) { - if (!sInstance) return; - -#if USE_VIEWER_AUTH - loadLoginPage(); -#else + if (!sInstance) + { + return; + } + + llinfos << "updatelocationcombo " << LLStartUp::getStartSLURL().asString() << llendl; + LLComboBox* combo = sInstance->getChild("start_location_combo"); + + switch(LLStartUp::getStartSLURL().getType()) + { + case LLSLURL::LOCATION: + { + + combo->setCurrentByIndex( 2 ); + combo->setTextEntry(LLStartUp::getStartSLURL().getLocationString()); + break; + } + case LLSLURL::HOME_LOCATION: + combo->setCurrentByIndex(1); + break; + default: + combo->setCurrentByIndex(0); + break; + } + BOOL show_start = TRUE; - + if ( ! force_visible ) - { - // Don't show on first run after install - // Otherwise ShowStartLocation defaults to true. show_start = gSavedSettings.getBOOL("ShowStartLocation"); - } - - // Update the value of the location combo. - updateLocationUI(); sInstance->childSetVisible("start_location_combo", show_start); sInstance->childSetVisible("start_location_text", show_start); - - BOOL show_server = gSavedSettings.getBOOL("ForceShowGrid"); - sInstance->childSetVisible("server_combo", show_server); - -#endif + + sInstance->childSetVisible("server_combo", TRUE); } // static -void LLPanelLogin::updateLocationUI() +void LLPanelLogin::onSelectLocation(LLUICtrl*, void*) { if (!sInstance) return; - std::string sim_string = LLURLSimString::sInstance.mSimString; - if (!sim_string.empty()) + LLComboBox* combo = sInstance->getChild("start_location_combo"); + S32 index = combo->getCurrentIndex(); + + switch (index) { - // Replace "" with this region name - LLComboBox* combo = sInstance->getChild("start_location_combo"); - combo->remove(2); - combo->add( sim_string ); - combo->setTextEntry(sim_string); - combo->setCurrentByIndex( 2 ); + case 2: + { + LLSLURL slurl = LLSLURL(combo->getSelectedValue()); + if((slurl.getType() == LLSLURL::LOCATION) && + (slurl.getGrid() != LLStartUp::getStartSLURL().getGrid())) + { + + + // we've changed the grid, so update the grid selection + try + { + LLStartUp::setStartSLURL(slurl); + } + catch (LLInvalidGridName ex) + { + LLSD args; + args["GRID"] = slurl.getGrid(); + LLNotificationsUtil::add("InvalidGrid", args); + return; + } + loadLoginPage(); + } + break; + } + case 1: + { + LLStartUp::setStartSLURL(LLSLURL(LLSLURL::SIM_LOCATION_HOME)); + break; + } + default: + { + LLStartUp::setStartSLURL(LLSLURL(LLSLURL::SIM_LOCATION_LAST)); + break; + } } } + +// static +void LLPanelLogin::getLocation(LLSLURL& slurl) +{ + LLSLURL result; + if (!sInstance) + { + llwarns << "Attempted getLocation with no login view shown" << llendl; + } + + LLComboBox* combo = sInstance->getChild("start_location_combo"); + + switch(combo->getCurrentIndex()) + { + case 0: + slurl = LLSLURL(LLSLURL::SIM_LOCATION_HOME); + case 1: + slurl = LLSLURL(LLSLURL::SIM_LOCATION_LAST); + default: + slurl = LLSLURL(combo->getValue().asString()); + } +} + +void LLPanelLogin::setLocation(const LLSLURL& slurl) +{ + LLStartUp::setStartSLURL(slurl); + updateServer(); +} + // static void LLPanelLogin::closePanel() { @@ -741,15 +832,13 @@ void LLPanelLogin::loadLoginPage() std::ostringstream oStr; - std::string login_page = gSavedSettings.getString("LoginPage"); - if (login_page.empty()) - { - login_page = sInstance->getString( "real_url" ); - } + std::string login_page = LLGridManager::getInstance()->getLoginPage(); + oStr << login_page; // Use the right delimeter depending on how LLURI parses the URL LLURI login_page_uri = LLURI(login_page); + std::string first_query_delimiter = "&"; if (login_page_uri.queryMap().size() == 0) { @@ -781,11 +870,10 @@ void LLPanelLogin::loadLoginPage() curl_free(curl_version); // Grid - char* curl_grid = curl_escape(LLViewerLogin::getInstance()->getGridLabel().c_str(), 0); + char* curl_grid = curl_escape(LLGridManager::getInstance()->getGridLoginID().c_str(), 0); oStr << "&grid=" << curl_grid; curl_free(curl_grid); - - gViewerWindow->setMenuBackgroundColor(false, !LLViewerLogin::getInstance()->isInProductionGrid()); + gViewerWindow->setMenuBackgroundColor(false, !LLGridManager::getInstance()->isInProductionGrid()); gLoginMenuBarView->setBackgroundColor(gMenuBarView->getBackgroundColor()); @@ -810,30 +898,20 @@ void LLPanelLogin::loadLoginPage() location = gSavedSettings.getString("LoginLocation"); } - std::string firstname, lastname; + std::string username; if(gSavedSettings.getLLSD("UserLoginInfo").size() == 3) { LLSD cmd_line_login = gSavedSettings.getLLSD("UserLoginInfo"); - firstname = cmd_line_login[0].asString(); - lastname = cmd_line_login[1].asString(); + username = cmd_line_login[0].asString() + " " + cmd_line_login[1]; password = cmd_line_login[2].asString(); } - if (firstname.empty()) - { - firstname = gSavedSettings.getString("FirstName"); - } - - if (lastname.empty()) - { - lastname = gSavedSettings.getString("LastName"); - } char* curl_region = curl_escape(region.c_str(), 0); - oStr <<"firstname=" << firstname << - "&lastname=" << lastname << "&location=" << location << "®ion=" << curl_region; + oStr <<"username=" << username << + "&location=" << location << "®ion=" << curl_region; curl_free(curl_region); @@ -866,7 +944,7 @@ void LLPanelLogin::loadLoginPage() #endif LLMediaCtrl* web_browser = sInstance->getChild("login_html"); - + // navigate to the "real" page if (gSavedSettings.getBOOL("RegInClient")) { @@ -915,34 +993,33 @@ void LLPanelLogin::onClickConnect(void *) // JC - Make sure the fields all get committed. sInstance->setFocus(FALSE); - std::string first = sInstance->childGetText("first_name_edit"); - std::string last = sInstance->childGetText("last_name_edit"); - LLComboBox* combo = sInstance->getChild("start_location_combo"); - std::string combo_text = combo->getSimple(); - - bool has_first_and_last = !(first.empty() || last.empty()); - bool has_location = false; - - if(combo_text=="" || combo_text =="") + LLComboBox* combo = sInstance->getChild("server_combo"); + LLSD combo_val = combo->getSelectedValue(); + if (combo_val.isUndefined()) { - // *NOTE: Mani - Location field is not always committed by this point! - // This may be duplicate work, but better than not doing the work! - LLURLSimString::sInstance.setString(""); + combo_val = combo->getValue(); } - else + if(combo_val.isUndefined()) { - // *NOTE: Mani - Location field is not always committed by this point! - LLURLSimString::sInstance.setString(combo_text); - has_location = true; + LLNotificationsUtil::add("StartRegionEmpty"); + return; + } + try + { + LLGridManager::getInstance()->setGridChoice(combo_val.asString()); } - - if(!has_first_and_last) + catch (LLInvalidGridName ex) { - LLNotificationsUtil::add("MustHaveAccountToLogIn"); + LLSD args; + args["GRID"] = combo_val.asString(); + LLNotificationsUtil::add("InvalidGrid", args); + return; } - else if(!has_location) + + std::string username = sInstance->childGetText("username_edit"); + if(username.empty()) { - LLNotificationsUtil::add("StartRegionEmpty"); + LLNotificationsUtil::add("MustHaveAccountToLogIn"); } else { @@ -1005,6 +1082,8 @@ void LLPanelLogin::onClickHelp(void*) // static void LLPanelLogin::onPassKey(LLLineEditor* caller, void* user_data) { + LLPanelLogin *This = (LLPanelLogin *) user_data; + This->mPasswordModified = TRUE; if (gKeyboard->getKeyDown(KEY_CAPSLOCK) && sCapslockDidNotification == FALSE) { LLNotificationsUtil::add("CapsKeyOn"); @@ -1012,54 +1091,90 @@ void LLPanelLogin::onPassKey(LLLineEditor* caller, void* user_data) } } -// static -void LLPanelLogin::onSelectServer(LLUICtrl*, void*) -{ - // *NOTE: The paramters for this method are ignored. - // LLPanelLogin::onServerComboLostFocus(LLFocusableElement* fe, void*) - // calls this method. - - // The user twiddled with the grid choice ui. - // apply the selection to the grid setting. - std::string grid_label; - S32 grid_index; - LLComboBox* combo = sInstance->getChild("server_combo"); - LLSD combo_val = combo->getValue(); - - if (LLSD::TypeInteger == combo_val.type()) +void LLPanelLogin::updateServer() +{ + try { - grid_index = combo->getValue().asInteger(); - if ((S32)GRID_INFO_OTHER == grid_index) + updateServerCombo(); + // if they've selected another grid, we should load the credentials + // for that grid and set them to the UI. + if(sInstance && !sInstance->areCredentialFieldsDirty()) { - // This happens if the user specifies a custom grid - // via command line. - grid_label = combo->getSimple(); + LLPointer credential = gSecAPIHandler->loadCredential(LLGridManager::getInstance()->getGrid()); + bool remember = sInstance->childGetValue("remember_check"); + sInstance->setFields(credential, remember); } + // grid changed so show new splash screen (possibly) + loadLoginPage(); + updateLocationCombo(LLStartUp::getStartSLURL().getType() == LLSLURL::LOCATION); } - else + catch (LLInvalidGridName ex) { - // no valid selection, return other - grid_index = (S32)GRID_INFO_OTHER; - grid_label = combo_val.asString(); + // do nothing } +} - // This new seelction will override preset uris - // from the command line. - LLViewerLogin* vl = LLViewerLogin::getInstance(); - vl->resetURIs(); - if(grid_index != GRID_INFO_OTHER) +void LLPanelLogin::updateServerCombo() +{ + if (!sInstance) { - vl->setGridChoice((EGridInfo)grid_index); + return; } - else + // We add all of the possible values, sorted, and then add a bar and the current value at the top + LLComboBox* server_choice_combo = sInstance->getChild("server_combo"); + server_choice_combo->removeall(); +#ifdef LL_RELEASE_FOR_DOWNLOAD + std::map known_grids = LLGridManager::getInstance()->getKnownGrids(TRUE); +#else + std::map known_grids = LLGridManager::getInstance()->getKnownGrids(FALSE); +#endif + for (std::map::iterator grid_choice = known_grids.begin(); + grid_choice != known_grids.end(); + grid_choice++) { - vl->setGridChoice(grid_label); + if (!grid_choice->first.empty()) + { + server_choice_combo->add(grid_choice->second, grid_choice->first, ADD_SORTED); + } } + + server_choice_combo->addSeparator(ADD_TOP); + + server_choice_combo->add(LLGridManager::getInstance()->getGridLabel(), + LLGridManager::getInstance()->getGrid(), ADD_TOP); + + server_choice_combo->selectFirstItem(); +} - // grid changed so show new splash screen (possibly) - loadLoginPage(); +// static +void LLPanelLogin::onSelectServer(LLUICtrl*, void*) +{ + // *NOTE: The paramters for this method are ignored. + // LLPanelLogin::onServerComboLostFocus(LLFocusableElement* fe, void*) + // calls this method. + LL_INFOS("AppInit") << "onSelectServer" << LL_ENDL; + // The user twiddled with the grid choice ui. + // apply the selection to the grid setting. + LLPointer credential; + + LLComboBox* combo = sInstance->getChild("server_combo"); + LLSD combo_val = combo->getSelectedValue(); + if (combo_val.isUndefined()) + { + combo_val = combo->getValue(); + } + + combo = sInstance->getChild("start_location_combo"); + combo->setCurrentByIndex(1); + LLStartUp::setStartSLURL(LLSLURL(gSavedSettings.getString("LoginLocation"))); + LLGridManager::getInstance()->setGridChoice(combo_val.asString()); + // This new selection will override preset uris + // from the command line. + updateServer(); + updateLocationCombo(false); + updateLoginPanelLinks(); } void LLPanelLogin::onServerComboLostFocus(LLFocusableElement* fe) @@ -1072,3 +1187,14 @@ void LLPanelLogin::onServerComboLostFocus(LLFocusableElement* fe) onSelectServer(combo, NULL); } } + +void LLPanelLogin::updateLoginPanelLinks() +{ + LLSD grid_data = LLGridManager::getInstance()->getGridInfo(); + bool system_grid = grid_data.has(GRID_IS_SYSTEM_GRID_VALUE); + + // need to call through sInstance, as it's called from onSelectServer, which + // is static. + sInstance->childSetVisible("create_new_account_text", system_grid); + sInstance->childSetVisible("forgot_password_text", system_grid); +} diff --git a/indra/newview/llpanellogin.h b/indra/newview/llpanellogin.h index 1fdc3a9361..9301c263da 100644 --- a/indra/newview/llpanellogin.h +++ b/indra/newview/llpanellogin.h @@ -41,6 +41,8 @@ class LLLineEditor; class LLUIImage; class LLPanelLoginListener; +class LLSLURL; +class LLCredential; class LLPanelLogin: public LLPanel, @@ -65,20 +67,16 @@ public: void (*callback)(S32 option, void* user_data), void* callback_data); - // Remember password checkbox is set via gSavedSettings "RememberPassword" - static void setFields(const std::string& firstname, const std::string& lastname, - const std::string& password); + static void setFields(LLPointer credential, BOOL remember); - static void addServer(const std::string& server, S32 domain_name); - static void refreshLocation( bool force_visible ); - static void updateLocationUI(); - - static void getFields(std::string *firstname, std::string *lastname, - std::string *password); + static void getFields(LLPointer& credential, BOOL remember); static BOOL isGridComboDirty(); - static void getLocation(std::string &location); - + static BOOL areCredentialFieldsDirty(); + static void getLocation(LLSLURL& slurl); + static void setLocation(const LLSLURL& slurl); + + static void updateLocationCombo(bool force_visible); // simply update the combo box static void closePanel(); void setSiteIsAlive( bool alive ); @@ -86,10 +84,10 @@ public: static void loadLoginPage(); static void giveFocus(); static void setAlwaysRefresh(bool refresh); - static void mungePassword(LLUICtrl* caller, void* user_data); // inherited from LLViewerMediaObserver /*virtual*/ void handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event); + static void updateServer(); // update the combo box, change the login page to the new server, clear the combo private: friend class LLPanelLoginListener; @@ -103,6 +101,10 @@ private: static void onPassKey(LLLineEditor* caller, void* user_data); static void onSelectServer(LLUICtrl*, void*); static void onServerComboLostFocus(LLFocusableElement*); + static void updateServerCombo(); + static void onSelectLocation(LLUICtrl*, void*); + + static void updateLoginPanelLinks(); private: LLPointer mLogoImage; @@ -111,8 +113,7 @@ private: void (*mCallback)(S32 option, void *userdata); void* mCallbackData; - std::string mIncomingPassword; - std::string mMungedPassword; + BOOL mPasswordModified; static LLPanelLogin* sInstance; static BOOL sCapslockDidNotification; diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp index 288edeb031..7f5e63adee 100644 --- a/indra/newview/llpanelpeople.cpp +++ b/indra/newview/llpanelpeople.cpp @@ -619,7 +619,7 @@ BOOL LLPanelPeople::postBuild() if(recent_view_sort) mRecentViewSortMenuHandle = recent_view_sort->getHandle(); - gVoiceClient->addObserver(this); + LLVoiceClient::getInstance()->addObserver(this); // call this method in case some list is empty and buttons can be in inconsistent state updateButtons(); @@ -809,7 +809,7 @@ void LLPanelPeople::updateButtons() } } - bool enable_calls = gVoiceClient->voiceWorking() && gVoiceClient->voiceEnabled(); + bool enable_calls = LLVoiceClient::getInstance()->isVoiceWorking() && LLVoiceClient::getInstance()->voiceEnabled(); buttonSetEnabled("teleport_btn", friends_tab_active && item_selected && isFriendOnline(selected_uuids.front())); buttonSetEnabled("view_profile_btn", item_selected); diff --git a/indra/newview/llpanelplacestab.cpp b/indra/newview/llpanelplacestab.cpp index 9806b8c64d..6b12796e59 100644 --- a/indra/newview/llpanelplacestab.cpp +++ b/indra/newview/llpanelplacestab.cpp @@ -70,10 +70,7 @@ void LLPanelPlacesTab::onRegionResponse(const LLVector3d& landmark_global_pos, std::string sl_url; if ( gotSimName ) { - F32 region_x = (F32)fmod( landmark_global_pos.mdV[VX], (F64)REGION_WIDTH_METERS ); - F32 region_y = (F32)fmod( landmark_global_pos.mdV[VY], (F64)REGION_WIDTH_METERS ); - - sl_url = LLSLURL::buildSLURL(sim_name, llround(region_x), llround(region_y), llround((F32)landmark_global_pos.mdV[VZ])); + sl_url = LLSLURL(sim_name, landmark_global_pos).getSLURLString(); } else { diff --git a/indra/newview/llparticipantlist.cpp b/indra/newview/llparticipantlist.cpp index 0a20ff6226..268738d88c 100644 --- a/indra/newview/llparticipantlist.cpp +++ b/indra/newview/llparticipantlist.cpp @@ -346,7 +346,6 @@ void LLParticipantList::addAvatarIDExceptAgent(const LLUUID& avatar_id) { if (mExcludeAgent && gAgent.getID() == avatar_id) return; if (mAvatarList->contains(avatar_id)) return; - mAvatarList->getIDs().push_back(avatar_id); mAvatarList->setDirty(); adjustParticipant(avatar_id); @@ -632,7 +631,7 @@ bool LLParticipantList::LLParticipantListMenu::enableContextMenuItem(const LLSD& else if (item == "can_call") { bool not_agent = mUUIDs.front() != gAgentID; - bool can_call = not_agent && LLVoiceClient::voiceEnabled() && gVoiceClient->voiceWorking(); + bool can_call = not_agent && LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking(); return can_call; } diff --git a/indra/newview/llsecapi.cpp b/indra/newview/llsecapi.cpp new file mode 100644 index 0000000000..ba343f5387 --- /dev/null +++ b/indra/newview/llsecapi.cpp @@ -0,0 +1,161 @@ +/** + * @file llsecapi.cpp + * @brief Security API for services such as certificate handling + * secure local storage, etc. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" +#include "llsecapi.h" +#include "llsechandler_basic.h" +#include +#include +#include "llhttpclient.h" + + + +std::map > gHandlerMap; +LLPointer gSecAPIHandler; + +void initializeSecHandler() +{ + OpenSSL_add_all_algorithms(); + OpenSSL_add_all_ciphers(); + OpenSSL_add_all_digests(); + gHandlerMap[BASIC_SECHANDLER] = new LLSecAPIBasicHandler(); + + + // Currently, we only have the Basic handler, so we can point the main sechandler + // pointer to the basic handler. Later, we'll create a wrapper handler that + // selects the appropriate sechandler as needed, for instance choosing the + // mac keyring handler, with fallback to the basic sechandler + gSecAPIHandler = gHandlerMap[BASIC_SECHANDLER]; + + // initialize all SecAPIHandlers + LLProtectedDataException ex = LLProtectedDataException(""); + std::map >::const_iterator itr; + for(itr = gHandlerMap.begin(); itr != gHandlerMap.end(); ++itr) + { + LLPointer handler = (*itr).second; + try + { + handler->init(); + } + catch (LLProtectedDataException e) + { + ex = e; + } + } + if (ex.getMessage().length() > 0 ) // an exception was thrown. + { + throw ex; + } + +} +// start using a given security api handler. If the string is empty +// the default is used +LLPointer getSecHandler(const std::string& handler_type) +{ + if (gHandlerMap.find(handler_type) != gHandlerMap.end()) + { + return gHandlerMap[handler_type]; + } + else + { + return LLPointer(NULL); + } +} +// register a handler +void registerSecHandler(const std::string& handler_type, + LLPointer& handler) +{ + gHandlerMap[handler_type] = handler; +} + +std::ostream& operator <<(std::ostream& s, const LLCredential& cred) +{ + return s << (std::string)cred; +} + + +// secapiSSLCertVerifyCallback +// basic callback called when a cert verification is requested. +// calls SECAPI to validate the context +// not initialized in the above initialization function, due to unit tests +// see llappviewer + +int secapiSSLCertVerifyCallback(X509_STORE_CTX *ctx, void *param) +{ + LLURLRequest *req = (LLURLRequest *)param; + LLPointer store = gSecAPIHandler->getCertificateStore(""); + LLPointer chain = gSecAPIHandler->getCertificateChain(ctx); + LLSD validation_params = LLSD::emptyMap(); + LLURI uri(req->getURL()); + validation_params[CERT_HOSTNAME] = uri.hostName(); + try + { + chain->validate(VALIDATION_POLICY_SSL, store, validation_params); + } + catch (LLCertValidationTrustException& cert_exception) + { + LL_WARNS("AppInit") << "Cert not trusted: " << cert_exception.getMessage() << LL_ENDL; + return 0; + } + catch (LLCertException& cert_exception) + { + LL_WARNS("AppInit") << "cert error " << cert_exception.getMessage() << LL_ENDL; + return 0; + } + catch (...) + { + LL_WARNS("AppInit") << "cert error " << LL_ENDL; + return 0; + } + return 1; +} + +LLSD LLCredential::getLoginParams() +{ + LLSD result = LLSD::emptyMap(); + if (mIdentifier["type"].asString() == "agent") + { + // legacy credential + result["passwd"] = "$1$" + mAuthenticator["secret"].asString(); + result["first"] = mIdentifier["first_name"]; + result["last"] = mIdentifier["last_name"]; + + } + else if (mIdentifier["type"].asString() == "account") + { + result["username"] = mIdentifier["account_name"]; + result["passwd"] = mAuthenticator["secret"]; + + } + return result; +} diff --git a/indra/newview/llsecapi.h b/indra/newview/llsecapi.h new file mode 100644 index 0000000000..5211dc2699 --- /dev/null +++ b/indra/newview/llsecapi.h @@ -0,0 +1,493 @@ +/** + * @file llsecapi.h + * @brief Security API for services such as certificate handling + * secure local storage, etc. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LLSECAPI_H +#define LLSECAPI_H +#include +#include +#include + +#ifdef LL_WINDOWS +#pragma warning(disable:4250) +#endif // LL_WINDOWS + +// All error handling is via exceptions. + + +#define CERT_SUBJECT_NAME "subject_name" +#define CERT_ISSUER_NAME "issuer_name" +#define CERT_NAME_CN "commonName" + +#define CERT_SUBJECT_NAME_STRING "subject_name_string" +#define CERT_ISSUER_NAME_STRING "issuer_name_string" + +#define CERT_SERIAL_NUMBER "serial_number" + +#define CERT_VALID_FROM "valid_from" +#define CERT_VALID_TO "valid_to" +#define CERT_SHA1_DIGEST "sha1_digest" +#define CERT_MD5_DIGEST "md5_digest" +#define CERT_HOSTNAME "hostname" +#define CERT_BASIC_CONSTRAINTS "basicConstraints" +#define CERT_BASIC_CONSTRAINTS_CA "CA" +#define CERT_BASIC_CONSTRAINTS_PATHLEN "pathLen" + +#define CERT_KEY_USAGE "keyUsage" +#define CERT_KU_DIGITAL_SIGNATURE "digitalSignature" +#define CERT_KU_NON_REPUDIATION "nonRepudiation" +#define CERT_KU_KEY_ENCIPHERMENT "keyEncipherment" +#define CERT_KU_DATA_ENCIPHERMENT "dataEncipherment" +#define CERT_KU_KEY_AGREEMENT "keyAgreement" +#define CERT_KU_CERT_SIGN "certSigning" +#define CERT_KU_CRL_SIGN "crlSigning" +#define CERT_KU_ENCIPHER_ONLY "encipherOnly" +#define CERT_KU_DECIPHER_ONLY "decipherOnly" + +#define BASIC_SECHANDLER "BASIC_SECHANDLER" +#define CERT_VALIDATION_DATE "validation_date" + +#define CERT_EXTENDED_KEY_USAGE "extendedKeyUsage" +#define CERT_EKU_SERVER_AUTH SN_server_auth + +#define CERT_SUBJECT_KEY_IDENTFIER "subjectKeyIdentifier" +#define CERT_AUTHORITY_KEY_IDENTIFIER "authorityKeyIdentifier" +#define CERT_AUTHORITY_KEY_IDENTIFIER_ID "authorityKeyIdentifierId" +#define CERT_AUTHORITY_KEY_IDENTIFIER_NAME "authorityKeyIdentifierName" +#define CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL "authorityKeyIdentifierSerial" + +// validate the current time lies within +// the validation period of the cert +#define VALIDATION_POLICY_TIME 1 + +// validate that the CA, or some cert in the chain +// lies within the certificate store +#define VALIDATION_POLICY_TRUSTED 2 + +// validate that the subject name of +// the cert contains the passed in hostname +// or validates against the hostname +#define VALIDATION_POLICY_HOSTNAME 4 + + +// validate that the cert contains the SSL EKU +#define VALIDATION_POLICY_SSL_KU 8 + +// validate that the cert contains the SSL EKU +#define VALIDATION_POLICY_CA_KU 16 + +#define VALIDATION_POLICY_CA_BASIC_CONSTRAINTS 32 + +// validate that the cert is correct for SSL +#define VALIDATION_POLICY_SSL (VALIDATION_POLICY_TIME | \ + VALIDATION_POLICY_HOSTNAME | \ + VALIDATION_POLICY_TRUSTED | \ + VALIDATION_POLICY_SSL_KU | \ + VALIDATION_POLICY_CA_BASIC_CONSTRAINTS | \ + VALIDATION_POLICY_CA_KU) + + + + + + +class LLProtectedDataException +{ +public: + LLProtectedDataException(const char *msg) + { + LL_WARNS("SECAPI") << "Protected Data Error: " << (std::string)msg << LL_ENDL; + mMsg = (std::string)msg; + } + std::string getMessage() { return mMsg; } +protected: + std::string mMsg; +}; + +// class LLCertificate +// parent class providing an interface for certifiate. +// LLCertificates are considered unmodifiable +// Certificates are pulled out of stores, or created via +// factory calls +class LLCertificate : public LLRefCount +{ + LOG_CLASS(LLCertificate); +public: + LLCertificate() {} + + virtual ~LLCertificate() {} + + // return a PEM encoded certificate. The encoding + // includes the -----BEGIN CERTIFICATE----- and end certificate elements + virtual std::string getPem() const=0; + + // return a DER encoded certificate + virtual std::vector getBinary() const=0; + + // return an LLSD object containing information about the certificate + // such as its name, signature, expiry time, serial number + virtual LLSD getLLSD() const=0; + + // return an openSSL X509 struct for the certificate + virtual X509* getOpenSSLX509() const=0; + +}; + +// class LLCertificateVector +// base class for a list of certificates. + + +class LLCertificateVector : public LLRefCount +{ + +public: + + LLCertificateVector() {}; + virtual ~LLCertificateVector() {}; + + // base iterator implementation class, providing + // the functionality needed for the iterator class. + class iterator_impl : public LLRefCount + { + public: + iterator_impl() {}; + virtual ~iterator_impl() {}; + virtual void seek(bool incr)=0; + virtual LLPointer clone() const=0; + virtual bool equals(const LLPointer& _iter) const=0; + virtual LLPointer get()=0; + }; + + // iterator class + class iterator + { + public: + iterator(LLPointer impl) : mImpl(impl) {} + iterator() : mImpl(NULL) {} + iterator(const iterator& _iter) {mImpl = _iter.mImpl->clone(); } + ~iterator() {} + iterator& operator++() { if(mImpl.notNull()) mImpl->seek(true); return *this;} + iterator& operator--() { if(mImpl.notNull()) mImpl->seek(false); return *this;} + + iterator operator++(int) { iterator result = *this; if(mImpl.notNull()) mImpl->seek(true); return result;} + iterator operator--(int) { iterator result = *this; if(mImpl.notNull()) mImpl->seek(false); return result;} + LLPointer operator*() { return mImpl->get(); } + + LLPointer mImpl; + protected: + friend bool operator==(const LLCertificateVector::iterator& _lhs, const LLCertificateVector::iterator& _rhs); + bool equals(const iterator& _iter) const { return mImpl->equals(_iter.mImpl); } + }; + + // numeric indexer + virtual LLPointer operator[](int)=0; + + // Iteration + virtual iterator begin()=0; + + virtual iterator end()=0; + + // find a cert given params + virtual iterator find(const LLSD& params) =0; + + // return the number of certs in the store + virtual int size() const = 0; + + // append the cert to the store. if a copy of the cert already exists in the store, it is removed first + virtual void add(LLPointer cert)=0; + + // insert the cert to the store. if a copy of the cert already exists in the store, it is removed first + virtual void insert(iterator location, LLPointer cert)=0; + + // remove a certificate from the store + virtual LLPointer erase(iterator cert)=0; +}; + + +// class LLCertificateStore +// represents a store of certificates, typically a store of root CA +// certificates. The store can be persisted, and can be used to validate +// a cert chain +// +class LLCertificateStore : virtual public LLCertificateVector +{ + +public: + + LLCertificateStore() {} + virtual ~LLCertificateStore() {} + + // persist the store + virtual void save()=0; + + // return the store id + virtual std::string storeId() const=0; +}; + +// class LLCertificateChain +// Class representing a chain of certificates in order, with the +// first element being the child cert. +class LLCertificateChain : virtual public LLCertificateVector +{ + +public: + LLCertificateChain() {} + + virtual ~LLCertificateChain() {} + + // validate a certificate chain given the params. + // Will throw exceptions on error + + virtual void validate(int validation_policy, + LLPointer ca_store, + const LLSD& validation_params) =0; +}; + + + + +inline +bool operator==(const LLCertificateVector::iterator& _lhs, const LLCertificateVector::iterator& _rhs) +{ + return _lhs.equals(_rhs); +} +inline +bool operator!=(const LLCertificateVector::iterator& _lhs, const LLCertificateVector::iterator& _rhs) +{ + return !(_lhs == _rhs); +} + + +// +// LLCredential - interface for credentials providing the following functionality: +// * persistance of credential information based on grid (for saving username/password) +// * serialization to an OGP identifier/authenticator pair +// +class LLCredential : public LLRefCount +{ +public: + + LLCredential() {} + + LLCredential(const std::string& grid) + { + mGrid = grid; + mIdentifier = LLSD::emptyMap(); + mAuthenticator = LLSD::emptyMap(); + } + + virtual ~LLCredential() {} + + virtual void setCredentialData(const LLSD& identifier, const LLSD& authenticator) + { + mIdentifier = identifier; + mAuthenticator = authenticator; + } + virtual LLSD getIdentifier() { return mIdentifier; } + virtual LLSD getAuthenticator() { return mAuthenticator; } + virtual LLSD getLoginParams(); + virtual std::string getGrid() { return mGrid; } + + + virtual void clearAuthenticator() { mAuthenticator = LLSD(); } + virtual std::string userID() const { return std::string("unknown");} + virtual std::string asString() const { return std::string("unknown");} + operator std::string() const { return asString(); } +protected: + LLSD mIdentifier; + LLSD mAuthenticator; + std::string mGrid; +}; + +std::ostream& operator <<(std::ostream& s, const LLCredential& cred); + + +// All error handling is via exceptions. + +class LLCertException +{ +public: + LLCertException(LLPointer cert, const char* msg) + { + + mCert = cert; + + LL_WARNS("SECAPI") << "Certificate Error: " << (std::string)msg << LL_ENDL; + mMsg = (std::string)msg; + } + LLPointer getCert() { return mCert; } + std::string getMessage() { return mMsg; } +protected: + LLPointer mCert; + std::string mMsg; +}; + +class LLInvalidCertificate : public LLCertException +{ +public: + LLInvalidCertificate(LLPointer cert) : LLCertException(cert, "CertInvalid") + { + } +protected: +}; + +class LLCertValidationTrustException : public LLCertException +{ +public: + LLCertValidationTrustException(LLPointer cert) : LLCertException(cert, "CertUntrusted") + { + } +protected: +}; + +class LLCertValidationHostnameException : public LLCertException +{ +public: + LLCertValidationHostnameException(std::string hostname, + LLPointer cert) : LLCertException(cert, "CertInvalidHostname") + { + mHostname = hostname; + } + + std::string getHostname() { return mHostname; } +protected: + std::string mHostname; +}; + +class LLCertValidationExpirationException : public LLCertException +{ +public: + LLCertValidationExpirationException(LLPointer cert, + LLDate current_time) : LLCertException(cert, "CertExpired") + { + mTime = current_time; + } + LLDate GetTime() { return mTime; } +protected: + LLDate mTime; +}; + +class LLCertKeyUsageValidationException : public LLCertException +{ +public: + LLCertKeyUsageValidationException(LLPointer cert) : LLCertException(cert, "CertKeyUsage") + { + } +protected: +}; + +class LLCertBasicConstraintsValidationException : public LLCertException +{ +public: + LLCertBasicConstraintsValidationException(LLPointer cert) : LLCertException(cert, "CertBasicConstraints") + { + } +protected: +}; + +class LLCertValidationInvalidSignatureException : public LLCertException +{ +public: + LLCertValidationInvalidSignatureException(LLPointer cert) : LLCertException(cert, "CertInvalidSignature") + { + } +protected: +}; + +// LLSecAPIHandler Class +// Interface handler class for the various security storage handlers. +class LLSecAPIHandler : public LLRefCount +{ +public: + + + LLSecAPIHandler() {} + virtual ~LLSecAPIHandler() {} + + // initialize the SecAPIHandler + virtual void init() {}; + + // instantiate a certificate from a pem string + virtual LLPointer getCertificate(const std::string& pem_cert)=0; + + + + // instiate a certificate from an openssl X509 structure + virtual LLPointer getCertificate(X509* openssl_cert)=0; + + // instantiate a chain from an X509_STORE_CTX + virtual LLPointer getCertificateChain(const X509_STORE_CTX* chain)=0; + + // instantiate a cert store given it's id. if a persisted version + // exists, it'll be loaded. If not, one will be created (but not + // persisted) + virtual LLPointer getCertificateStore(const std::string& store_id)=0; + + // persist data in a protected store + virtual void setProtectedData(const std::string& data_type, + const std::string& data_id, + const LLSD& data)=0; + + // retrieve protected data + virtual LLSD getProtectedData(const std::string& data_type, + const std::string& data_id)=0; + + // delete a protected data item from the store + virtual void deleteProtectedData(const std::string& data_type, + const std::string& data_id)=0; + + virtual LLPointer createCredential(const std::string& grid, + const LLSD& identifier, + const LLSD& authenticator)=0; + + virtual LLPointer loadCredential(const std::string& grid)=0; + + virtual void saveCredential(LLPointer cred, bool save_authenticator)=0; + + virtual void deleteCredential(LLPointer cred)=0; + +}; + +void initializeSecHandler(); + +// retrieve a security api depending on the api type +LLPointer getSecHandler(const std::string& handler_type); + +void registerSecHandler(const std::string& handler_type, + LLPointer& handler); + +extern LLPointer gSecAPIHandler; + + +int secapiSSLCertVerifyCallback(X509_STORE_CTX *ctx, void *param); + + +#endif // LL_SECAPI_H diff --git a/indra/newview/llsechandler_basic.cpp b/indra/newview/llsechandler_basic.cpp new file mode 100644 index 0000000000..51e250ffc6 --- /dev/null +++ b/indra/newview/llsechandler_basic.cpp @@ -0,0 +1,1586 @@ +/** + * @file llsechandler_basic.cpp + * @brief Security API for services such as certificate handling + * secure local storage, etc. + * + * $LicenseInfo:firstyear=2003&license=viewergpl$ + * + * Copyright (c) 2003-2000, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * +LLS * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + + +#include "llviewerprecompiledheaders.h" +#include "llsecapi.h" +#include "llsechandler_basic.h" +#include "llsdserialize.h" +#include "llviewernetwork.h" +#include "llxorcipher.h" +#include "llfile.h" +#include "lldir.h" +#include "llviewercontrol.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +// 128 bits of salt data... +#define STORE_SALT_SIZE 16 +#define BUFFER_READ_SIZE 256 +std::string cert_string_from_asn1_string(ASN1_STRING* value); +std::string cert_string_from_octet_string(ASN1_OCTET_STRING* value); + +LLSD _basic_constraints_ext(X509* cert); +LLSD _key_usage_ext(X509* cert); +LLSD _ext_key_usage_ext(X509* cert); +LLSD _subject_key_identifier_ext(X509 *cert); +LLSD _authority_key_identifier_ext(X509* cert); + +LLBasicCertificate::LLBasicCertificate(const std::string& pem_cert) +{ + + // BIO_new_mem_buf returns a read only bio, but takes a void* which isn't const + // so we need to cast it. + BIO * pem_bio = BIO_new_mem_buf((void*)pem_cert.c_str(), pem_cert.length()); + if(pem_bio == NULL) + { + LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; + throw LLInvalidCertificate(this); + } + mCert = NULL; + PEM_read_bio_X509(pem_bio, &mCert, 0, NULL); + BIO_free(pem_bio); + if (!mCert) + { + throw LLInvalidCertificate(this); + } + _initLLSD(); +} + + +LLBasicCertificate::LLBasicCertificate(X509* pCert) +{ + if (!pCert || !pCert->cert_info) + { + throw LLInvalidCertificate(this); + } + mCert = X509_dup(pCert); + _initLLSD(); +} + +LLBasicCertificate::~LLBasicCertificate() +{ + if(mCert) + { + X509_free(mCert); + } +} + +// +// retrieve the pem using the openssl functionality +std::string LLBasicCertificate::getPem() const +{ + char * pem_bio_chars = NULL; + // a BIO is the equivalent of a 'std::stream', and + // can be a file, mem stream, whatever. Grab a memory based + // BIO for the result + BIO *pem_bio = BIO_new(BIO_s_mem()); + if (!pem_bio) + { + LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; + return std::string(); + } + PEM_write_bio_X509(pem_bio, mCert); + int length = BIO_get_mem_data(pem_bio, &pem_bio_chars); + std::string result = std::string(pem_bio_chars, length); + BIO_free(pem_bio); + return result; +} + +// get the DER encoding for the cert +// DER is a binary encoding format for certs... +std::vector LLBasicCertificate::getBinary() const +{ + U8 * der_bio_data = NULL; + // get a memory bio + BIO *der_bio = BIO_new(BIO_s_mem()); + if (!der_bio) + { + LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; + return std::vector(); + } + i2d_X509_bio(der_bio, mCert); + int length = BIO_get_mem_data(der_bio, &der_bio_data); + std::vector result(length); + // vectors are guranteed to be a contiguous chunk of memory. + memcpy(&result[0], der_bio_data, length); + BIO_free(der_bio); + return result; +} + + +LLSD LLBasicCertificate::getLLSD() const +{ + return mLLSDInfo; +} + +// Initialize the LLSD info for the certificate +LLSD& LLBasicCertificate::_initLLSD() +{ + + // call the various helpers to build the LLSD + mLLSDInfo[CERT_SUBJECT_NAME] = cert_name_from_X509_NAME(X509_get_subject_name(mCert)); + mLLSDInfo[CERT_ISSUER_NAME] = cert_name_from_X509_NAME(X509_get_issuer_name(mCert)); + mLLSDInfo[CERT_SUBJECT_NAME_STRING] = cert_string_name_from_X509_NAME(X509_get_subject_name(mCert)); + mLLSDInfo[CERT_ISSUER_NAME_STRING] = cert_string_name_from_X509_NAME(X509_get_issuer_name(mCert)); + ASN1_INTEGER *sn = X509_get_serialNumber(mCert); + if (sn != NULL) + { + mLLSDInfo[CERT_SERIAL_NUMBER] = cert_string_from_asn1_integer(sn); + } + + mLLSDInfo[CERT_VALID_TO] = cert_date_from_asn1_time(X509_get_notAfter(mCert)); + mLLSDInfo[CERT_VALID_FROM] = cert_date_from_asn1_time(X509_get_notBefore(mCert)); + mLLSDInfo[CERT_SHA1_DIGEST] = cert_get_digest("sha1", mCert); + mLLSDInfo[CERT_MD5_DIGEST] = cert_get_digest("md5", mCert); + // add the known extensions + mLLSDInfo[CERT_BASIC_CONSTRAINTS] = _basic_constraints_ext(mCert); + mLLSDInfo[CERT_KEY_USAGE] = _key_usage_ext(mCert); + mLLSDInfo[CERT_EXTENDED_KEY_USAGE] = _ext_key_usage_ext(mCert); + mLLSDInfo[CERT_SUBJECT_KEY_IDENTFIER] = _subject_key_identifier_ext(mCert); + mLLSDInfo[CERT_AUTHORITY_KEY_IDENTIFIER] = _authority_key_identifier_ext(mCert); + return mLLSDInfo; +} + +// Retrieve the basic constraints info +LLSD _basic_constraints_ext(X509* cert) +{ + LLSD result; + BASIC_CONSTRAINTS *bs = (BASIC_CONSTRAINTS *)X509_get_ext_d2i(cert, NID_basic_constraints, NULL, NULL); + if(bs) + { + result = LLSD::emptyMap(); + // Determines whether the cert can be used as a CA + result[CERT_BASIC_CONSTRAINTS_CA] = (bool)bs->ca; + + if(bs->pathlen) + { + // the pathlen determines how deep a certificate chain can be from + // this CA + if((bs->pathlen->type == V_ASN1_NEG_INTEGER) + || !bs->ca) + { + result[CERT_BASIC_CONSTRAINTS_PATHLEN] = 0; + } + else + { + result[CERT_BASIC_CONSTRAINTS_PATHLEN] = (int)ASN1_INTEGER_get(bs->pathlen); + } + } + + } + return result; +} + +// retrieve the key usage, which specifies how the cert can be used. +// +LLSD _key_usage_ext(X509* cert) +{ + LLSD result; + ASN1_STRING *usage_str = (ASN1_STRING *)X509_get_ext_d2i(cert, NID_key_usage, NULL, NULL); + if(usage_str) + { + result = LLSD::emptyArray(); + long usage = 0; + if(usage_str->length > 0) + { + usage = usage_str->data[0]; + if(usage_str->length > 1) + { + usage |= usage_str->data[1] << 8; + } + } + ASN1_STRING_free(usage_str); + if(usage) + { + if(usage & KU_DIGITAL_SIGNATURE) result.append(LLSD((std::string)CERT_KU_DIGITAL_SIGNATURE)); + if(usage & KU_NON_REPUDIATION) result.append(LLSD((std::string)CERT_KU_NON_REPUDIATION)); + if(usage & KU_KEY_ENCIPHERMENT) result.append(LLSD((std::string)CERT_KU_KEY_ENCIPHERMENT)); + if(usage & KU_DATA_ENCIPHERMENT) result.append(LLSD((std::string)CERT_KU_DATA_ENCIPHERMENT)); + if(usage & KU_KEY_AGREEMENT) result.append(LLSD((std::string)CERT_KU_KEY_AGREEMENT)); + if(usage & KU_KEY_CERT_SIGN) result.append(LLSD((std::string)CERT_KU_CERT_SIGN)); + if(usage & KU_CRL_SIGN) result.append(LLSD((std::string)CERT_KU_CRL_SIGN)); + if(usage & KU_ENCIPHER_ONLY) result.append(LLSD((std::string)CERT_KU_ENCIPHER_ONLY)); + if(usage & KU_DECIPHER_ONLY) result.append(LLSD((std::string)CERT_KU_DECIPHER_ONLY)); + } + } + return result; +} + +// retrieve the extended key usage for the cert +LLSD _ext_key_usage_ext(X509* cert) +{ + LLSD result; + EXTENDED_KEY_USAGE *eku = (EXTENDED_KEY_USAGE *)X509_get_ext_d2i(cert, NID_ext_key_usage, NULL, NULL); + if(eku) + { + result = LLSD::emptyArray(); + while(sk_ASN1_OBJECT_num(eku)) + { + ASN1_OBJECT *usage = sk_ASN1_OBJECT_pop(eku); + if(usage) + { + int nid = OBJ_obj2nid(usage); + if (nid) + { + std::string sn = OBJ_nid2sn(nid); + result.append(sn); + } + ASN1_OBJECT_free(usage); + } + } + } + return result; +} + +// retrieve the subject key identifier of the cert +LLSD _subject_key_identifier_ext(X509 *cert) +{ + LLSD result; + ASN1_OCTET_STRING *skeyid = (ASN1_OCTET_STRING *)X509_get_ext_d2i(cert, NID_subject_key_identifier, NULL, NULL); + if(skeyid) + { + result = cert_string_from_octet_string(skeyid); + } + return result; +} + +// retrieve the authority key identifier of the cert +LLSD _authority_key_identifier_ext(X509* cert) +{ + LLSD result; + AUTHORITY_KEYID *akeyid = (AUTHORITY_KEYID *)X509_get_ext_d2i(cert, NID_authority_key_identifier, NULL, NULL); + if(akeyid) + { + result = LLSD::emptyMap(); + if(akeyid->keyid) + { + result[CERT_AUTHORITY_KEY_IDENTIFIER_ID] = cert_string_from_octet_string(akeyid->keyid); + } + if(akeyid->serial) + { + result[CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL] = cert_string_from_asn1_integer(akeyid->serial); + } + } + + // we ignore the issuer name in the authority key identifier, we check the issue name via + // the the issuer name entry in the cert. + + + return result; +} + +// retrieve an openssl x509 object, +// which must be freed by X509_free +X509* LLBasicCertificate::getOpenSSLX509() const +{ + return X509_dup(mCert); +} + +// generate a single string containing the subject or issuer +// name of the cert. +std::string cert_string_name_from_X509_NAME(X509_NAME* name) +{ + char * name_bio_chars = NULL; + // get a memory bio + BIO *name_bio = BIO_new(BIO_s_mem()); + // stream the name into the bio. The name will be in the 'short name' format + X509_NAME_print_ex(name_bio, name, 0, XN_FLAG_RFC2253); + int length = BIO_get_mem_data(name_bio, &name_bio_chars); + std::string result = std::string(name_bio_chars, length); + BIO_free(name_bio); + return result; +} + +// generate an LLSD from a certificate name (issuer or subject name). +// the name will be strings indexed by the 'long form' +LLSD cert_name_from_X509_NAME(X509_NAME* name) +{ + LLSD result = LLSD::emptyMap(); + int name_entries = X509_NAME_entry_count(name); + for (int entry_index=0; entry_index < name_entries; entry_index++) + { + char buffer[32]; + X509_NAME_ENTRY *entry = X509_NAME_get_entry(name, entry_index); + + std::string name_value = std::string((const char*)M_ASN1_STRING_data(X509_NAME_ENTRY_get_data(entry)), + M_ASN1_STRING_length(X509_NAME_ENTRY_get_data(entry))); + + ASN1_OBJECT* name_obj = X509_NAME_ENTRY_get_object(entry); + OBJ_obj2txt(buffer, sizeof(buffer), name_obj, 0); + std::string obj_buffer_str = std::string(buffer); + result[obj_buffer_str] = name_value; + } + + return result; +} + +// Generate a string from an ASN1 integer. ASN1 Integers are +// bignums, so they can be 'infinitely' long, therefore we +// cannot simply use a conversion to U64 or something. +// We retrieve as a readable string for UI + +std::string cert_string_from_asn1_integer(ASN1_INTEGER* value) +{ + std::string result; + BIGNUM *bn = ASN1_INTEGER_to_BN(value, NULL); + if(bn) + { + char * ascii_bn = BN_bn2hex(bn); + + if(ascii_bn) + { + result = ascii_bn; + OPENSSL_free(ascii_bn); + } + BN_free(bn); + } + return result; +} + +// Generate a string from an OCTET string. +// we retrieve as a + +std::string cert_string_from_octet_string(ASN1_OCTET_STRING* value) +{ + + std::stringstream result; + result << std::hex << std::setprecision(2); + for (int i=0; i < value->length; i++) + { + if (i != 0) + { + result << ":"; + } + result << std::setfill('0') << std::setw(2) << (int)value->data[i]; + } + return result.str(); +} + +// Generate a string from an ASN1 integer. ASN1 Integers are +// bignums, so they can be 'infinitely' long, therefore we +// cannot simply use a conversion to U64 or something. +// We retrieve as a readable string for UI + +std::string cert_string_from_asn1_string(ASN1_STRING* value) +{ + char * string_bio_chars = NULL; + std::string result; + // get a memory bio + BIO *string_bio = BIO_new(BIO_s_mem()); + if(!string_bio) + { + // stream the name into the bio. The name will be in the 'short name' format + ASN1_STRING_print_ex(string_bio, value, ASN1_STRFLGS_RFC2253); + int length = BIO_get_mem_data(string_bio, &string_bio_chars); + result = std::string(string_bio_chars, length); + BIO_free(string_bio); + } + else + { + LL_WARNS("SECAPI") << "Could not allocate an openssl memory BIO." << LL_ENDL; + } + + return result; +} + +// retrieve a date structure from an ASN1 time, for +// validity checking. +LLDate cert_date_from_asn1_time(ASN1_TIME* asn1_time) +{ + + struct tm timestruct = {0}; + int i = asn1_time->length; + + if (i < 10) + { + return LLDate(); + } + // convert the date from the ASN1 time (which is a string in ZULU time), to + // a timeval. + timestruct.tm_year = (asn1_time->data[0]-'0') * 10 + (asn1_time->data[1]-'0'); + + /* Deal with Year 2000 */ + if (timestruct.tm_year < 70) + timestruct.tm_year += 100; + + timestruct.tm_mon = (asn1_time->data[2]-'0') * 10 + (asn1_time->data[3]-'0') - 1; + timestruct.tm_mday = (asn1_time->data[4]-'0') * 10 + (asn1_time->data[5]-'0'); + timestruct.tm_hour = (asn1_time->data[6]-'0') * 10 + (asn1_time->data[7]-'0'); + timestruct.tm_min = (asn1_time->data[8]-'0') * 10 + (asn1_time->data[9]-'0'); + timestruct.tm_sec = (asn1_time->data[10]-'0') * 10 + (asn1_time->data[11]-'0'); + +#if LL_WINDOWS + return LLDate((F64)_mkgmtime(×truct)); +#else // LL_WINDOWS + return LLDate((F64)timegm(×truct)); +#endif // LL_WINDOWS +} + + +// Generate a string containing a digest. The digest time is 'ssh1' or +// 'md5', and the resulting string is of the form "aa:12:5c:' and so on +std::string cert_get_digest(const std::string& digest_type, X509 *cert) +{ + unsigned char digest_data[BUFFER_READ_SIZE]; + unsigned int len = sizeof(digest_data); + std::stringstream result; + const EVP_MD* digest = NULL; + // we could use EVP_get_digestbyname, but that requires initializer code which + // would require us to complicate things by plumbing it into the system. + if (digest_type == "md5") + { + digest = EVP_md5(); + } + else if (digest_type == "sha1") + { + digest = EVP_sha1(); + } + else + { + return std::string(); + } + + X509_digest(cert, digest, digest_data, &len); + result << std::hex << std::setprecision(2); + for (unsigned int i=0; i < len; i++) + { + if (i != 0) + { + result << ":"; + } + result << std::setfill('0') << std::setw(2) << (int)digest_data[i]; + } + return result.str(); +} + + +// class LLBasicCertificateVector +// This class represents a list of certificates, implemented by a vector of certificate pointers. +// it contains implementations of the virtual functions for iterators, search, add, remove, etc. +// + +// Find a certificate in the list. +// It will find a cert that has minimally the params listed, with the values being the same +LLBasicCertificateVector::iterator LLBasicCertificateVector::find(const LLSD& params) +{ + BOOL found = FALSE; + // loop through the entire vector comparing the values in the certs + // against those passed in via the params. + // params should be a map. Only the items specified in the map will be + // checked, but they must match exactly, even if they're maps or arrays. + + for(iterator cert = begin(); + cert != end(); + cert++) + { + + found= TRUE; + LLSD cert_info = (*cert)->getLLSD(); + for (LLSD::map_const_iterator param = params.beginMap(); + param != params.endMap(); + param++) + { + + if (!cert_info.has((std::string)param->first) || + (!valueCompareLLSD(cert_info[(std::string)param->first], param->second))) + { + found = FALSE; + break; + } + } + if (found) + { + return (cert); + } + } + return end(); +} + +// Insert a certificate into the store. If the certificate already +// exists in the store, nothing is done. +void LLBasicCertificateVector::insert(iterator _iter, + LLPointer cert) +{ + LLSD cert_info = cert->getLLSD(); + if (cert_info.isMap() && cert_info.has(CERT_SHA1_DIGEST)) + { + LLSD existing_cert_info = LLSD::emptyMap(); + existing_cert_info[CERT_MD5_DIGEST] = cert_info[CERT_MD5_DIGEST]; + if(find(existing_cert_info) == end()) + { + BasicIteratorImpl *basic_iter = dynamic_cast(_iter.mImpl.get()); + mCerts.insert(basic_iter->mIter, cert); + } + } +} + +// remove a certificate from the store +LLPointer LLBasicCertificateVector::erase(iterator _iter) +{ + + if (_iter != end()) + { + BasicIteratorImpl *basic_iter = dynamic_cast(_iter.mImpl.get()); + LLPointer result = (*_iter); + mCerts.erase(basic_iter->mIter); + return result; + } + return NULL; +} + + +// +// LLBasicCertificateStore +// This class represents a store of CA certificates. The basic implementation +// uses a pem file such as the legacy CA.pem stored in the existing +// SL implementation. +LLBasicCertificateStore::LLBasicCertificateStore(const std::string& filename) +{ + mFilename = filename; + load_from_file(filename); +} + +void LLBasicCertificateStore::load_from_file(const std::string& filename) +{ + // scan the PEM file extracting each certificate + BIO* file_bio = BIO_new(BIO_s_file()); + if(file_bio) + { + if (BIO_read_filename(file_bio, filename.c_str()) > 0) + { + X509 *cert_x509 = NULL; + while((PEM_read_bio_X509(file_bio, &cert_x509, 0, NULL)) && + (cert_x509 != NULL)) + { + try + { + add(new LLBasicCertificate(cert_x509)); + } + catch (...) + { + LL_WARNS("SECAPI") << "Failure creating certificate from the certificate store file." << LL_ENDL; + } + X509_free(cert_x509); + cert_x509 = NULL; + } + BIO_free(file_bio); + } + } + else + { + LL_WARNS("SECAPI") << "Could not allocate a file BIO" << LL_ENDL; + } +} + + +LLBasicCertificateStore::~LLBasicCertificateStore() +{ +} + + +// persist the store +void LLBasicCertificateStore::save() +{ + llofstream file_store(mFilename, llofstream::binary); + if(!file_store.fail()) + { + for(iterator cert = begin(); + cert != end(); + cert++) + { + std::string pem = (*cert)->getPem(); + if(!pem.empty()) + { + file_store << (*cert)->getPem() << std::endl; + } + } + file_store.close(); + } + else + { + LL_WARNS("SECAPI") << "Could not open certificate store " << mFilename << "for save" << LL_ENDL; + } +} + +// return the store id +std::string LLBasicCertificateStore::storeId() const +{ + // this is the basic handler which uses the CA.pem store, + // so we ignore this. + return std::string(""); +} + + +// +// LLBasicCertificateChain +// This class represents a chain of certs, each cert being signed by the next cert +// in the chain. Certs must be properly signed by the parent +LLBasicCertificateChain::LLBasicCertificateChain(const X509_STORE_CTX* store) +{ + + // we're passed in a context, which contains a cert, and a blob of untrusted + // certificates which compose the chain. + if((store == NULL) || (store->cert == NULL)) + { + LL_WARNS("SECAPI") << "An invalid store context was passed in when trying to create a certificate chain" << LL_ENDL; + return; + } + // grab the child cert + LLPointer current = new LLBasicCertificate(store->cert); + + add(current); + if(store->untrusted != NULL) + { + // if there are other certs in the chain, we build up a vector + // of untrusted certs so we can search for the parents of each + // consecutive cert. + LLBasicCertificateVector untrusted_certs; + for(int i = 0; i < sk_X509_num(store->untrusted); i++) + { + LLPointer cert = new LLBasicCertificate(sk_X509_value(store->untrusted, i)); + untrusted_certs.add(cert); + + } + while(untrusted_certs.size() > 0) + { + LLSD find_data = LLSD::emptyMap(); + LLSD cert_data = current->getLLSD(); + // we simply build the chain via subject/issuer name as the + // client should not have passed in multiple CA's with the same + // subject name. If they did, it'll come out in the wash during + // validation. + find_data[CERT_SUBJECT_NAME_STRING] = cert_data[CERT_ISSUER_NAME_STRING]; + LLBasicCertificateVector::iterator issuer = untrusted_certs.find(find_data); + if (issuer != untrusted_certs.end()) + { + current = untrusted_certs.erase(issuer); + add(current); + } + else + { + break; + } + } + } +} + + +// subdomain wildcard specifiers can be divided into 3 parts +// the part before the first *, the part after the first * but before +// the second *, and the part after the second *. +// It then iterates over the second for each place in the string +// that it matches. ie if the subdomain was testfoofoobar, and +// the wildcard was test*foo*bar, it would match test, then +// recursively match foofoobar and foobar + +bool _cert_subdomain_wildcard_match(const std::string& subdomain, + const std::string& wildcard) +{ + // split wildcard into the portion before the *, and the portion after + + int wildcard_pos = wildcard.find_first_of('*'); + // check the case where there is no wildcard. + if(wildcard_pos == wildcard.npos) + { + return (subdomain == wildcard); + } + + // we need to match the first part of the subdomain string up to the wildcard + // position + if(subdomain.substr(0, wildcard_pos) != wildcard.substr(0, wildcard_pos)) + { + // the first portions of the strings didn't match + return FALSE; + } + + // as the portion of the wildcard string before the * matched, we need to check the + // portion afterwards. Grab that portion. + std::string new_wildcard_string = wildcard.substr( wildcard_pos+1, wildcard.npos); + if(new_wildcard_string.empty()) + { + // we had nothing after the *, so it's an automatic match + return TRUE; + } + + // grab the portion of the remaining wildcard string before the next '*'. We need to find this + // within the remaining subdomain string. and then recursively check. + std::string new_wildcard_match_string = new_wildcard_string.substr(0, new_wildcard_string.find_first_of('*')); + + // grab the portion of the subdomain after the part that matched the initial wildcard portion + std::string new_subdomain = subdomain.substr(wildcard_pos, subdomain.npos); + + // iterate through the current subdomain, finding instances of the match string. + int sub_pos = new_subdomain.find_first_of(new_wildcard_match_string); + while(sub_pos != std::string::npos) + { + new_subdomain = new_subdomain.substr(sub_pos, std::string::npos); + if(_cert_subdomain_wildcard_match(new_subdomain, new_wildcard_string)) + { + return TRUE; + } + sub_pos = new_subdomain.find_first_of(new_wildcard_match_string, 1); + + + } + // didn't find any instances of the match string that worked in the subdomain, so fail. + return FALSE; +} + + +// RFC2459 does not address wildcards as part of it's name matching +// specification, and there is no RFC specifying wildcard matching, +// RFC2818 does a few statements about wildcard matching, but is very +// general. Generally, wildcard matching is per implementation, although +// it's pretty similar. +// in our case, we use the '*' wildcard character only, within each +// subdomain. The hostname and the CN specification should have the +// same number of subdomains. +// We then iterate that algorithm over each subdomain. +bool _cert_hostname_wildcard_match(const std::string& hostname, const std::string& common_name) +{ + std::string new_hostname = hostname; + std::string new_cn = common_name; + int subdomain_pos = new_hostname.find_first_of('.'); + int subcn_pos = new_cn.find_first_of('.'); + + while((subcn_pos != std::string::npos) && (subdomain_pos != std::string::npos)) + { + // snip out the first subdomain and cn element + + if(!_cert_subdomain_wildcard_match(new_hostname.substr(0, subdomain_pos), + new_cn.substr(0, subcn_pos))) + { + return FALSE; + } + new_hostname = new_hostname.substr(subdomain_pos+1, std::string::npos); + new_cn = new_cn.substr(subcn_pos+1, std::string::npos); + subdomain_pos = new_hostname.find_first_of('.'); + subcn_pos = new_cn.find_first_of('.'); + } + return _cert_subdomain_wildcard_match(new_hostname, new_cn); + +} + +// validate that the LLSD array in llsd_set contains the llsd_value +bool _LLSDArrayIncludesValue(const LLSD& llsd_set, LLSD llsd_value) +{ + for(LLSD::array_const_iterator set_value = llsd_set.beginArray(); + set_value != llsd_set.endArray(); + set_value++) + { + if(valueCompareLLSD((*set_value), llsd_value)) + { + return TRUE; + } + } + return FALSE; +} + +void _validateCert(int validation_policy, + const LLPointer cert, + const LLSD& validation_params, + int depth) +{ + + LLSD current_cert_info = cert->getLLSD(); + // check basic properties exist in the cert + if(!current_cert_info.has(CERT_SUBJECT_NAME) || !current_cert_info.has(CERT_SUBJECT_NAME_STRING)) + { + throw LLCertException(cert, "Cert doesn't have a Subject Name"); + } + + if(!current_cert_info.has(CERT_ISSUER_NAME_STRING)) + { + throw LLCertException(cert, "Cert doesn't have an Issuer Name"); + } + + // check basic properties exist in the cert + if(!current_cert_info.has(CERT_VALID_FROM) || !current_cert_info.has(CERT_VALID_TO)) + { + throw LLCertException(cert, "Cert doesn't have an expiration period"); + } + if (!current_cert_info.has(CERT_SHA1_DIGEST)) + { + throw LLCertException(cert, "No SHA1 digest"); + } + + if (validation_policy & VALIDATION_POLICY_TIME) + { + + LLDate validation_date(time(NULL)); + if(validation_params.has(CERT_VALIDATION_DATE)) + { + validation_date = validation_params[CERT_VALIDATION_DATE]; + } + + if((validation_date < current_cert_info[CERT_VALID_FROM].asDate()) || + (validation_date > current_cert_info[CERT_VALID_TO].asDate())) + { + throw LLCertValidationExpirationException(cert, validation_date); + } + } + if (validation_policy & VALIDATION_POLICY_SSL_KU) + { + if (current_cert_info.has(CERT_KEY_USAGE) && current_cert_info[CERT_KEY_USAGE].isArray() && + (!(_LLSDArrayIncludesValue(current_cert_info[CERT_KEY_USAGE], + LLSD((std::string)CERT_KU_DIGITAL_SIGNATURE))) || + !(_LLSDArrayIncludesValue(current_cert_info[CERT_KEY_USAGE], + LLSD((std::string)CERT_KU_KEY_ENCIPHERMENT))))) + { + throw LLCertKeyUsageValidationException(cert); + } + // only validate EKU if the cert has it + if(current_cert_info.has(CERT_EXTENDED_KEY_USAGE) && current_cert_info[CERT_EXTENDED_KEY_USAGE].isArray() && + (!_LLSDArrayIncludesValue(current_cert_info[CERT_EXTENDED_KEY_USAGE], + LLSD((std::string)CERT_EKU_SERVER_AUTH)))) + { + throw LLCertKeyUsageValidationException(cert); + } + } + if (validation_policy & VALIDATION_POLICY_CA_KU) + { + if (current_cert_info.has(CERT_KEY_USAGE) && current_cert_info[CERT_KEY_USAGE].isArray() && + (!_LLSDArrayIncludesValue(current_cert_info[CERT_KEY_USAGE], + (std::string)CERT_KU_CERT_SIGN))) + { + throw LLCertKeyUsageValidationException(cert); + } + } + + // validate basic constraints + if ((validation_policy & VALIDATION_POLICY_CA_BASIC_CONSTRAINTS) && + current_cert_info.has(CERT_BASIC_CONSTRAINTS) && + current_cert_info[CERT_BASIC_CONSTRAINTS].isMap()) + { + if(!current_cert_info[CERT_BASIC_CONSTRAINTS].has(CERT_BASIC_CONSTRAINTS_CA) || + !current_cert_info[CERT_BASIC_CONSTRAINTS][CERT_BASIC_CONSTRAINTS_CA]) + { + throw LLCertBasicConstraintsValidationException(cert); + } + if (current_cert_info[CERT_BASIC_CONSTRAINTS].has(CERT_BASIC_CONSTRAINTS_PATHLEN) && + ((current_cert_info[CERT_BASIC_CONSTRAINTS][CERT_BASIC_CONSTRAINTS_PATHLEN].asInteger() != 0) && + (depth > current_cert_info[CERT_BASIC_CONSTRAINTS][CERT_BASIC_CONSTRAINTS_PATHLEN].asInteger()))) + { + throw LLCertBasicConstraintsValidationException(cert); + } + } +} + +bool _verify_signature(LLPointer parent, + LLPointer child) +{ + bool verify_result = FALSE; + LLSD cert1 = parent->getLLSD(); + LLSD cert2 = child->getLLSD(); + X509 *signing_cert = parent->getOpenSSLX509(); + X509 *child_cert = child->getOpenSSLX509(); + if((signing_cert != NULL) && (child_cert != NULL)) + { + EVP_PKEY *pkey = X509_get_pubkey(signing_cert); + + + if(pkey) + { + int verify_code = X509_verify(child_cert, pkey); + verify_result = ( verify_code > 0); + EVP_PKEY_free(pkey); + } + else + { + LL_WARNS("SECAPI") << "Could not validate the cert chain signature, as the public key of the signing cert could not be retrieved" << LL_ENDL; + } + + } + else + { + LL_WARNS("SECAPI") << "Signature verification failed as there are no certs in the chain" << LL_ENDL; + } + if(child_cert) + { + X509_free(child_cert); + } + if(signing_cert) + { + X509_free(signing_cert); + } + return verify_result; +} + +// validate the certificate chain against a store. +// There are many aspects of cert validatioin policy involved in +// trust validation. The policies in this validation algorithm include +// * Hostname matching for SSL certs +// * Expiration time matching +// * Signature validation +// * Chain trust (is the cert chain trusted against the store) +// * Basic constraints +// * key usage and extended key usage +// TODO: We should add 'authority key identifier' for chaining. +// This algorithm doesn't simply validate the chain by itself +// and verify the last cert is in the certificate store, or points +// to a cert in the store. It validates whether any cert in the chain +// is trusted in the store, even if it's not the last one. +void LLBasicCertificateChain::validate(int validation_policy, + LLPointer ca_store, + const LLSD& validation_params) +{ + + if(size() < 1) + { + throw LLCertException(NULL, "No certs in chain"); + } + iterator current_cert = begin(); + LLSD current_cert_info = (*current_cert)->getLLSD(); + LLSD validation_date; + if (validation_params.has(CERT_VALIDATION_DATE)) + { + validation_date = validation_params[CERT_VALIDATION_DATE]; + } + + if (validation_policy & VALIDATION_POLICY_HOSTNAME) + { + if(!validation_params.has(CERT_HOSTNAME)) + { + throw LLCertException((*current_cert), "No hostname passed in for validation"); + } + if(!current_cert_info.has(CERT_SUBJECT_NAME) || !current_cert_info[CERT_SUBJECT_NAME].has(CERT_NAME_CN)) + { + throw LLInvalidCertificate((*current_cert)); + } + + LL_INFOS("SECAPI") << "Validating the hostname " << validation_params[CERT_HOSTNAME].asString() << + "against the cert CN " << current_cert_info[CERT_SUBJECT_NAME][CERT_NAME_CN].asString() << LL_ENDL; + if(!_cert_hostname_wildcard_match(validation_params[CERT_HOSTNAME].asString(), + current_cert_info[CERT_SUBJECT_NAME][CERT_NAME_CN].asString())) + { + throw LLCertValidationHostnameException(validation_params[CERT_HOSTNAME].asString(), + (*current_cert)); + } + } + + + int depth = 0; + LLPointer previous_cert; + // loop through the cert chain, validating the current cert against the next one. + while(current_cert != end()) + { + + int local_validation_policy = validation_policy; + if(current_cert == begin()) + { + // for the child cert, we don't validate CA stuff + local_validation_policy &= ~(VALIDATION_POLICY_CA_KU | + VALIDATION_POLICY_CA_BASIC_CONSTRAINTS); + } + else + { + // for non-child certs, we don't validate SSL Key usage + local_validation_policy &= ~VALIDATION_POLICY_SSL_KU; + if(!_verify_signature((*current_cert), + previous_cert)) + { + throw LLCertValidationInvalidSignatureException(previous_cert); + } + } + _validateCert(local_validation_policy, + (*current_cert), + validation_params, + depth); + + // look for a CA in the CA store that may belong to this chain. + LLSD cert_llsd = (*current_cert)->getLLSD(); + LLSD cert_search_params = LLSD::emptyMap(); + // is the cert itself in the store? + cert_search_params[CERT_SHA1_DIGEST] = cert_llsd[CERT_SHA1_DIGEST]; + LLCertificateStore::iterator found_store_cert = ca_store->find(cert_search_params); + if(found_store_cert != ca_store->end()) + { + return; + } + + // is the parent in the cert store? + + cert_search_params = LLSD::emptyMap(); + cert_search_params[CERT_SUBJECT_NAME_STRING] = cert_llsd[CERT_ISSUER_NAME_STRING]; + if (cert_llsd.has(CERT_AUTHORITY_KEY_IDENTIFIER)) + { + LLSD cert_aki = cert_llsd[CERT_AUTHORITY_KEY_IDENTIFIER]; + if(cert_aki.has(CERT_AUTHORITY_KEY_IDENTIFIER_ID)) + { + cert_search_params[CERT_SUBJECT_KEY_IDENTFIER] = cert_aki[CERT_AUTHORITY_KEY_IDENTIFIER_ID]; + } + if(cert_aki.has(CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL)) + { + cert_search_params[CERT_SERIAL_NUMBER] = cert_aki[CERT_AUTHORITY_KEY_IDENTIFIER_SERIAL]; + } + } + found_store_cert = ca_store->find(cert_search_params); + + if(found_store_cert != ca_store->end()) + { + LLSD foo = (*found_store_cert)->getLLSD(); + // validate the store cert against the depth + _validateCert(validation_policy & VALIDATION_POLICY_CA_BASIC_CONSTRAINTS, + (*found_store_cert), + LLSD(), + depth); + + // verify the signature of the CA + if(!_verify_signature((*found_store_cert), + (*current_cert))) + { + throw LLCertValidationInvalidSignatureException(*current_cert); + } + // successfully validated. + return; + } + previous_cert = (*current_cert); + current_cert++; + depth++; + } + if (validation_policy & VALIDATION_POLICY_TRUSTED) + { + LLPointer untrusted_ca_cert = (*this)[size()-1]; + // we reached the end without finding a trusted cert. + throw LLCertValidationTrustException((*this)[size()-1]); + + } +} + + +// LLSecAPIBasicHandler Class +// Interface handler class for the various security storage handlers. + +// We read the file on construction, and write it on destruction. This +// means multiple processes cannot modify the datastore. +LLSecAPIBasicHandler::LLSecAPIBasicHandler(const std::string& protected_data_file, + const std::string& legacy_password_path) +{ + mProtectedDataFilename = protected_data_file; + mProtectedDataMap = LLSD::emptyMap(); + mLegacyPasswordPath = legacy_password_path; + +} + +LLSecAPIBasicHandler::LLSecAPIBasicHandler() +{ +} + + +void LLSecAPIBasicHandler::init() +{ + mProtectedDataMap = LLSD::emptyMap(); + if (mProtectedDataFilename.length() == 0) + { + mProtectedDataFilename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, + "bin_conf.dat"); + mLegacyPasswordPath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "password.dat"); + + mProtectedDataFilename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, + "bin_conf.dat"); + std::string store_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, + "CA.pem"); + // copy the CA file to a user writable location so we can manipulate it. + // for this provider, by using a user writable file, there is a risk that + // an attacking program can modify the file, but OS dependent providers + // will reduce that risk. + // by using a user file, modifications will be limited to one user if + // we read-only the main file + if (!LLFile::isfile(store_file)) + { + + std::string ca_file_path = gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS, "CA.pem"); + llifstream ca_file(ca_file_path.c_str(), llifstream::binary | llifstream::in); + llofstream copied_store_file(store_file.c_str(), llofstream::binary | llofstream::out); + + while(!ca_file.fail()) + { + char buffer[BUFFER_READ_SIZE]; + ca_file.read(buffer, sizeof(buffer)); + copied_store_file.write(buffer, ca_file.gcount()); + } + ca_file.close(); + copied_store_file.close(); + } + LL_INFOS("SECAPI") << "Loading certificate store from " << store_file << LL_ENDL; + mStore = new LLBasicCertificateStore(store_file); + } + _readProtectedData(); // initialize mProtectedDataMap + // may throw LLProtectedDataException if saved datamap is not decryptable +} +LLSecAPIBasicHandler::~LLSecAPIBasicHandler() +{ + _writeProtectedData(); +} + +void LLSecAPIBasicHandler::_readProtectedData() +{ + // attempt to load the file into our map + LLPointer parser = new LLSDXMLParser(); + llifstream protected_data_stream(mProtectedDataFilename.c_str(), + llifstream::binary); + + if (!protected_data_stream.fail()) { + int offset; + U8 salt[STORE_SALT_SIZE]; + U8 buffer[BUFFER_READ_SIZE]; + U8 decrypted_buffer[BUFFER_READ_SIZE]; + int decrypted_length; + unsigned char MACAddress[MAC_ADDRESS_BYTES]; + LLUUID::getNodeID(MACAddress); + LLXORCipher cipher(MACAddress, MAC_ADDRESS_BYTES); + + // read in the salt and key + protected_data_stream.read((char *)salt, STORE_SALT_SIZE); + offset = 0; + if (protected_data_stream.gcount() < STORE_SALT_SIZE) + { + throw LLProtectedDataException("Config file too short."); + } + + cipher.decrypt(salt, STORE_SALT_SIZE); + + // totally lame. As we're not using the OS level protected data, we need to + // at least obfuscate the data. We do this by using a salt stored at the head of the file + // to encrypt the data, therefore obfuscating it from someone using simple existing tools. + // We do include the MAC address as part of the obfuscation, which would require an + // attacker to get the MAC address as well as the protected store, which improves things + // somewhat. It would be better to use the password, but as this store + // will be used to store the SL password when the user decides to have SL remember it, + // so we can't use that. OS-dependent store implementations will use the OS password/storage + // mechanisms and are considered to be more secure. + // We've a strong intent to move to OS dependent protected data stores. + + + // read in the rest of the file. + EVP_CIPHER_CTX ctx; + EVP_CIPHER_CTX_init(&ctx); + EVP_DecryptInit(&ctx, EVP_rc4(), salt, NULL); + // allocate memory: + std::string decrypted_data; + + while(protected_data_stream.good()) { + // read data as a block: + protected_data_stream.read((char *)buffer, BUFFER_READ_SIZE); + + EVP_DecryptUpdate(&ctx, decrypted_buffer, &decrypted_length, + buffer, protected_data_stream.gcount()); + decrypted_data.append((const char *)decrypted_buffer, protected_data_stream.gcount()); + } + + // RC4 is a stream cipher, so we don't bother to EVP_DecryptFinal, as there is + // no block padding. + EVP_CIPHER_CTX_cleanup(&ctx); + std::istringstream parse_stream(decrypted_data); + if (parser->parse(parse_stream, mProtectedDataMap, + LLSDSerialize::SIZE_UNLIMITED) == LLSDParser::PARSE_FAILURE) + { + throw LLProtectedDataException("Config file cannot be decrypted."); + } + } +} + +void LLSecAPIBasicHandler::_writeProtectedData() +{ + std::ostringstream formatted_data_ostream; + U8 salt[STORE_SALT_SIZE]; + U8 buffer[BUFFER_READ_SIZE]; + U8 encrypted_buffer[BUFFER_READ_SIZE]; + + + if(mProtectedDataMap.isUndefined()) + { + LLFile::remove(mProtectedDataFilename); + return; + } + // create a string with the formatted data. + LLSDSerialize::toXML(mProtectedDataMap, formatted_data_ostream); + std::istringstream formatted_data_istream(formatted_data_ostream.str()); + // generate the seed + RAND_bytes(salt, STORE_SALT_SIZE); + + + // write to a temp file so we don't clobber the initial file if there is + // an error. + std::string tmp_filename = mProtectedDataFilename + ".tmp"; + + llofstream protected_data_stream(tmp_filename.c_str(), + llofstream::binary); + try + { + + EVP_CIPHER_CTX ctx; + EVP_CIPHER_CTX_init(&ctx); + EVP_EncryptInit(&ctx, EVP_rc4(), salt, NULL); + unsigned char MACAddress[MAC_ADDRESS_BYTES]; + LLUUID::getNodeID(MACAddress); + LLXORCipher cipher(MACAddress, MAC_ADDRESS_BYTES); + cipher.encrypt(salt, STORE_SALT_SIZE); + protected_data_stream.write((const char *)salt, STORE_SALT_SIZE); + + while (formatted_data_istream.good()) + { + formatted_data_istream.read((char *)buffer, BUFFER_READ_SIZE); + if(formatted_data_istream.gcount() == 0) + { + break; + } + int encrypted_length; + EVP_EncryptUpdate(&ctx, encrypted_buffer, &encrypted_length, + buffer, formatted_data_istream.gcount()); + protected_data_stream.write((const char *)encrypted_buffer, encrypted_length); + } + + // no EVP_EncrypteFinal, as this is a stream cipher + EVP_CIPHER_CTX_cleanup(&ctx); + + protected_data_stream.close(); + } + catch (...) + { + // it's good practice to clean up any secure information on error + // (even though this file isn't really secure. Perhaps in the future + // it may be, however. + LLFile::remove(tmp_filename); + throw LLProtectedDataException("Error writing Protected Data Store"); + } + + // move the temporary file to the specified file location. + if((((LLFile::isfile(mProtectedDataFilename) != 0) && + (LLFile::remove(mProtectedDataFilename) != 0))) || + (LLFile::rename(tmp_filename, mProtectedDataFilename))) + { + LLFile::remove(tmp_filename); + throw LLProtectedDataException("Could not overwrite protected data store"); + } +} + +// instantiate a certificate from a pem string +LLPointer LLSecAPIBasicHandler::getCertificate(const std::string& pem_cert) +{ + LLPointer result = new LLBasicCertificate(pem_cert); + return result; +} + + + +// instiate a certificate from an openssl X509 structure +LLPointer LLSecAPIBasicHandler::getCertificate(X509* openssl_cert) +{ + LLPointer result = new LLBasicCertificate(openssl_cert); + return result; +} + +// instantiate a chain from an X509_STORE_CTX +LLPointer LLSecAPIBasicHandler::getCertificateChain(const X509_STORE_CTX* chain) +{ + LLPointer result = new LLBasicCertificateChain(chain); + return result; +} + +// instantiate a cert store given it's id. if a persisted version +// exists, it'll be loaded. If not, one will be created (but not +// persisted) +LLPointer LLSecAPIBasicHandler::getCertificateStore(const std::string& store_id) +{ + return mStore; +} + +// retrieve protected data +LLSD LLSecAPIBasicHandler::getProtectedData(const std::string& data_type, + const std::string& data_id) +{ + + if (mProtectedDataMap.has(data_type) && + mProtectedDataMap[data_type].isMap() && + mProtectedDataMap[data_type].has(data_id)) + { + return mProtectedDataMap[data_type][data_id]; + } + + return LLSD(); +} + +void LLSecAPIBasicHandler::deleteProtectedData(const std::string& data_type, + const std::string& data_id) +{ + if (mProtectedDataMap.has(data_type) && + mProtectedDataMap[data_type].isMap() && + mProtectedDataMap[data_type].has(data_id)) + { + mProtectedDataMap[data_type].erase(data_id); + } +} + + +// +// persist data in a protected store +// +void LLSecAPIBasicHandler::setProtectedData(const std::string& data_type, + const std::string& data_id, + const LLSD& data) +{ + if (!mProtectedDataMap.has(data_type) || !mProtectedDataMap[data_type].isMap()) { + mProtectedDataMap[data_type] = LLSD::emptyMap(); + } + + mProtectedDataMap[data_type][data_id] = data; +} + +// +// Create a credential object from an identifier and authenticator. credentials are +// per grid. +LLPointer LLSecAPIBasicHandler::createCredential(const std::string& grid, + const LLSD& identifier, + const LLSD& authenticator) +{ + LLPointer result = new LLSecAPIBasicCredential(grid); + result->setCredentialData(identifier, authenticator); + return result; +} + +// Load a credential from the credential store, given the grid +LLPointer LLSecAPIBasicHandler::loadCredential(const std::string& grid) +{ + LLSD credential = getProtectedData("credential", grid); + LLPointer result = new LLSecAPIBasicCredential(grid); + if(credential.isMap() && + credential.has("identifier")) + { + + LLSD identifier = credential["identifier"]; + LLSD authenticator; + if (credential.has("authenticator")) + { + authenticator = credential["authenticator"]; + } + result->setCredentialData(identifier, authenticator); + } + else + { + // credential was not in protected storage, so pull the credential + // from the legacy store. + std::string first_name = gSavedSettings.getString("FirstName"); + std::string last_name = gSavedSettings.getString("LastName"); + + if ((first_name != "") && + (last_name != "")) + { + LLSD identifier = LLSD::emptyMap(); + LLSD authenticator; + identifier["type"] = "agent"; + identifier["first_name"] = first_name; + identifier["last_name"] = last_name; + + std::string legacy_password = _legacyLoadPassword(); + if (legacy_password.length() > 0) + { + authenticator = LLSD::emptyMap(); + authenticator["type"] = "hash"; + authenticator["algorithm"] = "md5"; + authenticator["secret"] = legacy_password; + } + result->setCredentialData(identifier, authenticator); + } + } + return result; +} + +// Save the credential to the credential store. Save the authenticator also if requested. +// That feature is used to implement the 'remember password' functionality. +void LLSecAPIBasicHandler::saveCredential(LLPointer cred, bool save_authenticator) +{ + LLSD credential = LLSD::emptyMap(); + credential["identifier"] = cred->getIdentifier(); + if (save_authenticator) + { + credential["authenticator"] = cred->getAuthenticator(); + } + LL_INFOS("SECAPI") << "Saving Credential " << cred->getGrid() << ":" << cred->userID() << " " << save_authenticator << LL_ENDL; + setProtectedData("credential", cred->getGrid(), credential); + //*TODO: If we're saving Agni credentials, should we write the + // credentials to the legacy password.dat/etc? + _writeProtectedData(); +} + +// Remove a credential from the credential store. +void LLSecAPIBasicHandler::deleteCredential(LLPointer cred) +{ + LLSD undefVal; + deleteProtectedData("credential", cred->getGrid()); + cred->setCredentialData(undefVal, undefVal); + _writeProtectedData(); +} + +// load the legacy hash for agni, and decrypt it given the +// mac address +std::string LLSecAPIBasicHandler::_legacyLoadPassword() +{ + const S32 HASHED_LENGTH = 32; + std::vector buffer(HASHED_LENGTH); + llifstream password_file(mLegacyPasswordPath, llifstream::binary); + + if(password_file.fail()) + { + return std::string(""); + } + + password_file.read((char*)&buffer[0], buffer.size()); + if(password_file.gcount() != buffer.size()) + { + return std::string(""); + } + + // Decipher with MAC address + unsigned char MACAddress[MAC_ADDRESS_BYTES]; + LLUUID::getNodeID(MACAddress); + LLXORCipher cipher(MACAddress, 6); + cipher.decrypt(&buffer[0], buffer.size()); + + return std::string((const char*)&buffer[0], buffer.size()); +} + + +// return an identifier for the user +std::string LLSecAPIBasicCredential::userID() const +{ + if (!mIdentifier.isMap()) + { + return mGrid + "(null)"; + } + else if ((std::string)mIdentifier["type"] == "agent") + { + return (std::string)mIdentifier["first_name"] + "_" + (std::string)mIdentifier["last_name"]; + } + else if ((std::string)mIdentifier["type"] == "account") + { + return (std::string)mIdentifier["account_name"]; + } + + return "unknown"; + +} + +// return a printable user identifier +std::string LLSecAPIBasicCredential::asString() const +{ + if (!mIdentifier.isMap()) + { + return mGrid + ":(null)"; + } + else if ((std::string)mIdentifier["type"] == "agent") + { + return mGrid + ":" + (std::string)mIdentifier["first_name"] + " " + (std::string)mIdentifier["last_name"]; + } + else if ((std::string)mIdentifier["type"] == "account") + { + return mGrid + ":" + (std::string)mIdentifier["account_name"]; + } + + return mGrid + ":(unknown type)"; +} + + +bool valueCompareLLSD(const LLSD& lhs, const LLSD& rhs) +{ + if (lhs.type() != rhs.type()) + { + return FALSE; + } + if (lhs.isMap()) + { + // iterate through the map, verifying the right hand side has all of the + // values that the left hand side has. + for (LLSD::map_const_iterator litt = lhs.beginMap(); + litt != lhs.endMap(); + litt++) + { + if (!rhs.has(litt->first)) + { + return FALSE; + } + } + + // Now validate that the left hand side has everything the + // right hand side has, and that the values are equal. + for (LLSD::map_const_iterator ritt = rhs.beginMap(); + ritt != rhs.endMap(); + ritt++) + { + if (!lhs.has(ritt->first)) + { + return FALSE; + } + if (!valueCompareLLSD(lhs[ritt->first], ritt->second)) + { + return FALSE; + } + } + return TRUE; + } + else if (lhs.isArray()) + { + LLSD::array_const_iterator ritt = rhs.beginArray(); + // iterate through the array, comparing + for (LLSD::array_const_iterator litt = lhs.beginArray(); + litt != lhs.endArray(); + litt++) + { + if (!valueCompareLLSD(*ritt, *litt)) + { + return FALSE; + } + ritt++; + } + + return (ritt == rhs.endArray()); + } + else + { + // simple type, compare as string + return (lhs.asString() == rhs.asString()); + } + +} diff --git a/indra/newview/llsechandler_basic.h b/indra/newview/llsechandler_basic.h new file mode 100644 index 0000000000..4bbb73f062 --- /dev/null +++ b/indra/newview/llsechandler_basic.h @@ -0,0 +1,285 @@ +/** + * @file llsechandler_basic.h + * @brief Security API for services such as certificate handling + * secure local storage, etc. + * + * $LicenseInfo:firstyear=2009&license=viewergpl$ + * + * Copyright (c) 2009, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlife.com/developers/opensource/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at http://secondlife.com/developers/opensource/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#ifndef LLSECHANDLER_BASIC +#define LLSECHANDLER_BASIC + +#include "llsecapi.h" +#include +#include + +// helpers +extern LLSD cert_name_from_X509_NAME(X509_NAME* name); +extern std::string cert_string_name_from_X509_NAME(X509_NAME* name); +extern std::string cert_string_from_asn1_integer(ASN1_INTEGER* value); +extern LLDate cert_date_from_asn1_time(ASN1_TIME* asn1_time); +extern std::string cert_get_digest(const std::string& digest_type, X509 *cert); + + +// class LLCertificate +// +class LLBasicCertificate : public LLCertificate +{ +public: + LOG_CLASS(LLBasicCertificate); + + LLBasicCertificate(const std::string& pem_cert); + LLBasicCertificate(X509* openSSLX509); + + virtual ~LLBasicCertificate(); + + virtual std::string getPem() const; + virtual std::vector getBinary() const; + virtual LLSD getLLSD() const; + + virtual X509* getOpenSSLX509() const; + + // set llsd elements for testing + void setLLSD(const std::string name, const LLSD& value) { mLLSDInfo[name] = value; } +protected: + + // certificates are stored as X509 objects, as validation and + // other functionality is via openssl + X509* mCert; + + LLSD& _initLLSD(); + LLSD mLLSDInfo; +}; + + +// class LLBasicCertificateVector +// Class representing a list of certificates +// This implementation uses a stl vector of certificates. +class LLBasicCertificateVector : virtual public LLCertificateVector +{ + +public: + LLBasicCertificateVector() {} + + virtual ~LLBasicCertificateVector() {} + + // Implementation of the basic iterator implementation. + // The implementation uses a vector iterator derived from + // the vector in the LLBasicCertificateVector class + class BasicIteratorImpl : public iterator_impl + { + public: + BasicIteratorImpl(std::vector >::iterator _iter) { mIter = _iter;} + virtual ~BasicIteratorImpl() {}; + // seek forward or back. Used by the operator++/operator-- implementations + virtual void seek(bool incr) + { + if(incr) + { + mIter++; + } + else + { + mIter--; + } + } + // create a copy of the iterator implementation class, used by the iterator copy constructor + virtual LLPointer clone() const + { + return new BasicIteratorImpl(mIter); + } + + virtual bool equals(const LLPointer& _iter) const + { + const BasicIteratorImpl *rhs_iter = dynamic_cast(_iter.get()); + return (mIter == rhs_iter->mIter); + } + virtual LLPointer get() + { + return *mIter; + } + protected: + friend class LLBasicCertificateVector; + std::vector >::iterator mIter; + }; + + // numeric index of the vector + virtual LLPointer operator[](int _index) { return mCerts[_index];} + + // Iteration + virtual iterator begin() { return iterator(new BasicIteratorImpl(mCerts.begin())); } + + virtual iterator end() { return iterator(new BasicIteratorImpl(mCerts.end())); } + + // find a cert given params + virtual iterator find(const LLSD& params); + + // return the number of certs in the store + virtual int size() const { return mCerts.size(); } + + // insert the cert to the store. if a copy of the cert already exists in the store, it is removed first + virtual void add(LLPointer cert) { insert(end(), cert); } + + // insert the cert to the store. if a copy of the cert already exists in the store, it is removed first + virtual void insert(iterator _iter, LLPointer cert); + + // remove a certificate from the store + virtual LLPointer erase(iterator _iter); + +protected: + std::vector >mCerts; +}; + +// class LLCertificateStore +// represents a store of certificates, typically a store of root CA +// certificates. The store can be persisted, and can be used to validate +// a cert chain +// +class LLBasicCertificateStore : virtual public LLBasicCertificateVector, public LLCertificateStore +{ +public: + LLBasicCertificateStore(const std::string& filename); + void load_from_file(const std::string& filename); + + virtual ~LLBasicCertificateStore(); + + // persist the store + virtual void save(); + + // return the store id + virtual std::string storeId() const; + +protected: + std::vector >mCerts; + std::string mFilename; +}; + +// class LLCertificateChain +// Class representing a chain of certificates in order, with the +// first element being the child cert. +class LLBasicCertificateChain : virtual public LLBasicCertificateVector, public LLCertificateChain +{ + +public: + LLBasicCertificateChain(const X509_STORE_CTX * store); + + virtual ~LLBasicCertificateChain() {} + + // validate a certificate chain against a certificate store, using the + // given validation policy. + virtual void validate(int validation_policy, + LLPointer ca_store, + const LLSD& validation_params); +}; + + + +// LLSecAPIBasicCredential class +class LLSecAPIBasicCredential : public LLCredential +{ +public: + LLSecAPIBasicCredential(const std::string& grid) : LLCredential(grid) {} + virtual ~LLSecAPIBasicCredential() {} + // return a value representing the user id, (could be guid, name, whatever) + virtual std::string userID() const; + + // printible string identifying the credential. + virtual std::string asString() const; +}; + +// LLSecAPIBasicHandler Class +// Interface handler class for the various security storage handlers. +class LLSecAPIBasicHandler : public LLSecAPIHandler +{ +public: + + LLSecAPIBasicHandler(const std::string& protected_data_filename, + const std::string& legacy_password_path); + LLSecAPIBasicHandler(); + + void init(); + + virtual ~LLSecAPIBasicHandler(); + + // instantiate a certificate from a pem string + virtual LLPointer getCertificate(const std::string& pem_cert); + + + // instiate a certificate from an openssl X509 structure + virtual LLPointer getCertificate(X509* openssl_cert); + + // instantiate a chain from an X509_STORE_CTX + virtual LLPointer getCertificateChain(const X509_STORE_CTX* chain); + + // instantiate a cert store given it's id. if a persisted version + // exists, it'll be loaded. If not, one will be created (but not + // persisted) + virtual LLPointer getCertificateStore(const std::string& store_id); + + // persist data in a protected store + virtual void setProtectedData(const std::string& data_type, + const std::string& data_id, + const LLSD& data); + + // retrieve protected data + virtual LLSD getProtectedData(const std::string& data_type, + const std::string& data_id); + + // delete a protected data item from the store + virtual void deleteProtectedData(const std::string& data_type, + const std::string& data_id); + + // credential management routines + + virtual LLPointer createCredential(const std::string& grid, + const LLSD& identifier, + const LLSD& authenticator); + + virtual LLPointer loadCredential(const std::string& grid); + + virtual void saveCredential(LLPointer cred, bool save_authenticator); + + virtual void deleteCredential(LLPointer cred); + +protected: + void _readProtectedData(); + void _writeProtectedData(); + std::string _legacyLoadPassword(); + + std::string mProtectedDataFilename; + LLSD mProtectedDataMap; + LLPointer mStore; + + std::string mLegacyPasswordPath; +}; + +bool valueCompareLLSD(const LLSD& lhs, const LLSD& rhs); + +#endif // LLSECHANDLER_BASIC + + + diff --git a/indra/newview/llselectmgr.cpp b/indra/newview/llselectmgr.cpp index d03a492cd1..3ef810c3e9 100644 --- a/indra/newview/llselectmgr.cpp +++ b/indra/newview/llselectmgr.cpp @@ -2435,7 +2435,7 @@ BOOL LLSelectMgr::selectGetCreator(LLUUID& result_id, std::string& name) if (identical) { - name = LLSLURL::buildCommand("agent", first_id, "inspect"); + name = LLSLURL("agent", first_id, "inspect").getSLURLString(); } else { @@ -2494,11 +2494,11 @@ BOOL LLSelectMgr::selectGetOwner(LLUUID& result_id, std::string& name) BOOL public_owner = (first_id.isNull() && !first_group_owned); if (first_group_owned) { - name = LLSLURL::buildCommand("group", first_id, "inspect"); + name = LLSLURL("group", first_id, "inspect").getSLURLString(); } else if(!public_owner) { - name = LLSLURL::buildCommand("agent", first_id, "inspect"); + name = LLSLURL("agent", first_id, "inspect").getSLURLString(); } else { @@ -2558,7 +2558,7 @@ BOOL LLSelectMgr::selectGetLastOwner(LLUUID& result_id, std::string& name) BOOL public_owner = (first_id.isNull()); if(!public_owner) { - name = LLSLURL::buildCommand("agent", first_id, "inspect"); + name = LLSLURL("agent", first_id, "inspect").getSLURLString(); } else { diff --git a/indra/newview/llslurl.cpp b/indra/newview/llslurl.cpp index 5d20e280b5..ff7e479368 100644 --- a/indra/newview/llslurl.cpp +++ b/indra/newview/llslurl.cpp @@ -1,10 +1,11 @@ /** - * @file llslurl.cpp - * @brief SLURL manipulation + * @file llurlsimstring.cpp (was llsimurlstring.cpp) + * @brief Handles "SLURL fragments" like Ahern/123/45 for + * startup processing, login screen, prefs, etc. * - * $LicenseInfo:firstyear=2009&license=viewergpl$ + * $LicenseInfo:firstyear=2010&license=viewergpl$ * - * Copyright (c) 2009, Linden Research, Inc. + * Copyright (c) 2006-2010, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -12,13 +13,12 @@ * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * online at http://secondlife.com/developers/opensource/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * online at http://secondlife.com/developers/opensource/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, @@ -34,155 +34,443 @@ #include "llslurl.h" -#include "llweb.h" - -#include "llurlregistry.h" - -const std::string LLSLURL::PREFIX_SL_HELP = "secondlife://app."; -const std::string LLSLURL::PREFIX_SL = "sl://"; -const std::string LLSLURL::PREFIX_SECONDLIFE = "secondlife://"; -const std::string LLSLURL::PREFIX_SLURL_OLD = "http://slurl.com/secondlife/"; - -// For DnD - even though www.slurl.com redirects to slurl.com in a browser, you can copy and drag +#include "llpanellogin.h" +#include "llviewercontrol.h" +#include "llviewernetwork.h" +#include "llfiltersd2xmlrpc.h" +#include "curl/curl.h" +const char* LLSLURL::SLURL_HTTP_SCHEME = "http"; +const char* LLSLURL::SLURL_HTTPS_SCHEME = "https"; +const char* LLSLURL::SLURL_SECONDLIFE_SCHEME = "secondlife"; +const char* LLSLURL::SLURL_SECONDLIFE_PATH = "secondlife"; +const char* LLSLURL::SLURL_COM = "slurl.com"; +// For DnD - even though www.slurl.com redirects to slurl.com in a browser, you can copy and drag // text with www.slurl.com or a link explicitly pointing at www.slurl.com so testing for this // version is required also. -const std::string LLSLURL::PREFIX_SLURL_WWW = "http://www.slurl.com/secondlife/"; -const std::string LLSLURL::PREFIX_SLURL = "http://maps.secondlife.com/secondlife/"; +const char* LLSLURL::WWW_SLURL_COM = "www.slurl.com"; +const char* LLSLURL::MAPS_SECONDLIFE_COM = "maps.secondlife.com"; +const char* LLSLURL::SLURL_X_GRID_LOCATION_INFO_SCHEME = "x-grid-location-info"; +const char* LLSLURL::SLURL_APP_PATH = "app"; +const char* LLSLURL::SLURL_REGION_PATH = "region"; +const char* LLSLURL::SIM_LOCATION_HOME = "home"; +const char* LLSLURL::SIM_LOCATION_LAST = "last"; -const std::string LLSLURL::APP_TOKEN = "app/"; - -// static -std::string LLSLURL::stripProtocol(const std::string& url) +// resolve a simstring from a slurl +LLSLURL::LLSLURL(const std::string& slurl) { - std::string stripped = url; - if (matchPrefix(stripped, PREFIX_SL_HELP)) - { - stripped.erase(0, PREFIX_SL_HELP.length()); - } - else if (matchPrefix(stripped, PREFIX_SL)) - { - stripped.erase(0, PREFIX_SL.length()); - } - else if (matchPrefix(stripped, PREFIX_SECONDLIFE)) - { - stripped.erase(0, PREFIX_SECONDLIFE.length()); - } - else if (matchPrefix(stripped, PREFIX_SLURL)) + // by default we go to agni. + mType = INVALID; + LL_INFOS("AppInit") << "SLURL: " << slurl << LL_ENDL; + if(slurl == SIM_LOCATION_HOME) { - stripped.erase(0, PREFIX_SLURL.length()); + mType = HOME_LOCATION; } - else if (matchPrefix(stripped, PREFIX_SLURL_OLD)) + else if(slurl.empty() || (slurl == SIM_LOCATION_LAST)) { - stripped.erase(0, PREFIX_SLURL_OLD.length()); + + mType = LAST_LOCATION; } - else if (matchPrefix(stripped, PREFIX_SLURL_WWW)) + else { - stripped.erase(0, PREFIX_SLURL_WWW.length()); + LLURI slurl_uri; + // parse the slurl as a uri + if(slurl.find(':') == std::string::npos) + { + // There may be no scheme ('secondlife:' etc.) passed in. In that case + // we want to normalize the slurl by putting the appropriate scheme + // in front of the slurl. So, we grab the appropriate slurl base + // from the grid manager which may be http://slurl.com/secondlife/ for maingrid, or + // https:///region/ for Standalone grid (the word region, not the region name) + // these slurls are typically passed in from the 'starting location' box on the login panel, + // where the user can type in /// + std::string fixed_slurl = LLGridManager::getInstance()->getSLURLBase(); + // the slurl that was passed in might have a prepended /, or not. So, + // we strip off the prepended '/' so we don't end up with http://slurl.com/secondlife//// + // or some such. + + if(slurl[0] == '/') + { + fixed_slurl += slurl.substr(1); + } + else + { + fixed_slurl += slurl; + } + // We then load the slurl into a LLURI form + slurl_uri = LLURI(fixed_slurl); + } + else + { + // as we did have a scheme, implying a URI style slurl, we + // simply parse it as a URI + slurl_uri = LLURI(slurl); + } + + LLSD path_array = slurl_uri.pathArray(); + + // determine whether it's a maingrid URI or an Standalone/open style URI + // by looking at the scheme. If it's a 'secondlife:' slurl scheme or + // 'sl:' scheme, we know it's maingrid + + // At the end of this if/else block, we'll have determined the grid, + // and the slurl type (APP or LOCATION) + if(slurl_uri.scheme() == LLSLURL::SLURL_SECONDLIFE_SCHEME) + { + // parse a maingrid style slurl. We know the grid is maingrid + // so grab it. + // A location slurl for maingrid (with the special schemes) can be in the form + // secondlife:///// + // or + // secondlife:///secondlife//// + // where if grid is empty, it specifies Agni + + // An app style slurl for maingrid can be + // secondlife:///app/ + // where an empty grid implies Agni + + // we'll start by checking the top of the 'path' which will be + // either 'app', 'secondlife', or . + + // default to maingrid + + mGrid = MAINGRID; + + if ((path_array[0].asString() == LLSLURL::SLURL_SECONDLIFE_PATH) || + (path_array[0].asString() == LLSLURL::SLURL_APP_PATH)) + { + // it's in the form secondlife:///(app|secondlife) + // so parse the grid name to derive the grid ID + if (!slurl_uri.hostName().empty()) + { + mGrid = LLGridManager::getInstance()->getGridByLabel(slurl_uri.hostName()); + } + else if(path_array[0].asString() == LLSLURL::SLURL_SECONDLIFE_PATH) + { + // If the slurl is in the form secondlife:///secondlife/ form, + // then we are in fact on maingrid. + mGrid = MAINGRID; + } + else if(path_array[0].asString() == LLSLURL::SLURL_APP_PATH) + { + // for app style slurls, where no grid name is specified, assume the currently + // selected or logged in grid. + mGrid = LLGridManager::getInstance()->getGrid(); + } + + if(mGrid.empty()) + { + // we couldn't find the grid in the grid manager, so bail + return; + } + // set the type as appropriate. + if(path_array[0].asString() == LLSLURL::SLURL_SECONDLIFE_PATH) + { + mType = LOCATION; + } + else + { + mType = APP; + } + path_array.erase(0); + } + else + { + // it wasn't a /secondlife/ or /app/, so it must be secondlife:// + // therefore the hostname will be the region name, and it's a location type + mType = LOCATION; + // 'normalize' it so the region name is in fact the head of the path_array + path_array.insert(0, slurl_uri.hostName()); + } + } + else if((slurl_uri.scheme() == LLSLURL::SLURL_HTTP_SCHEME) || + (slurl_uri.scheme() == LLSLURL::SLURL_HTTPS_SCHEME) || + (slurl_uri.scheme() == LLSLURL::SLURL_X_GRID_LOCATION_INFO_SCHEME)) + { + // We're dealing with either a Standalone style slurl or slurl.com slurl + if ((slurl_uri.hostName() == LLSLURL::SLURL_COM) || + (slurl_uri.hostName() == LLSLURL::WWW_SLURL_COM) || + (slurl_uri.hostName() == LLSLURL::MAPS_SECONDLIFE_COM)) + { + // slurl.com implies maingrid + mGrid = MAINGRID; + } + else + { + // As it's a Standalone grid/open, we will always have a hostname, as Standalone/open style + // urls are properly formed, unlike the stinky maingrid style + mGrid = slurl_uri.hostName(); + } + if (path_array.size() == 0) + { + // um, we need a path... + return; + } + + // we need to normalize the urls so + // the path portion starts with the 'command' that we want to do + // it can either be region or app. + if ((path_array[0].asString() == LLSLURL::SLURL_REGION_PATH) || + (path_array[0].asString() == LLSLURL::SLURL_SECONDLIFE_PATH)) + { + // strip off 'region' or 'secondlife' + path_array.erase(0); + // it's a location + mType = LOCATION; + } + else if (path_array[0].asString() == LLSLURL::SLURL_APP_PATH) + { + mType = APP; + path_array.erase(0); + // leave app appended. + } + else + { + // not a valid https/http/x-grid-location-info slurl, so it'll likely just be a URL + return; + } + } + else + { + // invalid scheme, so bail + return; + } + + + if(path_array.size() == 0) + { + // we gotta have some stuff after the specifier as to whether it's a region or command + return; + } + + // now that we know whether it's an app slurl or a location slurl, + // parse the slurl into the proper data structures. + if(mType == APP) + { + // grab the app command type and strip it (could be a command to jump somewhere, + // or whatever ) + mAppCmd = path_array[0].asString(); + path_array.erase(0); + + // Grab the parameters + mAppPath = path_array; + // and the query + mAppQuery = slurl_uri.query(); + mAppQueryMap = slurl_uri.queryMap(); + return; + } + else if(mType == LOCATION) + { + // at this point, head of the path array should be [ , , , ] where x, y and z + // are collectively optional + // are optional + mRegion = LLURI::unescape(path_array[0].asString()); + path_array.erase(0); + + // parse the x, y, z + if(path_array.size() >= 3) + { + + mPosition = LLVector3(path_array); + if((F32(mPosition[VX]) < 0.f) || + (mPosition[VX] > REGION_WIDTH_METERS) || + (F32(mPosition[VY]) < 0.f) || + (mPosition[VY] > REGION_WIDTH_METERS) || + (F32(mPosition[VZ]) < 0.f) || + (mPosition[VZ] > REGION_HEIGHT_METERS)) + { + mType = INVALID; + return; + } + + } + else + { + // if x, y and z were not fully passed in, go to the middle of the region. + // teleport will adjust the actual location to make sure you're on the ground + // and such + mPosition = LLVector3(REGION_WIDTH_METERS/2, REGION_WIDTH_METERS/2, 0); + } + } } - - return stripped; } -// static -bool LLSLURL::isSLURL(const std::string& url) + +// Create a slurl for the middle of the region +LLSLURL::LLSLURL(const std::string& grid, + const std::string& region) { - if (matchPrefix(url, PREFIX_SL_HELP)) return true; - if (matchPrefix(url, PREFIX_SL)) return true; - if (matchPrefix(url, PREFIX_SECONDLIFE)) return true; - if (matchPrefix(url, PREFIX_SLURL)) return true; - if (matchPrefix(url, PREFIX_SLURL_OLD)) return true; - if (matchPrefix(url, PREFIX_SLURL_WWW)) return true; - - return false; + mGrid = grid; + mRegion = region; + mType = LOCATION; + mPosition = LLVector3((F64)REGION_WIDTH_METERS/2, (F64)REGION_WIDTH_METERS/2, 0); } -bool LLSLURL::isValidSLURL(const std::string& url) + + +// create a slurl given the position. The position will be modded with the region +// width handling global positions as well +LLSLURL::LLSLURL(const std::string& grid, + const std::string& region, + const LLVector3& position) { - std::string temp_url(url); - //"www." may appear in DnD- see description of PREFIX_SLURL_WWW. - // If it is found, we remove it because it isn't expected in regexp. - if (matchPrefix(url, PREFIX_SLURL_WWW)) - { - size_t position = url.find("www."); - temp_url.erase(position,4); - } - - return LLUrlRegistry::getInstance()->isUrl(temp_url); + mGrid = grid; + mRegion = region; + S32 x = llround( (F32)fmod( position[VX], (F32)REGION_WIDTH_METERS ) ); + S32 y = llround( (F32)fmod( position[VY], (F32)REGION_WIDTH_METERS ) ); + S32 z = llround( (F32)position[VZ] ); + mType = LOCATION; + mPosition = LLVector3(x, y, z); } -// static -bool LLSLURL::isSLURLCommand(const std::string& url) -{ - if (matchPrefix(url, PREFIX_SL + APP_TOKEN) || - matchPrefix(url, PREFIX_SECONDLIFE + "/" + APP_TOKEN) || - matchPrefix(url, PREFIX_SLURL + APP_TOKEN) || - matchPrefix(url, PREFIX_SLURL_WWW + APP_TOKEN) || - matchPrefix(url, PREFIX_SLURL_OLD + APP_TOKEN) ) - { - return true; - } - return false; +// create a simstring +LLSLURL::LLSLURL(const std::string& region, + const LLVector3& position) +{ + *this = LLSLURL(LLGridManager::getInstance()->getGrid(), + region, position); } -// static -bool LLSLURL::isSLURLHelp(const std::string& url) +// create a slurl from a global position +LLSLURL::LLSLURL(const std::string& grid, + const std::string& region, + const LLVector3d& global_position) { - return matchPrefix(url, PREFIX_SL_HELP); + *this = LLSLURL(grid, + region, LLVector3(global_position.mdV[VX], + global_position.mdV[VY], + global_position.mdV[VZ])); } -// static -std::string LLSLURL::buildSLURL(const std::string& regionname, S32 x, S32 y, S32 z) +// create a slurl from a global position +LLSLURL::LLSLURL(const std::string& region, + const LLVector3d& global_position) { - std::string slurl = PREFIX_SLURL + regionname + llformat("/%d/%d/%d",x,y,z); - slurl = LLWeb::escapeURL( slurl ); - return slurl; + *this = LLSLURL(LLGridManager::getInstance()->getGrid(), + region, global_position); } -// static -std::string LLSLURL::buildCommand(const char* noun, const LLUUID& id, const char* verb) +LLSLURL::LLSLURL(const std::string& command, const LLUUID&id, const std::string& verb) { - std::string slurl = llformat("secondlife:///app/%s/%s/%s", - noun, id.asString().c_str(), verb); - return slurl; + mType = APP; + mAppCmd = command; + mAppPath = LLSD::emptyArray(); + mAppPath.append(LLSD(id)); + mAppPath.append(LLSD(verb)); } -// static -std::string LLSLURL::buildUnescapedSLURL(const std::string& regionname, S32 x, S32 y, S32 z) + +std::string LLSLURL::getSLURLString() const { - std::string unescapedslurl = PREFIX_SLURL + regionname + llformat("/%d/%d/%d",x,y,z); - return unescapedslurl; + switch(mType) + { + case HOME_LOCATION: + return SIM_LOCATION_HOME; + case LAST_LOCATION: + return SIM_LOCATION_LAST; + case LOCATION: + { + // lookup the grid + S32 x = llround( (F32)mPosition[VX] ); + S32 y = llround( (F32)mPosition[VY] ); + S32 z = llround( (F32)mPosition[VZ] ); + return LLGridManager::getInstance()->getSLURLBase(mGrid) + + LLURI::escape(mRegion) + llformat("/%d/%d/%d",x,y,z); + } + case APP: + { + std::ostringstream app_url; + app_url << LLGridManager::getInstance()->getAppSLURLBase() << "/" << mAppCmd; + for(LLSD::array_const_iterator i = mAppPath.beginArray(); + i != mAppPath.endArray(); + i++) + { + app_url << "/" << i->asString(); + } + if(mAppQuery.length() > 0) + { + app_url << "?" << mAppQuery; + } + return app_url.str(); + } + default: + LL_WARNS("AppInit") << "Unexpected SLURL type for SLURL string" << (int)mType << LL_ENDL; + return std::string(); + } } -// static -std::string LLSLURL::buildSLURLfromPosGlobal(const std::string& regionname, - const LLVector3d& global_pos, - bool escaped /*= true*/) +std::string LLSLURL::getLoginString() const { - S32 x, y, z; - globalPosToXYZ(global_pos, x, y, z); - if(escaped) + + std::stringstream unescaped_start; + switch(mType) { - return buildSLURL(regionname, x, y, z); + case LOCATION: + unescaped_start << "uri:" + << mRegion << "&" + << llround(mPosition[0]) << "&" + << llround(mPosition[1]) << "&" + << llround(mPosition[2]); + break; + case HOME_LOCATION: + unescaped_start << "home"; + break; + case LAST_LOCATION: + unescaped_start << "last"; + break; + default: + LL_WARNS("AppInit") << "Unexpected SLURL type for login string" << (int)mType << LL_ENDL; + break; } - else + return xml_escape_string(unescaped_start.str()); +} + +bool LLSLURL::operator==(const LLSLURL& rhs) +{ + if(rhs.mType != mType) return false; + switch(mType) { - return buildUnescapedSLURL(regionname, x, y, z); + case LOCATION: + return ((mGrid == rhs.mGrid) && + (mRegion == rhs.mRegion) && + (mPosition == rhs.mPosition)); + case APP: + return getSLURLString() == rhs.getSLURLString(); + + case HOME_LOCATION: + case LAST_LOCATION: + return true; + default: + return false; } } -// static -bool LLSLURL::matchPrefix(const std::string& url, const std::string& prefix) +bool LLSLURL::operator !=(const LLSLURL& rhs) { - std::string test_prefix = url.substr(0, prefix.length()); - LLStringUtil::toLower(test_prefix); - return test_prefix == prefix; + return !(*this == rhs); } -void LLSLURL::globalPosToXYZ(const LLVector3d& pos, S32& x, S32& y, S32& z) +std::string LLSLURL::getLocationString() const +{ + return llformat("%s/%d/%d/%d", + mRegion.c_str(), + (int)llround(mPosition[0]), + (int)llround(mPosition[1]), + (int)llround(mPosition[2])); +} +std::string LLSLURL::asString() const { - x = llround((F32)fmod(pos.mdV[VX], (F64)REGION_WIDTH_METERS)); - y = llround((F32)fmod(pos.mdV[VY], (F64)REGION_WIDTH_METERS)); - z = llround((F32)pos.mdV[VZ]); + std::ostringstream result; + result << " mAppCmd:" << getAppCmd() << + " mAppPath:" + getAppPath().asString() << + " mAppQueryMap:" + getAppQueryMap().asString() << + " mAppQuery: " + getAppQuery() << + " mGrid: " + getGrid() << + " mRegion: " + getRegion() << + " mPosition: " << + " mType: " << mType << + " mPosition: " << mPosition; + return result.str(); } + diff --git a/indra/newview/llslurl.h b/indra/newview/llslurl.h index a79a8fc97c..28c23561cf 100644 --- a/indra/newview/llslurl.h +++ b/indra/newview/llslurl.h @@ -1,10 +1,11 @@ -/** +/** * @file llslurl.h - * @brief SLURL manipulation + * @brief Handles "SLURL fragments" like Ahern/123/45 for + * startup processing, login screen, prefs, etc. * - * $LicenseInfo:firstyear=2009&license=viewergpl$ + * $LicenseInfo:firstyear=2010&license=viewergpl$ * - * Copyright (c) 2009, Linden Research, Inc. + * Copyright (c) 2006-2010, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -12,13 +13,12 @@ * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * online at http://secondlife.com/developers/opensource/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * online at http://secondlife.com/developers/opensource/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, @@ -29,85 +29,84 @@ * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ +#ifndef LLSLURL_H +#define LLSLURL_H -#ifndef LL_SLURL_H -#define LL_SLURL_H +#include "llstring.h" -#include -// IAN BUG: where should this live? -// IAN BUG: are static utility functions right? See LLUUID. -// question of whether to have a LLSLURL object or a -// some of this was moved from LLURLDispatcher +// represents a location in a grid -/** - * SLURL manipulation - */ class LLSLURL { public: - static const std::string PREFIX_SL_HELP; - static const std::string PREFIX_SL; - static const std::string PREFIX_SECONDLIFE; - static const std::string PREFIX_SLURL; - static const std::string PREFIX_SLURL_OLD; - static const std::string PREFIX_SLURL_WWW; - - static const std::string APP_TOKEN; - - /** - * Is this any sort of secondlife:// or sl:// URL? - */ - static bool isSLURL(const std::string& url); - - /** - * Returns true if url is proven valid by regexp check from LLUrlRegistry - */ - static bool isValidSLURL(const std::string& url); - - /** - * Is this a special secondlife://app/ URL? - */ - static bool isSLURLCommand(const std::string& url); - - /** - * Not sure what it is. - */ - static bool isSLURLHelp(const std::string& url); - - /** - * builds: http://slurl.com/secondlife/Region%20Name/x/y/z/ escaping result url. - */ - static std::string buildSLURL(const std::string& regionname, S32 x, S32 y, S32 z); - - /// Build a SLURL like secondlife:///app/agent//inspect - static std::string buildCommand(const char* noun, const LLUUID& id, const char* verb); - - /** - * builds: http://slurl.com/secondlife/Region Name/x/y/z/ without escaping result url. - */ - static std::string buildUnescapedSLURL(const std::string& regionname, S32 x, S32 y, S32 z); - - /** - * builds SLURL from global position. Returns escaped or unescaped url. - * Returns escaped url by default. - */ - static std::string buildSLURLfromPosGlobal(const std::string& regionname, - const LLVector3d& global_pos, - bool escaped = true); - /** - * Strip protocol part from the URL. - */ - static std::string stripProtocol(const std::string& url); - - /** - * Convert global position to X, Y Z - */ - static void globalPosToXYZ(const LLVector3d& pos, S32& x, S32& y, S32& z); - -private: - static bool matchPrefix(const std::string& url, const std::string& prefix); - + static const char* SLURL_HTTPS_SCHEME; + static const char* SLURL_HTTP_SCHEME; + static const char* SLURL_SL_SCHEME; + static const char* SLURL_SECONDLIFE_SCHEME; + static const char* SLURL_SECONDLIFE_PATH; + static const char* SLURL_COM; + static const char* WWW_SLURL_COM; + static const char* MAPS_SECONDLIFE_COM; + static const char* SLURL_X_GRID_LOCATION_INFO_SCHEME; + static LLSLURL START_LOCATION; + static const char* SIM_LOCATION_HOME; + static const char* SIM_LOCATION_LAST; + static const char* SLURL_APP_PATH; + static const char* SLURL_REGION_PATH; + + enum SLURL_TYPE { + INVALID, + LOCATION, + HOME_LOCATION, + LAST_LOCATION, + APP, + HELP + }; + + + LLSLURL(): mType(LAST_LOCATION) { } + LLSLURL(const std::string& slurl); + LLSLURL(const std::string& grid, const std::string& region); + LLSLURL(const std::string& region, const LLVector3& position); + LLSLURL(const std::string& grid, const std::string& region, const LLVector3& position); + LLSLURL(const std::string& grid, const std::string& region, const LLVector3d& global_position); + LLSLURL(const std::string& region, const LLVector3d& global_position); + LLSLURL(const std::string& command, const LLUUID&id, const std::string& verb); + + SLURL_TYPE getType() const { return mType; } + + std::string getSLURLString() const; + std::string getLoginString() const; + std::string getLocationString() const; + std::string getGrid() const { return mGrid; } + std::string getRegion() const { return mRegion; } + LLVector3 getPosition() const { return mPosition; } + std::string getAppCmd() const { return mAppCmd; } + std::string getAppQuery() const { return mAppQuery; } + LLSD getAppQueryMap() const { return mAppQueryMap; } + LLSD getAppPath() const { return mAppPath; } + + bool isValid() const { return mType != INVALID; } + bool isSpatial() const { return (mType == LAST_LOCATION) || (mType == HOME_LOCATION) || (mType == LOCATION); } + + bool operator==(const LLSLURL& rhs); + bool operator!=(const LLSLURL&rhs); + + std::string asString() const ; + +protected: + SLURL_TYPE mType; + + // used for Apps and Help + std::string mAppCmd; + LLSD mAppPath; + LLSD mAppQueryMap; + std::string mAppQuery; + + std::string mGrid; // reference to grid manager grid + std::string mRegion; + LLVector3 mPosition; }; -#endif +#endif // LLSLURL_H diff --git a/indra/newview/llspeakbutton.cpp b/indra/newview/llspeakbutton.cpp index c5c311ed33..d7de050636 100644 --- a/indra/newview/llspeakbutton.cpp +++ b/indra/newview/llspeakbutton.cpp @@ -59,9 +59,9 @@ LLSpeakButton::Params::Params() void LLSpeakButton::draw() { - // gVoiceClient is the authoritative global source of info regarding our open-mic state, we merely reflect that state. - bool openmic = gVoiceClient->getUserPTTState(); - bool voiceenabled = gVoiceClient->voiceEnabled(); + // LLVoiceClient::getInstance() is the authoritative global source of info regarding our open-mic state, we merely reflect that state. + bool openmic = LLVoiceClient::getInstance()->getUserPTTState(); + bool voiceenabled = LLVoiceClient::getInstance()->voiceEnabled(); mSpeakBtn->setToggleState(openmic && voiceenabled); mOutputMonitor->setIsMuted(!voiceenabled); LLUICtrl::draw(); @@ -176,11 +176,11 @@ void LLSpeakButton::setLabelVisible(bool visible) void LLSpeakButton::onMouseDown_SpeakBtn() { bool down = true; - gVoiceClient->inputUserControlState(down); // this method knows/care about whether this translates into a toggle-to-talk or down-to-talk + LLVoiceClient::getInstance()->inputUserControlState(down); // this method knows/care about whether this translates into a toggle-to-talk or down-to-talk } void LLSpeakButton::onMouseUp_SpeakBtn() { bool down = false; - gVoiceClient->inputUserControlState(down); + LLVoiceClient::getInstance()->inputUserControlState(down); } diff --git a/indra/newview/llspeakers.cpp b/indra/newview/llspeakers.cpp index 4573520647..b9534fac9a 100644 --- a/indra/newview/llspeakers.cpp +++ b/indra/newview/llspeakers.cpp @@ -299,7 +299,7 @@ LLPointer LLSpeakerMgr::setSpeaker(const LLUUID& id, const std::strin void LLSpeakerMgr::update(BOOL resort_ok) { - if (!gVoiceClient) + if (!LLVoiceClient::getInstance()) { return; } @@ -313,7 +313,7 @@ void LLSpeakerMgr::update(BOOL resort_ok) } // update status of all current speakers - BOOL voice_channel_active = (!mVoiceChannel && gVoiceClient->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive()); + BOOL voice_channel_active = (!mVoiceChannel && LLVoiceClient::getInstance()->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive()); for (speaker_map_t::iterator speaker_it = mSpeakers.begin(); speaker_it != mSpeakers.end();) { LLUUID speaker_id = speaker_it->first; @@ -321,21 +321,21 @@ void LLSpeakerMgr::update(BOOL resort_ok) speaker_map_t::iterator cur_speaker_it = speaker_it++; - if (voice_channel_active && gVoiceClient->getVoiceEnabled(speaker_id)) + if (voice_channel_active && LLVoiceClient::getInstance()->getVoiceEnabled(speaker_id)) { - speakerp->mSpeechVolume = gVoiceClient->getCurrentPower(speaker_id); - BOOL moderator_muted_voice = gVoiceClient->getIsModeratorMuted(speaker_id); + speakerp->mSpeechVolume = LLVoiceClient::getInstance()->getCurrentPower(speaker_id); + BOOL moderator_muted_voice = LLVoiceClient::getInstance()->getIsModeratorMuted(speaker_id); if (moderator_muted_voice != speakerp->mModeratorMutedVoice) { speakerp->mModeratorMutedVoice = moderator_muted_voice; speakerp->fireEvent(new LLSpeakerVoiceModerationEvent(speakerp)); } - if (gVoiceClient->getOnMuteList(speaker_id) || speakerp->mModeratorMutedVoice) + if (LLVoiceClient::getInstance()->getOnMuteList(speaker_id) || speakerp->mModeratorMutedVoice) { speakerp->mStatus = LLSpeaker::STATUS_MUTED; } - else if (gVoiceClient->getIsSpeaking(speaker_id)) + else if (LLVoiceClient::getInstance()->getIsSpeaking(speaker_id)) { // reset inactivity expiration if (speakerp->mStatus != LLSpeaker::STATUS_SPEAKING) @@ -417,19 +417,21 @@ void LLSpeakerMgr::update(BOOL resort_ok) void LLSpeakerMgr::updateSpeakerList() { // are we bound to the currently active voice channel? - if ((!mVoiceChannel && gVoiceClient->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive())) - { - LLVoiceClient::participantMap* participants = gVoiceClient->getParticipantList(); - if(participants) + if ((!mVoiceChannel && LLVoiceClient::getInstance()->inProximalChannel()) || (mVoiceChannel && mVoiceChannel->isActive())) + { + std::set participants; + LLVoiceClient::getInstance()->getParticipantList(participants); + // add new participants to our list of known speakers + for (std::set::iterator participant_it = participants.begin(); + participant_it != participants.end(); + ++participant_it) { - LLVoiceClient::participantMap::iterator participant_it; + setSpeaker(*participant_it, + LLVoiceClient::getInstance()->getDisplayName(*participant_it), + LLSpeaker::STATUS_VOICE_ACTIVE, + (LLVoiceClient::getInstance()->isParticipantAvatar(*participant_it)?LLSpeaker::SPEAKER_AGENT:LLSpeaker::SPEAKER_EXTERNAL)); + - // add new participants to our list of known speakers - for (participant_it = participants->begin(); participant_it != participants->end(); ++participant_it) - { - LLVoiceClient::participantState* participantp = participant_it->second; - setSpeaker(participantp->mAvatarID, participantp->mDisplayName, LLSpeaker::STATUS_VOICE_ACTIVE, (participantp->isAvatar()?LLSpeaker::SPEAKER_AGENT:LLSpeaker::SPEAKER_EXTERNAL)); - } } } } @@ -519,7 +521,7 @@ void LLSpeakerMgr::speakerChatted(const LLUUID& speaker_id) BOOL LLSpeakerMgr::isVoiceActive() { // mVoiceChannel = NULL means current voice channel, whatever it is - return LLVoiceClient::voiceEnabled() && mVoiceChannel && mVoiceChannel->isActive(); + return LLVoiceClient::getInstance()->voiceEnabled() && mVoiceChannel && mVoiceChannel->isActive(); } diff --git a/indra/newview/llspeakingindicatormanager.cpp b/indra/newview/llspeakingindicatormanager.cpp index cc06179481..29237946d2 100644 --- a/indra/newview/llspeakingindicatormanager.cpp +++ b/indra/newview/llspeakingindicatormanager.cpp @@ -158,7 +158,7 @@ void SpeakingIndicatorManager::registerSpeakingIndicator(const LLUUID& speaker_i mSpeakingIndicators.insert(value_type); speaker_ids_t speakers_uuids; - BOOL is_in_same_voice = LLVoiceClient::getInstance()->findParticipantByID(speaker_id) != NULL; + BOOL is_in_same_voice = LLVoiceClient::getInstance()->isParticipant(speaker_id); speakers_uuids.insert(speaker_id); switchSpeakerIndicators(speakers_uuids, is_in_same_voice); @@ -210,7 +210,7 @@ void SpeakingIndicatorManager::onChange() LL_DEBUGS("SpeakingIndicator") << "Voice participant list was changed, updating indicators" << LL_ENDL; speaker_ids_t speakers_uuids; - LLVoiceClient::getInstance()->getParticipantsUUIDSet(speakers_uuids); + LLVoiceClient::getInstance()->getParticipantList(speakers_uuids); LL_DEBUGS("SpeakingIndicator") << "Switching all OFF, count: " << mSwitchedIndicatorsOn.size() << LL_ENDL; // switch all indicators off diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index b5a73a3143..4f1bcde302 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -147,7 +147,7 @@ #include "lltrans.h" #include "llui.h" #include "llurldispatcher.h" -#include "llurlsimstring.h" +#include "llslurl.h" #include "llurlhistory.h" #include "llurlwhitelist.h" #include "llvieweraudio.h" @@ -192,6 +192,7 @@ #include "llinventorybridge.h" #include "llappearancemgr.h" #include "llavatariconctrl.h" +#include "llvoicechannel.h" #include "lllogin.h" #include "llevents.h" @@ -228,11 +229,11 @@ static std::string sInitialOutfitGender; // "male" or "female" static bool gUseCircuitCallbackCalled = false; EStartupState LLStartUp::gStartupState = STATE_FIRST; +LLSLURL LLStartUp::sStartSLURL; -// *NOTE:Mani - to reconcile with giab changes... -static std::string gFirstname; -static std::string gLastname; -static std::string gPassword; +static LLPointer gUserCredential; +static std::string gDisplayName; +static BOOL gRememberPassword = TRUE; static U64 gFirstSimHandle = 0; static LLHost gFirstSim; @@ -249,7 +250,6 @@ boost::scoped_ptr LLStartUp::sListener(new LLStartupListener( void login_show(); void login_callback(S32 option, void* userdata); -bool is_hex_string(U8* str, S32 len); void show_first_run_dialog(); bool first_run_dialog_callback(const LLSD& notification, const LLSD& response); void set_startup_status(const F32 frac, const std::string& string, const std::string& msg); @@ -262,6 +262,9 @@ bool callback_choose_gender(const LLSD& notification, const LLSD& response); void init_start_screen(S32 location_id); void release_start_screen(); void reset_login(); +LLSD transform_cert_args(LLPointer cert); +void general_cert_done(const LLSD& notification, const LLSD& response); +void trust_cert_done(const LLSD& notification, const LLSD& response); void apply_udp_blacklist(const std::string& csv); bool process_login_success_response(); void transition_back_to_login_panel(const std::string& emsg); @@ -364,7 +367,7 @@ bool idle_startup() if ( STATE_FIRST == LLStartUp::getStartupState() ) { - gViewerWindow->showCursor(); + gViewerWindow->showCursor(); gViewerWindow->getWindow()->setCursor(UI_CURSOR_WAIT); ///////////////////////////////////////////////// @@ -662,69 +665,25 @@ bool idle_startup() // // Log on to system // - if (!LLStartUp::sSLURLCommand.empty()) - { - // this might be a secondlife:///app/login URL - gLoginHandler.parseDirectLogin(LLStartUp::sSLURLCommand); - } - if (!gLoginHandler.getFirstName().empty() - || !gLoginHandler.getLastName().empty() - /*|| !gLoginHandler.getWebLoginKey().isNull()*/ ) - { - // We have at least some login information on a SLURL - gFirstname = gLoginHandler.getFirstName(); - gLastname = gLoginHandler.getLastName(); - LL_DEBUGS("LLStartup") << "STATE_FIRST: setting gFirstname, gLastname from gLoginHandler: '" << gFirstname << "' '" << gLastname << "'" << LL_ENDL; - - // Show the login screen if we don't have everything - show_connect_box = - gFirstname.empty() || gLastname.empty(); - } - else if(gSavedSettings.getLLSD("UserLoginInfo").size() == 3) - { - LLSD cmd_line_login = gSavedSettings.getLLSD("UserLoginInfo"); - gFirstname = cmd_line_login[0].asString(); - gLastname = cmd_line_login[1].asString(); - LL_DEBUGS("LLStartup") << "Setting gFirstname, gLastname from gSavedSettings(\"UserLoginInfo\"): '" << gFirstname << "' '" << gLastname << "'" << LL_ENDL; - - LLMD5 pass((unsigned char*)cmd_line_login[2].asString().c_str()); - char md5pass[33]; /* Flawfinder: ignore */ - pass.hex_digest(md5pass); - gPassword = md5pass; - -#ifdef USE_VIEWER_AUTH - show_connect_box = true; -#else - show_connect_box = false; -#endif - gSavedSettings.setBOOL("AutoLogin", TRUE); - } - else if (gSavedSettings.getBOOL("AutoLogin")) - { - gFirstname = gSavedSettings.getString("FirstName"); - gLastname = gSavedSettings.getString("LastName"); - LL_DEBUGS("LLStartup") << "AutoLogin: setting gFirstname, gLastname from gSavedSettings(\"First|LastName\"): '" << gFirstname << "' '" << gLastname << "'" << LL_ENDL; - gPassword = LLStartUp::loadPasswordFromDisk(); - gSavedSettings.setBOOL("RememberPassword", TRUE); - -#ifdef USE_VIEWER_AUTH - show_connect_box = true; -#else - show_connect_box = false; -#endif + if (gUserCredential.isNull()) + { + gUserCredential = gLoginHandler.initializeLoginInfo(); } - else + if (gUserCredential.isNull()) { - // if not automatically logging in, display login dialog - // a valid grid is selected - gFirstname = gSavedSettings.getString("FirstName"); - gLastname = gSavedSettings.getString("LastName"); - LL_DEBUGS("LLStartup") << "normal login: setting gFirstname, gLastname from gSavedSettings(\"First|LastName\"): '" << gFirstname << "' '" << gLastname << "'" << LL_ENDL; - gPassword = LLStartUp::loadPasswordFromDisk(); - show_connect_box = true; + show_connect_box = TRUE; + } + else if (gSavedSettings.getBOOL("AutoLogin")) + { + gRememberPassword = TRUE; + gSavedSettings.setBOOL("RememberPassword", TRUE); + show_connect_box = false; + } + else + { + gRememberPassword = gSavedSettings.getBOOL("RememberPassword"); + show_connect_box = TRUE; } - - // Go to the next startup state LLStartUp::setStartupState( STATE_BROWSER_INIT ); return FALSE; @@ -756,8 +715,10 @@ bool idle_startup() // Load all the name information out of the login view // NOTE: Hits "Attempted getFields with no login view shown" warning, since we don't // show the login view until login_show() is called below. - // LLPanelLogin::getFields(gFirstname, gLastname, gPassword); - + if (gUserCredential.isNull()) + { + gUserCredential = gLoginHandler.initializeLoginInfo(); + } if (gNoRender) { LL_ERRS("AppInit") << "Need to autologin or use command line with norender!" << LL_ENDL; @@ -768,8 +729,10 @@ bool idle_startup() // Show the login dialog login_show(); // connect dialog is already shown, so fill in the names - LLPanelLogin::setFields( gFirstname, gLastname, gPassword); - + if (gUserCredential.notNull()) + { + LLPanelLogin::setFields( gUserCredential, gRememberPassword); + } LLPanelLogin::giveFocus(); gSavedSettings.setBOOL("FirstRunThisInstall", FALSE); @@ -839,39 +802,36 @@ bool idle_startup() // DEV-42215: Make sure they're not empty -- gFirstname and gLastname // might already have been set from gSavedSettings, and it's too bad // to overwrite valid values with empty strings. - if (! gLoginHandler.getFirstName().empty() && ! gLoginHandler.getLastName().empty()) - { - gFirstname = gLoginHandler.getFirstName(); - gLastname = gLoginHandler.getLastName(); - LL_DEBUGS("LLStartup") << "STATE_LOGIN_CLEANUP: setting gFirstname, gLastname from gLoginHandler: '" << gFirstname << "' '" << gLastname << "'" << LL_ENDL; - } if (show_connect_box) { // TODO if not use viewer auth // Load all the name information out of the login view - LLPanelLogin::getFields(&gFirstname, &gLastname, &gPassword); + LLPanelLogin::getFields(gUserCredential, gRememberPassword); // end TODO // HACK: Try to make not jump on login gKeyboard->resetKeys(); } - if (!gFirstname.empty() && !gLastname.empty()) - { - gSavedSettings.setString("FirstName", gFirstname); - gSavedSettings.setString("LastName", gLastname); - - LL_INFOS("AppInit") << "Attempting login as: " << gFirstname << " " << gLastname << LL_ENDL; - gDebugInfo["LoginName"] = gFirstname + " " + gLastname; + // save the credentials + std::string userid = "unknown"; + if(gUserCredential.notNull()) + { + userid = gUserCredential->userID(); + gSecAPIHandler->saveCredential(gUserCredential, gRememberPassword); } - + gSavedSettings.setBOOL("RememberPassword", gRememberPassword); + LL_INFOS("AppInit") << "Attempting login as: " << userid << LL_ENDL; + gDebugInfo["LoginName"] = userid; + // create necessary directories // *FIX: these mkdir's should error check - gDirUtilp->setLindenUserDir(gFirstname, gLastname); + gDirUtilp->setLindenUserDir(userid); LLFile::mkdir(gDirUtilp->getLindenUserDir()); - + // Set PerAccountSettingsFile to the default value. + std::string per_account_settings_file = LLAppViewer::instance()->getSettingsFilename("Default", "PerAccount"); gSavedSettings.setString("PerAccountSettingsFile", gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, LLAppViewer::instance()->getSettingsFilename("Default", "PerAccount"))); @@ -901,9 +861,8 @@ bool idle_startup() { gDirUtilp->setChatLogsDir(gSavedPerAccountSettings.getString("InstantMessageLogPath")); } + gDirUtilp->setPerAccountChatLogsDir(userid); - gDirUtilp->setPerAccountChatLogsDir(gFirstname, gLastname); - LLFile::mkdir(gDirUtilp->getChatLogsDir()); LLFile::mkdir(gDirUtilp->getPerAccountChatLogsDir()); @@ -924,11 +883,7 @@ bool idle_startup() if (show_connect_box) { - std::string location; - LLPanelLogin::getLocation( location ); - LLURLSimString::setString( location ); - - // END TODO + LLSLURL slurl; LLPanelLogin::closePanel(); } @@ -952,26 +907,21 @@ bool idle_startup() // their last location, or some URL "-url //sim/x/y[/z]" // All accounts have both a home and a last location, and we don't support // more locations than that. Choose the appropriate one. JC - if (LLURLSimString::parse()) - { - // a startup URL was specified - agent_location_id = START_LOCATION_ID_URL; - - // doesn't really matter what location_which is, since - // gAgentStartLookAt will be overwritten when the - // UserLoginLocationReply arrives - location_which = START_LOCATION_ID_LAST; - } - else if (gSavedSettings.getString("LoginLocation") == "last" ) - { - agent_location_id = START_LOCATION_ID_LAST; // last location - location_which = START_LOCATION_ID_LAST; - } - else - { - agent_location_id = START_LOCATION_ID_HOME; // home - location_which = START_LOCATION_ID_HOME; - } + switch (LLStartUp::getStartSLURL().getType()) + { + case LLSLURL::LOCATION: + agent_location_id = START_LOCATION_ID_URL; + location_which = START_LOCATION_ID_LAST; + break; + case LLSLURL::LAST_LOCATION: + agent_location_id = START_LOCATION_ID_LAST; + location_which = START_LOCATION_ID_LAST; + break; + default: + agent_location_id = START_LOCATION_ID_HOME; + location_which = START_LOCATION_ID_HOME; + break; + } gViewerWindow->getWindow()->setCursor(UI_CURSOR_WAIT); @@ -998,7 +948,7 @@ bool idle_startup() if(STATE_LOGIN_AUTH_INIT == LLStartUp::getStartupState()) { - gDebugInfo["GridName"] = LLViewerLogin::getInstance()->getGridLabel(); + gDebugInfo["GridName"] = LLGridManager::getInstance()->getGridLabel(); // Update progress status and the display loop. auth_desc = LLTrans::getString("LoginInProgress"); @@ -1022,11 +972,7 @@ bool idle_startup() // This call to LLLoginInstance::connect() starts the // authentication process. - LLSD credentials; - credentials["first"] = gFirstname; - credentials["last"] = gLastname; - credentials["passwd"] = gPassword; - login->connect(credentials); + login->connect(gUserCredential); LLStartUp::setStartupState( STATE_LOGIN_CURL_UNSTUCK ); return FALSE; @@ -1051,10 +997,11 @@ bool idle_startup() { LL_INFOS("LLStartup") << "Login failed, LLLoginInstance::getResponse(): " << LLLoginInstance::getInstance()->getResponse() << LL_ENDL; + LLSD response = LLLoginInstance::getInstance()->getResponse(); // Still have error conditions that may need some // sort of handling. - std::string reason_response = LLLoginInstance::getInstance()->getResponse("reason"); - std::string message_response = LLLoginInstance::getInstance()->getResponse("message"); + std::string reason_response = response["reason"]; + std::string message_response = response["message"]; if(!message_response.empty()) { @@ -1074,8 +1021,8 @@ bool idle_startup() if(reason_response == "key") { // Couldn't login because user/password is wrong - // Clear the password - gPassword = ""; + // Clear the credential + gUserCredential->clearAuthenticator(); } if(reason_response == "update" @@ -1088,18 +1035,65 @@ bool idle_startup() LLLoginInstance::getInstance()->disconnect(); LLAppViewer::instance()->forceQuit(); } - else + else { - // Don't pop up a notification in the TOS case because - // LLFloaterTOS::onCancel() already scolded the user. - if (reason_response != "tos") + if (reason_response != "tos") { - LLSD args; - args["ERROR_MESSAGE"] = emsg.str(); - LL_INFOS("LLStartup") << "Notification: " << args << LL_ENDL; - LLNotificationsUtil::add("ErrorMessage", args, LLSD(), login_alert_done); + // Don't pop up a notification in the TOS case because + // LLFloaterTOS::onCancel() already scolded the user. + std::string error_code; + if(response.has("errorcode")) + { + error_code = response["errorcode"].asString(); + } + if ((reason_response == "CURLError") && + (error_code == "SSL_CACERT" || error_code == "SSL_PEER_CERTIFICATE") && + response.has("certificate")) + { + // This was a certificate error, so grab the certificate + // and throw up the appropriate dialog. + LLPointer certificate = gSecAPIHandler->getCertificate(response["certificate"]); + if(certificate) + { + LLSD args = transform_cert_args(certificate); + + if(error_code == "SSL_CACERT") + { + // if we are handling an untrusted CA, throw up the dialog + // with the 'trust this CA' button. + LLNotificationsUtil::add("TrustCertificateError", args, response, + trust_cert_done); + + show_connect_box = true; + } + else + { + // the certificate exception returns a unique string for each type of exception. + // we grab this string via the LLUserAuth object, and use that to grab the localized + // string. + args["REASON"] = LLTrans::getString(message_response); + + LLNotificationsUtil::add("GeneralCertificateError", args, response, + general_cert_done); + + reset_login(); + gSavedSettings.setBOOL("AutoLogin", FALSE); + show_connect_box = true; + + } + + } + } + else + { + // This wasn't a certificate error, so throw up the normal + // notificatioin message. + LLSD args; + args["ERROR_MESSAGE"] = emsg.str(); + LL_INFOS("LLStartup") << "Notification: " << args << LL_ENDL; + LLNotificationsUtil::add("ErrorMessage", args, LLSD(), login_alert_done); + } } - //setup map of datetime strings to codes and slt & local time offset from utc // *TODO: Does this need to be here? LLStringOps::setupDatetimeInfo (false); @@ -1112,7 +1106,12 @@ bool idle_startup() if(process_login_success_response()) { // Pass the user information to the voice chat server interface. - gVoiceClient->userAuthorized(gFirstname, gLastname, gAgentID); + LLVoiceClient::getInstance()->userAuthorized(gUserCredential->userID(), gAgentID); + // create the default proximal channel + LLVoiceChannel::initClass(); + // update the voice settings + LLVoiceClient::getInstance()->updateSettings(); + LLGridManager::getInstance()->setFavorite(); LLStartUp::setStartupState( STATE_WORLD_INIT); } else @@ -1123,6 +1122,7 @@ bool idle_startup() LLNotificationsUtil::add("ErrorMessage", args, LLSD(), login_alert_done); transition_back_to_login_panel(emsg.str()); show_connect_box = true; + return FALSE; } } return FALSE; @@ -1807,9 +1807,12 @@ bool idle_startup() // thus, do not show this alert. if (!gAgent.isFirstLogin()) { - bool url_ok = LLURLSimString::sInstance.parse(); - if ((url_ok && gAgentStartLocation == "url") || - (!url_ok && ((gAgentStartLocation == gSavedSettings.getString("LoginLocation"))))) + llinfos << "gAgentStartLocation : " << gAgentStartLocation << llendl; + LLSLURL start_slurl = LLStartUp::getStartSLURL(); + + if (((start_slurl.getType() == LLSLURL::LOCATION) && (gAgentStartLocation == "url")) || + ((start_slurl.getType() == LLSLURL::LAST_LOCATION) && (gAgentStartLocation == "last")) || + ((start_slurl.getType() == LLSLURL::HOME_LOCATION) && (gAgentStartLocation == "home"))) { // Start location is OK // Disabled code to restore camera location and focus if logging in to default location @@ -1831,17 +1834,23 @@ bool idle_startup() else { std::string msg; - if (url_ok) - { - msg = "AvatarMovedDesired"; - } - else if (gSavedSettings.getString("LoginLocation") == "home") - { - msg = "AvatarMovedHome"; - } - else + switch(start_slurl.getType()) { - msg = "AvatarMovedLast"; + case LLSLURL::LOCATION: + { + + msg = "AvatarMovedDesired"; + break; + } + case LLSLURL::HOME_LOCATION: + { + msg = "AvatarMovedHome"; + break; + } + default: + { + msg = "AvatarMovedLast"; + } } LLNotificationsUtil::add(msg); } @@ -2057,20 +2066,9 @@ void login_show() #endif LLPanelLogin::show( gViewerWindow->getWindowRectScaled(), - bUseDebugLogin, + bUseDebugLogin || gSavedSettings.getBOOL("SecondLifeEnterprise"), login_callback, NULL ); - // UI textures have been previously loaded in doPreloadImages() - - LL_DEBUGS("AppInit") << "Setting Servers" << LL_ENDL; - - LLPanelLogin::addServer(LLViewerLogin::getInstance()->getGridLabel(), LLViewerLogin::getInstance()->getGridChoice()); - - LLViewerLogin* vl = LLViewerLogin::getInstance(); - for(int grid_index = GRID_INFO_ADITI; grid_index < GRID_INFO_OTHER; ++grid_index) - { - LLPanelLogin::addServer(vl->getKnownGridLabel((EGridInfo)grid_index), grid_index); - } } // Callback for when login screen is closed. Option 0 = connect, option 1 = quit. @@ -2086,9 +2084,6 @@ void login_callback(S32 option, void *userdata) } else if (QUIT_OPTION == option) // *TODO: THIS CODE SEEMS TO BE UNREACHABLE!!!!! login_callback is never called with option equal to QUIT_OPTION { - // Make sure we don't save the password if the user is trying to clear it. - std::string first, last, password; - LLPanelLogin::getFields(&first, &last, &password); if (!gSavedSettings.getBOOL("RememberPassword")) { // turn off the setting and write out to disk @@ -2111,142 +2106,6 @@ void login_callback(S32 option, void *userdata) } } - -// static -std::string LLStartUp::loadPasswordFromDisk() -{ - // Only load password if we also intend to save it (otherwise the user - // wonders what we're doing behind his back). JC - BOOL remember_password = gSavedSettings.getBOOL("RememberPassword"); - if (!remember_password) - { - return std::string(""); - } - - std::string hashed_password(""); - - // Look for legacy "marker" password from settings.ini - hashed_password = gSavedSettings.getString("Marker"); - if (!hashed_password.empty()) - { - // Stomp the Marker entry. - gSavedSettings.setString("Marker", ""); - - // Return that password. - return hashed_password; - } - - std::string filepath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, - "password.dat"); - LLFILE* fp = LLFile::fopen(filepath, "rb"); /* Flawfinder: ignore */ - if (!fp) - { - return hashed_password; - } - - // UUID is 16 bytes, written into ASCII is 32 characters - // without trailing \0 - const S32 HASHED_LENGTH = 32; - U8 buffer[HASHED_LENGTH+1]; - - if (1 != fread(buffer, HASHED_LENGTH, 1, fp)) - { - return hashed_password; - } - - fclose(fp); - - // Decipher with MAC address - LLXORCipher cipher(gMACAddress, 6); - cipher.decrypt(buffer, HASHED_LENGTH); - - buffer[HASHED_LENGTH] = '\0'; - - // Check to see if the mac address generated a bad hashed - // password. It should be a hex-string or else the mac adress has - // changed. This is a security feature to make sure that if you - // get someone's password.dat file, you cannot hack their account. - if(is_hex_string(buffer, HASHED_LENGTH)) - { - hashed_password.assign((char*)buffer); - } - - return hashed_password; -} - - -// static -void LLStartUp::savePasswordToDisk(const std::string& hashed_password) -{ - std::string filepath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, - "password.dat"); - LLFILE* fp = LLFile::fopen(filepath, "wb"); /* Flawfinder: ignore */ - if (!fp) - { - return; - } - - // Encipher with MAC address - const S32 HASHED_LENGTH = 32; - U8 buffer[HASHED_LENGTH+1]; - - LLStringUtil::copy((char*)buffer, hashed_password.c_str(), HASHED_LENGTH+1); - - LLXORCipher cipher(gMACAddress, 6); - cipher.encrypt(buffer, HASHED_LENGTH); - - if (fwrite(buffer, HASHED_LENGTH, 1, fp) != 1) - { - LL_WARNS("AppInit") << "Short write" << LL_ENDL; - } - - fclose(fp); -} - - -// static -void LLStartUp::deletePasswordFromDisk() -{ - std::string filepath = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, - "password.dat"); - LLFile::remove(filepath); -} - - -bool is_hex_string(U8* str, S32 len) -{ - bool rv = true; - U8* c = str; - while(rv && len--) - { - switch(*c) - { - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case 'a': - case 'b': - case 'c': - case 'd': - case 'e': - case 'f': - ++c; - break; - default: - rv = false; - break; - } - } - return rv; -} - void show_first_run_dialog() { LLNotificationsUtil::add("FirstRun", LLSD(), LLSD(), first_run_dialog_callback); @@ -2288,7 +2147,7 @@ bool login_alert_status(const LLSD& notification, const LLSD& response) // break; case 2: // Teleport // Restart the login process, starting at our home locaton - LLURLSimString::setString("home"); + LLStartUp::setStartSLURL(LLSLURL(LLSLURL::SIM_LOCATION_HOME)); LLStartUp::setStartupState( STATE_LOGIN_CLEANUP ); break; default: @@ -2508,30 +2367,35 @@ void asset_callback_nothing(LLVFS*, const LLUUID&, LLAssetType::EType, void*, S3 const std::string COMMON_GESTURES_FOLDER = "Common Gestures"; const std::string MALE_GESTURES_FOLDER = "Male Gestures"; const std::string FEMALE_GESTURES_FOLDER = "Female Gestures"; -const std::string MALE_OUTFIT_FOLDER = "Male Shape & Outfit"; -const std::string FEMALE_OUTFIT_FOLDER = "Female Shape & Outfit"; const S32 OPT_CLOSED_WINDOW = -1; const S32 OPT_MALE = 0; const S32 OPT_FEMALE = 1; - +const S32 OPT_TRUST_CERT = 0; +const S32 OPT_CANCEL_TRUST = 1; + bool callback_choose_gender(const LLSD& notification, const LLSD& response) -{ - S32 option = LLNotificationsUtil::getSelectedOption(notification, response); +{ + + // These defaults are returned from the server on login. They are set in login.xml. + // If no default is returned from the server, they are retrieved from settings.xml. + + S32 option = LLNotification::getSelectedOption(notification, response); switch(option) { - case OPT_MALE: - LLStartUp::loadInitialOutfit( MALE_OUTFIT_FOLDER, "male" ); - break; - - case OPT_FEMALE: - case OPT_CLOSED_WINDOW: - default: - LLStartUp::loadInitialOutfit( FEMALE_OUTFIT_FOLDER, "female" ); - break; + case OPT_MALE: + LLStartUp::loadInitialOutfit( gSavedSettings.getString("DefaultMaleAvatar"), "male" ); + break; + + case OPT_FEMALE: + case OPT_CLOSED_WINDOW: + default: + LLStartUp::loadInitialOutfit( gSavedSettings.getString("DefaultFemaleAvatar"), "female" ); + break; } return false; } + void LLStartUp::loadInitialOutfit( const std::string& outfit_folder_name, const std::string& gender_name ) { @@ -2746,7 +2610,6 @@ void reset_login() //--------------------------------------------------------------------------- -std::string LLStartUp::sSLURLCommand; bool LLStartUp::canGoFullscreen() { @@ -2779,41 +2642,145 @@ void LLStartUp::fontInit() bool LLStartUp::dispatchURL() { // ok, if we've gotten this far and have a startup URL - if (!sSLURLCommand.empty()) + if (!getStartSLURL().isValid()) { - LLMediaCtrl* web = NULL; - const bool trusted_browser = false; - LLURLDispatcher::dispatch(sSLURLCommand, web, trusted_browser); + return false; } - else if (LLURLSimString::parse()) - { + if(getStartSLURL().getType() != LLSLURL::APP) + { + // If we started with a location, but we're already // at that location, don't pop dialogs open. LLVector3 pos = gAgent.getPositionAgent(); - F32 dx = pos.mV[VX] - (F32)LLURLSimString::sInstance.mX; - F32 dy = pos.mV[VY] - (F32)LLURLSimString::sInstance.mY; + LLVector3 slurlpos = getStartSLURL().getPosition(); + F32 dx = pos.mV[VX] - slurlpos.mV[VX]; + F32 dy = pos.mV[VY] - slurlpos.mV[VY]; const F32 SLOP = 2.f; // meters - if( LLURLSimString::sInstance.mSimName != gAgent.getRegion()->getName() + if( getStartSLURL().getRegion() != gAgent.getRegion()->getName() || (dx*dx > SLOP*SLOP) || (dy*dy > SLOP*SLOP) ) { - std::string url = LLURLSimString::getURL(); - LLMediaCtrl* web = NULL; - const bool trusted_browser = false; - LLURLDispatcher::dispatch(url, web, trusted_browser); + LLURLDispatcher::dispatch(getStartSLURL().getSLURLString(), + NULL, false); } return true; } return false; } +void LLStartUp::setStartSLURL(const LLSLURL& slurl) +{ + sStartSLURL = slurl; + switch(slurl.getType()) + { + case LLSLURL::HOME_LOCATION: + { + gSavedSettings.setString("LoginLocation", LLSLURL::SIM_LOCATION_HOME); + break; + } + case LLSLURL::LAST_LOCATION: + { + gSavedSettings.setString("LoginLocation", LLSLURL::SIM_LOCATION_LAST); + break; + } + default: + LLGridManager::getInstance()->setGridChoice(slurl.getGrid()); + break; + } +} + bool login_alert_done(const LLSD& notification, const LLSD& response) { LLPanelLogin::giveFocus(); return false; } +// parse the certificate information into args for the +// certificate notifications +LLSD transform_cert_args(LLPointer cert) +{ + LLSD args = LLSD::emptyMap(); + std::string value; + LLSD cert_info = cert->getLLSD(); + // convert all of the elements in the cert into + // args for the xml dialog, so we have flexability to + // display various parts of the cert by only modifying + // the cert alert dialog xml. + for(LLSD::map_iterator iter = cert_info.beginMap(); + iter != cert_info.endMap(); + iter++) + { + // key usage and extended key usage + // are actually arrays, and we want to format them as comma separated + // strings, so special case those. + LLSDSerialize::toXML(cert_info[iter->first], std::cout); + if((iter->first== std::string(CERT_KEY_USAGE)) | + (iter->first == std::string(CERT_EXTENDED_KEY_USAGE))) + { + value = ""; + LLSD usage = cert_info[iter->first]; + for (LLSD::array_iterator usage_iter = usage.beginArray(); + usage_iter != usage.endArray(); + usage_iter++) + { + + if(usage_iter != usage.beginArray()) + { + value += ", "; + } + + value += (*usage_iter).asString(); + } + + } + else + { + value = iter->second.asString(); + } + + std::string name = iter->first; + std::transform(name.begin(), name.end(), name.begin(), + (int(*)(int))toupper); + args[name.c_str()] = value; + } + return args; +} + + +// when we handle a cert error, give focus back to the login panel +void general_cert_done(const LLSD& notification, const LLSD& response) +{ + LLStartUp::setStartupState( STATE_LOGIN_SHOW ); + LLPanelLogin::giveFocus(); +} + +// check to see if the user wants to trust the cert. +// if they do, add it to the cert store and +void trust_cert_done(const LLSD& notification, const LLSD& response) +{ + S32 option = LLNotification::getSelectedOption(notification, response); + switch(option) + { + case OPT_TRUST_CERT: + { + LLPointer cert = gSecAPIHandler->getCertificate(notification["payload"]["certificate"]); + LLPointer store = gSecAPIHandler->getCertificateStore(gSavedSettings.getString("CertStore")); + store->add(cert); + store->save(); + LLStartUp::setStartupState( STATE_LOGIN_CLEANUP ); + break; + } + case OPT_CANCEL_TRUST: + reset_login(); + gSavedSettings.setBOOL("AutoLogin", FALSE); + LLStartUp::setStartupState( STATE_LOGIN_SHOW ); + default: + LLPanelLogin::giveFocus(); + break; + } + +} void apply_udp_blacklist(const std::string& csv) { @@ -2861,33 +2828,45 @@ bool process_login_success_response() text = response["secure_session_id"].asString(); if(!text.empty()) gAgent.mSecureSessionID.set(text); - text = response["first_name"].asString(); - if(!text.empty()) - { - // Remove quotes from string. Login.cgi sends these to force - // names that look like numbers into strings. - gFirstname.assign(text); - LLStringUtil::replaceChar(gFirstname, '"', ' '); - LLStringUtil::trim(gFirstname); - } - text = response["last_name"].asString(); - if(!text.empty()) + // if the response contains a display name, use that, + // otherwise if the response contains a first and/or last name, + // use those. Otherwise use the credential identifier + + gDisplayName = ""; + if (response.has("display_name")) { - gLastname.assign(text); + gDisplayName.assign(response["display_name"].asString()); + if(!gDisplayName.empty()) + { + // Remove quotes from string. Login.cgi sends these to force + // names that look like numbers into strings. + LLStringUtil::replaceChar(gDisplayName, '"', ' '); + LLStringUtil::trim(gDisplayName); + } } - gSavedSettings.setString("FirstName", gFirstname); - gSavedSettings.setString("LastName", gLastname); - - if (gSavedSettings.getBOOL("RememberPassword")) + if(gDisplayName.empty()) { - // Successful login means the password is valid, so save it. - LLStartUp::savePasswordToDisk(gPassword); + if(response.has("first_name")) + { + gDisplayName.assign(response["first_name"].asString()); + LLStringUtil::replaceChar(gDisplayName, '"', ' '); + LLStringUtil::trim(gDisplayName); + } + if(response.has("last_name")) + { + text.assign(response["last_name"].asString()); + LLStringUtil::replaceChar(text, '"', ' '); + LLStringUtil::trim(text); + if(!gDisplayName.empty()) + { + gDisplayName += " "; + } + gDisplayName += text; + } } - else + if(gDisplayName.empty()) { - // Don't leave password from previous session sitting around - // during this login session. - LLStartUp::deletePasswordFromDisk(); + gDisplayName.assign(gUserCredential->asString()); } // this is their actual ability to access content @@ -2981,7 +2960,7 @@ bool process_login_success_response() // replace the default help URL format gSavedSettings.setString("HelpURLFormat",text); - // don't fall back to Nebraska's pre-connection static help + // don't fall back to Standalone's pre-connection static help gSavedSettings.setBOOL("HelpUseLocal", false); } @@ -3043,7 +3022,44 @@ bool process_login_success_response() //setup map of datetime strings to codes and slt & local time offset from utc LLStringOps::setupDatetimeInfo(pacific_daylight_time); } - + + static const char* CONFIG_OPTIONS[] = {"voice-config", "newuser-config"}; + for (int i = 0; i < sizeof(CONFIG_OPTIONS)/sizeof(CONFIG_OPTIONS[0]); i++) + { + LLSD options = response[CONFIG_OPTIONS[i]]; + if (!options.isArray() && (options.size() < 1) && !options[0].isMap()) + { + continue; + } + llinfos << "config option " << CONFIG_OPTIONS[i][0] << "response " << options << llendl; + for(LLSD::map_iterator option_it = options[0].beginMap(); + option_it != options[0].endMap(); + option_it++) + { + llinfos << "trying option " << option_it->first << llendl; + LLPointer control = gSavedSettings.getControl(option_it->first); + if(control.notNull()) + { + if(control->isType(TYPE_BOOLEAN)) + { + llinfos << "Setting BOOL from login " << option_it->first << " " << option_it->second << llendl; + + gSavedSettings.setBOOL(option_it->first, !((option_it->second == "F") || + (option_it->second == "false") || + (!option_it->second))); + } + else if (control->isType(TYPE_STRING)) + { + llinfos << "Setting String from login " << option_it->first << " " << option_it->second << llendl; + gSavedSettings.setString(option_it->first, option_it->second); + } + // we don't support other types now + + } + + } + } + LLSD initial_outfit = response["initial-outfit"][0]; if(initial_outfit.size()) { @@ -3097,7 +3113,7 @@ bool process_login_success_response() bool success = false; // JC: gesture loading done below, when we have an asset system - // in place. Don't delete/clear user_credentials until then. + // in place. Don't delete/clear gUserCredentials until then. if(gAgentID.notNull() && gAgentSessionID.notNull() && gMessageSystem->mOurCircuitCode diff --git a/indra/newview/llstartup.h b/indra/newview/llstartup.h index 92fe9521d3..16cc74504f 100644 --- a/indra/newview/llstartup.h +++ b/indra/newview/llstartup.h @@ -38,6 +38,7 @@ class LLViewerTexture ; class LLEventPump; class LLStartupListener; +class LLSLURL; // functions bool idle_startup(); @@ -101,26 +102,18 @@ public: static void loadInitialOutfit( const std::string& outfit_folder_name, const std::string& gender_name ); - // Load MD5 of user's password from local disk file. - static std::string loadPasswordFromDisk(); - - // Record MD5 of user's password for subsequent login. - static void savePasswordToDisk(const std::string& hashed_password); - - // Delete the saved password local disk file. - static void deletePasswordFromDisk(); static bool dispatchURL(); // if we have a SLURL or sim string ("Ahern/123/45") that started // the viewer, dispatch it - static std::string sSLURLCommand; - // *HACK: On startup, if we were passed a secondlife://app/do/foo - // command URL, store it for later processing. - static void postStartupState(); + static void setStartSLURL(const LLSLURL& slurl); + static LLSLURL& getStartSLURL() { return sStartSLURL; } private: + static LLSLURL sStartSLURL; + static std::string startupStateToString(EStartupState state); static EStartupState gStartupState; // Do not set directly, use LLStartup::setStartupState static boost::scoped_ptr sStateWatcher; diff --git a/indra/newview/llstylemap.cpp b/indra/newview/llstylemap.cpp index 61705c4eb3..8fab3bb361 100644 --- a/indra/newview/llstylemap.cpp +++ b/indra/newview/llstylemap.cpp @@ -51,7 +51,7 @@ const LLStyle::Params &LLStyleMap::lookupAgent(const LLUUID &source) style_params.color.control = "HTMLLinkColor"; style_params.readonly_color.control = "HTMLLinkColor"; style_params.link_href = - LLSLURL::buildCommand("agent", source, "inspect"); + LLSLURL("agent", source, "inspect").getSLURLString(); } else { diff --git a/indra/newview/llurl.cpp b/indra/newview/llurl.cpp index ab65ead4c5..83a5839a93 100644 --- a/indra/newview/llurl.cpp +++ b/indra/newview/llurl.cpp @@ -286,5 +286,11 @@ const char * LLURL::getFullPath() return(sReturnString); } +const char * LLURL::getAuthority() +{ + strncpy(LLURL::sReturnString,mAuthority, LL_MAX_PATH -1); /* Flawfinder: ignore */ + LLURL::sReturnString[LL_MAX_PATH -1] = '\0'; + return(sReturnString); +} char LLURL::sReturnString[LL_MAX_PATH] = ""; diff --git a/indra/newview/llurl.h b/indra/newview/llurl.h index 9a089dd835..e41b83d29f 100644 --- a/indra/newview/llurl.h +++ b/indra/newview/llurl.h @@ -79,6 +79,7 @@ public: virtual const char *getFQURL() const; virtual const char *getFullPath(); + virtual const char *getAuthority(); virtual const char *updateRelativePath(const LLURL &url); diff --git a/indra/newview/llurldispatcher.cpp b/indra/newview/llurldispatcher.cpp index b88069cd48..a31c3a0f1b 100644 --- a/indra/newview/llurldispatcher.cpp +++ b/indra/newview/llurldispatcher.cpp @@ -4,7 +4,7 @@ * * $LicenseInfo:firstyear=2007&license=viewergpl$ * - * Copyright (c) 2007-2009, Linden Research, Inc. + * Copyright (c) 2010, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -12,13 +12,12 @@ * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * online at http://secondlife.com/developers/opensource/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * online at http://secondlife.com/developers/opensource/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, @@ -45,10 +44,10 @@ #include "llsidetray.h" #include "llslurl.h" #include "llstartup.h" // gStartupState -#include "llurlsimstring.h" #include "llweb.h" #include "llworldmapmessage.h" #include "llurldispatcherlistener.h" +#include "llviewernetwork.h" // library includes #include "llnotificationsutil.h" @@ -59,25 +58,25 @@ static LLURLDispatcherListener sURLDispatcherListener; class LLURLDispatcherImpl { public: - static bool dispatch(const std::string& url, + static bool dispatch(const LLSLURL& slurl, LLMediaCtrl* web, bool trusted_browser); // returns true if handled or explicitly blocked. - static bool dispatchRightClick(const std::string& url); + static bool dispatchRightClick(const LLSLURL& slurl); private: - static bool dispatchCore(const std::string& url, + static bool dispatchCore(const LLSLURL& slurl, bool right_mouse, LLMediaCtrl* web, bool trusted_browser); // handles both left and right click - static bool dispatchHelp(const std::string& url, bool right_mouse); + static bool dispatchHelp(const LLSLURL& slurl, bool right_mouse); // Handles sl://app.floater.html.help by showing Help floater. // Returns true if handled. - static bool dispatchApp(const std::string& url, + static bool dispatchApp(const LLSLURL& slurl, bool right_mouse, LLMediaCtrl* web, bool trusted_browser); @@ -85,16 +84,16 @@ private: // by showing panel in Search floater. // Returns true if handled or explicitly blocked. - static bool dispatchRegion(const std::string& url, bool right_mouse); + static bool dispatchRegion(const LLSLURL& slurl, bool right_mouse); // handles secondlife://Ahern/123/45/67/ // Returns true if handled. - static void regionHandleCallback(U64 handle, const std::string& url, + static void regionHandleCallback(U64 handle, const LLSLURL& slurl, const LLUUID& snapshot_id, bool teleport); // Called by LLWorldMap when a location has been resolved to a // region name - static void regionNameCallback(U64 handle, const std::string& url, + static void regionNameCallback(U64 handle, const LLSLURL& slurl, const LLUUID& snapshot_id, bool teleport); // Called by LLWorldMap when a region name has been resolved to a // location in-world, used by places-panel display. @@ -103,65 +102,57 @@ private: }; // static -bool LLURLDispatcherImpl::dispatchCore(const std::string& url, +bool LLURLDispatcherImpl::dispatchCore(const LLSLURL& slurl, bool right_mouse, LLMediaCtrl* web, bool trusted_browser) { - if (url.empty()) return false; - //if (dispatchHelp(url, right_mouse)) return true; - if (dispatchApp(url, right_mouse, web, trusted_browser)) return true; - if (dispatchRegion(url, right_mouse)) return true; + //if (dispatchHelp(slurl, right_mouse)) return true; + switch(slurl.getType()) + { + case LLSLURL::APP: + return dispatchApp(slurl, right_mouse, web, trusted_browser); + case LLSLURL::LOCATION: + return dispatchRegion(slurl, right_mouse); + default: + return false; + } /* // Inform the user we can't handle this std::map args; - args["SLURL"] = url; + args["SLURL"] = slurl; r; */ - - return false; } // static -bool LLURLDispatcherImpl::dispatch(const std::string& url, +bool LLURLDispatcherImpl::dispatch(const LLSLURL& slurl, LLMediaCtrl* web, bool trusted_browser) { - llinfos << "url: " << url << llendl; const bool right_click = false; - return dispatchCore(url, right_click, web, trusted_browser); + return dispatchCore(slurl, right_click, web, trusted_browser); } // static -bool LLURLDispatcherImpl::dispatchRightClick(const std::string& url) +bool LLURLDispatcherImpl::dispatchRightClick(const LLSLURL& slurl) { - llinfos << "url: " << url << llendl; const bool right_click = true; LLMediaCtrl* web = NULL; const bool trusted_browser = false; - return dispatchCore(url, right_click, web, trusted_browser); + return dispatchCore(slurl, right_click, web, trusted_browser); } // static -bool LLURLDispatcherImpl::dispatchApp(const std::string& url, +bool LLURLDispatcherImpl::dispatchApp(const LLSLURL& slurl, bool right_mouse, LLMediaCtrl* web, bool trusted_browser) { - // ensure the URL is in the secondlife:///app/ format - if (!LLSLURL::isSLURLCommand(url)) - { - return false; - } - - LLURI uri(url); - LLSD pathArray = uri.pathArray(); - pathArray.erase(0); // erase "app" - std::string cmd = pathArray.get(0); - pathArray.erase(0); // erase "cmd" + llinfos << "cmd: " << slurl.getAppCmd() << " path: " << slurl.getAppPath() << " query: " << slurl.getAppQuery() << llendl; bool handled = LLCommandDispatcher::dispatch( - cmd, pathArray, uri.queryMap(), web, trusted_browser); + slurl.getAppCmd(), slurl.getAppPath(), slurl.getAppQuery(), web, trusted_browser); // alert if we didn't handle this secondlife:///app/ SLURL // (but still return true because it is a valid app SLURL) @@ -173,81 +164,72 @@ bool LLURLDispatcherImpl::dispatchApp(const std::string& url, } // static -bool LLURLDispatcherImpl::dispatchRegion(const std::string& url, bool right_mouse) +bool LLURLDispatcherImpl::dispatchRegion(const LLSLURL& slurl, bool right_mouse) { - if (!LLSLURL::isSLURL(url)) - { - return false; - } - - std::string sim_string = LLSLURL::stripProtocol(url); - std::string region_name; - S32 x = 128; - S32 y = 128; - S32 z = 0; - if (! LLURLSimString::parse(sim_string, ®ion_name, &x, &y, &z)) - { - return false; - } - + if(slurl.getType() != LLSLURL::LOCATION) + { + return false; + } // Before we're logged in, need to update the startup screen // to tell the user where they are going. if (LLStartUp::getStartupState() < STATE_LOGIN_CLEANUP) { - // Parse it and stash in globals, it will be dispatched in - // STATE_CLEANUP. - LLURLSimString::setString(url); // We're at the login screen, so make sure user can see // the login location box to know where they are going. - LLPanelLogin::refreshLocation( true ); + LLPanelLogin::setLocation(slurl); return true; } // LLFloaterURLDisplay functionality moved to LLPanelPlaces in Side Tray. - //LLFloaterURLDisplay* url_displayp = LLFloaterReg::getTypedInstance("preview_url",LLSD()); - //if(url_displayp) url_displayp->setName(region_name); + //LLFloaterURLDisplay* slurl_displayp = LLFloaterReg::getTypedInstance("preview_url",LLSD()); + //if(slurl_displayp) slurl_displayp->setName(region_name); // Request a region handle by name - LLWorldMapMessage::getInstance()->sendNamedRegionRequest(region_name, - LLURLDispatcherImpl::regionNameCallback, - url, - false); // don't teleport + LLWorldMapMessage::getInstance()->sendNamedRegionRequest(slurl.getRegion(), + LLURLDispatcherImpl::regionNameCallback, + slurl.getSLURLString(), + false); // don't teleport return true; } /*static*/ -void LLURLDispatcherImpl::regionNameCallback(U64 region_handle, const std::string& url, const LLUUID& snapshot_id, bool teleport) +void LLURLDispatcherImpl::regionNameCallback(U64 region_handle, const LLSLURL& slurl, const LLUUID& snapshot_id, bool teleport) { - std::string sim_string = LLSLURL::stripProtocol(url); - std::string region_name; - S32 x = 128; - S32 y = 128; - S32 z = 0; - - if (LLURLSimString::parse(sim_string, ®ion_name, &x, &y, &z)) - { - regionHandleCallback(region_handle, url, snapshot_id, teleport); - } + + if(slurl.getType() == LLSLURL::LOCATION) + { + regionHandleCallback(region_handle, slurl, snapshot_id, teleport); + } } /* static */ -void LLURLDispatcherImpl::regionHandleCallback(U64 region_handle, const std::string& url, const LLUUID& snapshot_id, bool teleport) +void LLURLDispatcherImpl::regionHandleCallback(U64 region_handle, const LLSLURL& slurl, const LLUUID& snapshot_id, bool teleport) { - std::string sim_string = LLSLURL::stripProtocol(url); - std::string region_name; - S32 x = 128; - S32 y = 128; - S32 z = 0; - LLURLSimString::parse(sim_string, ®ion_name, &x, &y, &z); - - LLVector3 local_pos; - local_pos.mV[VX] = (F32)x; - local_pos.mV[VY] = (F32)y; - local_pos.mV[VZ] = (F32)z; + // we can't teleport cross grid at this point + if((!LLGridManager::getInstance()->isSystemGrid(slurl.getGrid()) || !LLGridManager::getInstance()->isSystemGrid()) && + (slurl.getGrid() != LLGridManager::getInstance()->getGrid())) + { + LLSD args; + args["SLURL"] = slurl.getLocationString(); + args["CURRENT_GRID"] = LLGridManager::getInstance()->getGridLabel(); + LLSD grid_info = LLGridManager::getInstance()->getGridInfo(slurl.getGrid()); + + if(grid_info.has(GRID_LABEL_VALUE)) + { + args["GRID"] = grid_info[GRID_LABEL_VALUE].asString(); + } + else + { + args["GRID"] = slurl.getGrid(); + } + LLNotificationsUtil::add("CantTeleportToGrid", args); + return; + } + LLVector3d global_pos = from_region_handle(region_handle); - global_pos += LLVector3d(local_pos); + global_pos += LLVector3d(slurl.getPosition()); if (teleport) { @@ -271,8 +253,8 @@ void LLURLDispatcherImpl::regionHandleCallback(U64 region_handle, const std::str // LLFloaterURLDisplay functionality moved to LLPanelPlaces in Side Tray. // // display informational floater, allow user to click teleport btn -// LLFloaterURLDisplay* url_displayp = LLFloaterReg::getTypedInstance("preview_url",LLSD()); -// if(url_displayp) +// LLFloaterURLDisplay* slurl_displayp = LLFloaterReg::getTypedInstance("preview_url",LLSD()); +// if(slurl_displayp) // { // url_displayp->displayParcelInfo(region_handle, local_pos); // if(snapshot_id.notNull()) @@ -287,7 +269,7 @@ void LLURLDispatcherImpl::regionHandleCallback(U64 region_handle, const std::str //--------------------------------------------------------------------------- // Teleportation links are handled here because they are tightly coupled -// to URL parsing and sim-fragment parsing +// to SLURL parsing and sim-fragment parsing class LLTeleportHandler : public LLCommandHandler { public: @@ -303,18 +285,21 @@ public: // a global position, and teleport to it if (tokens.size() < 1) return false; - // Region names may be %20 escaped. - std::string region_name = LLURLSimString::unescapeRegionName(tokens[0]); - - // build secondlife://De%20Haro/123/45/67 for use in callback - std::string url = LLSLURL::PREFIX_SECONDLIFE; - for (int i = 0; i < tokens.size(); ++i) + LLVector3 coords(128, 128, 0); + if (tokens.size() <= 4) { - url += tokens[i].asString() + "/"; + coords = LLVector3(tokens[1].asReal(), + tokens[2].asReal(), + tokens[3].asReal()); } + + // Region names may be %20 escaped. + + std::string region_name = LLURI::unescape(tokens[0]); + LLWorldMapMessage::getInstance()->sendNamedRegionRequest(region_name, LLURLDispatcherImpl::regionHandleCallback, - url, + LLSLURL(region_name, coords).getSLURLString(), true); // teleport return true; } @@ -324,21 +309,21 @@ LLTeleportHandler gTeleportHandler; //--------------------------------------------------------------------------- // static -bool LLURLDispatcher::dispatch(const std::string& url, +bool LLURLDispatcher::dispatch(const std::string& slurl, LLMediaCtrl* web, bool trusted_browser) { - return LLURLDispatcherImpl::dispatch(url, web, trusted_browser); + return LLURLDispatcherImpl::dispatch(LLSLURL(slurl), web, trusted_browser); } // static -bool LLURLDispatcher::dispatchRightClick(const std::string& url) +bool LLURLDispatcher::dispatchRightClick(const std::string& slurl) { - return LLURLDispatcherImpl::dispatchRightClick(url); + return LLURLDispatcherImpl::dispatchRightClick(LLSLURL(slurl)); } // static -bool LLURLDispatcher::dispatchFromTextEditor(const std::string& url) +bool LLURLDispatcher::dispatchFromTextEditor(const std::string& slurl) { // *NOTE: Text editors are considered sources of trusted URLs // in order to make avatar profile links in chat history work. @@ -348,5 +333,7 @@ bool LLURLDispatcher::dispatchFromTextEditor(const std::string& url) // *TODO: Make this trust model more refined. JC const bool trusted_browser = true; LLMediaCtrl* web = NULL; - return LLURLDispatcherImpl::dispatch(url, web, trusted_browser); + return LLURLDispatcherImpl::dispatch(LLSLURL(slurl), web, trusted_browser); } + + diff --git a/indra/newview/llurldispatcher.h b/indra/newview/llurldispatcher.h index ff8a351253..407e417e58 100644 --- a/indra/newview/llurldispatcher.h +++ b/indra/newview/llurldispatcher.h @@ -2,9 +2,9 @@ * @file llurldispatcher.h * @brief Central registry for all SL URL handlers * - * $LicenseInfo:firstyear=2007&license=viewergpl$ + * $LicenseInfo:firstyear=2010&license=viewergpl$ * - * Copyright (c) 2007-2009, Linden Research, Inc. + * Copyright (c) 2007-2010, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -12,13 +12,12 @@ * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * online at http://secondlife.com/developers/opensource/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * online at http://secondlife.com/developers/opensource/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, @@ -31,16 +30,16 @@ */ #ifndef LLURLDISPATCHER_H #define LLURLDISPATCHER_H - class LLMediaCtrl; class LLURLDispatcher { public: - static bool dispatch(const std::string& url, + + static bool dispatch(const std::string& slurl, LLMediaCtrl* web, - bool trusted_browser); + bool trusted_browser); // At startup time and on clicks in internal web browsers, // teleport, open map, or run requested command. // @param url @@ -54,9 +53,9 @@ public: // that navigates to trusted (Linden Lab) pages. // Returns true if someone handled the URL. - static bool dispatchRightClick(const std::string& url); + static bool dispatchRightClick(const std::string& slurl); - static bool dispatchFromTextEditor(const std::string& url); + static bool dispatchFromTextEditor(const std::string& slurl); }; #endif diff --git a/indra/newview/llurllineeditorctrl.cpp b/indra/newview/llurllineeditorctrl.cpp index 1d2687a8c2..8488527185 100644 --- a/indra/newview/llurllineeditorctrl.cpp +++ b/indra/newview/llurllineeditorctrl.cpp @@ -89,7 +89,7 @@ void LLURLLineEditor::copyEscapedURLToClipboard() const std::string unescaped_text = wstring_to_utf8str(mText.getWString().substr(left_pos, length)); LLWString text_to_copy; - if (LLSLURL::isSLURL(unescaped_text)) + if (LLSLURL(unescaped_text).isValid()) text_to_copy = utf8str_to_wstring(LLWeb::escapeURL(unescaped_text)); else text_to_copy = utf8str_to_wstring(unescaped_text); diff --git a/indra/newview/llvieweraudio.cpp b/indra/newview/llvieweraudio.cpp index 2661c9f32b..ef6f4194e0 100644 --- a/indra/newview/llvieweraudio.cpp +++ b/indra/newview/llvieweraudio.cpp @@ -157,21 +157,21 @@ void audio_update_volume(bool force_update) LLViewerMedia::setVolume( media_muted ? 0.0f : media_volume ); // Voice - if (gVoiceClient) + if (LLVoiceClient::getInstance()) { F32 voice_volume = gSavedSettings.getF32("AudioLevelVoice"); voice_volume = mute_volume * master_volume * voice_volume; BOOL voice_mute = gSavedSettings.getBOOL("MuteVoice"); - gVoiceClient->setVoiceVolume(voice_mute ? 0.f : voice_volume); - gVoiceClient->setMicGain(voice_mute ? 0.f : gSavedSettings.getF32("AudioLevelMic")); + LLVoiceClient::getInstance()->setVoiceVolume(voice_mute ? 0.f : voice_volume); + LLVoiceClient::getInstance()->setMicGain(voice_mute ? 0.f : gSavedSettings.getF32("AudioLevelMic")); if (!gViewerWindow->getActive() && (gSavedSettings.getBOOL("MuteWhenMinimized"))) { - gVoiceClient->setMuteMic(true); + LLVoiceClient::getInstance()->setMuteMic(true); } else { - gVoiceClient->setMuteMic(false); + LLVoiceClient::getInstance()->setMuteMic(false); } } } diff --git a/indra/newview/llviewercontrol.cpp b/indra/newview/llviewercontrol.cpp index b2b7e653e4..8627f08891 100644 --- a/indra/newview/llviewercontrol.cpp +++ b/indra/newview/llviewercontrol.cpp @@ -413,9 +413,9 @@ bool handleHighResSnapshotChanged(const LLSD& newvalue) bool handleVoiceClientPrefsChanged(const LLSD& newvalue) { - if(gVoiceClient) + if(LLVoiceClient::getInstance()) { - gVoiceClient->updateSettings(); + LLVoiceClient::getInstance()->updateSettings(); } return true; } @@ -446,7 +446,7 @@ bool handleVelocityInterpolate(const LLSD& newvalue) bool handleForceShowGrid(const LLSD& newvalue) { - LLPanelLogin::refreshLocation( false ); + LLPanelLogin::updateServer( ); return true; } diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index b42d25c1d8..17221219eb 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -36,7 +36,6 @@ #include "llnotificationsutil.h" #include "llsdserialize.h" #include "message.h" -#include "indra_constants.h" #include "llagent.h" #include "llagentcamera.h" @@ -264,10 +263,14 @@ void LLViewerInventoryItem::fetchFromServer(void) const // we have to check region. It can be null after region was destroyed. See EXT-245 if (region) { - if( ALEXANDRIA_LINDEN_ID.getString() == mPermissions.getOwner().getString()) - url = region->getCapability("FetchLib"); - else - url = region->getCapability("FetchInventory"); + if(gAgent.getID() != mPermissions.getOwner()) + { + url = region->getCapability("FetchLib"); + } + else + { + url = region->getCapability("FetchInventory"); + } } else { diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 7d87f06794..a8b1257cf6 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -429,7 +429,7 @@ void init_menus() gPopupMenuView->setBackgroundColor( color ); // If we are not in production, use a different color to make it apparent. - if (LLViewerLogin::getInstance()->isInProductionGrid()) + if (LLGridManager::getInstance()->isInProductionGrid()) { color = LLUIColorTable::instance().getColor( "MenuBarBgColor" ); } @@ -445,7 +445,7 @@ void init_menus() menu_bar_holder->addChild(gMenuBarView); gViewerWindow->setMenuBackgroundColor(false, - LLViewerLogin::getInstance()->isInProductionGrid()); + LLGridManager::getInstance()->isInProductionGrid()); // Assume L$10 for now, the server will tell us the real cost at login // *TODO:Also fix cost in llfolderview.cpp for Inventory menus @@ -3467,7 +3467,7 @@ void set_god_level(U8 god_level) if(gViewerWindow) { gViewerWindow->setMenuBackgroundColor(god_level > GOD_NOT, - LLViewerLogin::getInstance()->isInProductionGrid()); + LLGridManager::getInstance()->isInProductionGrid()); } LLSD args; @@ -3507,7 +3507,7 @@ BOOL check_toggle_hacked_godmode(void*) bool enable_toggle_hacked_godmode(void*) { - return !LLViewerLogin::getInstance()->isInProductionGrid(); + return !LLGridManager::getInstance()->isInProductionGrid(); } #endif @@ -4378,7 +4378,7 @@ BOOL enable_take() return TRUE; #else # ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (!LLViewerLogin::getInstance()->isInProductionGrid() + if (!LLGridManager::getInstance()->isInProductionGrid() && gAgent.isGodlike()) { return TRUE; @@ -4991,7 +4991,7 @@ bool enable_object_delete() TRUE; #else # ifdef TOGGLE_HACKED_GODLIKE_VIEWER - (!LLViewerLogin::getInstance()->isInProductionGrid() + (!LLGridManager::getInstance()->isInProductionGrid() && gAgent.isGodlike()) || # endif LLSelectMgr::getInstance()->canDoDelete(); @@ -6627,7 +6627,7 @@ bool enable_object_take_copy() all_valid = true; #ifndef HACKED_GODLIKE_VIEWER # ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (LLViewerLogin::getInstance()->isInProductionGrid() + if (LLGridManager::getInstance()->isInProductionGrid() || !gAgent.isGodlike()) # endif { @@ -6689,7 +6689,7 @@ BOOL enable_save_into_inventory(void*) return TRUE; #else # ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (!LLViewerLogin::getInstance()->isInProductionGrid() + if (!LLGridManager::getInstance()->isInProductionGrid() && gAgent.isGodlike()) { return TRUE; diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 1426c0b9e2..7346b2a76e 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -1573,9 +1573,9 @@ void inventory_offer_handler(LLOfferInfo* info) payload["give_inventory_notification"] = FALSE; args["OBJECTFROMNAME"] = info->mFromName; args["NAME"] = info->mFromName; - args["NAME_SLURL"] = LLSLURL::buildCommand("agent", info->mFromID, "about"); + args["NAME_SLURL"] = LLSLURL("agent", info->mFromID, "about").getSLURLString(); std::string verb = "select?name=" + LLURI::escape(msg); - args["ITEM_SLURL"] = LLSLURL::buildCommand("inventory", info->mObjectID, verb.c_str()); + args["ITEM_SLURL"] = LLSLURL("inventory", info->mObjectID, verb.c_str()).getSLURLString(); LLNotification::Params p("ObjectGiveItem"); @@ -2244,10 +2244,7 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) query_string["groupowned"] = "true"; } - std::ostringstream link; - link << "secondlife:///app/objectim/" << session_id << LLURI::mapToQueryString(query_string); - - chat.mURL = link.str(); + chat.mURL = LLSLURL("objectim", session_id, "").getSLURLString(); chat.mText = message; chat.mSourceType = CHAT_SOURCE_OBJECT; @@ -2330,7 +2327,7 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) { LLSD args; // *TODO: Translate -> [FIRST] [LAST] (maybe) - args["NAME_SLURL"] = LLSLURL::buildCommand("agent", from_id, "about"); + args["NAME_SLURL"] = LLSLURL("agent", from_id, "about").getSLURLString(); args["MESSAGE"] = message; LLSD payload; payload["from_id"] = from_id; @@ -2396,7 +2393,7 @@ void process_improved_im(LLMessageSystem *msg, void **user_data) } else { - args["NAME_SLURL"] = LLSLURL::buildCommand("agent", from_id, "about"); + args["NAME_SLURL"] = LLSLURL("agent", from_id, "about").getSLURLString(); if(message.empty()) { //support for frienship offers from clients before July 2008 @@ -3155,7 +3152,9 @@ void process_agent_movement_complete(LLMessageSystem* msg, void**) { // Chat the "back" SLURL. (DEV-4907) - LLSD substitution = LLSD().with("[T_SLURL]", gAgent.getTeleportSourceSLURL()); + LLSLURL slurl; + gAgent.getTeleportSourceSLURL(slurl); + LLSD substitution = LLSD().with("[T_SLURL]", slurl.getSLURLString()); std::string completed_from = LLAgent::sTeleportProgressMessages["completed_from"]; LLStringUtil::format(completed_from, substitution); @@ -5548,7 +5547,9 @@ void send_group_notice(const LLUUID& group_id, bool handle_lure_callback(const LLSD& notification, const LLSD& response) { std::string text = response["message"].asString(); - text.append("\r\n").append(LLAgentUI::buildSLURL()); + LLSLURL slurl; + LLAgentUI::buildSLURL(slurl); + text.append("\r\n").append(slurl.getSLURLString()); S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if(0 == option) @@ -5991,7 +5992,7 @@ void process_covenant_reply(LLMessageSystem* msg, void**) LLFloaterBuyLand::updateEstateName(estate_name); std::string owner_name = - LLSLURL::buildCommand("agent", estate_owner_id, "inspect"); + LLSLURL("agent", estate_owner_id, "inspect").getSLURLString(); LLPanelEstateCovenant::updateEstateOwnerName(owner_name); LLPanelLandCovenant::updateEstateOwnerName(owner_name); LLFloaterBuyLand::updateEstateOwnerName(owner_name); diff --git a/indra/newview/llviewernetwork.cpp b/indra/newview/llviewernetwork.cpp index 987d23630a..d7bb4efe85 100644 --- a/indra/newview/llviewernetwork.cpp +++ b/indra/newview/llviewernetwork.cpp @@ -5,7 +5,7 @@ * * $LicenseInfo:firstyear=2006&license=viewergpl$ * - * Copyright (c) 2006-2009, Linden Research, Inc. + * Copyright (c) 2006-2010, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -13,13 +13,12 @@ * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * online at http://secondlife.com/developers/opensource/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * online at http://secondlife.com/developers/opensource/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, @@ -34,303 +33,478 @@ #include "llviewerprecompiledheaders.h" #include "llviewernetwork.h" +#include "llviewercontrol.h" +#include "llsdserialize.h" +#include "llweb.h" -#include "llevents.h" -#include "net.h" + +const char* DEFAULT_LOGIN_PAGE = "http://secondlife.com/app/login/"; -#include "llviewercontrol.h" -#include "lllogin.h" +const char* SYSTEM_GRID_SLURL_BASE = "secondlife://%s/secondlife/"; +const char* MAIN_GRID_SLURL_BASE = "http://maps.secondlife.com/secondlife/"; +const char* SYSTEM_GRID_APP_SLURL_BASE = "secondlife:///app"; -struct LLGridData -{ - const char* mLabel; - const char* mName; - const char* mLoginURI; - const char* mHelperURI; -}; +const char* DEFAULT_SLURL_BASE = "https://%s/region/"; +const char* DEFAULT_APP_SLURL_BASE = "x-grid-location-info://%s/app"; -static LLGridData gGridInfo[GRID_INFO_COUNT] = +LLGridManager::LLGridManager() { - { "None", "", "", ""}, - { "Aditi", - "util.aditi.lindenlab.com", - "https://login.aditi.lindenlab.com/cgi-bin/login.cgi", - "http://aditi-secondlife.webdev.lindenlab.com/helpers/" }, - { "Agni", - "util.agni.lindenlab.com", - "https://login.agni.lindenlab.com/cgi-bin/login.cgi", - "https://secondlife.com/helpers/" }, - { "Aruna", - "util.aruna.lindenlab.com", - "https://login.aruna.lindenlab.com/cgi-bin/login.cgi", - "http://aruna-secondlife.webdev.lindenlab.com/helpers/" }, - { "Bharati", - "util.bharati.lindenlab.com", - "https://login.bharati.lindenlab.com/cgi-bin/login.cgi", - "http://bharati-secondlife.webdev.lindenlab.com/helpers/" }, - { "Chandra", - "util.chandra.lindenlab.com", - "https://login.chandra.lindenlab.com/cgi-bin/login.cgi", - "http://chandra-secondlife.webdev.lindenlab.com/helpers/" }, - { "Damballah", - "util.damballah.lindenlab.com", - "https://login.damballah.lindenlab.com/cgi-bin/login.cgi", - "http://damballah-secondlife.webdev.lindenlab.com/helpers/" }, - { "Danu", - "util.danu.lindenlab.com", - "https://login.danu.lindenlab.com/cgi-bin/login.cgi", - "http://danu-secondlife.webdev.lindenlab.com/helpers/" }, - { "Durga", - "util.durga.lindenlab.com", - "https://login.durga.lindenlab.com/cgi-bin/login.cgi", - "http://durga-secondlife.webdev.lindenlab.com/helpers/" }, - { "Ganga", - "util.ganga.lindenlab.com", - "https://login.ganga.lindenlab.com/cgi-bin/login.cgi", - "http://ganga-secondlife.webdev.lindenlab.com/helpers/" }, - { "Mitra", - "util.mitra.lindenlab.com", - "https://login.mitra.lindenlab.com/cgi-bin/login.cgi", - "http://mitra-secondlife.webdev.lindenlab.com/helpers/" }, - { "Mohini", - "util.mohini.lindenlab.com", - "https://login.mohini.lindenlab.com/cgi-bin/login.cgi", - "http://mohini-secondlife.webdev.lindenlab.com/helpers/" }, - { "Nandi", - "util.nandi.lindenlab.com", - "https://login.nandi.lindenlab.com/cgi-bin/login.cgi", - "http://nandi-secondlife.webdev.lindenlab.com/helpers/" }, - { "Parvati", - "util.parvati.lindenlab.com", - "https://login.parvati.lindenlab.com/cgi-bin/login.cgi", - "http://parvati-secondlife.webdev.lindenlab.com/helpers/" }, - { "Radha", - "util.radha.lindenlab.com", - "https://login.radha.lindenlab.com/cgi-bin/login.cgi", - "http://radha-secondlife.webdev.lindenlab.com/helpers/" }, - { "Ravi", - "util.ravi.lindenlab.com", - "https://login.ravi.lindenlab.com/cgi-bin/login.cgi", - "http://ravi-secondlife.webdev.lindenlab.com/helpers/" }, - { "Siva", - "util.siva.lindenlab.com", - "https://login.siva.lindenlab.com/cgi-bin/login.cgi", - "http://siva-secondlife.webdev.lindenlab.com/helpers/" }, - { "Shakti", - "util.shakti.lindenlab.com", - "https://login.shakti.lindenlab.com/cgi-bin/login.cgi", - "http://shakti-secondlife.webdev.lindenlab.com/helpers/" }, - { "Skanda", - "util.skanda.lindenlab.com", - "https://login.skanda.lindenlab.com/cgi-bin/login.cgi", - "http://skanda-secondlife.webdev.lindenlab.com/helpers/" }, - { "Soma", - "util.soma.lindenlab.com", - "https://login.soma.lindenlab.com/cgi-bin/login.cgi", - "http://soma-secondlife.webdev.lindenlab.com/helpers/" }, - { "Uma", - "util.uma.lindenlab.com", - "https://login.uma.lindenlab.com/cgi-bin/login.cgi", - "http://uma-secondlife.webdev.lindenlab.com/helpers/" }, - { "Vaak", - "util.vaak.lindenlab.com", - "https://login.vaak.lindenlab.com/cgi-bin/login.cgi", - "http://vaak-secondlife.webdev.lindenlab.com/helpers/" }, - { "Yami", - "util.yami.lindenlab.com", - "https://login.yami.lindenlab.com/cgi-bin/login.cgi", - "http://yami-secondlife.webdev.lindenlab.com/helpers/" }, - { "Local", - "localhost", - "https://login.dmz.lindenlab.com/cgi-bin/login.cgi", - "" }, - { "Other", - "", - "https://login.dmz.lindenlab.com/cgi-bin/login.cgi", - "" } -}; - -const EGridInfo DEFAULT_GRID_CHOICE = GRID_INFO_AGNI; - - -unsigned char gMACAddress[MAC_ADDRESS_BYTES]; /* Flawfinder: ignore */ - -LLViewerLogin::LLViewerLogin() : - mGridChoice(DEFAULT_GRID_CHOICE) + // by default, we use the 'grids.xml' file in the user settings directory + // this file is an LLSD file containing multiple grid definitions. + // This file does not contain definitions for secondlife.com grids, + // as that would be a security issue when they are overwritten by + // an attacker. Don't want someone snagging a password. + std::string grid_file = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, + "grids.xml"); + initialize(grid_file); + +} + + +LLGridManager::LLGridManager(const std::string& grid_file) { + // initialize with an explicity grid file for testing. + initialize(grid_file); } - LLViewerLogin::~LLViewerLogin() - { - } +// +// LLGridManager - class for managing the list of known grids, and the current +// selection +// + -void LLViewerLogin::setGridChoice(EGridInfo grid) -{ - if(grid < 0 || grid >= GRID_INFO_COUNT) +// +// LLGridManager::initialze - initialize the list of known grids based +// on the fixed list of linden grids (fixed for security reasons) +// the grids.xml file +// and the command line. +void LLGridManager::initialize(const std::string& grid_file) +{ + // default grid list. + // Don't move to a modifiable file for security reasons, + mGrid.clear() ; + // set to undefined + mGridList = LLSD(); + mGridFile = grid_file; + // as we don't want an attacker to override our grid list + // to point the default grid to an invalid grid + addSystemGrid("None", "", "", "", DEFAULT_LOGIN_PAGE); + + + +#ifndef LL_RELEASE_FOR_DOWNLOAD + addSystemGrid("Agni", + MAINGRID, + "https://login.agni.lindenlab.com/cgi-bin/login.cgi", + "https://secondlife.com/helpers/", + DEFAULT_LOGIN_PAGE); +#else + addSystemGrid("Secondlife.com", + MAINGRID, + "https://login.agni.lindenlab.com/cgi-bin/login.cgi", + "https://secondlife.com/helpers/", + DEFAULT_LOGIN_PAGE, + "Agni"); +#endif // LL_RELEASE_FOR_DOWNLOAD + addSystemGrid("Aditi", + "util.aditi.lindenlab.com", + "https://login.aditi.lindenlab.com/cgi-bin/login.cgi", + "http://aditi-secondlife.webdev.lindenlab.com/helpers/", + DEFAULT_LOGIN_PAGE); + addSystemGrid("Aruna", + "util.aruna.lindenlab.com", + "https://login.aruna.lindenlab.com/cgi-bin/login.cgi", + "http://aruna-secondlife.webdev.lindenlab.com/helpers/", + DEFAULT_LOGIN_PAGE); + addSystemGrid("Durga", + "util.durga.lindenlab.com", + "https://login.durga.lindenlab.com/cgi-bin/login.cgi", + "http://durga-secondlife.webdev.lindenlab.com/helpers/", + DEFAULT_LOGIN_PAGE); + addSystemGrid("Ganga", + "util.ganga.lindenlab.com", + "https://login.ganga.lindenlab.com/cgi-bin/login.cgi", + "http://ganga-secondlife.webdev.lindenlab.com/helpers/", + DEFAULT_LOGIN_PAGE); + addSystemGrid("Mitra", + "util.mitra.lindenlab.com", + "https://login.mitra.lindenlab.com/cgi-bin/login.cgi", + "http://mitra-secondlife.webdev.lindenlab.com/helpers/", + DEFAULT_LOGIN_PAGE); + addSystemGrid("Mohini", + "util.mohini.lindenlab.com", + "https://login.mohini.lindenlab.com/cgi-bin/login.cgi", + "http://mohini-secondlife.webdev.lindenlab.com/helpers/", + DEFAULT_LOGIN_PAGE); + addSystemGrid("Nandi", + "util.nandi.lindenlab.com", + "https://login.nandi.lindenlab.com/cgi-bin/login.cgi", + "http://nandi-secondlife.webdev.lindenlab.com/helpers/", + DEFAULT_LOGIN_PAGE); + addSystemGrid("Radha", + "util.radha.lindenlab.com", + "https://login.radha.lindenlab.com/cgi-bin/login.cgi", + "http://radha-secondlife.webdev.lindenlab.com/helpers/", + DEFAULT_LOGIN_PAGE); + addSystemGrid("Ravi", + "util.ravi.lindenlab.com", + "https://login.ravi.lindenlab.com/cgi-bin/login.cgi", + "http://ravi-secondlife.webdev.lindenlab.com/helpers/", + DEFAULT_LOGIN_PAGE); + addSystemGrid("Siva", + "util.siva.lindenlab.com", + "https://login.siva.lindenlab.com/cgi-bin/login.cgi", + "http://siva-secondlife.webdev.lindenlab.com/helpers/", + DEFAULT_LOGIN_PAGE); + addSystemGrid("Shakti", + "util.shakti.lindenlab.com", + "https://login.shakti.lindenlab.com/cgi-bin/login.cgi", + "http://shakti-secondlife.webdev.lindenlab.com/helpers/", + DEFAULT_LOGIN_PAGE); + addSystemGrid("Soma", + "util.soma.lindenlab.com", + "https://login.soma.lindenlab.com/cgi-bin/login.cgi", + "http://soma-secondlife.webdev.lindenlab.com/helpers/", + DEFAULT_LOGIN_PAGE); + + addSystemGrid("Uma", + "util.uma.lindenlab.com", + "https://login.uma.lindenlab.com/cgi-bin/login.cgi", + "http://uma-secondlife.webdev.lindenlab.com/helpers/", + DEFAULT_LOGIN_PAGE); + addSystemGrid("Vaak", + "util.vaak.lindenlab.com", + "https://login.vaak.lindenlab.com/cgi-bin/login.cgi", + "http://vaak-secondlife.webdev.lindenlab.com/helpers/", + DEFAULT_LOGIN_PAGE); + addSystemGrid("Yami", + "util.yami.lindenlab.com", + "https://login.yami.lindenlab.com/cgi-bin/login.cgi", + "http://yami-secondlife.webdev.lindenlab.com/helpers/", + DEFAULT_LOGIN_PAGE); + addSystemGrid("Local (Linden)", + "localhost", + "https://login.dmz.lindenlab.com/cgi-bin/login.cgi", + "", + DEFAULT_LOGIN_PAGE); + + + LLSD other_grids; + llifstream llsd_xml; + if (!grid_file.empty()) { - llerrs << "Invalid grid index specified." << llendl; - return; + llsd_xml.open( grid_file.c_str(), std::ios::in | std::ios::binary ); + + // parse through the gridfile, inserting grids into the list unless + // they overwrite a linden grid. + if( llsd_xml.is_open()) + { + LLSDSerialize::fromXMLDocument( other_grids, llsd_xml ); + if(other_grids.isMap()) + { + for(LLSD::map_iterator grid_itr = other_grids.beginMap(); + grid_itr != other_grids.endMap(); + ++grid_itr) + { + LLSD::String key_name = grid_itr->first; + LLSD grid = grid_itr->second; + // TODO: Make sure gridfile specified label is not + // a system grid label + LL_INFOS("GridManager") << "reading: " << key_name << LL_ENDL; + if (mGridList.has(key_name) && + mGridList[key_name].has(GRID_IS_SYSTEM_GRID_VALUE)) + { + LL_INFOS("GridManager") << "Cannot override grid " << key_name << " as it's a system grid" << LL_ENDL; + // If the system grid does exist in the grids file, and it's marked as a favorite, set it as a favorite. + if(grid_itr->second.has(GRID_IS_FAVORITE_VALUE) && grid_itr->second[GRID_IS_FAVORITE_VALUE].asBoolean() ) + { + mGridList[key_name][GRID_IS_FAVORITE_VALUE] = TRUE; + } + } + else + { + try + { + addGrid(grid); + LL_INFOS("GridManager") << "Added grid: " << key_name << LL_ENDL; + } + catch (...) + { + } + } + } + llsd_xml.close(); + } + } } + + // load a grid from the command line. + // if the actual grid name is specified from the command line, + // set it as the 'selected' grid. + mGrid = gSavedSettings.getString("CmdLineGridChoice"); + LL_INFOS("GridManager") << "Grid Name: " << mGrid << LL_ENDL; + + // If a command line login URI was passed in, so we should add the command + // line grid to the list of grids - if(mGridChoice != grid || gSavedSettings.getS32("ServerChoice") != grid) + LLSD cmd_line_login_uri = gSavedSettings.getLLSD("CmdLineLoginURI"); + if (cmd_line_login_uri.isString()) { - mGridChoice = grid; - if(GRID_INFO_LOCAL == mGridChoice) + LL_INFOS("GridManager") << "adding cmd line login uri" << LL_ENDL; + // grab the other related URI values + std::string cmd_line_helper_uri = gSavedSettings.getString("CmdLineHelperURI"); + std::string cmd_line_login_page = gSavedSettings.getString("LoginPage"); + + // we've a cmd line login, so add a grid for the command line, + // overwriting any existing grids + LLSD grid = LLSD::emptyMap(); + grid[GRID_LOGIN_URI_VALUE] = LLSD::emptyArray(); + grid[GRID_LOGIN_URI_VALUE].append(cmd_line_login_uri); + LL_INFOS("GridManager") << "cmd line login uri: " << cmd_line_login_uri.asString() << LL_ENDL; + LLURI uri(cmd_line_login_uri.asString()); + if (mGrid.empty()) { - mGridName = LOOPBACK_ADDRESS_STRING; + // if a grid name was not passed in via the command line, + // then set the grid name based on the hostname of the + // login uri + mGrid = uri.hostName(); } - else if(GRID_INFO_OTHER == mGridChoice) + + grid[GRID_VALUE] = mGrid; + + if (mGridList.has(mGrid) && mGridList[mGrid].has(GRID_LABEL_VALUE)) { - // *FIX:Mani - could this possibly be valid? - mGridName = "other"; + grid[GRID_LABEL_VALUE] = mGridList[mGrid][GRID_LABEL_VALUE]; } else { - mGridName = gGridInfo[mGridChoice].mLabel; + grid[GRID_LABEL_VALUE] = mGrid; + } + if(!cmd_line_helper_uri.empty()) + { + grid[GRID_HELPER_URI_VALUE] = cmd_line_helper_uri; } - gSavedSettings.setS32("ServerChoice", mGridChoice); - gSavedSettings.setString("CustomServer", ""); + if(!cmd_line_login_page.empty()) + { + grid[GRID_LOGIN_PAGE_VALUE] = cmd_line_login_page; + } + // if the login page, helper URI value, and so on are not specified, + // add grid will generate them. + + // Also, we will override a system grid if values are passed in via the command + // line, for testing. These values will not be remembered though. + if (mGridList.has(mGrid) && mGridList[mGrid].has(GRID_IS_SYSTEM_GRID_VALUE)) + { + grid[GRID_IS_SYSTEM_GRID_VALUE] = TRUE; + } + addGrid(grid); } -} + + // if a grid was not passed in via the command line, grab it from the CurrentGrid setting. + if (mGrid.empty()) + { + + mGrid = gSavedSettings.getString("CurrentGrid"); + } + + if (mGrid.empty() || !mGridList.has(mGrid)) + { + // the grid name was empty, or the grid isn't actually in the list, then set it to the + // appropriate default. + LL_INFOS("GridManager") << "Resetting grid as grid name " << mGrid << " is not in the list" << LL_ENDL; +#if LL_RELEASE_FOR_DOWNLOAD + mGrid = MAINGRID; +#else + mGrid = ""; +#endif + } + LL_INFOS("GridManager") << "Selected grid is " << mGrid << LL_ENDL; + gSavedSettings.setString("CurrentGrid", mGrid); -void LLViewerLogin::setGridChoice(const std::string& grid_name) -{ - // Set the grid choice based on a string. - // The string can be: - // - a grid label from the gGridInfo table - // - an ip address - if(!grid_name.empty()) - { - // find the grid choice from the user setting. - int grid_index = GRID_INFO_NONE; - for(;grid_index < GRID_INFO_OTHER; ++grid_index) - { - if(0 == LLStringUtil::compareInsensitive(gGridInfo[grid_index].mLabel, grid_name)) - { - // Founding a matching label in the list... - setGridChoice((EGridInfo)grid_index); - break; - } - } - - if(GRID_INFO_OTHER == grid_index) - { - // *FIX:MEP Can and should we validate that this is an IP address? - mGridChoice = GRID_INFO_OTHER; - mGridName = grid_name; - gSavedSettings.setS32("ServerChoice", mGridChoice); - gSavedSettings.setString("CustomServer", mGridName); - } - } } -void LLViewerLogin::resetURIs() +LLGridManager::~LLGridManager() { - // Clear URIs when picking a new server - gSavedSettings.setLLSD("CmdLineLoginURI", LLSD::emptyArray()); - gSavedSettings.setString("CmdLineHelperURI", ""); + saveFavorites(); } -EGridInfo LLViewerLogin::getGridChoice() const +// +// LLGridManager::addGrid - add a grid to the grid list, populating the needed values +// if they're not populated yet. +// + +void LLGridManager::addGrid(LLSD& grid_data) { - return mGridChoice; + if (grid_data.isMap() && grid_data.has(GRID_VALUE)) + { + std::string grid = utf8str_tolower(grid_data[GRID_VALUE]); + + // grid should be in the form of a dns address + if (!grid.empty() && + grid.find_first_not_of("abcdefghijklmnopqrstuvwxyz1234567890-_. ") != std::string::npos) + { + printf("grid name: %s", grid.c_str()); + throw LLInvalidGridName(grid); + } + + // populate the other values if they don't exist + if (!grid_data.has(GRID_LABEL_VALUE)) + { + grid_data[GRID_LABEL_VALUE] = grid; + } + if (!grid_data.has(GRID_ID_VALUE)) + { + grid_data[GRID_ID_VALUE] = grid; + } + + // if the grid data doesn't include any of the URIs, then + // generate them from the grid, which should be a dns address + if (!grid_data.has(GRID_LOGIN_URI_VALUE)) + { + grid_data[GRID_LOGIN_URI_VALUE] = LLSD::emptyArray(); + grid_data[GRID_LOGIN_URI_VALUE].append(std::string("https://") + + grid + "/cgi-bin/login.cgi"); + } + // Populate to the default values + if (!grid_data.has(GRID_LOGIN_PAGE_VALUE)) + { + grid_data[GRID_LOGIN_PAGE_VALUE] = std::string("http://") + grid + "/app/login/"; + } + if (!grid_data.has(GRID_HELPER_URI_VALUE)) + { + grid_data[GRID_HELPER_URI_VALUE] = std::string("https://") + grid + "/helpers/"; + } + LL_INFOS("GridManager") << "ADDING: " << grid << LL_ENDL; + mGridList[grid] = grid_data; + } } -std::string LLViewerLogin::getGridLabel() const +// +// LLGridManager::addSystemGrid - helper for adding a system grid. +void LLGridManager::addSystemGrid(const std::string& label, + const std::string& name, + const std::string& login, + const std::string& helper, + const std::string& login_page, + const std::string& login_id) { - if(mGridChoice == GRID_INFO_NONE) + LLSD grid = LLSD::emptyMap(); + grid[GRID_VALUE] = name; + grid[GRID_LABEL_VALUE] = label; + grid[GRID_HELPER_URI_VALUE] = helper; + grid[GRID_LOGIN_URI_VALUE] = LLSD::emptyArray(); + grid[GRID_LOGIN_URI_VALUE].append(login); + grid[GRID_LOGIN_PAGE_VALUE] = login_page; + grid[GRID_IS_SYSTEM_GRID_VALUE] = TRUE; + grid[GRID_LOGIN_CREDENTIAL_PAGE_TYPE_VALUE] = GRID_LOGIN_CREDENTIAL_PAGE_TYPE_AGENT; + + grid[GRID_APP_SLURL_BASE] = SYSTEM_GRID_APP_SLURL_BASE; + if (login_id.empty()) { - return "None"; + grid[GRID_ID_VALUE] = name; } - else if(mGridChoice < GRID_INFO_OTHER) + else { - return gGridInfo[mGridChoice].mLabel; + grid[GRID_ID_VALUE] = login_id; } - - return mGridName; -} - -std::string LLViewerLogin::getKnownGridLabel(EGridInfo grid_index) const -{ - if(grid_index > GRID_INFO_NONE && grid_index < GRID_INFO_OTHER) + + // only add the system grids beyond agni to the visible list + // if we're building a debug version. + if (name == std::string(MAINGRID)) + { + grid[GRID_SLURL_BASE] = MAIN_GRID_SLURL_BASE; + grid[GRID_IS_FAVORITE_VALUE] = TRUE; + } + else { - return gGridInfo[grid_index].mLabel; + grid[GRID_SLURL_BASE] = llformat(SYSTEM_GRID_SLURL_BASE, label.c_str()); } - return gGridInfo[GRID_INFO_NONE].mLabel; + addGrid(grid); } -void LLViewerLogin::getLoginURIs(std::vector& uris) const +// return a list of grid name -> grid label mappings for UI purposes +std::map LLGridManager::getKnownGrids(bool favorite_only) { - // return the login uri set on the command line. - LLControlVariable* c = gSavedSettings.getControl("CmdLineLoginURI"); - if(c) + std::map result; + for(LLSD::map_iterator grid_iter = mGridList.beginMap(); + grid_iter != mGridList.endMap(); + grid_iter++) { - LLSD v = c->getValue(); - if(v.isArray()) + if(!favorite_only || grid_iter->second.has(GRID_IS_FAVORITE_VALUE)) { - for(LLSD::array_const_iterator itr = v.beginArray(); - itr != v.endArray(); ++itr) - { - std::string uri = itr->asString(); - if(!uri.empty()) - { - uris.push_back(uri); - } - } - } - else - { - std::string uri = v.asString(); - if(!uri.empty()) - { - uris.push_back(uri); - } + result[grid_iter->first] = grid_iter->second[GRID_LABEL_VALUE].asString(); } } - // If there was no command line uri... - if(uris.empty()) + return result; +} + +void LLGridManager::setGridChoice(const std::string& grid) +{ + // Set the grid choice based on a string. + // The string can be: + // - a grid label from the gGridInfo table + // - a hostname + // - an ip address + + // loop through. We could do just a hash lookup but we also want to match + // on label + for(LLSD::map_iterator grid_iter = mGridList.beginMap(); + grid_iter != mGridList.endMap(); + grid_iter++) { - // If its a known grid choice, get the uri from the table, - // else try the grid name. - if(mGridChoice > GRID_INFO_NONE && mGridChoice < GRID_INFO_OTHER) + if((grid == grid_iter->first) || + (grid == grid_iter->second[GRID_LABEL_VALUE].asString())) { - uris.push_back(gGridInfo[mGridChoice].mLoginURI); - } - else - { - uris.push_back(mGridName); + mGrid = grid_iter->second[GRID_VALUE].asString(); + gSavedSettings.setString("CurrentGrid", grid_iter->second[GRID_VALUE]); + return; + } } + LLSD grid_data = LLSD::emptyMap(); + grid_data[GRID_VALUE] = grid; + addGrid(grid_data); + mGrid = grid; + gSavedSettings.setString("CurrentGrid", grid); } -std::string LLViewerLogin::getHelperURI() const +std::string LLGridManager::getGridByLabel( const std::string &grid_label) { - std::string helper_uri = gSavedSettings.getString("CmdLineHelperURI"); - if (helper_uri.empty()) + for(LLSD::map_iterator grid_iter = mGridList.beginMap(); + grid_iter != mGridList.endMap(); + grid_iter++) { - // grab URI from selected grid - if(mGridChoice > GRID_INFO_NONE && mGridChoice < GRID_INFO_OTHER) + if (grid_iter->second.has(GRID_LABEL_VALUE) && (grid_iter->second[GRID_LABEL_VALUE].asString() == grid_label)) { - helper_uri = gGridInfo[mGridChoice].mHelperURI; + return grid_iter->first; } + } + return std::string(); +} - if (helper_uri.empty()) - { - // what do we do with unnamed/miscellaneous grids? - // for now, operations that rely on the helper URI (currency/land purchasing) will fail - } +void LLGridManager::getLoginURIs(std::vector& uris) +{ + uris.clear(); + for (LLSD::array_iterator llsd_uri = mGridList[mGrid][GRID_LOGIN_URI_VALUE].beginArray(); + llsd_uri != mGridList[mGrid][GRID_LOGIN_URI_VALUE].endArray(); + llsd_uri++) + { + uris.push_back(llsd_uri->asString()); } - return helper_uri; } -bool LLViewerLogin::isInProductionGrid() +bool LLGridManager::isInProductionGrid() { // *NOTE:Mani This used to compare GRID_INFO_AGNI to gGridChoice, // but it seems that loginURI trumps that. std::vector uris; getLoginURIs(uris); + if (uris.size() < 1) + { + return 1; + } LLStringUtil::toLower(uris[0]); if((uris[0].find("agni") != std::string::npos)) { @@ -339,3 +513,51 @@ bool LLViewerLogin::isInProductionGrid() return false; } + +void LLGridManager::saveFavorites() +{ + // filter out just those marked as favorites + LLSD output_grid_list = LLSD::emptyMap(); + for(LLSD::map_iterator grid_iter = mGridList.beginMap(); + grid_iter != mGridList.endMap(); + grid_iter++) + { + if(grid_iter->second.has(GRID_IS_FAVORITE_VALUE)) + { + output_grid_list[grid_iter->first] = grid_iter->second; + } + } + llofstream llsd_xml; + llsd_xml.open( mGridFile.c_str(), std::ios::out | std::ios::binary); + LLSDSerialize::toPrettyXML(output_grid_list, llsd_xml); + llsd_xml.close(); +} + + +// build a slurl for the given region within the selected grid +std::string LLGridManager::getSLURLBase(const std::string& grid) +{ + std::string grid_base; + if(mGridList.has(grid) && mGridList[grid].has(GRID_SLURL_BASE)) + { + return mGridList[grid][GRID_SLURL_BASE].asString(); + } + else + { + return llformat(DEFAULT_SLURL_BASE, grid.c_str()); + } +} + +// build a slurl for the given region within the selected grid +std::string LLGridManager::getAppSLURLBase(const std::string& grid) +{ + std::string grid_base; + if(mGridList.has(grid) && mGridList[grid].has(GRID_APP_SLURL_BASE)) + { + return mGridList[grid][GRID_APP_SLURL_BASE].asString(); + } + else + { + return llformat(DEFAULT_APP_SLURL_BASE, grid.c_str()); + } +} diff --git a/indra/newview/llviewernetwork.h b/indra/newview/llviewernetwork.h index edae6dc47b..46f21bf20f 100644 --- a/indra/newview/llviewernetwork.h +++ b/indra/newview/llviewernetwork.h @@ -5,7 +5,7 @@ * * $LicenseInfo:firstyear=2006&license=viewergpl$ * - * Copyright (c) 2006-2009, Linden Research, Inc. + * Copyright (c) 2006-2010, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab @@ -13,13 +13,12 @@ * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or - * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * online at http://secondlife.com/developers/opensource/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * online at http://secondlife.com/developers/opensource/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, @@ -33,83 +32,136 @@ #ifndef LL_LLVIEWERNETWORK_H #define LL_LLVIEWERNETWORK_H + +extern const char* DEFAULT_LOGIN_PAGE; + +#define GRID_VALUE "name" +#define GRID_LABEL_VALUE "label" +#define GRID_ID_VALUE "grid_login_id" +#define GRID_LOGIN_URI_VALUE "login_uri" +#define GRID_HELPER_URI_VALUE "helper_uri" +#define GRID_LOGIN_PAGE_VALUE "login_page" +#define GRID_IS_SYSTEM_GRID_VALUE "system_grid" +#define GRID_IS_FAVORITE_VALUE "favorite" +#define GRID_LOGIN_CREDENTIAL_PAGE_TYPE_VALUE "credential_type" +#define GRID_LOGIN_CREDENTIAL_PAGE_TYPE_AGENT "agent" +#define GRID_LOGIN_CREDENTIAL_PAGE_TYPE_ACCOUNT "account" +#define MAINGRID "util.agni.lindenlab.com" -#include +// defines slurl formats associated with various grids. +// we need to continue to support existing forms, as slurls +// are shared between viewers that may not understand newer +// forms. +#define GRID_SLURL_BASE "slurl_base" +#define GRID_APP_SLURL_BASE "app_slurl_base" -class LLHost; -class LLLogin; - -enum EGridInfo +class LLInvalidGridName { - GRID_INFO_NONE, - GRID_INFO_ADITI, - GRID_INFO_AGNI, - GRID_INFO_ARUNA, - GRID_INFO_BHARATI, - GRID_INFO_CHANDRA, - GRID_INFO_DAMBALLAH, - GRID_INFO_DANU, - GRID_INFO_DURGA, - GRID_INFO_GANGA, - GRID_INFO_MITRA, - GRID_INFO_MOHINI, - GRID_INFO_NANDI, - GRID_INFO_PARVATI, - GRID_INFO_RADHA, - GRID_INFO_RAVI, - GRID_INFO_SIVA, - GRID_INFO_SHAKTI, - GRID_INFO_SKANDA, - GRID_INFO_SOMA, - GRID_INFO_UMA, - GRID_INFO_VAAK, - GRID_INFO_YAMI, - GRID_INFO_LOCAL, - GRID_INFO_OTHER, // IP address set via command line option - GRID_INFO_COUNT +public: + LLInvalidGridName(std::string grid) : mGrid(grid) + { + } +protected: + std::string mGrid; }; + /** - * @brief A class to manage the viewer's login state. + * @brief A class to manage the grids available to the viewer + * including persistance. This class also maintains the currently + * selected grid. * **/ -class LLViewerLogin : public LLSingleton +class LLGridManager : public LLSingleton { public: - LLViewerLogin(); - ~LLViewerLogin(); - - void setGridChoice(EGridInfo grid); - void setGridChoice(const std::string& grid_name); - void resetURIs(); + + // when the grid manager is instantiated, the default grids are automatically + // loaded, and the grids favorites list is loaded from the xml file. + LLGridManager(const std::string& grid_file); + LLGridManager(); + ~LLGridManager(); + + void initialize(const std::string& grid_file); + // grid list management + + // add a grid to the list of grids + void addGrid(LLSD& grid_info); - /** - * @brief Get the enumeration of the grid choice. - * Should only return values > 0 && < GRID_INFO_COUNT - **/ - EGridInfo getGridChoice() const; - - /** - * @brief Get a readable label for the grid choice. - * Returns the readable name for the grid choice. - * If the grid is 'other', returns something - * the string used to specifiy the grid. - **/ - std::string getGridLabel() const; - - std::string getKnownGridLabel(EGridInfo grid_index) const; - - void getLoginURIs(std::vector& uris) const; - std::string getHelperURI() const; + // retrieve a map of grid-name <-> label + // by default only return the user visible grids + std::map getKnownGrids(bool favorites_only=FALSE); + + LLSD getGridInfo(const std::string& grid) + { + if(mGridList.has(grid)) + { + return mGridList[grid]; + } + else + { + return LLSD(); + } + } + + // current grid management + // select a given grid as the current grid. If the grid + // is not a known grid, then it's assumed to be a dns name for the + // grid, and the various URIs will be automatically generated. + void setGridChoice(const std::string& grid); + + + std::string getGridLabel() { return mGridList[mGrid][GRID_LABEL_VALUE]; } + std::string getGrid() const { return mGrid; } + void getLoginURIs(std::vector& uris); + std::string getHelperURI() {return mGridList[mGrid][GRID_HELPER_URI_VALUE];} + std::string getLoginPage() {return mGridList[mGrid][GRID_LOGIN_PAGE_VALUE];} + std::string getGridLoginID() { return mGridList[mGrid][GRID_ID_VALUE]; } + std::string getLoginPage(const std::string& grid) { return mGridList[grid][GRID_LOGIN_PAGE_VALUE]; } + + // build a slurl for the given region within the selected grid + std::string getSLURLBase(const std::string& grid); + std::string getSLURLBase() { return getSLURLBase(mGrid); } + + std::string getAppSLURLBase(const std::string& grid); + std::string getAppSLURLBase() { return getAppSLURLBase(mGrid); } + + LLSD getGridInfo() { return mGridList[mGrid]; } + + std::string getGridByLabel( const std::string &grid_label); + + bool isSystemGrid(const std::string& grid) + { + return mGridList.has(grid) && + mGridList[grid].has(GRID_IS_SYSTEM_GRID_VALUE) && + mGridList[grid][GRID_IS_SYSTEM_GRID_VALUE].asBoolean(); + } + bool isSystemGrid() { return isSystemGrid(mGrid); } + // Mark this grid as a favorite that should be persisited on 'save' + // this is currently used to persist a grid after a successful login + void setFavorite() { mGridList[mGrid][GRID_IS_FAVORITE_VALUE] = TRUE; } + bool isInProductionGrid(); + void saveFavorites(); + void clearFavorites(); + +protected: -private: - EGridInfo mGridChoice; - std::string mGridName; + // helper function for adding the predefined grids + void addSystemGrid(const std::string& label, + const std::string& name, + const std::string& login, + const std::string& helper, + const std::string& login_page, + const std::string& login_id = ""); + + + std::string mGrid; + std::string mGridFile; + LLSD mGridList; }; const S32 MAC_ADDRESS_BYTES = 6; -extern unsigned char gMACAddress[MAC_ADDRESS_BYTES]; /* Flawfinder: ignore */ #endif diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp index 8860b734bb..e6d14079c9 100644 --- a/indra/newview/llviewerobject.cpp +++ b/indra/newview/llviewerobject.cpp @@ -4726,7 +4726,7 @@ BOOL LLViewerObject::permYouOwner() const return TRUE; #else # ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (!LLViewerLogin::getInstance()->isInProductionGrid() + if (!LLGridManager::getInstance()->isInProductionGrid() && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) { return TRUE; @@ -4763,7 +4763,7 @@ BOOL LLViewerObject::permOwnerModify() const return TRUE; #else # ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (!LLViewerLogin::getInstance()->isInProductionGrid() + if (!LLGridManager::getInstance()->isInProductionGrid() && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) { return TRUE; @@ -4787,7 +4787,7 @@ BOOL LLViewerObject::permModify() const return TRUE; #else # ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (!LLViewerLogin::getInstance()->isInProductionGrid() + if (!LLGridManager::getInstance()->isInProductionGrid() && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) { return TRUE; @@ -4811,7 +4811,7 @@ BOOL LLViewerObject::permCopy() const return TRUE; #else # ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (!LLViewerLogin::getInstance()->isInProductionGrid() + if (!LLGridManager::getInstance()->isInProductionGrid() && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) { return TRUE; @@ -4835,7 +4835,7 @@ BOOL LLViewerObject::permMove() const return TRUE; #else # ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (!LLViewerLogin::getInstance()->isInProductionGrid() + if (!LLGridManager::getInstance()->isInProductionGrid() && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) { return TRUE; @@ -4859,7 +4859,7 @@ BOOL LLViewerObject::permTransfer() const return TRUE; #else # ifdef TOGGLE_HACKED_GODLIKE_VIEWER - if (!LLViewerLogin::getInstance()->isInProductionGrid() + if (!LLGridManager::getInstance()->isInProductionGrid() && (gAgent.getGodLevel() >= GOD_MAINTENANCE)) { return TRUE; diff --git a/indra/newview/llviewerstats.cpp b/indra/newview/llviewerstats.cpp index b7c265be59..bdc34d0f18 100644 --- a/indra/newview/llviewerstats.cpp +++ b/indra/newview/llviewerstats.cpp @@ -768,9 +768,11 @@ void send_stats() system["ram"] = (S32) gSysMemory.getPhysicalMemoryKB(); system["os"] = LLAppViewer::instance()->getOSInfo().getOSStringSimple(); system["cpu"] = gSysCPU.getCPUString(); + unsigned char MACAddress[MAC_ADDRESS_BYTES]; + LLUUID::getNodeID(MACAddress); std::string macAddressString = llformat("%02x-%02x-%02x-%02x-%02x-%02x", - gMACAddress[0],gMACAddress[1],gMACAddress[2], - gMACAddress[3],gMACAddress[4],gMACAddress[5]); + MACAddress[0],MACAddress[1],MACAddress[2], + MACAddress[3],MACAddress[4],MACAddress[5]); system["mac_address"] = macAddressString; system["serial_number"] = LLAppViewer::instance()->getSerialNumber(); std::string gpu_desc = llformat( diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index ae3f680cbf..4c6a02db87 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -85,7 +85,6 @@ #include "lltooltip.h" #include "llmediaentry.h" #include "llurldispatcher.h" -#include "llurlsimstring.h" // newview includes #include "llagent.h" @@ -799,7 +798,7 @@ BOOL LLViewerWindow::handleRightMouseUp(LLWindow *window, LLCoordGL pos, MASK m BOOL LLViewerWindow::handleMiddleMouseDown(LLWindow *window, LLCoordGL pos, MASK mask) { BOOL down = TRUE; - gVoiceClient->middleMouseState(true); + LLVoiceClient::getInstance()->middleMouseState(true); handleAnyMouseClick(window,pos,mask,LLMouseHandler::CLICK_MIDDLE,down); // Always handled as far as the OS is concerned. @@ -826,20 +825,15 @@ LLWindowCallbacks::DragNDropResult LLViewerWindow::handleDragNDrop( LLWindow *wi if (slurl_dnd_enabled) { - - // special case SLURLs - // isValidSLURL() call was added here to make sure that dragged SLURL is valid (EXT-4964) - if ( LLSLURL::isSLURL( data ) && LLSLURL::isValidSLURL( data ) ) + LLSLURL dropped_slurl(data); + if(dropped_slurl.isSpatial()) { if (drop) { - LLURLDispatcher::dispatch( data, NULL, true ); - LLURLSimString::setStringRaw( LLSLURL::stripProtocol( data ) ); - LLPanelLogin::refreshLocation( true ); - LLPanelLogin::updateLocationUI(); + LLURLDispatcher::dispatch( dropped_slurl.getSLURLString(), NULL, true ); + return LLWindowCallbacks::DND_MOVE; } - return LLWindowCallbacks::DND_MOVE; - }; + } } if (prim_media_dnd_enabled) @@ -957,7 +951,7 @@ LLWindowCallbacks::DragNDropResult LLViewerWindow::handleDragNDrop( LLWindow *wi BOOL LLViewerWindow::handleMiddleMouseUp(LLWindow *window, LLCoordGL pos, MASK mask) { BOOL down = FALSE; - gVoiceClient->middleMouseState(false); + LLVoiceClient::getInstance()->middleMouseState(false); handleAnyMouseClick(window,pos,mask,LLMouseHandler::CLICK_MIDDLE,down); // Always handled as far as the OS is concerned. @@ -1074,7 +1068,7 @@ void LLViewerWindow::handleFocusLost(LLWindow *window) BOOL LLViewerWindow::handleTranslatedKeyDown(KEY key, MASK mask, BOOL repeated) { // Let the voice chat code check for its PTT key. Note that this never affects event processing. - gVoiceClient->keyDown(key, mask); + LLVoiceClient::getInstance()->keyDown(key, mask); if (gAwayTimer.getElapsedTimeF32() > MIN_AFK_TIME) { @@ -1096,7 +1090,7 @@ BOOL LLViewerWindow::handleTranslatedKeyDown(KEY key, MASK mask, BOOL repeated) BOOL LLViewerWindow::handleTranslatedKeyUp(KEY key, MASK mask) { // Let the voice chat code check for its PTT key. Note that this never affects event processing. - gVoiceClient->keyUp(key, mask); + LLVoiceClient::getInstance()->keyUp(key, mask); return FALSE; } @@ -1955,7 +1949,7 @@ void LLViewerWindow::setNormalControlsVisible( BOOL visible ) // ...and set the menu color appropriately. setMenuBackgroundColor(gAgent.getGodLevel() > GOD_NOT, - LLViewerLogin::getInstance()->isInProductionGrid()); + LLGridManager::getInstance()->isInProductionGrid()); } if ( gStatusBar ) @@ -1976,15 +1970,15 @@ void LLViewerWindow::setMenuBackgroundColor(bool god_mode, bool dev_grid) LLSD args; LLColor4 new_bg_color; - if(god_mode && LLViewerLogin::getInstance()->isInProductionGrid()) + if(god_mode && LLGridManager::getInstance()->isInProductionGrid()) { new_bg_color = LLUIColorTable::instance().getColor( "MenuBarGodBgColor" ); } - else if(god_mode && !LLViewerLogin::getInstance()->isInProductionGrid()) + else if(god_mode && !LLGridManager::getInstance()->isInProductionGrid()) { new_bg_color = LLUIColorTable::instance().getColor( "MenuNonProductionGodBgColor" ); } - else if(!god_mode && !LLViewerLogin::getInstance()->isInProductionGrid()) + else if(!god_mode && !LLGridManager::getInstance()->isInProductionGrid()) { new_bg_color = LLUIColorTable::instance().getColor( "MenuNonProductionBgColor" ); } @@ -2200,7 +2194,6 @@ BOOL LLViewerWindow::handleKey(KEY key, MASK mask) } return TRUE; } - // hidden edit menu for cut/copy/paste if (gEditMenu && gEditMenu->handleAcceleratorKey(key, mask)) { diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 0ce8894872..540cb47710 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -1266,7 +1266,7 @@ void LLVOAvatar::initInstance(void) //VTPause(); // VTune - mVoiceVisualizer->setVoiceEnabled( gVoiceClient->getVoiceEnabled( mID ) ); + mVoiceVisualizer->setVoiceEnabled( LLVoiceClient::getInstance()->getVoiceEnabled( mID ) ); } const LLVector3 LLVOAvatar::getRenderPosition() const @@ -2197,8 +2197,8 @@ BOOL LLVOAvatar::idleUpdate(LLAgent &agent, LLWorld &world, const F64 &time) } static LLUICachedControl visualizers_in_calls("ShowVoiceVisualizersInCalls", false); - bool voice_enabled = (visualizers_in_calls || gVoiceClient->inProximalChannel()) && - gVoiceClient->getVoiceEnabled(mID); + bool voice_enabled = (visualizers_in_calls || LLVoiceClient::getInstance()->inProximalChannel()) && + LLVoiceClient::getInstance()->getVoiceEnabled(mID); idleUpdateVoiceVisualizer( voice_enabled ); idleUpdateMisc( detailed_update ); @@ -2261,7 +2261,7 @@ void LLVOAvatar::idleUpdateVoiceVisualizer(bool voice_enabled) // Notice the calls to "gAwayTimer.reset()". This resets the timer that determines how long the avatar has been // "away", so that the avatar doesn't lapse into away-mode (and slump over) while the user is still talking. //----------------------------------------------------------------------------------------------------------------- - if (gVoiceClient->getIsSpeaking( mID )) + if (LLVoiceClient::getInstance()->getIsSpeaking( mID )) { if (!mVoiceVisualizer->getCurrentlySpeaking()) { @@ -2270,7 +2270,7 @@ void LLVOAvatar::idleUpdateVoiceVisualizer(bool voice_enabled) //printf( "gAwayTimer.reset();\n" ); } - mVoiceVisualizer->setSpeakingAmplitude( gVoiceClient->getCurrentPower( mID ) ); + mVoiceVisualizer->setSpeakingAmplitude( LLVoiceClient::getInstance()->getCurrentPower( mID ) ); if( isSelf() ) { @@ -2499,7 +2499,7 @@ F32 LLVOAvatar::calcMorphAmount() void LLVOAvatar::idleUpdateLipSync(bool voice_enabled) { // Use the Lipsync_Ooh and Lipsync_Aah morphs for lip sync - if ( voice_enabled && (gVoiceClient->lipSyncEnabled()) && gVoiceClient->getIsSpeaking( mID ) ) + if ( voice_enabled && (LLVoiceClient::getInstance()->lipSyncEnabled()) && LLVoiceClient::getInstance()->getIsSpeaking( mID ) ) { F32 ooh_morph_amount = 0.0f; F32 aah_morph_amount = 0.0f; diff --git a/indra/newview/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp index fac7fa6a18..338bc12f04 100644 --- a/indra/newview/llvoicechannel.cpp +++ b/indra/newview/llvoicechannel.cpp @@ -72,9 +72,9 @@ private: void LLVoiceCallCapResponder::error(U32 status, const std::string& reason) { - llwarns << "LLVoiceCallCapResponder::error(" + LL_WARNS("Voice") << "LLVoiceCallCapResponder::error(" << status << ": " << reason << ")" - << llendl; + << LL_ENDL; LLVoiceChannel* channelp = LLVoiceChannel::getChannelByID(mSessionID); if ( channelp ) { @@ -104,8 +104,8 @@ void LLVoiceCallCapResponder::result(const LLSD& content) LLSD::map_const_iterator iter; for(iter = content.beginMap(); iter != content.endMap(); ++iter) { - llinfos << "LLVoiceCallCapResponder::result got " - << iter->first << llendl; + LL_DEBUGS("Voice") << "LLVoiceCallCapResponder::result got " + << iter->first << LL_ENDL; } channelp->setChannelInfo( @@ -131,10 +131,8 @@ LLVoiceChannel::LLVoiceChannel(const LLUUID& session_id, const std::string& sess { // a voice channel already exists for this session id, so this instance will be orphaned // the end result should simply be the failure to make voice calls - llwarns << "Duplicate voice channels registered for session_id " << session_id << llendl; + LL_WARNS("Voice") << "Duplicate voice channels registered for session_id " << session_id << LL_ENDL; } - - LLVoiceClient::getInstance()->addObserver(this); } LLVoiceChannel::~LLVoiceChannel() @@ -145,7 +143,7 @@ LLVoiceChannel::~LLVoiceChannel() // later in other destructors anyway). EXT-5524 if(LLVoiceClient::instanceExists()) { - gVoiceClient->removeObserver(this); + LLVoiceClient::getInstance()->removeObserver(this); } sVoiceChannelMap.erase(mSessionID); @@ -165,13 +163,13 @@ void LLVoiceChannel::setChannelInfo( if (mURI.empty()) { LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs); - llwarns << "Received empty URI for channel " << mSessionName << llendl; + LL_WARNS("Voice") << "Received empty URI for channel " << mSessionName << LL_ENDL; deactivate(); } else if (mCredentials.empty()) { LLNotificationsUtil::add("VoiceChannelJoinFailed", mNotifyArgs); - llwarns << "Received empty credentials for channel " << mSessionName << llendl; + LL_WARNS("Voice") << "Received empty credentials for channel " << mSessionName << LL_ENDL; deactivate(); } else @@ -286,13 +284,14 @@ void LLVoiceChannel::deactivate() //Default mic is OFF when leaving voice calls if (gSavedSettings.getBOOL("AutoDisengageMic") && sCurrentVoiceChannel == this && - gVoiceClient->getUserPTTState()) + LLVoiceClient::getInstance()->getUserPTTState()) { gSavedSettings.setBOOL("PTTCurrentlyEnabled", true); - gVoiceClient->inputUserControlState(true); + LLVoiceClient::getInstance()->inputUserControlState(true); } } - + LLVoiceClient::getInstance()->removeObserver(this); + if (sCurrentVoiceChannel == this) { // default channel is proximal channel @@ -332,7 +331,9 @@ void LLVoiceChannel::activate() { setState(STATE_CALL_STARTED); } - + + LLVoiceClient::getInstance()->addObserver(this); + //do not send earlier, channel should be initialized, should not be in STATE_NO_CHANNEL_INFO state sCurrentVoiceChannelChangedSignal(this->mSessionID); } @@ -374,6 +375,11 @@ LLVoiceChannel* LLVoiceChannel::getChannelByURI(std::string uri) } } +LLVoiceChannel* LLVoiceChannel::getCurrentVoiceChannel() +{ + return sCurrentVoiceChannel; +} + void LLVoiceChannel::updateSessionID(const LLUUID& new_session_id) { sVoiceChannelMap.erase(sVoiceChannelMap.find(mSessionID)); @@ -425,7 +431,6 @@ void LLVoiceChannel::initClass() sCurrentVoiceChannel = LLVoiceChannelProximal::getInstance(); } - //static void LLVoiceChannel::suspend() { @@ -441,7 +446,7 @@ void LLVoiceChannel::resume() { if (sSuspended) { - if (gVoiceClient->voiceEnabled()) + if (LLVoiceClient::getInstance()->voiceEnabled()) { if (sSuspendedVoiceChannel) { @@ -511,9 +516,9 @@ void LLVoiceChannelGroup::activate() #endif //Mic default state is OFF on initiating/joining Ad-Hoc/Group calls - if (gVoiceClient->getUserPTTState() && gVoiceClient->getPTTIsToggle()) + if (LLVoiceClient::getInstance()->getUserPTTState() && LLVoiceClient::getInstance()->getPTTIsToggle()) { - gVoiceClient->inputUserControlState(true); + LLVoiceClient::getInstance()->inputUserControlState(true); } } @@ -560,7 +565,7 @@ void LLVoiceChannelGroup::setChannelInfo( else { //*TODO: notify user - llwarns << "Received invalid credentials for channel " << mSessionName << llendl; + LL_WARNS("Voice") << "Received invalid credentials for channel " << mSessionName << LL_ENDL; deactivate(); } } @@ -659,7 +664,6 @@ void LLVoiceChannelGroup::setState(EState state) LLVoiceChannelProximal::LLVoiceChannelProximal() : LLVoiceChannel(LLUUID::null, LLStringUtil::null) { - activate(); } BOOL LLVoiceChannelProximal::isActive() @@ -671,13 +675,13 @@ void LLVoiceChannelProximal::activate() { if (callStarted()) return; - LLVoiceChannel::activate(); - - if (callStarted()) + if((LLVoiceChannel::sCurrentVoiceChannel != this) && (LLVoiceChannel::getState() == STATE_CONNECTED)) { - // this implicitly puts you back in the spatial channel - LLVoiceClient::getInstance()->leaveNonSpatialChannel(); + // we're connected to a non-spatial channel, so disconnect. + LLVoiceClient::getInstance()->leaveNonSpatialChannel(); } + LLVoiceChannel::activate(); + } void LLVoiceChannelProximal::onChange(EStatusType type, const std::string &channelURI, bool proximal) @@ -707,7 +711,7 @@ void LLVoiceChannelProximal::handleStatusChange(EStatusType status) return; case STATUS_VOICE_DISABLED: //skip showing "Voice not available at your current location" when agent voice is disabled (EXT-4749) - if(LLVoiceClient::voiceEnabled() && gVoiceClient->voiceWorking()) + if(LLVoiceClient::getInstance()->voiceEnabled() && LLVoiceClient::getInstance()->isVoiceWorking()) { //TODO: remove or redirect this call status notification // LLCallInfoDialog::show("unavailable", mNotifyArgs); @@ -767,7 +771,7 @@ LLVoiceChannelP2P::LLVoiceChannelP2P(const LLUUID& session_id, const std::string void LLVoiceChannelP2P::handleStatusChange(EStatusType type) { - llinfos << "P2P CALL CHANNEL STATUS CHANGE: incoming=" << int(mReceivedCall) << " newstatus=" << LLVoiceClientStatusObserver::status2string(type) << " (mState=" << mState << ")" << llendl; + LL_INFOS("Voice") << "P2P CALL CHANNEL STATUS CHANGE: incoming=" << int(mReceivedCall) << " newstatus=" << LLVoiceClientStatusObserver::status2string(type) << " (mState=" << mState << ")" << LL_ENDL; // status updates switch(type) @@ -841,9 +845,9 @@ void LLVoiceChannelP2P::activate() LLRecentPeople::instance().add(mOtherUserID); //Default mic is ON on initiating/joining P2P calls - if (!gVoiceClient->getUserPTTState() && gVoiceClient->getPTTIsToggle()) + if (!LLVoiceClient::getInstance()->getUserPTTState() && LLVoiceClient::getInstance()->getPTTIsToggle()) { - gVoiceClient->inputUserControlState(true); + LLVoiceClient::getInstance()->inputUserControlState(true); } } } @@ -906,7 +910,7 @@ void LLVoiceChannelP2P::setSessionHandle(const std::string& handle, const std::s void LLVoiceChannelP2P::setState(EState state) { - llinfos << "P2P CALL STATE CHANGE: incoming=" << int(mReceivedCall) << " oldstate=" << mState << " newstate=" << state << llendl; + LL_INFOS("Voice") << "P2P CALL STATE CHANGE: incoming=" << int(mReceivedCall) << " oldstate=" << mState << " newstate=" << state << LL_ENDL; if (mReceivedCall) // incoming call { diff --git a/indra/newview/llvoicechannel.h b/indra/newview/llvoicechannel.h index 941cccacc3..573fab1f4f 100644 --- a/indra/newview/llvoicechannel.h +++ b/indra/newview/llvoicechannel.h @@ -98,7 +98,8 @@ public: static LLVoiceChannel* getChannelByID(const LLUUID& session_id); static LLVoiceChannel* getChannelByURI(std::string uri); - static LLVoiceChannel* getCurrentVoiceChannel() { return sCurrentVoiceChannel; } + static LLVoiceChannel* getCurrentVoiceChannel(); + static void initClass(); static void suspend(); diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index 2238acd643..e067754e3e 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -1,6 +1,6 @@ /** * @file llvoiceclient.cpp - * @brief Implementation of LLVoiceClient class which is the interface to the voice client process. + * @brief Voice client delegation class implementation. * * $LicenseInfo:firstyear=2001&license=viewergpl$ * @@ -17,8 +17,7 @@ * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, @@ -32,7244 +31,730 @@ #include "llviewerprecompiledheaders.h" #include "llvoiceclient.h" +#include "llviewercontrol.h" +#include "llviewerwindow.h" +#include "llvoicedw.h" +#include "llvoicevivox.h" +#include "llviewernetwork.h" +#include "llhttpnode.h" +#include "llnotificationsutil.h" +#include "llsdserialize.h" +#include "llui.h" -#include - -// library includes -#include "llnotificationsutil.h" -#include "llsdserialize.h" -#include "llsdutil.h" - - -// project includes -#include "llvoavatar.h" -#include "llbufferstream.h" -#include "llfile.h" -#ifdef LL_STANDALONE -# include "expat.h" -#else -# include "expat/expat.h" -#endif -#include "llcallbacklist.h" -#include "llcallingcard.h" // for LLFriendObserver -#include "llviewerregion.h" -#include "llviewernetwork.h" // for gGridChoice -#include "llbase64.h" -#include "llviewercontrol.h" -#include "llkeyboard.h" -#include "llappviewer.h" // for gDisconnected, gDisableVoice -#include "llmutelist.h" // to check for muted avatars -#include "llagent.h" -#include "llvoavatarself.h" -#include "llcachename.h" -#include "llimview.h" // for LLIMMgr -#include "llparcel.h" -#include "llviewerparcelmgr.h" -//#include "llfirstuse.h" -#include "llspeakers.h" -#include "lltrans.h" -#include "llviewerwindow.h" -#include "llviewercamera.h" -#include "llvoavatarself.h" -#include "llvoicechannel.h" - -// for base64 decoding -#include "apr_base64.h" - -// for SHA1 hash -#include "apr_sha1.h" - -// for MD5 hash -#include "llmd5.h" - -#define USE_SESSION_GROUPS 0 - -static bool sConnectingToAgni = false; -F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f; - -const F32 LLVoiceClient::VOLUME_MIN = 0.f; -const F32 LLVoiceClient::VOLUME_DEFAULT = 0.5f; -const F32 LLVoiceClient::VOLUME_MAX = 1.0f; - -const F32 VOLUME_SCALE_VIVOX = 0.01f; - -const F32 SPEAKING_TIMEOUT = 1.f; - -const int VOICE_MAJOR_VERSION = 1; -const int VOICE_MINOR_VERSION = 0; - -LLVoiceClient *gVoiceClient = NULL; - -// Don't retry connecting to the daemon more frequently than this: -const F32 CONNECT_THROTTLE_SECONDS = 1.0f; - -// Don't send positional updates more frequently than this: -const F32 UPDATE_THROTTLE_SECONDS = 0.1f; - -const F32 LOGIN_RETRY_SECONDS = 10.0f; -const int MAX_LOGIN_RETRIES = 12; - -// Defines the maximum number of times(in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine() -// which is treated as normal. If this number is exceeded we suspect there is a problem with connection -// to voice server (EXT-4313). When voice works correctly, there is from 1 to 15 times. 50 was chosen -// to make sure we don't make mistake when slight connection problems happen- situation when connection to server is -// blocked is VERY rare and it's better to sacrifice response time in this situation for the sake of stability. -const int MAX_NORMAL_JOINING_SPATIAL_NUM = 50; - -static void setUUIDFromStringHash(LLUUID &uuid, const std::string &str) -{ - LLMD5 md5_uuid; - md5_uuid.update((const unsigned char*)str.data(), str.size()); - md5_uuid.finalize(); - md5_uuid.raw_digest(uuid.mData); -} - -static int scale_mic_volume(float volume) -{ - // incoming volume has the range [0.0 ... 2.0], with 1.0 as the default. - // Map it to Vivox levels as follows: 0.0 -> 30, 1.0 -> 50, 2.0 -> 70 - return 30 + (int)(volume * 20.0f); -} - -static int scale_speaker_volume(float volume) -{ - // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. - // Map it to Vivox levels as follows: 0.0 -> 30, 0.5 -> 50, 1.0 -> 70 - return 30 + (int)(volume * 40.0f); -} - -class LLViewerVoiceAccountProvisionResponder : - public LLHTTPClient::Responder -{ -public: - LLViewerVoiceAccountProvisionResponder(int retries) - { - mRetries = retries; - } - - virtual void error(U32 status, const std::string& reason) - { - if ( mRetries > 0 ) - { - LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, retrying. status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL; - if ( gVoiceClient ) gVoiceClient->requestVoiceAccountProvision( - mRetries - 1); - } - else - { - LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, too many retries (giving up). status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL; - if ( gVoiceClient ) gVoiceClient->giveUp(); - } - } - - virtual void result(const LLSD& content) - { - if ( gVoiceClient ) - { - std::string voice_sip_uri_hostname; - std::string voice_account_server_uri; - - LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response:" << ll_pretty_print_sd(content) << LL_ENDL; - - if(content.has("voice_sip_uri_hostname")) - voice_sip_uri_hostname = content["voice_sip_uri_hostname"].asString(); - - // this key is actually misnamed -- it will be an entire URI, not just a hostname. - if(content.has("voice_account_server_name")) - voice_account_server_uri = content["voice_account_server_name"].asString(); - - gVoiceClient->login( - content["username"].asString(), - content["password"].asString(), - voice_sip_uri_hostname, - voice_account_server_uri); - } - } - -private: - int mRetries; -}; - -/** - * @class LLVivoxProtocolParser - * @brief This class helps construct new LLIOPipe specializations - * @see LLIOPipe - * - * THOROUGH_DESCRIPTION - */ -class LLVivoxProtocolParser : public LLIOPipe -{ - LOG_CLASS(LLVivoxProtocolParser); -public: - LLVivoxProtocolParser(); - virtual ~LLVivoxProtocolParser(); - -protected: - /* @name LLIOPipe virtual implementations - */ - //@{ - /** - * @brief Process the data in buffer - */ - virtual EStatus process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump); - //@} - - std::string mInput; - - // Expat control members - XML_Parser parser; - int responseDepth; - bool ignoringTags; - bool isEvent; - int ignoreDepth; - - // Members for processing responses. The values are transient and only valid within a call to processResponse(). - bool squelchDebugOutput; - int returnCode; - int statusCode; - std::string statusString; - std::string requestId; - std::string actionString; - std::string connectorHandle; - std::string versionID; - std::string accountHandle; - std::string sessionHandle; - std::string sessionGroupHandle; - std::string alias; - std::string applicationString; - - // Members for processing events. The values are transient and only valid within a call to processResponse(). - std::string eventTypeString; - int state; - std::string uriString; - bool isChannel; - bool incoming; - bool enabled; - std::string nameString; - std::string audioMediaString; - std::string displayNameString; - std::string deviceString; - int participantType; - bool isLocallyMuted; - bool isModeratorMuted; - bool isSpeaking; - int volume; - F32 energy; - std::string messageHeader; - std::string messageBody; - std::string notificationType; - bool hasText; - bool hasAudio; - bool hasVideo; - bool terminated; - std::string blockMask; - std::string presenceOnly; - std::string autoAcceptMask; - std::string autoAddAsBuddy; - int numberOfAliases; - std::string subscriptionHandle; - std::string subscriptionType; - - - // Members for processing text between tags - std::string textBuffer; - bool accumulateText; - - void reset(); - - void processResponse(std::string tag); - -static void XMLCALL ExpatStartTag(void *data, const char *el, const char **attr); -static void XMLCALL ExpatEndTag(void *data, const char *el); -static void XMLCALL ExpatCharHandler(void *data, const XML_Char *s, int len); - - void StartTag(const char *tag, const char **attr); - void EndTag(const char *tag); - void CharData(const char *buffer, int length); - -}; - -LLVivoxProtocolParser::LLVivoxProtocolParser() -{ - parser = NULL; - parser = XML_ParserCreate(NULL); - - reset(); -} - -void LLVivoxProtocolParser::reset() -{ - responseDepth = 0; - ignoringTags = false; - accumulateText = false; - energy = 0.f; - hasText = false; - hasAudio = false; - hasVideo = false; - terminated = false; - ignoreDepth = 0; - isChannel = false; - incoming = false; - enabled = false; - isEvent = false; - isLocallyMuted = false; - isModeratorMuted = false; - isSpeaking = false; - participantType = 0; - squelchDebugOutput = false; - returnCode = -1; - state = 0; - statusCode = 0; - volume = 0; - textBuffer.clear(); - alias.clear(); - numberOfAliases = 0; - applicationString.clear(); -} - -//virtual -LLVivoxProtocolParser::~LLVivoxProtocolParser() -{ - if (parser) - XML_ParserFree(parser); -} - -// virtual -LLIOPipe::EStatus LLVivoxProtocolParser::process_impl( - const LLChannelDescriptors& channels, - buffer_ptr_t& buffer, - bool& eos, - LLSD& context, - LLPumpIO* pump) -{ - LLBufferStream istr(channels, buffer.get()); - std::ostringstream ostr; - while (istr.good()) - { - char buf[1024]; - istr.read(buf, sizeof(buf)); - mInput.append(buf, istr.gcount()); - } - - // Look for input delimiter(s) in the input buffer. If one is found, send the message to the xml parser. - int start = 0; - int delim; - while((delim = mInput.find("\n\n\n", start)) != std::string::npos) - { - - // Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser) - reset(); - - XML_ParserReset(parser, NULL); - XML_SetElementHandler(parser, ExpatStartTag, ExpatEndTag); - XML_SetCharacterDataHandler(parser, ExpatCharHandler); - XML_SetUserData(parser, this); - XML_Parse(parser, mInput.data() + start, delim - start, false); - - // If this message isn't set to be squelched, output the raw XML received. - if(!squelchDebugOutput) - { - LL_DEBUGS("Voice") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL; - } - - start = delim + 3; - } - - if(start != 0) - mInput = mInput.substr(start); - - LL_DEBUGS("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL; - - if(!gVoiceClient->mConnected) - { - // If voice has been disabled, we just want to close the socket. This does so. - LL_INFOS("Voice") << "returning STATUS_STOP" << LL_ENDL; - return STATUS_STOP; - } - - return STATUS_OK; -} - -void XMLCALL LLVivoxProtocolParser::ExpatStartTag(void *data, const char *el, const char **attr) -{ - if (data) - { - LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; - object->StartTag(el, attr); - } -} - -// -------------------------------------------------------------------------------- - -void XMLCALL LLVivoxProtocolParser::ExpatEndTag(void *data, const char *el) -{ - if (data) - { - LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; - object->EndTag(el); - } -} - -// -------------------------------------------------------------------------------- - -void XMLCALL LLVivoxProtocolParser::ExpatCharHandler(void *data, const XML_Char *s, int len) -{ - if (data) - { - LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; - object->CharData(s, len); - } -} - -// -------------------------------------------------------------------------------- - - -void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) -{ - // Reset the text accumulator. We shouldn't have strings that are inturrupted by new tags - textBuffer.clear(); - // only accumulate text if we're not ignoring tags. - accumulateText = !ignoringTags; - - if (responseDepth == 0) - { - isEvent = !stricmp("Event", tag); - - if (!stricmp("Response", tag) || isEvent) - { - // Grab the attributes - while (*attr) - { - const char *key = *attr++; - const char *value = *attr++; - - if (!stricmp("requestId", key)) - { - requestId = value; - } - else if (!stricmp("action", key)) - { - actionString = value; - } - else if (!stricmp("type", key)) - { - eventTypeString = value; - } - } - } - LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; - } - else - { - if (ignoringTags) - { - LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - } - else - { - LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; - - // Ignore the InputXml stuff so we don't get confused - if (!stricmp("InputXml", tag)) - { - ignoringTags = true; - ignoreDepth = responseDepth; - accumulateText = false; - - LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL; - } - else if (!stricmp("CaptureDevices", tag)) - { - gVoiceClient->clearCaptureDevices(); - } - else if (!stricmp("RenderDevices", tag)) - { - gVoiceClient->clearRenderDevices(); - } - else if (!stricmp("CaptureDevice", tag)) - { - deviceString.clear(); - } - else if (!stricmp("RenderDevice", tag)) - { - deviceString.clear(); - } - else if (!stricmp("Buddies", tag)) - { - gVoiceClient->deleteAllBuddies(); - } - else if (!stricmp("BlockRules", tag)) - { - gVoiceClient->deleteAllBlockRules(); - } - else if (!stricmp("AutoAcceptRules", tag)) - { - gVoiceClient->deleteAllAutoAcceptRules(); - } - - } - } - responseDepth++; -} - -// -------------------------------------------------------------------------------- - -void LLVivoxProtocolParser::EndTag(const char *tag) -{ - const std::string& string = textBuffer; - - responseDepth--; - - if (ignoringTags) - { - if (ignoreDepth == responseDepth) - { - LL_DEBUGS("VivoxProtocolParser") << "end of ignore" << LL_ENDL; - ignoringTags = false; - } - else - { - LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - } - } - - if (!ignoringTags) - { - LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; - - // Closing a tag. Finalize the text we've accumulated and reset - if (!stricmp("ReturnCode", tag)) - returnCode = strtol(string.c_str(), NULL, 10); - else if (!stricmp("SessionHandle", tag)) - sessionHandle = string; - else if (!stricmp("SessionGroupHandle", tag)) - sessionGroupHandle = string; - else if (!stricmp("StatusCode", tag)) - statusCode = strtol(string.c_str(), NULL, 10); - else if (!stricmp("StatusString", tag)) - statusString = string; - else if (!stricmp("ParticipantURI", tag)) - uriString = string; - else if (!stricmp("Volume", tag)) - volume = strtol(string.c_str(), NULL, 10); - else if (!stricmp("Energy", tag)) - energy = (F32)strtod(string.c_str(), NULL); - else if (!stricmp("IsModeratorMuted", tag)) - isModeratorMuted = !stricmp(string.c_str(), "true"); - else if (!stricmp("IsSpeaking", tag)) - isSpeaking = !stricmp(string.c_str(), "true"); - else if (!stricmp("Alias", tag)) - alias = string; - else if (!stricmp("NumberOfAliases", tag)) - numberOfAliases = strtol(string.c_str(), NULL, 10); - else if (!stricmp("Application", tag)) - applicationString = string; - else if (!stricmp("ConnectorHandle", tag)) - connectorHandle = string; - else if (!stricmp("VersionID", tag)) - versionID = string; - else if (!stricmp("AccountHandle", tag)) - accountHandle = string; - else if (!stricmp("State", tag)) - state = strtol(string.c_str(), NULL, 10); - else if (!stricmp("URI", tag)) - uriString = string; - else if (!stricmp("IsChannel", tag)) - isChannel = !stricmp(string.c_str(), "true"); - else if (!stricmp("Incoming", tag)) - incoming = !stricmp(string.c_str(), "true"); - else if (!stricmp("Enabled", tag)) - enabled = !stricmp(string.c_str(), "true"); - else if (!stricmp("Name", tag)) - nameString = string; - else if (!stricmp("AudioMedia", tag)) - audioMediaString = string; - else if (!stricmp("ChannelName", tag)) - nameString = string; - else if (!stricmp("DisplayName", tag)) - displayNameString = string; - else if (!stricmp("Device", tag)) - deviceString = string; - else if (!stricmp("AccountName", tag)) - nameString = string; - else if (!stricmp("ParticipantType", tag)) - participantType = strtol(string.c_str(), NULL, 10); - else if (!stricmp("IsLocallyMuted", tag)) - isLocallyMuted = !stricmp(string.c_str(), "true"); - else if (!stricmp("MicEnergy", tag)) - energy = (F32)strtod(string.c_str(), NULL); - else if (!stricmp("ChannelName", tag)) - nameString = string; - else if (!stricmp("ChannelURI", tag)) - uriString = string; - else if (!stricmp("BuddyURI", tag)) - uriString = string; - else if (!stricmp("Presence", tag)) - statusString = string; - else if (!stricmp("CaptureDevice", tag)) - { - gVoiceClient->addCaptureDevice(deviceString); - } - else if (!stricmp("RenderDevice", tag)) - { - gVoiceClient->addRenderDevice(deviceString); - } - else if (!stricmp("Buddy", tag)) - { - gVoiceClient->processBuddyListEntry(uriString, displayNameString); - } - else if (!stricmp("BlockRule", tag)) - { - gVoiceClient->addBlockRule(blockMask, presenceOnly); - } - else if (!stricmp("BlockMask", tag)) - blockMask = string; - else if (!stricmp("PresenceOnly", tag)) - presenceOnly = string; - else if (!stricmp("AutoAcceptRule", tag)) - { - gVoiceClient->addAutoAcceptRule(autoAcceptMask, autoAddAsBuddy); - } - else if (!stricmp("AutoAcceptMask", tag)) - autoAcceptMask = string; - else if (!stricmp("AutoAddAsBuddy", tag)) - autoAddAsBuddy = string; - else if (!stricmp("MessageHeader", tag)) - messageHeader = string; - else if (!stricmp("MessageBody", tag)) - messageBody = string; - else if (!stricmp("NotificationType", tag)) - notificationType = string; - else if (!stricmp("HasText", tag)) - hasText = !stricmp(string.c_str(), "true"); - else if (!stricmp("HasAudio", tag)) - hasAudio = !stricmp(string.c_str(), "true"); - else if (!stricmp("HasVideo", tag)) - hasVideo = !stricmp(string.c_str(), "true"); - else if (!stricmp("Terminated", tag)) - terminated = !stricmp(string.c_str(), "true"); - else if (!stricmp("SubscriptionHandle", tag)) - subscriptionHandle = string; - else if (!stricmp("SubscriptionType", tag)) - subscriptionType = string; - - textBuffer.clear(); - accumulateText= false; - - if (responseDepth == 0) - { - // We finished all of the XML, process the data - processResponse(tag); - } - } -} - -// -------------------------------------------------------------------------------- - -void LLVivoxProtocolParser::CharData(const char *buffer, int length) -{ - /* - This method is called for anything that isn't a tag, which can be text you - want that lies between tags, and a lot of stuff you don't want like file formatting - (tabs, spaces, CR/LF, etc). - - Only copy text if we are in accumulate mode... - */ - if (accumulateText) - textBuffer.append(buffer, length); -} - -// -------------------------------------------------------------------------------- - -void LLVivoxProtocolParser::processResponse(std::string tag) -{ - LL_DEBUGS("VivoxProtocolParser") << tag << LL_ENDL; - - // SLIM SDK: the SDK now returns a statusCode of "200" (OK) for success. This is a change vs. previous SDKs. - // According to Mike S., "The actual API convention is that responses with return codes of 0 are successful, regardless of the status code returned", - // so I believe this will give correct behavior. - - if(returnCode == 0) - statusCode = 0; - - if (isEvent) - { - const char *eventTypeCstr = eventTypeString.c_str(); - if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent")) - { - gVoiceClient->accountLoginStateChangeEvent(accountHandle, statusCode, statusString, state); - } - else if (!stricmp(eventTypeCstr, "SessionAddedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 - sip:confctl-1408789@bhr.vivox.com - true - false - - - */ - gVoiceClient->sessionAddedEvent(uriString, alias, sessionHandle, sessionGroupHandle, isChannel, incoming, nameString, applicationString); - } - else if (!stricmp(eventTypeCstr, "SessionRemovedEvent")) - { - gVoiceClient->sessionRemovedEvent(sessionHandle, sessionGroupHandle); - } - else if (!stricmp(eventTypeCstr, "SessionGroupAddedEvent")) - { - gVoiceClient->sessionGroupAddedEvent(sessionGroupHandle); - } - else if (!stricmp(eventTypeCstr, "MediaStreamUpdatedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 - 200 - OK - 2 - false - - */ - gVoiceClient->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming); - } - else if (!stricmp(eventTypeCstr, "TextStreamUpdatedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg1 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==1 - true - 1 - true - - */ - gVoiceClient->textStreamUpdatedEvent(sessionHandle, sessionGroupHandle, enabled, state, incoming); - } - else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==4 - sip:xI5auBZ60SJWIk606-1JGRQ==@bhr.vivox.com - xI5auBZ60SJWIk606-1JGRQ== - - 0 - - */ - gVoiceClient->participantAddedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString, displayNameString, participantType); - } - else if (!stricmp(eventTypeCstr, "ParticipantRemovedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==4 - sip:xtx7YNV-3SGiG7rA1fo5Ndw==@bhr.vivox.com - xtx7YNV-3SGiG7rA1fo5Ndw== - - */ - gVoiceClient->participantRemovedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString); - } - else if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 - sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com - false - true - 44 - 0.0879437 - - */ - - // These happen so often that logging them is pretty useless. - squelchDebugOutput = true; - - gVoiceClient->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy); - } - else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent")) - { - gVoiceClient->auxAudioPropertiesEvent(energy); - } - else if (!stricmp(eventTypeCstr, "BuddyPresenceEvent")) - { - gVoiceClient->buddyPresenceEvent(uriString, alias, statusString, applicationString); - } - else if (!stricmp(eventTypeCstr, "BuddyAndGroupListChangedEvent")) - { - // The buddy list was updated during parsing. - // Need to recheck against the friends list. - gVoiceClient->buddyListChanged(); - } - else if (!stricmp(eventTypeCstr, "BuddyChangedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw== - sip:x9fFHFZjOTN6OESF1DUPrZQ==@bhr.vivox.com - Monroe Tester - - 0 - Set - - */ - // TODO: Question: Do we need to process this at all? - } - else if (!stricmp(eventTypeCstr, "MessageEvent")) - { - gVoiceClient->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString); - } - else if (!stricmp(eventTypeCstr, "SessionNotificationEvent")) - { - gVoiceClient->sessionNotificationEvent(sessionHandle, uriString, notificationType); - } - else if (!stricmp(eventTypeCstr, "SubscriptionEvent")) - { - gVoiceClient->subscriptionEvent(uriString, subscriptionHandle, alias, displayNameString, applicationString, subscriptionType); - } - else if (!stricmp(eventTypeCstr, "SessionUpdatedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 - c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 - sip:confctl-9@bhd.vivox.com - 0 - 50 - 1 - 0 - 000 - 0 - - */ - // We don't need to process this, but we also shouldn't warn on it, since that confuses people. - } - - else if (!stricmp(eventTypeCstr, "SessionGroupRemovedEvent")) - { - /* - - c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 - - */ - // We don't need to process this, but we also shouldn't warn on it, since that confuses people. - } - else - { - LL_WARNS("VivoxProtocolParser") << "Unknown event type " << eventTypeString << LL_ENDL; - } - } - else - { - const char *actionCstr = actionString.c_str(); - if (!stricmp(actionCstr, "Connector.Create.1")) - { - gVoiceClient->connectorCreateResponse(statusCode, statusString, connectorHandle, versionID); - } - else if (!stricmp(actionCstr, "Account.Login.1")) - { - gVoiceClient->loginResponse(statusCode, statusString, accountHandle, numberOfAliases); - } - else if (!stricmp(actionCstr, "Session.Create.1")) - { - gVoiceClient->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle); - } - else if (!stricmp(actionCstr, "SessionGroup.AddSession.1")) - { - gVoiceClient->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle); - } - else if (!stricmp(actionCstr, "Session.Connect.1")) - { - gVoiceClient->sessionConnectResponse(requestId, statusCode, statusString); - } - else if (!stricmp(actionCstr, "Account.Logout.1")) - { - gVoiceClient->logoutResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Connector.InitiateShutdown.1")) - { - gVoiceClient->connectorShutdownResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Account.ListBlockRules.1")) - { - gVoiceClient->accountListBlockRulesResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Account.ListAutoAcceptRules.1")) - { - gVoiceClient->accountListAutoAcceptRulesResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Session.Set3DPosition.1")) - { - // We don't need to process these, but they're so spammy we don't want to log them. - squelchDebugOutput = true; - } -/* - else if (!stricmp(actionCstr, "Account.ChannelGetList.1")) - { - gVoiceClient->channelGetListResponse(statusCode, statusString); - } - else if (!stricmp(actionCstr, "Connector.AccountCreate.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.MuteLocalMic.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.MuteLocalSpeaker.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.SetLocalMicVolume.1")) - { - - } - else if (!stricmp(actionCstr, "Connector.SetLocalSpeakerVolume.1")) - { - - } - else if (!stricmp(actionCstr, "Session.ListenerSetPosition.1")) - { - - } - else if (!stricmp(actionCstr, "Session.SpeakerSetPosition.1")) - { - - } - else if (!stricmp(actionCstr, "Session.AudioSourceSetPosition.1")) - { - - } - else if (!stricmp(actionCstr, "Session.GetChannelParticipants.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelCreate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelUpdate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelDelete.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelCreateAndInvite.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelFolderCreate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelFolderUpdate.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelFolderDelete.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelAddModerator.1")) - { - - } - else if (!stricmp(actionCstr, "Account.ChannelDeleteModerator.1")) - { - - } -*/ - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////// - -class LLVoiceClientMuteListObserver : public LLMuteListObserver -{ - /* virtual */ void onChange() { gVoiceClient->muteListChanged();} -}; - -class LLVoiceClientFriendsObserver : public LLFriendObserver -{ -public: - /* virtual */ void changed(U32 mask) { gVoiceClient->updateFriends(mask);} -}; - -static LLVoiceClientMuteListObserver mutelist_listener; -static bool sMuteListListener_listening = false; - -static LLVoiceClientFriendsObserver *friendslist_listener = NULL; - -/////////////////////////////////////////////////////////////////////////////////////////////// - -class LLVoiceClientCapResponder : public LLHTTPClient::Responder -{ -public: - LLVoiceClientCapResponder(void){}; - - virtual void error(U32 status, const std::string& reason); // called with bad status codes - virtual void result(const LLSD& content); - -private: -}; - -void LLVoiceClientCapResponder::error(U32 status, const std::string& reason) -{ - LL_WARNS("Voice") << "LLVoiceClientCapResponder::error(" - << status << ": " << reason << ")" - << LL_ENDL; -} - -void LLVoiceClientCapResponder::result(const LLSD& content) -{ - LLSD::map_const_iterator iter; - - LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest response:" << ll_pretty_print_sd(content) << LL_ENDL; - - if ( content.has("voice_credentials") ) - { - LLSD voice_credentials = content["voice_credentials"]; - std::string uri; - std::string credentials; - - if ( voice_credentials.has("channel_uri") ) - { - uri = voice_credentials["channel_uri"].asString(); - } - if ( voice_credentials.has("channel_credentials") ) - { - credentials = - voice_credentials["channel_credentials"].asString(); - } - - gVoiceClient->setSpatialChannel(uri, credentials); - } -} - - - -#if LL_WINDOWS -static HANDLE sGatewayHandle = 0; - -static bool isGatewayRunning() -{ - bool result = false; - if(sGatewayHandle != 0) - { - DWORD waitresult = WaitForSingleObject(sGatewayHandle, 0); - if(waitresult != WAIT_OBJECT_0) - { - result = true; - } - } - return result; -} -static void killGateway() -{ - if(sGatewayHandle != 0) - { - TerminateProcess(sGatewayHandle,0); - } -} - -#else // Mac and linux - -static pid_t sGatewayPID = 0; -static bool isGatewayRunning() -{ - bool result = false; - if(sGatewayPID != 0) - { - // A kill with signal number 0 has no effect, just does error checking. It should return an error if the process no longer exists. - if(kill(sGatewayPID, 0) == 0) - { - result = true; - } - } - return result; -} - -static void killGateway() -{ - if(sGatewayPID != 0) - { - kill(sGatewayPID, SIGTERM); - } -} - -#endif - -class LLSpeakerVolumeStorage : public LLSingleton -{ - LOG_CLASS(LLSpeakerVolumeStorage); -public: - - /** - * Stores volume level for specified user. - * - * @param[in] speaker_id - LLUUID of user to store volume level for. - * @param[in] volume - volume level to be stored for user. - */ - void storeSpeakerVolume(const LLUUID& speaker_id, F32 volume); - - /** - * Gets stored volume level for specified speaker - * - * @param[in] speaker_id - LLUUID of user to retrieve volume level for. - * @param[out] volume - set to stored volume if found, otherwise unmodified. - * @return - true if a stored volume is found. - */ - bool getSpeakerVolume(const LLUUID& speaker_id, F32& volume); - - /** - * Removes stored volume level for specified user. - * - * @param[in] speaker_id - LLUUID of user to remove. - */ - void removeSpeakerVolume(const LLUUID& speaker_id); - -private: - friend class LLSingleton; - LLSpeakerVolumeStorage(); - ~LLSpeakerVolumeStorage(); - - const static std::string SETTINGS_FILE_NAME; - - void load(); - void save(); - - static F32 transformFromLegacyVolume(F32 volume_in); - static F32 transformToLegacyVolume(F32 volume_in); - - typedef std::map speaker_data_map_t; - speaker_data_map_t mSpeakersData; -}; - -const std::string LLSpeakerVolumeStorage::SETTINGS_FILE_NAME = "volume_settings.xml"; - -LLSpeakerVolumeStorage::LLSpeakerVolumeStorage() -{ - load(); -} - -LLSpeakerVolumeStorage::~LLSpeakerVolumeStorage() -{ - save(); -} - -void LLSpeakerVolumeStorage::storeSpeakerVolume(const LLUUID& speaker_id, F32 volume) -{ - if ((volume >= LLVoiceClient::VOLUME_MIN) && (volume <= LLVoiceClient::VOLUME_MAX)) - { - mSpeakersData[speaker_id] = volume; - - // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. - // LL_DEBUGS("Voice") << "Stored volume = " << volume << " for " << id << LL_ENDL; - } - else - { - LL_WARNS("Voice") << "Attempted to store out of range volume " << volume << " for " << speaker_id << LL_ENDL; - llassert(0); - } -} - -bool LLSpeakerVolumeStorage::getSpeakerVolume(const LLUUID& speaker_id, F32& volume) -{ - speaker_data_map_t::const_iterator it = mSpeakersData.find(speaker_id); - - if (it != mSpeakersData.end()) - { - volume = it->second; - - // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. - // LL_DEBUGS("Voice") << "Retrieved stored volume = " << volume << " for " << id << LL_ENDL; - - return true; - } - - return false; -} - -void LLSpeakerVolumeStorage::removeSpeakerVolume(const LLUUID& speaker_id) -{ - mSpeakersData.erase(speaker_id); - - // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. - // LL_DEBUGS("Voice") << "Removing stored volume for " << id << LL_ENDL; -} - -/* static */ F32 LLSpeakerVolumeStorage::transformFromLegacyVolume(F32 volume_in) -{ - // Convert to linear-logarithmic [0.0..1.0] with 0.5 = 0dB - // from legacy characteristic composed of two square-curves - // that intersect at volume_in = 0.5, volume_out = 0.56 - - F32 volume_out = 0.f; - volume_in = llclamp(volume_in, 0.f, 1.0f); - - if (volume_in <= 0.5f) - { - volume_out = volume_in * volume_in * 4.f * 0.56f; - } - else - { - volume_out = (1.f - 0.56f) * (4.f * volume_in * volume_in - 1.f) / 3.f + 0.56f; - } - - return volume_out; -} - -/* static */ F32 LLSpeakerVolumeStorage::transformToLegacyVolume(F32 volume_in) -{ - // Convert from linear-logarithmic [0.0..1.0] with 0.5 = 0dB - // to legacy characteristic composed of two square-curves - // that intersect at volume_in = 0.56, volume_out = 0.5 - - F32 volume_out = 0.f; - volume_in = llclamp(volume_in, 0.f, 1.0f); - - if (volume_in <= 0.56f) - { - volume_out = sqrt(volume_in / (4.f * 0.56f)); - } - else - { - volume_out = sqrt((3.f * (volume_in - 0.56f) / (1.f - 0.56f) + 1.f) / 4.f); - } - - return volume_out; -} - -void LLSpeakerVolumeStorage::load() -{ - // load per-resident voice volume information - std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, SETTINGS_FILE_NAME); - - LL_INFOS("Voice") << "Loading stored speaker volumes from: " << filename << LL_ENDL; - - LLSD settings_llsd; - llifstream file; - file.open(filename); - if (file.is_open()) - { - LLSDSerialize::fromXML(settings_llsd, file); - } - - for (LLSD::map_const_iterator iter = settings_llsd.beginMap(); - iter != settings_llsd.endMap(); ++iter) - { - // Maintain compatibility with 1.23 non-linear saved volume levels - F32 volume = transformFromLegacyVolume((F32)iter->second.asReal()); - - storeSpeakerVolume(LLUUID(iter->first), volume); - } -} - -void LLSpeakerVolumeStorage::save() -{ - // If we quit from the login screen we will not have an SL account - // name. Don't try to save, otherwise we'll dump a file in - // C:\Program Files\SecondLife\ or similar. JC - std::string user_dir = gDirUtilp->getLindenUserDir(); - if (!user_dir.empty()) - { - std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, SETTINGS_FILE_NAME); - LLSD settings_llsd; - - LL_INFOS("Voice") << "Saving stored speaker volumes to: " << filename << LL_ENDL; - - for(speaker_data_map_t::const_iterator iter = mSpeakersData.begin(); iter != mSpeakersData.end(); ++iter) - { - // Maintain compatibility with 1.23 non-linear saved volume levels - F32 volume = transformToLegacyVolume(iter->second); - - settings_llsd[iter->first.asString()] = volume; - } - - llofstream file; - file.open(filename); - LLSDSerialize::toPrettyXML(settings_llsd, file); - } -} - - -/////////////////////////////////////////////////////////////////////////////////////////////// - -LLVoiceClient::LLVoiceClient() : - mState(stateDisabled), - mSessionTerminateRequested(false), - mRelogRequested(false), - mConnected(false), - mPump(NULL), - mSpatialJoiningNum(0), - - mTuningMode(false), - mTuningEnergy(0.0f), - mTuningMicVolume(0), - mTuningMicVolumeDirty(true), - mTuningSpeakerVolume(0), - mTuningSpeakerVolumeDirty(true), - mTuningExitState(stateDisabled), - - mAreaVoiceDisabled(false), - mAudioSession(NULL), - mAudioSessionChanged(false), - mNextAudioSession(NULL), - - mCurrentParcelLocalID(0), - mNumberOfAliases(0), - mCommandCookie(0), - mLoginRetryCount(0), - - mBuddyListMapPopulated(false), - mBlockRulesListReceived(false), - mAutoAcceptRulesListReceived(false), - mCaptureDeviceDirty(false), - mRenderDeviceDirty(false), - mSpatialCoordsDirty(false), - - mPTTDirty(true), - mPTT(true), - mUsePTT(true), - mPTTIsMiddleMouse(false), - mPTTKey(0), - mPTTIsToggle(false), - mUserPTTState(false), - mMuteMic(false), - mFriendsListDirty(true), - - mEarLocation(0), - mSpeakerVolumeDirty(true), - mSpeakerMuteDirty(true), - mMicVolume(0), - mMicVolumeDirty(true), - - mVoiceEnabled(false), - mWriteInProgress(false), - - mLipSyncEnabled(false) -{ - gVoiceClient = this; - - mAPIVersion = LLTrans::getString("NotConnected"); - - mSpeakerVolume = scale_speaker_volume(0); - -#if LL_DARWIN || LL_LINUX || LL_SOLARIS - // HACK: THIS DOES NOT BELONG HERE - // When the vivox daemon dies, the next write attempt on our socket generates a SIGPIPE, which kills us. - // This should cause us to ignore SIGPIPE and handle the error through proper channels. - // This should really be set up elsewhere. Where should it go? - signal(SIGPIPE, SIG_IGN); - - // Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes. - // Ignoring SIGCHLD should prevent zombies from being created. Alternately, we could use wait(), but I'd rather not do that. - signal(SIGCHLD, SIG_IGN); -#endif - - // set up state machine - setState(stateDisabled); - - gIdleCallbacks.addFunction(idle, this); -} - -//--------------------------------------------------- - -LLVoiceClient::~LLVoiceClient() -{ -} - -//---------------------------------------------- - -void LLVoiceClient::init(LLPumpIO *pump) -{ - // constructor will set up gVoiceClient - LLVoiceClient::getInstance()->mPump = pump; - LLVoiceClient::getInstance()->updateSettings(); -} - -void LLVoiceClient::terminate() -{ - if(gVoiceClient) - { -// gVoiceClient->leaveAudioSession(); - gVoiceClient->logout(); - // As of SDK version 4885, this should no longer be necessary. It will linger after the socket close if it needs to. - // ms_sleep(2000); - gVoiceClient->connectorShutdown(); - gVoiceClient->closeSocket(); // Need to do this now -- bad things happen if the destructor does it later. - - // This will do unpleasant things on windows. -// killGateway(); - - // Don't do this anymore -- LLSingleton will take care of deleting the object. -// delete gVoiceClient; - - // Hint to other code not to access the voice client anymore. - gVoiceClient = NULL; - } -} - -//--------------------------------------------------- - -void LLVoiceClient::updateSettings() -{ - setVoiceEnabled(gSavedSettings.getBOOL("EnableVoiceChat")); - setUsePTT(gSavedSettings.getBOOL("PTTCurrentlyEnabled")); - std::string keyString = gSavedSettings.getString("PushToTalkButton"); - setPTTKey(keyString); - setPTTIsToggle(gSavedSettings.getBOOL("PushToTalkToggle")); - setEarLocation(gSavedSettings.getS32("VoiceEarLocation")); - - std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); - setCaptureDevice(inputDevice); - std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); - setRenderDevice(outputDevice); - F32 mic_level = gSavedSettings.getF32("AudioLevelMic"); - setMicGain(mic_level); - setLipSyncEnabled(gSavedSettings.getBOOL("LipSyncEnabled")); -} - -///////////////////////////// -// utility functions - -bool LLVoiceClient::writeString(const std::string &str) -{ - bool result = false; - if(mConnected) - { - apr_status_t err; - apr_size_t size = (apr_size_t)str.size(); - apr_size_t written = size; - - //MARK: Turn this on to log outgoing XML -// LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL; - - // check return code - sockets will fail (broken, etc.) - err = apr_socket_send( - mSocket->getSocket(), - (const char*)str.data(), - &written); - - if(err == 0) - { - // Success. - result = true; - } - // TODO: handle partial writes (written is number of bytes written) - // Need to set socket to non-blocking before this will work. -// else if(APR_STATUS_IS_EAGAIN(err)) -// { -// // -// } - else - { - // Assume any socket error means something bad. For now, just close the socket. - char buf[MAX_STRING]; - LL_WARNS("Voice") << "apr error " << err << " ("<< apr_strerror(err, buf, MAX_STRING) << ") sending data to vivox daemon." << LL_ENDL; - daemonDied(); - } - } - - return result; -} - - -///////////////////////////// -// session control messages -void LLVoiceClient::connectorCreate() -{ - std::ostringstream stream; - std::string logpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); - std::string loglevel = "0"; - - // Transition to stateConnectorStarted when the connector handle comes back. - setState(stateConnectorStarting); - - std::string savedLogLevel = gSavedSettings.getString("VivoxDebugLevel"); - - if(savedLogLevel != "-1") - { - LL_DEBUGS("Voice") << "creating connector with logging enabled" << LL_ENDL; - loglevel = "10"; - } - - stream - << "" - << "V2 SDK" - << "" << mVoiceAccountServerURI << "" - << "Normal" - << "" - << "" << logpath << "" - << "Connector" - << ".log" - << "" << loglevel << "" - << "" - << "SecondLifeViewer.1" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVoiceClient::connectorShutdown() -{ - setState(stateConnectorStopping); - - if(!mConnectorHandle.empty()) - { - std::ostringstream stream; - stream - << "" - << "" << mConnectorHandle << "" - << "" - << "\n\n\n"; - - mConnectorHandle.clear(); - - writeString(stream.str()); - } -} - -void LLVoiceClient::userAuthorized(const std::string& firstName, const std::string& lastName, const LLUUID &agentID) -{ - mAccountFirstName = firstName; - mAccountLastName = lastName; - - mAccountDisplayName = firstName; - mAccountDisplayName += " "; - mAccountDisplayName += lastName; - - LL_INFOS("Voice") << "name \"" << mAccountDisplayName << "\" , ID " << agentID << LL_ENDL; - - sConnectingToAgni = LLViewerLogin::getInstance()->isInProductionGrid(); - - mAccountName = nameFromID(agentID); -} - -void LLVoiceClient::requestVoiceAccountProvision(S32 retries) -{ - if ( gAgent.getRegion() && mVoiceEnabled ) - { - std::string url = - gAgent.getRegion()->getCapability( - "ProvisionVoiceAccountRequest"); - - if ( url == "" ) return; - - LLHTTPClient::post( - url, - LLSD(), - new LLViewerVoiceAccountProvisionResponder(retries)); - } -} - -void LLVoiceClient::login( - const std::string& account_name, - const std::string& password, - const std::string& voice_sip_uri_hostname, - const std::string& voice_account_server_uri) -{ - mVoiceSIPURIHostName = voice_sip_uri_hostname; - mVoiceAccountServerURI = voice_account_server_uri; - - if(!mAccountHandle.empty()) - { - // Already logged in. - LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL; - - // Don't process another login. - return; - } - else if ( account_name != mAccountName ) - { - //TODO: error? - LL_WARNS("Voice") << "Wrong account name! " << account_name - << " instead of " << mAccountName << LL_ENDL; - } - else - { - mAccountPassword = password; - } - - std::string debugSIPURIHostName = gSavedSettings.getString("VivoxDebugSIPURIHostName"); - - if( !debugSIPURIHostName.empty() ) - { - mVoiceSIPURIHostName = debugSIPURIHostName; - } - - if( mVoiceSIPURIHostName.empty() ) - { - // we have an empty account server name - // so we fall back to hardcoded defaults - - if(sConnectingToAgni) - { - // Use the release account server - mVoiceSIPURIHostName = "bhr.vivox.com"; - } - else - { - // Use the development account server - mVoiceSIPURIHostName = "bhd.vivox.com"; - } - } - - std::string debugAccountServerURI = gSavedSettings.getString("VivoxDebugVoiceAccountServerURI"); - - if( !debugAccountServerURI.empty() ) - { - mVoiceAccountServerURI = debugAccountServerURI; - } - - if( mVoiceAccountServerURI.empty() ) - { - // If the account server URI isn't specified, construct it from the SIP URI hostname - mVoiceAccountServerURI = "https://www." + mVoiceSIPURIHostName + "/api2/"; - } -} - -void LLVoiceClient::idle(void* user_data) -{ - LLVoiceClient* self = (LLVoiceClient*)user_data; - self->stateMachine(); -} - -std::string LLVoiceClient::state2string(LLVoiceClient::state inState) -{ - std::string result = "UNKNOWN"; - - // Prevent copy-paste errors when updating this list... -#define CASE(x) case x: result = #x; break - - switch(inState) - { - CASE(stateDisableCleanup); - CASE(stateDisabled); - CASE(stateStart); - CASE(stateDaemonLaunched); - CASE(stateConnecting); - CASE(stateConnected); - CASE(stateIdle); - CASE(stateMicTuningStart); - CASE(stateMicTuningRunning); - CASE(stateMicTuningStop); - CASE(stateConnectorStart); - CASE(stateConnectorStarting); - CASE(stateConnectorStarted); - CASE(stateLoginRetry); - CASE(stateLoginRetryWait); - CASE(stateNeedsLogin); - CASE(stateLoggingIn); - CASE(stateLoggedIn); - CASE(stateCreatingSessionGroup); - CASE(stateNoChannel); - CASE(stateJoiningSession); - CASE(stateSessionJoined); - CASE(stateRunning); - CASE(stateLeavingSession); - CASE(stateSessionTerminated); - CASE(stateLoggingOut); - CASE(stateLoggedOut); - CASE(stateConnectorStopping); - CASE(stateConnectorStopped); - CASE(stateConnectorFailed); - CASE(stateConnectorFailedWaiting); - CASE(stateLoginFailed); - CASE(stateLoginFailedWaiting); - CASE(stateJoinSessionFailed); - CASE(stateJoinSessionFailedWaiting); - CASE(stateJail); - } - -#undef CASE - - return result; -} - -std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserver::EStatusType inStatus) -{ - std::string result = "UNKNOWN"; - - // Prevent copy-paste errors when updating this list... -#define CASE(x) case x: result = #x; break - - switch(inStatus) - { - CASE(STATUS_LOGIN_RETRY); - CASE(STATUS_LOGGED_IN); - CASE(STATUS_JOINING); - CASE(STATUS_JOINED); - CASE(STATUS_LEFT_CHANNEL); - CASE(STATUS_VOICE_DISABLED); - CASE(STATUS_VOICE_ENABLED); - CASE(BEGIN_ERROR_STATUS); - CASE(ERROR_CHANNEL_FULL); - CASE(ERROR_CHANNEL_LOCKED); - CASE(ERROR_NOT_AVAILABLE); - CASE(ERROR_UNKNOWN); - default: - break; - } - -#undef CASE - - return result; -} - -void LLVoiceClient::setState(state inState) -{ - LL_DEBUGS("Voice") << "entering state " << state2string(inState) << LL_ENDL; - - mState = inState; -} - -void LLVoiceClient::stateMachine() -{ - if(gDisconnected) - { - // The viewer has been disconnected from the sim. Disable voice. - setVoiceEnabled(false); - } - - if(mVoiceEnabled) - { - updatePosition(); - } - else if(mTuningMode) - { - // Tuning mode is special -- it needs to launch SLVoice even if voice is disabled. - } - else - { - if((getState() != stateDisabled) && (getState() != stateDisableCleanup)) - { - // User turned off voice support. Send the cleanup messages, close the socket, and reset. - if(!mConnected) - { - // if voice was turned off after the daemon was launched but before we could connect to it, we may need to issue a kill. - LL_INFOS("Voice") << "Disabling voice before connection to daemon, terminating." << LL_ENDL; - killGateway(); - } - - logout(); - connectorShutdown(); - - setState(stateDisableCleanup); - } - } - - // Check for parcel boundary crossing - { - LLViewerRegion *region = gAgent.getRegion(); - LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); - - if(region && parcel) - { - S32 parcelLocalID = parcel->getLocalID(); - std::string regionName = region->getName(); - std::string capURI = region->getCapability("ParcelVoiceInfoRequest"); - -// LL_DEBUGS("Voice") << "Region name = \"" << regionName << "\", parcel local ID = " << parcelLocalID << ", cap URI = \"" << capURI << "\"" << LL_ENDL; - - // The region name starts out empty and gets filled in later. - // Also, the cap gets filled in a short time after the region cross, but a little too late for our purposes. - // If either is empty, wait for the next time around. - if(!regionName.empty()) - { - if(!capURI.empty()) - { - if((parcelLocalID != mCurrentParcelLocalID) || (regionName != mCurrentRegionName)) - { - // We have changed parcels. Initiate a parcel channel lookup. - mCurrentParcelLocalID = parcelLocalID; - mCurrentRegionName = regionName; - - parcelChanged(); - } - } - else - { - LL_WARNS_ONCE("Voice") << "region doesn't have ParcelVoiceInfoRequest capability. This is normal for a short time after teleporting, but bad if it persists for very long." << LL_ENDL; - } - } - } - } - - switch(getState()) - { - //MARK: stateDisableCleanup - case stateDisableCleanup: - // Clean up and reset everything. - closeSocket(); - deleteAllSessions(); - deleteAllBuddies(); - - mConnectorHandle.clear(); - mAccountHandle.clear(); - mAccountPassword.clear(); - mVoiceAccountServerURI.clear(); - - setState(stateDisabled); - break; - - //MARK: stateDisabled - case stateDisabled: - if(mTuningMode || (mVoiceEnabled && !mAccountName.empty())) - { - setState(stateStart); - } - break; - - //MARK: stateStart - case stateStart: - if(gSavedSettings.getBOOL("CmdLineDisableVoice")) - { - // Voice is locked out, we must not launch the vivox daemon. - setState(stateJail); - } - else if(!isGatewayRunning()) - { - if(true) - { - // Launch the voice daemon - - // *FIX:Mani - Using the executable dir instead - // of mAppRODataDir, the working directory from which the app - // is launched. - //std::string exe_path = gDirUtilp->getAppRODataDir(); - std::string exe_path = gDirUtilp->getExecutableDir(); - exe_path += gDirUtilp->getDirDelimiter(); -#if LL_WINDOWS - exe_path += "SLVoice.exe"; -#elif LL_DARWIN - exe_path += "../Resources/SLVoice"; -#else - exe_path += "SLVoice"; -#endif - // See if the vivox executable exists - llstat s; - if(!LLFile::stat(exe_path, &s)) - { - // vivox executable exists. Build the command line and launch the daemon. - // SLIM SDK: these arguments are no longer necessary. -// std::string args = " -p tcp -h -c"; - std::string args; - std::string loglevel = gSavedSettings.getString("VivoxDebugLevel"); - - if(loglevel.empty()) - { - loglevel = "-1"; // turn logging off completely - } - - args += " -ll "; - args += loglevel; - - LL_DEBUGS("Voice") << "Args for SLVoice: " << args << LL_ENDL; - -#if LL_WINDOWS - PROCESS_INFORMATION pinfo; - STARTUPINFOW sinfo; - memset(&sinfo, 0, sizeof(sinfo)); - - std::string exe_dir = gDirUtilp->getExecutableDir(); - - llutf16string exe_path16 = utf8str_to_utf16str(exe_path); - llutf16string exe_dir16 = utf8str_to_utf16str(exe_dir); - llutf16string args16 = utf8str_to_utf16str(args); - // Create a writeable copy to keep Windows happy. - U16 *argscpy_16 = new U16[args16.size() + 1]; - wcscpy_s(argscpy_16,args16.size()+1,args16.c_str()); - if(!CreateProcessW(exe_path16.c_str(), argscpy_16, NULL, NULL, FALSE, 0, NULL, exe_dir16.c_str(), &sinfo, &pinfo)) - { -// DWORD dwErr = GetLastError(); - } - else - { - // foo = pinfo.dwProcessId; // get your pid here if you want to use it later on - // CloseHandle(pinfo.hProcess); // stops leaks - nothing else - sGatewayHandle = pinfo.hProcess; - CloseHandle(pinfo.hThread); // stops leaks - nothing else - } - - delete[] argscpy_16; -#else // LL_WINDOWS - // This should be the same for mac and linux - { - std::vector arglist; - arglist.push_back(exe_path); - - // Split the argument string into separate strings for each argument - typedef boost::tokenizer > tokenizer; - boost::char_separator sep(" "); - tokenizer tokens(args, sep); - tokenizer::iterator token_iter; - - for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) - { - arglist.push_back(*token_iter); - } - - // create an argv vector for the child process - char **fakeargv = new char*[arglist.size() + 1]; - int i; - for(i=0; i < arglist.size(); i++) - fakeargv[i] = const_cast(arglist[i].c_str()); - - fakeargv[i] = NULL; - - fflush(NULL); // flush all buffers before the child inherits them - pid_t id = vfork(); - if(id == 0) - { - // child - execv(exe_path.c_str(), fakeargv); - - // If we reach this point, the exec failed. - // Use _exit() instead of exit() per the vfork man page. - _exit(0); - } - - // parent - delete[] fakeargv; - sGatewayPID = id; - } -#endif // LL_WINDOWS - mDaemonHost = LLHost(gSavedSettings.getString("VoiceHost").c_str(), gSavedSettings.getU32("VoicePort")); - } - else - { - LL_INFOS("Voice") << exe_path << " not found." << LL_ENDL; - } - } - else - { - // SLIM SDK: port changed from 44124 to 44125. - // We can connect to a client gateway running on another host. This is useful for testing. - // To do this, launch the gateway on a nearby host like this: - // vivox-gw.exe -p tcp -i 0.0.0.0:44125 - // and put that host's IP address here. - mDaemonHost = LLHost(gSavedSettings.getString("VoiceHost"), gSavedSettings.getU32("VoicePort")); - } - - mUpdateTimer.start(); - mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS); - - setState(stateDaemonLaunched); - - // Dirty the states we'll need to sync with the daemon when it comes up. - mPTTDirty = true; - mMicVolumeDirty = true; - mSpeakerVolumeDirty = true; - mSpeakerMuteDirty = true; - // These only need to be set if they're not default (i.e. empty string). - mCaptureDeviceDirty = !mCaptureDevice.empty(); - mRenderDeviceDirty = !mRenderDevice.empty(); - - mMainSessionGroupHandle.clear(); - } - break; - - //MARK: stateDaemonLaunched - case stateDaemonLaunched: - if(mUpdateTimer.hasExpired()) - { - LL_DEBUGS("Voice") << "Connecting to vivox daemon" << LL_ENDL; - - mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS); - - if(!mSocket) - { - mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); - } - - mConnected = mSocket->blockingConnect(mDaemonHost); - if(mConnected) - { - setState(stateConnecting); - } - else - { - // If the connect failed, the socket may have been put into a bad state. Delete it. - closeSocket(); - } - } - break; - - //MARK: stateConnecting - case stateConnecting: - // Can't do this until we have the pump available. - if(mPump) - { - // MBW -- Note to self: pumps and pipes examples in - // indra/test/io.cpp - // indra/test/llpipeutil.{cpp|h} - - // Attach the pumps and pipes - - LLPumpIO::chain_t readChain; - - readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket))); - readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser())); - - mPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS); - - setState(stateConnected); - } - - break; - - //MARK: stateConnected - case stateConnected: - // Initial devices query - getCaptureDevicesSendMessage(); - getRenderDevicesSendMessage(); - - mLoginRetryCount = 0; - - setState(stateIdle); - break; - - //MARK: stateIdle - case stateIdle: - // This is the idle state where we're connected to the daemon but haven't set up a connector yet. - if(mTuningMode) - { - mTuningExitState = stateIdle; - setState(stateMicTuningStart); - } - else if(!mVoiceEnabled) - { - // We never started up the connector. This will shut down the daemon. - setState(stateConnectorStopped); - } - else if(!mAccountName.empty()) - { - LLViewerRegion *region = gAgent.getRegion(); - - if(region) - { - if ( region->getCapability("ProvisionVoiceAccountRequest") != "" ) - { - if ( mAccountPassword.empty() ) - { - requestVoiceAccountProvision(); - } - setState(stateConnectorStart); - } - else - { - LL_WARNS_ONCE("Voice") << "region doesn't have ProvisionVoiceAccountRequest capability!" << LL_ENDL; - } - } - } - break; - - //MARK: stateMicTuningStart - case stateMicTuningStart: - if(mUpdateTimer.hasExpired()) - { - if(mCaptureDeviceDirty || mRenderDeviceDirty) - { - // These can't be changed while in tuning mode. Set them before starting. - std::ostringstream stream; - - buildSetCaptureDevice(stream); - buildSetRenderDevice(stream); - - if(!stream.str().empty()) - { - writeString(stream.str()); - } - - // This will come around again in the same state and start the capture, after the timer expires. - mUpdateTimer.start(); - mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); - } - else - { - // duration parameter is currently unused, per Mike S. - tuningCaptureStartSendMessage(10000); - - setState(stateMicTuningRunning); - } - } - - break; - - //MARK: stateMicTuningRunning - case stateMicTuningRunning: - if(!mTuningMode || mCaptureDeviceDirty || mRenderDeviceDirty) - { - // All of these conditions make us leave tuning mode. - setState(stateMicTuningStop); - } - else - { - // process mic/speaker volume changes - if(mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty) - { - std::ostringstream stream; - - if(mTuningMicVolumeDirty) - { - LL_INFOS("Voice") << "setting tuning mic level to " << mTuningMicVolume << LL_ENDL; - stream - << "" - << "" << mTuningMicVolume << "" - << "\n\n\n"; - } - - if(mTuningSpeakerVolumeDirty) - { - stream - << "" - << "" << mTuningSpeakerVolume << "" - << "\n\n\n"; - } - - mTuningMicVolumeDirty = false; - mTuningSpeakerVolumeDirty = false; - - if(!stream.str().empty()) - { - writeString(stream.str()); - } - } - } - break; - - //MARK: stateMicTuningStop - case stateMicTuningStop: - { - // transition out of mic tuning - tuningCaptureStopSendMessage(); - - setState(mTuningExitState); - - // if we exited just to change devices, this will keep us from re-entering too fast. - mUpdateTimer.start(); - mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); - - } - break; - - //MARK: stateConnectorStart - case stateConnectorStart: - if(!mVoiceEnabled) - { - // We were never logged in. This will shut down the connector. - setState(stateLoggedOut); - } - else if(!mVoiceAccountServerURI.empty()) - { - connectorCreate(); - } - break; - - //MARK: stateConnectorStarting - case stateConnectorStarting: // waiting for connector handle - // connectorCreateResponse() will transition from here to stateConnectorStarted. - break; - - //MARK: stateConnectorStarted - case stateConnectorStarted: // connector handle received - if(!mVoiceEnabled) - { - // We were never logged in. This will shut down the connector. - setState(stateLoggedOut); - } - else - { - // The connector is started. Send a login message. - setState(stateNeedsLogin); - } - break; - - //MARK: stateLoginRetry - case stateLoginRetry: - if(mLoginRetryCount == 0) - { - // First retry -- display a message to the user - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGIN_RETRY); - } - - mLoginRetryCount++; - - if(mLoginRetryCount > MAX_LOGIN_RETRIES) - { - LL_WARNS("Voice") << "too many login retries, giving up." << LL_ENDL; - setState(stateLoginFailed); - } - else - { - LL_INFOS("Voice") << "will retry login in " << LOGIN_RETRY_SECONDS << " seconds." << LL_ENDL; - mUpdateTimer.start(); - mUpdateTimer.setTimerExpirySec(LOGIN_RETRY_SECONDS); - setState(stateLoginRetryWait); - } - break; - - //MARK: stateLoginRetryWait - case stateLoginRetryWait: - if(mUpdateTimer.hasExpired()) - { - setState(stateNeedsLogin); - } - break; - - //MARK: stateNeedsLogin - case stateNeedsLogin: - if(!mAccountPassword.empty()) - { - setState(stateLoggingIn); - loginSendMessage(); - } - break; - - //MARK: stateLoggingIn - case stateLoggingIn: // waiting for account handle - // loginResponse() will transition from here to stateLoggedIn. - break; - - //MARK: stateLoggedIn - case stateLoggedIn: // account handle received - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); - - // request the current set of block rules (we'll need them when updating the friends list) - accountListBlockRulesSendMessage(); - - // request the current set of auto-accept rules - accountListAutoAcceptRulesSendMessage(); - - // Set up the mute list observer if it hasn't been set up already. - if((!sMuteListListener_listening)) - { - LLMuteList::getInstance()->addObserver(&mutelist_listener); - sMuteListListener_listening = true; - } - - // Set up the friends list observer if it hasn't been set up already. - if(friendslist_listener == NULL) - { - friendslist_listener = new LLVoiceClientFriendsObserver; - LLAvatarTracker::instance().addObserver(friendslist_listener); - } - - // Set the initial state of mic mute, local speaker volume, etc. - { - std::ostringstream stream; - - buildLocalAudioUpdates(stream); - - if(!stream.str().empty()) - { - writeString(stream.str()); - } - } - -#if USE_SESSION_GROUPS - // create the main session group - sessionGroupCreateSendMessage(); - - setState(stateCreatingSessionGroup); -#else - // Not using session groups -- skip the stateCreatingSessionGroup state. - setState(stateNoChannel); - - // Initial kick-off of channel lookup logic - parcelChanged(); -#endif - break; - - //MARK: stateCreatingSessionGroup - case stateCreatingSessionGroup: - if(mSessionTerminateRequested || !mVoiceEnabled) - { - // TODO: Question: is this the right way out of this state - setState(stateSessionTerminated); - } - else if(!mMainSessionGroupHandle.empty()) - { - setState(stateNoChannel); - - // Start looped recording (needed for "panic button" anti-griefing tool) - recordingLoopStart(); - - // Initial kick-off of channel lookup logic - parcelChanged(); - } - break; - - //MARK: stateNoChannel - case stateNoChannel: - - mSpatialJoiningNum = 0; - // Do this here as well as inside sendPositionalUpdate(). - // Otherwise, if you log in but don't join a proximal channel (such as when your login location has voice disabled), your friends list won't sync. - sendFriendsListUpdates(); - - if(mSessionTerminateRequested || !mVoiceEnabled) - { - // TODO: Question: Is this the right way out of this state? - setState(stateSessionTerminated); - } - else if(mTuningMode) - { - mTuningExitState = stateNoChannel; - setState(stateMicTuningStart); - } - else if(sessionNeedsRelog(mNextAudioSession)) - { - requestRelog(); - setState(stateSessionTerminated); - } - else if(mNextAudioSession) - { - sessionState *oldSession = mAudioSession; - - mAudioSession = mNextAudioSession; - if(!mAudioSession->mReconnect) - { - mNextAudioSession = NULL; - } - - // The old session may now need to be deleted. - reapSession(oldSession); - - if(!mAudioSession->mHandle.empty()) - { - // Connect to a session by session handle - - sessionMediaConnectSendMessage(mAudioSession); - } - else - { - // Connect to a session by URI - sessionCreateSendMessage(mAudioSession, true, false); - } - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); - setState(stateJoiningSession); - } - else if(!mSpatialSessionURI.empty()) - { - // If we're not headed elsewhere and have a spatial URI, return to spatial. - switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials); - } - break; - - //MARK: stateJoiningSession - case stateJoiningSession: // waiting for session handle - - // If this is true we have problem with connection to voice server (EXT-4313). - // See descriptions of mSpatialJoiningNum and MAX_NORMAL_JOINING_SPATIAL_NUM. - if(mSpatialJoiningNum == MAX_NORMAL_JOINING_SPATIAL_NUM) - { - // Notify observers to let them know there is problem with voice - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - llwarns << "There seems to be problem with connection to voice server. Disabling voice chat abilities." << llendl; - } - - // Increase mSpatialJoiningNum only for spatial sessions- it's normal to reach this case for - // example for p2p many times while waiting for response, so it can't be used to detect errors - if(mAudioSession && mAudioSession->mIsSpatial) - { - mSpatialJoiningNum++; - } - - // joinedAudioSession() will transition from here to stateSessionJoined. - if(!mVoiceEnabled) - { - // User bailed out during connect -- jump straight to teardown. - setState(stateSessionTerminated); - } - else if(mSessionTerminateRequested) - { - if(mAudioSession && !mAudioSession->mHandle.empty()) - { - // Only allow direct exits from this state in p2p calls (for cancelling an invite). - // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. - if(mAudioSession->mIsP2P) - { - sessionMediaDisconnectSendMessage(mAudioSession); - setState(stateSessionTerminated); - } - } - } - break; - - //MARK: stateSessionJoined - case stateSessionJoined: // session handle received - - mSpatialJoiningNum = 0; - // It appears that I need to wait for BOTH the SessionGroup.AddSession response and the SessionStateChangeEvent with state 4 - // before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck. - // For now, the SessionGroup.AddSession response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined. - // This is a cheap way to make sure both have happened before proceeding. - if(mAudioSession && mAudioSession->mVoiceEnabled) - { - // Dirty state that may need to be sync'ed with the daemon. - mPTTDirty = true; - mSpeakerVolumeDirty = true; - mSpatialCoordsDirty = true; - - setState(stateRunning); - - // Start the throttle timer - mUpdateTimer.start(); - mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); - - // Events that need to happen when a session is joined could go here. - // Maybe send initial spatial data? - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); - - } - else if(!mVoiceEnabled) - { - // User bailed out during connect -- jump straight to teardown. - setState(stateSessionTerminated); - } - else if(mSessionTerminateRequested) - { - // Only allow direct exits from this state in p2p calls (for cancelling an invite). - // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. - if(mAudioSession && mAudioSession->mIsP2P) - { - sessionMediaDisconnectSendMessage(mAudioSession); - setState(stateSessionTerminated); - } - } - break; - - //MARK: stateRunning - case stateRunning: // steady state - // Disabling voice or disconnect requested. - if(!mVoiceEnabled || mSessionTerminateRequested) - { - leaveAudioSession(); - } - else - { - - // Figure out whether the PTT state needs to change - { - bool newPTT; - if(mUsePTT) - { - // If configured to use PTT, track the user state. - newPTT = mUserPTTState; - } - else - { - // If not configured to use PTT, it should always be true (otherwise the user will be unable to speak). - newPTT = true; - } - - if(mMuteMic) - { - // This always overrides any other PTT setting. - newPTT = false; - } - - // Dirty if state changed. - if(newPTT != mPTT) - { - mPTT = newPTT; - mPTTDirty = true; - } - } - - if(!inSpatialChannel()) - { - // When in a non-spatial channel, never send positional updates. - mSpatialCoordsDirty = false; - } - else - { - // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position) - enforceTether(); - } - - // Send an update only if the ptt or mute state has changed (which shouldn't be able to happen that often - // -- the user can only click so fast) or every 10hz, whichever is sooner. - // Sending for every volume update causes an excessive flood of messages whenever a volume slider is dragged. - if((mAudioSession && mAudioSession->mMuteDirty) || mPTTDirty || mUpdateTimer.hasExpired()) - { - mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); - sendPositionalUpdate(); - } - } - break; - - //MARK: stateLeavingSession - case stateLeavingSession: // waiting for terminate session response - // The handler for the Session.Terminate response will transition from here to stateSessionTerminated. - break; - - //MARK: stateSessionTerminated - case stateSessionTerminated: - - // Must do this first, since it uses mAudioSession. - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); - - if(mAudioSession) - { - sessionState *oldSession = mAudioSession; - - mAudioSession = NULL; - // We just notified status observers about this change. Don't do it again. - mAudioSessionChanged = false; - - // The old session may now need to be deleted. - reapSession(oldSession); - } - else - { - LL_WARNS("Voice") << "stateSessionTerminated with NULL mAudioSession" << LL_ENDL; - } - - // Always reset the terminate request flag when we get here. - mSessionTerminateRequested = false; - - if(mVoiceEnabled && !mRelogRequested) - { - // Just leaving a channel, go back to stateNoChannel (the "logged in but have no channel" state). - setState(stateNoChannel); - } - else - { - // Shutting down voice, continue with disconnecting. - logout(); - - // The state machine will take it from here - mRelogRequested = false; - } - - break; - - //MARK: stateLoggingOut - case stateLoggingOut: // waiting for logout response - // The handler for the AccountLoginStateChangeEvent will transition from here to stateLoggedOut. - break; - - //MARK: stateLoggedOut - case stateLoggedOut: // logout response received - - // Once we're logged out, all these things are invalid. - mAccountHandle.clear(); - deleteAllSessions(); - deleteAllBuddies(); - - if(mVoiceEnabled && !mRelogRequested) - { - // User was logged out, but wants to be logged in. Send a new login request. - setState(stateNeedsLogin); - } - else - { - // shut down the connector - connectorShutdown(); - } - break; - - //MARK: stateConnectorStopping - case stateConnectorStopping: // waiting for connector stop - // The handler for the Connector.InitiateShutdown response will transition from here to stateConnectorStopped. - break; - - //MARK: stateConnectorStopped - case stateConnectorStopped: // connector stop received - setState(stateDisableCleanup); - break; - - //MARK: stateConnectorFailed - case stateConnectorFailed: - setState(stateConnectorFailedWaiting); - break; - //MARK: stateConnectorFailedWaiting - case stateConnectorFailedWaiting: - if(!mVoiceEnabled) - { - setState(stateDisableCleanup); - } - break; - - //MARK: stateLoginFailed - case stateLoginFailed: - setState(stateLoginFailedWaiting); - break; - //MARK: stateLoginFailedWaiting - case stateLoginFailedWaiting: - if(!mVoiceEnabled) - { - setState(stateDisableCleanup); - } - break; - - //MARK: stateJoinSessionFailed - case stateJoinSessionFailed: - // Transition to error state. Send out any notifications here. - if(mAudioSession) - { - LL_WARNS("Voice") << "stateJoinSessionFailed: (" << mAudioSession->mErrorStatusCode << "): " << mAudioSession->mErrorStatusString << LL_ENDL; - } - else - { - LL_WARNS("Voice") << "stateJoinSessionFailed with no current session" << LL_ENDL; - } - - notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); - setState(stateJoinSessionFailedWaiting); - break; - - //MARK: stateJoinSessionFailedWaiting - case stateJoinSessionFailedWaiting: - // Joining a channel failed, either due to a failed channel name -> sip url lookup or an error from the join message. - // Region crossings may leave this state and try the join again. - if(mSessionTerminateRequested) - { - setState(stateSessionTerminated); - } - break; - - //MARK: stateJail - case stateJail: - // We have given up. Do nothing. - break; - - } - - if(mAudioSession && mAudioSession->mParticipantsChanged) - { - mAudioSession->mParticipantsChanged = false; - mAudioSessionChanged = true; - } - - if(mAudioSessionChanged) - { - mAudioSessionChanged = false; - notifyParticipantObservers(); - } -} - -void LLVoiceClient::closeSocket(void) -{ - mSocket.reset(); - mConnected = false; -} - -void LLVoiceClient::loginSendMessage() -{ - std::ostringstream stream; - - bool autoPostCrashDumps = gSavedSettings.getBOOL("VivoxAutoPostCrashDumps"); - - stream - << "" - << "" << mConnectorHandle << "" - << "" << mAccountName << "" - << "" << mAccountPassword << "" - << "VerifyAnswer" - << "true" - << "Application" - << "5" - << (autoPostCrashDumps?"true":"") - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVoiceClient::logout() -{ - // Ensure that we'll re-request provisioning before logging in again - mAccountPassword.clear(); - mVoiceAccountServerURI.clear(); - - setState(stateLoggingOut); - logoutSendMessage(); -} - -void LLVoiceClient::logoutSendMessage() -{ - if(!mAccountHandle.empty()) - { - std::ostringstream stream; - stream - << "" - << "" << mAccountHandle << "" - << "" - << "\n\n\n"; - - mAccountHandle.clear(); - - writeString(stream.str()); - } -} - -void LLVoiceClient::accountListBlockRulesSendMessage() -{ - if(!mAccountHandle.empty()) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "requesting block rules" << LL_ENDL; - - stream - << "" - << "" << mAccountHandle << "" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVoiceClient::accountListAutoAcceptRulesSendMessage() -{ - if(!mAccountHandle.empty()) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "requesting auto-accept rules" << LL_ENDL; - - stream - << "" - << "" << mAccountHandle << "" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVoiceClient::sessionGroupCreateSendMessage() -{ - if(!mAccountHandle.empty()) - { - std::ostringstream stream; - - LL_DEBUGS("Voice") << "creating session group" << LL_ENDL; - - stream - << "" - << "" << mAccountHandle << "" - << "Normal" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVoiceClient::sessionCreateSendMessage(sessionState *session, bool startAudio, bool startText) -{ - LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL; - - session->mCreateInProgress = true; - if(startAudio) - { - session->mMediaConnectInProgress = true; - } - - std::ostringstream stream; - stream - << "mSIPURI << "\" action=\"Session.Create.1\">" - << "" << mAccountHandle << "" - << "" << session->mSIPURI << ""; - - static const std::string allowed_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - "0123456789" - "-._~"; - - if(!session->mHash.empty()) - { - stream - << "" << LLURI::escape(session->mHash, allowed_chars) << "" - << "SHA1UserName"; - } - - stream - << "" << (startAudio?"true":"false") << "" - << "" << (startText?"true":"false") << "" - << "" << mChannelName << "" - << "\n\n\n"; - writeString(stream.str()); -} - -void LLVoiceClient::sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio, bool startText) -{ - LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL; - - session->mCreateInProgress = true; - if(startAudio) - { - session->mMediaConnectInProgress = true; - } - - std::string password; - if(!session->mHash.empty()) - { - static const std::string allowed_chars = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" - "0123456789" - "-._~" - ; - password = LLURI::escape(session->mHash, allowed_chars); - } - - std::ostringstream stream; - stream - << "mSIPURI << "\" action=\"SessionGroup.AddSession.1\">" - << "" << session->mGroupHandle << "" - << "" << session->mSIPURI << "" - << "" << mChannelName << "" - << "" << (startAudio?"true":"false") << "" - << "" << (startText?"true":"false") << "" - << "" << password << "" - << "SHA1UserName" - << "\n\n\n" - ; - - writeString(stream.str()); -} - -void LLVoiceClient::sessionMediaConnectSendMessage(sessionState *session) -{ - LL_DEBUGS("Voice") << "connecting audio to session handle: " << session->mHandle << LL_ENDL; - - session->mMediaConnectInProgress = true; - - std::ostringstream stream; - - stream - << "mHandle << "\" action=\"Session.MediaConnect.1\">" - << "" << session->mGroupHandle << "" - << "" << session->mHandle << "" - << "Audio" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVoiceClient::sessionTextConnectSendMessage(sessionState *session) -{ - LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL; - - std::ostringstream stream; - - stream - << "mHandle << "\" action=\"Session.TextConnect.1\">" - << "" << session->mGroupHandle << "" - << "" << session->mHandle << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVoiceClient::sessionTerminate() -{ - mSessionTerminateRequested = true; -} - -void LLVoiceClient::requestRelog() -{ - mSessionTerminateRequested = true; - mRelogRequested = true; -} - - -void LLVoiceClient::leaveAudioSession() -{ - if(mAudioSession) - { - LL_DEBUGS("Voice") << "leaving session: " << mAudioSession->mSIPURI << LL_ENDL; - - switch(getState()) - { - case stateNoChannel: - // In this case, we want to pretend the join failed so our state machine doesn't get stuck. - // Skip the join failed transition state so we don't send out error notifications. - setState(stateJoinSessionFailedWaiting); - break; - case stateJoiningSession: - case stateSessionJoined: - case stateRunning: - if(!mAudioSession->mHandle.empty()) - { - -#if RECORD_EVERYTHING - // HACK: for testing only - // Save looped recording - std::string savepath("/tmp/vivoxrecording"); - { - time_t now = time(NULL); - const size_t BUF_SIZE = 64; - char time_str[BUF_SIZE]; /* Flawfinder: ignore */ - - strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); - savepath += time_str; - } - recordingLoopSave(savepath); -#endif - - sessionMediaDisconnectSendMessage(mAudioSession); - setState(stateLeavingSession); - } - else - { - LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; - setState(stateSessionTerminated); - } - break; - case stateJoinSessionFailed: - case stateJoinSessionFailedWaiting: - setState(stateSessionTerminated); - break; - - default: - LL_WARNS("Voice") << "called from unknown state" << LL_ENDL; - break; - } - } - else - { - LL_WARNS("Voice") << "called with no active session" << LL_ENDL; - setState(stateSessionTerminated); - } -} - -void LLVoiceClient::sessionTerminateSendMessage(sessionState *session) -{ - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL; - stream - << "" - << "" << session->mHandle << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVoiceClient::sessionGroupTerminateSendMessage(sessionState *session) -{ - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Sending SessionGroup.Terminate with handle " << session->mGroupHandle << LL_ENDL; - stream - << "" - << "" << session->mGroupHandle << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVoiceClient::sessionMediaDisconnectSendMessage(sessionState *session) -{ - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL; - stream - << "" - << "" << session->mGroupHandle << "" - << "" << session->mHandle << "" - << "Audio" - << "\n\n\n"; - - writeString(stream.str()); - -} - -void LLVoiceClient::sessionTextDisconnectSendMessage(sessionState *session) -{ - std::ostringstream stream; - - LL_DEBUGS("Voice") << "Sending Session.TextDisconnect with handle " << session->mHandle << LL_ENDL; - stream - << "" - << "" << session->mGroupHandle << "" - << "" << session->mHandle << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVoiceClient::getCaptureDevicesSendMessage() -{ - std::ostringstream stream; - stream - << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVoiceClient::getRenderDevicesSendMessage() -{ - std::ostringstream stream; - stream - << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVoiceClient::clearCaptureDevices() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - mCaptureDevices.clear(); -} - -void LLVoiceClient::addCaptureDevice(const std::string& name) -{ - LL_DEBUGS("Voice") << name << LL_ENDL; - - mCaptureDevices.push_back(name); -} - -LLVoiceClient::deviceList *LLVoiceClient::getCaptureDevices() -{ - return &mCaptureDevices; -} - -void LLVoiceClient::setCaptureDevice(const std::string& name) -{ - if(name == "Default") - { - if(!mCaptureDevice.empty()) - { - mCaptureDevice.clear(); - mCaptureDeviceDirty = true; - } - } - else - { - if(mCaptureDevice != name) - { - mCaptureDevice = name; - mCaptureDeviceDirty = true; - } - } -} - -void LLVoiceClient::clearRenderDevices() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - mRenderDevices.clear(); -} - -void LLVoiceClient::addRenderDevice(const std::string& name) -{ - LL_DEBUGS("Voice") << name << LL_ENDL; - mRenderDevices.push_back(name); -} - -LLVoiceClient::deviceList *LLVoiceClient::getRenderDevices() -{ - return &mRenderDevices; -} - -void LLVoiceClient::setRenderDevice(const std::string& name) -{ - if(name == "Default") - { - if(!mRenderDevice.empty()) - { - mRenderDevice.clear(); - mRenderDeviceDirty = true; - } - } - else - { - if(mRenderDevice != name) - { - mRenderDevice = name; - mRenderDeviceDirty = true; - } - } - -} - -void LLVoiceClient::tuningStart() -{ - mTuningMode = true; - if(getState() >= stateNoChannel) - { - sessionTerminate(); - } -} - -void LLVoiceClient::tuningStop() -{ - mTuningMode = false; -} - -bool LLVoiceClient::inTuningMode() -{ - bool result = false; - switch(getState()) - { - case stateMicTuningRunning: - result = true; - break; - default: - break; - } - return result; -} - -void LLVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop) -{ - mTuningAudioFile = name; - std::ostringstream stream; - stream - << "" - << "" << mTuningAudioFile << "" - << "" << (loop?"1":"0") << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVoiceClient::tuningRenderStopSendMessage() -{ - std::ostringstream stream; - stream - << "" - << "" << mTuningAudioFile << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVoiceClient::tuningCaptureStartSendMessage(int duration) -{ - LL_DEBUGS("Voice") << "sending CaptureAudioStart" << LL_ENDL; - - std::ostringstream stream; - stream - << "" - << "" << duration << "" - << "\n\n\n"; - - writeString(stream.str()); -} - -void LLVoiceClient::tuningCaptureStopSendMessage() -{ - LL_DEBUGS("Voice") << "sending CaptureAudioStop" << LL_ENDL; - - std::ostringstream stream; - stream - << "" - << "\n\n\n"; - - writeString(stream.str()); - - mTuningEnergy = 0.0f; -} - -void LLVoiceClient::tuningSetMicVolume(float volume) -{ - int scaled_volume = scale_mic_volume(volume); - - if(scaled_volume != mTuningMicVolume) - { - mTuningMicVolume = scaled_volume; - mTuningMicVolumeDirty = true; - } -} - -void LLVoiceClient::tuningSetSpeakerVolume(float volume) -{ - int scaled_volume = scale_speaker_volume(volume); - - if(scaled_volume != mTuningSpeakerVolume) - { - mTuningSpeakerVolume = scaled_volume; - mTuningSpeakerVolumeDirty = true; - } -} - -float LLVoiceClient::tuningGetEnergy(void) -{ - return mTuningEnergy; -} - -bool LLVoiceClient::deviceSettingsAvailable() -{ - bool result = true; - - if(!mConnected) - result = false; - - if(mRenderDevices.empty()) - result = false; - - return result; -} - -void LLVoiceClient::refreshDeviceLists(bool clearCurrentList) -{ - if(clearCurrentList) - { - clearCaptureDevices(); - clearRenderDevices(); - } - getCaptureDevicesSendMessage(); - getRenderDevicesSendMessage(); -} - -void LLVoiceClient::daemonDied() -{ - // The daemon died, so the connection is gone. Reset everything and start over. - LL_WARNS("Voice") << "Connection to vivox daemon lost. Resetting state."<< LL_ENDL; - - // Try to relaunch the daemon - setState(stateDisableCleanup); -} - -void LLVoiceClient::giveUp() -{ - // All has failed. Clean up and stop trying. - closeSocket(); - deleteAllSessions(); - deleteAllBuddies(); - - setState(stateJail); -} - -static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel) -{ - F32 nat[3], nup[3], nl[3], nvel[3]; // the new at, up, left vectors and the new position and velocity - F64 npos[3]; - - // The original XML command was sent like this: - /* - << "" - << "" << pos[VX] << "" - << "" << pos[VZ] << "" - << "" << pos[VY] << "" - << "" - << "" - << "" << mAvatarVelocity[VX] << "" - << "" << mAvatarVelocity[VZ] << "" - << "" << mAvatarVelocity[VY] << "" - << "" - << "" - << "" << l.mV[VX] << "" - << "" << u.mV[VX] << "" - << "" << a.mV[VX] << "" - << "" - << "" - << "" << l.mV[VZ] << "" - << "" << u.mV[VY] << "" - << "" << a.mV[VZ] << "" - << "" - << "" - << "" << l.mV [VY] << "" - << "" << u.mV [VZ] << "" - << "" << a.mV [VY] << "" - << ""; - */ - -#if 1 - // This was the original transform done when building the XML command - nat[0] = left.mV[VX]; - nat[1] = up.mV[VX]; - nat[2] = at.mV[VX]; - - nup[0] = left.mV[VZ]; - nup[1] = up.mV[VY]; - nup[2] = at.mV[VZ]; - - nl[0] = left.mV[VY]; - nl[1] = up.mV[VZ]; - nl[2] = at.mV[VY]; - - npos[0] = pos.mdV[VX]; - npos[1] = pos.mdV[VZ]; - npos[2] = pos.mdV[VY]; - - nvel[0] = vel.mV[VX]; - nvel[1] = vel.mV[VZ]; - nvel[2] = vel.mV[VY]; - - for(int i=0;i<3;++i) { - at.mV[i] = nat[i]; - up.mV[i] = nup[i]; - left.mV[i] = nl[i]; - pos.mdV[i] = npos[i]; - } - - // This was the original transform done in the SDK - nat[0] = at.mV[2]; - nat[1] = 0; // y component of at vector is always 0, this was up[2] - nat[2] = -1 * left.mV[2]; - - // We override whatever the application gives us - nup[0] = 0; // x component of up vector is always 0 - nup[1] = 1; // y component of up vector is always 1 - nup[2] = 0; // z component of up vector is always 0 - - nl[0] = at.mV[0]; - nl[1] = 0; // y component of left vector is always zero, this was up[0] - nl[2] = -1 * left.mV[0]; - - npos[2] = pos.mdV[2] * -1.0; - npos[1] = pos.mdV[1]; - npos[0] = pos.mdV[0]; - - for(int i=0;i<3;++i) { - at.mV[i] = nat[i]; - up.mV[i] = nup[i]; - left.mV[i] = nl[i]; - pos.mdV[i] = npos[i]; - } -#else - // This is the compose of the two transforms (at least, that's what I'm trying for) - nat[0] = at.mV[VX]; - nat[1] = 0; // y component of at vector is always 0, this was up[2] - nat[2] = -1 * up.mV[VZ]; - - // We override whatever the application gives us - nup[0] = 0; // x component of up vector is always 0 - nup[1] = 1; // y component of up vector is always 1 - nup[2] = 0; // z component of up vector is always 0 - - nl[0] = left.mV[VX]; - nl[1] = 0; // y component of left vector is always zero, this was up[0] - nl[2] = -1 * left.mV[VY]; - - npos[0] = pos.mdV[VX]; - npos[1] = pos.mdV[VZ]; - npos[2] = pos.mdV[VY] * -1.0; - - nvel[0] = vel.mV[VX]; - nvel[1] = vel.mV[VZ]; - nvel[2] = vel.mV[VY]; - - for(int i=0;i<3;++i) { - at.mV[i] = nat[i]; - up.mV[i] = nup[i]; - left.mV[i] = nl[i]; - pos.mdV[i] = npos[i]; - } - -#endif -} - -void LLVoiceClient::sendPositionalUpdate(void) -{ - std::ostringstream stream; - - if(mSpatialCoordsDirty) - { - LLVector3 l, u, a, vel; - LLVector3d pos; - - mSpatialCoordsDirty = false; - - // Always send both speaker and listener positions together. - stream << "" - << "" << getAudioSessionHandle() << ""; - - stream << ""; - -// LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL; - l = mAvatarRot.getLeftRow(); - u = mAvatarRot.getUpRow(); - a = mAvatarRot.getFwdRow(); - pos = mAvatarPosition; - vel = mAvatarVelocity; - - // SLIM SDK: the old SDK was doing a transform on the passed coordinates that the new one doesn't do anymore. - // The old transform is replicated by this function. - oldSDKTransform(l, u, a, pos, vel); - - stream - << "" - << "" << pos.mdV[VX] << "" - << "" << pos.mdV[VY] << "" - << "" << pos.mdV[VZ] << "" - << "" - << "" - << "" << vel.mV[VX] << "" - << "" << vel.mV[VY] << "" - << "" << vel.mV[VZ] << "" - << "" - << "" - << "" << a.mV[VX] << "" - << "" << a.mV[VY] << "" - << "" << a.mV[VZ] << "" - << "" - << "" - << "" << u.mV[VX] << "" - << "" << u.mV[VY] << "" - << "" << u.mV[VZ] << "" - << "" - << "" - << "" << l.mV [VX] << "" - << "" << l.mV [VY] << "" - << "" << l.mV [VZ] << "" - << ""; - - stream << ""; - - stream << ""; - - LLVector3d earPosition; - LLVector3 earVelocity; - LLMatrix3 earRot; - - switch(mEarLocation) - { - case earLocCamera: - default: - earPosition = mCameraPosition; - earVelocity = mCameraVelocity; - earRot = mCameraRot; - break; - - case earLocAvatar: - earPosition = mAvatarPosition; - earVelocity = mAvatarVelocity; - earRot = mAvatarRot; - break; - - case earLocMixed: - earPosition = mAvatarPosition; - earVelocity = mAvatarVelocity; - earRot = mCameraRot; - break; - } - - l = earRot.getLeftRow(); - u = earRot.getUpRow(); - a = earRot.getFwdRow(); - pos = earPosition; - vel = earVelocity; - -// LL_DEBUGS("Voice") << "Sending listener position " << earPosition << LL_ENDL; - - oldSDKTransform(l, u, a, pos, vel); - - stream - << "" - << "" << pos.mdV[VX] << "" - << "" << pos.mdV[VY] << "" - << "" << pos.mdV[VZ] << "" - << "" - << "" - << "" << vel.mV[VX] << "" - << "" << vel.mV[VY] << "" - << "" << vel.mV[VZ] << "" - << "" - << "" - << "" << a.mV[VX] << "" - << "" << a.mV[VY] << "" - << "" << a.mV[VZ] << "" - << "" - << "" - << "" << u.mV[VX] << "" - << "" << u.mV[VY] << "" - << "" << u.mV[VZ] << "" - << "" - << "" - << "" << l.mV [VX] << "" - << "" << l.mV [VY] << "" - << "" << l.mV [VZ] << "" - << ""; - - - stream << ""; - - stream << "\n\n\n"; - } - - if(mAudioSession && (mAudioSession->mVolumeDirty || mAudioSession->mMuteDirty)) - { - participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); - - mAudioSession->mVolumeDirty = false; - mAudioSession->mMuteDirty = false; - - for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) - { - participantState *p = iter->second; - - if(p->mVolumeDirty) - { - // Can't set volume/mute for yourself - if(!p->mIsSelf) - { - // scale from the range 0.0-1.0 to vivox volume in the range 0-100 - S32 volume = llround(p->mVolume / VOLUME_SCALE_VIVOX); - - bool mute = p->mOnMuteList; - - if(mute) - { - // SetParticipantMuteForMe doesn't work in p2p sessions. - // If we want the user to be muted, set their volume to 0 as well. - // This isn't perfect, but it will at least reduce their volume to a minimum. - volume = 0; - - // Mark the current volume level as set to prevent incoming events - // changing it to 0, so that we can return to it when unmuting. - p->mVolumeSet = true; - } - - if(volume == 0) - { - mute = true; - } - - LL_DEBUGS("Voice") << "Setting volume/mute for avatar " << p->mAvatarID << " to " << volume << (mute?"/true":"/false") << LL_ENDL; - - // SLIM SDK: Send both volume and mute commands. - - // Send a "volume for me" command for the user. - stream << "" - << "" << getAudioSessionHandle() << "" - << "" << p->mURI << "" - << "" << volume << "" - << "\n\n\n"; - - if(!mAudioSession->mIsP2P) - { - // Send a "mute for me" command for the user - // Doesn't work in P2P sessions - stream << "" - << "" << getAudioSessionHandle() << "" - << "" << p->mURI << "" - << "" << (mute?"1":"0") << "" - << "Audio" - << "\n\n\n"; - } - } - - p->mVolumeDirty = false; - } - } - } - - buildLocalAudioUpdates(stream); - - if(!stream.str().empty()) - { - writeString(stream.str()); - } - - // Friends list updates can be huge, especially on the first voice login of an account with lots of friends. - // Batching them all together can choke SLVoice, so send them in separate writes. - sendFriendsListUpdates(); -} - -void LLVoiceClient::buildSetCaptureDevice(std::ostringstream &stream) -{ - if(mCaptureDeviceDirty) - { - LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL; - - stream - << "" - << "" << mCaptureDevice << "" - << "" - << "\n\n\n"; - - mCaptureDeviceDirty = false; - } -} - -void LLVoiceClient::buildSetRenderDevice(std::ostringstream &stream) -{ - if(mRenderDeviceDirty) - { - LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL; - - stream - << "" - << "" << mRenderDevice << "" - << "" - << "\n\n\n"; - mRenderDeviceDirty = false; - } -} - -void LLVoiceClient::buildLocalAudioUpdates(std::ostringstream &stream) -{ - buildSetCaptureDevice(stream); - - buildSetRenderDevice(stream); - - if(mPTTDirty) - { - mPTTDirty = false; - - // Send a local mute command. - // NOTE that the state of "PTT" is the inverse of "local mute". - // (i.e. when PTT is true, we send a mute command with "false", and vice versa) - - LL_DEBUGS("Voice") << "Sending MuteLocalMic command with parameter " << (mPTT?"false":"true") << LL_ENDL; - - stream << "" - << "" << mConnectorHandle << "" - << "" << (mPTT?"false":"true") << "" - << "\n\n\n"; - - } - - if(mSpeakerMuteDirty) - { - const char *muteval = ((mSpeakerVolume <= scale_speaker_volume(0))?"true":"false"); - - mSpeakerMuteDirty = false; - - LL_INFOS("Voice") << "Setting speaker mute to " << muteval << LL_ENDL; - - stream << "" - << "" << mConnectorHandle << "" - << "" << muteval << "" - << "\n\n\n"; - - } - - if(mSpeakerVolumeDirty) - { - mSpeakerVolumeDirty = false; - - LL_INFOS("Voice") << "Setting speaker volume to " << mSpeakerVolume << LL_ENDL; - - stream << "" - << "" << mConnectorHandle << "" - << "" << mSpeakerVolume << "" - << "\n\n\n"; - - } - - if(mMicVolumeDirty) - { - mMicVolumeDirty = false; - - LL_INFOS("Voice") << "Setting mic volume to " << mMicVolume << LL_ENDL; - - stream << "" - << "" << mConnectorHandle << "" - << "" << mMicVolume << "" - << "\n\n\n"; - } - - -} - -void LLVoiceClient::checkFriend(const LLUUID& id) -{ - std::string name; - buddyListEntry *buddy = findBuddy(id); - - // Make sure we don't add a name before it's been looked up. - if(gCacheName->getFullName(id, name)) - { - - const LLRelationship* relationInfo = LLAvatarTracker::instance().getBuddyInfo(id); - bool canSeeMeOnline = false; - if(relationInfo && relationInfo->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)) - canSeeMeOnline = true; - - // When we get here, mNeedsSend is true and mInSLFriends is false. Change them as necessary. - - if(buddy) - { - // This buddy is already in both lists. - - if(name != buddy->mDisplayName) - { - // The buddy is in the list with the wrong name. Update it with the correct name. - LL_WARNS("Voice") << "Buddy " << id << " has wrong name (\"" << buddy->mDisplayName << "\" should be \"" << name << "\"), updating."<< LL_ENDL; - buddy->mDisplayName = name; - buddy->mNeedsNameUpdate = true; // This will cause the buddy to be resent. - } - } - else - { - // This buddy was not in the vivox list, needs to be added. - buddy = addBuddy(sipURIFromID(id), name); - buddy->mUUID = id; - } - - // In all the above cases, the buddy is in the SL friends list (which is how we got here). - buddy->mInSLFriends = true; - buddy->mCanSeeMeOnline = canSeeMeOnline; - buddy->mNameResolved = true; - - } - else - { - // This name hasn't been looked up yet. Don't do anything with this buddy list entry until it has. - if(buddy) - { - buddy->mNameResolved = false; - } - - // Initiate a lookup. - // The "lookup completed" callback will ensure that the friends list is rechecked after it completes. - lookupName(id); - } -} - -void LLVoiceClient::clearAllLists() -{ - // FOR TESTING ONLY - - // This will send the necessary commands to delete ALL buddies, autoaccept rules, and block rules SLVoice tells us about. - buddyListMap::iterator buddy_it; - for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end();) - { - buddyListEntry *buddy = buddy_it->second; - buddy_it++; - - std::ostringstream stream; - - if(buddy->mInVivoxBuddies) - { - // delete this entry from the vivox buddy list - buddy->mInVivoxBuddies = false; - LL_DEBUGS("Voice") << "delete " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL; - stream << "" - << "" << mAccountHandle << "" - << "" << buddy->mURI << "" - << "\n\n\n"; - } - - if(buddy->mHasBlockListEntry) - { - // Delete the associated block list entry (so the block list doesn't fill up with junk) - buddy->mHasBlockListEntry = false; - stream << "" - << "" << mAccountHandle << "" - << "" << buddy->mURI << "" - << "\n\n\n"; - } - if(buddy->mHasAutoAcceptListEntry) - { - // Delete the associated auto-accept list entry (so the auto-accept list doesn't fill up with junk) - buddy->mHasAutoAcceptListEntry = false; - stream << "" - << "" << mAccountHandle << "" - << "" << buddy->mURI << "" - << "\n\n\n"; - } - - writeString(stream.str()); - - } -} - -void LLVoiceClient::sendFriendsListUpdates() -{ - if(mBuddyListMapPopulated && mBlockRulesListReceived && mAutoAcceptRulesListReceived && mFriendsListDirty) - { - mFriendsListDirty = false; - - if(0) - { - // FOR TESTING ONLY -- clear all buddy list, block list, and auto-accept list entries. - clearAllLists(); - return; - } - - LL_INFOS("Voice") << "Checking vivox buddy list against friends list..." << LL_ENDL; - - buddyListMap::iterator buddy_it; - for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++) - { - // reset the temp flags in the local buddy list - buddy_it->second->mInSLFriends = false; - } - - // correlate with the friends list - { - LLCollectAllBuddies collect; - LLAvatarTracker::instance().applyFunctor(collect); - LLCollectAllBuddies::buddy_map_t::const_iterator it = collect.mOnline.begin(); - LLCollectAllBuddies::buddy_map_t::const_iterator end = collect.mOnline.end(); - - for ( ; it != end; ++it) - { - checkFriend(it->second); - } - it = collect.mOffline.begin(); - end = collect.mOffline.end(); - for ( ; it != end; ++it) - { - checkFriend(it->second); - } - } - - LL_INFOS("Voice") << "Sending friend list updates..." << LL_ENDL; - - for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end();) - { - buddyListEntry *buddy = buddy_it->second; - buddy_it++; - - // Ignore entries that aren't resolved yet. - if(buddy->mNameResolved) - { - std::ostringstream stream; - - if(buddy->mInSLFriends && (!buddy->mInVivoxBuddies || buddy->mNeedsNameUpdate)) - { - if(mNumberOfAliases > 0) - { - // Add (or update) this entry in the vivox buddy list - buddy->mInVivoxBuddies = true; - buddy->mNeedsNameUpdate = false; - LL_DEBUGS("Voice") << "add/update " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL; - stream - << "" - << "" << mAccountHandle << "" - << "" << buddy->mURI << "" - << "" << buddy->mDisplayName << "" - << "" // Without this, SLVoice doesn't seem to parse the command. - << "0" - << "\n\n\n"; - } - } - else if(!buddy->mInSLFriends) - { - // This entry no longer exists in your SL friends list. Remove all traces of it from the Vivox buddy list. - if(buddy->mInVivoxBuddies) - { - // delete this entry from the vivox buddy list - buddy->mInVivoxBuddies = false; - LL_DEBUGS("Voice") << "delete " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL; - stream << "" - << "" << mAccountHandle << "" - << "" << buddy->mURI << "" - << "\n\n\n"; - } - - if(buddy->mHasBlockListEntry) - { - // Delete the associated block list entry, if any - buddy->mHasBlockListEntry = false; - stream << "" - << "" << mAccountHandle << "" - << "" << buddy->mURI << "" - << "\n\n\n"; - } - if(buddy->mHasAutoAcceptListEntry) - { - // Delete the associated auto-accept list entry, if any - buddy->mHasAutoAcceptListEntry = false; - stream << "" - << "" << mAccountHandle << "" - << "" << buddy->mURI << "" - << "\n\n\n"; - } - } - - if(buddy->mInSLFriends) - { - - if(buddy->mCanSeeMeOnline) - { - // Buddy should not be blocked. - - // If this buddy doesn't already have either a block or autoaccept list entry, we'll update their status when we receive a SubscriptionEvent. - - // If the buddy has a block list entry, delete it. - if(buddy->mHasBlockListEntry) - { - buddy->mHasBlockListEntry = false; - stream << "" - << "" << mAccountHandle << "" - << "" << buddy->mURI << "" - << "\n\n\n"; - - - // If we just deleted a block list entry, add an auto-accept entry. - if(!buddy->mHasAutoAcceptListEntry) - { - buddy->mHasAutoAcceptListEntry = true; - stream << "" - << "" << mAccountHandle << "" - << "" << buddy->mURI << "" - << "0" - << "\n\n\n"; - } - } - } - else - { - // Buddy should be blocked. - - // If this buddy doesn't already have either a block or autoaccept list entry, we'll update their status when we receive a SubscriptionEvent. - - // If this buddy has an autoaccept entry, delete it - if(buddy->mHasAutoAcceptListEntry) - { - buddy->mHasAutoAcceptListEntry = false; - stream << "" - << "" << mAccountHandle << "" - << "" << buddy->mURI << "" - << "\n\n\n"; - - // If we just deleted an auto-accept entry, add a block list entry. - if(!buddy->mHasBlockListEntry) - { - buddy->mHasBlockListEntry = true; - stream << "" - << "" << mAccountHandle << "" - << "" << buddy->mURI << "" - << "1" - << "\n\n\n"; - } - } - } - - if(!buddy->mInSLFriends && !buddy->mInVivoxBuddies) - { - // Delete this entry from the local buddy list. This should NOT invalidate the iterator, - // since it has already been incremented to the next entry. - deleteBuddy(buddy->mURI); - } - - } - writeString(stream.str()); - } - } - } -} - -///////////////////////////// -// Response/Event handlers - -void LLVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID) -{ - if(statusCode != 0) - { - LL_WARNS("Voice") << "Connector.Create response failure: " << statusString << LL_ENDL; - setState(stateConnectorFailed); - } - else - { - // Connector created, move forward. - LL_INFOS("Voice") << "Connector.Create succeeded, Vivox SDK version is " << versionID << LL_ENDL; - mAPIVersion = versionID; - mConnectorHandle = connectorHandle; - if(getState() == stateConnectorStarting) - { - setState(stateConnectorStarted); - } - } -} - -void LLVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases) -{ - LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL; - - // Status code of 20200 means "bad password". We may want to special-case that at some point. - - if ( statusCode == 401 ) - { - // Login failure which is probably caused by the delay after a user's password being updated. - LL_INFOS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; - setState(stateLoginRetry); - } - else if(statusCode != 0) - { - LL_WARNS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; - setState(stateLoginFailed); - } - else - { - // Login succeeded, move forward. - mAccountHandle = accountHandle; - mNumberOfAliases = numberOfAliases; - // This needs to wait until the AccountLoginStateChangeEvent is received. -// if(getState() == stateLoggingIn) -// { -// setState(stateLoggedIn); -// } - } -} - -void LLVoiceClient::sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) -{ - sessionState *session = findSessionBeingCreatedByURI(requestId); - - if(session) - { - session->mCreateInProgress = false; - } - - if(statusCode != 0) - { - LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL; - if(session) - { - session->mErrorStatusCode = statusCode; - session->mErrorStatusString = statusString; - if(session == mAudioSession) - { - setState(stateJoinSessionFailed); - } - else - { - reapSession(session); - } - } - } - else - { - LL_INFOS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL; - if(session) - { - setSessionHandle(session, sessionHandle); - } - } -} - -void LLVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) -{ - sessionState *session = findSessionBeingCreatedByURI(requestId); - - if(session) - { - session->mCreateInProgress = false; - } - - if(statusCode != 0) - { - LL_WARNS("Voice") << "SessionGroup.AddSession response failure (" << statusCode << "): " << statusString << LL_ENDL; - if(session) - { - session->mErrorStatusCode = statusCode; - session->mErrorStatusString = statusString; - if(session == mAudioSession) - { - setState(stateJoinSessionFailed); - } - else - { - reapSession(session); - } - } - } - else - { - LL_DEBUGS("Voice") << "SessionGroup.AddSession response received (success), session handle is " << sessionHandle << LL_ENDL; - if(session) - { - setSessionHandle(session, sessionHandle); - } - } -} - -void LLVoiceClient::sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString) -{ - sessionState *session = findSession(requestId); - if(statusCode != 0) - { - LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL; - if(session) - { - session->mMediaConnectInProgress = false; - session->mErrorStatusCode = statusCode; - session->mErrorStatusString = statusString; - if(session == mAudioSession) - setState(stateJoinSessionFailed); - } - } - else - { - LL_DEBUGS("Voice") << "Session.Connect response received (success)" << LL_ENDL; - } -} - -void LLVoiceClient::logoutResponse(int statusCode, std::string &statusString) -{ - if(statusCode != 0) - { - LL_WARNS("Voice") << "Account.Logout response failure: " << statusString << LL_ENDL; - // Should this ever fail? do we care if it does? - } -} - -void LLVoiceClient::connectorShutdownResponse(int statusCode, std::string &statusString) -{ - if(statusCode != 0) - { - LL_WARNS("Voice") << "Connector.InitiateShutdown response failure: " << statusString << LL_ENDL; - // Should this ever fail? do we care if it does? - } - - mConnected = false; - - if(getState() == stateConnectorStopping) - { - setState(stateConnectorStopped); - } -} - -void LLVoiceClient::sessionAddedEvent( - std::string &uriString, - std::string &alias, - std::string &sessionHandle, - std::string &sessionGroupHandle, - bool isChannel, - bool incoming, - std::string &nameString, - std::string &applicationString) -{ - sessionState *session = NULL; - - LL_INFOS("Voice") << "session " << uriString << ", alias " << alias << ", name " << nameString << " handle " << sessionHandle << LL_ENDL; - - session = addSession(uriString, sessionHandle); - if(session) - { - session->mGroupHandle = sessionGroupHandle; - session->mIsChannel = isChannel; - session->mIncoming = incoming; - session->mAlias = alias; - - // Generate a caller UUID -- don't need to do this for channels - if(!session->mIsChannel) - { - if(IDFromName(session->mSIPURI, session->mCallerID)) - { - // Normal URI(base64-encoded UUID) - } - else if(!session->mAlias.empty() && IDFromName(session->mAlias, session->mCallerID)) - { - // Wrong URI, but an alias is available. Stash the incoming URI as an alternate - session->mAlternateSIPURI = session->mSIPURI; - - // and generate a proper URI from the ID. - setSessionURI(session, sipURIFromID(session->mCallerID)); - } - else - { - LL_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL; - setUUIDFromStringHash(session->mCallerID, session->mSIPURI); - session->mSynthesizedCallerID = true; - - // Can't look up the name in this case -- we have to extract it from the URI. - std::string namePortion = nameFromsipURI(session->mSIPURI); - if(namePortion.empty()) - { - // Didn't seem to be a SIP URI, just use the whole provided name. - namePortion = nameString; - } - - // Some incoming names may be separated with an underscore instead of a space. Fix this. - LLStringUtil::replaceChar(namePortion, '_', ' '); - - // Act like we just finished resolving the name (this stores it in all the right places) - avatarNameResolved(session->mCallerID, namePortion); - } - - LL_INFOS("Voice") << "caller ID: " << session->mCallerID << LL_ENDL; - - if(!session->mSynthesizedCallerID) - { - // If we got here, we don't have a proper name. Initiate a lookup. - lookupName(session->mCallerID); - } - } - } -} - -void LLVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle) -{ - LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL; - -#if USE_SESSION_GROUPS - if(mMainSessionGroupHandle.empty()) - { - // This is the first (i.e. "main") session group. Save its handle. - mMainSessionGroupHandle = sessionGroupHandle; - } - else - { - LL_DEBUGS("Voice") << "Already had a session group handle " << mMainSessionGroupHandle << LL_ENDL; - } -#endif -} - -void LLVoiceClient::joinedAudioSession(sessionState *session) -{ - if(mAudioSession != session) - { - sessionState *oldSession = mAudioSession; - - mAudioSession = session; - mAudioSessionChanged = true; - - // The old session may now need to be deleted. - reapSession(oldSession); - } - - // This is the session we're joining. - if(getState() == stateJoiningSession) - { - setState(stateSessionJoined); - - // SLIM SDK: we don't always receive a participant state change for ourselves when joining a channel now. - // Add the current user as a participant here. - participantState *participant = session->addParticipant(sipURIFromName(mAccountName)); - if(participant) - { - participant->mIsSelf = true; - lookupName(participant->mAvatarID); - - LL_INFOS("Voice") << "added self as participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - } - - if(!session->mIsChannel) - { - // this is a p2p session. Make sure the other end is added as a participant. - participantState *participant = session->addParticipant(session->mSIPURI); - if(participant) - { - if(participant->mAvatarIDValid) - { - lookupName(participant->mAvatarID); - } - else if(!session->mName.empty()) - { - participant->mDisplayName = session->mName; - avatarNameResolved(participant->mAvatarID, session->mName); - } - - // TODO: Question: Do we need to set up mAvatarID/mAvatarIDValid here? - LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - } - } - } -} - -void LLVoiceClient::sessionRemovedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle) -{ - LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL; - - sessionState *session = findSession(sessionHandle); - if(session) - { - leftAudioSession(session); - - // This message invalidates the session's handle. Set it to empty. - setSessionHandle(session); - - // This also means that the session's session group is now empty. - // Terminate the session group so it doesn't leak. - sessionGroupTerminateSendMessage(session); - - // Reset the media state (we now have no info) - session->mMediaStreamState = streamStateUnknown; - session->mTextStreamState = streamStateUnknown; - - // Conditionally delete the session - reapSession(session); - } - else - { - LL_WARNS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL; - } -} - -void LLVoiceClient::reapSession(sessionState *session) -{ - if(session) - { - if(!session->mHandle.empty()) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (non-null session handle)" << LL_ENDL; - } - else if(session->mCreateInProgress) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (create in progress)" << LL_ENDL; - } - else if(session->mMediaConnectInProgress) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (connect in progress)" << LL_ENDL; - } - else if(session == mAudioSession) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the current session)" << LL_ENDL; - } - else if(session == mNextAudioSession) - { - LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the next session)" << LL_ENDL; - } - else - { - // TODO: Question: Should we check for queued text messages here? - // We don't have a reason to keep tracking this session, so just delete it. - LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL; - deleteSession(session); - session = NULL; - } - } - else - { -// LL_DEBUGS("Voice") << "session is NULL" << LL_ENDL; - } -} - -// Returns true if the session seems to indicate we've moved to a region on a different voice server -bool LLVoiceClient::sessionNeedsRelog(sessionState *session) -{ - bool result = false; - - if(session != NULL) - { - // Only make this check for spatial channels (so it won't happen for group or p2p calls) - if(session->mIsSpatial) - { - std::string::size_type atsign; - - atsign = session->mSIPURI.find("@"); - - if(atsign != std::string::npos) - { - std::string urihost = session->mSIPURI.substr(atsign + 1); - if(stricmp(urihost.c_str(), mVoiceSIPURIHostName.c_str())) - { - // The hostname in this URI is different from what we expect. This probably means we need to relog. - - // We could make a ProvisionVoiceAccountRequest and compare the result with the current values of - // mVoiceSIPURIHostName and mVoiceAccountServerURI to be really sure, but this is a pretty good indicator. - - result = true; - } - } - } - } - - return result; -} - -void LLVoiceClient::leftAudioSession( - sessionState *session) -{ - if(mAudioSession == session) - { - switch(getState()) - { - case stateJoiningSession: - case stateSessionJoined: - case stateRunning: - case stateLeavingSession: - case stateJoinSessionFailed: - case stateJoinSessionFailedWaiting: - // normal transition - LL_DEBUGS("Voice") << "left session " << session->mHandle << " in state " << state2string(getState()) << LL_ENDL; - setState(stateSessionTerminated); - break; - - case stateSessionTerminated: - // this will happen sometimes -- there are cases where we send the terminate and then go straight to this state. - LL_WARNS("Voice") << "left session " << session->mHandle << " in state " << state2string(getState()) << LL_ENDL; - break; - - default: - LL_WARNS("Voice") << "unexpected SessionStateChangeEvent (left session) in state " << state2string(getState()) << LL_ENDL; - setState(stateSessionTerminated); - break; - } - } -} - -void LLVoiceClient::accountLoginStateChangeEvent( - std::string &accountHandle, - int statusCode, - std::string &statusString, - int state) -{ - LL_DEBUGS("Voice") << "state is " << state << LL_ENDL; - /* - According to Mike S., status codes for this event are: - login_state_logged_out=0, - login_state_logged_in = 1, - login_state_logging_in = 2, - login_state_logging_out = 3, - login_state_resetting = 4, - login_state_error=100 - */ - - switch(state) - { - case 1: - if(getState() == stateLoggingIn) - { - setState(stateLoggedIn); - } - break; - - case 3: - // The user is in the process of logging out. - setState(stateLoggingOut); - break; - - case 0: - // The user has been logged out. - setState(stateLoggedOut); - break; - - default: - //Used to be a commented out warning - LL_DEBUGS("Voice") << "unknown state: " << state << LL_ENDL; - break; - } -} - -void LLVoiceClient::mediaStreamUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - int statusCode, - std::string &statusString, - int state, - bool incoming) -{ - sessionState *session = findSession(sessionHandle); - - LL_DEBUGS("Voice") << "session " << sessionHandle << ", status code " << statusCode << ", string \"" << statusString << "\"" << LL_ENDL; - - if(session) - { - // We know about this session - - // Save the state for later use - session->mMediaStreamState = state; - - switch(statusCode) - { - case 0: - case 200: - // generic success - // Don't change the saved error code (it may have been set elsewhere) - break; - default: - // save the status code for later - session->mErrorStatusCode = statusCode; - break; - } - - switch(state) - { - case streamStateIdle: - // Standard "left audio session" - session->mVoiceEnabled = false; - session->mMediaConnectInProgress = false; - leftAudioSession(session); - break; - - case streamStateConnected: - session->mVoiceEnabled = true; - session->mMediaConnectInProgress = false; - joinedAudioSession(session); - break; - - case streamStateRinging: - if(incoming) - { - // Send the voice chat invite to the GUI layer - // *TODO: Question: Should we correlate with the mute list here? - session->mIMSessionID = LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, session->mCallerID); - session->mVoiceInvitePending = true; - if(session->mName.empty()) - { - lookupName(session->mCallerID); - } - else - { - // Act like we just finished resolving the name - avatarNameResolved(session->mCallerID, session->mName); - } - } - break; - - default: - LL_WARNS("Voice") << "unknown state " << state << LL_ENDL; - break; - - } - - } - else - { - LL_WARNS("Voice") << "session " << sessionHandle << "not found"<< LL_ENDL; - } -} - -void LLVoiceClient::textStreamUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - bool enabled, - int state, - bool incoming) -{ - sessionState *session = findSession(sessionHandle); - - if(session) - { - // Save the state for later use - session->mTextStreamState = state; - - // We know about this session - switch(state) - { - case 0: // We see this when the text stream closes - LL_DEBUGS("Voice") << "stream closed" << LL_ENDL; - break; - - case 1: // We see this on an incoming call from the Connector - // Try to send any text messages queued for this session. - sendQueuedTextMessages(session); - - // Send the text chat invite to the GUI layer - // TODO: Question: Should we correlate with the mute list here? - session->mTextInvitePending = true; - if(session->mName.empty()) - { - lookupName(session->mCallerID); - } - else - { - // Act like we just finished resolving the name - avatarNameResolved(session->mCallerID, session->mName); - } - break; - - default: - LL_WARNS("Voice") << "unknown state " << state << LL_ENDL; - break; - - } - } -} - -void LLVoiceClient::participantAddedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - std::string &nameString, - std::string &displayNameString, - int participantType) -{ - sessionState *session = findSession(sessionHandle); - if(session) - { - participantState *participant = session->addParticipant(uriString); - if(participant) - { - participant->mAccountName = nameString; - - LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName - << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; - - if(participant->mAvatarIDValid) - { - // Initiate a lookup - lookupName(participant->mAvatarID); - } - else - { - // If we don't have a valid avatar UUID, we need to fill in the display name to make the active speakers floater work. - std::string namePortion = nameFromsipURI(uriString); - if(namePortion.empty()) - { - // Problem with the SIP URI, fall back to the display name - namePortion = displayNameString; - } - if(namePortion.empty()) - { - // Problems with both of the above, fall back to the account name - namePortion = nameString; - } - - // Set the display name (which is a hint to the active speakers window not to do its own lookup) - participant->mDisplayName = namePortion; - avatarNameResolved(participant->mAvatarID, namePortion); - } - } - } -} - -void LLVoiceClient::participantRemovedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - std::string &nameString) -{ - sessionState *session = findSession(sessionHandle); - if(session) - { - participantState *participant = session->findParticipant(uriString); - if(participant) - { - session->removeParticipant(participant); - } - else - { - LL_DEBUGS("Voice") << "unknown participant " << uriString << LL_ENDL; - } - } - else - { - LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; - } -} - - -void LLVoiceClient::participantUpdatedEvent( - std::string &sessionHandle, - std::string &sessionGroupHandle, - std::string &uriString, - std::string &alias, - bool isModeratorMuted, - bool isSpeaking, - int volume, - F32 energy) -{ - sessionState *session = findSession(sessionHandle); - if(session) - { - participantState *participant = session->findParticipant(uriString); - - if(participant) - { - participant->mIsSpeaking = isSpeaking; - participant->mIsModeratorMuted = isModeratorMuted; - - // SLIM SDK: convert range: ensure that energy is set to zero if is_speaking is false - if (isSpeaking) - { - participant->mSpeakingTimeout.reset(); - participant->mPower = energy; - } - else - { - participant->mPower = 0.0f; - } - - // Ignore incoming volume level if it has been explicitly set, or there - // is a volume or mute change pending. - if ( !participant->mVolumeSet && !participant->mVolumeDirty) - { - participant->mVolume = (F32)volume * VOLUME_SCALE_VIVOX; - } - - // *HACK: mantipov: added while working on EXT-3544 - /* - Sometimes LLVoiceClient::participantUpdatedEvent callback is called BEFORE - LLViewerChatterBoxSessionAgentListUpdates::post() sometimes AFTER. - - participantUpdatedEvent updates voice participant state in particular participantState::mIsModeratorMuted - Originally we wanted to update session Speaker Manager to fire LLSpeakerVoiceModerationEvent to fix the EXT-3544 bug. - Calling of the LLSpeakerMgr::update() method was added into LLIMMgr::processAgentListUpdates. - - But in case participantUpdatedEvent() is called after LLViewerChatterBoxSessionAgentListUpdates::post() - voice participant mIsModeratorMuted is changed after speakers are updated in Speaker Manager - and event is not fired. - - So, we have to call LLSpeakerMgr::update() here. In any case it is better than call it - in LLCallFloater::draw() - */ - LLVoiceChannel* voice_cnl = LLVoiceChannel::getCurrentVoiceChannel(); - - // ignore session ID of local chat - if (voice_cnl && voice_cnl->getSessionID().notNull()) - { - LLSpeakerMgr* speaker_manager = LLIMModel::getInstance()->getSpeakerManager(voice_cnl->getSessionID()); - if (speaker_manager) - { - speaker_manager->update(true); - } - } - } - else - { - LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL; - } - } - else - { - LL_INFOS("Voice") << "unknown session " << sessionHandle << LL_ENDL; - } -} - -void LLVoiceClient::buddyPresenceEvent( - std::string &uriString, - std::string &alias, - std::string &statusString, - std::string &applicationString) -{ - buddyListEntry *buddy = findBuddy(uriString); - - if(buddy) - { - LL_DEBUGS("Voice") << "Presence event for " << buddy->mDisplayName << " status \"" << statusString << "\", application \"" << applicationString << "\""<< LL_ENDL; - LL_DEBUGS("Voice") << "before: mOnlineSL = " << (buddy->mOnlineSL?"true":"false") << ", mOnlineSLim = " << (buddy->mOnlineSLim?"true":"false") << LL_ENDL; - - if(applicationString.empty()) - { - // This presence event is from a client that doesn't set up the Application string. Do things the old-skool way. - // NOTE: this will be needed to support people who aren't on the 3010-class SDK yet. - - if ( stricmp("Unknown", statusString.c_str())== 0) - { - // User went offline with a non-SLim-enabled viewer. - buddy->mOnlineSL = false; - } - else if ( stricmp("Online", statusString.c_str())== 0) - { - // User came online with a non-SLim-enabled viewer. - buddy->mOnlineSL = true; - } - else - { - // If the user is online through SLim, their status will be "Online-slc", "Away", or something else. - // NOTE: we should never see this unless someone is running an OLD version of SLim -- the versions that should be in use now all set the application string. - buddy->mOnlineSLim = true; - } - } - else if(applicationString.find("SecondLifeViewer") != std::string::npos) - { - // This presence event is from a viewer that sets the application string - if ( stricmp("Unknown", statusString.c_str())== 0) - { - // Viewer says they're offline - buddy->mOnlineSL = false; - } - else - { - // Viewer says they're online - buddy->mOnlineSL = true; - } - } - else - { - // This presence event is from something which is NOT the SL viewer (assume it's SLim). - if ( stricmp("Unknown", statusString.c_str())== 0) - { - // SLim says they're offline - buddy->mOnlineSLim = false; - } - else - { - // SLim says they're online - buddy->mOnlineSLim = true; - } - } - - LL_DEBUGS("Voice") << "after: mOnlineSL = " << (buddy->mOnlineSL?"true":"false") << ", mOnlineSLim = " << (buddy->mOnlineSLim?"true":"false") << LL_ENDL; - - // HACK -- increment the internal change serial number in the LLRelationship (without changing the actual status), so the UI notices the change. - LLAvatarTracker::instance().setBuddyOnline(buddy->mUUID,LLAvatarTracker::instance().isBuddyOnline(buddy->mUUID)); - - notifyFriendObservers(); - } - else - { - LL_DEBUGS("Voice") << "Presence for unknown buddy " << uriString << LL_ENDL; - } -} - -void LLVoiceClient::messageEvent( - std::string &sessionHandle, - std::string &uriString, - std::string &alias, - std::string &messageHeader, - std::string &messageBody, - std::string &applicationString) -{ - LL_DEBUGS("Voice") << "Message event, session " << sessionHandle << " from " << uriString << LL_ENDL; -// LL_DEBUGS("Voice") << " header " << messageHeader << ", body: \n" << messageBody << LL_ENDL; - - if(messageHeader.find("text/html") != std::string::npos) - { - std::string message; - - { - const std::string startMarker = ", try looking for a instead. - start = messageBody.find(startSpan); - start = messageBody.find(startMarker2, start); - end = messageBody.find(endSpan); - - if(start != std::string::npos) - { - start += startMarker2.size(); - - if(end != std::string::npos) - end -= start; - - message.assign(messageBody, start, end); - } - } - } - -// LL_DEBUGS("Voice") << " raw message = \n" << message << LL_ENDL; - - // strip formatting tags - { - std::string::size_type start; - std::string::size_type end; - - while((start = message.find('<')) != std::string::npos) - { - if((end = message.find('>', start + 1)) != std::string::npos) - { - // Strip out the tag - message.erase(start, (end + 1) - start); - } - else - { - // Avoid an infinite loop - break; - } - } - } - - // Decode ampersand-escaped chars - { - std::string::size_type mark = 0; - - // The text may contain text encoded with <, >, and & - mark = 0; - while((mark = message.find("<", mark)) != std::string::npos) - { - message.replace(mark, 4, "<"); - mark += 1; - } - - mark = 0; - while((mark = message.find(">", mark)) != std::string::npos) - { - message.replace(mark, 4, ">"); - mark += 1; - } - - mark = 0; - while((mark = message.find("&", mark)) != std::string::npos) - { - message.replace(mark, 5, "&"); - mark += 1; - } - } - - // strip leading/trailing whitespace (since we always seem to get a couple newlines) - LLStringUtil::trim(message); - -// LL_DEBUGS("Voice") << " stripped message = \n" << message << LL_ENDL; - - sessionState *session = findSession(sessionHandle); - if(session) - { - bool is_busy = gAgent.getBusy(); - bool is_muted = LLMuteList::getInstance()->isMuted(session->mCallerID, session->mName, LLMute::flagTextChat); - bool is_linden = LLMuteList::getInstance()->isLinden(session->mName); - bool quiet_chat = false; - LLChat chat; - - chat.mMuted = is_muted && !is_linden; - - if(!chat.mMuted) - { - chat.mFromID = session->mCallerID; - chat.mFromName = session->mName; - chat.mSourceType = CHAT_SOURCE_AGENT; - - if(is_busy && !is_linden) - { - quiet_chat = true; - // TODO: Question: Return busy mode response here? Or maybe when session is started instead? - } - - LL_DEBUGS("Voice") << "adding message, name " << session->mName << " session " << session->mIMSessionID << ", target " << session->mCallerID << LL_ENDL; - gIMMgr->addMessage(session->mIMSessionID, - session->mCallerID, - session->mName.c_str(), - message.c_str(), - LLStringUtil::null, // default arg - IM_NOTHING_SPECIAL, // default arg - 0, // default arg - LLUUID::null, // default arg - LLVector3::zero, // default arg - true); // prepend name and make it a link to the user's profile - } - } - } -} - -void LLVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType) -{ - sessionState *session = findSession(sessionHandle); - - if(session) - { - participantState *participant = session->findParticipant(uriString); - if(participant) - { - if (!stricmp(notificationType.c_str(), "Typing")) - { - // Other end started typing - // TODO: The proper way to add a typing notification seems to be LLIMMgr::processIMTypingStart(). - // It requires an LLIMInfo for the message, which we don't have here. - } - else if (!stricmp(notificationType.c_str(), "NotTyping")) - { - // Other end stopped typing - // TODO: The proper way to remove a typing notification seems to be LLIMMgr::processIMTypingStop(). - // It requires an LLIMInfo for the message, which we don't have here. - } - else - { - LL_DEBUGS("Voice") << "Unknown notification type " << notificationType << "for participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; - } - } - else - { - LL_DEBUGS("Voice") << "Unknown participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; - } - } - else - { - LL_DEBUGS("Voice") << "Unknown session handle " << sessionHandle << LL_ENDL; - } -} - -void LLVoiceClient::subscriptionEvent(std::string &buddyURI, std::string &subscriptionHandle, std::string &alias, std::string &displayName, std::string &applicationString, std::string &subscriptionType) -{ - buddyListEntry *buddy = findBuddy(buddyURI); - - if(!buddy) - { - // Couldn't find buddy by URI, try converting the alias... - if(!alias.empty()) - { - LLUUID id; - if(IDFromName(alias, id)) - { - buddy = findBuddy(id); - } - } - } - - if(buddy) - { - std::ostringstream stream; - - if(buddy->mCanSeeMeOnline) - { - // Sending the response will create an auto-accept rule - buddy->mHasAutoAcceptListEntry = true; - } - else - { - // Sending the response will create a block rule - buddy->mHasBlockListEntry = true; - } - - if(buddy->mInSLFriends) - { - buddy->mInVivoxBuddies = true; - } - - stream - << "" - << "" << mAccountHandle << "" - << "" << buddy->mURI << "" - << "" << (buddy->mCanSeeMeOnline?"Allow":"Hide") << "" - << ""<< (buddy->mInSLFriends?"1":"0")<< "" - << "" << subscriptionHandle << "" - << "" - << "\n\n\n"; - - writeString(stream.str()); - } -} - -void LLVoiceClient::auxAudioPropertiesEvent(F32 energy) -{ - LL_DEBUGS("Voice") << "got energy " << energy << LL_ENDL; - mTuningEnergy = energy; -} - -void LLVoiceClient::buddyListChanged() -{ - // This is called after we receive a BuddyAndGroupListChangedEvent. - mBuddyListMapPopulated = true; - mFriendsListDirty = true; -} - -void LLVoiceClient::muteListChanged() -{ - // The user's mute list has been updated. Go through the current participant list and sync it with the mute list. - if(mAudioSession) - { - participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); - - for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) - { - participantState *p = iter->second; - - // Check to see if this participant is on the mute list already - if(p->updateMuteState()) - mAudioSession->mMuteDirty = true; - } - } -} - -void LLVoiceClient::updateFriends(U32 mask) -{ - if(mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::POWERS)) - { - // Just resend the whole friend list to the daemon - mFriendsListDirty = true; - } -} - -///////////////////////////// -// Managing list of participants -LLVoiceClient::participantState::participantState(const std::string &uri) : - mURI(uri), - mPTT(false), - mIsSpeaking(false), - mIsModeratorMuted(false), - mLastSpokeTimestamp(0.f), - mPower(0.f), - mVolume(VOLUME_DEFAULT), - mOnMuteList(false), - mVolumeSet(false), - mVolumeDirty(false), - mAvatarIDValid(false), - mIsSelf(false) -{ -} - -LLVoiceClient::participantState *LLVoiceClient::sessionState::addParticipant(const std::string &uri) -{ - participantState *result = NULL; - bool useAlternateURI = false; - - // Note: this is mostly the body of LLVoiceClient::sessionState::findParticipant(), but since we need to know if it - // matched the alternate SIP URI (so we can add it properly), we need to reproduce it here. - { - participantMap::iterator iter = mParticipantsByURI.find(&uri); - - if(iter == mParticipantsByURI.end()) - { - if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) - { - // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. - // Use mSIPURI instead, since it will be properly encoded. - iter = mParticipantsByURI.find(&(mSIPURI)); - useAlternateURI = true; - } - } - - if(iter != mParticipantsByURI.end()) - { - result = iter->second; - } - } - - if(!result) - { - // participant isn't already in one list or the other. - result = new participantState(useAlternateURI?mSIPURI:uri); - mParticipantsByURI.insert(participantMap::value_type(&(result->mURI), result)); - mParticipantsChanged = true; - - // Try to do a reverse transform on the URI to get the GUID back. - { - LLUUID id; - if(IDFromName(result->mURI, id)) - { - result->mAvatarIDValid = true; - result->mAvatarID = id; - - if(result->updateMuteState()) - mMuteDirty = true; - } - else - { - // Create a UUID by hashing the URI, but do NOT set mAvatarIDValid. - // This tells both code in LLVoiceClient and code in llfloateractivespeakers.cpp that the ID will not be in the name cache. - setUUIDFromStringHash(result->mAvatarID, uri); - } - } - - mParticipantsByUUID.insert(participantUUIDMap::value_type(&(result->mAvatarID), result)); - - if (LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID, result->mVolume)) - { - result->mVolumeDirty = true; - mVolumeDirty = true; - } - - LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL; - } - - return result; -} - -bool LLVoiceClient::participantState::updateMuteState() -{ - bool result = false; - - if(mAvatarIDValid) - { - bool isMuted = LLMuteList::getInstance()->isMuted(mAvatarID, LLMute::flagVoiceChat); - if(mOnMuteList != isMuted) - { - mOnMuteList = isMuted; - mVolumeDirty = true; - result = true; - } - } - return result; -} - -bool LLVoiceClient::participantState::isAvatar() -{ - return mAvatarIDValid; -} - -void LLVoiceClient::sessionState::removeParticipant(LLVoiceClient::participantState *participant) -{ - if(participant) - { - participantMap::iterator iter = mParticipantsByURI.find(&(participant->mURI)); - participantUUIDMap::iterator iter2 = mParticipantsByUUID.find(&(participant->mAvatarID)); - - LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL; - - if(iter == mParticipantsByURI.end()) - { - LL_ERRS("Voice") << "Internal error: participant " << participant->mURI << " not in URI map" << LL_ENDL; - } - else if(iter2 == mParticipantsByUUID.end()) - { - LL_ERRS("Voice") << "Internal error: participant ID " << participant->mAvatarID << " not in UUID map" << LL_ENDL; - } - else if(iter->second != iter2->second) - { - LL_ERRS("Voice") << "Internal error: participant mismatch!" << LL_ENDL; - } - else - { - mParticipantsByURI.erase(iter); - mParticipantsByUUID.erase(iter2); - - delete participant; - mParticipantsChanged = true; - } - } -} - -void LLVoiceClient::sessionState::removeAllParticipants() -{ - LL_DEBUGS("Voice") << "called" << LL_ENDL; - - while(!mParticipantsByURI.empty()) - { - removeParticipant(mParticipantsByURI.begin()->second); - } - - if(!mParticipantsByUUID.empty()) - { - LL_ERRS("Voice") << "Internal error: empty URI map, non-empty UUID map" << LL_ENDL; - } -} - -LLVoiceClient::participantMap *LLVoiceClient::getParticipantList(void) -{ - participantMap *result = NULL; - if(mAudioSession) - { - result = &(mAudioSession->mParticipantsByURI); - } - return result; -} - -void LLVoiceClient::getParticipantsUUIDSet(std::set& participant_uuids) -{ - if (NULL == mAudioSession) return; - - participantUUIDMap::const_iterator it = mAudioSession->mParticipantsByUUID.begin(), - it_end = mAudioSession->mParticipantsByUUID.end(); - for (; it != it_end; ++it) - { - participant_uuids.insert((*(*it).first)); - } -} - -LLVoiceClient::participantState *LLVoiceClient::sessionState::findParticipant(const std::string &uri) -{ - participantState *result = NULL; - - participantMap::iterator iter = mParticipantsByURI.find(&uri); - - if(iter == mParticipantsByURI.end()) - { - if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) - { - // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. - // Look up the other URI - iter = mParticipantsByURI.find(&(mSIPURI)); - } - } - - if(iter != mParticipantsByURI.end()) - { - result = iter->second; - } - - return result; -} - -LLVoiceClient::participantState* LLVoiceClient::sessionState::findParticipantByID(const LLUUID& id) -{ - participantState * result = NULL; - participantUUIDMap::iterator iter = mParticipantsByUUID.find(&id); - - if(iter != mParticipantsByUUID.end()) - { - result = iter->second; - } - - return result; -} - -LLVoiceClient::participantState* LLVoiceClient::findParticipantByID(const LLUUID& id) -{ - participantState * result = NULL; - - if(mAudioSession) - { - result = mAudioSession->findParticipantByID(id); - } - - return result; -} - - -void LLVoiceClient::parcelChanged() -{ - if(getState() >= stateNoChannel) - { - // If the user is logged in, start a channel lookup. - LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL; - - std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest"); - LLSD data; - LLHTTPClient::post( - url, - data, - new LLVoiceClientCapResponder); - } - else - { - // The transition to stateNoChannel needs to kick this off again. - LL_INFOS("Voice") << "not logged in yet, deferring" << LL_ENDL; - } -} - -void LLVoiceClient::switchChannel( - std::string uri, - bool spatial, - bool no_reconnect, - bool is_p2p, - std::string hash) -{ - bool needsSwitch = false; - - LL_DEBUGS("Voice") - << "called in state " << state2string(getState()) - << " with uri \"" << uri << "\"" - << (spatial?", spatial is true":", spatial is false") - << LL_ENDL; - - switch(getState()) - { - case stateJoinSessionFailed: - case stateJoinSessionFailedWaiting: - case stateNoChannel: - // Always switch to the new URI from these states. - needsSwitch = true; - break; - - default: - if(mSessionTerminateRequested) - { - // If a terminate has been requested, we need to compare against where the URI we're already headed to. - if(mNextAudioSession) - { - if(mNextAudioSession->mSIPURI != uri) - needsSwitch = true; - } - else - { - // mNextAudioSession is null -- this probably means we're on our way back to spatial. - if(!uri.empty()) - { - // We do want to process a switch in this case. - needsSwitch = true; - } - } - } - else - { - // Otherwise, compare against the URI we're in now. - if(mAudioSession) - { - if(mAudioSession->mSIPURI != uri) - { - needsSwitch = true; - } - } - else - { - if(!uri.empty()) - { - // mAudioSession is null -- it's not clear what case would cause this. - // For now, log it as a warning and see if it ever crops up. - LL_WARNS("Voice") << "No current audio session." << LL_ENDL; - } - } - } - break; - } - - if(needsSwitch) - { - if(uri.empty()) - { - // Leave any channel we may be in - LL_DEBUGS("Voice") << "leaving channel" << LL_ENDL; - - sessionState *oldSession = mNextAudioSession; - mNextAudioSession = NULL; - - // The old session may now need to be deleted. - reapSession(oldSession); - - notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); - } - else - { - LL_DEBUGS("Voice") << "switching to channel " << uri << LL_ENDL; - - mNextAudioSession = addSession(uri); - mNextAudioSession->mHash = hash; - mNextAudioSession->mIsSpatial = spatial; - mNextAudioSession->mReconnect = !no_reconnect; - mNextAudioSession->mIsP2P = is_p2p; - } - - if(getState() <= stateNoChannel) - { - // We're already set up to join a channel, just needed to fill in the session URI - } - else - { - // State machine will come around and rejoin if uri/handle is not empty. - sessionTerminate(); - } - } -} - -void LLVoiceClient::joinSession(sessionState *session) -{ - mNextAudioSession = session; - - if(getState() <= stateNoChannel) - { - // We're already set up to join a channel, just needed to fill in the session handle - } - else - { - // State machine will come around and rejoin if uri/handle is not empty. - sessionTerminate(); - } -} - -void LLVoiceClient::setNonSpatialChannel( - const std::string &uri, - const std::string &credentials) -{ - switchChannel(uri, false, false, false, credentials); -} - -void LLVoiceClient::setSpatialChannel( - const std::string &uri, - const std::string &credentials) -{ - mSpatialSessionURI = uri; - mSpatialSessionCredentials = credentials; - mAreaVoiceDisabled = mSpatialSessionURI.empty(); - - LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL; - - if((mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial))) - { - // User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels. - LL_INFOS("Voice") << "in non-spatial chat, not switching channels" << LL_ENDL; - } - else - { - switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials); - } -} - -void LLVoiceClient::callUser(const LLUUID &uuid) -{ - std::string userURI = sipURIFromID(uuid); - - switchChannel(userURI, false, true, true); -} - -LLVoiceClient::sessionState* LLVoiceClient::startUserIMSession(const LLUUID &uuid) -{ - // Figure out if a session with the user already exists - sessionState *session = findSession(uuid); - if(!session) - { - // No session with user, need to start one. - std::string uri = sipURIFromID(uuid); - session = addSession(uri); - - llassert(session); - if (!session) return NULL; - - session->mIsSpatial = false; - session->mReconnect = false; - session->mIsP2P = true; - session->mCallerID = uuid; - } - - if(session->mHandle.empty()) - { - // Session isn't active -- start it up. - sessionCreateSendMessage(session, false, true); - } - else - { - // Session is already active -- start up text. - sessionTextConnectSendMessage(session); - } - - return session; -} - -bool LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message) -{ - bool result = false; - - // Attempt to locate the indicated session - sessionState *session = startUserIMSession(participant_id); - if(session) - { - // found the session, attempt to send the message - session->mTextMsgQueue.push(message); - - // Try to send queued messages (will do nothing if the session is not open yet) - sendQueuedTextMessages(session); - - // The message is queued, so we succeed. - result = true; - } - else - { - LL_DEBUGS("Voice") << "Session not found for participant ID " << participant_id << LL_ENDL; - } - - return result; -} - -void LLVoiceClient::sendQueuedTextMessages(sessionState *session) -{ - if(session->mTextStreamState == 1) - { - if(!session->mTextMsgQueue.empty()) - { - std::ostringstream stream; - - while(!session->mTextMsgQueue.empty()) - { - std::string message = session->mTextMsgQueue.front(); - session->mTextMsgQueue.pop(); - stream - << "" - << "" << session->mHandle << "" - << "text/HTML" - << "" << message << "" - << "" - << "\n\n\n"; - } - writeString(stream.str()); - } - } - else - { - // Session isn't connected yet, defer until later. - } -} - -void LLVoiceClient::endUserIMSession(const LLUUID &uuid) -{ - // Figure out if a session with the user exists - sessionState *session = findSession(uuid); - if(session) - { - // found the session - if(!session->mHandle.empty()) - { - sessionTextDisconnectSendMessage(session); - } - } - else - { - LL_DEBUGS("Voice") << "Session not found for participant ID " << uuid << LL_ENDL; - } -} - -bool LLVoiceClient::answerInvite(std::string &sessionHandle) -{ - // this is only ever used to answer incoming p2p call invites. - - sessionState *session = findSession(sessionHandle); - if(session) - { - session->mIsSpatial = false; - session->mReconnect = false; - session->mIsP2P = true; - - joinSession(session); - return true; - } - - return false; -} - -bool LLVoiceClient::isOnlineSIP(const LLUUID &id) -{ - bool result = false; - buddyListEntry *buddy = findBuddy(id); - if(buddy) - { - result = buddy->mOnlineSLim; - LL_DEBUGS("Voice") << "Buddy " << buddy->mDisplayName << " is SIP " << (result?"online":"offline") << LL_ENDL; - } - - if(!result) - { - // This user isn't on the buddy list or doesn't show online status through the buddy list, but could be a participant in an existing session if they initiated a text IM. - sessionState *session = findSession(id); - if(session && !session->mHandle.empty()) - { - if((session->mTextStreamState != streamStateUnknown) || (session->mMediaStreamState > streamStateIdle)) - { - LL_DEBUGS("Voice") << "Open session with " << id << " found, returning SIP online state" << LL_ENDL; - // we have a p2p text session open with this user, so by definition they're online. - result = true; - } - } - } - - return result; -} - -// Returns true if the indicated participant in the current audio session is really an SL avatar. -// Currently this will be false only for PSTN callers into group chats, and PSTN p2p calls. -bool LLVoiceClient::isParticipantAvatar(const LLUUID &id) -{ - bool result = true; - sessionState *session = findSession(id); - - if(session != NULL) - { - // this is a p2p session with the indicated caller, or the session with the specified UUID. - if(session->mSynthesizedCallerID) - result = false; - } - else - { - // Didn't find a matching session -- check the current audio session for a matching participant - if(mAudioSession != NULL) - { - participantState *participant = findParticipantByID(id); - if(participant != NULL) - { - result = participant->isAvatar(); - } - } - } - - return result; -} - -// Returns true if calling back the session URI after the session has closed is possible. -// Currently this will be false only for PSTN P2P calls. -bool LLVoiceClient::isSessionCallBackPossible(const LLUUID &session_id) -{ - bool result = true; - sessionState *session = findSession(session_id); - - if(session != NULL) - { - result = session->isCallBackPossible(); - } - - return result; -} - -// Returns true if the session can accepte text IM's. -// Currently this will be false only for PSTN P2P calls. -bool LLVoiceClient::isSessionTextIMPossible(const LLUUID &session_id) -{ - bool result = true; - sessionState *session = findSession(session_id); - - if(session != NULL) - { - result = session->isTextIMPossible(); - } - - return result; -} - - -void LLVoiceClient::declineInvite(std::string &sessionHandle) -{ - sessionState *session = findSession(sessionHandle); - if(session) - { - sessionMediaDisconnectSendMessage(session); - } -} - -void LLVoiceClient::leaveNonSpatialChannel() -{ - LL_DEBUGS("Voice") - << "called in state " << state2string(getState()) - << LL_ENDL; - - // Make sure we don't rejoin the current session. - sessionState *oldNextSession = mNextAudioSession; - mNextAudioSession = NULL; - - // Most likely this will still be the current session at this point, but check it anyway. - reapSession(oldNextSession); - - verifySessionState(); - - sessionTerminate(); -} - -std::string LLVoiceClient::getCurrentChannel() -{ - std::string result; - - if((getState() == stateRunning) && !mSessionTerminateRequested) - { - result = getAudioSessionURI(); - } - - return result; -} - -bool LLVoiceClient::inProximalChannel() -{ - bool result = false; - - if((getState() == stateRunning) && !mSessionTerminateRequested) - { - result = inSpatialChannel(); - } - - return result; -} - -std::string LLVoiceClient::sipURIFromID(const LLUUID &id) -{ - std::string result; - result = "sip:"; - result += nameFromID(id); - result += "@"; - result += mVoiceSIPURIHostName; - - return result; -} - -std::string LLVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar) -{ - std::string result; - if(avatar) - { - result = "sip:"; - result += nameFromID(avatar->getID()); - result += "@"; - result += mVoiceSIPURIHostName; - } - - return result; -} - -std::string LLVoiceClient::nameFromAvatar(LLVOAvatar *avatar) -{ - std::string result; - if(avatar) - { - result = nameFromID(avatar->getID()); - } - return result; -} - -std::string LLVoiceClient::nameFromID(const LLUUID &uuid) -{ - std::string result; - - if (uuid.isNull()) { - //VIVOX, the uuid emtpy look for the mURIString and return that instead. - //result.assign(uuid.mURIStringName); - LLStringUtil::replaceChar(result, '_', ' '); - return result; - } - // Prepending this apparently prevents conflicts with reserved names inside the vivox and diamondware code. - result = "x"; - - // Base64 encode and replace the pieces of base64 that are less compatible - // with e-mail local-parts. - // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet" - result += LLBase64::encode(uuid.mData, UUID_BYTES); - LLStringUtil::replaceChar(result, '+', '-'); - LLStringUtil::replaceChar(result, '/', '_'); - - // If you need to transform a GUID to this form on the Mac OS X command line, this will do so: - // echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-') - - // The reverse transform can be done with: - // echo 'x5mkTKmxDTuGnjWyC__WfMg==' |cut -b 2- -|tr '_-' '/+' |openssl base64 -d|xxd -p - - return result; -} +const F32 LLVoiceClient::OVERDRIVEN_POWER_LEVEL = 0.7f; -bool LLVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) +std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserver::EStatusType inStatus) { - bool result = false; - - // SLIM SDK: The "name" may actually be a SIP URI such as: "sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com" - // If it is, convert to a bare name before doing the transform. - std::string name = nameFromsipURI(inName); - - // Doesn't look like a SIP URI, assume it's an actual name. - if(name.empty()) - name = inName; - - // This will only work if the name is of the proper form. - // As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is: - // "xFnPP04IpREWNkuw1cOXlhw==" - - if((name.size() == 25) && (name[0] == 'x') && (name[23] == '=') && (name[24] == '=')) - { - // The name appears to have the right form. - - // Reverse the transforms done by nameFromID - std::string temp = name; - LLStringUtil::replaceChar(temp, '-', '+'); - LLStringUtil::replaceChar(temp, '_', '/'); - - U8 rawuuid[UUID_BYTES + 1]; - int len = apr_base64_decode_binary(rawuuid, temp.c_str() + 1); - if(len == UUID_BYTES) - { - // The decode succeeded. Stuff the bits into the result's UUID - memcpy(uuid.mData, rawuuid, UUID_BYTES); - result = true; - } - } + std::string result = "UNKNOWN"; - if(!result) - { - // VIVOX: not a standard account name, just copy the URI name mURIString field - // and hope for the best. bpj - uuid.setNull(); // VIVOX, set the uuid field to nulls - } + // Prevent copy-paste errors when updating this list... +#define CASE(x) case x: result = #x; break - return result; -} - -std::string LLVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar) -{ - return avatar->getFullname(); -} - -std::string LLVoiceClient::sipURIFromName(std::string &name) -{ - std::string result; - result = "sip:"; - result += name; - result += "@"; - result += mVoiceSIPURIHostName; - -// LLStringUtil::toLower(result); - - return result; -} - -std::string LLVoiceClient::nameFromsipURI(const std::string &uri) -{ - std::string result; - - std::string::size_type sipOffset, atOffset; - sipOffset = uri.find("sip:"); - atOffset = uri.find("@"); - if((sipOffset != std::string::npos) && (atOffset != std::string::npos)) + switch(inStatus) { - result = uri.substr(sipOffset + 4, atOffset - (sipOffset + 4)); + CASE(STATUS_LOGIN_RETRY); + CASE(STATUS_LOGGED_IN); + CASE(STATUS_JOINING); + CASE(STATUS_JOINED); + CASE(STATUS_LEFT_CHANNEL); + CASE(STATUS_VOICE_DISABLED); + CASE(BEGIN_ERROR_STATUS); + CASE(ERROR_CHANNEL_FULL); + CASE(ERROR_CHANNEL_LOCKED); + CASE(ERROR_NOT_AVAILABLE); + CASE(ERROR_UNKNOWN); + default: + break; } - return result; -} - -bool LLVoiceClient::inSpatialChannel(void) -{ - bool result = false; - - if(mAudioSession) - result = mAudioSession->mIsSpatial; - - return result; -} - -std::string LLVoiceClient::getAudioSessionURI() -{ - std::string result; - - if(mAudioSession) - result = mAudioSession->mSIPURI; - - return result; -} - -std::string LLVoiceClient::getAudioSessionHandle() -{ - std::string result; +#undef CASE - if(mAudioSession) - result = mAudioSession->mHandle; - return result; } -///////////////////////////// -// Sending updates of current state - -void LLVoiceClient::enforceTether(void) -{ - LLVector3d tethered = mCameraRequestedPosition; - - // constrain 'tethered' to within 50m of mAvatarPosition. - { - F32 max_dist = 50.0f; - LLVector3d camera_offset = mCameraRequestedPosition - mAvatarPosition; - F32 camera_distance = (F32)camera_offset.magVec(); - if(camera_distance > max_dist) - { - tethered = mAvatarPosition + - (max_dist / camera_distance) * camera_offset; - } - } - - if(dist_vec(mCameraPosition, tethered) > 0.1) - { - mCameraPosition = tethered; - mSpatialCoordsDirty = true; - } -} - -void LLVoiceClient::updatePosition(void) -{ - if(gVoiceClient) - { - LLViewerRegion *region = gAgent.getRegion(); - if(region && isAgentAvatarValid()) - { - LLMatrix3 rot; - LLVector3d pos; - - // TODO: If camera and avatar velocity are actually used by the voice system, we could compute them here... - // They're currently always set to zero. - - // Send the current camera position to the voice code - rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis()); - pos = gAgent.getRegion()->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin()); - - gVoiceClient->setCameraPosition( - pos, // position - LLVector3::zero, // velocity - rot); // rotation matrix - - // Send the current avatar position to the voice code - rot = gAgentAvatarp->getRootJoint()->getWorldRotation().getMatrix3(); - - pos = gAgentAvatarp->getPositionGlobal(); - // TODO: Can we get the head offset from outside the LLVOAvatar? -// pos += LLVector3d(mHeadOffset); - pos += LLVector3d(0.f, 0.f, 1.f); - - gVoiceClient->setAvatarPosition( - pos, // position - LLVector3::zero, // velocity - rot); // rotation matrix - } - } -} - -void LLVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) -{ - mCameraRequestedPosition = position; - - if(mCameraVelocity != velocity) - { - mCameraVelocity = velocity; - mSpatialCoordsDirty = true; - } - - if(mCameraRot != rot) - { - mCameraRot = rot; - mSpatialCoordsDirty = true; - } -} - -void LLVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) -{ - if(dist_vec(mAvatarPosition, position) > 0.1) - { - mAvatarPosition = position; - mSpatialCoordsDirty = true; - } - - if(mAvatarVelocity != velocity) - { - mAvatarVelocity = velocity; - mSpatialCoordsDirty = true; - } - - if(mAvatarRot != rot) - { - mAvatarRot = rot; - mSpatialCoordsDirty = true; - } -} - -bool LLVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name) -{ - bool result = false; - - if(region) - { - name = region->getName(); - } - - if(!name.empty()) - result = true; - - return result; -} -void LLVoiceClient::leaveChannel(void) -{ - if(getState() == stateRunning) - { - LL_DEBUGS("Voice") << "leaving channel for teleport/logout" << LL_ENDL; - mChannelName.clear(); - sessionTerminate(); - } -} -void LLVoiceClient::setMuteMic(bool muted) -{ - mMuteMic = muted; -} +/////////////////////////////////////////////////////////////////////////////////////////////// -bool LLVoiceClient::getMuteMic() const +LLVoiceClient::LLVoiceClient() { - return mMuteMic; + mVoiceModule = NULL; } -void LLVoiceClient::setUserPTTState(bool ptt) -{ - mUserPTTState = ptt; -} +//--------------------------------------------------- +// Basic setup/shutdown -bool LLVoiceClient::getUserPTTState() +LLVoiceClient::~LLVoiceClient() { - return mUserPTTState; } -void LLVoiceClient::toggleUserPTTState(void) +void LLVoiceClient::init(LLPumpIO *pump) { - mUserPTTState = !mUserPTTState; + // Initialize all of the voice modules + m_servicePump = pump; } -void LLVoiceClient::setVoiceEnabled(bool enabled) +void LLVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID) { - if (enabled != mVoiceEnabled) + // In the future, we should change this to allow voice module registration + // with a table lookup of sorts. + std::string voice_server = gSavedSettings.getString("VoiceServerType"); + LL_DEBUGS("Voice") << "voice server type " << voice_server << LL_ENDL; + if(voice_server == "diamondware") { - mVoiceEnabled = enabled; - LLVoiceClientStatusObserver::EStatusType status; - - if (enabled) - { - LLVoiceChannel::getCurrentVoiceChannel()->activate(); - status = LLVoiceClientStatusObserver::STATUS_VOICE_ENABLED; - } - else - { - // Turning voice off looses your current channel -- this makes sure the UI isn't out of sync when you re-enable it. - LLVoiceChannel::getCurrentVoiceChannel()->deactivate(); - status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED; - } - - notifyStatusObservers(status); + mVoiceModule = (LLVoiceModuleInterface *)LLDiamondwareVoiceClient::getInstance(); } -} - -bool LLVoiceClient::voiceEnabled() -{ - return gSavedSettings.getBOOL("EnableVoiceChat") && !gSavedSettings.getBOOL("CmdLineDisableVoice"); -} - -//AD *TODO: investigate possible merge of voiceWorking() and voiceEnabled() into one non-static method -bool LLVoiceClient::voiceWorking() -{ - //Added stateSessionTerminated state to avoid problems with call in parcels with disabled voice (EXT-4758) - // Condition with joining spatial num was added to take into account possible problems with connection to voice - // server(EXT-4313). See bug descriptions and comments for MAX_NORMAL_JOINING_SPATIAL_NUM for more info. - return (mSpatialJoiningNum < MAX_NORMAL_JOINING_SPATIAL_NUM) && (stateLoggedIn <= mState) && (mState <= stateSessionTerminated); -} - -void LLVoiceClient::setLipSyncEnabled(BOOL enabled) -{ - mLipSyncEnabled = enabled; -} - -BOOL LLVoiceClient::lipSyncEnabled() -{ - - if ( mVoiceEnabled && stateDisabled != getState() ) + else if(voice_server == "vivox") { - return mLipSyncEnabled; + mVoiceModule = (LLVoiceModuleInterface *)LLVivoxVoiceClient::getInstance(); } else { - return FALSE; - } -} - -void LLVoiceClient::setUsePTT(bool usePTT) -{ - if(usePTT && !mUsePTT) - { - // When the user turns on PTT, reset the current state. - mUserPTTState = false; + mVoiceModule = NULL; + return; } - mUsePTT = usePTT; + mVoiceModule->init(m_servicePump); + mVoiceModule->userAuthorized(user_id, agentID); } -void LLVoiceClient::setPTTIsToggle(bool PTTIsToggle) -{ - if(!PTTIsToggle && mPTTIsToggle) - { - // When the user turns off toggle, reset the current state. - mUserPTTState = false; - } - - mPTTIsToggle = PTTIsToggle; -} -bool LLVoiceClient::getPTTIsToggle() +void LLVoiceClient::terminate() { - return mPTTIsToggle; + if (mVoiceModule) mVoiceModule->terminate(); + mVoiceModule = NULL; } -void LLVoiceClient::setPTTKey(std::string &key) +const LLVoiceVersionInfo LLVoiceClient::getVersion() { - if(key == "MiddleMouse") + if (mVoiceModule) { - mPTTIsMiddleMouse = true; + return mVoiceModule->getVersion(); } else { - mPTTIsMiddleMouse = false; - if(!LLKeyboard::keyFromString(key, &mPTTKey)) - { - // If the call failed, don't match any key. - key = KEY_NONE; - } + LLVoiceVersionInfo result; + result.serverVersion = std::string(); + result.serverType = std::string(); + return result; } } -void LLVoiceClient::setEarLocation(S32 loc) +void LLVoiceClient::updateSettings() { - if(mEarLocation != loc) - { - LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL; - - mEarLocation = loc; - mSpatialCoordsDirty = true; - } + if (mVoiceModule) mVoiceModule->updateSettings(); } -void LLVoiceClient::setVoiceVolume(F32 volume) -{ - int scaled_volume = scale_speaker_volume(volume); - - if(scaled_volume != mSpeakerVolume) - { - int min_volume = scale_speaker_volume(0); - if((scaled_volume == min_volume) || (mSpeakerVolume == min_volume)) - { - mSpeakerMuteDirty = true; - } - - mSpeakerVolume = scaled_volume; - mSpeakerVolumeDirty = true; - } -} +//-------------------------------------------------- +// tuning -void LLVoiceClient::setMicGain(F32 volume) +void LLVoiceClient::tuningStart() { - int scaled_volume = scale_mic_volume(volume); - - if(scaled_volume != mMicVolume) - { - mMicVolume = scaled_volume; - mMicVolumeDirty = true; - } + if (mVoiceModule) mVoiceModule->tuningStart(); } -void LLVoiceClient::keyDown(KEY key, MASK mask) -{ - if (gKeyboard->getKeyRepeated(key)) - { - // ignore auto-repeat keys - return; - } - - if(!mPTTIsMiddleMouse) - { - bool down = (mPTTKey != KEY_NONE) - && gKeyboard->getKeyDown(mPTTKey); - inputUserControlState(down); - } -} -void LLVoiceClient::keyUp(KEY key, MASK mask) -{ - if(!mPTTIsMiddleMouse) - { - bool down = (mPTTKey != KEY_NONE) - && gKeyboard->getKeyDown(mPTTKey); - inputUserControlState(down); - } -} -void LLVoiceClient::inputUserControlState(bool down) -{ - if(mPTTIsToggle) - { - if(down) // toggle open-mic state on 'down' - { - toggleUserPTTState(); - } - } - else // set open-mic state as an absolute - { - setUserPTTState(down); - } -} -void LLVoiceClient::middleMouseState(bool down) +void LLVoiceClient::tuningStop() { - if(mPTTIsMiddleMouse) - { - inputUserControlState(down); - } + if (mVoiceModule) mVoiceModule->tuningStop(); } -///////////////////////////// -// Accessors for data related to nearby speakers -BOOL LLVoiceClient::getVoiceEnabled(const LLUUID& id) +bool LLVoiceClient::inTuningMode() { - BOOL result = FALSE; - participantState *participant = findParticipantByID(id); - if(participant) + if (mVoiceModule) { - // I'm not sure what the semantics of this should be. - // For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled. - result = TRUE; + return mVoiceModule->inTuningMode(); + } + else + { + return false; } - - return result; } -BOOL LLVoiceClient::getIsSpeaking(const LLUUID& id) +void LLVoiceClient::tuningSetMicVolume(float volume) { - BOOL result = FALSE; - - participantState *participant = findParticipantByID(id); - if(participant) - { - if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) - { - participant->mIsSpeaking = FALSE; - } - result = participant->mIsSpeaking; - } - - return result; + if (mVoiceModule) mVoiceModule->tuningSetMicVolume(volume); } -BOOL LLVoiceClient::getIsModeratorMuted(const LLUUID& id) +void LLVoiceClient::tuningSetSpeakerVolume(float volume) { - BOOL result = FALSE; + if (mVoiceModule) mVoiceModule->tuningSetSpeakerVolume(volume); +} - participantState *participant = findParticipantByID(id); - if(participant) +float LLVoiceClient::tuningGetEnergy(void) +{ + if (mVoiceModule) { - result = participant->mIsModeratorMuted; + return mVoiceModule->tuningGetEnergy(); } - - return result; -} - -F32 LLVoiceClient::getCurrentPower(const LLUUID& id) -{ - F32 result = 0; - participantState *participant = findParticipantByID(id); - if(participant) + else { - result = participant->mPower; + return 0.0; } - - return result; } -std::string LLVoiceClient::getDisplayName(const LLUUID& id) +//------------------------------------------------ +// devices + +bool LLVoiceClient::deviceSettingsAvailable() { - std::string result; - participantState *participant = findParticipantByID(id); - if(participant) + if (mVoiceModule) { - result = participant->mDisplayName; + return mVoiceModule->deviceSettingsAvailable(); + } + else + { + return false; } - - return result; } - -BOOL LLVoiceClient::getUsingPTT(const LLUUID& id) +void LLVoiceClient::refreshDeviceLists(bool clearCurrentList) { - BOOL result = FALSE; - - participantState *participant = findParticipantByID(id); - if(participant) - { - // I'm not sure what the semantics of this should be. - // Does "using PTT" mean they're configured with a push-to-talk button? - // For now, we know there's no PTT mechanism in place, so nobody is using it. - } - - return result; + if (mVoiceModule) mVoiceModule->refreshDeviceLists(clearCurrentList); } -BOOL LLVoiceClient::getOnMuteList(const LLUUID& id) +void LLVoiceClient::setCaptureDevice(const std::string& name) { - BOOL result = FALSE; + if (mVoiceModule) mVoiceModule->setCaptureDevice(name); - participantState *participant = findParticipantByID(id); - if(participant) - { - result = participant->mOnMuteList; - } +} - return result; +void LLVoiceClient::setRenderDevice(const std::string& name) +{ + if (mVoiceModule) mVoiceModule->setRenderDevice(name); } -// External accessors. -F32 LLVoiceClient::getUserVolume(const LLUUID& id) +const LLVoiceDeviceList& LLVoiceClient::getCaptureDevices() { - // Minimum volume will be returned for users with voice disabled - F32 result = VOLUME_MIN; - - participantState *participant = findParticipantByID(id); - if(participant) + static LLVoiceDeviceList nullCaptureDevices; + if (mVoiceModule) { - result = participant->mVolume; - - // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. - // LL_DEBUGS("Voice") << "mVolume = " << result << " for " << id << LL_ENDL; + return mVoiceModule->getCaptureDevices(); + } + else + { + return nullCaptureDevices; } - - return result; } -void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) + +const LLVoiceDeviceList& LLVoiceClient::getRenderDevices() { - if(mAudioSession) + static LLVoiceDeviceList nullRenderDevices; + if (mVoiceModule) { - participantState *participant = findParticipantByID(id); - if (participant && !participant->mIsSelf) - { - if (!is_approx_equal(volume, VOLUME_DEFAULT)) - { - // Store this volume setting for future sessions if it has been - // changed from the default - LLSpeakerVolumeStorage::getInstance()->storeSpeakerVolume(id, volume); - } - else - { - // Remove stored volume setting if it is returned to the default - LLSpeakerVolumeStorage::getInstance()->removeSpeakerVolume(id); - } - - participant->mVolume = llclamp(volume, VOLUME_MIN, VOLUME_MAX); - participant->mVolumeDirty = true; - mAudioSession->mVolumeDirty = true; - } + return mVoiceModule->getRenderDevices(); + } + else + { + return nullRenderDevices; } } -std::string LLVoiceClient::getGroupID(const LLUUID& id) -{ - std::string result; - participantState *participant = findParticipantByID(id); - if(participant) +//-------------------------------------------------- +// participants + +void LLVoiceClient::getParticipantList(std::set &participants) +{ + if (mVoiceModule) { - result = participant->mGroupID; + mVoiceModule->getParticipantList(participants); + } + else + { + participants = std::set(); } - - return result; } -BOOL LLVoiceClient::getAreaVoiceDisabled() +bool LLVoiceClient::isParticipant(const LLUUID &speaker_id) { - return mAreaVoiceDisabled; + if(mVoiceModule) + { + return mVoiceModule->isParticipant(speaker_id); + } + return false; } -void LLVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame) -{ -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL; - - if(!mMainSessionGroupHandle.empty()) - { - std::ostringstream stream; - stream - << "" - << "" << mMainSessionGroupHandle << "" - << "Start" - << "" << deltaFramesPerControlFrame << "" - << "" << "" << "" - << "false" - << "" << seconds << "" - << "\n\n\n"; +//-------------------------------------------------- +// text chat - writeString(stream.str()); - } -} -void LLVoiceClient::recordingLoopSave(const std::string& filename) +BOOL LLVoiceClient::isSessionTextIMPossible(const LLUUID& id) { -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Flush)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) + if (mVoiceModule) { - std::ostringstream stream; - stream - << "" - << "" << mMainSessionGroupHandle << "" - << "Flush" - << "" << filename << "" - << "\n\n\n"; - - writeString(stream.str()); + return mVoiceModule->isSessionTextIMPossible(id); } + else + { + return FALSE; + } } -void LLVoiceClient::recordingStop() +BOOL LLVoiceClient::isSessionCallBackPossible(const LLUUID& id) { -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Stop)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) + if (mVoiceModule) { - std::ostringstream stream; - stream - << "" - << "" << mMainSessionGroupHandle << "" - << "Stop" - << "\n\n\n"; - - writeString(stream.str()); + return mVoiceModule->isSessionCallBackPossible(id); } + else + { + return FALSE; + } } -void LLVoiceClient::filePlaybackStart(const std::string& filename) +BOOL LLVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message) { -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Start)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) + if (mVoiceModule) { - std::ostringstream stream; - stream - << "" - << "" << mMainSessionGroupHandle << "" - << "Start" - << "" << filename << "" - << "\n\n\n"; - - writeString(stream.str()); + return mVoiceModule->sendTextMessage(participant_id, message); } + else + { + return FALSE; + } } -void LLVoiceClient::filePlaybackStop() +void LLVoiceClient::endUserIMSession(const LLUUID& participant_id) { -// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Stop)" << LL_ENDL; - - if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) + if (mVoiceModule) { - std::ostringstream stream; - stream - << "" - << "" << mMainSessionGroupHandle << "" - << "Stop" - << "\n\n\n"; - - writeString(stream.str()); + mVoiceModule->endUserIMSession(participant_id); } } -void LLVoiceClient::filePlaybackSetPaused(bool paused) -{ - // TODO: Implement once Vivox gives me a sample -} - -void LLVoiceClient::filePlaybackSetMode(bool vox, float speed) -{ - // TODO: Implement once Vivox gives me a sample -} - -LLVoiceClient::sessionState::sessionState() : - mErrorStatusCode(0), - mMediaStreamState(streamStateUnknown), - mTextStreamState(streamStateUnknown), - mCreateInProgress(false), - mMediaConnectInProgress(false), - mVoiceInvitePending(false), - mTextInvitePending(false), - mSynthesizedCallerID(false), - mIsChannel(false), - mIsSpatial(false), - mIsP2P(false), - mIncoming(false), - mVoiceEnabled(false), - mReconnect(false), - mVolumeDirty(false), - mMuteDirty(false), - mParticipantsChanged(false) -{ -} +//---------------------------------------------- +// channels -LLVoiceClient::sessionState::~sessionState() +bool LLVoiceClient::inProximalChannel() { - removeAllParticipants(); + if (mVoiceModule) + { + return mVoiceModule->inProximalChannel(); + } + else + { + return false; + } } -bool LLVoiceClient::sessionState::isCallBackPossible() +void LLVoiceClient::setNonSpatialChannel( + const std::string &uri, + const std::string &credentials) { - // This may change to be explicitly specified by vivox in the future... - // Currently, only PSTN P2P calls cannot be returned. - // Conveniently, this is also the only case where we synthesize a caller UUID. - return !mSynthesizedCallerID; + if (mVoiceModule) mVoiceModule->setNonSpatialChannel(uri, credentials); } -bool LLVoiceClient::sessionState::isTextIMPossible() +void LLVoiceClient::setSpatialChannel( + const std::string &uri, + const std::string &credentials) { - // This may change to be explicitly specified by vivox in the future... - return !mSynthesizedCallerID; + if (mVoiceModule) mVoiceModule->setSpatialChannel(uri, credentials); } - -LLVoiceClient::sessionIterator LLVoiceClient::sessionsBegin(void) +void LLVoiceClient::leaveNonSpatialChannel() { - return mSessions.begin(); + if (mVoiceModule) mVoiceModule->leaveNonSpatialChannel(); } -LLVoiceClient::sessionIterator LLVoiceClient::sessionsEnd(void) +void LLVoiceClient::leaveChannel(void) { - return mSessions.end(); + if (mVoiceModule) mVoiceModule->leaveChannel(); } - -LLVoiceClient::sessionState *LLVoiceClient::findSession(const std::string &handle) +std::string LLVoiceClient::getCurrentChannel() { - sessionState *result = NULL; - sessionMap::iterator iter = mSessionsByHandle.find(&handle); - if(iter != mSessionsByHandle.end()) + if (mVoiceModule) { - result = iter->second; + return mVoiceModule->getCurrentChannel(); } - - return result; -} - -LLVoiceClient::sessionState *LLVoiceClient::findSessionBeingCreatedByURI(const std::string &uri) -{ - sessionState *result = NULL; - for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) + else { - sessionState *session = *iter; - if(session->mCreateInProgress && (session->mSIPURI == uri)) - { - result = session; - break; - } + return std::string(); } - - return result; } -LLVoiceClient::sessionState *LLVoiceClient::findSession(const LLUUID &participant_id) + +//--------------------------------------- +// invitations + +void LLVoiceClient::callUser(const LLUUID &uuid) { - sessionState *result = NULL; - - for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) - { - sessionState *session = *iter; - if((session->mCallerID == participant_id) || (session->mIMSessionID == participant_id)) - { - result = session; - break; - } - } - - return result; + if (mVoiceModule) mVoiceModule->callUser(uuid); } -LLVoiceClient::sessionState *LLVoiceClient::addSession(const std::string &uri, const std::string &handle) +bool LLVoiceClient::answerInvite(std::string &channelHandle) { - sessionState *result = NULL; - - if(handle.empty()) - { - // No handle supplied. - // Check whether there's already a session with this URI - for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) - { - sessionState *s = *iter; - if((s->mSIPURI == uri) || (s->mAlternateSIPURI == uri)) - { - // TODO: I need to think about this logic... it's possible that this case should raise an internal error. - result = s; - break; - } - } - } - else // (!handle.empty()) - { - // Check for an existing session with this handle - sessionMap::iterator iter = mSessionsByHandle.find(&handle); - - if(iter != mSessionsByHandle.end()) - { - result = iter->second; - } - } - - if(!result) + if (mVoiceModule) { - // No existing session found. - - LL_DEBUGS("Voice") << "adding new session: handle " << handle << " URI " << uri << LL_ENDL; - result = new sessionState(); - result->mSIPURI = uri; - result->mHandle = handle; - - mSessions.insert(result); - - if(!result->mHandle.empty()) - { - mSessionsByHandle.insert(sessionMap::value_type(&(result->mHandle), result)); - } + return mVoiceModule->answerInvite(channelHandle); } else { - // Found an existing session - - if(uri != result->mSIPURI) - { - // TODO: Should this be an internal error? - LL_DEBUGS("Voice") << "changing uri from " << result->mSIPURI << " to " << uri << LL_ENDL; - setSessionURI(result, uri); - } - - if(handle != result->mHandle) - { - if(handle.empty()) - { - // There's at least one race condition where where addSession was clearing an existing session handle, which caused things to break. - LL_DEBUGS("Voice") << "NOT clearing handle " << result->mHandle << LL_ENDL; - } - else - { - // TODO: Should this be an internal error? - LL_DEBUGS("Voice") << "changing handle from " << result->mHandle << " to " << handle << LL_ENDL; - setSessionHandle(result, handle); - } - } - - LL_DEBUGS("Voice") << "returning existing session: handle " << handle << " URI " << uri << LL_ENDL; + return false; } - - verifySessionState(); - - return result; } -void LLVoiceClient::setSessionHandle(sessionState *session, const std::string &handle) +void LLVoiceClient::declineInvite(std::string &channelHandle) { - // Have to remove the session from the handle-indexed map before changing the handle, or things will break badly. - - if(!session->mHandle.empty()) - { - // Remove session from the map if it should have been there. - sessionMap::iterator iter = mSessionsByHandle.find(&(session->mHandle)); - if(iter != mSessionsByHandle.end()) - { - if(iter->second != session) - { - LL_ERRS("Voice") << "Internal error: session mismatch!" << LL_ENDL; - } + if (mVoiceModule) mVoiceModule->declineInvite(channelHandle); +} - mSessionsByHandle.erase(iter); - } - else - { - LL_ERRS("Voice") << "Internal error: session handle not found in map!" << LL_ENDL; - } - } - - session->mHandle = handle; - if(!handle.empty()) - { - mSessionsByHandle.insert(sessionMap::value_type(&(session->mHandle), session)); - } +//------------------------------------------ +// Volume/gain - verifySessionState(); -} -void LLVoiceClient::setSessionURI(sessionState *session, const std::string &uri) +void LLVoiceClient::setVoiceVolume(F32 volume) { - // There used to be a map of session URIs to sessions, which made this complex.... - session->mSIPURI = uri; - - verifySessionState(); + if (mVoiceModule) mVoiceModule->setVoiceVolume(volume); } -void LLVoiceClient::deleteSession(sessionState *session) +void LLVoiceClient::setMicGain(F32 volume) { - // Remove the session from the handle map - if(!session->mHandle.empty()) - { - sessionMap::iterator iter = mSessionsByHandle.find(&(session->mHandle)); - if(iter != mSessionsByHandle.end()) - { - if(iter->second != session) - { - LL_ERRS("Voice") << "Internal error: session mismatch" << LL_ENDL; - } - mSessionsByHandle.erase(iter); - } - } + if (mVoiceModule) mVoiceModule->setMicGain(volume); +} - // Remove the session from the URI map - mSessions.erase(session); - - // At this point, the session should be unhooked from all lists and all state should be consistent. - verifySessionState(); - // If this is the current audio session, clean up the pointer which will soon be dangling. - if(mAudioSession == session) +//------------------------------------------ +// enable/disable voice features + +bool LLVoiceClient::voiceEnabled() +{ + if (mVoiceModule) { - mAudioSession = NULL; - mAudioSessionChanged = true; + return mVoiceModule->voiceEnabled(); } - - // ditto for the next audio session - if(mNextAudioSession == session) + else { - mNextAudioSession = NULL; + return false; } +} - // delete the session - delete session; +void LLVoiceClient::setVoiceEnabled(bool enabled) +{ + if (mVoiceModule) mVoiceModule->setVoiceEnabled(enabled); } -void LLVoiceClient::deleteAllSessions() +void LLVoiceClient::setLipSyncEnabled(BOOL enabled) { - LL_DEBUGS("Voice") << "called" << LL_ENDL; + if (mVoiceModule) mVoiceModule->setLipSyncEnabled(enabled); +} - while(!mSessions.empty()) +BOOL LLVoiceClient::lipSyncEnabled() +{ + if (mVoiceModule) { - deleteSession(*(sessionsBegin())); + return mVoiceModule->lipSyncEnabled(); } - - if(!mSessionsByHandle.empty()) + else { - LL_ERRS("Voice") << "Internal error: empty session map, non-empty handle map" << LL_ENDL; + return false; } } -void LLVoiceClient::verifySessionState(void) +void LLVoiceClient::setMuteMic(bool muted) { - // This is mostly intended for debugging problems with session state management. - LL_DEBUGS("Voice") << "Total session count: " << mSessions.size() << " , session handle map size: " << mSessionsByHandle.size() << LL_ENDL; + if (mVoiceModule) mVoiceModule->setMuteMic(muted); +} - for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) - { - sessionState *session = *iter; - LL_DEBUGS("Voice") << "session " << session << ": handle " << session->mHandle << ", URI " << session->mSIPURI << LL_ENDL; - - if(!session->mHandle.empty()) - { - // every session with a non-empty handle needs to be in the handle map - sessionMap::iterator i2 = mSessionsByHandle.find(&(session->mHandle)); - if(i2 == mSessionsByHandle.end()) - { - LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " not found in session map)" << LL_ENDL; - } - else - { - if(i2->second != session) - { - LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " in session map points to another session)" << LL_ENDL; - } - } - } - } - - // check that every entry in the handle map points to a valid session in the session set - for(sessionMap::iterator iter = mSessionsByHandle.begin(); iter != mSessionsByHandle.end(); iter++) +// ---------------------------------------------- +// PTT + +void LLVoiceClient::setUserPTTState(bool ptt) +{ + if (mVoiceModule) mVoiceModule->setUserPTTState(ptt); +} + +bool LLVoiceClient::getUserPTTState() +{ + if (mVoiceModule) { - sessionState *session = iter->second; - sessionIterator i2 = mSessions.find(session); - if(i2 == mSessions.end()) - { - LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " not found in session map)" << LL_ENDL; - } - else - { - if(session->mHandle != (*i2)->mHandle) - { - LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " points to session with different handle " << (*i2)->mHandle << ")" << LL_ENDL; - } - } + return mVoiceModule->getUserPTTState(); + } + else + { + return false; } } -LLVoiceClient::buddyListEntry::buddyListEntry(const std::string &uri) : - mURI(uri) +void LLVoiceClient::setUsePTT(bool usePTT) { - mOnlineSL = false; - mOnlineSLim = false; - mCanSeeMeOnline = true; - mHasBlockListEntry = false; - mHasAutoAcceptListEntry = false; - mNameResolved = false; - mInVivoxBuddies = false; - mInSLFriends = false; - mNeedsNameUpdate = false; + if (mVoiceModule) mVoiceModule->setUsePTT(usePTT); } -void LLVoiceClient::processBuddyListEntry(const std::string &uri, const std::string &displayName) +void LLVoiceClient::setPTTIsToggle(bool PTTIsToggle) { - buddyListEntry *buddy = addBuddy(uri, displayName); - buddy->mInVivoxBuddies = true; + if (mVoiceModule) mVoiceModule->setPTTIsToggle(PTTIsToggle); } -LLVoiceClient::buddyListEntry *LLVoiceClient::addBuddy(const std::string &uri) +bool LLVoiceClient::getPTTIsToggle() { - std::string empty; - buddyListEntry *buddy = addBuddy(uri, empty); - if(buddy->mDisplayName.empty()) + if (mVoiceModule) { - buddy->mNameResolved = false; + return mVoiceModule->getPTTIsToggle(); } - return buddy; -} - -LLVoiceClient::buddyListEntry *LLVoiceClient::addBuddy(const std::string &uri, const std::string &displayName) -{ - buddyListEntry *result = NULL; - buddyListMap::iterator iter = mBuddyListMap.find(&uri); - - if(iter != mBuddyListMap.end()) - { - // Found a matching buddy already in the map. - LL_DEBUGS("Voice") << "adding existing buddy " << uri << LL_ENDL; - result = iter->second; + else { + return false; } - if(!result) - { - // participant isn't already in one list or the other. - LL_DEBUGS("Voice") << "adding new buddy " << uri << LL_ENDL; - result = new buddyListEntry(uri); - result->mDisplayName = displayName; - - if(IDFromName(uri, result->mUUID)) - { - // Extracted UUID from name successfully. - } - else - { - LL_DEBUGS("Voice") << "Couldn't find ID for buddy " << uri << " (\"" << displayName << "\")" << LL_ENDL; - } - - mBuddyListMap.insert(buddyListMap::value_type(&(result->mURI), result)); - } - - return result; } -LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddy(const std::string &uri) +void LLVoiceClient::inputUserControlState(bool down) { - buddyListEntry *result = NULL; - buddyListMap::iterator iter = mBuddyListMap.find(&uri); - if(iter != mBuddyListMap.end()) - { - result = iter->second; - } - - return result; + if (mVoiceModule) mVoiceModule->inputUserControlState(down); } -LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddy(const LLUUID &id) +void LLVoiceClient::toggleUserPTTState(void) { - buddyListEntry *result = NULL; - buddyListMap::iterator iter; - - for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++) - { - if(iter->second->mUUID == id) - { - result = iter->second; - break; - } - } - - return result; + if (mVoiceModule) mVoiceModule->toggleUserPTTState(); } -LLVoiceClient::buddyListEntry *LLVoiceClient::findBuddyByDisplayName(const std::string &name) +void LLVoiceClient::keyDown(KEY key, MASK mask) +{ + if (mVoiceModule) mVoiceModule->keyDown(key, mask); +} +void LLVoiceClient::keyUp(KEY key, MASK mask) +{ + if (mVoiceModule) mVoiceModule->keyUp(key, mask); +} +void LLVoiceClient::middleMouseState(bool down) { - buddyListEntry *result = NULL; - buddyListMap::iterator iter; + if (mVoiceModule) mVoiceModule->middleMouseState(down); +} + + +//------------------------------------------- +// nearby speaker accessors - for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++) +BOOL LLVoiceClient::getVoiceEnabled(const LLUUID& id) +{ + if (mVoiceModule) { - if(iter->second->mDisplayName == name) - { - result = iter->second; - break; - } + return mVoiceModule->getVoiceEnabled(id); + } + else + { + return FALSE; } - - return result; } -void LLVoiceClient::deleteBuddy(const std::string &uri) +std::string LLVoiceClient::getDisplayName(const LLUUID& id) { - buddyListMap::iterator iter = mBuddyListMap.find(&uri); - if(iter != mBuddyListMap.end()) + if (mVoiceModule) { - LL_DEBUGS("Voice") << "deleting buddy " << uri << LL_ENDL; - buddyListEntry *buddy = iter->second; - mBuddyListMap.erase(iter); - delete buddy; + return mVoiceModule->getDisplayName(id); } else { - LL_DEBUGS("Voice") << "attempt to delete nonexistent buddy " << uri << LL_ENDL; + return std::string(); } - } -void LLVoiceClient::deleteAllBuddies(void) +bool LLVoiceClient::isVoiceWorking() { - while(!mBuddyListMap.empty()) + if (mVoiceModule) { - deleteBuddy(*(mBuddyListMap.begin()->first)); + return mVoiceModule->isVoiceWorking(); } - - // Don't want to correlate with friends list when we've emptied the buddy list. - mBuddyListMapPopulated = false; - - // Don't want to correlate with friends list when we've reset the block rules. - mBlockRulesListReceived = false; - mAutoAcceptRulesListReceived = false; + return false; } -void LLVoiceClient::deleteAllBlockRules(void) +BOOL LLVoiceClient::isParticipantAvatar(const LLUUID& id) { - // Clear the block list entry flags from all local buddy list entries - buddyListMap::iterator buddy_it; - for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++) + if (mVoiceModule) { - buddy_it->second->mHasBlockListEntry = false; + return mVoiceModule->isParticipantAvatar(id); } -} - -void LLVoiceClient::deleteAllAutoAcceptRules(void) -{ - // Clear the auto-accept list entry flags from all local buddy list entries - buddyListMap::iterator buddy_it; - for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++) + else { - buddy_it->second->mHasAutoAcceptListEntry = false; + return FALSE; } } -void LLVoiceClient::addBlockRule(const std::string &blockMask, const std::string &presenceOnly) +BOOL LLVoiceClient::isOnlineSIP(const LLUUID& id) { - buddyListEntry *buddy = NULL; - - // blockMask is the SIP URI of a friends list entry - buddyListMap::iterator iter = mBuddyListMap.find(&blockMask); - if(iter != mBuddyListMap.end()) + if (mVoiceModule) + { + return mVoiceModule->isOnlineSIP(id); + } + else { - LL_DEBUGS("Voice") << "block list entry for " << blockMask << LL_ENDL; - buddy = iter->second; + return FALSE; } +} - if(buddy == NULL) +BOOL LLVoiceClient::getIsSpeaking(const LLUUID& id) +{ + if (mVoiceModule) { - LL_DEBUGS("Voice") << "block list entry for unknown buddy " << blockMask << LL_ENDL; - buddy = addBuddy(blockMask); + return mVoiceModule->getIsSpeaking(id); } - - if(buddy != NULL) + else { - buddy->mHasBlockListEntry = true; + return FALSE; } } -void LLVoiceClient::addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy) +BOOL LLVoiceClient::getIsModeratorMuted(const LLUUID& id) { - buddyListEntry *buddy = NULL; - - // blockMask is the SIP URI of a friends list entry - buddyListMap::iterator iter = mBuddyListMap.find(&autoAcceptMask); - if(iter != mBuddyListMap.end()) + if (mVoiceModule) { - LL_DEBUGS("Voice") << "auto-accept list entry for " << autoAcceptMask << LL_ENDL; - buddy = iter->second; + return mVoiceModule->getIsModeratorMuted(id); } - - if(buddy == NULL) + else { - LL_DEBUGS("Voice") << "auto-accept list entry for unknown buddy " << autoAcceptMask << LL_ENDL; - buddy = addBuddy(autoAcceptMask); + return FALSE; } +} - if(buddy != NULL) +F32 LLVoiceClient::getCurrentPower(const LLUUID& id) +{ + if (mVoiceModule) { - buddy->mHasAutoAcceptListEntry = true; + return mVoiceModule->getCurrentPower(id); + } + else + { + return 0.0; } } -void LLVoiceClient::accountListBlockRulesResponse(int statusCode, const std::string &statusString) -{ - // Block list entries were updated via addBlockRule() during parsing. Just flag that we're done. - mBlockRulesListReceived = true; -} - -void LLVoiceClient::accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString) +BOOL LLVoiceClient::getOnMuteList(const LLUUID& id) { - // Block list entries were updated via addBlockRule() during parsing. Just flag that we're done. - mAutoAcceptRulesListReceived = true; + if (mVoiceModule) + { + return mVoiceModule->getOnMuteList(id); + } + else + { + return FALSE; + } } -void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) +F32 LLVoiceClient::getUserVolume(const LLUUID& id) { - mParticipantObservers.insert(observer); + if (mVoiceModule) + { + return mVoiceModule->getUserVolume(id); + } + else + { + return 0.0; + } } -void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) +void LLVoiceClient::setUserVolume(const LLUUID& id, F32 volume) { - mParticipantObservers.erase(observer); + if (mVoiceModule) mVoiceModule->setUserVolume(id, volume); } -void LLVoiceClient::notifyParticipantObservers() -{ - for (observer_set_t::iterator it = mParticipantObservers.begin(); - it != mParticipantObservers.end(); - ) - { - LLVoiceClientParticipantObserver* observer = *it; - observer->onChange(); - // In case onChange() deleted an entry. - it = mParticipantObservers.upper_bound(observer); - } -} +//-------------------------------------------------- +// status observers void LLVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) { - mStatusObservers.insert(observer); + if (mVoiceModule) mVoiceModule->addObserver(observer); } void LLVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) { - mStatusObservers.erase(observer); -} - -void LLVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status) -{ - if(mAudioSession) - { - if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN) - { - switch(mAudioSession->mErrorStatusCode) - { - case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break; - case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break; - case 20715: - //invalid channel, we may be using a set of poorly cached - //info - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - case 1009: - //invalid username and password - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - break; - } - - // Reset the error code to make sure it won't be reused later by accident. - mAudioSession->mErrorStatusCode = 0; - } - else if(status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL) - { - switch(mAudioSession->mErrorStatusCode) - { - case 404: // NOT_FOUND - case 480: // TEMPORARILY_UNAVAILABLE - case 408: // REQUEST_TIMEOUT - // call failed because other user was not available - // treat this as an error case - status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; - - // Reset the error code to make sure it won't be reused later by accident. - mAudioSession->mErrorStatusCode = 0; - break; - } - } - } - - LL_DEBUGS("Voice") - << " " << LLVoiceClientStatusObserver::status2string(status) - << ", session URI " << getAudioSessionURI() - << (inSpatialChannel()?", proximal is true":", proximal is false") - << LL_ENDL; - - for (status_observer_set_t::iterator it = mStatusObservers.begin(); - it != mStatusObservers.end(); - ) - { - LLVoiceClientStatusObserver* observer = *it; - observer->onChange(status, getAudioSessionURI(), inSpatialChannel()); - // In case onError() deleted an entry. - it = mStatusObservers.upper_bound(observer); - } - + if (mVoiceModule) mVoiceModule->removeObserver(observer); } void LLVoiceClient::addObserver(LLFriendObserver* observer) { - mFriendObservers.insert(observer); + if (mVoiceModule) mVoiceModule->addObserver(observer); } void LLVoiceClient::removeObserver(LLFriendObserver* observer) { - mFriendObservers.erase(observer); + if (mVoiceModule) mVoiceModule->removeObserver(observer); } -void LLVoiceClient::notifyFriendObservers() +void LLVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) { - for (friend_observer_set_t::iterator it = mFriendObservers.begin(); - it != mFriendObservers.end(); - ) - { - LLFriendObserver* observer = *it; - it++; - // The only friend-related thing we notify on is online/offline transitions. - observer->changed(LLFriendObserver::ONLINE); - } + if (mVoiceModule) mVoiceModule->addObserver(observer); } -void LLVoiceClient::lookupName(const LLUUID &id) +void LLVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) { - BOOL is_group = FALSE; - gCacheName->get(id, is_group, &LLVoiceClient::onAvatarNameLookup); + if (mVoiceModule) mVoiceModule->removeObserver(observer); } -//static -void LLVoiceClient::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group) +std::string LLVoiceClient::sipURIFromID(const LLUUID &id) { - if(gVoiceClient) + if (mVoiceModule) + { + return mVoiceModule->sipURIFromID(id); + } + else { - std::string name = llformat("%s %s", first.c_str(), last.c_str()); - gVoiceClient->avatarNameResolved(id, name); + return std::string(); } } -void LLVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name) + +/////////////////// +// version checking + +class LLViewerRequiredVoiceVersion : public LLHTTPNode { - // If the avatar whose name just resolved is on our friends list, resync the friends list. - if(LLAvatarTracker::instance().getBuddyInfo(id) != NULL) - { - mFriendsListDirty = true; - } - - // Iterate over all sessions. - for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) + static BOOL sAlertedUser; + virtual void post( + LLHTTPNode::ResponsePtr response, + const LLSD& context, + const LLSD& input) const { - sessionState *session = *iter; - - // Check for this user as a participant in this session - participantState *participant = session->findParticipantByID(id); - if(participant) - { - // Found -- fill in the name - participant->mAccountName = name; - // and post a "participants updated" message to listeners later. - session->mParticipantsChanged = true; - } - - // Check whether this is a p2p session whose caller name just resolved - if(session->mCallerID == id) + //You received this messsage (most likely on region cross or + //teleport) + if ( input.has("body") && input["body"].has("major_version") ) { - // this session's "caller ID" just resolved. Fill in the name. - session->mName = name; - if(session->mTextInvitePending) - { - session->mTextInvitePending = false; - - // We don't need to call gIMMgr->addP2PSession() here. The first incoming message will create the panel. - } - if(session->mVoiceInvitePending) + int major_voice_version = + input["body"]["major_version"].asInteger(); + // int minor_voice_version = + // input["body"]["minor_version"].asInteger(); + LLVoiceVersionInfo versionInfo = LLVoiceClient::getInstance()->getVersion(); + + if (major_voice_version > 1) { - session->mVoiceInvitePending = false; - - gIMMgr->inviteToSession( - session->mIMSessionID, - session->mName, - session->mCallerID, - session->mName, - IM_SESSION_P2P_INVITE, - LLIMMgr::INVITATION_TYPE_VOICE, - session->mHandle, - session->mSIPURI); + if (!sAlertedUser) + { + //sAlertedUser = TRUE; + LLNotificationsUtil::add("VoiceVersionMismatch"); + gSavedSettings.setBOOL("EnableVoiceChat", FALSE); // toggles listener + } } - } } -} +}; class LLViewerParcelVoiceInfo : public LLHTTPNode { virtual void post( - LLHTTPNode::ResponsePtr response, - const LLSD& context, - const LLSD& input) const + LLHTTPNode::ResponsePtr response, + const LLSD& context, + const LLSD& input) const { //the parcel you are in has changed something about its //voice information - + //this is a misnomer, as it can also be when you are not in //a parcel at all. Should really be something like //LLViewerVoiceInfoChanged..... if ( input.has("body") ) { LLSD body = input["body"]; - + //body has "region_name" (str), "parcel_local_id"(int), //"voice_credentials" (map). - + //body["voice_credentials"] has "channel_uri" (str), //body["voice_credentials"] has "channel_credentials" (str) - + //if we really wanted to be extra careful, //we'd check the supplied //local parcel id to make sure it's for the same parcel @@ -7279,7 +764,7 @@ class LLViewerParcelVoiceInfo : public LLHTTPNode LLSD voice_credentials = body["voice_credentials"]; std::string uri; std::string credentials; - + if ( voice_credentials.has("channel_uri") ) { uri = voice_credentials["channel_uri"].asString(); @@ -7287,51 +772,96 @@ class LLViewerParcelVoiceInfo : public LLHTTPNode if ( voice_credentials.has("channel_credentials") ) { credentials = - voice_credentials["channel_credentials"].asString(); + voice_credentials["channel_credentials"].asString(); } - - gVoiceClient->setSpatialChannel(uri, credentials); + + LLVoiceClient::getInstance()->setSpatialChannel(uri, credentials); } } } }; -class LLViewerRequiredVoiceVersion : public LLHTTPNode +const std::string LLSpeakerVolumeStorage::SETTINGS_FILE_NAME = "volume_settings.xml"; + +LLSpeakerVolumeStorage::LLSpeakerVolumeStorage() { - static BOOL sAlertedUser; - virtual void post( - LLHTTPNode::ResponsePtr response, - const LLSD& context, - const LLSD& input) const + load(); +} + +LLSpeakerVolumeStorage::~LLSpeakerVolumeStorage() +{ + save(); +} + +void LLSpeakerVolumeStorage::storeSpeakerVolume(const LLUUID& speaker_id, F32 volume) +{ + mSpeakersData[speaker_id] = volume; +} + +S32 LLSpeakerVolumeStorage::getSpeakerVolume(const LLUUID& speaker_id) +{ + // Return value of -1 indicates no level is stored for this speaker + S32 ret_val = -1; + speaker_data_map_t::const_iterator it = mSpeakersData.find(speaker_id); + + if (it != mSpeakersData.end()) { - //You received this messsage (most likely on region cross or - //teleport) - if ( input.has("body") && input["body"].has("major_version") ) - { - int major_voice_version = - input["body"]["major_version"].asInteger(); -// int minor_voice_version = -// input["body"]["minor_version"].asInteger(); + F32 f_val = it->second; + // volume can amplify by as much as 4x! + S32 ivol = (S32)(400.f * f_val * f_val); + ret_val = llclamp(ivol, 0, 400); + } + return ret_val; +} - if (gVoiceClient && - (major_voice_version > VOICE_MAJOR_VERSION) ) - { - if (!sAlertedUser) - { - //sAlertedUser = TRUE; - LLNotificationsUtil::add("VoiceVersionMismatch"); - gSavedSettings.setBOOL("EnableVoiceChat", FALSE); // toggles listener - } - } +void LLSpeakerVolumeStorage::load() +{ + // load per-resident voice volume information + std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, SETTINGS_FILE_NAME); + + LLSD settings_llsd; + llifstream file; + file.open(filename); + if (file.is_open()) + { + LLSDSerialize::fromXML(settings_llsd, file); + } + + for (LLSD::map_const_iterator iter = settings_llsd.beginMap(); + iter != settings_llsd.endMap(); ++iter) + { + mSpeakersData.insert(std::make_pair(LLUUID(iter->first), (F32)iter->second.asReal())); + } +} + +void LLSpeakerVolumeStorage::save() +{ + // If we quit from the login screen we will not have an SL account + // name. Don't try to save, otherwise we'll dump a file in + // C:\Program Files\SecondLife\ or similar. JC + std::string user_dir = gDirUtilp->getLindenUserDir(); + if (!user_dir.empty()) + { + std::string filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, SETTINGS_FILE_NAME); + LLSD settings_llsd; + + for(speaker_data_map_t::const_iterator iter = mSpeakersData.begin(); iter != mSpeakersData.end(); ++iter) + { + settings_llsd[iter->first.asString()] = iter->second; } + + llofstream file; + file.open(filename); + LLSDSerialize::toPrettyXML(settings_llsd, file); } -}; +} + BOOL LLViewerRequiredVoiceVersion::sAlertedUser = FALSE; LLHTTPRegistration - gHTTPRegistrationMessageParcelVoiceInfo( - "/message/ParcelVoiceInfo"); +gHTTPRegistrationMessageParcelVoiceInfo( + "/message/ParcelVoiceInfo"); LLHTTPRegistration - gHTTPRegistrationMessageRequiredVoiceVersion( - "/message/RequiredVoiceVersion"); +gHTTPRegistrationMessageRequiredVoiceVersion( + "/message/RequiredVoiceVersion"); diff --git a/indra/newview/llvoiceclient.h b/indra/newview/llvoiceclient.h index a29c386182..f1a7d3dbec 100644 --- a/indra/newview/llvoiceclient.h +++ b/indra/newview/llvoiceclient.h @@ -17,8 +17,7 @@ * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or - * online at - * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, @@ -33,7 +32,6 @@ #define LL_VOICE_CLIENT_H class LLVOAvatar; -class LLVivoxProtocolParser; #include "lliopipe.h" #include "llpumpio.h" @@ -42,9 +40,14 @@ class LLVivoxProtocolParser; #include "v3math.h" #include "llframetimer.h" #include "llviewerregion.h" -#include "m3math.h" // LLMatrix3 +#include "llcallingcard.h" // for LLFriendObserver +#include "llsecapi.h" + +// devices + +typedef std::vector LLVoiceDeviceList; + -class LLFriendObserver; class LLVoiceClientParticipantObserver { public: @@ -52,6 +55,9 @@ public: virtual void onChange() = 0; }; + +/////////////////////////////////// +/// @class LLVoiceClientStatusObserver class LLVoiceClientStatusObserver { public: @@ -65,11 +71,7 @@ public: STATUS_JOINED, STATUS_LEFT_CHANNEL, STATUS_VOICE_DISABLED, - - // Adding STATUS_VOICE_ENABLED as pair status for STATUS_VOICE_DISABLED - // See LLVoiceClient::setVoiceEnabled() STATUS_VOICE_ENABLED, - BEGIN_ERROR_STATUS, ERROR_CHANNEL_FULL, ERROR_CHANNEL_LOCKED, @@ -83,699 +85,367 @@ public: static std::string status2string(EStatusType inStatus); }; -class LLVoiceClient: public LLSingleton +struct LLVoiceVersionInfo { - LOG_CLASS(LLVoiceClient); - public: - LLVoiceClient(); - ~LLVoiceClient(); - - public: - static void init(LLPumpIO *pump); // Call this once at application startup (creates connector) - static void terminate(); // Call this to clean up during shutdown - - protected: - bool writeString(const std::string &str); - - public: - - static F32 OVERDRIVEN_POWER_LEVEL; + std::string serverType; + std::string serverVersion; +}; - static const F32 VOLUME_MIN; - static const F32 VOLUME_DEFAULT; - static const F32 VOLUME_MAX; +////////////////////////////////// +/// @class LLVoiceModuleInterface +/// @brief Voice module interface +/// +/// Voice modules should provide an implementation for this interface. +///////////////////////////////// - void updateSettings(); // call after loading settings and whenever they change +class LLVoiceModuleInterface +{ +public: + LLVoiceModuleInterface() {} + virtual ~LLVoiceModuleInterface() {} - void getCaptureDevicesSendMessage(); - void getRenderDevicesSendMessage(); - - void clearCaptureDevices(); - void addCaptureDevice(const std::string& name); - void setCaptureDevice(const std::string& name); - - void clearRenderDevices(); - void addRenderDevice(const std::string& name); - void setRenderDevice(const std::string& name); - - void tuningStart(); - void tuningStop(); - bool inTuningMode(); - bool inTuningStates(); - - void tuningRenderStartSendMessage(const std::string& name, bool loop); - void tuningRenderStopSendMessage(); - - void tuningCaptureStartSendMessage(int duration); - void tuningCaptureStopSendMessage(); - - void tuningSetMicVolume(float volume); - void tuningSetSpeakerVolume(float volume); - float tuningGetEnergy(void); - - // This returns true when it's safe to bring up the "device settings" dialog in the prefs. - // i.e. when the daemon is running and connected, and the device lists are populated. - bool deviceSettingsAvailable(); - - // Requery the vivox daemon for the current list of input/output devices. - // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed - // (use this if you want to know when it's done). - // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. - void refreshDeviceLists(bool clearCurrentList = true); - - // Call this if the connection to the daemon terminates unexpectedly. It will attempt to reset everything and relaunch. - void daemonDied(); - - // Call this if we're just giving up on voice (can't provision an account, etc.). It will clean up and go away. - void giveUp(); - - ///////////////////////////// - // Response/Event handlers - void connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID); - void loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases); - void sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); - void sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); - void sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString); - void logoutResponse(int statusCode, std::string &statusString); - void connectorShutdownResponse(int statusCode, std::string &statusString); - - void accountLoginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state); - void mediaStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, int statusCode, std::string &statusString, int state, bool incoming); - void textStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, bool enabled, int state, bool incoming); - void sessionAddedEvent(std::string &uriString, std::string &alias, std::string &sessionHandle, std::string &sessionGroupHandle, bool isChannel, bool incoming, std::string &nameString, std::string &applicationString); - void sessionGroupAddedEvent(std::string &sessionGroupHandle); - void sessionRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle); - void participantAddedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString, std::string &displayNameString, int participantType); - void participantRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString); - void participantUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, bool isModeratorMuted, bool isSpeaking, int volume, F32 energy); - void auxAudioPropertiesEvent(F32 energy); - void buddyPresenceEvent(std::string &uriString, std::string &alias, std::string &statusString, std::string &applicationString); - void messageEvent(std::string &sessionHandle, std::string &uriString, std::string &alias, std::string &messageHeader, std::string &messageBody, std::string &applicationString); - void sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType); - void subscriptionEvent(std::string &buddyURI, std::string &subscriptionHandle, std::string &alias, std::string &displayName, std::string &applicationString, std::string &subscriptionType); - - void buddyListChanged(); - void muteListChanged(); - void updateFriends(U32 mask); - - ///////////////////////////// - // Sending updates of current state -static void updatePosition(void); - void setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot); - void setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot); - bool channelFromRegion(LLViewerRegion *region, std::string &name); - void leaveChannel(void); // call this on logout or teleport begin - - - void setMuteMic(bool muted); // Use this to mute the local mic (for when the client is minimized, etc), ignoring user PTT state. - bool getMuteMic() const; - void setUserPTTState(bool ptt); - bool getUserPTTState(); - void toggleUserPTTState(void); - void inputUserControlState(bool down); // interpret any sort of up-down mic-open control input according to ptt-toggle prefs - void setVoiceEnabled(bool enabled); - static bool voiceEnabled(); - // Checks is voice working judging from mState - // Returns true if vivox has successfully logged in and is not in error state - bool voiceWorking(); - void setUsePTT(bool usePTT); - void setPTTIsToggle(bool PTTIsToggle); - bool getPTTIsToggle(); - void setPTTKey(std::string &key); - void setEarLocation(S32 loc); - void setVoiceVolume(F32 volume); - void setMicGain(F32 volume); - void setUserVolume(const LLUUID& id, F32 volume); // sets volume for specified agent, from 0-1 (where .5 is nominal) - void setLipSyncEnabled(BOOL enabled); - BOOL lipSyncEnabled(); - - // PTT key triggering - void keyDown(KEY key, MASK mask); - void keyUp(KEY key, MASK mask); - void middleMouseState(bool down); - - // Return the version of the Vivox library - std::string getAPIVersion() const { return mAPIVersion; } - - ///////////////////////////// - // Accessors for data related to nearby speakers - BOOL getVoiceEnabled(const LLUUID& id); // true if we've received data for this avatar - BOOL getIsSpeaking(const LLUUID& id); - BOOL getIsModeratorMuted(const LLUUID& id); - F32 getCurrentPower(const LLUUID& id); // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... - BOOL getOnMuteList(const LLUUID& id); - F32 getUserVolume(const LLUUID& id); - std::string getDisplayName(const LLUUID& id); - - // MBW -- XXX -- Not sure how to get this data out of the TVC - BOOL getUsingPTT(const LLUUID& id); - std::string getGroupID(const LLUUID& id); // group ID if the user is in group chat (empty string if not applicable) - - ///////////////////////////// - BOOL getAreaVoiceDisabled(); // returns true if the area the avatar is in is speech-disabled. - // Use this to determine whether to show a "no speech" icon in the menu bar. - - ///////////////////////////// - // Recording controls - void recordingLoopStart(int seconds = 3600, int deltaFramesPerControlFrame = 200); - void recordingLoopSave(const std::string& filename); - void recordingStop(); - - // Playback controls - void filePlaybackStart(const std::string& filename); - void filePlaybackStop(); - void filePlaybackSetPaused(bool paused); - void filePlaybackSetMode(bool vox = false, float speed = 1.0f); - - - // This is used by the string-keyed maps below, to avoid storing the string twice. - // The 'const std::string *' in the key points to a string actually stored in the object referenced by the map. - // The add and delete operations for each map allocate and delete in the right order to avoid dangling references. - // The default compare operation would just compare pointers, which is incorrect, so they must use this comparitor instead. - struct stringMapComparitor - { - bool operator()(const std::string* a, const std::string * b) const - { - return a->compare(*b) < 0; - } - }; - - struct uuidMapComparitor - { - bool operator()(const LLUUID* a, const LLUUID * b) const - { - return *a < *b; - } - }; - - struct participantState - { - public: - participantState(const std::string &uri); - - bool updateMuteState(); // true if mute state has changed - bool isAvatar(); - - std::string mURI; - LLUUID mAvatarID; - std::string mAccountName; - std::string mDisplayName; - LLFrameTimer mSpeakingTimeout; - F32 mLastSpokeTimestamp; - F32 mPower; - F32 mVolume; - std::string mGroupID; - bool mPTT; - bool mIsSpeaking; - bool mIsModeratorMuted; - bool mOnMuteList; // true if this avatar is on the user's mute list (and should be muted) - bool mVolumeSet; // true if incoming volume messages should not change the volume - bool mVolumeDirty; // true if this participant needs a volume command sent (either mOnMuteList or mUserVolume has changed) - bool mAvatarIDValid; - bool mIsSelf; - }; - typedef std::map participantMap; - - typedef std::map participantUUIDMap; - - enum streamState - { - streamStateUnknown = 0, - streamStateIdle = 1, - streamStateConnected = 2, - streamStateRinging = 3, - }; - - struct sessionState - { - public: - sessionState(); - ~sessionState(); - - participantState *addParticipant(const std::string &uri); - // Note: after removeParticipant returns, the participant* that was passed to it will have been deleted. - // Take care not to use the pointer again after that. - void removeParticipant(participantState *participant); - void removeAllParticipants(); - - participantState *findParticipant(const std::string &uri); - participantState *findParticipantByID(const LLUUID& id); - - bool isCallBackPossible(); - bool isTextIMPossible(); - - std::string mHandle; - std::string mGroupHandle; - std::string mSIPURI; - std::string mAlias; - std::string mName; - std::string mAlternateSIPURI; - std::string mHash; // Channel password - std::string mErrorStatusString; - std::queue mTextMsgQueue; - - LLUUID mIMSessionID; - LLUUID mCallerID; - int mErrorStatusCode; - int mMediaStreamState; - int mTextStreamState; - bool mCreateInProgress; // True if a Session.Create has been sent for this session and no response has been received yet. - bool mMediaConnectInProgress; // True if a Session.MediaConnect has been sent for this session and no response has been received yet. - bool mVoiceInvitePending; // True if a voice invite is pending for this session (usually waiting on a name lookup) - bool mTextInvitePending; // True if a text invite is pending for this session (usually waiting on a name lookup) - bool mSynthesizedCallerID; // True if the caller ID is a hash of the SIP URI -- this means we shouldn't do a name lookup. - bool mIsChannel; // True for both group and spatial channels (false for p2p, PSTN) - bool mIsSpatial; // True for spatial channels - bool mIsP2P; - bool mIncoming; - bool mVoiceEnabled; - bool mReconnect; // Whether we should try to reconnect to this session if it's dropped - // Set to true when the mute state of someone in the participant list changes. - // The code will have to walk the list to find the changed participant(s). - bool mVolumeDirty; - bool mMuteDirty; - - bool mParticipantsChanged; - participantMap mParticipantsByURI; - participantUUIDMap mParticipantsByUUID; - }; - - participantState *findParticipantByID(const LLUUID& id); - participantMap *getParticipantList(void); - void getParticipantsUUIDSet(std::set& participant_uuids); - - typedef std::map sessionMap; - typedef std::set sessionSet; - - typedef sessionSet::iterator sessionIterator; - sessionIterator sessionsBegin(void); - sessionIterator sessionsEnd(void); - - sessionState *findSession(const std::string &handle); - sessionState *findSessionBeingCreatedByURI(const std::string &uri); - sessionState *findSession(const LLUUID &participant_id); - sessionState *findSessionByCreateID(const std::string &create_id); - - sessionState *addSession(const std::string &uri, const std::string &handle = LLStringUtil::null); - void setSessionHandle(sessionState *session, const std::string &handle = LLStringUtil::null); - void setSessionURI(sessionState *session, const std::string &uri); - void deleteSession(sessionState *session); - void deleteAllSessions(void); + virtual void init(LLPumpIO *pump)=0; // Call this once at application startup (creates connector) + virtual void terminate()=0; // Call this to clean up during shutdown + + virtual void updateSettings()=0; // call after loading settings and whenever they change + + virtual bool isVoiceWorking()=0; // connected to a voice server and voice channel - void verifySessionState(void); + virtual const LLVoiceVersionInfo& getVersion()=0; + + ///////////////////// + /// @name Tuning + //@{ + virtual void tuningStart()=0; + virtual void tuningStop()=0; + virtual bool inTuningMode()=0; + + virtual void tuningSetMicVolume(float volume)=0; + virtual void tuningSetSpeakerVolume(float volume)=0; + virtual float tuningGetEnergy(void)=0; + //@} + + ///////////////////// + /// @name Devices + //@{ + // This returns true when it's safe to bring up the "device settings" dialog in the prefs. + // i.e. when the daemon is running and connected, and the device lists are populated. + virtual bool deviceSettingsAvailable()=0; + + // Requery the vivox daemon for the current list of input/output devices. + // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed + // (use this if you want to know when it's done). + // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. + virtual void refreshDeviceLists(bool clearCurrentList = true)=0; + + virtual void setCaptureDevice(const std::string& name)=0; + virtual void setRenderDevice(const std::string& name)=0; + + virtual LLVoiceDeviceList& getCaptureDevices()=0; + virtual LLVoiceDeviceList& getRenderDevices()=0; + + virtual void getParticipantList(std::set &participants)=0; + virtual bool isParticipant(const LLUUID& speaker_id)=0; + //@} + + //////////////////////////// + /// @ name Channel stuff + //@{ + // returns true iff the user is currently in a proximal (local spatial) channel. + // Note that gestures should only fire if this returns true. + virtual bool inProximalChannel()=0; + + virtual void setNonSpatialChannel(const std::string &uri, + const std::string &credentials)=0; + + virtual void setSpatialChannel(const std::string &uri, + const std::string &credentials)=0; + + virtual void leaveNonSpatialChannel()=0; + + virtual void leaveChannel(void)=0; + + // Returns the URI of the current channel, or an empty string if not currently in a channel. + // NOTE that it will return an empty string if it's in the process of joining a channel. + virtual std::string getCurrentChannel()=0; + //@} + + + ////////////////////////// + /// @name invitations + //@{ + // start a voice channel with the specified user + virtual void callUser(const LLUUID &uuid)=0; + virtual bool answerInvite(std::string &channelHandle)=0; + virtual void declineInvite(std::string &channelHandle)=0; + //@} + + ///////////////////////// + /// @name Volume/gain + //@{ + virtual void setVoiceVolume(F32 volume)=0; + virtual void setMicGain(F32 volume)=0; + //@} + + ///////////////////////// + /// @name enable disable voice and features + //@{ + virtual bool voiceEnabled()=0; + virtual void setVoiceEnabled(bool enabled)=0; + virtual void setLipSyncEnabled(BOOL enabled)=0; + virtual BOOL lipSyncEnabled()=0; + virtual void setMuteMic(bool muted)=0; // Use this to mute the local mic (for when the client is minimized, etc), ignoring user PTT state. + //@} + + //////////////////////// + /// @name PTT + //@{ + virtual void setUserPTTState(bool ptt)=0; + virtual bool getUserPTTState()=0; + virtual void setUsePTT(bool usePTT)=0; + virtual void setPTTIsToggle(bool PTTIsToggle)=0; + virtual bool getPTTIsToggle()=0; + virtual void toggleUserPTTState(void)=0; + virtual void inputUserControlState(bool down)=0; // interpret any sort of up-down mic-open control input according to ptt-toggle prefs + + virtual void keyDown(KEY key, MASK mask)=0; + virtual void keyUp(KEY key, MASK mask)=0; + virtual void middleMouseState(bool down)=0; + //@} + + ////////////////////////// + /// @name nearby speaker accessors + //@{ + + + virtual BOOL getVoiceEnabled(const LLUUID& id)=0; // true if we've received data for this avatar + virtual std::string getDisplayName(const LLUUID& id)=0; + virtual BOOL isOnlineSIP(const LLUUID &id)=0; + virtual BOOL isParticipantAvatar(const LLUUID &id)=0; + virtual BOOL getIsSpeaking(const LLUUID& id)=0; + virtual BOOL getIsModeratorMuted(const LLUUID& id)=0; + virtual F32 getCurrentPower(const LLUUID& id)=0; // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... + virtual BOOL getOnMuteList(const LLUUID& id)=0; + virtual F32 getUserVolume(const LLUUID& id)=0; + virtual void setUserVolume(const LLUUID& id, F32 volume)=0; // set's volume for specified agent, from 0-1 (where .5 is nominal) + //@} + + ////////////////////////// + /// @name text chat + //@{ + virtual BOOL isSessionTextIMPossible(const LLUUID& id)=0; + virtual BOOL isSessionCallBackPossible(const LLUUID& id)=0; + virtual BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message)=0; + virtual void endUserIMSession(const LLUUID &uuid)=0; + //@} + + // authorize the user + virtual void userAuthorized(const std::string& user_id, + const LLUUID &agentID)=0; + + ////////////////////////////// + /// @name Status notification + //@{ + virtual void addObserver(LLVoiceClientStatusObserver* observer)=0; + virtual void removeObserver(LLVoiceClientStatusObserver* observer)=0; + virtual void addObserver(LLFriendObserver* observer)=0; + virtual void removeObserver(LLFriendObserver* observer)=0; + virtual void addObserver(LLVoiceClientParticipantObserver* observer)=0; + virtual void removeObserver(LLVoiceClientParticipantObserver* observer)=0; + //@} + + virtual std::string sipURIFromID(const LLUUID &id)=0; + //@} + +}; - void joinedAudioSession(sessionState *session); - void leftAudioSession(sessionState *session); - // This is called in several places where the session _may_ need to be deleted. - // It contains logic for whether to delete the session or keep it around. - void reapSession(sessionState *session); - - // Returns true if the session seems to indicate we've moved to a region on a different voice server - bool sessionNeedsRelog(sessionState *session); - - struct buddyListEntry - { - buddyListEntry(const std::string &uri); - std::string mURI; - std::string mDisplayName; - LLUUID mUUID; - bool mOnlineSL; - bool mOnlineSLim; - bool mCanSeeMeOnline; - bool mHasBlockListEntry; - bool mHasAutoAcceptListEntry; - bool mNameResolved; - bool mInSLFriends; - bool mInVivoxBuddies; - bool mNeedsNameUpdate; - }; - - typedef std::map buddyListMap; - - // This should be called when parsing a buddy list entry sent by SLVoice. - void processBuddyListEntry(const std::string &uri, const std::string &displayName); - - buddyListEntry *addBuddy(const std::string &uri); - buddyListEntry *addBuddy(const std::string &uri, const std::string &displayName); - buddyListEntry *findBuddy(const std::string &uri); - buddyListEntry *findBuddy(const LLUUID &id); - buddyListEntry *findBuddyByDisplayName(const std::string &name); - void deleteBuddy(const std::string &uri); - void deleteAllBuddies(void); - - void deleteAllBlockRules(void); - void addBlockRule(const std::string &blockMask, const std::string &presenceOnly); - void deleteAllAutoAcceptRules(void); - void addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy); - void accountListBlockRulesResponse(int statusCode, const std::string &statusString); - void accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString); - - ///////////////////////////// - // session control messages - void connectorCreate(); - void connectorShutdown(); - - void requestVoiceAccountProvision(S32 retries = 3); - void userAuthorized( - const std::string& firstName, - const std::string& lastName, - const LLUUID &agentID); - void login( - const std::string& account_name, - const std::string& password, - const std::string& voice_sip_uri_hostname, - const std::string& voice_account_server_uri); - void loginSendMessage(); - void logout(); - void logoutSendMessage(); - - void accountListBlockRulesSendMessage(); - void accountListAutoAcceptRulesSendMessage(); - - void sessionGroupCreateSendMessage(); - void sessionCreateSendMessage(sessionState *session, bool startAudio = true, bool startText = false); - void sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio = true, bool startText = false); - void sessionMediaConnectSendMessage(sessionState *session); // just joins the audio session - void sessionTextConnectSendMessage(sessionState *session); // just joins the text session - void sessionTerminateSendMessage(sessionState *session); - void sessionGroupTerminateSendMessage(sessionState *session); - void sessionMediaDisconnectSendMessage(sessionState *session); - void sessionTextDisconnectSendMessage(sessionState *session); - - // Pokes the state machine to leave the audio session next time around. - void sessionTerminate(); - - // Pokes the state machine to shut down the connector and restart it. - void requestRelog(); - - // Does the actual work to get out of the audio session - void leaveAudioSession(); - - void addObserver(LLVoiceClientParticipantObserver* observer); - void removeObserver(LLVoiceClientParticipantObserver* observer); +class LLVoiceClient: public LLSingleton +{ + LOG_CLASS(LLVoiceClient); +public: + LLVoiceClient(); + ~LLVoiceClient(); - void addObserver(LLVoiceClientStatusObserver* observer); - void removeObserver(LLVoiceClientStatusObserver* observer); + void init(LLPumpIO *pump); // Call this once at application startup (creates connector) + void terminate(); // Call this to clean up during shutdown + + const LLVoiceVersionInfo getVersion(); + +static const F32 OVERDRIVEN_POWER_LEVEL; - void addObserver(LLFriendObserver* observer); - void removeObserver(LLFriendObserver* observer); - - void lookupName(const LLUUID &id); - static void onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group); - void avatarNameResolved(const LLUUID &id, const std::string &name); - - typedef std::vector deviceList; + void updateSettings(); // call after loading settings and whenever they change - deviceList *getCaptureDevices(); - deviceList *getRenderDevices(); - - void setNonSpatialChannel( - const std::string &uri, - const std::string &credentials); - void setSpatialChannel( - const std::string &uri, - const std::string &credentials); - // start a voice session with the specified user - void callUser(const LLUUID &uuid); - - // Send a text message to the specified user, initiating the session if necessary. - bool sendTextMessage(const LLUUID& participant_id, const std::string& message); - - // close any existing text IM session with the specified user - void endUserIMSession(const LLUUID &uuid); - - bool answerInvite(std::string &sessionHandle); - void declineInvite(std::string &sessionHandle); - void leaveNonSpatialChannel(); + bool isVoiceWorking(); // connected to a voice server and voice channel - // Returns the URI of the current channel, or an empty string if not currently in a channel. - // NOTE that it will return an empty string if it's in the process of joining a channel. - std::string getCurrentChannel(); + // tuning + void tuningStart(); + void tuningStop(); + bool inTuningMode(); - // returns true iff the user is currently in a proximal (local spatial) channel. - // Note that gestures should only fire if this returns true. - bool inProximalChannel(); - - std::string sipURIFromID(const LLUUID &id); + void tuningSetMicVolume(float volume); + void tuningSetSpeakerVolume(float volume); + float tuningGetEnergy(void); - // Returns true if the indicated user is online via SIP presence according to SLVoice. - // Note that we only get SIP presence data for other users that are in our vivox buddy list. - bool isOnlineSIP(const LLUUID &id); - - // Returns true if the indicated participant is really an SL avatar. - // This should be used to control the state of the "profile" button. - // Currently this will be false only for PSTN callers into group chats, and PSTN p2p calls. - bool isParticipantAvatar(const LLUUID &id); - - // Returns true if calling back the session URI after the session has closed is possible. - // Currently this will be false only for PSTN P2P calls. - // NOTE: this will return true if the session can't be found. - bool isSessionCallBackPossible(const LLUUID &session_id); - - // Returns true if the session can accepte text IM's. - // Currently this will be false only for PSTN P2P calls. - // NOTE: this will return true if the session can't be found. - bool isSessionTextIMPossible(const LLUUID &session_id); - - private: - - // internal state for a simple state machine. This is used to deal with the asynchronous nature of some of the messages. - // Note: if you change this list, please make corresponding changes to LLVoiceClient::state2string(). - enum state - { - stateDisableCleanup, - stateDisabled, // Voice is turned off. - stateStart, // Class is initialized, socket is created - stateDaemonLaunched, // Daemon has been launched - stateConnecting, // connect() call has been issued - stateConnected, // connection to the daemon has been made, send some initial setup commands. - stateIdle, // socket is connected, ready for messaging - stateMicTuningStart, - stateMicTuningRunning, - stateMicTuningStop, - stateConnectorStart, // connector needs to be started - stateConnectorStarting, // waiting for connector handle - stateConnectorStarted, // connector handle received - stateLoginRetry, // need to retry login (failed due to changing password) - stateLoginRetryWait, // waiting for retry timer - stateNeedsLogin, // send login request - stateLoggingIn, // waiting for account handle - stateLoggedIn, // account handle received - stateCreatingSessionGroup, // Creating the main session group - stateNoChannel, // - stateJoiningSession, // waiting for session handle - stateSessionJoined, // session handle received - stateRunning, // in session, steady state - stateLeavingSession, // waiting for terminate session response - stateSessionTerminated, // waiting for terminate session response - - stateLoggingOut, // waiting for logout response - stateLoggedOut, // logout response received - stateConnectorStopping, // waiting for connector stop - stateConnectorStopped, // connector stop received - - // We go to this state if the login fails because the account needs to be provisioned. - - // error states. No way to recover from these yet. - stateConnectorFailed, - stateConnectorFailedWaiting, - stateLoginFailed, - stateLoginFailedWaiting, - stateJoinSessionFailed, - stateJoinSessionFailedWaiting, - - stateJail // Go here when all else has failed. Nothing will be retried, we're done. - }; - - state mState; - bool mSessionTerminateRequested; - bool mRelogRequested; - // Number of times (in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine(). - // The larger it is the greater is possibility there is a problem with connection to voice server. - // Introduced while fixing EXT-4313. - int mSpatialJoiningNum; - - void setState(state inState); - state getState(void) { return mState; }; - static std::string state2string(state inState); - - void stateMachine(); - static void idle(void *user_data); - - LLHost mDaemonHost; - LLSocket::ptr_t mSocket; - bool mConnected; - - void closeSocket(void); - - LLPumpIO *mPump; - friend class LLVivoxProtocolParser; - - std::string mAccountName; - std::string mAccountPassword; - std::string mAccountDisplayName; - std::string mAccountFirstName; - std::string mAccountLastName; - - bool mTuningMode; - float mTuningEnergy; - std::string mTuningAudioFile; - int mTuningMicVolume; - bool mTuningMicVolumeDirty; - int mTuningSpeakerVolume; - bool mTuningSpeakerVolumeDirty; - state mTuningExitState; // state to return to when we leave tuning mode. + // devices + + // This returns true when it's safe to bring up the "device settings" dialog in the prefs. + // i.e. when the daemon is running and connected, and the device lists are populated. + bool deviceSettingsAvailable(); - std::string mSpatialSessionURI; - std::string mSpatialSessionCredentials; + // Requery the vivox daemon for the current list of input/output devices. + // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed + // (use this if you want to know when it's done). + // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. + void refreshDeviceLists(bool clearCurrentList = true); - std::string mMainSessionGroupHandle; // handle of the "main" session group. - - std::string mChannelName; // Name of the channel to be looked up - bool mAreaVoiceDisabled; - sessionState *mAudioSession; // Session state for the current audio session - bool mAudioSessionChanged; // set to true when the above pointer gets changed, so observers can be notified. + void setCaptureDevice(const std::string& name); + void setRenderDevice(const std::string& name); - sessionState *mNextAudioSession; // Session state for the audio session we're trying to join + const LLVoiceDeviceList& getCaptureDevices(); + const LLVoiceDeviceList& getRenderDevices(); -// std::string mSessionURI; // URI of the session we're in. -// std::string mSessionHandle; // returned by ? - - S32 mCurrentParcelLocalID; // Used to detect parcel boundary crossings - std::string mCurrentRegionName; // Used to detect parcel boundary crossings - - std::string mConnectorHandle; // returned by "Create Connector" message - std::string mAccountHandle; // returned by login message - int mNumberOfAliases; - U32 mCommandCookie; + //////////////////////////// + // Channel stuff + // + + // returns true iff the user is currently in a proximal (local spatial) channel. + // Note that gestures should only fire if this returns true. + bool inProximalChannel(); + void setNonSpatialChannel( + const std::string &uri, + const std::string &credentials); + void setSpatialChannel( + const std::string &uri, + const std::string &credentials); + void leaveNonSpatialChannel(); + + // Returns the URI of the current channel, or an empty string if not currently in a channel. + // NOTE that it will return an empty string if it's in the process of joining a channel. + std::string getCurrentChannel(); + // start a voice channel with the specified user + void callUser(const LLUUID &uuid); + bool answerInvite(std::string &channelHandle); + void declineInvite(std::string &channelHandle); + void leaveChannel(void); // call this on logout or teleport begin + + + ///////////////////////////// + // Sending updates of current state - std::string mVoiceAccountServerURI; - std::string mVoiceSIPURIHostName; - - int mLoginRetryCount; - - sessionMap mSessionsByHandle; // Active sessions, indexed by session handle. Sessions which are being initiated may not be in this map. - sessionSet mSessions; // All sessions, not indexed. This is the canonical session list. - - bool mBuddyListMapPopulated; - bool mBlockRulesListReceived; - bool mAutoAcceptRulesListReceived; - buddyListMap mBuddyListMap; - - deviceList mCaptureDevices; - deviceList mRenderDevices; - - std::string mCaptureDevice; - std::string mRenderDevice; - bool mCaptureDeviceDirty; - bool mRenderDeviceDirty; - - // This should be called when the code detects we have changed parcels. - // It initiates the call to the server that gets the parcel channel. - void parcelChanged(); - - void switchChannel(std::string uri = std::string(), bool spatial = true, bool no_reconnect = false, bool is_p2p = false, std::string hash = ""); - void joinSession(sessionState *session); - -static std::string nameFromAvatar(LLVOAvatar *avatar); -static std::string nameFromID(const LLUUID &id); -static bool IDFromName(const std::string name, LLUUID &uuid); -static std::string displayNameFromAvatar(LLVOAvatar *avatar); - std::string sipURIFromAvatar(LLVOAvatar *avatar); - std::string sipURIFromName(std::string &name); - - // Returns the name portion of the SIP URI if the string looks vaguely like a SIP URI, or an empty string if not. -static std::string nameFromsipURI(const std::string &uri); - bool inSpatialChannel(void); - std::string getAudioSessionURI(); - std::string getAudioSessionHandle(); - - void sendPositionalUpdate(void); - - void buildSetCaptureDevice(std::ostringstream &stream); - void buildSetRenderDevice(std::ostringstream &stream); - void buildLocalAudioUpdates(std::ostringstream &stream); - - void clearAllLists(); - void checkFriend(const LLUUID& id); - void sendFriendsListUpdates(); - - // start a text IM session with the specified user - // This will be asynchronous, the session may be established at a future time. - sessionState* startUserIMSession(const LLUUID& uuid); - void sendQueuedTextMessages(sessionState *session); - - void enforceTether(void); - - bool mSpatialCoordsDirty; - - LLVector3d mCameraPosition; - LLVector3d mCameraRequestedPosition; - LLVector3 mCameraVelocity; - LLMatrix3 mCameraRot; - - LLVector3d mAvatarPosition; - LLVector3 mAvatarVelocity; - LLMatrix3 mAvatarRot; - - bool mPTTDirty; - bool mPTT; - - bool mUsePTT; - bool mPTTIsMiddleMouse; - KEY mPTTKey; - bool mPTTIsToggle; - bool mUserPTTState; - bool mMuteMic; - - // Set to true when the friends list is known to have changed. - bool mFriendsListDirty; - - enum - { - earLocCamera = 0, // ear at camera - earLocAvatar, // ear at avatar - earLocMixed // ear at avatar location/camera direction - }; - - S32 mEarLocation; - - bool mSpeakerVolumeDirty; - bool mSpeakerMuteDirty; - int mSpeakerVolume; + void setVoiceVolume(F32 volume); + void setMicGain(F32 volume); + void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal) + bool voiceEnabled(); + void setLipSyncEnabled(BOOL enabled); + void setMuteMic(bool muted); // Use this to mute the local mic (for when the client is minimized, etc), ignoring user PTT state. + void setUserPTTState(bool ptt); + bool getUserPTTState(); + void toggleUserPTTState(void); + void inputUserControlState(bool down); // interpret any sort of up-down mic-open control input according to ptt-toggle prefs + void setVoiceEnabled(bool enabled); + + void setUsePTT(bool usePTT); + void setPTTIsToggle(bool PTTIsToggle); + bool getPTTIsToggle(); + + BOOL lipSyncEnabled(); + + // PTT key triggering + void keyDown(KEY key, MASK mask); + void keyUp(KEY key, MASK mask); + void middleMouseState(bool down); + + + ///////////////////////////// + // Accessors for data related to nearby speakers + BOOL getVoiceEnabled(const LLUUID& id); // true if we've received data for this avatar + std::string getDisplayName(const LLUUID& id); + BOOL isOnlineSIP(const LLUUID &id); + BOOL isParticipantAvatar(const LLUUID &id); + BOOL getIsSpeaking(const LLUUID& id); + BOOL getIsModeratorMuted(const LLUUID& id); + F32 getCurrentPower(const LLUUID& id); // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... + BOOL getOnMuteList(const LLUUID& id); + F32 getUserVolume(const LLUUID& id); + + ///////////////////////////// + BOOL getAreaVoiceDisabled(); // returns true if the area the avatar is in is speech-disabled. + // Use this to determine whether to show a "no speech" icon in the menu bar. + void getParticipantList(std::set &participants); + bool isParticipant(const LLUUID& speaker_id); + + ////////////////////////// + /// @name text chat + //@{ + BOOL isSessionTextIMPossible(const LLUUID& id); + BOOL isSessionCallBackPossible(const LLUUID& id); + BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message); + void endUserIMSession(const LLUUID &uuid); + //@} + - int mMicVolume; - bool mMicVolumeDirty; - - bool mVoiceEnabled; - bool mWriteInProgress; - std::string mWriteString; - - LLTimer mUpdateTimer; + void userAuthorized(const std::string& user_id, + const LLUUID &agentID); + + void addObserver(LLVoiceClientStatusObserver* observer); + void removeObserver(LLVoiceClientStatusObserver* observer); + void addObserver(LLFriendObserver* observer); + void removeObserver(LLFriendObserver* observer); + void addObserver(LLVoiceClientParticipantObserver* observer); + void removeObserver(LLVoiceClientParticipantObserver* observer); + + std::string sipURIFromID(const LLUUID &id); - BOOL mLipSyncEnabled; - - std::string mAPIVersion; - - typedef std::set observer_set_t; - observer_set_t mParticipantObservers; +protected: + LLVoiceModuleInterface* mVoiceModule; + LLPumpIO *m_servicePump; +}; - void notifyParticipantObservers(); +/** + * Speaker volume storage helper class + **/ - typedef std::set status_observer_set_t; - status_observer_set_t mStatusObservers; - - void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status); +class LLSpeakerVolumeStorage : public LLSingleton +{ + LOG_CLASS(LLSpeakerVolumeStorage); +public: - typedef std::set friend_observer_set_t; - friend_observer_set_t mFriendObservers; - void notifyFriendObservers(); + /** + * Sets internal voluem level for specified user. + * + * @param[in] speaker_id - LLUUID of user to store volume level for + * @param[in] volume - external volume level to be stored for user. + */ + void storeSpeakerVolume(const LLUUID& speaker_id, F32 volume); + + /** + * Gets stored external volume level for specified speaker. + * + * If specified user is not found default level will be returned. It is equivalent of + * external level 0.5 from the 0.0..1.0 range. + * Default external level is calculated as: internal = 400 * external^2 + * Maps 0.0 to 1.0 to internal values 0-400 with default 0.5 == 100 + * + * @param[in] speaker_id - LLUUID of user to get his volume level + */ + S32 getSpeakerVolume(const LLUUID& speaker_id); + +private: + friend class LLSingleton; + LLSpeakerVolumeStorage(); + ~LLSpeakerVolumeStorage(); + + const static std::string SETTINGS_FILE_NAME; + + void load(); + void save(); + + typedef std::map speaker_data_map_t; + speaker_data_map_t mSpeakersData; }; -extern LLVoiceClient *gVoiceClient; - #endif //LL_VOICE_CLIENT_H diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp new file mode 100644 index 0000000000..a7efc49c67 --- /dev/null +++ b/indra/newview/llvoicevivox.cpp @@ -0,0 +1,6967 @@ + /** + * @file LLVivoxVoiceClient.cpp + * @brief Implementation of LLVivoxVoiceClient class which is the interface to the voice client process. + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ + +#include "llviewerprecompiledheaders.h" +#include "llvoicevivox.h" + +#include + +#include "llsdutil.h" + +#include "llvoavatarself.h" +#include "llbufferstream.h" +#include "llfile.h" +#ifdef LL_STANDALONE +# include "expat.h" +#else +# include "expat/expat.h" +#endif +#include "llcallbacklist.h" +#include "llviewerregion.h" +#include "llviewernetwork.h" // for gGridChoice +#include "llbase64.h" +#include "llviewercontrol.h" +#include "llkeyboard.h" +#include "llappviewer.h" // for gDisconnected, gDisableVoice +#include "llmutelist.h" // to check for muted avatars +#include "llagent.h" +#include "llcachename.h" +#include "llimview.h" // for LLIMMgr +#include "llparcel.h" +#include "llviewerparcelmgr.h" +//#include "llfirstuse.h" +#include "llspeakers.h" +#include "llviewerwindow.h" +#include "llviewercamera.h" + +#include "llfloaterfriends.h" //VIVOX, inorder to refresh communicate panel +#include "llviewernetwork.h" +#include "llnotificationsutil.h" + +// for base64 decoding +#include "apr_base64.h" + +// for SHA1 hash +#include "apr_sha1.h" + +// for MD5 hash +#include "llmd5.h" + +#define USE_SESSION_GROUPS 0 + +const F32 SPEAKING_TIMEOUT = 1.f; + +static const std::string VOICE_SERVER_TYPE = "Vivox"; + +// Don't retry connecting to the daemon more frequently than this: +const F32 CONNECT_THROTTLE_SECONDS = 1.0f; + +// Don't send positional updates more frequently than this: +const F32 UPDATE_THROTTLE_SECONDS = 0.1f; + +const F32 LOGIN_RETRY_SECONDS = 10.0f; +const int MAX_LOGIN_RETRIES = 12; + +// Defines the maximum number of times(in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine() +// which is treated as normal. If this number is exceeded we suspect there is a problem with connection +// to voice server (EXT-4313). When voice works correctly, there is from 1 to 15 times. 50 was chosen +// to make sure we don't make mistake when slight connection problems happen- situation when connection to server is +// blocked is VERY rare and it's better to sacrifice response time in this situation for the sake of stability. +const int MAX_NORMAL_JOINING_SPATIAL_NUM = 50; + + +static void setUUIDFromStringHash(LLUUID &uuid, const std::string &str) +{ + LLMD5 md5_uuid; + md5_uuid.update((const unsigned char*)str.data(), str.size()); + md5_uuid.finalize(); + md5_uuid.raw_digest(uuid.mData); +} + +static int scale_mic_volume(float volume) +{ + // incoming volume has the range [0.0 ... 2.0], with 1.0 as the default. + // Map it to Vivox levels as follows: 0.0 -> 30, 1.0 -> 50, 2.0 -> 70 + return 30 + (int)(volume * 20.0f); +} + +static int scale_speaker_volume(float volume) +{ + // incoming volume has the range [0.0 ... 1.0], with 0.5 as the default. + // Map it to Vivox levels as follows: 0.0 -> 30, 0.5 -> 50, 1.0 -> 70 + return 30 + (int)(volume * 40.0f); + +} + +class LLVivoxVoiceAccountProvisionResponder : + public LLHTTPClient::Responder +{ +public: + LLVivoxVoiceAccountProvisionResponder(int retries) + { + mRetries = retries; + } + + virtual void error(U32 status, const std::string& reason) + { + if ( mRetries > 0 ) + { + LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, retrying. status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL; + LLVivoxVoiceClient::getInstance()->requestVoiceAccountProvision( + mRetries - 1); + } + else + { + LL_WARNS("Voice") << "ProvisionVoiceAccountRequest returned an error, too many retries (giving up). status = " << status << ", reason = \"" << reason << "\"" << LL_ENDL; + LLVivoxVoiceClient::getInstance()->giveUp(); + } + } + + virtual void result(const LLSD& content) + { + + std::string voice_sip_uri_hostname; + std::string voice_account_server_uri; + + LL_DEBUGS("Voice") << "ProvisionVoiceAccountRequest response:" << ll_pretty_print_sd(content) << LL_ENDL; + + if(content.has("voice_sip_uri_hostname")) + voice_sip_uri_hostname = content["voice_sip_uri_hostname"].asString(); + + // this key is actually misnamed -- it will be an entire URI, not just a hostname. + if(content.has("voice_account_server_name")) + voice_account_server_uri = content["voice_account_server_name"].asString(); + + LLVivoxVoiceClient::getInstance()->login( + content["username"].asString(), + content["password"].asString(), + voice_sip_uri_hostname, + voice_account_server_uri); + + } + +private: + int mRetries; +}; + + + +/////////////////////////////////////////////////////////////////////////////////////////////// + +class LLVivoxVoiceClientMuteListObserver : public LLMuteListObserver +{ + /* virtual */ void onChange() { LLVivoxVoiceClient::getInstance()->muteListChanged();} +}; + +class LLVivoxVoiceClientFriendsObserver : public LLFriendObserver +{ +public: + /* virtual */ void changed(U32 mask) { LLVivoxVoiceClient::getInstance()->updateFriends(mask);} +}; + +static LLVivoxVoiceClientMuteListObserver mutelist_listener; +static bool sMuteListListener_listening = false; + +static LLVivoxVoiceClientFriendsObserver *friendslist_listener = NULL; + +/////////////////////////////////////////////////////////////////////////////////////////////// + +class LLVivoxVoiceClientCapResponder : public LLHTTPClient::Responder +{ +public: + LLVivoxVoiceClientCapResponder(void){}; + + virtual void error(U32 status, const std::string& reason); // called with bad status codes + virtual void result(const LLSD& content); + +private: +}; + +void LLVivoxVoiceClientCapResponder::error(U32 status, const std::string& reason) +{ + LL_WARNS("Voice") << "LLVivoxVoiceClientCapResponder::error(" + << status << ": " << reason << ")" + << LL_ENDL; +} + +void LLVivoxVoiceClientCapResponder::result(const LLSD& content) +{ + LLSD::map_const_iterator iter; + + LL_DEBUGS("Voice") << "ParcelVoiceInfoRequest response:" << ll_pretty_print_sd(content) << LL_ENDL; + + if ( content.has("voice_credentials") ) + { + LLSD voice_credentials = content["voice_credentials"]; + std::string uri; + std::string credentials; + + if ( voice_credentials.has("channel_uri") ) + { + uri = voice_credentials["channel_uri"].asString(); + } + if ( voice_credentials.has("channel_credentials") ) + { + credentials = + voice_credentials["channel_credentials"].asString(); + } + + LLVivoxVoiceClient::getInstance()->setSpatialChannel(uri, credentials); + } +} + + + +#if LL_WINDOWS +static HANDLE sGatewayHandle = 0; + +static bool isGatewayRunning() +{ + bool result = false; + if(sGatewayHandle != 0) + { + DWORD waitresult = WaitForSingleObject(sGatewayHandle, 0); + if(waitresult != WAIT_OBJECT_0) + { + result = true; + } + } + return result; +} +static void killGateway() +{ + if(sGatewayHandle != 0) + { + TerminateProcess(sGatewayHandle,0); + } +} + +#else // Mac and linux + +static pid_t sGatewayPID = 0; +static bool isGatewayRunning() +{ + bool result = false; + if(sGatewayPID != 0) + { + // A kill with signal number 0 has no effect, just does error checking. It should return an error if the process no longer exists. + if(kill(sGatewayPID, 0) == 0) + { + result = true; + } + } + return result; +} + +static void killGateway() +{ + if(sGatewayPID != 0) + { + kill(sGatewayPID, SIGTERM); + } +} + +#endif + +/////////////////////////////////////////////////////////////////////////////////////////////// + +LLVivoxVoiceClient::LLVivoxVoiceClient() : + mState(stateDisabled), + mSessionTerminateRequested(false), + mRelogRequested(false), + mConnected(false), + mPump(NULL), + mSpatialJoiningNum(0), + + mTuningMode(false), + mTuningEnergy(0.0f), + mTuningMicVolume(0), + mTuningMicVolumeDirty(true), + mTuningSpeakerVolume(0), + mTuningSpeakerVolumeDirty(true), + mTuningExitState(stateDisabled), + + mAreaVoiceDisabled(false), + mAudioSession(NULL), + mAudioSessionChanged(false), + mNextAudioSession(NULL), + + mCurrentParcelLocalID(0), + mNumberOfAliases(0), + mCommandCookie(0), + mLoginRetryCount(0), + + mBuddyListMapPopulated(false), + mBlockRulesListReceived(false), + mAutoAcceptRulesListReceived(false), + mCaptureDeviceDirty(false), + mRenderDeviceDirty(false), + mSpatialCoordsDirty(false), + + mPTTDirty(true), + mPTT(true), + mUsePTT(true), + mPTTIsMiddleMouse(false), + mPTTKey(0), + mPTTIsToggle(false), + mUserPTTState(false), + mMuteMic(false), + mFriendsListDirty(true), + + mEarLocation(0), + mSpeakerVolumeDirty(true), + mSpeakerMuteDirty(true), + mMicVolume(0), + mMicVolumeDirty(true), + + mVoiceEnabled(false), + mWriteInProgress(false), + + mLipSyncEnabled(false) + + + +{ + mSpeakerVolume = scale_speaker_volume(0); + + mVoiceVersion.serverVersion = ""; + mVoiceVersion.serverType = VOICE_SERVER_TYPE; + + // gMuteListp isn't set up at this point, so we defer this until later. +// gMuteListp->addObserver(&mutelist_listener); + + +#if LL_DARWIN || LL_LINUX || LL_SOLARIS + // HACK: THIS DOES NOT BELONG HERE + // When the vivox daemon dies, the next write attempt on our socket generates a SIGPIPE, which kills us. + // This should cause us to ignore SIGPIPE and handle the error through proper channels. + // This should really be set up elsewhere. Where should it go? + signal(SIGPIPE, SIG_IGN); + + // Since we're now launching the gateway with fork/exec instead of system(), we need to deal with zombie processes. + // Ignoring SIGCHLD should prevent zombies from being created. Alternately, we could use wait(), but I'd rather not do that. + signal(SIGCHLD, SIG_IGN); +#endif + + // set up state machine + setState(stateDisabled); + + gIdleCallbacks.addFunction(idle, this); +} + +//--------------------------------------------------- + +LLVivoxVoiceClient::~LLVivoxVoiceClient() +{ +} + +//---------------------------------------------- + +void LLVivoxVoiceClient::init(LLPumpIO *pump) +{ + // constructor will set up LLVoiceClient::getInstance() + LLVivoxVoiceClient::getInstance()->mPump = pump; +} + +void LLVivoxVoiceClient::terminate() +{ + +// leaveAudioSession(); + logout(); + // As of SDK version 4885, this should no longer be necessary. It will linger after the socket close if it needs to. + // ms_sleep(2000); + connectorShutdown(); + closeSocket(); // Need to do this now -- bad things happen if the destructor does it later. + + // This will do unpleasant things on windows. +// killGateway(); + + + +} + +const LLVoiceVersionInfo& LLVivoxVoiceClient::getVersion() +{ + return mVoiceVersion; +} + +//--------------------------------------------------- + +void LLVivoxVoiceClient::updateSettings() +{ + setVoiceEnabled(gSavedSettings.getBOOL("EnableVoiceChat")); + setUsePTT(gSavedSettings.getBOOL("PTTCurrentlyEnabled")); + std::string keyString = gSavedSettings.getString("PushToTalkButton"); + setPTTKey(keyString); + setPTTIsToggle(gSavedSettings.getBOOL("PushToTalkToggle")); + setEarLocation(gSavedSettings.getS32("VoiceEarLocation")); + + std::string inputDevice = gSavedSettings.getString("VoiceInputAudioDevice"); + setCaptureDevice(inputDevice); + std::string outputDevice = gSavedSettings.getString("VoiceOutputAudioDevice"); + setRenderDevice(outputDevice); + F32 mic_level = gSavedSettings.getF32("AudioLevelMic"); + setMicGain(mic_level); + setLipSyncEnabled(gSavedSettings.getBOOL("LipSyncEnabled")); +} + +///////////////////////////// +// utility functions + +bool LLVivoxVoiceClient::writeString(const std::string &str) +{ + bool result = false; + if(mConnected) + { + apr_status_t err; + apr_size_t size = (apr_size_t)str.size(); + apr_size_t written = size; + + //MARK: Turn this on to log outgoing XML +// LL_DEBUGS("Voice") << "sending: " << str << LL_ENDL; + + // check return code - sockets will fail (broken, etc.) + err = apr_socket_send( + mSocket->getSocket(), + (const char*)str.data(), + &written); + + if(err == 0) + { + // Success. + result = true; + } + // TODO: handle partial writes (written is number of bytes written) + // Need to set socket to non-blocking before this will work. +// else if(APR_STATUS_IS_EAGAIN(err)) +// { +// // +// } + else + { + // Assume any socket error means something bad. For now, just close the socket. + char buf[MAX_STRING]; + LL_WARNS("Voice") << "apr error " << err << " ("<< apr_strerror(err, buf, MAX_STRING) << ") sending data to vivox daemon." << LL_ENDL; + daemonDied(); + } + } + + return result; +} + + +///////////////////////////// +// session control messages +void LLVivoxVoiceClient::connectorCreate() +{ + std::ostringstream stream; + std::string logpath = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); + std::string loglevel = "0"; + + // Transition to stateConnectorStarted when the connector handle comes back. + setState(stateConnectorStarting); + + std::string savedLogLevel = gSavedSettings.getString("VivoxDebugLevel"); + + if(savedLogLevel != "-1") + { + LL_DEBUGS("Voice") << "creating connector with logging enabled" << LL_ENDL; + loglevel = "10"; + } + + stream + << "" + << "V2 SDK" + << "" << mVoiceAccountServerURI << "" + << "Normal" + << "" + << "" << logpath << "" + << "Connector" + << ".log" + << "" << loglevel << "" + << "" + << "SecondLifeViewer.1" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::connectorShutdown() +{ + setState(stateConnectorStopping); + + if(!mConnectorHandle.empty()) + { + std::ostringstream stream; + stream + << "" + << "" << mConnectorHandle << "" + << "" + << "\n\n\n"; + + mConnectorHandle.clear(); + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::userAuthorized(const std::string& user_id, const LLUUID &agentID) +{ + + mAccountDisplayName = user_id; + + LL_INFOS("Voice") << "name \"" << mAccountDisplayName << "\" , ID " << agentID << LL_ENDL; + + mAccountName = nameFromID(agentID); +} + +void LLVivoxVoiceClient::requestVoiceAccountProvision(S32 retries) +{ + if ( gAgent.getRegion() && mVoiceEnabled ) + { + std::string url = + gAgent.getRegion()->getCapability( + "ProvisionVoiceAccountRequest"); + + if ( url == "" ) return; + + LLHTTPClient::post( + url, + LLSD(), + new LLVivoxVoiceAccountProvisionResponder(retries)); + } +} + +void LLVivoxVoiceClient::login( + const std::string& account_name, + const std::string& password, + const std::string& voice_sip_uri_hostname, + const std::string& voice_account_server_uri) +{ + mVoiceSIPURIHostName = voice_sip_uri_hostname; + mVoiceAccountServerURI = voice_account_server_uri; + + if(!mAccountHandle.empty()) + { + // Already logged in. + LL_WARNS("Voice") << "Called while already logged in." << LL_ENDL; + + // Don't process another login. + return; + } + else if ( account_name != mAccountName ) + { + //TODO: error? + LL_WARNS("Voice") << "Wrong account name! " << account_name + << " instead of " << mAccountName << LL_ENDL; + } + else + { + mAccountPassword = password; + } + + std::string debugSIPURIHostName = gSavedSettings.getString("VivoxDebugSIPURIHostName"); + + if( !debugSIPURIHostName.empty() ) + { + mVoiceSIPURIHostName = debugSIPURIHostName; + } + + if( mVoiceSIPURIHostName.empty() ) + { + // we have an empty account server name + // so we fall back to hardcoded defaults + + if(LLGridManager::getInstance()->isInProductionGrid()) + { + // Use the release account server + mVoiceSIPURIHostName = "bhr.vivox.com"; + } + else + { + // Use the development account server + mVoiceSIPURIHostName = "bhd.vivox.com"; + } + } + + std::string debugAccountServerURI = gSavedSettings.getString("VivoxDebugVoiceAccountServerURI"); + + if( !debugAccountServerURI.empty() ) + { + mVoiceAccountServerURI = debugAccountServerURI; + } + + if( mVoiceAccountServerURI.empty() ) + { + // If the account server URI isn't specified, construct it from the SIP URI hostname + mVoiceAccountServerURI = "https://www." + mVoiceSIPURIHostName + "/api2/"; + } +} + +void LLVivoxVoiceClient::idle(void* user_data) +{ + LLVivoxVoiceClient* self = (LLVivoxVoiceClient*)user_data; + self->stateMachine(); +} + +std::string LLVivoxVoiceClient::state2string(LLVivoxVoiceClient::state inState) +{ + std::string result = "UNKNOWN"; + + // Prevent copy-paste errors when updating this list... +#define CASE(x) case x: result = #x; break + + switch(inState) + { + CASE(stateDisableCleanup); + CASE(stateDisabled); + CASE(stateStart); + CASE(stateDaemonLaunched); + CASE(stateConnecting); + CASE(stateConnected); + CASE(stateIdle); + CASE(stateMicTuningStart); + CASE(stateMicTuningRunning); + CASE(stateMicTuningStop); + CASE(stateConnectorStart); + CASE(stateConnectorStarting); + CASE(stateConnectorStarted); + CASE(stateLoginRetry); + CASE(stateLoginRetryWait); + CASE(stateNeedsLogin); + CASE(stateLoggingIn); + CASE(stateLoggedIn); + CASE(stateCreatingSessionGroup); + CASE(stateNoChannel); + CASE(stateJoiningSession); + CASE(stateSessionJoined); + CASE(stateRunning); + CASE(stateLeavingSession); + CASE(stateSessionTerminated); + CASE(stateLoggingOut); + CASE(stateLoggedOut); + CASE(stateConnectorStopping); + CASE(stateConnectorStopped); + CASE(stateConnectorFailed); + CASE(stateConnectorFailedWaiting); + CASE(stateLoginFailed); + CASE(stateLoginFailedWaiting); + CASE(stateJoinSessionFailed); + CASE(stateJoinSessionFailedWaiting); + CASE(stateJail); + } + +#undef CASE + + return result; +} + + + +void LLVivoxVoiceClient::setState(state inState) +{ + LL_DEBUGS("Voice") << "entering state " << state2string(inState) << LL_ENDL; + + mState = inState; +} + +void LLVivoxVoiceClient::stateMachine() +{ + if(gDisconnected) + { + // The viewer has been disconnected from the sim. Disable voice. + setVoiceEnabled(false); + } + + if(mVoiceEnabled) + { + updatePosition(); + } + else if(mTuningMode) + { + // Tuning mode is special -- it needs to launch SLVoice even if voice is disabled. + } + else + { + if((getState() != stateDisabled) && (getState() != stateDisableCleanup)) + { + // User turned off voice support. Send the cleanup messages, close the socket, and reset. + if(!mConnected) + { + // if voice was turned off after the daemon was launched but before we could connect to it, we may need to issue a kill. + LL_INFOS("Voice") << "Disabling voice before connection to daemon, terminating." << LL_ENDL; + killGateway(); + } + + logout(); + connectorShutdown(); + + setState(stateDisableCleanup); + } + } + + // Check for parcel boundary crossing + { + LLViewerRegion *region = gAgent.getRegion(); + LLParcel *parcel = LLViewerParcelMgr::getInstance()->getAgentParcel(); + + if(region && parcel) + { + S32 parcelLocalID = parcel->getLocalID(); + std::string regionName = region->getName(); + std::string capURI = region->getCapability("ParcelVoiceInfoRequest"); + +// LL_DEBUGS("Voice") << "Region name = \"" << regionName << "\", parcel local ID = " << parcelLocalID << ", cap URI = \"" << capURI << "\"" << LL_ENDL; + + // The region name starts out empty and gets filled in later. + // Also, the cap gets filled in a short time after the region cross, but a little too late for our purposes. + // If either is empty, wait for the next time around. + if(!regionName.empty()) + { + if(!capURI.empty()) + { + if((parcelLocalID != mCurrentParcelLocalID) || (regionName != mCurrentRegionName)) + { + // We have changed parcels. Initiate a parcel channel lookup. + mCurrentParcelLocalID = parcelLocalID; + mCurrentRegionName = regionName; + + parcelChanged(); + } + } + else + { + LL_WARNS_ONCE("Voice") << "region doesn't have ParcelVoiceInfoRequest capability. This is normal for a short time after teleporting, but bad if it persists for very long." << LL_ENDL; + } + } + } + } + + switch(getState()) + { + //MARK: stateDisableCleanup + case stateDisableCleanup: + // Clean up and reset everything. + closeSocket(); + deleteAllSessions(); + deleteAllBuddies(); + + mConnectorHandle.clear(); + mAccountHandle.clear(); + mAccountPassword.clear(); + mVoiceAccountServerURI.clear(); + + setState(stateDisabled); + break; + + //MARK: stateDisabled + case stateDisabled: + if(mTuningMode || (mVoiceEnabled && !mAccountName.empty())) + { + setState(stateStart); + } + break; + + //MARK: stateStart + case stateStart: + if(gSavedSettings.getBOOL("CmdLineDisableVoice")) + { + // Voice is locked out, we must not launch the vivox daemon. + setState(stateJail); + } + else if(!isGatewayRunning()) + { + if(true) + { + // Launch the voice daemon + + // *FIX:Mani - Using the executable dir instead + // of mAppRODataDir, the working directory from which the app + // is launched. + //std::string exe_path = gDirUtilp->getAppRODataDir(); + std::string exe_path = gDirUtilp->getExecutableDir(); + exe_path += gDirUtilp->getDirDelimiter(); +#if LL_WINDOWS + exe_path += "SLVoice.exe"; +#elif LL_DARWIN + exe_path += "../Resources/SLVoice"; +#else + exe_path += "SLVoice"; +#endif + // See if the vivox executable exists + llstat s; + if(!LLFile::stat(exe_path, &s)) + { + // vivox executable exists. Build the command line and launch the daemon. + // SLIM SDK: these arguments are no longer necessary. +// std::string args = " -p tcp -h -c"; + std::string args; + std::string cmd; + std::string loglevel = gSavedSettings.getString("VivoxDebugLevel"); + + if(loglevel.empty()) + { + loglevel = "-1"; // turn logging off completely + } + + args += " -ll "; + args += loglevel; + + LL_DEBUGS("Voice") << "Args for SLVoice: " << args << LL_ENDL; + +#if LL_WINDOWS + PROCESS_INFORMATION pinfo; + STARTUPINFOA sinfo; + + memset(&sinfo, 0, sizeof(sinfo)); + + std::string exe_dir = gDirUtilp->getAppRODataDir(); + cmd = "SLVoice.exe"; + cmd += args; + + // So retarded. Windows requires that the second parameter to CreateProcessA be writable (non-const) string... + char *args2 = new char[args.size() + 1]; + strcpy(args2, args.c_str()); + if(!CreateProcessA(exe_path.c_str(), args2, NULL, NULL, FALSE, 0, NULL, exe_dir.c_str(), &sinfo, &pinfo)) + { +// DWORD dwErr = GetLastError(); + } + else + { + // foo = pinfo.dwProcessId; // get your pid here if you want to use it later on + // CloseHandle(pinfo.hProcess); // stops leaks - nothing else + sGatewayHandle = pinfo.hProcess; + CloseHandle(pinfo.hThread); // stops leaks - nothing else + } + + delete[] args2; +#else // LL_WINDOWS + // This should be the same for mac and linux + { + std::vector arglist; + arglist.push_back(exe_path); + + // Split the argument string into separate strings for each argument + typedef boost::tokenizer > tokenizer; + boost::char_separator sep(" "); + tokenizer tokens(args, sep); + tokenizer::iterator token_iter; + + for(token_iter = tokens.begin(); token_iter != tokens.end(); ++token_iter) + { + arglist.push_back(*token_iter); + } + + // create an argv vector for the child process + char **fakeargv = new char*[arglist.size() + 1]; + int i; + for(i=0; i < arglist.size(); i++) + fakeargv[i] = const_cast(arglist[i].c_str()); + + fakeargv[i] = NULL; + + fflush(NULL); // flush all buffers before the child inherits them + pid_t id = vfork(); + if(id == 0) + { + // child + execv(exe_path.c_str(), fakeargv); + + // If we reach this point, the exec failed. + // Use _exit() instead of exit() per the vfork man page. + _exit(0); + } + + // parent + delete[] fakeargv; + sGatewayPID = id; + } +#endif // LL_WINDOWS + mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost").c_str(), gSavedSettings.getU32("VivoxVoicePort")); + } + else + { + LL_INFOS("Voice") << exe_path << " not found." << LL_ENDL; + } + } + else + { + // SLIM SDK: port changed from 44124 to 44125. + // We can connect to a client gateway running on another host. This is useful for testing. + // To do this, launch the gateway on a nearby host like this: + // vivox-gw.exe -p tcp -i 0.0.0.0:44125 + // and put that host's IP address here. + mDaemonHost = LLHost(gSavedSettings.getString("VivoxVoiceHost"), gSavedSettings.getU32("VivoxVoicePort")); + } + + mUpdateTimer.start(); + mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS); + + setState(stateDaemonLaunched); + + // Dirty the states we'll need to sync with the daemon when it comes up. + mPTTDirty = true; + mMicVolumeDirty = true; + mSpeakerVolumeDirty = true; + mSpeakerMuteDirty = true; + // These only need to be set if they're not default (i.e. empty string). + mCaptureDeviceDirty = !mCaptureDevice.empty(); + mRenderDeviceDirty = !mRenderDevice.empty(); + + mMainSessionGroupHandle.clear(); + } + break; + + //MARK: stateDaemonLaunched + case stateDaemonLaunched: + if(mUpdateTimer.hasExpired()) + { + LL_DEBUGS("Voice") << "Connecting to vivox daemon:" << mDaemonHost << LL_ENDL; + + mUpdateTimer.setTimerExpirySec(CONNECT_THROTTLE_SECONDS); + + if(!mSocket) + { + mSocket = LLSocket::create(gAPRPoolp, LLSocket::STREAM_TCP); + } + + mConnected = mSocket->blockingConnect(mDaemonHost); + if(mConnected) + { + setState(stateConnecting); + } + else + { + // If the connect failed, the socket may have been put into a bad state. Delete it. + closeSocket(); + } + } + break; + + //MARK: stateConnecting + case stateConnecting: + // Can't do this until we have the pump available. + if(mPump) + { + // MBW -- Note to self: pumps and pipes examples in + // indra/test/io.cpp + // indra/test/llpipeutil.{cpp|h} + + // Attach the pumps and pipes + + LLPumpIO::chain_t readChain; + + readChain.push_back(LLIOPipe::ptr_t(new LLIOSocketReader(mSocket))); + readChain.push_back(LLIOPipe::ptr_t(new LLVivoxProtocolParser())); + + mPump->addChain(readChain, NEVER_CHAIN_EXPIRY_SECS); + + setState(stateConnected); + } + + break; + + //MARK: stateConnected + case stateConnected: + // Initial devices query + getCaptureDevicesSendMessage(); + getRenderDevicesSendMessage(); + + mLoginRetryCount = 0; + + setState(stateIdle); + break; + + //MARK: stateIdle + case stateIdle: + // This is the idle state where we're connected to the daemon but haven't set up a connector yet. + if(mTuningMode) + { + mTuningExitState = stateIdle; + setState(stateMicTuningStart); + } + else if(!mVoiceEnabled) + { + // We never started up the connector. This will shut down the daemon. + setState(stateConnectorStopped); + } + else if(!mAccountName.empty()) + { + LLViewerRegion *region = gAgent.getRegion(); + + if(region) + { + if ( region->getCapability("ProvisionVoiceAccountRequest") != "" ) + { + if ( mAccountPassword.empty() ) + { + requestVoiceAccountProvision(); + } + setState(stateConnectorStart); + } + else + { + LL_WARNS_ONCE("Voice") << "region doesn't have ProvisionVoiceAccountRequest capability!" << LL_ENDL; + } + } + } + break; + + //MARK: stateMicTuningStart + case stateMicTuningStart: + if(mUpdateTimer.hasExpired()) + { + if(mCaptureDeviceDirty || mRenderDeviceDirty) + { + // These can't be changed while in tuning mode. Set them before starting. + std::ostringstream stream; + + buildSetCaptureDevice(stream); + buildSetRenderDevice(stream); + + if(!stream.str().empty()) + { + writeString(stream.str()); + } + + // This will come around again in the same state and start the capture, after the timer expires. + mUpdateTimer.start(); + mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); + } + else + { + // duration parameter is currently unused, per Mike S. + tuningCaptureStartSendMessage(10000); + + setState(stateMicTuningRunning); + } + } + + break; + + //MARK: stateMicTuningRunning + case stateMicTuningRunning: + if(!mTuningMode || mCaptureDeviceDirty || mRenderDeviceDirty) + { + // All of these conditions make us leave tuning mode. + setState(stateMicTuningStop); + } + else + { + // process mic/speaker volume changes + if(mTuningMicVolumeDirty || mTuningSpeakerVolumeDirty) + { + std::ostringstream stream; + + if(mTuningMicVolumeDirty) + { + LL_INFOS("Voice") << "setting tuning mic level to " << mTuningMicVolume << LL_ENDL; + stream + << "" + << "" << mTuningMicVolume << "" + << "\n\n\n"; + } + + if(mTuningSpeakerVolumeDirty) + { + stream + << "" + << "" << mTuningSpeakerVolume << "" + << "\n\n\n"; + } + + mTuningMicVolumeDirty = false; + mTuningSpeakerVolumeDirty = false; + + if(!stream.str().empty()) + { + writeString(stream.str()); + } + } + } + break; + + //MARK: stateMicTuningStop + case stateMicTuningStop: + { + // transition out of mic tuning + tuningCaptureStopSendMessage(); + + setState(mTuningExitState); + + // if we exited just to change devices, this will keep us from re-entering too fast. + mUpdateTimer.start(); + mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); + + } + break; + + //MARK: stateConnectorStart + case stateConnectorStart: + if(!mVoiceEnabled) + { + // We were never logged in. This will shut down the connector. + setState(stateLoggedOut); + } + else if(!mVoiceAccountServerURI.empty()) + { + connectorCreate(); + } + break; + + //MARK: stateConnectorStarting + case stateConnectorStarting: // waiting for connector handle + // connectorCreateResponse() will transition from here to stateConnectorStarted. + break; + + //MARK: stateConnectorStarted + case stateConnectorStarted: // connector handle received + if(!mVoiceEnabled) + { + // We were never logged in. This will shut down the connector. + setState(stateLoggedOut); + } + else + { + // The connector is started. Send a login message. + setState(stateNeedsLogin); + } + break; + + //MARK: stateLoginRetry + case stateLoginRetry: + if(mLoginRetryCount == 0) + { + // First retry -- display a message to the user + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGIN_RETRY); + } + + mLoginRetryCount++; + + if(mLoginRetryCount > MAX_LOGIN_RETRIES) + { + LL_WARNS("Voice") << "too many login retries, giving up." << LL_ENDL; + setState(stateLoginFailed); + LLSD args; + std::stringstream errs; + errs << mVoiceAccountServerURI << "\n:UDP: 3478, 3479, 5060, 5062, 12000-17000"; + args["HOSTID"] = errs.str(); + if (LLGridManager::getInstance()->isSystemGrid()) + { + LLNotificationsUtil::add("NoVoiceConnect", args); + } + else + { + LLNotificationsUtil::add("NoVoiceConnect-GIAB", args); + } + } + else + { + LL_INFOS("Voice") << "will retry login in " << LOGIN_RETRY_SECONDS << " seconds." << LL_ENDL; + mUpdateTimer.start(); + mUpdateTimer.setTimerExpirySec(LOGIN_RETRY_SECONDS); + setState(stateLoginRetryWait); + } + break; + + //MARK: stateLoginRetryWait + case stateLoginRetryWait: + if(mUpdateTimer.hasExpired()) + { + setState(stateNeedsLogin); + } + break; + + //MARK: stateNeedsLogin + case stateNeedsLogin: + if(!mAccountPassword.empty()) + { + setState(stateLoggingIn); + loginSendMessage(); + } + break; + + //MARK: stateLoggingIn + case stateLoggingIn: // waiting for account handle + // loginResponse() will transition from here to stateLoggedIn. + break; + + //MARK: stateLoggedIn + case stateLoggedIn: // account handle received + + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LOGGED_IN); + + // request the current set of block rules (we'll need them when updating the friends list) + accountListBlockRulesSendMessage(); + + // request the current set of auto-accept rules + accountListAutoAcceptRulesSendMessage(); + + // Set up the mute list observer if it hasn't been set up already. + if((!sMuteListListener_listening)) + { + LLMuteList::getInstance()->addObserver(&mutelist_listener); + sMuteListListener_listening = true; + } + + // Set up the friends list observer if it hasn't been set up already. + if(friendslist_listener == NULL) + { + friendslist_listener = new LLVivoxVoiceClientFriendsObserver; + LLAvatarTracker::instance().addObserver(friendslist_listener); + } + + // Set the initial state of mic mute, local speaker volume, etc. + { + std::ostringstream stream; + + buildLocalAudioUpdates(stream); + + if(!stream.str().empty()) + { + writeString(stream.str()); + } + } + +#if USE_SESSION_GROUPS + // create the main session group + sessionGroupCreateSendMessage(); + + setState(stateCreatingSessionGroup); +#else + // Not using session groups -- skip the stateCreatingSessionGroup state. + setState(stateNoChannel); + + // Initial kick-off of channel lookup logic + parcelChanged(); +#endif + break; + + //MARK: stateCreatingSessionGroup + case stateCreatingSessionGroup: + if(mSessionTerminateRequested || !mVoiceEnabled) + { + // *TODO: Question: is this the right way out of this state + setState(stateSessionTerminated); + } + else if(!mMainSessionGroupHandle.empty()) + { + setState(stateNoChannel); + + // Start looped recording (needed for "panic button" anti-griefing tool) + recordingLoopStart(); + + // Initial kick-off of channel lookup logic + parcelChanged(); + } + break; + + //MARK: stateNoChannel + case stateNoChannel: + + LL_DEBUGS("Voice") << "State No Channel" << LL_ENDL; + mSpatialJoiningNum = 0; + // Do this here as well as inside sendPositionalUpdate(). + // Otherwise, if you log in but don't join a proximal channel (such as when your login location has voice disabled), your friends list won't sync. + sendFriendsListUpdates(); + + if(mSessionTerminateRequested || !mVoiceEnabled) + { + // TODO: Question: Is this the right way out of this state? + setState(stateSessionTerminated); + } + else if(mTuningMode) + { + mTuningExitState = stateNoChannel; + setState(stateMicTuningStart); + } + else if(sessionNeedsRelog(mNextAudioSession)) + { + requestRelog(); + setState(stateSessionTerminated); + } + else if(mNextAudioSession) + { + sessionState *oldSession = mAudioSession; + + mAudioSession = mNextAudioSession; + if(!mAudioSession->mReconnect) + { + mNextAudioSession = NULL; + } + + // The old session may now need to be deleted. + reapSession(oldSession); + + if(!mAudioSession->mHandle.empty()) + { + // Connect to a session by session handle + + sessionMediaConnectSendMessage(mAudioSession); + } + else + { + // Connect to a session by URI + sessionCreateSendMessage(mAudioSession, true, false); + } + + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINING); + setState(stateJoiningSession); + } + else if(!mSpatialSessionURI.empty()) + { + // If we're not headed elsewhere and have a spatial URI, return to spatial. + switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials); + } + break; + + //MARK: stateJoiningSession + case stateJoiningSession: // waiting for session handle + + // If this is true we have problem with connection to voice server (EXT-4313). + // See descriptions of mSpatialJoiningNum and MAX_NORMAL_JOINING_SPATIAL_NUM. + if(mSpatialJoiningNum == MAX_NORMAL_JOINING_SPATIAL_NUM) + { + // Notify observers to let them know there is problem with voice + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); + llwarns << "There seems to be problem with connection to voice server. Disabling voice chat abilities." << llendl; + } + + // Increase mSpatialJoiningNum only for spatial sessions- it's normal to reach this case for + // example for p2p many times while waiting for response, so it can't be used to detect errors + if(mAudioSession && mAudioSession->mIsSpatial) + { + mSpatialJoiningNum++; + } + + // joinedAudioSession() will transition from here to stateSessionJoined. + if(!mVoiceEnabled) + { + // User bailed out during connect -- jump straight to teardown. + setState(stateSessionTerminated); + } + else if(mSessionTerminateRequested) + { + if(mAudioSession && !mAudioSession->mHandle.empty()) + { + // Only allow direct exits from this state in p2p calls (for cancelling an invite). + // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. + if(mAudioSession->mIsP2P) + { + sessionMediaDisconnectSendMessage(mAudioSession); + setState(stateSessionTerminated); + } + } + } + break; + + //MARK: stateSessionJoined + case stateSessionJoined: // session handle received + + mSpatialJoiningNum = 0; + // It appears that I need to wait for BOTH the SessionGroup.AddSession response and the SessionStateChangeEvent with state 4 + // before continuing from this state. They can happen in either order, and if I don't wait for both, things can get stuck. + // For now, the SessionGroup.AddSession response handler sets mSessionHandle and the SessionStateChangeEvent handler transitions to stateSessionJoined. + // This is a cheap way to make sure both have happened before proceeding. + if(mAudioSession && mAudioSession->mVoiceEnabled) + { + // Dirty state that may need to be sync'ed with the daemon. + mPTTDirty = true; + mSpeakerVolumeDirty = true; + mSpatialCoordsDirty = true; + + setState(stateRunning); + + // Start the throttle timer + mUpdateTimer.start(); + mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); + + // Events that need to happen when a session is joined could go here. + // Maybe send initial spatial data? + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_JOINED); + + } + else if(!mVoiceEnabled) + { + // User bailed out during connect -- jump straight to teardown. + setState(stateSessionTerminated); + } + else if(mSessionTerminateRequested) + { + // Only allow direct exits from this state in p2p calls (for cancelling an invite). + // Terminating a half-connected session on other types of calls seems to break something in the vivox gateway. + if(mAudioSession && mAudioSession->mIsP2P) + { + sessionMediaDisconnectSendMessage(mAudioSession); + setState(stateSessionTerminated); + } + } + break; + + //MARK: stateRunning + case stateRunning: // steady state + // Disabling voice or disconnect requested. + if(!mVoiceEnabled || mSessionTerminateRequested) + { + leaveAudioSession(); + } + else + { + + // Figure out whether the PTT state needs to change + { + bool newPTT; + if(mUsePTT) + { + // If configured to use PTT, track the user state. + newPTT = mUserPTTState; + } + else + { + // If not configured to use PTT, it should always be true (otherwise the user will be unable to speak). + newPTT = true; + } + + if(mMuteMic) + { + // This always overrides any other PTT setting. + newPTT = false; + } + + // Dirty if state changed. + if(newPTT != mPTT) + { + mPTT = newPTT; + mPTTDirty = true; + } + } + + if(!inSpatialChannel()) + { + // When in a non-spatial channel, never send positional updates. + mSpatialCoordsDirty = false; + } + else + { + // Do the calculation that enforces the listener<->speaker tether (and also updates the real camera position) + enforceTether(); + } + + // Send an update if the ptt state has changed (which shouldn't be able to happen that often -- the user can only click so fast) + // or every 10hz, whichever is sooner. + if((mAudioSession && mAudioSession->mVolumeDirty) || mPTTDirty || mSpeakerVolumeDirty || mUpdateTimer.hasExpired()) + { + mUpdateTimer.setTimerExpirySec(UPDATE_THROTTLE_SECONDS); + sendPositionalUpdate(); + } + } + break; + + //MARK: stateLeavingSession + case stateLeavingSession: // waiting for terminate session response + // The handler for the Session.Terminate response will transition from here to stateSessionTerminated. + break; + + //MARK: stateSessionTerminated + case stateSessionTerminated: + + // Must do this first, since it uses mAudioSession. + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL); + + if(mAudioSession) + { + sessionState *oldSession = mAudioSession; + + mAudioSession = NULL; + // We just notified status observers about this change. Don't do it again. + mAudioSessionChanged = false; + + // The old session may now need to be deleted. + reapSession(oldSession); + } + else + { + LL_WARNS("Voice") << "stateSessionTerminated with NULL mAudioSession" << LL_ENDL; + } + + // Always reset the terminate request flag when we get here. + mSessionTerminateRequested = false; + + if(mVoiceEnabled && !mRelogRequested) + { + // Just leaving a channel, go back to stateNoChannel (the "logged in but have no channel" state). + setState(stateNoChannel); + } + else + { + // Shutting down voice, continue with disconnecting. + logout(); + + // The state machine will take it from here + mRelogRequested = false; + } + + break; + + //MARK: stateLoggingOut + case stateLoggingOut: // waiting for logout response + // The handler for the AccountLoginStateChangeEvent will transition from here to stateLoggedOut. + break; + + //MARK: stateLoggedOut + case stateLoggedOut: // logout response received + + // Once we're logged out, all these things are invalid. + mAccountHandle.clear(); + deleteAllSessions(); + deleteAllBuddies(); + + if(mVoiceEnabled && !mRelogRequested) + { + // User was logged out, but wants to be logged in. Send a new login request. + setState(stateNeedsLogin); + } + else + { + // shut down the connector + connectorShutdown(); + } + break; + + //MARK: stateConnectorStopping + case stateConnectorStopping: // waiting for connector stop + // The handler for the Connector.InitiateShutdown response will transition from here to stateConnectorStopped. + break; + + //MARK: stateConnectorStopped + case stateConnectorStopped: // connector stop received + setState(stateDisableCleanup); + break; + + //MARK: stateConnectorFailed + case stateConnectorFailed: + setState(stateConnectorFailedWaiting); + break; + //MARK: stateConnectorFailedWaiting + case stateConnectorFailedWaiting: + if(!mVoiceEnabled) + { + setState(stateDisableCleanup); + } + break; + + //MARK: stateLoginFailed + case stateLoginFailed: + setState(stateLoginFailedWaiting); + break; + //MARK: stateLoginFailedWaiting + case stateLoginFailedWaiting: + if(!mVoiceEnabled) + { + setState(stateDisableCleanup); + } + break; + + //MARK: stateJoinSessionFailed + case stateJoinSessionFailed: + // Transition to error state. Send out any notifications here. + if(mAudioSession) + { + LL_WARNS("Voice") << "stateJoinSessionFailed: (" << mAudioSession->mErrorStatusCode << "): " << mAudioSession->mErrorStatusString << LL_ENDL; + } + else + { + LL_WARNS("Voice") << "stateJoinSessionFailed with no current session" << LL_ENDL; + } + + notifyStatusObservers(LLVoiceClientStatusObserver::ERROR_UNKNOWN); + setState(stateJoinSessionFailedWaiting); + break; + + //MARK: stateJoinSessionFailedWaiting + case stateJoinSessionFailedWaiting: + // Joining a channel failed, either due to a failed channel name -> sip url lookup or an error from the join message. + // Region crossings may leave this state and try the join again. + if(mSessionTerminateRequested) + { + setState(stateSessionTerminated); + } + break; + + //MARK: stateJail + case stateJail: + // We have given up. Do nothing. + break; + + } + + if(mAudioSession && mAudioSession->mParticipantsChanged) + { + mAudioSession->mParticipantsChanged = false; + mAudioSessionChanged = true; + } + + if(mAudioSessionChanged) + { + mAudioSessionChanged = false; + notifyParticipantObservers(); + } +} + +void LLVivoxVoiceClient::closeSocket(void) +{ + mSocket.reset(); + mConnected = false; +} + +void LLVivoxVoiceClient::loginSendMessage() +{ + std::ostringstream stream; + + bool autoPostCrashDumps = gSavedSettings.getBOOL("VivoxAutoPostCrashDumps"); + + stream + << "" + << "" << mConnectorHandle << "" + << "" << mAccountName << "" + << "" << mAccountPassword << "" + << "VerifyAnswer" + << "true" + << "Application" + << "5" + << (autoPostCrashDumps?"true":"") + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::logout() +{ + // Ensure that we'll re-request provisioning before logging in again + mAccountPassword.clear(); + mVoiceAccountServerURI.clear(); + + setState(stateLoggingOut); + logoutSendMessage(); +} + +void LLVivoxVoiceClient::logoutSendMessage() +{ + if(!mAccountHandle.empty()) + { + std::ostringstream stream; + stream + << "" + << "" << mAccountHandle << "" + << "" + << "\n\n\n"; + + mAccountHandle.clear(); + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::accountListBlockRulesSendMessage() +{ + if(!mAccountHandle.empty()) + { + std::ostringstream stream; + + LL_DEBUGS("Voice") << "requesting block rules" << LL_ENDL; + + stream + << "" + << "" << mAccountHandle << "" + << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::accountListAutoAcceptRulesSendMessage() +{ + if(!mAccountHandle.empty()) + { + std::ostringstream stream; + + LL_DEBUGS("Voice") << "requesting auto-accept rules" << LL_ENDL; + + stream + << "" + << "" << mAccountHandle << "" + << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::sessionGroupCreateSendMessage() +{ + if(!mAccountHandle.empty()) + { + std::ostringstream stream; + + LL_DEBUGS("Voice") << "creating session group" << LL_ENDL; + + stream + << "" + << "" << mAccountHandle << "" + << "Normal" + << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::sessionCreateSendMessage(sessionState *session, bool startAudio, bool startText) +{ + LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL; + + session->mCreateInProgress = true; + if(startAudio) + { + session->mMediaConnectInProgress = true; + } + + std::ostringstream stream; + stream + << "mSIPURI << "\" action=\"Session.Create.1\">" + << "" << mAccountHandle << "" + << "" << session->mSIPURI << ""; + + static const std::string allowed_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789" + "-._~"; + + if(!session->mHash.empty()) + { + stream + << "" << LLURI::escape(session->mHash, allowed_chars) << "" + << "SHA1UserName"; + } + + stream + << "" << (startAudio?"true":"false") << "" + << "" << (startText?"true":"false") << "" + << "" << mChannelName << "" + << "\n\n\n"; + writeString(stream.str()); +} + +void LLVivoxVoiceClient::sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio, bool startText) +{ + LL_DEBUGS("Voice") << "requesting create: " << session->mSIPURI << LL_ENDL; + + session->mCreateInProgress = true; + if(startAudio) + { + session->mMediaConnectInProgress = true; + } + + std::string password; + if(!session->mHash.empty()) + { + static const std::string allowed_chars = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + "0123456789" + "-._~" + ; + password = LLURI::escape(session->mHash, allowed_chars); + } + + std::ostringstream stream; + stream + << "mSIPURI << "\" action=\"SessionGroup.AddSession.1\">" + << "" << session->mGroupHandle << "" + << "" << session->mSIPURI << "" + << "" << mChannelName << "" + << "" << (startAudio?"true":"false") << "" + << "" << (startText?"true":"false") << "" + << "" << password << "" + << "SHA1UserName" + << "\n\n\n" + ; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::sessionMediaConnectSendMessage(sessionState *session) +{ + LL_DEBUGS("Voice") << "connecting audio to session handle: " << session->mHandle << LL_ENDL; + + session->mMediaConnectInProgress = true; + + std::ostringstream stream; + + stream + << "mHandle << "\" action=\"Session.MediaConnect.1\">" + << "" << session->mGroupHandle << "" + << "" << session->mHandle << "" + << "Audio" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::sessionTextConnectSendMessage(sessionState *session) +{ + LL_DEBUGS("Voice") << "connecting text to session handle: " << session->mHandle << LL_ENDL; + + std::ostringstream stream; + + stream + << "mHandle << "\" action=\"Session.TextConnect.1\">" + << "" << session->mGroupHandle << "" + << "" << session->mHandle << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::sessionTerminate() +{ + mSessionTerminateRequested = true; +} + +void LLVivoxVoiceClient::requestRelog() +{ + mSessionTerminateRequested = true; + mRelogRequested = true; +} + + +void LLVivoxVoiceClient::leaveAudioSession() +{ + if(mAudioSession) + { + LL_DEBUGS("Voice") << "leaving session: " << mAudioSession->mSIPURI << LL_ENDL; + + switch(getState()) + { + case stateNoChannel: + // In this case, we want to pretend the join failed so our state machine doesn't get stuck. + // Skip the join failed transition state so we don't send out error notifications. + setState(stateJoinSessionFailedWaiting); + break; + case stateJoiningSession: + case stateSessionJoined: + case stateRunning: + if(!mAudioSession->mHandle.empty()) + { + +#if RECORD_EVERYTHING + // HACK: for testing only + // Save looped recording + std::string savepath("/tmp/vivoxrecording"); + { + time_t now = time(NULL); + const size_t BUF_SIZE = 64; + char time_str[BUF_SIZE]; /* Flawfinder: ignore */ + + strftime(time_str, BUF_SIZE, "%Y-%m-%dT%H:%M:%SZ", gmtime(&now)); + savepath += time_str; + } + recordingLoopSave(savepath); +#endif + + sessionMediaDisconnectSendMessage(mAudioSession); + setState(stateLeavingSession); + } + else + { + LL_WARNS("Voice") << "called with no session handle" << LL_ENDL; + setState(stateSessionTerminated); + } + break; + case stateJoinSessionFailed: + case stateJoinSessionFailedWaiting: + setState(stateSessionTerminated); + break; + + default: + LL_WARNS("Voice") << "called from unknown state" << LL_ENDL; + break; + } + } + else + { + LL_WARNS("Voice") << "called with no active session" << LL_ENDL; + setState(stateSessionTerminated); + } +} + +void LLVivoxVoiceClient::sessionTerminateSendMessage(sessionState *session) +{ + std::ostringstream stream; + + LL_DEBUGS("Voice") << "Sending Session.Terminate with handle " << session->mHandle << LL_ENDL; + stream + << "" + << "" << session->mHandle << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::sessionGroupTerminateSendMessage(sessionState *session) +{ + std::ostringstream stream; + + LL_DEBUGS("Voice") << "Sending SessionGroup.Terminate with handle " << session->mGroupHandle << LL_ENDL; + stream + << "" + << "" << session->mGroupHandle << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::sessionMediaDisconnectSendMessage(sessionState *session) +{ + std::ostringstream stream; + + LL_DEBUGS("Voice") << "Sending Session.MediaDisconnect with handle " << session->mHandle << LL_ENDL; + stream + << "" + << "" << session->mGroupHandle << "" + << "" << session->mHandle << "" + << "Audio" + << "\n\n\n"; + + writeString(stream.str()); + +} + +void LLVivoxVoiceClient::sessionTextDisconnectSendMessage(sessionState *session) +{ + std::ostringstream stream; + + LL_DEBUGS("Voice") << "Sending Session.TextDisconnect with handle " << session->mHandle << LL_ENDL; + stream + << "" + << "" << session->mGroupHandle << "" + << "" << session->mHandle << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::getCaptureDevicesSendMessage() +{ + std::ostringstream stream; + stream + << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::getRenderDevicesSendMessage() +{ + std::ostringstream stream; + stream + << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::clearCaptureDevices() +{ + LL_DEBUGS("Voice") << "called" << LL_ENDL; + mCaptureDevices.clear(); +} + +void LLVivoxVoiceClient::addCaptureDevice(const std::string& name) +{ + LL_DEBUGS("Voice") << name << LL_ENDL; + + mCaptureDevices.push_back(name); +} + +LLVoiceDeviceList& LLVivoxVoiceClient::getCaptureDevices() +{ + return mCaptureDevices; +} + +void LLVivoxVoiceClient::setCaptureDevice(const std::string& name) +{ + if(name == "Default") + { + if(!mCaptureDevice.empty()) + { + mCaptureDevice.clear(); + mCaptureDeviceDirty = true; + } + } + else + { + if(mCaptureDevice != name) + { + mCaptureDevice = name; + mCaptureDeviceDirty = true; + } + } +} + +void LLVivoxVoiceClient::clearRenderDevices() +{ + LL_DEBUGS("Voice") << "called" << LL_ENDL; + mRenderDevices.clear(); +} + +void LLVivoxVoiceClient::addRenderDevice(const std::string& name) +{ + LL_DEBUGS("Voice") << name << LL_ENDL; + mRenderDevices.push_back(name); +} + +LLVoiceDeviceList& LLVivoxVoiceClient::getRenderDevices() +{ + return mRenderDevices; +} + +void LLVivoxVoiceClient::setRenderDevice(const std::string& name) +{ + if(name == "Default") + { + if(!mRenderDevice.empty()) + { + mRenderDevice.clear(); + mRenderDeviceDirty = true; + } + } + else + { + if(mRenderDevice != name) + { + mRenderDevice = name; + mRenderDeviceDirty = true; + } + } + +} + +void LLVivoxVoiceClient::tuningStart() +{ + mTuningMode = true; + LL_DEBUGS("Voice") << "Starting tuning" << LL_ENDL; + if(getState() >= stateNoChannel) + { + LL_DEBUGS("Voice") << "no channel" << LL_ENDL; + sessionTerminate(); + } +} + +void LLVivoxVoiceClient::tuningStop() +{ + mTuningMode = false; +} + +bool LLVivoxVoiceClient::inTuningMode() +{ + bool result = false; + switch(getState()) + { + case stateMicTuningRunning: + result = true; + break; + default: + break; + } + return result; +} + +void LLVivoxVoiceClient::tuningRenderStartSendMessage(const std::string& name, bool loop) +{ + mTuningAudioFile = name; + std::ostringstream stream; + stream + << "" + << "" << mTuningAudioFile << "" + << "" << (loop?"1":"0") << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::tuningRenderStopSendMessage() +{ + std::ostringstream stream; + stream + << "" + << "" << mTuningAudioFile << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::tuningCaptureStartSendMessage(int duration) +{ + LL_DEBUGS("Voice") << "sending CaptureAudioStart" << LL_ENDL; + + std::ostringstream stream; + stream + << "" + << "" << duration << "" + << "\n\n\n"; + + writeString(stream.str()); +} + +void LLVivoxVoiceClient::tuningCaptureStopSendMessage() +{ + LL_DEBUGS("Voice") << "sending CaptureAudioStop" << LL_ENDL; + + std::ostringstream stream; + stream + << "" + << "\n\n\n"; + + writeString(stream.str()); + + mTuningEnergy = 0.0f; +} + +void LLVivoxVoiceClient::tuningSetMicVolume(float volume) +{ + int scaled_volume = scale_mic_volume(volume); + + if(scaled_volume != mTuningMicVolume) + { + mTuningMicVolume = scaled_volume; + mTuningMicVolumeDirty = true; + } +} + +void LLVivoxVoiceClient::tuningSetSpeakerVolume(float volume) +{ + int scaled_volume = scale_speaker_volume(volume); + + if(scaled_volume != mTuningSpeakerVolume) + { + mTuningSpeakerVolume = scaled_volume; + mTuningSpeakerVolumeDirty = true; + } +} + +float LLVivoxVoiceClient::tuningGetEnergy(void) +{ + return mTuningEnergy; +} + +bool LLVivoxVoiceClient::deviceSettingsAvailable() +{ + bool result = true; + + if(!mConnected) + result = false; + + if(mRenderDevices.empty()) + result = false; + + return result; +} + +void LLVivoxVoiceClient::refreshDeviceLists(bool clearCurrentList) +{ + if(clearCurrentList) + { + clearCaptureDevices(); + clearRenderDevices(); + } + getCaptureDevicesSendMessage(); + getRenderDevicesSendMessage(); +} + +void LLVivoxVoiceClient::daemonDied() +{ + // The daemon died, so the connection is gone. Reset everything and start over. + LL_WARNS("Voice") << "Connection to vivox daemon lost. Resetting state."<< LL_ENDL; + + // Try to relaunch the daemon + setState(stateDisableCleanup); +} + +void LLVivoxVoiceClient::giveUp() +{ + // All has failed. Clean up and stop trying. + closeSocket(); + deleteAllSessions(); + deleteAllBuddies(); + + setState(stateJail); +} + +static void oldSDKTransform (LLVector3 &left, LLVector3 &up, LLVector3 &at, LLVector3d &pos, LLVector3 &vel) +{ + F32 nat[3], nup[3], nl[3], nvel[3]; // the new at, up, left vectors and the new position and velocity + F64 npos[3]; + + // The original XML command was sent like this: + /* + << "" + << "" << pos[VX] << "" + << "" << pos[VZ] << "" + << "" << pos[VY] << "" + << "" + << "" + << "" << mAvatarVelocity[VX] << "" + << "" << mAvatarVelocity[VZ] << "" + << "" << mAvatarVelocity[VY] << "" + << "" + << "" + << "" << l.mV[VX] << "" + << "" << u.mV[VX] << "" + << "" << a.mV[VX] << "" + << "" + << "" + << "" << l.mV[VZ] << "" + << "" << u.mV[VY] << "" + << "" << a.mV[VZ] << "" + << "" + << "" + << "" << l.mV [VY] << "" + << "" << u.mV [VZ] << "" + << "" << a.mV [VY] << "" + << ""; + */ + +#if 1 + // This was the original transform done when building the XML command + nat[0] = left.mV[VX]; + nat[1] = up.mV[VX]; + nat[2] = at.mV[VX]; + + nup[0] = left.mV[VZ]; + nup[1] = up.mV[VY]; + nup[2] = at.mV[VZ]; + + nl[0] = left.mV[VY]; + nl[1] = up.mV[VZ]; + nl[2] = at.mV[VY]; + + npos[0] = pos.mdV[VX]; + npos[1] = pos.mdV[VZ]; + npos[2] = pos.mdV[VY]; + + nvel[0] = vel.mV[VX]; + nvel[1] = vel.mV[VZ]; + nvel[2] = vel.mV[VY]; + + for(int i=0;i<3;++i) { + at.mV[i] = nat[i]; + up.mV[i] = nup[i]; + left.mV[i] = nl[i]; + pos.mdV[i] = npos[i]; + } + + // This was the original transform done in the SDK + nat[0] = at.mV[2]; + nat[1] = 0; // y component of at vector is always 0, this was up[2] + nat[2] = -1 * left.mV[2]; + + // We override whatever the application gives us + nup[0] = 0; // x component of up vector is always 0 + nup[1] = 1; // y component of up vector is always 1 + nup[2] = 0; // z component of up vector is always 0 + + nl[0] = at.mV[0]; + nl[1] = 0; // y component of left vector is always zero, this was up[0] + nl[2] = -1 * left.mV[0]; + + npos[2] = pos.mdV[2] * -1.0; + npos[1] = pos.mdV[1]; + npos[0] = pos.mdV[0]; + + for(int i=0;i<3;++i) { + at.mV[i] = nat[i]; + up.mV[i] = nup[i]; + left.mV[i] = nl[i]; + pos.mdV[i] = npos[i]; + } +#else + // This is the compose of the two transforms (at least, that's what I'm trying for) + nat[0] = at.mV[VX]; + nat[1] = 0; // y component of at vector is always 0, this was up[2] + nat[2] = -1 * up.mV[VZ]; + + // We override whatever the application gives us + nup[0] = 0; // x component of up vector is always 0 + nup[1] = 1; // y component of up vector is always 1 + nup[2] = 0; // z component of up vector is always 0 + + nl[0] = left.mV[VX]; + nl[1] = 0; // y component of left vector is always zero, this was up[0] + nl[2] = -1 * left.mV[VY]; + + npos[0] = pos.mdV[VX]; + npos[1] = pos.mdV[VZ]; + npos[2] = pos.mdV[VY] * -1.0; + + nvel[0] = vel.mV[VX]; + nvel[1] = vel.mV[VZ]; + nvel[2] = vel.mV[VY]; + + for(int i=0;i<3;++i) { + at.mV[i] = nat[i]; + up.mV[i] = nup[i]; + left.mV[i] = nl[i]; + pos.mdV[i] = npos[i]; + } + +#endif +} + +void LLVivoxVoiceClient::sendPositionalUpdate(void) +{ + std::ostringstream stream; + + if(mSpatialCoordsDirty) + { + LLVector3 l, u, a, vel; + LLVector3d pos; + + mSpatialCoordsDirty = false; + + // Always send both speaker and listener positions together. + stream << "" + << "" << getAudioSessionHandle() << ""; + + stream << ""; + +// LL_DEBUGS("Voice") << "Sending speaker position " << mAvatarPosition << LL_ENDL; + l = mAvatarRot.getLeftRow(); + u = mAvatarRot.getUpRow(); + a = mAvatarRot.getFwdRow(); + pos = mAvatarPosition; + vel = mAvatarVelocity; + + // SLIM SDK: the old SDK was doing a transform on the passed coordinates that the new one doesn't do anymore. + // The old transform is replicated by this function. + oldSDKTransform(l, u, a, pos, vel); + + stream + << "" + << "" << pos.mdV[VX] << "" + << "" << pos.mdV[VY] << "" + << "" << pos.mdV[VZ] << "" + << "" + << "" + << "" << vel.mV[VX] << "" + << "" << vel.mV[VY] << "" + << "" << vel.mV[VZ] << "" + << "" + << "" + << "" << a.mV[VX] << "" + << "" << a.mV[VY] << "" + << "" << a.mV[VZ] << "" + << "" + << "" + << "" << u.mV[VX] << "" + << "" << u.mV[VY] << "" + << "" << u.mV[VZ] << "" + << "" + << "" + << "" << l.mV [VX] << "" + << "" << l.mV [VY] << "" + << "" << l.mV [VZ] << "" + << ""; + + stream << ""; + + stream << ""; + + LLVector3d earPosition; + LLVector3 earVelocity; + LLMatrix3 earRot; + + switch(mEarLocation) + { + case earLocCamera: + default: + earPosition = mCameraPosition; + earVelocity = mCameraVelocity; + earRot = mCameraRot; + break; + + case earLocAvatar: + earPosition = mAvatarPosition; + earVelocity = mAvatarVelocity; + earRot = mAvatarRot; + break; + + case earLocMixed: + earPosition = mAvatarPosition; + earVelocity = mAvatarVelocity; + earRot = mCameraRot; + break; + } + + l = earRot.getLeftRow(); + u = earRot.getUpRow(); + a = earRot.getFwdRow(); + pos = earPosition; + vel = earVelocity; + +// LL_DEBUGS("Voice") << "Sending listener position " << earPosition << LL_ENDL; + + oldSDKTransform(l, u, a, pos, vel); + + stream + << "" + << "" << pos.mdV[VX] << "" + << "" << pos.mdV[VY] << "" + << "" << pos.mdV[VZ] << "" + << "" + << "" + << "" << vel.mV[VX] << "" + << "" << vel.mV[VY] << "" + << "" << vel.mV[VZ] << "" + << "" + << "" + << "" << a.mV[VX] << "" + << "" << a.mV[VY] << "" + << "" << a.mV[VZ] << "" + << "" + << "" + << "" << u.mV[VX] << "" + << "" << u.mV[VY] << "" + << "" << u.mV[VZ] << "" + << "" + << "" + << "" << l.mV [VX] << "" + << "" << l.mV [VY] << "" + << "" << l.mV [VZ] << "" + << ""; + + + stream << ""; + + stream << "\n\n\n"; + } + + if(mAudioSession && mAudioSession->mVolumeDirty) + { + participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); + + mAudioSession->mVolumeDirty = false; + + for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) + { + participantState *p = iter->second; + + if(p->mVolumeDirty) + { + // Can't set volume/mute for yourself + if(!p->mIsSelf) + { + int volume = 56; // nominal default value + bool mute = p->mOnMuteList; + + if(p->mUserVolume != -1) + { + // scale from user volume in the range 0-400 (with 100 as "normal") to vivox volume in the range 0-100 (with 56 as "normal") + if(p->mUserVolume < 100) + volume = (p->mUserVolume * 56) / 100; + else + volume = (((p->mUserVolume - 100) * (100 - 56)) / 300) + 56; + } + else if(p->mVolume != -1) + { + // Use the previously reported internal volume (comes in with a ParticipantUpdatedEvent) + volume = p->mVolume; + } + + + if(mute) + { + // SetParticipantMuteForMe doesn't work in p2p sessions. + // If we want the user to be muted, set their volume to 0 as well. + // This isn't perfect, but it will at least reduce their volume to a minimum. + volume = 0; + } + + if(volume == 0) + mute = true; + + LL_DEBUGS("Voice") << "Setting volume/mute for avatar " << p->mAvatarID << " to " << volume << (mute?"/true":"/false") << LL_ENDL; + + // SLIM SDK: Send both volume and mute commands. + + // Send a "volume for me" command for the user. + stream << "" + << "" << getAudioSessionHandle() << "" + << "" << p->mURI << "" + << "" << volume << "" + << "\n\n\n"; + + if(!mAudioSession->mIsP2P) + { + // Send a "mute for me" command for the user + // Doesn't work in P2P sessions + stream << "" + << "" << getAudioSessionHandle() << "" + << "" << p->mURI << "" + << "" << (mute?"1":"0") << "" + << "Audio" + << "\n\n\n"; + } + } + + p->mVolumeDirty = false; + } + } + } + + buildLocalAudioUpdates(stream); + + if(!stream.str().empty()) + { + writeString(stream.str()); + } + + // Friends list updates can be huge, especially on the first voice login of an account with lots of friends. + // Batching them all together can choke SLVoice, so send them in separate writes. + sendFriendsListUpdates(); +} + +void LLVivoxVoiceClient::buildSetCaptureDevice(std::ostringstream &stream) +{ + if(mCaptureDeviceDirty) + { + LL_DEBUGS("Voice") << "Setting input device = \"" << mCaptureDevice << "\"" << LL_ENDL; + + stream + << "" + << "" << mCaptureDevice << "" + << "" + << "\n\n\n"; + + mCaptureDeviceDirty = false; + } +} + +void LLVivoxVoiceClient::buildSetRenderDevice(std::ostringstream &stream) +{ + if(mRenderDeviceDirty) + { + LL_DEBUGS("Voice") << "Setting output device = \"" << mRenderDevice << "\"" << LL_ENDL; + + stream + << "" + << "" << mRenderDevice << "" + << "" + << "\n\n\n"; + mRenderDeviceDirty = false; + } +} + +void LLVivoxVoiceClient::buildLocalAudioUpdates(std::ostringstream &stream) +{ + buildSetCaptureDevice(stream); + + buildSetRenderDevice(stream); + + if(mPTTDirty) + { + mPTTDirty = false; + + // Send a local mute command. + // NOTE that the state of "PTT" is the inverse of "local mute". + // (i.e. when PTT is true, we send a mute command with "false", and vice versa) + + LL_DEBUGS("Voice") << "Sending MuteLocalMic command with parameter " << (mPTT?"false":"true") << LL_ENDL; + + stream << "" + << "" << mConnectorHandle << "" + << "" << (mPTT?"false":"true") << "" + << "\n\n\n"; + + } + + if(mSpeakerMuteDirty) + { + const char *muteval = ((mSpeakerVolume <= scale_speaker_volume(0))?"true":"false"); + + mSpeakerMuteDirty = false; + + LL_INFOS("Voice") << "Setting speaker mute to " << muteval << LL_ENDL; + + stream << "" + << "" << mConnectorHandle << "" + << "" << muteval << "" + << "\n\n\n"; + + } + + if(mSpeakerVolumeDirty) + { + mSpeakerVolumeDirty = false; + + LL_INFOS("Voice") << "Setting speaker volume to " << mSpeakerVolume << LL_ENDL; + + stream << "" + << "" << mConnectorHandle << "" + << "" << mSpeakerVolume << "" + << "\n\n\n"; + + } + + if(mMicVolumeDirty) + { + mMicVolumeDirty = false; + + LL_INFOS("Voice") << "Setting mic volume to " << mMicVolume << LL_ENDL; + + stream << "" + << "" << mConnectorHandle << "" + << "" << mMicVolume << "" + << "\n\n\n"; + } + + +} + +void LLVivoxVoiceClient::checkFriend(const LLUUID& id) +{ + std::string name; + buddyListEntry *buddy = findBuddy(id); + + // Make sure we don't add a name before it's been looked up. + if(gCacheName->getFullName(id, name)) + { + + const LLRelationship* relationInfo = LLAvatarTracker::instance().getBuddyInfo(id); + bool canSeeMeOnline = false; + if(relationInfo && relationInfo->isRightGrantedTo(LLRelationship::GRANT_ONLINE_STATUS)) + canSeeMeOnline = true; + + // When we get here, mNeedsSend is true and mInSLFriends is false. Change them as necessary. + + if(buddy) + { + // This buddy is already in both lists. + + if(name != buddy->mDisplayName) + { + // The buddy is in the list with the wrong name. Update it with the correct name. + LL_WARNS("Voice") << "Buddy " << id << " has wrong name (\"" << buddy->mDisplayName << "\" should be \"" << name << "\"), updating."<< LL_ENDL; + buddy->mDisplayName = name; + buddy->mNeedsNameUpdate = true; // This will cause the buddy to be resent. + } + } + else + { + // This buddy was not in the vivox list, needs to be added. + buddy = addBuddy(sipURIFromID(id), name); + buddy->mUUID = id; + } + + // In all the above cases, the buddy is in the SL friends list (which is how we got here). + buddy->mInSLFriends = true; + buddy->mCanSeeMeOnline = canSeeMeOnline; + buddy->mNameResolved = true; + + } + else + { + // This name hasn't been looked up yet. Don't do anything with this buddy list entry until it has. + if(buddy) + { + buddy->mNameResolved = false; + } + + // Initiate a lookup. + // The "lookup completed" callback will ensure that the friends list is rechecked after it completes. + lookupName(id); + } +} + +void LLVivoxVoiceClient::clearAllLists() +{ + // FOR TESTING ONLY + + // This will send the necessary commands to delete ALL buddies, autoaccept rules, and block rules SLVoice tells us about. + buddyListMap::iterator buddy_it; + for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end();) + { + buddyListEntry *buddy = buddy_it->second; + buddy_it++; + + std::ostringstream stream; + + if(buddy->mInVivoxBuddies) + { + // delete this entry from the vivox buddy list + buddy->mInVivoxBuddies = false; + LL_DEBUGS("Voice") << "delete " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL; + stream << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\n\n\n"; + } + + if(buddy->mHasBlockListEntry) + { + // Delete the associated block list entry (so the block list doesn't fill up with junk) + buddy->mHasBlockListEntry = false; + stream << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\n\n\n"; + } + if(buddy->mHasAutoAcceptListEntry) + { + // Delete the associated auto-accept list entry (so the auto-accept list doesn't fill up with junk) + buddy->mHasAutoAcceptListEntry = false; + stream << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\n\n\n"; + } + + writeString(stream.str()); + + } +} + +void LLVivoxVoiceClient::sendFriendsListUpdates() +{ + if(mBuddyListMapPopulated && mBlockRulesListReceived && mAutoAcceptRulesListReceived && mFriendsListDirty) + { + mFriendsListDirty = false; + + if(0) + { + // FOR TESTING ONLY -- clear all buddy list, block list, and auto-accept list entries. + clearAllLists(); + return; + } + + LL_INFOS("Voice") << "Checking vivox buddy list against friends list..." << LL_ENDL; + + buddyListMap::iterator buddy_it; + for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++) + { + // reset the temp flags in the local buddy list + buddy_it->second->mInSLFriends = false; + } + + // correlate with the friends list + { + LLCollectAllBuddies collect; + LLAvatarTracker::instance().applyFunctor(collect); + LLCollectAllBuddies::buddy_map_t::const_iterator it = collect.mOnline.begin(); + LLCollectAllBuddies::buddy_map_t::const_iterator end = collect.mOnline.end(); + + for ( ; it != end; ++it) + { + checkFriend(it->second); + } + it = collect.mOffline.begin(); + end = collect.mOffline.end(); + for ( ; it != end; ++it) + { + checkFriend(it->second); + } + } + + LL_INFOS("Voice") << "Sending friend list updates..." << LL_ENDL; + + for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end();) + { + buddyListEntry *buddy = buddy_it->second; + buddy_it++; + + // Ignore entries that aren't resolved yet. + if(buddy->mNameResolved) + { + std::ostringstream stream; + + if(buddy->mInSLFriends && (!buddy->mInVivoxBuddies || buddy->mNeedsNameUpdate)) + { + if(mNumberOfAliases > 0) + { + // Add (or update) this entry in the vivox buddy list + buddy->mInVivoxBuddies = true; + buddy->mNeedsNameUpdate = false; + LL_DEBUGS("Voice") << "add/update " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL; + stream + << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "" << buddy->mDisplayName << "" + << "" // Without this, SLVoice doesn't seem to parse the command. + << "0" + << "\n\n\n"; + } + } + else if(!buddy->mInSLFriends) + { + // This entry no longer exists in your SL friends list. Remove all traces of it from the Vivox buddy list. + if(buddy->mInVivoxBuddies) + { + // delete this entry from the vivox buddy list + buddy->mInVivoxBuddies = false; + LL_DEBUGS("Voice") << "delete " << buddy->mURI << " (" << buddy->mDisplayName << ")" << LL_ENDL; + stream << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\n\n\n"; + } + + if(buddy->mHasBlockListEntry) + { + // Delete the associated block list entry, if any + buddy->mHasBlockListEntry = false; + stream << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\n\n\n"; + } + if(buddy->mHasAutoAcceptListEntry) + { + // Delete the associated auto-accept list entry, if any + buddy->mHasAutoAcceptListEntry = false; + stream << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\n\n\n"; + } + } + + if(buddy->mInSLFriends) + { + + if(buddy->mCanSeeMeOnline) + { + // Buddy should not be blocked. + + // If this buddy doesn't already have either a block or autoaccept list entry, we'll update their status when we receive a SubscriptionEvent. + + // If the buddy has a block list entry, delete it. + if(buddy->mHasBlockListEntry) + { + buddy->mHasBlockListEntry = false; + stream << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\n\n\n"; + + + // If we just deleted a block list entry, add an auto-accept entry. + if(!buddy->mHasAutoAcceptListEntry) + { + buddy->mHasAutoAcceptListEntry = true; + stream << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "0" + << "\n\n\n"; + } + } + } + else + { + // Buddy should be blocked. + + // If this buddy doesn't already have either a block or autoaccept list entry, we'll update their status when we receive a SubscriptionEvent. + + // If this buddy has an autoaccept entry, delete it + if(buddy->mHasAutoAcceptListEntry) + { + buddy->mHasAutoAcceptListEntry = false; + stream << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "\n\n\n"; + + // If we just deleted an auto-accept entry, add a block list entry. + if(!buddy->mHasBlockListEntry) + { + buddy->mHasBlockListEntry = true; + stream << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "1" + << "\n\n\n"; + } + } + } + + if(!buddy->mInSLFriends && !buddy->mInVivoxBuddies) + { + // Delete this entry from the local buddy list. This should NOT invalidate the iterator, + // since it has already been incremented to the next entry. + deleteBuddy(buddy->mURI); + } + + } + writeString(stream.str()); + } + } + } +} + +///////////////////////////// +// Response/Event handlers + +void LLVivoxVoiceClient::connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID) +{ + if(statusCode != 0) + { + LL_WARNS("Voice") << "Connector.Create response failure: " << statusString << LL_ENDL; + setState(stateConnectorFailed); + LLSD args; + std::stringstream errs; + errs << mVoiceAccountServerURI << "\n:UDP: 3478, 3479, 5060, 5062, 12000-17000"; + args["HOSTID"] = errs.str(); + if (LLGridManager::getInstance()->isSystemGrid()) + { + LLNotificationsUtil::add("NoVoiceConnect", args); + } + else + { + LLNotificationsUtil::add("NoVoiceConnect-GIAB", args); + } + } + else + { + // Connector created, move forward. + LL_INFOS("Voice") << "Connector.Create succeeded, Vivox SDK version is " << versionID << LL_ENDL; + mVoiceVersion.serverVersion = versionID; + mConnectorHandle = connectorHandle; + if(getState() == stateConnectorStarting) + { + setState(stateConnectorStarted); + } + } +} + +void LLVivoxVoiceClient::loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases) +{ + LL_DEBUGS("Voice") << "Account.Login response (" << statusCode << "): " << statusString << LL_ENDL; + + // Status code of 20200 means "bad password". We may want to special-case that at some point. + + if ( statusCode == 401 ) + { + // Login failure which is probably caused by the delay after a user's password being updated. + LL_INFOS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; + setState(stateLoginRetry); + } + else if(statusCode != 0) + { + LL_WARNS("Voice") << "Account.Login response failure (" << statusCode << "): " << statusString << LL_ENDL; + setState(stateLoginFailed); + } + else + { + // Login succeeded, move forward. + mAccountHandle = accountHandle; + mNumberOfAliases = numberOfAliases; + // This needs to wait until the AccountLoginStateChangeEvent is received. +// if(getState() == stateLoggingIn) +// { +// setState(stateLoggedIn); +// } + } +} + +void LLVivoxVoiceClient::sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) +{ + sessionState *session = findSessionBeingCreatedByURI(requestId); + + if(session) + { + session->mCreateInProgress = false; + } + + if(statusCode != 0) + { + LL_WARNS("Voice") << "Session.Create response failure (" << statusCode << "): " << statusString << LL_ENDL; + if(session) + { + session->mErrorStatusCode = statusCode; + session->mErrorStatusString = statusString; + if(session == mAudioSession) + { + setState(stateJoinSessionFailed); + } + else + { + reapSession(session); + } + } + } + else + { + LL_INFOS("Voice") << "Session.Create response received (success), session handle is " << sessionHandle << LL_ENDL; + if(session) + { + setSessionHandle(session, sessionHandle); + } + } +} + +void LLVivoxVoiceClient::sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle) +{ + sessionState *session = findSessionBeingCreatedByURI(requestId); + + if(session) + { + session->mCreateInProgress = false; + } + + if(statusCode != 0) + { + LL_WARNS("Voice") << "SessionGroup.AddSession response failure (" << statusCode << "): " << statusString << LL_ENDL; + if(session) + { + session->mErrorStatusCode = statusCode; + session->mErrorStatusString = statusString; + if(session == mAudioSession) + { + setState(stateJoinSessionFailed); + } + else + { + reapSession(session); + } + } + } + else + { + LL_DEBUGS("Voice") << "SessionGroup.AddSession response received (success), session handle is " << sessionHandle << LL_ENDL; + if(session) + { + setSessionHandle(session, sessionHandle); + } + } +} + +void LLVivoxVoiceClient::sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString) +{ + sessionState *session = findSession(requestId); + if(statusCode != 0) + { + LL_WARNS("Voice") << "Session.Connect response failure (" << statusCode << "): " << statusString << LL_ENDL; + if(session) + { + session->mMediaConnectInProgress = false; + session->mErrorStatusCode = statusCode; + session->mErrorStatusString = statusString; + if(session == mAudioSession) + setState(stateJoinSessionFailed); + } + } + else + { + LL_DEBUGS("Voice") << "Session.Connect response received (success)" << LL_ENDL; + } +} + +void LLVivoxVoiceClient::logoutResponse(int statusCode, std::string &statusString) +{ + if(statusCode != 0) + { + LL_WARNS("Voice") << "Account.Logout response failure: " << statusString << LL_ENDL; + // Should this ever fail? do we care if it does? + } +} + +void LLVivoxVoiceClient::connectorShutdownResponse(int statusCode, std::string &statusString) +{ + if(statusCode != 0) + { + LL_WARNS("Voice") << "Connector.InitiateShutdown response failure: " << statusString << LL_ENDL; + // Should this ever fail? do we care if it does? + } + + mConnected = false; + + if(getState() == stateConnectorStopping) + { + setState(stateConnectorStopped); + } +} + +void LLVivoxVoiceClient::sessionAddedEvent( + std::string &uriString, + std::string &alias, + std::string &sessionHandle, + std::string &sessionGroupHandle, + bool isChannel, + bool incoming, + std::string &nameString, + std::string &applicationString) +{ + sessionState *session = NULL; + + LL_INFOS("Voice") << "session " << uriString << ", alias " << alias << ", name " << nameString << " handle " << sessionHandle << LL_ENDL; + + session = addSession(uriString, sessionHandle); + if(session) + { + session->mGroupHandle = sessionGroupHandle; + session->mIsChannel = isChannel; + session->mIncoming = incoming; + session->mAlias = alias; + + // Generate a caller UUID -- don't need to do this for channels + if(!session->mIsChannel) + { + if(IDFromName(session->mSIPURI, session->mCallerID)) + { + // Normal URI(base64-encoded UUID) + } + else if(!session->mAlias.empty() && IDFromName(session->mAlias, session->mCallerID)) + { + // Wrong URI, but an alias is available. Stash the incoming URI as an alternate + session->mAlternateSIPURI = session->mSIPURI; + + // and generate a proper URI from the ID. + setSessionURI(session, sipURIFromID(session->mCallerID)); + } + else + { + LL_INFOS("Voice") << "Could not generate caller id from uri, using hash of uri " << session->mSIPURI << LL_ENDL; + setUUIDFromStringHash(session->mCallerID, session->mSIPURI); + session->mSynthesizedCallerID = true; + + // Can't look up the name in this case -- we have to extract it from the URI. + std::string namePortion = nameFromsipURI(session->mSIPURI); + if(namePortion.empty()) + { + // Didn't seem to be a SIP URI, just use the whole provided name. + namePortion = nameString; + } + + // Some incoming names may be separated with an underscore instead of a space. Fix this. + LLStringUtil::replaceChar(namePortion, '_', ' '); + + // Act like we just finished resolving the name (this stores it in all the right places) + avatarNameResolved(session->mCallerID, namePortion); + } + + LL_INFOS("Voice") << "caller ID: " << session->mCallerID << LL_ENDL; + + if(!session->mSynthesizedCallerID) + { + // If we got here, we don't have a proper name. Initiate a lookup. + lookupName(session->mCallerID); + } + } + } +} + +void LLVivoxVoiceClient::sessionGroupAddedEvent(std::string &sessionGroupHandle) +{ + LL_DEBUGS("Voice") << "handle " << sessionGroupHandle << LL_ENDL; + +#if USE_SESSION_GROUPS + if(mMainSessionGroupHandle.empty()) + { + // This is the first (i.e. "main") session group. Save its handle. + mMainSessionGroupHandle = sessionGroupHandle; + } + else + { + LL_DEBUGS("Voice") << "Already had a session group handle " << mMainSessionGroupHandle << LL_ENDL; + } +#endif +} + +void LLVivoxVoiceClient::joinedAudioSession(sessionState *session) +{ + LL_DEBUGS("Voice") << "Joined Audio Session" << LL_ENDL; + if(mAudioSession != session) + { + sessionState *oldSession = mAudioSession; + + mAudioSession = session; + mAudioSessionChanged = true; + + // The old session may now need to be deleted. + reapSession(oldSession); + } + + // This is the session we're joining. + if(getState() == stateJoiningSession) + { + setState(stateSessionJoined); + + // SLIM SDK: we don't always receive a participant state change for ourselves when joining a channel now. + // Add the current user as a participant here. + participantState *participant = session->addParticipant(sipURIFromName(mAccountName)); + if(participant) + { + participant->mIsSelf = true; + lookupName(participant->mAvatarID); + + LL_INFOS("Voice") << "added self as participant \"" << participant->mAccountName + << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; + } + + if(!session->mIsChannel) + { + // this is a p2p session. Make sure the other end is added as a participant. + participantState *participant = session->addParticipant(session->mSIPURI); + if(participant) + { + if(participant->mAvatarIDValid) + { + lookupName(participant->mAvatarID); + } + else if(!session->mName.empty()) + { + participant->mDisplayName = session->mName; + avatarNameResolved(participant->mAvatarID, session->mName); + } + + // TODO: Question: Do we need to set up mAvatarID/mAvatarIDValid here? + LL_INFOS("Voice") << "added caller as participant \"" << participant->mAccountName + << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; + } + } + } +} + +void LLVivoxVoiceClient::sessionRemovedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle) +{ + LL_INFOS("Voice") << "handle " << sessionHandle << LL_ENDL; + + sessionState *session = findSession(sessionHandle); + if(session) + { + leftAudioSession(session); + + // This message invalidates the session's handle. Set it to empty. + setSessionHandle(session); + + // This also means that the session's session group is now empty. + // Terminate the session group so it doesn't leak. + sessionGroupTerminateSendMessage(session); + + // Reset the media state (we now have no info) + session->mMediaStreamState = streamStateUnknown; + session->mTextStreamState = streamStateUnknown; + + // Conditionally delete the session + reapSession(session); + } + else + { + LL_WARNS("Voice") << "unknown session " << sessionHandle << " removed" << LL_ENDL; + } +} + +void LLVivoxVoiceClient::reapSession(sessionState *session) +{ + if(session) + { + if(!session->mHandle.empty()) + { + LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (non-null session handle)" << LL_ENDL; + } + else if(session->mCreateInProgress) + { + LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (create in progress)" << LL_ENDL; + } + else if(session->mMediaConnectInProgress) + { + LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (connect in progress)" << LL_ENDL; + } + else if(session == mAudioSession) + { + LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the current session)" << LL_ENDL; + } + else if(session == mNextAudioSession) + { + LL_DEBUGS("Voice") << "NOT deleting session " << session->mSIPURI << " (it's the next session)" << LL_ENDL; + } + else + { + // TODO: Question: Should we check for queued text messages here? + // We don't have a reason to keep tracking this session, so just delete it. + LL_DEBUGS("Voice") << "deleting session " << session->mSIPURI << LL_ENDL; + deleteSession(session); + session = NULL; + } + } + else + { +// LL_DEBUGS("Voice") << "session is NULL" << LL_ENDL; + } +} + +// Returns true if the session seems to indicate we've moved to a region on a different voice server +bool LLVivoxVoiceClient::sessionNeedsRelog(sessionState *session) +{ + bool result = false; + + if(session != NULL) + { + // Only make this check for spatial channels (so it won't happen for group or p2p calls) + if(session->mIsSpatial) + { + std::string::size_type atsign; + + atsign = session->mSIPURI.find("@"); + + if(atsign != std::string::npos) + { + std::string urihost = session->mSIPURI.substr(atsign + 1); + if(stricmp(urihost.c_str(), mVoiceSIPURIHostName.c_str())) + { + // The hostname in this URI is different from what we expect. This probably means we need to relog. + + // We could make a ProvisionVoiceAccountRequest and compare the result with the current values of + // mVoiceSIPURIHostName and mVoiceAccountServerURI to be really sure, but this is a pretty good indicator. + + result = true; + } + } + } + } + + return result; +} + +void LLVivoxVoiceClient::leftAudioSession( + sessionState *session) +{ + if(mAudioSession == session) + { + switch(getState()) + { + case stateJoiningSession: + case stateSessionJoined: + case stateRunning: + case stateLeavingSession: + case stateJoinSessionFailed: + case stateJoinSessionFailedWaiting: + // normal transition + LL_DEBUGS("Voice") << "left session " << session->mHandle << " in state " << state2string(getState()) << LL_ENDL; + setState(stateSessionTerminated); + break; + + case stateSessionTerminated: + // this will happen sometimes -- there are cases where we send the terminate and then go straight to this state. + LL_WARNS("Voice") << "left session " << session->mHandle << " in state " << state2string(getState()) << LL_ENDL; + break; + + default: + LL_WARNS("Voice") << "unexpected SessionStateChangeEvent (left session) in state " << state2string(getState()) << LL_ENDL; + setState(stateSessionTerminated); + break; + } + } +} + +void LLVivoxVoiceClient::accountLoginStateChangeEvent( + std::string &accountHandle, + int statusCode, + std::string &statusString, + int state) +{ + /* + According to Mike S., status codes for this event are: + login_state_logged_out=0, + login_state_logged_in = 1, + login_state_logging_in = 2, + login_state_logging_out = 3, + login_state_resetting = 4, + login_state_error=100 + */ + + LL_DEBUGS("Voice") << "state change event: " << state << LL_ENDL; + switch(state) + { + case 1: + if(getState() == stateLoggingIn) + { + setState(stateLoggedIn); + } + break; + + case 3: + // The user is in the process of logging out. + setState(stateLoggingOut); + break; + + case 0: + // The user has been logged out. + setState(stateLoggedOut); + break; + + default: + //Used to be a commented out warning + LL_DEBUGS("Voice") << "unknown state: " << state << LL_ENDL; + break; + } +} + +void LLVivoxVoiceClient::mediaStreamUpdatedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle, + int statusCode, + std::string &statusString, + int state, + bool incoming) +{ + sessionState *session = findSession(sessionHandle); + + LL_DEBUGS("Voice") << "session " << sessionHandle << ", status code " << statusCode << ", string \"" << statusString << "\"" << LL_ENDL; + + if(session) + { + // We know about this session + + // Save the state for later use + session->mMediaStreamState = state; + + switch(statusCode) + { + case 0: + case 200: + // generic success + // Don't change the saved error code (it may have been set elsewhere) + break; + default: + // save the status code for later + session->mErrorStatusCode = statusCode; + break; + } + + switch(state) + { + case streamStateIdle: + // Standard "left audio session" + session->mVoiceEnabled = false; + session->mMediaConnectInProgress = false; + leftAudioSession(session); + break; + + case streamStateConnected: + session->mVoiceEnabled = true; + session->mMediaConnectInProgress = false; + joinedAudioSession(session); + break; + + case streamStateRinging: + if(incoming) + { + // Send the voice chat invite to the GUI layer + // TODO: Question: Should we correlate with the mute list here? + session->mIMSessionID = LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, session->mCallerID); + session->mVoiceInvitePending = true; + if(session->mName.empty()) + { + lookupName(session->mCallerID); + } + else + { + // Act like we just finished resolving the name + avatarNameResolved(session->mCallerID, session->mName); + } + } + break; + + default: + LL_WARNS("Voice") << "unknown state " << state << LL_ENDL; + break; + + } + + } + else + { + LL_WARNS("Voice") << "session " << sessionHandle << "not found"<< LL_ENDL; + } +} + +void LLVivoxVoiceClient::textStreamUpdatedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle, + bool enabled, + int state, + bool incoming) +{ + sessionState *session = findSession(sessionHandle); + + if(session) + { + // Save the state for later use + session->mTextStreamState = state; + + // We know about this session + switch(state) + { + case 0: // We see this when the text stream closes + LL_DEBUGS("Voice") << "stream closed" << LL_ENDL; + break; + + case 1: // We see this on an incoming call from the Connector + // Try to send any text messages queued for this session. + sendQueuedTextMessages(session); + + // Send the text chat invite to the GUI layer + // TODO: Question: Should we correlate with the mute list here? + session->mTextInvitePending = true; + if(session->mName.empty()) + { + lookupName(session->mCallerID); + } + else + { + // Act like we just finished resolving the name + avatarNameResolved(session->mCallerID, session->mName); + } + break; + + default: + LL_WARNS("Voice") << "unknown state " << state << LL_ENDL; + break; + + } + } +} + +void LLVivoxVoiceClient::participantAddedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle, + std::string &uriString, + std::string &alias, + std::string &nameString, + std::string &displayNameString, + int participantType) +{ + sessionState *session = findSession(sessionHandle); + if(session) + { + participantState *participant = session->addParticipant(uriString); + if(participant) + { + participant->mAccountName = nameString; + + LL_DEBUGS("Voice") << "added participant \"" << participant->mAccountName + << "\" (" << participant->mAvatarID << ")"<< LL_ENDL; + + if(participant->mAvatarIDValid) + { + // Initiate a lookup + lookupName(participant->mAvatarID); + } + else + { + // If we don't have a valid avatar UUID, we need to fill in the display name to make the active speakers floater work. + std::string namePortion = nameFromsipURI(uriString); + if(namePortion.empty()) + { + // Problem with the SIP URI, fall back to the display name + namePortion = displayNameString; + } + if(namePortion.empty()) + { + // Problems with both of the above, fall back to the account name + namePortion = nameString; + } + + // Set the display name (which is a hint to the active speakers window not to do its own lookup) + participant->mDisplayName = namePortion; + avatarNameResolved(participant->mAvatarID, namePortion); + } + } + } +} + +void LLVivoxVoiceClient::participantRemovedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle, + std::string &uriString, + std::string &alias, + std::string &nameString) +{ + sessionState *session = findSession(sessionHandle); + if(session) + { + participantState *participant = session->findParticipant(uriString); + if(participant) + { + session->removeParticipant(participant); + } + else + { + LL_DEBUGS("Voice") << "unknown participant " << uriString << LL_ENDL; + } + } + else + { + LL_DEBUGS("Voice") << "unknown session " << sessionHandle << LL_ENDL; + } +} + + +void LLVivoxVoiceClient::participantUpdatedEvent( + std::string &sessionHandle, + std::string &sessionGroupHandle, + std::string &uriString, + std::string &alias, + bool isModeratorMuted, + bool isSpeaking, + int volume, + F32 energy) +{ + sessionState *session = findSession(sessionHandle); + if(session) + { + participantState *participant = session->findParticipant(uriString); + + if(participant) + { + participant->mIsSpeaking = isSpeaking; + participant->mIsModeratorMuted = isModeratorMuted; + + // SLIM SDK: convert range: ensure that energy is set to zero if is_speaking is false + if (isSpeaking) + { + participant->mSpeakingTimeout.reset(); + participant->mPower = energy; + } + else + { + participant->mPower = 0.0f; + } + + // *HACK: Minimal hack to fix EXT-6508, ignore the incoming volume if it is zero. + // This happens because we send volume zero to Vivox when someone is muted, + // Vivox then send it back to us, overwriting the previous volume. + // Remove this hack once volume refactoring from EXT-6031 is applied. + if (volume != 0) + { + participant->mVolume = volume; + } + + + // *HACK: mantipov: added while working on EXT-3544 + /* + Sometimes LLVoiceClient::participantUpdatedEvent callback is called BEFORE + LLViewerChatterBoxSessionAgentListUpdates::post() sometimes AFTER. + + participantUpdatedEvent updates voice participant state in particular participantState::mIsModeratorMuted + Originally we wanted to update session Speaker Manager to fire LLSpeakerVoiceModerationEvent to fix the EXT-3544 bug. + Calling of the LLSpeakerMgr::update() method was added into LLIMMgr::processAgentListUpdates. + + But in case participantUpdatedEvent() is called after LLViewerChatterBoxSessionAgentListUpdates::post() + voice participant mIsModeratorMuted is changed after speakers are updated in Speaker Manager + and event is not fired. + + So, we have to call LLSpeakerMgr::update() here. In any case it is better than call it + in LLCallFloater::draw() + */ + LLVoiceChannel* voice_cnl = LLVoiceChannel::getCurrentVoiceChannel(); + + // ignore session ID of local chat + if (voice_cnl && voice_cnl->getSessionID().notNull()) + { + LLSpeakerMgr* speaker_manager = LLIMModel::getInstance()->getSpeakerManager(voice_cnl->getSessionID()); + if (speaker_manager) + { + speaker_manager->update(true); + } + } + + } + else + { + LL_WARNS("Voice") << "unknown participant: " << uriString << LL_ENDL; + } + } + else + { + LL_INFOS("Voice") << "unknown session " << sessionHandle << LL_ENDL; + } +} + +void LLVivoxVoiceClient::buddyPresenceEvent( + std::string &uriString, + std::string &alias, + std::string &statusString, + std::string &applicationString) +{ + buddyListEntry *buddy = findBuddy(uriString); + + if(buddy) + { + LL_DEBUGS("Voice") << "Presence event for " << buddy->mDisplayName << " status \"" << statusString << "\", application \"" << applicationString << "\""<< LL_ENDL; + LL_DEBUGS("Voice") << "before: mOnlineSL = " << (buddy->mOnlineSL?"true":"false") << ", mOnlineSLim = " << (buddy->mOnlineSLim?"true":"false") << LL_ENDL; + + if(applicationString.empty()) + { + // This presence event is from a client that doesn't set up the Application string. Do things the old-skool way. + // NOTE: this will be needed to support people who aren't on the 3010-class SDK yet. + + if ( stricmp("Unknown", statusString.c_str())== 0) + { + // User went offline with a non-SLim-enabled viewer. + buddy->mOnlineSL = false; + } + else if ( stricmp("Online", statusString.c_str())== 0) + { + // User came online with a non-SLim-enabled viewer. + buddy->mOnlineSL = true; + } + else + { + // If the user is online through SLim, their status will be "Online-slc", "Away", or something else. + // NOTE: we should never see this unless someone is running an OLD version of SLim -- the versions that should be in use now all set the application string. + buddy->mOnlineSLim = true; + } + } + else if(applicationString.find("SecondLifeViewer") != std::string::npos) + { + // This presence event is from a viewer that sets the application string + if ( stricmp("Unknown", statusString.c_str())== 0) + { + // Viewer says they're offline + buddy->mOnlineSL = false; + } + else + { + // Viewer says they're online + buddy->mOnlineSL = true; + } + } + else + { + // This presence event is from something which is NOT the SL viewer (assume it's SLim). + if ( stricmp("Unknown", statusString.c_str())== 0) + { + // SLim says they're offline + buddy->mOnlineSLim = false; + } + else + { + // SLim says they're online + buddy->mOnlineSLim = true; + } + } + + LL_DEBUGS("Voice") << "after: mOnlineSL = " << (buddy->mOnlineSL?"true":"false") << ", mOnlineSLim = " << (buddy->mOnlineSLim?"true":"false") << LL_ENDL; + + // HACK -- increment the internal change serial number in the LLRelationship (without changing the actual status), so the UI notices the change. + LLAvatarTracker::instance().setBuddyOnline(buddy->mUUID,LLAvatarTracker::instance().isBuddyOnline(buddy->mUUID)); + + notifyFriendObservers(); + } + else + { + LL_DEBUGS("Voice") << "Presence for unknown buddy " << uriString << LL_ENDL; + } +} + +void LLVivoxVoiceClient::messageEvent( + std::string &sessionHandle, + std::string &uriString, + std::string &alias, + std::string &messageHeader, + std::string &messageBody, + std::string &applicationString) +{ + LL_DEBUGS("Voice") << "Message event, session " << sessionHandle << " from " << uriString << LL_ENDL; +// LL_DEBUGS("Voice") << " header " << messageHeader << ", body: \n" << messageBody << LL_ENDL; + + if(messageHeader.find("text/html") != std::string::npos) + { + std::string message; + + { + const std::string startMarker = ", try looking for a instead. + start = messageBody.find(startSpan); + start = messageBody.find(startMarker2, start); + end = messageBody.find(endSpan); + + if(start != std::string::npos) + { + start += startMarker2.size(); + + if(end != std::string::npos) + end -= start; + + message.assign(messageBody, start, end); + } + } + } + +// LL_DEBUGS("Voice") << " raw message = \n" << message << LL_ENDL; + + // strip formatting tags + { + std::string::size_type start; + std::string::size_type end; + + while((start = message.find('<')) != std::string::npos) + { + if((end = message.find('>', start + 1)) != std::string::npos) + { + // Strip out the tag + message.erase(start, (end + 1) - start); + } + else + { + // Avoid an infinite loop + break; + } + } + } + + // Decode ampersand-escaped chars + { + std::string::size_type mark = 0; + + // The text may contain text encoded with <, >, and & + mark = 0; + while((mark = message.find("<", mark)) != std::string::npos) + { + message.replace(mark, 4, "<"); + mark += 1; + } + + mark = 0; + while((mark = message.find(">", mark)) != std::string::npos) + { + message.replace(mark, 4, ">"); + mark += 1; + } + + mark = 0; + while((mark = message.find("&", mark)) != std::string::npos) + { + message.replace(mark, 5, "&"); + mark += 1; + } + } + + // strip leading/trailing whitespace (since we always seem to get a couple newlines) + LLStringUtil::trim(message); + +// LL_DEBUGS("Voice") << " stripped message = \n" << message << LL_ENDL; + + sessionState *session = findSession(sessionHandle); + if(session) + { + bool is_busy = gAgent.getBusy(); + bool is_muted = LLMuteList::getInstance()->isMuted(session->mCallerID, session->mName, LLMute::flagTextChat); + bool is_linden = LLMuteList::getInstance()->isLinden(session->mName); + bool quiet_chat = false; + LLChat chat; + + chat.mMuted = is_muted && !is_linden; + + if(!chat.mMuted) + { + chat.mFromID = session->mCallerID; + chat.mFromName = session->mName; + chat.mSourceType = CHAT_SOURCE_AGENT; + + if(is_busy && !is_linden) + { + quiet_chat = true; + // TODO: Question: Return busy mode response here? Or maybe when session is started instead? + } + + LL_DEBUGS("Voice") << "adding message, name " << session->mName << " session " << session->mIMSessionID << ", target " << session->mCallerID << LL_ENDL; + gIMMgr->addMessage(session->mIMSessionID, + session->mCallerID, + session->mName.c_str(), + message.c_str(), + LLStringUtil::null, // default arg + IM_NOTHING_SPECIAL, // default arg + 0, // default arg + LLUUID::null, // default arg + LLVector3::zero, // default arg + true); // prepend name and make it a link to the user's profile + + } + } + } +} + +void LLVivoxVoiceClient::sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType) +{ + sessionState *session = findSession(sessionHandle); + + if(session) + { + participantState *participant = session->findParticipant(uriString); + if(participant) + { + if (!stricmp(notificationType.c_str(), "Typing")) + { + // Other end started typing + // TODO: The proper way to add a typing notification seems to be LLIMMgr::processIMTypingStart(). + // It requires an LLIMInfo for the message, which we don't have here. + } + else if (!stricmp(notificationType.c_str(), "NotTyping")) + { + // Other end stopped typing + // TODO: The proper way to remove a typing notification seems to be LLIMMgr::processIMTypingStop(). + // It requires an LLIMInfo for the message, which we don't have here. + } + else + { + LL_DEBUGS("Voice") << "Unknown notification type " << notificationType << "for participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; + } + } + else + { + LL_DEBUGS("Voice") << "Unknown participant " << uriString << " in session " << session->mSIPURI << LL_ENDL; + } + } + else + { + LL_DEBUGS("Voice") << "Unknown session handle " << sessionHandle << LL_ENDL; + } +} + +void LLVivoxVoiceClient::subscriptionEvent(std::string &buddyURI, std::string &subscriptionHandle, std::string &alias, std::string &displayName, std::string &applicationString, std::string &subscriptionType) +{ + buddyListEntry *buddy = findBuddy(buddyURI); + + if(!buddy) + { + // Couldn't find buddy by URI, try converting the alias... + if(!alias.empty()) + { + LLUUID id; + if(IDFromName(alias, id)) + { + buddy = findBuddy(id); + } + } + } + + if(buddy) + { + std::ostringstream stream; + + if(buddy->mCanSeeMeOnline) + { + // Sending the response will create an auto-accept rule + buddy->mHasAutoAcceptListEntry = true; + } + else + { + // Sending the response will create a block rule + buddy->mHasBlockListEntry = true; + } + + if(buddy->mInSLFriends) + { + buddy->mInVivoxBuddies = true; + } + + stream + << "" + << "" << mAccountHandle << "" + << "" << buddy->mURI << "" + << "" << (buddy->mCanSeeMeOnline?"Allow":"Hide") << "" + << ""<< (buddy->mInSLFriends?"1":"0")<< "" + << "" << subscriptionHandle << "" + << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::auxAudioPropertiesEvent(F32 energy) +{ + LL_DEBUGS("Voice") << "got energy " << energy << LL_ENDL; + mTuningEnergy = energy; +} + +void LLVivoxVoiceClient::buddyListChanged() +{ + // This is called after we receive a BuddyAndGroupListChangedEvent. + mBuddyListMapPopulated = true; + mFriendsListDirty = true; +} + +void LLVivoxVoiceClient::muteListChanged() +{ + // The user's mute list has been updated. Go through the current participant list and sync it with the mute list. + if(mAudioSession) + { + participantMap::iterator iter = mAudioSession->mParticipantsByURI.begin(); + + for(; iter != mAudioSession->mParticipantsByURI.end(); iter++) + { + participantState *p = iter->second; + + // Check to see if this participant is on the mute list already + if(p->updateMuteState()) + mAudioSession->mVolumeDirty = true; + } + } +} + +void LLVivoxVoiceClient::updateFriends(U32 mask) +{ + if(mask & (LLFriendObserver::ADD | LLFriendObserver::REMOVE | LLFriendObserver::POWERS)) + { + // Just resend the whole friend list to the daemon + mFriendsListDirty = true; + } +} + +///////////////////////////// +// Managing list of participants +LLVivoxVoiceClient::participantState::participantState(const std::string &uri) : + mURI(uri), + mPTT(false), + mIsSpeaking(false), + mIsModeratorMuted(false), + mLastSpokeTimestamp(0.f), + mPower(0.f), + mVolume(-1), + mOnMuteList(false), + mUserVolume(-1), + mVolumeDirty(false), + mAvatarIDValid(false), + mIsSelf(false) +{ +} + +LLVivoxVoiceClient::participantState *LLVivoxVoiceClient::sessionState::addParticipant(const std::string &uri) +{ + participantState *result = NULL; + bool useAlternateURI = false; + + // Note: this is mostly the body of LLVivoxVoiceClient::sessionState::findParticipant(), but since we need to know if it + // matched the alternate SIP URI (so we can add it properly), we need to reproduce it here. + { + participantMap::iterator iter = mParticipantsByURI.find(uri); + + if(iter == mParticipantsByURI.end()) + { + if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) + { + // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. + // Use mSIPURI instead, since it will be properly encoded. + iter = mParticipantsByURI.find(mSIPURI); + useAlternateURI = true; + } + } + + if(iter != mParticipantsByURI.end()) + { + result = iter->second; + } + } + + if(!result) + { + // participant isn't already in one list or the other. + result = new participantState(useAlternateURI?mSIPURI:uri); + mParticipantsByURI.insert(participantMap::value_type(result->mURI, result)); + mParticipantsChanged = true; + + // Try to do a reverse transform on the URI to get the GUID back. + { + LLUUID id; + if(LLVivoxVoiceClient::getInstance()->IDFromName(result->mURI, id)) + { + result->mAvatarIDValid = true; + result->mAvatarID = id; + + if(result->updateMuteState()) + mVolumeDirty = true; + } + else + { + // Create a UUID by hashing the URI, but do NOT set mAvatarIDValid. + // This tells both code in LLVivoxVoiceClient and code in llfloateractivespeakers.cpp that the ID will not be in the name cache. + setUUIDFromStringHash(result->mAvatarID, uri); + } + } + + mParticipantsByUUID.insert(participantUUIDMap::value_type(result->mAvatarID, result)); + + result->mUserVolume = LLSpeakerVolumeStorage::getInstance()->getSpeakerVolume(result->mAvatarID); + + LL_DEBUGS("Voice") << "participant \"" << result->mURI << "\" added." << LL_ENDL; + } + + return result; +} + +bool LLVivoxVoiceClient::participantState::updateMuteState() +{ + bool result = false; + + if(mAvatarIDValid) + { + bool isMuted = LLMuteList::getInstance()->isMuted(mAvatarID, LLMute::flagVoiceChat); + if(mOnMuteList != isMuted) + { + mOnMuteList = isMuted; + mVolumeDirty = true; + result = true; + } + } + return result; +} + +bool LLVivoxVoiceClient::participantState::isAvatar() +{ + return mAvatarIDValid; +} + +void LLVivoxVoiceClient::sessionState::removeParticipant(LLVivoxVoiceClient::participantState *participant) +{ + if(participant) + { + participantMap::iterator iter = mParticipantsByURI.find(participant->mURI); + participantUUIDMap::iterator iter2 = mParticipantsByUUID.find(participant->mAvatarID); + + LL_DEBUGS("Voice") << "participant \"" << participant->mURI << "\" (" << participant->mAvatarID << ") removed." << LL_ENDL; + + if(iter == mParticipantsByURI.end()) + { + LL_ERRS("Voice") << "Internal error: participant " << participant->mURI << " not in URI map" << LL_ENDL; + } + else if(iter2 == mParticipantsByUUID.end()) + { + LL_ERRS("Voice") << "Internal error: participant ID " << participant->mAvatarID << " not in UUID map" << LL_ENDL; + } + else if(iter->second != iter2->second) + { + LL_ERRS("Voice") << "Internal error: participant mismatch!" << LL_ENDL; + } + else + { + mParticipantsByURI.erase(iter); + mParticipantsByUUID.erase(iter2); + + delete participant; + mParticipantsChanged = true; + } + } +} + +void LLVivoxVoiceClient::sessionState::removeAllParticipants() +{ + LL_DEBUGS("Voice") << "called" << LL_ENDL; + + while(!mParticipantsByURI.empty()) + { + removeParticipant(mParticipantsByURI.begin()->second); + } + + if(!mParticipantsByUUID.empty()) + { + LL_ERRS("Voice") << "Internal error: empty URI map, non-empty UUID map" << LL_ENDL; + } +} + +void LLVivoxVoiceClient::getParticipantList(std::set &participants) +{ + if(mAudioSession) + { + for(participantUUIDMap::iterator iter = mAudioSession->mParticipantsByUUID.begin(); + iter != mAudioSession->mParticipantsByUUID.end(); + iter++) + { + participants.insert(iter->first); + } + } +} + +bool LLVivoxVoiceClient::isParticipant(const LLUUID &speaker_id) +{ + if(mAudioSession) + { + return (mAudioSession->mParticipantsByUUID.find(speaker_id) != mAudioSession->mParticipantsByUUID.end()); + } + return false; +} + + +LLVivoxVoiceClient::participantState *LLVivoxVoiceClient::sessionState::findParticipant(const std::string &uri) +{ + participantState *result = NULL; + + participantMap::iterator iter = mParticipantsByURI.find(uri); + + if(iter == mParticipantsByURI.end()) + { + if(!mAlternateSIPURI.empty() && (uri == mAlternateSIPURI)) + { + // This is a p2p session (probably with the SLIM client) with an alternate URI for the other participant. + // Look up the other URI + iter = mParticipantsByURI.find(mSIPURI); + } + } + + if(iter != mParticipantsByURI.end()) + { + result = iter->second; + } + + return result; +} + +LLVivoxVoiceClient::participantState* LLVivoxVoiceClient::sessionState::findParticipantByID(const LLUUID& id) +{ + participantState * result = NULL; + participantUUIDMap::iterator iter = mParticipantsByUUID.find(id); + + if(iter != mParticipantsByUUID.end()) + { + result = iter->second; + } + + return result; +} + +LLVivoxVoiceClient::participantState* LLVivoxVoiceClient::findParticipantByID(const LLUUID& id) +{ + participantState * result = NULL; + + if(mAudioSession) + { + result = mAudioSession->findParticipantByID(id); + } + + return result; +} + + +void LLVivoxVoiceClient::parcelChanged() +{ + if(getState() >= stateNoChannel) + { + // If the user is logged in, start a channel lookup. + LL_DEBUGS("Voice") << "sending ParcelVoiceInfoRequest (" << mCurrentRegionName << ", " << mCurrentParcelLocalID << ")" << LL_ENDL; + + std::string url = gAgent.getRegion()->getCapability("ParcelVoiceInfoRequest"); + LLSD data; + LLHTTPClient::post( + url, + data, + new LLVivoxVoiceClientCapResponder); + } + else + { + // The transition to stateNoChannel needs to kick this off again. + LL_INFOS("Voice") << "not logged in yet, deferring" << LL_ENDL; + } +} + +void LLVivoxVoiceClient::switchChannel( + std::string uri, + bool spatial, + bool no_reconnect, + bool is_p2p, + std::string hash) +{ + bool needsSwitch = false; + + LL_DEBUGS("Voice") + << "called in state " << state2string(getState()) + << " with uri \"" << uri << "\"" + << (spatial?", spatial is true":", spatial is false") + << LL_ENDL; + + switch(getState()) + { + case stateJoinSessionFailed: + case stateJoinSessionFailedWaiting: + case stateNoChannel: + // Always switch to the new URI from these states. + needsSwitch = true; + break; + + default: + if(mSessionTerminateRequested) + { + // If a terminate has been requested, we need to compare against where the URI we're already headed to. + if(mNextAudioSession) + { + if(mNextAudioSession->mSIPURI != uri) + needsSwitch = true; + } + else + { + // mNextAudioSession is null -- this probably means we're on our way back to spatial. + if(!uri.empty()) + { + // We do want to process a switch in this case. + needsSwitch = true; + } + } + } + else + { + // Otherwise, compare against the URI we're in now. + if(mAudioSession) + { + if(mAudioSession->mSIPURI != uri) + { + needsSwitch = true; + } + } + else + { + if(!uri.empty()) + { + // mAudioSession is null -- it's not clear what case would cause this. + // For now, log it as a warning and see if it ever crops up. + LL_WARNS("Voice") << "No current audio session." << LL_ENDL; + } + } + } + break; + } + + if(needsSwitch) + { + if(uri.empty()) + { + // Leave any channel we may be in + LL_DEBUGS("Voice") << "leaving channel" << LL_ENDL; + + sessionState *oldSession = mNextAudioSession; + mNextAudioSession = NULL; + + // The old session may now need to be deleted. + reapSession(oldSession); + + notifyStatusObservers(LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED); + } + else + { + LL_DEBUGS("Voice") << "switching to channel " << uri << LL_ENDL; + + mNextAudioSession = addSession(uri); + mNextAudioSession->mHash = hash; + mNextAudioSession->mIsSpatial = spatial; + mNextAudioSession->mReconnect = !no_reconnect; + mNextAudioSession->mIsP2P = is_p2p; + } + + if(getState() <= stateNoChannel) + { + // We're already set up to join a channel, just needed to fill in the session URI + } + else + { + // State machine will come around and rejoin if uri/handle is not empty. + sessionTerminate(); + } + } +} + +void LLVivoxVoiceClient::joinSession(sessionState *session) +{ + mNextAudioSession = session; + + if(getState() <= stateNoChannel) + { + // We're already set up to join a channel, just needed to fill in the session handle + } + else + { + // State machine will come around and rejoin if uri/handle is not empty. + sessionTerminate(); + } +} + +void LLVivoxVoiceClient::setNonSpatialChannel( + const std::string &uri, + const std::string &credentials) +{ + switchChannel(uri, false, false, false, credentials); +} + +void LLVivoxVoiceClient::setSpatialChannel( + const std::string &uri, + const std::string &credentials) +{ + mSpatialSessionURI = uri; + mSpatialSessionCredentials = credentials; + mAreaVoiceDisabled = mSpatialSessionURI.empty(); + + LL_DEBUGS("Voice") << "got spatial channel uri: \"" << uri << "\"" << LL_ENDL; + + if((mAudioSession && !(mAudioSession->mIsSpatial)) || (mNextAudioSession && !(mNextAudioSession->mIsSpatial))) + { + // User is in a non-spatial chat or joining a non-spatial chat. Don't switch channels. + LL_INFOS("Voice") << "in non-spatial chat, not switching channels" << LL_ENDL; + } + else + { + switchChannel(mSpatialSessionURI, true, false, false, mSpatialSessionCredentials); + } +} + +void LLVivoxVoiceClient::callUser(const LLUUID &uuid) +{ + std::string userURI = sipURIFromID(uuid); + + switchChannel(userURI, false, true, true); +} + +LLVivoxVoiceClient::sessionState* LLVivoxVoiceClient::startUserIMSession(const LLUUID &uuid) +{ + // Figure out if a session with the user already exists + sessionState *session = findSession(uuid); + if(!session) + { + // No session with user, need to start one. + std::string uri = sipURIFromID(uuid); + session = addSession(uri); + + llassert(session); + if (!session) return NULL; + + session->mIsSpatial = false; + session->mReconnect = false; + session->mIsP2P = true; + session->mCallerID = uuid; + } + + if(session->mHandle.empty()) + { + // Session isn't active -- start it up. + sessionCreateSendMessage(session, false, true); + } + else + { + // Session is already active -- start up text. + sessionTextConnectSendMessage(session); + } + + return session; +} + +BOOL LLVivoxVoiceClient::sendTextMessage(const LLUUID& participant_id, const std::string& message) +{ + bool result = false; + + // Attempt to locate the indicated session + sessionState *session = startUserIMSession(participant_id); + if(session) + { + // found the session, attempt to send the message + session->mTextMsgQueue.push(message); + + // Try to send queued messages (will do nothing if the session is not open yet) + sendQueuedTextMessages(session); + + // The message is queued, so we succeed. + result = true; + } + else + { + LL_DEBUGS("Voice") << "Session not found for participant ID " << participant_id << LL_ENDL; + } + + return result; +} + +void LLVivoxVoiceClient::sendQueuedTextMessages(sessionState *session) +{ + if(session->mTextStreamState == 1) + { + if(!session->mTextMsgQueue.empty()) + { + std::ostringstream stream; + + while(!session->mTextMsgQueue.empty()) + { + std::string message = session->mTextMsgQueue.front(); + session->mTextMsgQueue.pop(); + stream + << "" + << "" << session->mHandle << "" + << "text/HTML" + << "" << message << "" + << "" + << "\n\n\n"; + } + writeString(stream.str()); + } + } + else + { + // Session isn't connected yet, defer until later. + } +} + +void LLVivoxVoiceClient::endUserIMSession(const LLUUID &uuid) +{ + // Figure out if a session with the user exists + sessionState *session = findSession(uuid); + if(session) + { + // found the session + if(!session->mHandle.empty()) + { + sessionTextDisconnectSendMessage(session); + } + } + else + { + LL_DEBUGS("Voice") << "Session not found for participant ID " << uuid << LL_ENDL; + } +} + +bool LLVivoxVoiceClient::answerInvite(std::string &sessionHandle) +{ + // this is only ever used to answer incoming p2p call invites. + + sessionState *session = findSession(sessionHandle); + if(session) + { + session->mIsSpatial = false; + session->mReconnect = false; + session->mIsP2P = true; + + joinSession(session); + return true; + } + + return false; +} + +BOOL LLVivoxVoiceClient::isOnlineSIP(const LLUUID &id) +{ + bool result = false; + buddyListEntry *buddy = findBuddy(id); + if(buddy) + { + result = buddy->mOnlineSLim; + LL_DEBUGS("Voice") << "Buddy " << buddy->mDisplayName << " is SIP " << (result?"online":"offline") << LL_ENDL; + } + + if(!result) + { + // This user isn't on the buddy list or doesn't show online status through the buddy list, but could be a participant in an existing session if they initiated a text IM. + sessionState *session = findSession(id); + if(session && !session->mHandle.empty()) + { + if((session->mTextStreamState != streamStateUnknown) || (session->mMediaStreamState > streamStateIdle)) + { + LL_DEBUGS("Voice") << "Open session with " << id << " found, returning SIP online state" << LL_ENDL; + // we have a p2p text session open with this user, so by definition they're online. + result = true; + } + } + } + + return result; +} + +bool LLVivoxVoiceClient::isVoiceWorking() +{ + //Added stateSessionTerminated state to avoid problems with call in parcels with disabled voice (EXT-4758) + // Condition with joining spatial num was added to take into account possible problems with connection to voice + // server(EXT-4313). See bug descriptions and comments for MAX_NORMAL_JOINING_SPATIAL_NUM for more info. + return (mSpatialJoiningNum < MAX_NORMAL_JOINING_SPATIAL_NUM) && (stateLoggedIn <= mState) && (mState <= stateSessionTerminated); +} + +// Returns true if the indicated participant in the current audio session is really an SL avatar. +// Currently this will be false only for PSTN callers into group chats, and PSTN p2p calls. +BOOL LLVivoxVoiceClient::isParticipantAvatar(const LLUUID &id) +{ + BOOL result = TRUE; + sessionState *session = findSession(id); + + if(session != NULL) + { + // this is a p2p session with the indicated caller, or the session with the specified UUID. + if(session->mSynthesizedCallerID) + result = FALSE; + } + else + { + // Didn't find a matching session -- check the current audio session for a matching participant + if(mAudioSession != NULL) + { + participantState *participant = findParticipantByID(id); + if(participant != NULL) + { + result = participant->isAvatar(); + } + } + } + + return result; +} + +// Returns true if calling back the session URI after the session has closed is possible. +// Currently this will be false only for PSTN P2P calls. +BOOL LLVivoxVoiceClient::isSessionCallBackPossible(const LLUUID &session_id) +{ + BOOL result = TRUE; + sessionState *session = findSession(session_id); + + if(session != NULL) + { + result = session->isCallBackPossible(); + } + + return result; +} + +// Returns true if the session can accepte text IM's. +// Currently this will be false only for PSTN P2P calls. +BOOL LLVivoxVoiceClient::isSessionTextIMPossible(const LLUUID &session_id) +{ + bool result = TRUE; + sessionState *session = findSession(session_id); + + if(session != NULL) + { + result = session->isTextIMPossible(); + } + + return result; +} + + +void LLVivoxVoiceClient::declineInvite(std::string &sessionHandle) +{ + sessionState *session = findSession(sessionHandle); + if(session) + { + sessionMediaDisconnectSendMessage(session); + } +} + +void LLVivoxVoiceClient::leaveNonSpatialChannel() +{ + LL_DEBUGS("Voice") + << "called in state " << state2string(getState()) + << LL_ENDL; + + // Make sure we don't rejoin the current session. + sessionState *oldNextSession = mNextAudioSession; + mNextAudioSession = NULL; + + // Most likely this will still be the current session at this point, but check it anyway. + reapSession(oldNextSession); + + verifySessionState(); + + sessionTerminate(); +} + +std::string LLVivoxVoiceClient::getCurrentChannel() +{ + std::string result; + + if((getState() == stateRunning) && !mSessionTerminateRequested) + { + result = getAudioSessionURI(); + } + + return result; +} + +bool LLVivoxVoiceClient::inProximalChannel() +{ + bool result = false; + + if((getState() == stateRunning) && !mSessionTerminateRequested) + { + result = inSpatialChannel(); + } + + return result; +} + +std::string LLVivoxVoiceClient::sipURIFromID(const LLUUID &id) +{ + std::string result; + result = "sip:"; + result += nameFromID(id); + result += "@"; + result += mVoiceSIPURIHostName; + + return result; +} + +std::string LLVivoxVoiceClient::sipURIFromAvatar(LLVOAvatar *avatar) +{ + std::string result; + if(avatar) + { + result = "sip:"; + result += nameFromID(avatar->getID()); + result += "@"; + result += mVoiceSIPURIHostName; + } + + return result; +} + +std::string LLVivoxVoiceClient::nameFromAvatar(LLVOAvatar *avatar) +{ + std::string result; + if(avatar) + { + result = nameFromID(avatar->getID()); + } + return result; +} + +std::string LLVivoxVoiceClient::nameFromID(const LLUUID &uuid) +{ + std::string result; + + if (uuid.isNull()) { + //VIVOX, the uuid emtpy look for the mURIString and return that instead. + //result.assign(uuid.mURIStringName); + LLStringUtil::replaceChar(result, '_', ' '); + return result; + } + // Prepending this apparently prevents conflicts with reserved names inside the vivox and diamondware code. + result = "x"; + + // Base64 encode and replace the pieces of base64 that are less compatible + // with e-mail local-parts. + // See RFC-4648 "Base 64 Encoding with URL and Filename Safe Alphabet" + result += LLBase64::encode(uuid.mData, UUID_BYTES); + LLStringUtil::replaceChar(result, '+', '-'); + LLStringUtil::replaceChar(result, '/', '_'); + + // If you need to transform a GUID to this form on the Mac OS X command line, this will do so: + // echo -n x && (echo e669132a-6c43-4ee1-a78d-6c82fff59f32 |xxd -r -p |openssl base64|tr '/+' '_-') + + // The reverse transform can be done with: + // echo 'x5mkTKmxDTuGnjWyC__WfMg==' |cut -b 2- -|tr '_-' '/+' |openssl base64 -d|xxd -p + + return result; +} + +bool LLVivoxVoiceClient::IDFromName(const std::string inName, LLUUID &uuid) +{ + bool result = false; + + // SLIM SDK: The "name" may actually be a SIP URI such as: "sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com" + // If it is, convert to a bare name before doing the transform. + std::string name = nameFromsipURI(inName); + + // Doesn't look like a SIP URI, assume it's an actual name. + if(name.empty()) + name = inName; + + // This will only work if the name is of the proper form. + // As an example, the account name for Monroe Linden (UUID 1673cfd3-8229-4445-8d92-ec3570e5e587) is: + // "xFnPP04IpREWNkuw1cOXlhw==" + + if((name.size() == 25) && (name[0] == 'x') && (name[23] == '=') && (name[24] == '=')) + { + // The name appears to have the right form. + + // Reverse the transforms done by nameFromID + std::string temp = name; + LLStringUtil::replaceChar(temp, '-', '+'); + LLStringUtil::replaceChar(temp, '_', '/'); + + U8 rawuuid[UUID_BYTES + 1]; + int len = apr_base64_decode_binary(rawuuid, temp.c_str() + 1); + if(len == UUID_BYTES) + { + // The decode succeeded. Stuff the bits into the result's UUID + memcpy(uuid.mData, rawuuid, UUID_BYTES); + result = true; + } + } + + if(!result) + { + // VIVOX: not a standard account name, just copy the URI name mURIString field + // and hope for the best. bpj + uuid.setNull(); // VIVOX, set the uuid field to nulls + } + + return result; +} + +std::string LLVivoxVoiceClient::displayNameFromAvatar(LLVOAvatar *avatar) +{ + return avatar->getFullname(); +} + +std::string LLVivoxVoiceClient::sipURIFromName(std::string &name) +{ + std::string result; + result = "sip:"; + result += name; + result += "@"; + result += mVoiceSIPURIHostName; + +// LLStringUtil::toLower(result); + + return result; +} + +std::string LLVivoxVoiceClient::nameFromsipURI(const std::string &uri) +{ + std::string result; + + std::string::size_type sipOffset, atOffset; + sipOffset = uri.find("sip:"); + atOffset = uri.find("@"); + if((sipOffset != std::string::npos) && (atOffset != std::string::npos)) + { + result = uri.substr(sipOffset + 4, atOffset - (sipOffset + 4)); + } + + return result; +} + +bool LLVivoxVoiceClient::inSpatialChannel(void) +{ + bool result = false; + + if(mAudioSession) + result = mAudioSession->mIsSpatial; + + return result; +} + +std::string LLVivoxVoiceClient::getAudioSessionURI() +{ + std::string result; + + if(mAudioSession) + result = mAudioSession->mSIPURI; + + return result; +} + +std::string LLVivoxVoiceClient::getAudioSessionHandle() +{ + std::string result; + + if(mAudioSession) + result = mAudioSession->mHandle; + + return result; +} + + +///////////////////////////// +// Sending updates of current state + +void LLVivoxVoiceClient::enforceTether(void) +{ + LLVector3d tethered = mCameraRequestedPosition; + + // constrain 'tethered' to within 50m of mAvatarPosition. + { + F32 max_dist = 50.0f; + LLVector3d camera_offset = mCameraRequestedPosition - mAvatarPosition; + F32 camera_distance = (F32)camera_offset.magVec(); + if(camera_distance > max_dist) + { + tethered = mAvatarPosition + + (max_dist / camera_distance) * camera_offset; + } + } + + if(dist_vec(mCameraPosition, tethered) > 0.1) + { + mCameraPosition = tethered; + mSpatialCoordsDirty = true; + } +} + +void LLVivoxVoiceClient::updatePosition(void) +{ + + LLViewerRegion *region = gAgent.getRegion(); + if(region && isAgentAvatarValid()) + { + LLMatrix3 rot; + LLVector3d pos; + + // TODO: If camera and avatar velocity are actually used by the voice system, we could compute them here... + // They're currently always set to zero. + + // Send the current camera position to the voice code + rot.setRows(LLViewerCamera::getInstance()->getAtAxis(), LLViewerCamera::getInstance()->getLeftAxis (), LLViewerCamera::getInstance()->getUpAxis()); + pos = gAgent.getRegion()->getPosGlobalFromRegion(LLViewerCamera::getInstance()->getOrigin()); + + LLVivoxVoiceClient::getInstance()->setCameraPosition( + pos, // position + LLVector3::zero, // velocity + rot); // rotation matrix + + // Send the current avatar position to the voice code + rot = gAgentAvatarp->getRootJoint()->getWorldRotation().getMatrix3(); + pos = gAgentAvatarp->getPositionGlobal(); + + // TODO: Can we get the head offset from outside the LLVOAvatar? + // pos += LLVector3d(mHeadOffset); + pos += LLVector3d(0.f, 0.f, 1.f); + + LLVivoxVoiceClient::getInstance()->setAvatarPosition( + pos, // position + LLVector3::zero, // velocity + rot); // rotation matrix + } +} + +void LLVivoxVoiceClient::setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) +{ + mCameraRequestedPosition = position; + + if(mCameraVelocity != velocity) + { + mCameraVelocity = velocity; + mSpatialCoordsDirty = true; + } + + if(mCameraRot != rot) + { + mCameraRot = rot; + mSpatialCoordsDirty = true; + } +} + +void LLVivoxVoiceClient::setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot) +{ + if(dist_vec(mAvatarPosition, position) > 0.1) + { + mAvatarPosition = position; + mSpatialCoordsDirty = true; + } + + if(mAvatarVelocity != velocity) + { + mAvatarVelocity = velocity; + mSpatialCoordsDirty = true; + } + + if(mAvatarRot != rot) + { + mAvatarRot = rot; + mSpatialCoordsDirty = true; + } +} + +bool LLVivoxVoiceClient::channelFromRegion(LLViewerRegion *region, std::string &name) +{ + bool result = false; + + if(region) + { + name = region->getName(); + } + + if(!name.empty()) + result = true; + + return result; +} + +void LLVivoxVoiceClient::leaveChannel(void) +{ + if(getState() == stateRunning) + { + LL_DEBUGS("Voice") << "leaving channel for teleport/logout" << LL_ENDL; + mChannelName.clear(); + sessionTerminate(); + } +} + +void LLVivoxVoiceClient::setMuteMic(bool muted) +{ + mMuteMic = muted; +} + +void LLVivoxVoiceClient::setUserPTTState(bool ptt) +{ + mUserPTTState = ptt; +} + +bool LLVivoxVoiceClient::getUserPTTState() +{ + return mUserPTTState; +} + +void LLVivoxVoiceClient::inputUserControlState(bool down) +{ + if(mPTTIsToggle) + { + if(down) // toggle open-mic state on 'down' + { + toggleUserPTTState(); + } + } + else // set open-mic state as an absolute + { + setUserPTTState(down); + } +} + + +void LLVivoxVoiceClient::toggleUserPTTState(void) +{ + mUserPTTState = !mUserPTTState; +} + +void LLVivoxVoiceClient::setVoiceEnabled(bool enabled) +{ + if (enabled != mVoiceEnabled) + { + // TODO: Refactor this so we don't call into LLVoiceChannel, but simply + // use the status observer + mVoiceEnabled = enabled; + LLVoiceClientStatusObserver::EStatusType status; + + + if (enabled) + { + LLVoiceChannel::getCurrentVoiceChannel()->activate(); + status = LLVoiceClientStatusObserver::STATUS_VOICE_ENABLED; + } + else + { + // Turning voice off looses your current channel -- this makes sure the UI isn't out of sync when you re-enable it. + LLVoiceChannel::getCurrentVoiceChannel()->deactivate(); + status = LLVoiceClientStatusObserver::STATUS_VOICE_DISABLED; + } + } +} + +bool LLVivoxVoiceClient::voiceEnabled() +{ + return gSavedSettings.getBOOL("EnableVoiceChat") && !gSavedSettings.getBOOL("CmdLineDisableVoice"); +} + +void LLVivoxVoiceClient::setLipSyncEnabled(BOOL enabled) +{ + mLipSyncEnabled = enabled; +} + +BOOL LLVivoxVoiceClient::lipSyncEnabled() +{ + + if ( mVoiceEnabled && stateDisabled != getState() ) + { + return mLipSyncEnabled; + } + else + { + return FALSE; + } +} + +void LLVivoxVoiceClient::setUsePTT(bool usePTT) +{ + if(usePTT && !mUsePTT) + { + // When the user turns on PTT, reset the current state. + mUserPTTState = false; + } + mUsePTT = usePTT; +} + +void LLVivoxVoiceClient::setPTTIsToggle(bool PTTIsToggle) +{ + if(!PTTIsToggle && mPTTIsToggle) + { + // When the user turns off toggle, reset the current state. + mUserPTTState = false; + } + + mPTTIsToggle = PTTIsToggle; +} + +bool LLVivoxVoiceClient::getPTTIsToggle() +{ + return mPTTIsToggle; +} + +void LLVivoxVoiceClient::setPTTKey(std::string &key) +{ + if(key == "MiddleMouse") + { + mPTTIsMiddleMouse = true; + } + else + { + mPTTIsMiddleMouse = false; + if(!LLKeyboard::keyFromString(key, &mPTTKey)) + { + // If the call failed, don't match any key. + key = KEY_NONE; + } + } +} + +void LLVivoxVoiceClient::setEarLocation(S32 loc) +{ + if(mEarLocation != loc) + { + LL_DEBUGS("Voice") << "Setting mEarLocation to " << loc << LL_ENDL; + + mEarLocation = loc; + mSpatialCoordsDirty = true; + } +} + +void LLVivoxVoiceClient::setVoiceVolume(F32 volume) +{ + int scaled_volume = scale_speaker_volume(volume); + + if(scaled_volume != mSpeakerVolume) + { + int min_volume = scale_speaker_volume(0); + if((scaled_volume == min_volume) || (mSpeakerVolume == min_volume)) + { + mSpeakerMuteDirty = true; + } + + mSpeakerVolume = scaled_volume; + mSpeakerVolumeDirty = true; + } +} + +void LLVivoxVoiceClient::setMicGain(F32 volume) +{ + int scaled_volume = scale_mic_volume(volume); + + if(scaled_volume != mMicVolume) + { + mMicVolume = scaled_volume; + mMicVolumeDirty = true; + } +} + +void LLVivoxVoiceClient::keyDown(KEY key, MASK mask) +{ + if (gKeyboard->getKeyRepeated(key)) + { + // ignore auto-repeat keys + return; + } + + if(!mPTTIsMiddleMouse) + { + bool down = (mPTTKey != KEY_NONE) + && gKeyboard->getKeyDown(mPTTKey); + inputUserControlState(down); + } + + +} +void LLVivoxVoiceClient::keyUp(KEY key, MASK mask) +{ + if(!mPTTIsMiddleMouse) + { + bool down = (mPTTKey != KEY_NONE) + && gKeyboard->getKeyDown(mPTTKey); + inputUserControlState(down); + } + +} +void LLVivoxVoiceClient::middleMouseState(bool down) +{ + if(mPTTIsMiddleMouse) + { + if(mPTTIsMiddleMouse) + { + inputUserControlState(down); + } + } +} + +///////////////////////////// +// Accessors for data related to nearby speakers +BOOL LLVivoxVoiceClient::getVoiceEnabled(const LLUUID& id) +{ + BOOL result = FALSE; + participantState *participant = findParticipantByID(id); + if(participant) + { + // I'm not sure what the semantics of this should be. + // For now, if we have any data about the user that came through the chat channel, assume they're voice-enabled. + result = TRUE; + } + + return result; +} + +std::string LLVivoxVoiceClient::getDisplayName(const LLUUID& id) +{ + std::string result; + participantState *participant = findParticipantByID(id); + if(participant) + { + result = participant->mDisplayName; + } + + return result; +} + + + +BOOL LLVivoxVoiceClient::getIsSpeaking(const LLUUID& id) +{ + BOOL result = FALSE; + + participantState *participant = findParticipantByID(id); + if(participant) + { + if (participant->mSpeakingTimeout.getElapsedTimeF32() > SPEAKING_TIMEOUT) + { + participant->mIsSpeaking = FALSE; + } + result = participant->mIsSpeaking; + } + + return result; +} + +BOOL LLVivoxVoiceClient::getIsModeratorMuted(const LLUUID& id) +{ + BOOL result = FALSE; + + participantState *participant = findParticipantByID(id); + if(participant) + { + result = participant->mIsModeratorMuted; + } + + return result; +} + +F32 LLVivoxVoiceClient::getCurrentPower(const LLUUID& id) +{ + F32 result = 0; + participantState *participant = findParticipantByID(id); + if(participant) + { + result = participant->mPower; + } + + return result; +} + + + +BOOL LLVivoxVoiceClient::getUsingPTT(const LLUUID& id) +{ + BOOL result = FALSE; + + participantState *participant = findParticipantByID(id); + if(participant) + { + // I'm not sure what the semantics of this should be. + // Does "using PTT" mean they're configured with a push-to-talk button? + // For now, we know there's no PTT mechanism in place, so nobody is using it. + } + + return result; +} + +BOOL LLVivoxVoiceClient::getOnMuteList(const LLUUID& id) +{ + BOOL result = FALSE; + + participantState *participant = findParticipantByID(id); + if(participant) + { + result = participant->mOnMuteList; + } + + return result; +} + +// External accessiors. Maps 0.0 to 1.0 to internal values 0-400 with .5 == 100 +// internal = 400 * external^2 +F32 LLVivoxVoiceClient::getUserVolume(const LLUUID& id) +{ + F32 result = 0.0f; + + participantState *participant = findParticipantByID(id); + if(participant) + { + S32 ires = 100; // nominal default volume + + if(participant->mIsSelf) + { + // Always make it look like the user's own volume is set at the default. + } + else if(participant->mUserVolume != -1) + { + // Use the internal volume + ires = participant->mUserVolume; + + // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. +// LL_DEBUGS("Voice") << "mapping from mUserVolume " << ires << LL_ENDL; + } + else if(participant->mVolume != -1) + { + // Map backwards from vivox volume + + // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. +// LL_DEBUGS("Voice") << "mapping from mVolume " << participant->mVolume << LL_ENDL; + + if(participant->mVolume < 56) + { + ires = (participant->mVolume * 100) / 56; + } + else + { + ires = (((participant->mVolume - 56) * 300) / (100 - 56)) + 100; + } + } + result = sqrtf(((F32)ires) / 400.f); + } + + // Enable this when debugging voice slider issues. It's way to spammy even for debug-level logging. +// LL_DEBUGS("Voice") << "returning " << result << LL_ENDL; + + return result; +} + +void LLVivoxVoiceClient::setUserVolume(const LLUUID& id, F32 volume) +{ + if(mAudioSession) + { + participantState *participant = findParticipantByID(id); + if (participant) + { + // store this volume setting for future sessions + LLSpeakerVolumeStorage::getInstance()->storeSpeakerVolume(id, volume); + // volume can amplify by as much as 4x! + S32 ivol = (S32)(400.f * volume * volume); + participant->mUserVolume = llclamp(ivol, 0, 400); + participant->mVolumeDirty = TRUE; + mAudioSession->mVolumeDirty = TRUE; + + } + } +} + +std::string LLVivoxVoiceClient::getGroupID(const LLUUID& id) +{ + std::string result; + + participantState *participant = findParticipantByID(id); + if(participant) + { + result = participant->mGroupID; + } + + return result; +} + +BOOL LLVivoxVoiceClient::getAreaVoiceDisabled() +{ + return mAreaVoiceDisabled; +} + +void LLVivoxVoiceClient::recordingLoopStart(int seconds, int deltaFramesPerControlFrame) +{ +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Start)" << LL_ENDL; + + if(!mMainSessionGroupHandle.empty()) + { + std::ostringstream stream; + stream + << "" + << "" << mMainSessionGroupHandle << "" + << "Start" + << "" << deltaFramesPerControlFrame << "" + << "" << "" << "" + << "false" + << "" << seconds << "" + << "\n\n\n"; + + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::recordingLoopSave(const std::string& filename) +{ +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Flush)" << LL_ENDL; + + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) + { + std::ostringstream stream; + stream + << "" + << "" << mMainSessionGroupHandle << "" + << "Flush" + << "" << filename << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::recordingStop() +{ +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlRecording (Stop)" << LL_ENDL; + + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) + { + std::ostringstream stream; + stream + << "" + << "" << mMainSessionGroupHandle << "" + << "Stop" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::filePlaybackStart(const std::string& filename) +{ +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Start)" << LL_ENDL; + + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) + { + std::ostringstream stream; + stream + << "" + << "" << mMainSessionGroupHandle << "" + << "Start" + << "" << filename << "" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::filePlaybackStop() +{ +// LL_DEBUGS("Voice") << "sending SessionGroup.ControlPlayback (Stop)" << LL_ENDL; + + if(mAudioSession != NULL && !mAudioSession->mGroupHandle.empty()) + { + std::ostringstream stream; + stream + << "" + << "" << mMainSessionGroupHandle << "" + << "Stop" + << "\n\n\n"; + + writeString(stream.str()); + } +} + +void LLVivoxVoiceClient::filePlaybackSetPaused(bool paused) +{ + // TODO: Implement once Vivox gives me a sample +} + +void LLVivoxVoiceClient::filePlaybackSetMode(bool vox, float speed) +{ + // TODO: Implement once Vivox gives me a sample +} + +LLVivoxVoiceClient::sessionState::sessionState() : + mErrorStatusCode(0), + mMediaStreamState(streamStateUnknown), + mTextStreamState(streamStateUnknown), + mCreateInProgress(false), + mMediaConnectInProgress(false), + mVoiceInvitePending(false), + mTextInvitePending(false), + mSynthesizedCallerID(false), + mIsChannel(false), + mIsSpatial(false), + mIsP2P(false), + mIncoming(false), + mVoiceEnabled(false), + mReconnect(false), + mVolumeDirty(false), + mParticipantsChanged(false) +{ +} + +LLVivoxVoiceClient::sessionState::~sessionState() +{ + removeAllParticipants(); +} + +bool LLVivoxVoiceClient::sessionState::isCallBackPossible() +{ + // This may change to be explicitly specified by vivox in the future... + // Currently, only PSTN P2P calls cannot be returned. + // Conveniently, this is also the only case where we synthesize a caller UUID. + return !mSynthesizedCallerID; +} + +bool LLVivoxVoiceClient::sessionState::isTextIMPossible() +{ + // This may change to be explicitly specified by vivox in the future... + return !mSynthesizedCallerID; +} + + +LLVivoxVoiceClient::sessionIterator LLVivoxVoiceClient::sessionsBegin(void) +{ + return mSessions.begin(); +} + +LLVivoxVoiceClient::sessionIterator LLVivoxVoiceClient::sessionsEnd(void) +{ + return mSessions.end(); +} + + +LLVivoxVoiceClient::sessionState *LLVivoxVoiceClient::findSession(const std::string &handle) +{ + sessionState *result = NULL; + sessionMap::iterator iter = mSessionsByHandle.find(handle); + if(iter != mSessionsByHandle.end()) + { + result = iter->second; + } + + return result; +} + +LLVivoxVoiceClient::sessionState *LLVivoxVoiceClient::findSessionBeingCreatedByURI(const std::string &uri) +{ + sessionState *result = NULL; + for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) + { + sessionState *session = *iter; + if(session->mCreateInProgress && (session->mSIPURI == uri)) + { + result = session; + break; + } + } + + return result; +} + +LLVivoxVoiceClient::sessionState *LLVivoxVoiceClient::findSession(const LLUUID &participant_id) +{ + sessionState *result = NULL; + + for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) + { + sessionState *session = *iter; + if((session->mCallerID == participant_id) || (session->mIMSessionID == participant_id)) + { + result = session; + break; + } + } + + return result; +} + +LLVivoxVoiceClient::sessionState *LLVivoxVoiceClient::addSession(const std::string &uri, const std::string &handle) +{ + sessionState *result = NULL; + + if(handle.empty()) + { + // No handle supplied. + // Check whether there's already a session with this URI + for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) + { + sessionState *s = *iter; + if((s->mSIPURI == uri) || (s->mAlternateSIPURI == uri)) + { + // TODO: I need to think about this logic... it's possible that this case should raise an internal error. + result = s; + break; + } + } + } + else // (!handle.empty()) + { + // Check for an existing session with this handle + sessionMap::iterator iter = mSessionsByHandle.find(handle); + + if(iter != mSessionsByHandle.end()) + { + result = iter->second; + } + } + + if(!result) + { + // No existing session found. + + LL_DEBUGS("Voice") << "adding new session: handle " << handle << " URI " << uri << LL_ENDL; + result = new sessionState(); + result->mSIPURI = uri; + result->mHandle = handle; + + mSessions.insert(result); + + if(!result->mHandle.empty()) + { + mSessionsByHandle.insert(sessionMap::value_type(result->mHandle, result)); + } + } + else + { + // Found an existing session + + if(uri != result->mSIPURI) + { + // TODO: Should this be an internal error? + LL_DEBUGS("Voice") << "changing uri from " << result->mSIPURI << " to " << uri << LL_ENDL; + setSessionURI(result, uri); + } + + if(handle != result->mHandle) + { + if(handle.empty()) + { + // There's at least one race condition where where addSession was clearing an existing session handle, which caused things to break. + LL_DEBUGS("Voice") << "NOT clearing handle " << result->mHandle << LL_ENDL; + } + else + { + // TODO: Should this be an internal error? + LL_DEBUGS("Voice") << "changing handle from " << result->mHandle << " to " << handle << LL_ENDL; + setSessionHandle(result, handle); + } + } + + LL_DEBUGS("Voice") << "returning existing session: handle " << handle << " URI " << uri << LL_ENDL; + } + + verifySessionState(); + + return result; +} + +void LLVivoxVoiceClient::setSessionHandle(sessionState *session, const std::string &handle) +{ + // Have to remove the session from the handle-indexed map before changing the handle, or things will break badly. + + if(!session->mHandle.empty()) + { + // Remove session from the map if it should have been there. + sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); + if(iter != mSessionsByHandle.end()) + { + if(iter->second != session) + { + LL_ERRS("Voice") << "Internal error: session mismatch!" << LL_ENDL; + } + + mSessionsByHandle.erase(iter); + } + else + { + LL_ERRS("Voice") << "Internal error: session handle not found in map!" << LL_ENDL; + } + } + + session->mHandle = handle; + + if(!handle.empty()) + { + mSessionsByHandle.insert(sessionMap::value_type(session->mHandle, session)); + } + + verifySessionState(); +} + +void LLVivoxVoiceClient::setSessionURI(sessionState *session, const std::string &uri) +{ + // There used to be a map of session URIs to sessions, which made this complex.... + session->mSIPURI = uri; + + verifySessionState(); +} + +void LLVivoxVoiceClient::deleteSession(sessionState *session) +{ + // Remove the session from the handle map + if(!session->mHandle.empty()) + { + sessionMap::iterator iter = mSessionsByHandle.find(session->mHandle); + if(iter != mSessionsByHandle.end()) + { + if(iter->second != session) + { + LL_ERRS("Voice") << "Internal error: session mismatch" << LL_ENDL; + } + mSessionsByHandle.erase(iter); + } + } + + // Remove the session from the URI map + mSessions.erase(session); + + // At this point, the session should be unhooked from all lists and all state should be consistent. + verifySessionState(); + + // If this is the current audio session, clean up the pointer which will soon be dangling. + if(mAudioSession == session) + { + mAudioSession = NULL; + mAudioSessionChanged = true; + } + + // ditto for the next audio session + if(mNextAudioSession == session) + { + mNextAudioSession = NULL; + } + + // delete the session + delete session; +} + +void LLVivoxVoiceClient::deleteAllSessions() +{ + LL_DEBUGS("Voice") << "called" << LL_ENDL; + + while(!mSessions.empty()) + { + deleteSession(*(sessionsBegin())); + } + + if(!mSessionsByHandle.empty()) + { + LL_ERRS("Voice") << "Internal error: empty session map, non-empty handle map" << LL_ENDL; + } +} + +void LLVivoxVoiceClient::verifySessionState(void) +{ + // This is mostly intended for debugging problems with session state management. + LL_DEBUGS("Voice") << "Total session count: " << mSessions.size() << " , session handle map size: " << mSessionsByHandle.size() << LL_ENDL; + + for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) + { + sessionState *session = *iter; + + LL_DEBUGS("Voice") << "session " << session << ": handle " << session->mHandle << ", URI " << session->mSIPURI << LL_ENDL; + + if(!session->mHandle.empty()) + { + // every session with a non-empty handle needs to be in the handle map + sessionMap::iterator i2 = mSessionsByHandle.find(session->mHandle); + if(i2 == mSessionsByHandle.end()) + { + LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " not found in session map)" << LL_ENDL; + } + else + { + if(i2->second != session) + { + LL_ERRS("Voice") << "internal error (handle " << session->mHandle << " in session map points to another session)" << LL_ENDL; + } + } + } + } + + // check that every entry in the handle map points to a valid session in the session set + for(sessionMap::iterator iter = mSessionsByHandle.begin(); iter != mSessionsByHandle.end(); iter++) + { + sessionState *session = iter->second; + sessionIterator i2 = mSessions.find(session); + if(i2 == mSessions.end()) + { + LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " not found in session map)" << LL_ENDL; + } + else + { + if(session->mHandle != (*i2)->mHandle) + { + LL_ERRS("Voice") << "internal error (session for handle " << session->mHandle << " points to session with different handle " << (*i2)->mHandle << ")" << LL_ENDL; + } + } + } +} + +LLVivoxVoiceClient::buddyListEntry::buddyListEntry(const std::string &uri) : + mURI(uri) +{ + mOnlineSL = false; + mOnlineSLim = false; + mCanSeeMeOnline = true; + mHasBlockListEntry = false; + mHasAutoAcceptListEntry = false; + mNameResolved = false; + mInVivoxBuddies = false; + mInSLFriends = false; + mNeedsNameUpdate = false; +} + +void LLVivoxVoiceClient::processBuddyListEntry(const std::string &uri, const std::string &displayName) +{ + buddyListEntry *buddy = addBuddy(uri, displayName); + buddy->mInVivoxBuddies = true; +} + +LLVivoxVoiceClient::buddyListEntry *LLVivoxVoiceClient::addBuddy(const std::string &uri) +{ + std::string empty; + buddyListEntry *buddy = addBuddy(uri, empty); + if(buddy->mDisplayName.empty()) + { + buddy->mNameResolved = false; + } + return buddy; +} + +LLVivoxVoiceClient::buddyListEntry *LLVivoxVoiceClient::addBuddy(const std::string &uri, const std::string &displayName) +{ + buddyListEntry *result = NULL; + buddyListMap::iterator iter = mBuddyListMap.find(uri); + + if(iter != mBuddyListMap.end()) + { + // Found a matching buddy already in the map. + LL_DEBUGS("Voice") << "adding existing buddy " << uri << LL_ENDL; + result = iter->second; + } + + if(!result) + { + // participant isn't already in one list or the other. + LL_DEBUGS("Voice") << "adding new buddy " << uri << LL_ENDL; + result = new buddyListEntry(uri); + result->mDisplayName = displayName; + + if(IDFromName(uri, result->mUUID)) + { + // Extracted UUID from name successfully. + } + else + { + LL_DEBUGS("Voice") << "Couldn't find ID for buddy " << uri << " (\"" << displayName << "\")" << LL_ENDL; + } + + mBuddyListMap.insert(buddyListMap::value_type(result->mURI, result)); + } + + return result; +} + +LLVivoxVoiceClient::buddyListEntry *LLVivoxVoiceClient::findBuddy(const std::string &uri) +{ + buddyListEntry *result = NULL; + buddyListMap::iterator iter = mBuddyListMap.find(uri); + if(iter != mBuddyListMap.end()) + { + result = iter->second; + } + + return result; +} + +LLVivoxVoiceClient::buddyListEntry *LLVivoxVoiceClient::findBuddy(const LLUUID &id) +{ + buddyListEntry *result = NULL; + buddyListMap::iterator iter; + + for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++) + { + if(iter->second->mUUID == id) + { + result = iter->second; + break; + } + } + + return result; +} + +LLVivoxVoiceClient::buddyListEntry *LLVivoxVoiceClient::findBuddyByDisplayName(const std::string &name) +{ + buddyListEntry *result = NULL; + buddyListMap::iterator iter; + + for(iter = mBuddyListMap.begin(); iter != mBuddyListMap.end(); iter++) + { + if(iter->second->mDisplayName == name) + { + result = iter->second; + break; + } + } + + return result; +} + +void LLVivoxVoiceClient::deleteBuddy(const std::string &uri) +{ + buddyListMap::iterator iter = mBuddyListMap.find(uri); + if(iter != mBuddyListMap.end()) + { + LL_DEBUGS("Voice") << "deleting buddy " << uri << LL_ENDL; + buddyListEntry *buddy = iter->second; + mBuddyListMap.erase(iter); + delete buddy; + } + else + { + LL_DEBUGS("Voice") << "attempt to delete nonexistent buddy " << uri << LL_ENDL; + } + +} + +void LLVivoxVoiceClient::deleteAllBuddies(void) +{ + while(!mBuddyListMap.empty()) + { + deleteBuddy(mBuddyListMap.begin()->first); + } + + // Don't want to correlate with friends list when we've emptied the buddy list. + mBuddyListMapPopulated = false; + + // Don't want to correlate with friends list when we've reset the block rules. + mBlockRulesListReceived = false; + mAutoAcceptRulesListReceived = false; +} + +void LLVivoxVoiceClient::deleteAllBlockRules(void) +{ + // Clear the block list entry flags from all local buddy list entries + buddyListMap::iterator buddy_it; + for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++) + { + buddy_it->second->mHasBlockListEntry = false; + } +} + +void LLVivoxVoiceClient::deleteAllAutoAcceptRules(void) +{ + // Clear the auto-accept list entry flags from all local buddy list entries + buddyListMap::iterator buddy_it; + for(buddy_it = mBuddyListMap.begin(); buddy_it != mBuddyListMap.end(); buddy_it++) + { + buddy_it->second->mHasAutoAcceptListEntry = false; + } +} + +void LLVivoxVoiceClient::addBlockRule(const std::string &blockMask, const std::string &presenceOnly) +{ + buddyListEntry *buddy = NULL; + + // blockMask is the SIP URI of a friends list entry + buddyListMap::iterator iter = mBuddyListMap.find(blockMask); + if(iter != mBuddyListMap.end()) + { + LL_DEBUGS("Voice") << "block list entry for " << blockMask << LL_ENDL; + buddy = iter->second; + } + + if(buddy == NULL) + { + LL_DEBUGS("Voice") << "block list entry for unknown buddy " << blockMask << LL_ENDL; + buddy = addBuddy(blockMask); + } + + if(buddy != NULL) + { + buddy->mHasBlockListEntry = true; + } +} + +void LLVivoxVoiceClient::addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy) +{ + buddyListEntry *buddy = NULL; + + // blockMask is the SIP URI of a friends list entry + buddyListMap::iterator iter = mBuddyListMap.find(autoAcceptMask); + if(iter != mBuddyListMap.end()) + { + LL_DEBUGS("Voice") << "auto-accept list entry for " << autoAcceptMask << LL_ENDL; + buddy = iter->second; + } + + if(buddy == NULL) + { + LL_DEBUGS("Voice") << "auto-accept list entry for unknown buddy " << autoAcceptMask << LL_ENDL; + buddy = addBuddy(autoAcceptMask); + } + + if(buddy != NULL) + { + buddy->mHasAutoAcceptListEntry = true; + } +} + +void LLVivoxVoiceClient::accountListBlockRulesResponse(int statusCode, const std::string &statusString) +{ + // Block list entries were updated via addBlockRule() during parsing. Just flag that we're done. + mBlockRulesListReceived = true; +} + +void LLVivoxVoiceClient::accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString) +{ + // Block list entries were updated via addBlockRule() during parsing. Just flag that we're done. + mAutoAcceptRulesListReceived = true; +} + +void LLVivoxVoiceClient::addObserver(LLVoiceClientParticipantObserver* observer) +{ + mParticipantObservers.insert(observer); +} + +void LLVivoxVoiceClient::removeObserver(LLVoiceClientParticipantObserver* observer) +{ + mParticipantObservers.erase(observer); +} + +void LLVivoxVoiceClient::notifyParticipantObservers() +{ + for (observer_set_t::iterator it = mParticipantObservers.begin(); + it != mParticipantObservers.end(); + ) + { + LLVoiceClientParticipantObserver* observer = *it; + observer->onChange(); + // In case onChange() deleted an entry. + it = mParticipantObservers.upper_bound(observer); + } +} + +void LLVivoxVoiceClient::addObserver(LLVoiceClientStatusObserver* observer) +{ + mStatusObservers.insert(observer); +} + +void LLVivoxVoiceClient::removeObserver(LLVoiceClientStatusObserver* observer) +{ + mStatusObservers.erase(observer); +} + +void LLVivoxVoiceClient::notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status) +{ + if(mAudioSession) + { + if(status == LLVoiceClientStatusObserver::ERROR_UNKNOWN) + { + switch(mAudioSession->mErrorStatusCode) + { + case 20713: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_FULL; break; + case 20714: status = LLVoiceClientStatusObserver::ERROR_CHANNEL_LOCKED; break; + case 20715: + //invalid channel, we may be using a set of poorly cached + //info + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + break; + case 1009: + //invalid username and password + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + break; + } + + // Reset the error code to make sure it won't be reused later by accident. + mAudioSession->mErrorStatusCode = 0; + } + else if(status == LLVoiceClientStatusObserver::STATUS_LEFT_CHANNEL) + { + switch(mAudioSession->mErrorStatusCode) + { + case 404: // NOT_FOUND + case 480: // TEMPORARILY_UNAVAILABLE + case 408: // REQUEST_TIMEOUT + // call failed because other user was not available + // treat this as an error case + status = LLVoiceClientStatusObserver::ERROR_NOT_AVAILABLE; + + // Reset the error code to make sure it won't be reused later by accident. + mAudioSession->mErrorStatusCode = 0; + break; + } + } + } + + LL_DEBUGS("Voice") + << " " << LLVoiceClientStatusObserver::status2string(status) + << ", session URI " << getAudioSessionURI() + << (inSpatialChannel()?", proximal is true":", proximal is false") + << LL_ENDL; + + for (status_observer_set_t::iterator it = mStatusObservers.begin(); + it != mStatusObservers.end(); + ) + { + LLVoiceClientStatusObserver* observer = *it; + observer->onChange(status, getAudioSessionURI(), inSpatialChannel()); + // In case onError() deleted an entry. + it = mStatusObservers.upper_bound(observer); + } + +} + +void LLVivoxVoiceClient::addObserver(LLFriendObserver* observer) +{ + mFriendObservers.insert(observer); +} + +void LLVivoxVoiceClient::removeObserver(LLFriendObserver* observer) +{ + mFriendObservers.erase(observer); +} + +void LLVivoxVoiceClient::notifyFriendObservers() +{ + for (friend_observer_set_t::iterator it = mFriendObservers.begin(); + it != mFriendObservers.end(); + ) + { + LLFriendObserver* observer = *it; + it++; + // The only friend-related thing we notify on is online/offline transitions. + observer->changed(LLFriendObserver::ONLINE); + } +} + +void LLVivoxVoiceClient::lookupName(const LLUUID &id) +{ + BOOL is_group = FALSE; + gCacheName->get(id, is_group, &LLVivoxVoiceClient::onAvatarNameLookup); +} + +//static +void LLVivoxVoiceClient::onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group) +{ + std::string name = llformat("%s %s", first.c_str(), last.c_str()); + LLVivoxVoiceClient::getInstance()->avatarNameResolved(id, name); + +} + +void LLVivoxVoiceClient::avatarNameResolved(const LLUUID &id, const std::string &name) +{ + // If the avatar whose name just resolved is on our friends list, resync the friends list. + if(LLAvatarTracker::instance().getBuddyInfo(id) != NULL) + { + mFriendsListDirty = true; + } + + // Iterate over all sessions. + for(sessionIterator iter = sessionsBegin(); iter != sessionsEnd(); iter++) + { + sessionState *session = *iter; + + // Check for this user as a participant in this session + participantState *participant = session->findParticipantByID(id); + if(participant) + { + // Found -- fill in the name + participant->mAccountName = name; + // and post a "participants updated" message to listeners later. + session->mParticipantsChanged = true; + } + + // Check whether this is a p2p session whose caller name just resolved + if(session->mCallerID == id) + { + // this session's "caller ID" just resolved. Fill in the name. + session->mName = name; + if(session->mTextInvitePending) + { + session->mTextInvitePending = false; + + // We don't need to call gIMMgr->addP2PSession() here. The first incoming message will create the panel. + } + if(session->mVoiceInvitePending) + { + session->mVoiceInvitePending = false; + + gIMMgr->inviteToSession( + LLIMMgr::computeSessionID(IM_SESSION_P2P_INVITE, session->mCallerID), + session->mName, + session->mCallerID, + session->mName, + IM_SESSION_P2P_INVITE, + LLIMMgr::INVITATION_TYPE_VOICE, + session->mHandle); + } + + } + } +} + + +LLVivoxProtocolParser::LLVivoxProtocolParser() +{ + parser = NULL; + parser = XML_ParserCreate(NULL); + + reset(); +} + +void LLVivoxProtocolParser::reset() +{ + responseDepth = 0; + ignoringTags = false; + accumulateText = false; + energy = 0.f; + hasText = false; + hasAudio = false; + hasVideo = false; + terminated = false; + ignoreDepth = 0; + isChannel = false; + incoming = false; + enabled = false; + isEvent = false; + isLocallyMuted = false; + isModeratorMuted = false; + isSpeaking = false; + participantType = 0; + squelchDebugOutput = false; + returnCode = -1; + state = 0; + statusCode = 0; + volume = 0; + textBuffer.clear(); + alias.clear(); + numberOfAliases = 0; + applicationString.clear(); +} + +//virtual +LLVivoxProtocolParser::~LLVivoxProtocolParser() +{ + if (parser) + XML_ParserFree(parser); +} + +// virtual +LLIOPipe::EStatus LLVivoxProtocolParser::process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump) +{ + LLBufferStream istr(channels, buffer.get()); + std::ostringstream ostr; + while (istr.good()) + { + char buf[1024]; + istr.read(buf, sizeof(buf)); + mInput.append(buf, istr.gcount()); + } + + // Look for input delimiter(s) in the input buffer. If one is found, send the message to the xml parser. + int start = 0; + int delim; + while((delim = mInput.find("\n\n\n", start)) != std::string::npos) + { + + // Reset internal state of the LLVivoxProtocolParser (no effect on the expat parser) + reset(); + + XML_ParserReset(parser, NULL); + XML_SetElementHandler(parser, ExpatStartTag, ExpatEndTag); + XML_SetCharacterDataHandler(parser, ExpatCharHandler); + XML_SetUserData(parser, this); + XML_Parse(parser, mInput.data() + start, delim - start, false); + + // If this message isn't set to be squelched, output the raw XML received. + if(!squelchDebugOutput) + { + LL_DEBUGS("Voice") << "parsing: " << mInput.substr(start, delim - start) << LL_ENDL; + } + + start = delim + 3; + } + + if(start != 0) + mInput = mInput.substr(start); + + LL_DEBUGS("VivoxProtocolParser") << "at end, mInput is: " << mInput << LL_ENDL; + + if(!LLVivoxVoiceClient::getInstance()->mConnected) + { + // If voice has been disabled, we just want to close the socket. This does so. + LL_INFOS("Voice") << "returning STATUS_STOP" << LL_ENDL; + return STATUS_STOP; + } + + return STATUS_OK; +} + +void XMLCALL LLVivoxProtocolParser::ExpatStartTag(void *data, const char *el, const char **attr) +{ + if (data) + { + LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; + object->StartTag(el, attr); + } +} + +// -------------------------------------------------------------------------------- + +void XMLCALL LLVivoxProtocolParser::ExpatEndTag(void *data, const char *el) +{ + if (data) + { + LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; + object->EndTag(el); + } +} + +// -------------------------------------------------------------------------------- + +void XMLCALL LLVivoxProtocolParser::ExpatCharHandler(void *data, const XML_Char *s, int len) +{ + if (data) + { + LLVivoxProtocolParser *object = (LLVivoxProtocolParser*)data; + object->CharData(s, len); + } +} + +// -------------------------------------------------------------------------------- + + +void LLVivoxProtocolParser::StartTag(const char *tag, const char **attr) +{ + // Reset the text accumulator. We shouldn't have strings that are inturrupted by new tags + textBuffer.clear(); + // only accumulate text if we're not ignoring tags. + accumulateText = !ignoringTags; + + if (responseDepth == 0) + { + isEvent = !stricmp("Event", tag); + + if (!stricmp("Response", tag) || isEvent) + { + // Grab the attributes + while (*attr) + { + const char *key = *attr++; + const char *value = *attr++; + + if (!stricmp("requestId", key)) + { + requestId = value; + } + else if (!stricmp("action", key)) + { + actionString = value; + } + else if (!stricmp("type", key)) + { + eventTypeString = value; + } + } + } + LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; + } + else + { + if (ignoringTags) + { + LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; + } + else + { + LL_DEBUGS("VivoxProtocolParser") << tag << " (" << responseDepth << ")" << LL_ENDL; + + // Ignore the InputXml stuff so we don't get confused + if (!stricmp("InputXml", tag)) + { + ignoringTags = true; + ignoreDepth = responseDepth; + accumulateText = false; + + LL_DEBUGS("VivoxProtocolParser") << "starting ignore, ignoreDepth is " << ignoreDepth << LL_ENDL; + } + else if (!stricmp("CaptureDevices", tag)) + { + LLVivoxVoiceClient::getInstance()->clearCaptureDevices(); + } + else if (!stricmp("RenderDevices", tag)) + { + LLVivoxVoiceClient::getInstance()->clearRenderDevices(); + } + else if (!stricmp("CaptureDevice", tag)) + { + deviceString.clear(); + } + else if (!stricmp("RenderDevice", tag)) + { + deviceString.clear(); + } + else if (!stricmp("Buddies", tag)) + { + LLVivoxVoiceClient::getInstance()->deleteAllBuddies(); + } + else if (!stricmp("BlockRules", tag)) + { + LLVivoxVoiceClient::getInstance()->deleteAllBlockRules(); + } + else if (!stricmp("AutoAcceptRules", tag)) + { + LLVivoxVoiceClient::getInstance()->deleteAllAutoAcceptRules(); + } + + } + } + responseDepth++; +} + +// -------------------------------------------------------------------------------- + +void LLVivoxProtocolParser::EndTag(const char *tag) +{ + const std::string& string = textBuffer; + + responseDepth--; + + if (ignoringTags) + { + if (ignoreDepth == responseDepth) + { + LL_DEBUGS("VivoxProtocolParser") << "end of ignore" << LL_ENDL; + ignoringTags = false; + } + else + { + LL_DEBUGS("VivoxProtocolParser") << "ignoring tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; + } + } + + if (!ignoringTags) + { + LL_DEBUGS("VivoxProtocolParser") << "processing tag " << tag << " (depth = " << responseDepth << ")" << LL_ENDL; + + // Closing a tag. Finalize the text we've accumulated and reset + if (!stricmp("ReturnCode", tag)) + returnCode = strtol(string.c_str(), NULL, 10); + else if (!stricmp("SessionHandle", tag)) + sessionHandle = string; + else if (!stricmp("SessionGroupHandle", tag)) + sessionGroupHandle = string; + else if (!stricmp("StatusCode", tag)) + statusCode = strtol(string.c_str(), NULL, 10); + else if (!stricmp("StatusString", tag)) + statusString = string; + else if (!stricmp("ParticipantURI", tag)) + uriString = string; + else if (!stricmp("Volume", tag)) + volume = strtol(string.c_str(), NULL, 10); + else if (!stricmp("Energy", tag)) + energy = (F32)strtod(string.c_str(), NULL); + else if (!stricmp("IsModeratorMuted", tag)) + isModeratorMuted = !stricmp(string.c_str(), "true"); + else if (!stricmp("IsSpeaking", tag)) + isSpeaking = !stricmp(string.c_str(), "true"); + else if (!stricmp("Alias", tag)) + alias = string; + else if (!stricmp("NumberOfAliases", tag)) + numberOfAliases = strtol(string.c_str(), NULL, 10); + else if (!stricmp("Application", tag)) + applicationString = string; + else if (!stricmp("ConnectorHandle", tag)) + connectorHandle = string; + else if (!stricmp("VersionID", tag)) + versionID = string; + else if (!stricmp("AccountHandle", tag)) + accountHandle = string; + else if (!stricmp("State", tag)) + state = strtol(string.c_str(), NULL, 10); + else if (!stricmp("URI", tag)) + uriString = string; + else if (!stricmp("IsChannel", tag)) + isChannel = !stricmp(string.c_str(), "true"); + else if (!stricmp("Incoming", tag)) + incoming = !stricmp(string.c_str(), "true"); + else if (!stricmp("Enabled", tag)) + enabled = !stricmp(string.c_str(), "true"); + else if (!stricmp("Name", tag)) + nameString = string; + else if (!stricmp("AudioMedia", tag)) + audioMediaString = string; + else if (!stricmp("ChannelName", tag)) + nameString = string; + else if (!stricmp("DisplayName", tag)) + displayNameString = string; + else if (!stricmp("Device", tag)) + deviceString = string; + else if (!stricmp("AccountName", tag)) + nameString = string; + else if (!stricmp("ParticipantType", tag)) + participantType = strtol(string.c_str(), NULL, 10); + else if (!stricmp("IsLocallyMuted", tag)) + isLocallyMuted = !stricmp(string.c_str(), "true"); + else if (!stricmp("MicEnergy", tag)) + energy = (F32)strtod(string.c_str(), NULL); + else if (!stricmp("ChannelName", tag)) + nameString = string; + else if (!stricmp("ChannelURI", tag)) + uriString = string; + else if (!stricmp("BuddyURI", tag)) + uriString = string; + else if (!stricmp("Presence", tag)) + statusString = string; + else if (!stricmp("CaptureDevice", tag)) + { + LLVivoxVoiceClient::getInstance()->addCaptureDevice(deviceString); + } + else if (!stricmp("RenderDevice", tag)) + { + LLVivoxVoiceClient::getInstance()->addRenderDevice(deviceString); + } + else if (!stricmp("Buddy", tag)) + { + LLVivoxVoiceClient::getInstance()->processBuddyListEntry(uriString, displayNameString); + } + else if (!stricmp("BlockRule", tag)) + { + LLVivoxVoiceClient::getInstance()->addBlockRule(blockMask, presenceOnly); + } + else if (!stricmp("BlockMask", tag)) + blockMask = string; + else if (!stricmp("PresenceOnly", tag)) + presenceOnly = string; + else if (!stricmp("AutoAcceptRule", tag)) + { + LLVivoxVoiceClient::getInstance()->addAutoAcceptRule(autoAcceptMask, autoAddAsBuddy); + } + else if (!stricmp("AutoAcceptMask", tag)) + autoAcceptMask = string; + else if (!stricmp("AutoAddAsBuddy", tag)) + autoAddAsBuddy = string; + else if (!stricmp("MessageHeader", tag)) + messageHeader = string; + else if (!stricmp("MessageBody", tag)) + messageBody = string; + else if (!stricmp("NotificationType", tag)) + notificationType = string; + else if (!stricmp("HasText", tag)) + hasText = !stricmp(string.c_str(), "true"); + else if (!stricmp("HasAudio", tag)) + hasAudio = !stricmp(string.c_str(), "true"); + else if (!stricmp("HasVideo", tag)) + hasVideo = !stricmp(string.c_str(), "true"); + else if (!stricmp("Terminated", tag)) + terminated = !stricmp(string.c_str(), "true"); + else if (!stricmp("SubscriptionHandle", tag)) + subscriptionHandle = string; + else if (!stricmp("SubscriptionType", tag)) + subscriptionType = string; + + + textBuffer.clear(); + accumulateText= false; + + if (responseDepth == 0) + { + // We finished all of the XML, process the data + processResponse(tag); + } + } +} + +// -------------------------------------------------------------------------------- + +void LLVivoxProtocolParser::CharData(const char *buffer, int length) +{ + /* + This method is called for anything that isn't a tag, which can be text you + want that lies between tags, and a lot of stuff you don't want like file formatting + (tabs, spaces, CR/LF, etc). + + Only copy text if we are in accumulate mode... + */ + if (accumulateText) + textBuffer.append(buffer, length); +} + +// -------------------------------------------------------------------------------- + +void LLVivoxProtocolParser::processResponse(std::string tag) +{ + LL_DEBUGS("VivoxProtocolParser") << tag << LL_ENDL; + + // SLIM SDK: the SDK now returns a statusCode of "200" (OK) for success. This is a change vs. previous SDKs. + // According to Mike S., "The actual API convention is that responses with return codes of 0 are successful, regardless of the status code returned", + // so I believe this will give correct behavior. + + if(returnCode == 0) + statusCode = 0; + + if (isEvent) + { + const char *eventTypeCstr = eventTypeString.c_str(); + if (!stricmp(eventTypeCstr, "AccountLoginStateChangeEvent")) + { + LLVivoxVoiceClient::getInstance()->accountLoginStateChangeEvent(accountHandle, statusCode, statusString, state); + } + else if (!stricmp(eventTypeCstr, "SessionAddedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 + sip:confctl-1408789@bhr.vivox.com + true + false + + + */ + LLVivoxVoiceClient::getInstance()->sessionAddedEvent(uriString, alias, sessionHandle, sessionGroupHandle, isChannel, incoming, nameString, applicationString); + } + else if (!stricmp(eventTypeCstr, "SessionRemovedEvent")) + { + LLVivoxVoiceClient::getInstance()->sessionRemovedEvent(sessionHandle, sessionGroupHandle); + } + else if (!stricmp(eventTypeCstr, "SessionGroupAddedEvent")) + { + LLVivoxVoiceClient::getInstance()->sessionGroupAddedEvent(sessionGroupHandle); + } + else if (!stricmp(eventTypeCstr, "MediaStreamUpdatedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 + 200 + OK + 2 + false + + */ + LLVivoxVoiceClient::getInstance()->mediaStreamUpdatedEvent(sessionHandle, sessionGroupHandle, statusCode, statusString, state, incoming); + } + else if (!stricmp(eventTypeCstr, "TextStreamUpdatedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg1 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==1 + true + 1 + true + + */ + LLVivoxVoiceClient::getInstance()->textStreamUpdatedEvent(sessionHandle, sessionGroupHandle, enabled, state, incoming); + } + else if (!stricmp(eventTypeCstr, "ParticipantAddedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==4 + sip:xI5auBZ60SJWIk606-1JGRQ==@bhr.vivox.com + xI5auBZ60SJWIk606-1JGRQ== + + 0 + + */ + LLVivoxVoiceClient::getInstance()->participantAddedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString, displayNameString, participantType); + } + else if (!stricmp(eventTypeCstr, "ParticipantRemovedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg4 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==4 + sip:xtx7YNV-3SGiG7rA1fo5Ndw==@bhr.vivox.com + xtx7YNV-3SGiG7rA1fo5Ndw== + + */ + LLVivoxVoiceClient::getInstance()->participantRemovedEvent(sessionHandle, sessionGroupHandle, uriString, alias, nameString); + } + else if (!stricmp(eventTypeCstr, "ParticipantUpdatedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 + sip:xFnPP04IpREWNkuw1cOXlhw==@bhr.vivox.com + false + true + 44 + 0.0879437 + + */ + + // These happen so often that logging them is pretty useless. + squelchDebugOutput = true; + + LLVivoxVoiceClient::getInstance()->participantUpdatedEvent(sessionHandle, sessionGroupHandle, uriString, alias, isModeratorMuted, isSpeaking, volume, energy); + } + else if (!stricmp(eventTypeCstr, "AuxAudioPropertiesEvent")) + { + LLVivoxVoiceClient::getInstance()->auxAudioPropertiesEvent(energy); + } + else if (!stricmp(eventTypeCstr, "BuddyPresenceEvent")) + { + LLVivoxVoiceClient::getInstance()->buddyPresenceEvent(uriString, alias, statusString, applicationString); + } + else if (!stricmp(eventTypeCstr, "BuddyAndGroupListChangedEvent")) + { + // The buddy list was updated during parsing. + // Need to recheck against the friends list. + LLVivoxVoiceClient::getInstance()->buddyListChanged(); + } + else if (!stricmp(eventTypeCstr, "BuddyChangedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw== + sip:x9fFHFZjOTN6OESF1DUPrZQ==@bhr.vivox.com + Monroe Tester + + 0 + Set + + */ + // TODO: Question: Do we need to process this at all? + } + else if (!stricmp(eventTypeCstr, "MessageEvent")) + { + LLVivoxVoiceClient::getInstance()->messageEvent(sessionHandle, uriString, alias, messageHeader, messageBody, applicationString); + } + else if (!stricmp(eventTypeCstr, "SessionNotificationEvent")) + { + LLVivoxVoiceClient::getInstance()->sessionNotificationEvent(sessionHandle, uriString, notificationType); + } + else if (!stricmp(eventTypeCstr, "SubscriptionEvent")) + { + LLVivoxVoiceClient::getInstance()->subscriptionEvent(uriString, subscriptionHandle, alias, displayNameString, applicationString, subscriptionType); + } + else if (!stricmp(eventTypeCstr, "SessionUpdatedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 + c1_m1000xFnPP04IpREWNkuw1cOXlhw==0 + sip:confctl-9@bhd.vivox.com + 0 + 50 + 1 + 0 + 000 + 0 + + */ + // We don't need to process this, but we also shouldn't warn on it, since that confuses people. + } + + else if (!stricmp(eventTypeCstr, "SessionGroupRemovedEvent")) + { + /* + + c1_m1000xFnPP04IpREWNkuw1cOXlhw==_sg0 + + */ + // We don't need to process this, but we also shouldn't warn on it, since that confuses people. + } + else + { + LL_WARNS("VivoxProtocolParser") << "Unknown event type " << eventTypeString << LL_ENDL; + } + } + else + { + const char *actionCstr = actionString.c_str(); + if (!stricmp(actionCstr, "Connector.Create.1")) + { + LLVivoxVoiceClient::getInstance()->connectorCreateResponse(statusCode, statusString, connectorHandle, versionID); + } + else if (!stricmp(actionCstr, "Account.Login.1")) + { + LLVivoxVoiceClient::getInstance()->loginResponse(statusCode, statusString, accountHandle, numberOfAliases); + } + else if (!stricmp(actionCstr, "Session.Create.1")) + { + LLVivoxVoiceClient::getInstance()->sessionCreateResponse(requestId, statusCode, statusString, sessionHandle); + } + else if (!stricmp(actionCstr, "SessionGroup.AddSession.1")) + { + LLVivoxVoiceClient::getInstance()->sessionGroupAddSessionResponse(requestId, statusCode, statusString, sessionHandle); + } + else if (!stricmp(actionCstr, "Session.Connect.1")) + { + LLVivoxVoiceClient::getInstance()->sessionConnectResponse(requestId, statusCode, statusString); + } + else if (!stricmp(actionCstr, "Account.Logout.1")) + { + LLVivoxVoiceClient::getInstance()->logoutResponse(statusCode, statusString); + } + else if (!stricmp(actionCstr, "Connector.InitiateShutdown.1")) + { + LLVivoxVoiceClient::getInstance()->connectorShutdownResponse(statusCode, statusString); + } + else if (!stricmp(actionCstr, "Account.ListBlockRules.1")) + { + LLVivoxVoiceClient::getInstance()->accountListBlockRulesResponse(statusCode, statusString); + } + else if (!stricmp(actionCstr, "Account.ListAutoAcceptRules.1")) + { + LLVivoxVoiceClient::getInstance()->accountListAutoAcceptRulesResponse(statusCode, statusString); + } + else if (!stricmp(actionCstr, "Session.Set3DPosition.1")) + { + // We don't need to process these, but they're so spammy we don't want to log them. + squelchDebugOutput = true; + } + /* + else if (!stricmp(actionCstr, "Account.ChannelGetList.1")) + { + LLVoiceClient::getInstance()->channelGetListResponse(statusCode, statusString); + } + else if (!stricmp(actionCstr, "Connector.AccountCreate.1")) + { + + } + else if (!stricmp(actionCstr, "Connector.MuteLocalMic.1")) + { + + } + else if (!stricmp(actionCstr, "Connector.MuteLocalSpeaker.1")) + { + + } + else if (!stricmp(actionCstr, "Connector.SetLocalMicVolume.1")) + { + + } + else if (!stricmp(actionCstr, "Connector.SetLocalSpeakerVolume.1")) + { + + } + else if (!stricmp(actionCstr, "Session.ListenerSetPosition.1")) + { + + } + else if (!stricmp(actionCstr, "Session.SpeakerSetPosition.1")) + { + + } + else if (!stricmp(actionCstr, "Session.AudioSourceSetPosition.1")) + { + + } + else if (!stricmp(actionCstr, "Session.GetChannelParticipants.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelCreate.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelUpdate.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelDelete.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelCreateAndInvite.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelFolderCreate.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelFolderUpdate.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelFolderDelete.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelAddModerator.1")) + { + + } + else if (!stricmp(actionCstr, "Account.ChannelDeleteModerator.1")) + { + + } + */ + } +} + diff --git a/indra/newview/llvoicevivox.h b/indra/newview/llvoicevivox.h new file mode 100644 index 0000000000..10577254e8 --- /dev/null +++ b/indra/newview/llvoicevivox.h @@ -0,0 +1,914 @@ +/** + * @file llvoicevivox.h + * @brief Declaration of LLDiamondwareVoiceClient class which is the interface to the voice client process. + * + * $LicenseInfo:firstyear=2001&license=viewergpl$ + * + * Copyright (c) 2001-2010, Linden Research, Inc. + * + * Second Life Viewer Source Code + * The source code in this file ("Source Code") is provided by Linden Lab + * to you under the terms of the GNU General Public License, version 2.0 + * ("GPL"), unless you have obtained a separate licensing agreement + * ("Other License"), formally executed by you and Linden Lab. Terms of + * the GPL can be found in doc/GPL-license.txt in this distribution, or + * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 + * + * There are special exceptions to the terms and conditions of the GPL as + * it is applied to this Source Code. View the full text of the exception + * in the file doc/FLOSS-exception.txt in this software distribution, or + * online at + * http://secondlifegrid.net/programs/open_source/licensing/flossexception + * + * By copying, modifying or distributing this software, you acknowledge + * that you have read and understood your obligations described above, + * and agree to abide by those obligations. + * + * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO + * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, + * COMPLETENESS OR PERFORMANCE. + * $/LicenseInfo$ + */ +#ifndef LL_VOICE_VIVOX_H +#define LL_VOICE_VIVOX_H + +class LLVOAvatar; +class LLVivoxProtocolParser; + +#include "lliopipe.h" +#include "llpumpio.h" +#include "llchainio.h" +#include "lliosocket.h" +#include "v3math.h" +#include "llframetimer.h" +#include "llviewerregion.h" +#include "llcallingcard.h" // for LLFriendObserver + +#ifdef LL_STANDALONE +# include "expat.h" +#else +# include "expat/expat.h" +#endif +#include "llvoiceclient.h" + + +class LLVivoxVoiceAccountProvisionResponder; +class LLVivoxVoiceClientMuteListObserver; +class LLVivoxVoiceClientFriendsObserver; + + +class LLVivoxVoiceClientParticipantObserver +{ +public: + virtual ~LLVivoxVoiceClientParticipantObserver() { } + virtual void onChange() = 0; +}; + + +class LLVivoxVoiceClient: public LLSingleton, virtual public LLVoiceModuleInterface +{ + LOG_CLASS(LLVivoxVoiceClient); +public: + LLVivoxVoiceClient(); + virtual ~LLVivoxVoiceClient(); + + + /// @name LLVoiceModuleInterface virtual implementations + /// @see LLVoiceModuleInterface + //@{ + virtual void init(LLPumpIO *pump); // Call this once at application startup (creates connector) + virtual void terminate(); // Call this to clean up during shutdown + + virtual const LLVoiceVersionInfo& getVersion(); + + virtual void updateSettings(); // call after loading settings and whenever they change + + // Returns true if vivox has successfully logged in and is not in error state + virtual bool isVoiceWorking(); + + ///////////////////// + /// @name Tuning + //@{ + virtual void tuningStart(); + virtual void tuningStop(); + virtual bool inTuningMode(); + + virtual void tuningSetMicVolume(float volume); + virtual void tuningSetSpeakerVolume(float volume); + virtual float tuningGetEnergy(void); + //@} + + ///////////////////// + /// @name Devices + //@{ + // This returns true when it's safe to bring up the "device settings" dialog in the prefs. + // i.e. when the daemon is running and connected, and the device lists are populated. + virtual bool deviceSettingsAvailable(); + + // Requery the vivox daemon for the current list of input/output devices. + // If you pass true for clearCurrentList, deviceSettingsAvailable() will be false until the query has completed + // (use this if you want to know when it's done). + // If you pass false, you'll have no way to know when the query finishes, but the device lists will not appear empty in the interim. + virtual void refreshDeviceLists(bool clearCurrentList = true); + + virtual void setCaptureDevice(const std::string& name); + virtual void setRenderDevice(const std::string& name); + + virtual LLVoiceDeviceList& getCaptureDevices(); + virtual LLVoiceDeviceList& getRenderDevices(); + //@} + + virtual void getParticipantList(std::set &participants); + virtual bool isParticipant(const LLUUID& speaker_id); + + // Send a text message to the specified user, initiating the session if necessary. + virtual BOOL sendTextMessage(const LLUUID& participant_id, const std::string& message); + + // close any existing text IM session with the specified user + virtual void endUserIMSession(const LLUUID &uuid); + + // Returns true if calling back the session URI after the session has closed is possible. + // Currently this will be false only for PSTN P2P calls. + // NOTE: this will return true if the session can't be found. + virtual BOOL isSessionCallBackPossible(const LLUUID &session_id); + + // Returns true if the session can accepte text IM's. + // Currently this will be false only for PSTN P2P calls. + // NOTE: this will return true if the session can't be found. + virtual BOOL isSessionTextIMPossible(const LLUUID &session_id); + + + //////////////////////////// + /// @name Channel stuff + //@{ + // returns true iff the user is currently in a proximal (local spatial) channel. + // Note that gestures should only fire if this returns true. + virtual bool inProximalChannel(); + + virtual void setNonSpatialChannel(const std::string &uri, + const std::string &credentials); + + virtual void setSpatialChannel(const std::string &uri, + const std::string &credentials); + + virtual void leaveNonSpatialChannel(); + + virtual void leaveChannel(void); + + // Returns the URI of the current channel, or an empty string if not currently in a channel. + // NOTE that it will return an empty string if it's in the process of joining a channel. + virtual std::string getCurrentChannel(); + //@} + + + ////////////////////////// + /// @name invitations + //@{ + // start a voice channel with the specified user + virtual void callUser(const LLUUID &uuid); + virtual bool answerInvite(std::string &channelHandle); + virtual void declineInvite(std::string &channelHandle); + //@} + + ///////////////////////// + /// @name Volume/gain + //@{ + virtual void setVoiceVolume(F32 volume); + virtual void setMicGain(F32 volume); + //@} + + ///////////////////////// + /// @name enable disable voice and features + //@{ + virtual bool voiceEnabled(); + virtual void setVoiceEnabled(bool enabled); + virtual BOOL lipSyncEnabled(); + virtual void setLipSyncEnabled(BOOL enabled); + virtual void setMuteMic(bool muted); // Use this to mute the local mic (for when the client is minimized, etc), ignoring user PTT state. + //@} + + //////////////////////// + /// @name PTT + //@{ + virtual void setUserPTTState(bool ptt); + virtual bool getUserPTTState(); + virtual void setUsePTT(bool usePTT); + virtual void setPTTIsToggle(bool PTTIsToggle); + virtual bool getPTTIsToggle(); + virtual void inputUserControlState(bool down); // interpret any sort of up-down mic-open control input according to ptt-toggle prefs + virtual void toggleUserPTTState(void); + + virtual void keyDown(KEY key, MASK mask); + virtual void keyUp(KEY key, MASK mask); + virtual void middleMouseState(bool down); + //@} + + ////////////////////////// + /// @name nearby speaker accessors + //@{ + virtual BOOL getVoiceEnabled(const LLUUID& id); // true if we've received data for this avatar + virtual std::string getDisplayName(const LLUUID& id); + virtual BOOL isOnlineSIP(const LLUUID &id); + virtual BOOL isParticipantAvatar(const LLUUID &id); + virtual BOOL getIsSpeaking(const LLUUID& id); + virtual BOOL getIsModeratorMuted(const LLUUID& id); + virtual F32 getCurrentPower(const LLUUID& id); // "power" is related to "amplitude" in a defined way. I'm just not sure what the formula is... + virtual BOOL getOnMuteList(const LLUUID& id); + virtual F32 getUserVolume(const LLUUID& id); + virtual void setUserVolume(const LLUUID& id, F32 volume); // set's volume for specified agent, from 0-1 (where .5 is nominal) + //@} + + // authorize the user + virtual void userAuthorized(const std::string& user_id, + const LLUUID &agentID); + + ////////////////////////////// + /// @name Status notification + //@{ + virtual void addObserver(LLVoiceClientStatusObserver* observer); + virtual void removeObserver(LLVoiceClientStatusObserver* observer); + virtual void addObserver(LLFriendObserver* observer); + virtual void removeObserver(LLFriendObserver* observer); + virtual void addObserver(LLVoiceClientParticipantObserver* observer); + virtual void removeObserver(LLVoiceClientParticipantObserver* observer); + + + + //@} + + virtual std::string sipURIFromID(const LLUUID &id); + //@} + + +protected: + ////////////////////// + // Vivox Specific definitions + + friend class LLVivoxVoiceAccountProvisionResponder; + friend class LLVivoxVoiceClientMuteListObserver; + friend class LLVivoxVoiceClientFriendsObserver; + + enum streamState + { + streamStateUnknown = 0, + streamStateIdle = 1, + streamStateConnected = 2, + streamStateRinging = 3, + }; + struct participantState + { + public: + participantState(const std::string &uri); + + bool updateMuteState(); + bool isAvatar(); + + std::string mURI; + LLUUID mAvatarID; + std::string mAccountName; + std::string mDisplayName; + LLFrameTimer mSpeakingTimeout; + F32 mLastSpokeTimestamp; + F32 mPower; + int mVolume; + std::string mGroupID; + int mUserVolume; + bool mPTT; + bool mIsSpeaking; + bool mIsModeratorMuted; + bool mOnMuteList; // true if this avatar is on the user's mute list (and should be muted) + bool mVolumeDirty; // true if this participant needs a volume command sent (either mOnMuteList or mUserVolume has changed) + bool mAvatarIDValid; + bool mIsSelf; + }; + + typedef std::map participantMap; + + typedef std::map participantUUIDMap; + + struct sessionState + { + public: + sessionState(); + ~sessionState(); + + participantState *addParticipant(const std::string &uri); + // Note: after removeParticipant returns, the participant* that was passed to it will have been deleted. + // Take care not to use the pointer again after that. + void removeParticipant(participantState *participant); + void removeAllParticipants(); + + participantState *findParticipant(const std::string &uri); + participantState *findParticipantByID(const LLUUID& id); + + bool isCallBackPossible(); + bool isTextIMPossible(); + + std::string mHandle; + std::string mGroupHandle; + std::string mSIPURI; + std::string mAlias; + std::string mName; + std::string mAlternateSIPURI; + std::string mHash; // Channel password + std::string mErrorStatusString; + std::queue mTextMsgQueue; + + LLUUID mIMSessionID; + LLUUID mCallerID; + int mErrorStatusCode; + int mMediaStreamState; + int mTextStreamState; + bool mCreateInProgress; // True if a Session.Create has been sent for this session and no response has been received yet. + bool mMediaConnectInProgress; // True if a Session.MediaConnect has been sent for this session and no response has been received yet. + bool mVoiceInvitePending; // True if a voice invite is pending for this session (usually waiting on a name lookup) + bool mTextInvitePending; // True if a text invite is pending for this session (usually waiting on a name lookup) + bool mSynthesizedCallerID; // True if the caller ID is a hash of the SIP URI -- this means we shouldn't do a name lookup. + bool mIsChannel; // True for both group and spatial channels (false for p2p, PSTN) + bool mIsSpatial; // True for spatial channels + bool mIsP2P; + bool mIncoming; + bool mVoiceEnabled; + bool mReconnect; // Whether we should try to reconnect to this session if it's dropped + // Set to true when the mute state of someone in the participant list changes. + // The code will have to walk the list to find the changed participant(s). + bool mVolumeDirty; + + bool mParticipantsChanged; + participantMap mParticipantsByURI; + participantUUIDMap mParticipantsByUUID; + }; + + // internal state for a simple state machine. This is used to deal with the asynchronous nature of some of the messages. + // Note: if you change this list, please make corresponding changes to LLVivoxVoiceClient::state2string(). + enum state + { + stateDisableCleanup, + stateDisabled, // Voice is turned off. + stateStart, // Class is initialized, socket is created + stateDaemonLaunched, // Daemon has been launched + stateConnecting, // connect() call has been issued + stateConnected, // connection to the daemon has been made, send some initial setup commands. + stateIdle, // socket is connected, ready for messaging + stateMicTuningStart, + stateMicTuningRunning, + stateMicTuningStop, + stateConnectorStart, // connector needs to be started + stateConnectorStarting, // waiting for connector handle + stateConnectorStarted, // connector handle received + stateLoginRetry, // need to retry login (failed due to changing password) + stateLoginRetryWait, // waiting for retry timer + stateNeedsLogin, // send login request + stateLoggingIn, // waiting for account handle + stateLoggedIn, // account handle received + stateCreatingSessionGroup, // Creating the main session group + stateNoChannel, // + stateJoiningSession, // waiting for session handle + stateSessionJoined, // session handle received + stateRunning, // in session, steady state + stateLeavingSession, // waiting for terminate session response + stateSessionTerminated, // waiting for terminate session response + + stateLoggingOut, // waiting for logout response + stateLoggedOut, // logout response received + stateConnectorStopping, // waiting for connector stop + stateConnectorStopped, // connector stop received + + // We go to this state if the login fails because the account needs to be provisioned. + + // error states. No way to recover from these yet. + stateConnectorFailed, + stateConnectorFailedWaiting, + stateLoginFailed, + stateLoginFailedWaiting, + stateJoinSessionFailed, + stateJoinSessionFailedWaiting, + + stateJail // Go here when all else has failed. Nothing will be retried, we're done. + }; + + typedef std::map sessionMap; + + + + /////////////////////////////////////////////////////// + // Private Member Functions + ////////////////////////////////////////////////////// + + ////////////////////////////// + /// @name TVC/Server management and communication + //@{ + // Call this if the connection to the daemon terminates unexpectedly. It will attempt to reset everything and relaunch. + void daemonDied(); + + // Call this if we're just giving up on voice (can't provision an account, etc.). It will clean up and go away. + void giveUp(); + + // write to the tvc + bool writeString(const std::string &str); + + void connectorCreate(); + void connectorShutdown(); + void closeSocket(void); + + void requestVoiceAccountProvision(S32 retries = 3); + void login( + const std::string& account_name, + const std::string& password, + const std::string& voice_sip_uri_hostname, + const std::string& voice_account_server_uri); + void loginSendMessage(); + void logout(); + void logoutSendMessage(); + + + //@} + + //------------------------------------ + // tuning + + void tuningRenderStartSendMessage(const std::string& name, bool loop); + void tuningRenderStopSendMessage(); + + void tuningCaptureStartSendMessage(int duration); + void tuningCaptureStopSendMessage(); + + bool inTuningStates(); + + //---------------------------------- + // devices + void clearCaptureDevices(); + void addCaptureDevice(const std::string& name); + void clearRenderDevices(); + void addRenderDevice(const std::string& name); + void buildSetAudioDevices(std::ostringstream &stream); + + void getCaptureDevicesSendMessage(); + void getRenderDevicesSendMessage(); + + // local audio updates + void buildLocalAudioUpdates(std::ostringstream &stream); + + + ///////////////////////////// + // Response/Event handlers + void connectorCreateResponse(int statusCode, std::string &statusString, std::string &connectorHandle, std::string &versionID); + void loginResponse(int statusCode, std::string &statusString, std::string &accountHandle, int numberOfAliases); + void sessionCreateResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); + void sessionGroupAddSessionResponse(std::string &requestId, int statusCode, std::string &statusString, std::string &sessionHandle); + void sessionConnectResponse(std::string &requestId, int statusCode, std::string &statusString); + void logoutResponse(int statusCode, std::string &statusString); + void connectorShutdownResponse(int statusCode, std::string &statusString); + + void accountLoginStateChangeEvent(std::string &accountHandle, int statusCode, std::string &statusString, int state); + void mediaStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, int statusCode, std::string &statusString, int state, bool incoming); + void textStreamUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, bool enabled, int state, bool incoming); + void sessionAddedEvent(std::string &uriString, std::string &alias, std::string &sessionHandle, std::string &sessionGroupHandle, bool isChannel, bool incoming, std::string &nameString, std::string &applicationString); + void sessionGroupAddedEvent(std::string &sessionGroupHandle); + void sessionRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle); + void participantAddedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString, std::string &displayNameString, int participantType); + void participantRemovedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, std::string &nameString); + void participantUpdatedEvent(std::string &sessionHandle, std::string &sessionGroupHandle, std::string &uriString, std::string &alias, bool isModeratorMuted, bool isSpeaking, int volume, F32 energy); + void auxAudioPropertiesEvent(F32 energy); + void buddyPresenceEvent(std::string &uriString, std::string &alias, std::string &statusString, std::string &applicationString); + void messageEvent(std::string &sessionHandle, std::string &uriString, std::string &alias, std::string &messageHeader, std::string &messageBody, std::string &applicationString); + void sessionNotificationEvent(std::string &sessionHandle, std::string &uriString, std::string ¬ificationType); + void subscriptionEvent(std::string &buddyURI, std::string &subscriptionHandle, std::string &alias, std::string &displayName, std::string &applicationString, std::string &subscriptionType); + + void buddyListChanged(); + void muteListChanged(); + void updateFriends(U32 mask); + + ///////////////////////////// + // Sending updates of current state + void updatePosition(void); + void setCameraPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot); + void setAvatarPosition(const LLVector3d &position, const LLVector3 &velocity, const LLMatrix3 &rot); + bool channelFromRegion(LLViewerRegion *region, std::string &name); + + void setEarLocation(S32 loc); + + + ///////////////////////////// + // Accessors for data related to nearby speakers + + // MBW -- XXX -- Not sure how to get this data out of the TVC + BOOL getUsingPTT(const LLUUID& id); + std::string getGroupID(const LLUUID& id); // group ID if the user is in group chat (empty string if not applicable) + + ///////////////////////////// + BOOL getAreaVoiceDisabled(); // returns true if the area the avatar is in is speech-disabled. + // Use this to determine whether to show a "no speech" icon in the menu bar. + + + // PTT + void setPTTKey(std::string &key); + + ///////////////////////////// + // Recording controls + void recordingLoopStart(int seconds = 3600, int deltaFramesPerControlFrame = 200); + void recordingLoopSave(const std::string& filename); + void recordingStop(); + + // Playback controls + void filePlaybackStart(const std::string& filename); + void filePlaybackStop(); + void filePlaybackSetPaused(bool paused); + void filePlaybackSetMode(bool vox = false, float speed = 1.0f); + + participantState *findParticipantByID(const LLUUID& id); + + + //////////////////////////////////////// + // voice sessions. + typedef std::set sessionSet; + + typedef sessionSet::iterator sessionIterator; + sessionIterator sessionsBegin(void); + sessionIterator sessionsEnd(void); + + sessionState *findSession(const std::string &handle); + sessionState *findSessionBeingCreatedByURI(const std::string &uri); + sessionState *findSession(const LLUUID &participant_id); + sessionState *findSessionByCreateID(const std::string &create_id); + + sessionState *addSession(const std::string &uri, const std::string &handle = LLStringUtil::null); + void setSessionHandle(sessionState *session, const std::string &handle = LLStringUtil::null); + void setSessionURI(sessionState *session, const std::string &uri); + void deleteSession(sessionState *session); + void deleteAllSessions(void); + + void verifySessionState(void); + + void joinedAudioSession(sessionState *session); + void leftAudioSession(sessionState *session); + + // This is called in several places where the session _may_ need to be deleted. + // It contains logic for whether to delete the session or keep it around. + void reapSession(sessionState *session); + + // Returns true if the session seems to indicate we've moved to a region on a different voice server + bool sessionNeedsRelog(sessionState *session); + + + ////////////////////////////////////// + // buddy list stuff, needed for SLIM later + struct buddyListEntry + { + buddyListEntry(const std::string &uri); + std::string mURI; + std::string mDisplayName; + LLUUID mUUID; + bool mOnlineSL; + bool mOnlineSLim; + bool mCanSeeMeOnline; + bool mHasBlockListEntry; + bool mHasAutoAcceptListEntry; + bool mNameResolved; + bool mInSLFriends; + bool mInVivoxBuddies; + bool mNeedsNameUpdate; + }; + + typedef std::map buddyListMap; + + // This should be called when parsing a buddy list entry sent by SLVoice. + void processBuddyListEntry(const std::string &uri, const std::string &displayName); + + buddyListEntry *addBuddy(const std::string &uri); + buddyListEntry *addBuddy(const std::string &uri, const std::string &displayName); + buddyListEntry *findBuddy(const std::string &uri); + buddyListEntry *findBuddy(const LLUUID &id); + buddyListEntry *findBuddyByDisplayName(const std::string &name); + void deleteBuddy(const std::string &uri); + void deleteAllBuddies(void); + + void deleteAllBlockRules(void); + void addBlockRule(const std::string &blockMask, const std::string &presenceOnly); + void deleteAllAutoAcceptRules(void); + void addAutoAcceptRule(const std::string &autoAcceptMask, const std::string &autoAddAsBuddy); + void accountListBlockRulesResponse(int statusCode, const std::string &statusString); + void accountListAutoAcceptRulesResponse(int statusCode, const std::string &statusString); + + ///////////////////////////// + // session control messages + + void accountListBlockRulesSendMessage(); + void accountListAutoAcceptRulesSendMessage(); + + void sessionGroupCreateSendMessage(); + void sessionCreateSendMessage(sessionState *session, bool startAudio = true, bool startText = false); + void sessionGroupAddSessionSendMessage(sessionState *session, bool startAudio = true, bool startText = false); + void sessionMediaConnectSendMessage(sessionState *session); // just joins the audio session + void sessionTextConnectSendMessage(sessionState *session); // just joins the text session + void sessionTerminateSendMessage(sessionState *session); + void sessionGroupTerminateSendMessage(sessionState *session); + void sessionMediaDisconnectSendMessage(sessionState *session); + void sessionTextDisconnectSendMessage(sessionState *session); + + // Pokes the state machine to leave the audio session next time around. + void sessionTerminate(); + + // Pokes the state machine to shut down the connector and restart it. + void requestRelog(); + + // Does the actual work to get out of the audio session + void leaveAudioSession(); + + void lookupName(const LLUUID &id); + static void onAvatarNameLookup(const LLUUID& id, const std::string& first, const std::string& last, BOOL is_group); + void avatarNameResolved(const LLUUID &id, const std::string &name); + +private: + LLVoiceVersionInfo mVoiceVersion; + + state mState; + bool mSessionTerminateRequested; + bool mRelogRequested; + // Number of times (in a row) "stateJoiningSession" case for spatial channel is reached in stateMachine(). + // The larger it is the greater is possibility there is a problem with connection to voice server. + // Introduced while fixing EXT-4313. + int mSpatialJoiningNum; + + void setState(state inState); + state getState(void) { return mState; }; + std::string state2string(state inState); + + void stateMachine(); + static void idle(void *user_data); + + LLHost mDaemonHost; + LLSocket::ptr_t mSocket; + bool mConnected; + + + LLPumpIO *mPump; + friend class LLVivoxProtocolParser; + + std::string mAccountName; + std::string mAccountPassword; + std::string mAccountDisplayName; + + bool mTuningMode; + float mTuningEnergy; + std::string mTuningAudioFile; + int mTuningMicVolume; + bool mTuningMicVolumeDirty; + int mTuningSpeakerVolume; + bool mTuningSpeakerVolumeDirty; + state mTuningExitState; // state to return to when we leave tuning mode. + + std::string mSpatialSessionURI; + std::string mSpatialSessionCredentials; + + std::string mMainSessionGroupHandle; // handle of the "main" session group. + + std::string mChannelName; // Name of the channel to be looked up + bool mAreaVoiceDisabled; + sessionState *mAudioSession; // Session state for the current audio session + bool mAudioSessionChanged; // set to true when the above pointer gets changed, so observers can be notified. + + sessionState *mNextAudioSession; // Session state for the audio session we're trying to join + +// std::string mSessionURI; // URI of the session we're in. +// std::string mSessionHandle; // returned by ? + + S32 mCurrentParcelLocalID; // Used to detect parcel boundary crossings + std::string mCurrentRegionName; // Used to detect parcel boundary crossings + + std::string mConnectorHandle; // returned by "Create Connector" message + std::string mAccountHandle; // returned by login message + int mNumberOfAliases; + U32 mCommandCookie; + + std::string mVoiceAccountServerURI; + std::string mVoiceSIPURIHostName; + + int mLoginRetryCount; + + sessionMap mSessionsByHandle; // Active sessions, indexed by session handle. Sessions which are being initiated may not be in this map. + sessionSet mSessions; // All sessions, not indexed. This is the canonical session list. + + bool mBuddyListMapPopulated; + bool mBlockRulesListReceived; + bool mAutoAcceptRulesListReceived; + buddyListMap mBuddyListMap; + + LLVoiceDeviceList mCaptureDevices; + LLVoiceDeviceList mRenderDevices; + + std::string mCaptureDevice; + std::string mRenderDevice; + bool mCaptureDeviceDirty; + bool mRenderDeviceDirty; + + // This should be called when the code detects we have changed parcels. + // It initiates the call to the server that gets the parcel channel. + void parcelChanged(); + + void switchChannel(std::string uri = std::string(), bool spatial = true, bool no_reconnect = false, bool is_p2p = false, std::string hash = ""); + void joinSession(sessionState *session); + + std::string nameFromAvatar(LLVOAvatar *avatar); + std::string nameFromID(const LLUUID &id); + bool IDFromName(const std::string name, LLUUID &uuid); + std::string displayNameFromAvatar(LLVOAvatar *avatar); + std::string sipURIFromAvatar(LLVOAvatar *avatar); + std::string sipURIFromName(std::string &name); + + // Returns the name portion of the SIP URI if the string looks vaguely like a SIP URI, or an empty string if not. + std::string nameFromsipURI(const std::string &uri); + + bool inSpatialChannel(void); + std::string getAudioSessionURI(); + std::string getAudioSessionHandle(); + + void sendPositionalUpdate(void); + + void buildSetCaptureDevice(std::ostringstream &stream); + void buildSetRenderDevice(std::ostringstream &stream); + + void clearAllLists(); + void checkFriend(const LLUUID& id); + void sendFriendsListUpdates(); + + // start a text IM session with the specified user + // This will be asynchronous, the session may be established at a future time. + sessionState* startUserIMSession(const LLUUID& uuid); + void sendQueuedTextMessages(sessionState *session); + + void enforceTether(void); + + bool mSpatialCoordsDirty; + + LLVector3d mCameraPosition; + LLVector3d mCameraRequestedPosition; + LLVector3 mCameraVelocity; + LLMatrix3 mCameraRot; + + LLVector3d mAvatarPosition; + LLVector3 mAvatarVelocity; + LLMatrix3 mAvatarRot; + + bool mPTTDirty; + bool mPTT; + + bool mUsePTT; + bool mPTTIsMiddleMouse; + KEY mPTTKey; + bool mPTTIsToggle; + bool mUserPTTState; + bool mMuteMic; + + // Set to true when the friends list is known to have changed. + bool mFriendsListDirty; + + enum + { + earLocCamera = 0, // ear at camera + earLocAvatar, // ear at avatar + earLocMixed // ear at avatar location/camera direction + }; + + S32 mEarLocation; + + bool mSpeakerVolumeDirty; + bool mSpeakerMuteDirty; + int mSpeakerVolume; + + int mMicVolume; + bool mMicVolumeDirty; + + bool mVoiceEnabled; + bool mWriteInProgress; + std::string mWriteString; + size_t mWriteOffset; + + LLTimer mUpdateTimer; + + BOOL mLipSyncEnabled; + + typedef std::set observer_set_t; + observer_set_t mParticipantObservers; + + void notifyParticipantObservers(); + + typedef std::set status_observer_set_t; + status_observer_set_t mStatusObservers; + + void notifyStatusObservers(LLVoiceClientStatusObserver::EStatusType status); + + typedef std::set friend_observer_set_t; + friend_observer_set_t mFriendObservers; + void notifyFriendObservers(); +}; + +/** + * @class LLVivoxProtocolParser + * @brief This class helps construct new LLIOPipe specializations + * @see LLIOPipe + * + * THOROUGH_DESCRIPTION + */ +class LLVivoxProtocolParser : public LLIOPipe +{ + LOG_CLASS(LLVivoxProtocolParser); +public: + LLVivoxProtocolParser(); + virtual ~LLVivoxProtocolParser(); + +protected: + /* @name LLIOPipe virtual implementations + */ + //@{ + /** + * @brief Process the data in buffer + */ + virtual EStatus process_impl( + const LLChannelDescriptors& channels, + buffer_ptr_t& buffer, + bool& eos, + LLSD& context, + LLPumpIO* pump); + //@} + + std::string mInput; + + // Expat control members + XML_Parser parser; + int responseDepth; + bool ignoringTags; + bool isEvent; + int ignoreDepth; + + // Members for processing responses. The values are transient and only valid within a call to processResponse(). + bool squelchDebugOutput; + int returnCode; + int statusCode; + std::string statusString; + std::string requestId; + std::string actionString; + std::string connectorHandle; + std::string versionID; + std::string accountHandle; + std::string sessionHandle; + std::string sessionGroupHandle; + std::string alias; + std::string applicationString; + + // Members for processing events. The values are transient and only valid within a call to processResponse(). + std::string eventTypeString; + int state; + std::string uriString; + bool isChannel; + bool incoming; + bool enabled; + std::string nameString; + std::string audioMediaString; + std::string deviceString; + std::string displayNameString; + int participantType; + bool isLocallyMuted; + bool isModeratorMuted; + bool isSpeaking; + int volume; + F32 energy; + std::string messageHeader; + std::string messageBody; + std::string notificationType; + bool hasText; + bool hasAudio; + bool hasVideo; + bool terminated; + std::string blockMask; + std::string presenceOnly; + std::string autoAcceptMask; + std::string autoAddAsBuddy; + int numberOfAliases; + std::string subscriptionHandle; + std::string subscriptionType; + + + // Members for processing text between tags + std::string textBuffer; + bool accumulateText; + + void reset(); + + void processResponse(std::string tag); + + static void XMLCALL ExpatStartTag(void *data, const char *el, const char **attr); + static void XMLCALL ExpatEndTag(void *data, const char *el); + static void XMLCALL ExpatCharHandler(void *data, const XML_Char *s, int len); + + void StartTag(const char *tag, const char **attr); + void EndTag(const char *tag); + void CharData(const char *buffer, int length); + +}; + + +#endif //LL_VIVOX_VOICE_CLIENT_H + + + diff --git a/indra/newview/llweb.cpp b/indra/newview/llweb.cpp index 1a64f9d881..aa03b1afd1 100644 --- a/indra/newview/llweb.cpp +++ b/indra/newview/llweb.cpp @@ -155,7 +155,7 @@ std::string LLWeb::expandURLSubstitutions(const std::string &url, substitution["VERSION_PATCH"] = LLVersionInfo::getPatch(); substitution["VERSION_BUILD"] = LLVersionInfo::getBuild(); substitution["CHANNEL"] = LLVersionInfo::getChannel(); - substitution["GRID"] = LLViewerLogin::getInstance()->getGridLabel(); + substitution["GRID"] = LLGridManager::getInstance()->getGridLabel(); substitution["OS"] = LLAppViewer::instance()->getOSInfo().getOSStringSimple(); substitution["SESSION_ID"] = gAgent.getSessionID(); substitution["FIRST_LOGIN"] = gAgent.isFirstLogin(); diff --git a/indra/newview/llworld.cpp b/indra/newview/llworld.cpp index 0b63f5efbd..58b9f5ce18 100644 --- a/indra/newview/llworld.cpp +++ b/indra/newview/llworld.cpp @@ -133,10 +133,11 @@ void LLWorld::destroyClass() LLViewerRegion* LLWorld::addRegion(const U64 ®ion_handle, const LLHost &host) { LLMemType mt(LLMemType::MTYPE_REGIONS); - + llinfos << "Add region with handle: " << region_handle << " on host " << host << llendl; LLViewerRegion *regionp = getRegionFromHandle(region_handle); if (regionp) { + llinfos << "Region exists, removing it " << llendl; LLHost old_host = regionp->getHost(); // region already exists! if (host == old_host && regionp->isAlive()) diff --git a/indra/newview/llxmlrpclistener.cpp b/indra/newview/llxmlrpclistener.cpp index 15417614af..8237132ac5 100644 --- a/indra/newview/llxmlrpclistener.cpp +++ b/indra/newview/llxmlrpclistener.cpp @@ -28,6 +28,7 @@ #include "llerror.h" #include "stringize.h" #include "llxmlrpctransaction.h" +#include "llsecapi.h" #if LL_WINDOWS #pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally @@ -356,7 +357,22 @@ public: << data["errorcode"].asString() << " (" << data["error"].asString() << ")" << LL_ENDL; - // In addition to CURLE_OK, LLUserAuth distinguishes different error + + switch (curlcode) + { + case CURLE_SSL_PEER_CERTIFICATE: + case CURLE_SSL_CACERT: + { + LLPointer error_cert(mTransaction->getErrorCert()); + if(error_cert) + { + data["certificate"] = error_cert->getPem(); + } + break; + } + default: + break; + } // values of 'curlcode': // CURLE_COULDNT_RESOLVE_HOST, // CURLE_SSL_PEER_CERTIFICATE, diff --git a/indra/newview/llxmlrpctransaction.cpp b/indra/newview/llxmlrpctransaction.cpp index c19be37e75..da61840761 100644 --- a/indra/newview/llxmlrpctransaction.cpp +++ b/indra/newview/llxmlrpctransaction.cpp @@ -31,6 +31,9 @@ */ #include "llviewerprecompiledheaders.h" +#include +#include +#include "llsecapi.h" #include "llxmlrpctransaction.h" #include "llxmlrpclistener.h" @@ -176,6 +179,8 @@ public: std::string mResponseText; XMLRPC_REQUEST mResponse; + std::string mCertStore; + LLPointer mErrorCert; Impl(const std::string& uri, XMLRPC_REQUEST request, bool useGzip); Impl(const std::string& uri, @@ -190,7 +195,8 @@ public: private: void init(XMLRPC_REQUEST request, bool useGzip); - + static int _sslCertVerifyCallback(X509_STORE_CTX *ctx, void *param); + static CURLcode _sslCtxFunction(CURL * curl, void *sslctx, void *param); static size_t curlDownloadCallback( char* data, size_t size, size_t nmemb, void* user_data); }; @@ -228,8 +234,74 @@ LLXMLRPCTransaction::Impl::Impl(const std::string& uri, XMLRPC_RequestFree(request, 1); } +// _sslCertVerifyCallback +// callback called when a cert verification is requested. +// calls SECAPI to validate the context +int LLXMLRPCTransaction::Impl::_sslCertVerifyCallback(X509_STORE_CTX *ctx, void *param) +{ + LLXMLRPCTransaction::Impl *transaction = (LLXMLRPCTransaction::Impl *)param; + LLPointer store = gSecAPIHandler->getCertificateStore(transaction->mCertStore); + LLPointer chain = gSecAPIHandler->getCertificateChain(ctx); + LLSD validation_params = LLSD::emptyMap(); + LLURI uri(transaction->mURI); + validation_params[CERT_HOSTNAME] = uri.hostName(); + try + { + chain->validate(VALIDATION_POLICY_SSL, store, validation_params); + } + catch (LLCertValidationTrustException& cert_exception) + { + // this exception is is handled differently than the general cert + // exceptions, as we allow the user to actually add the certificate + // for trust. + // therefore we pass back a different error code + // NOTE: We're currently 'wired' to pass around CURL error codes. This is + // somewhat clumsy, as we may run into errors that do not map directly to curl + // error codes. Should be refactored with login refactoring, perhaps. + transaction->mCurlCode = CURLE_SSL_CACERT; + // set the status directly. set curl status generates error messages and we want + // to use the fixed ones from the exceptions + transaction->setStatus(StatusCURLError, cert_exception.getMessage(), std::string()); + // We should probably have a more generic way of passing information + // back to the error handlers. + transaction->mErrorCert = cert_exception.getCert(); + return 0; + } + catch (LLCertException& cert_exception) + { + transaction->mCurlCode = CURLE_SSL_PEER_CERTIFICATE; + // set the status directly. set curl status generates error messages and we want + // to use the fixed ones from the exceptions + transaction->setStatus(StatusCURLError, cert_exception.getMessage(), std::string()); + transaction->mErrorCert = cert_exception.getCert(); + return 0; + } + catch (...) + { + // any other odd error, we just handle as a connect error. + transaction->mCurlCode = CURLE_SSL_CONNECT_ERROR; + transaction->setCurlStatus(CURLE_SSL_CONNECT_ERROR); + return 0; + } + return 1; +} +// _sslCtxFunction +// Callback function called when an SSL Context is created via CURL +// used to configure the context for custom cert validate(<, <#const & xs#>, <#T * #>, <#long #>)tion +// based on SECAPI +CURLcode LLXMLRPCTransaction::Impl::_sslCtxFunction(CURL * curl, void *sslctx, void *param) +{ + SSL_CTX * ctx = (SSL_CTX *) sslctx; + // disable any default verification for server certs + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); + // set the verification callback. + SSL_CTX_set_cert_verify_callback(ctx, _sslCertVerifyCallback, param); + // the calls are void + return CURLE_OK; + +} void LLXMLRPCTransaction::Impl::init(XMLRPC_REQUEST request, bool useGzip) { @@ -237,6 +309,7 @@ void LLXMLRPCTransaction::Impl::init(XMLRPC_REQUEST request, bool useGzip) { mCurlRequest = new LLCurlEasyRequest(); } + mErrorCert = NULL; if (gSavedSettings.getBOOL("BrowserProxyEnabled")) { @@ -252,10 +325,13 @@ void LLXMLRPCTransaction::Impl::init(XMLRPC_REQUEST request, bool useGzip) // mCurlRequest->setopt(CURLOPT_VERBOSE, 1); // usefull for debugging mCurlRequest->setopt(CURLOPT_NOSIGNAL, 1); mCurlRequest->setWriteCallback(&curlDownloadCallback, (void*)this); - mCurlRequest->setopt(CURLOPT_SSL_VERIFYPEER, LLCurl::getSSLVerify()); - mCurlRequest->setopt(CURLOPT_SSL_VERIFYHOST, LLCurl::getSSLVerify() ? 2 : 0); + BOOL vefifySSLCert = !gSavedSettings.getBOOL("NoVerifySSLCert"); + mCertStore = gSavedSettings.getString("CertStore"); + mCurlRequest->setopt(CURLOPT_SSL_VERIFYPEER, vefifySSLCert); + mCurlRequest->setopt(CURLOPT_SSL_VERIFYHOST, vefifySSLCert ? 2 : 0); // Be a little impatient about establishing connections. mCurlRequest->setopt(CURLOPT_CONNECTTIMEOUT, 40L); + mCurlRequest->setSSLCtxCallback(_sslCtxFunction, (void *)this); /* Setting the DNS cache timeout to -1 disables it completely. This might help with bug #503 */ @@ -341,11 +417,19 @@ bool LLXMLRPCTransaction::Impl::process() { if (result != CURLE_OK) { - setCurlStatus(result); - llwarns << "LLXMLRPCTransaction CURL error " - << mCurlCode << ": " << mCurlRequest->getErrorString() << llendl; - llwarns << "LLXMLRPCTransaction request URI: " - << mURI << llendl; + if ((result != CURLE_SSL_PEER_CERTIFICATE) && + (result != CURLE_SSL_CACERT)) + { + // if we have a curl error that's not already been handled + // (a non cert error), then generate the error message as + // appropriate + setCurlStatus(result); + + llwarns << "LLXMLRPCTransaction CURL error " + << mCurlCode << ": " << mCurlRequest->getErrorString() << llendl; + llwarns << "LLXMLRPCTransaction request URI: " + << mURI << llendl; + } return true; } @@ -423,7 +507,6 @@ void LLXMLRPCTransaction::Impl::setStatus(EStatus status, case StatusComplete: mStatusMessage = "(done)"; break; - default: // Usually this means that there's a problem with the login server, // not with the client. Direct user to status page. @@ -539,6 +622,11 @@ std::string LLXMLRPCTransaction::statusMessage() return impl.mStatusMessage; } +LLPointer LLXMLRPCTransaction::getErrorCert() +{ + return impl.mErrorCert; +} + std::string LLXMLRPCTransaction::statusURI() { return impl.mStatusURI; diff --git a/indra/newview/llxmlrpctransaction.h b/indra/newview/llxmlrpctransaction.h index c835423d67..8beb2e2623 100644 --- a/indra/newview/llxmlrpctransaction.h +++ b/indra/newview/llxmlrpctransaction.h @@ -38,6 +38,7 @@ typedef struct _xmlrpc_request* XMLRPC_REQUEST; typedef struct _xmlrpc_value* XMLRPC_VALUE; // foward decl of types from xmlrpc.h (this usage is type safe) +class LLCertificate; class LLXMLRPCValue // a c++ wrapper around XMLRPC_VALUE @@ -115,6 +116,8 @@ public: EStatus status(int* curlCode); // return status, and extended CURL code, if code isn't null + + LLPointer getErrorCert(); std::string statusMessage(); // return a message string, suitable for showing the user std::string statusURI(); diff --git a/indra/newview/skins/default/xui/en/floater_about.xml b/indra/newview/skins/default/xui/en/floater_about.xml index d03231a3fa..a55201fd53 100644 --- a/indra/newview/skins/default/xui/en/floater_about.xml +++ b/indra/newview/skins/default/xui/en/floater_about.xml @@ -49,7 +49,7 @@ libcurl Version: [LIBCURL_VERSION] J2C Decoder Version: [J2C_VERSION] Audio Driver Version: [AUDIO_DRIVER_VERSION] Qt Webkit Version: [QT_WEBKIT_VERSION] -Vivox Version: [VIVOX_VERSION] +Voice Server Version: [VOICE_VERSION] diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index e8ba8c683d..c42b846dbb 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -1382,6 +1382,18 @@ Unknown Vorbis encode failure on: [FILE] Unable to encode file: [FILE] + + We are unable to read your protected data so it is being reset. + This may happen when you change network setup. + + + + + +Could not teleport to [SLURL] as it's on a different grid ([GRID]) than the current grid ([CURRENT_GRID]). Please close your viewer and try again. + + + + +Could not connect to the server. +[REASON] + +SubjectName: [SUBJECT_NAME_STRING] +IssuerName: [ISSUER_NAME_STRING] +Valid From: [VALID_FROM] +Valid To: [VALID_TO] +MD5 Fingerprint: [SHA1_DIGEST] +SHA1 Fingerprint: [MD5_DIGEST] +Key Usage: [KEYUSAGE] +Extended Key Usage: [EXTENDEDKEYUSAGE] +Subject Key Identifier: [SUBJECTKEYIDENTIFIER] + + + + +The certification authority for this server is not known. + +Certificate Information: +SubjectName: [SUBJECT_NAME_STRING] +IssuerName: [ISSUER_NAME_STRING] +Valid From: [VALID_FROM] +Valid To: [VALID_TO] +MD5 Fingerprint: [SHA1_DIGEST] +SHA1 Fingerprint: [MD5_DIGEST] +Key Usage: [KEYUSAGE] +Extended Key Usage: [EXTENDEDKEYUSAGE] +Subject Key Identifier: [SUBJECTKEYIDENTIFIER] + +Would you like to trust this authority? + + -First name: +Username: - - Last name: - +width="150" /> + width="135"> Password: - - + width="83"> - Date: Wed, 12 May 2010 17:38:07 +0300 Subject: EXT-7203 FIXED Re-enabled inventory search for wearables panel - Insert filter editor and inventory panels into layout_stack - restored filter functionality for a list view - added proper switching between flat list view and folder list view - made a little optimization: got rid of redundant findChild calls because of childSetVisible(..., childGetValue(...)); Reviewed by Mike Antipov at https://codereview.productengine.com/secondlife/r/362/ --HG-- branch : product-engine --- indra/newview/llpaneloutfitedit.cpp | 49 +++++- indra/newview/llpaneloutfitedit.h | 16 +- .../skins/default/xui/en/panel_outfit_edit.xml | 192 ++++++++++----------- 3 files changed, 154 insertions(+), 103 deletions(-) diff --git a/indra/newview/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp index 008661221a..1c74c8f26a 100644 --- a/indra/newview/llpaneloutfitedit.cpp +++ b/indra/newview/llpaneloutfitedit.cpp @@ -210,7 +210,11 @@ BOOL LLPanelOutfitEdit::postBuild() mCurrentOutfitName = getChild("curr_outfit_name"); mStatus = getChild("status"); + mFolderViewBtn = getChild("folder_view_btn"); + mListViewBtn = getChild("list_view_btn"); + childSetCommitCallback("filter_button", boost::bind(&LLPanelOutfitEdit::showWearablesFilter, this), NULL); + childSetCommitCallback("folder_view_btn", boost::bind(&LLPanelOutfitEdit::showFilteredFolderWearablesPanel, this), NULL); childSetCommitCallback("list_view_btn", boost::bind(&LLPanelOutfitEdit::showFilteredWearablesPanel, this), NULL); mCOFWearables = getChild("cof_wearables_list"); @@ -267,8 +271,9 @@ BOOL LLPanelOutfitEdit::postBuild() save_registar.add("Outfit.SaveAsNew.Action", boost::bind(&LLPanelOutfitEdit::saveOutfit, this, true)); mSaveMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_save_outfit.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); - mWearableListManager = new LLFilteredWearableListManager( - getChild("filtered_wearables_list"), ALL_ITEMS_MASK); + mWearableItemsPanel = getChild("filtered_wearables_panel"); + mWearableItemsList = getChild("filtered_wearables_list"); + mWearableListManager = new LLFilteredWearableListManager(mWearableItemsList, ALL_ITEMS_MASK); return TRUE; } @@ -289,12 +294,33 @@ void LLPanelOutfitEdit::toggleAddWearablesPanel() void LLPanelOutfitEdit::showWearablesFilter() { - childSetVisible("filter_combobox_panel", childGetValue("filter_button")); + bool filter_visible = childGetValue("filter_button"); + + childSetVisible("filter_panel", filter_visible); + + if(!filter_visible) + { + mSearchFilter->clear(); + onSearchEdit(LLStringUtil::null); + } } void LLPanelOutfitEdit::showFilteredWearablesPanel() { - childSetVisible("filtered_wearables_panel", !childIsVisible("filtered_wearables_panel")); + if(switchPanels(mInventoryItemsPanel, mWearableItemsPanel)) + { + mFolderViewBtn->setToggleState(FALSE); + } + mListViewBtn->setToggleState(TRUE); +} + +void LLPanelOutfitEdit::showFilteredFolderWearablesPanel() +{ + if(switchPanels(mWearableItemsPanel, mInventoryItemsPanel)) + { + mListViewBtn->setToggleState(FALSE); + } + mFolderViewBtn->setToggleState(TRUE); } void LLPanelOutfitEdit::saveOutfit(bool as_new) @@ -359,7 +385,7 @@ void LLPanelOutfitEdit::onSearchEdit(const std::string& string) if (mSearchString == "") { mInventoryItemsPanel->setFilterSubString(LLStringUtil::null); - + mWearableItemsList->setFilterSubString(LLStringUtil::null); // re-open folders that were initially open mSavedFolderState->setApply(TRUE); mInventoryItemsPanel->getRootFolder()->applyFunctorRecursively(*mSavedFolderState); @@ -385,6 +411,8 @@ void LLPanelOutfitEdit::onSearchEdit(const std::string& string) // set new filter string mInventoryItemsPanel->setFilterSubString(mSearchString); + mWearableItemsList->setFilterSubString(mSearchString); + } void LLPanelOutfitEdit::onAddToOutfitClicked(void) @@ -540,4 +568,15 @@ void LLPanelOutfitEdit::updateVerbs() } +bool LLPanelOutfitEdit::switchPanels(LLPanel* switch_from_panel, LLPanel* switch_to_panel) +{ + if(switch_from_panel && switch_to_panel && !switch_to_panel->getVisible()) + { + switch_from_panel->setVisible(FALSE); + switch_to_panel->setVisible(TRUE); + return true; + } + return false; +} + // EOF diff --git a/indra/newview/llpaneloutfitedit.h b/indra/newview/llpaneloutfitedit.h index 04c94e553f..cb8283fca3 100644 --- a/indra/newview/llpaneloutfitedit.h +++ b/indra/newview/llpaneloutfitedit.h @@ -42,6 +42,7 @@ #include "llremoteparcelrequest.h" #include "llinventory.h" +#include "llinventoryitemslist.h" #include "llinventorymodel.h" class LLButton; @@ -87,6 +88,7 @@ public: void toggleAddWearablesPanel(); void showWearablesFilter(); void showFilteredWearablesPanel(); + void showFilteredFolderWearablesPanel(); void saveOutfit(bool as_new = false); void showSaveMenu(); @@ -103,6 +105,14 @@ public: void update(); void updateVerbs(); + /** + * @brief Helper function. Shows one panel instead of another. + * If panels already switched does nothing and returns false. + * @param switch_from_panel panel to hide + * @param switch_to_panel panel to show + * @retun returns true if switching happened, false if not. + */ + bool switchPanels(LLPanel* switch_from_panel, LLPanel* switch_to_panel); private: @@ -115,9 +125,13 @@ private: LLSaveFolderState* mSavedFolderState; std::string mSearchString; LLButton* mEditWearableBtn; + LLButton* mFolderViewBtn; + LLButton* mListViewBtn; LLToggleableMenu* mSaveMenu; - LLFilteredWearableListManager* mWearableListManager; + LLFilteredWearableListManager* mWearableListManager; + LLInventoryItemsList* mWearableItemsList; + LLPanel* mWearableItemsPanel; LLCOFObserver* mCOFObserver; std::vector mLookItemTypes; diff --git a/indra/newview/skins/default/xui/en/panel_outfit_edit.xml b/indra/newview/skins/default/xui/en/panel_outfit_edit.xml index 3200951253..7961664516 100644 --- a/indra/newview/skins/default/xui/en/panel_outfit_edit.xml +++ b/indra/newview/skins/default/xui/en/panel_outfit_edit.xml @@ -215,124 +215,120 @@ tab_group="2" user_resize="true" visible="false"> - - - + - + width="36"> + + + + width="189"> @@ -341,11 +336,11 @@ image_pressed_selected "Lit" + "Selected" - there are new messages and the Well height="28" layout="topleft" min_height="28" - top="0" - name="notification_well_panel" - width="37" min_width="37" - user_resize="false"> + name="notification_well_panel" + top="0" + user_resize="false" + width="37"> - - + label_color="Black" + left="0" + name="Unread" + tool_tip="Notifications" + width="34"> + + + -- cgit v1.2.3 From f1c775b7be55731475796b6b3b63eef1b2e290ef Mon Sep 17 00:00:00 2001 From: Loren Shih Date: Wed, 12 May 2010 11:40:38 -0400 Subject: EXT-7319 : FIXED : INFRASTRUCTURE : LLInventoryBridge cleanup Superficial header file cleanup --- indra/newview/llinventorybridge.h | 168 +++++++++++++++----------------------- 1 file changed, 64 insertions(+), 104 deletions(-) diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index d77062ba84..1c85dc8d7e 100644 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -79,16 +79,15 @@ public: virtual void restoreItem() {} virtual void restoreToWorld() {} + //-------------------------------------------------------------------- // LLFolderViewEventListener functions + //-------------------------------------------------------------------- virtual const std::string& getName() const; virtual const std::string& getDisplayName() const; virtual PermissionMask getPermissionMask() const; virtual LLFolderType::EType getPreferredType() const; virtual time_t getCreationDate() const; - virtual LLFontGL::StyleFlags getLabelStyle() const - { - return LLFontGL::NORMAL; - } + virtual LLFontGL::StyleFlags getLabelStyle() const { return LLFontGL::NORMAL; } virtual std::string getLabelSuffix() const { return LLStringUtil::null; } virtual void openItem() {} virtual void closeItem() {} @@ -120,11 +119,12 @@ public: void* cargo_data) { return FALSE; } virtual LLInventoryType::EType getInventoryType() const { return mInvType; } + //-------------------------------------------------------------------- // LLInvFVBridge functionality + //-------------------------------------------------------------------- virtual void clearDisplayName() {} - // Allow context menus to be customized for side panel. - bool isInOutfitsSidePanel() const; + bool isInOutfitsSidePanel() const; // allow context menus to be customized for side panel bool canShare(); @@ -147,7 +147,7 @@ protected: BOOL isLinkedObjectMissing() const; // Is this a linked obj whose baseobj is not in inventory? BOOL isAgentInventory() const; // false if lost or in the inventory library - BOOL isCOFFolder() const; // true if COF or descendent of. + BOOL isCOFFolder() const; // true if COF or descendent of virtual BOOL isItemPermissive() const; static void changeItemParent(LLInventoryModel* model, LLViewerInventoryItem* item, @@ -168,7 +168,7 @@ protected: }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// Class LLInvFVBridge +// Class LLInvFVBridgeBuilder // // This class intended to build Folder View Bridge via LLInvFVBridge::createBridge. // It can be overridden with another way of creation necessary Inventory-Folder-View-Bridge. @@ -214,7 +214,7 @@ public: virtual BOOL hasChildren() const { return FALSE; } virtual BOOL isUpToDate() const { return TRUE; } - // override for LLInvFVBridge + // Override for LLInvFVBridge virtual void clearDisplayName() { mDisplayName.clear(); } LLViewerInventoryItem* getItem() const; @@ -232,8 +232,14 @@ protected: class LLFolderBridge : public LLInvFVBridge { - friend class LLInvFVBridge; public: + LLFolderBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLInvFVBridge(inventory, root, uuid), + mCallingCards(FALSE), + mWearables(FALSE), + mMenu(NULL) {} BOOL dragItemIntoFolder(LLInventoryItem* inv_item, BOOL drop); BOOL dragCategoryIntoFolder(LLInventoryCategory* inv_category, @@ -278,18 +284,11 @@ public: LLViewerInventoryCategory* getCategory() const; protected: - LLFolderBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : - LLInvFVBridge(inventory, root, uuid), - mCallingCards(FALSE), - mWearables(FALSE), - mMenu(NULL) {} - - // menu callbacks + //-------------------------------------------------------------------- + // Menu callbacks + //-------------------------------------------------------------------- static void pasteClipboard(void* user_data); static void createNewCategory(void* user_data); - static void createNewShirt(void* user_data); static void createNewPants(void* user_data); static void createNewShoes(void* user_data); @@ -310,10 +309,7 @@ protected: void modifyOutfit(BOOL append); void determineFolderType(); - /** - * Returns a copy of current menu items. - */ - menuentry_vec_t getMenuItems() { return mItems; } + menuentry_vec_t getMenuItems() { return mItems; } // returns a copy of current menu items public: static LLFolderBridge* sSelf; @@ -329,63 +325,57 @@ private: class LLTextureBridge : public LLItemBridge { - friend class LLInvFVBridge; public: - virtual LLUIImagePtr getIcon() const; - virtual void openItem(); - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - virtual void performAction(LLInventoryModel* model, std::string action); - -protected: LLTextureBridge(LLInventoryPanel* inventory, LLFolderView* root, const LLUUID& uuid, LLInventoryType::EType type) : LLItemBridge(inventory, root, uuid), - mInvType(type) - {} + mInvType(type) {} + virtual LLUIImagePtr getIcon() const; + virtual void openItem(); + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + virtual void performAction(LLInventoryModel* model, std::string action); + bool canSaveTexture(void); LLInventoryType::EType mInvType; }; class LLSoundBridge : public LLItemBridge { - friend class LLInvFVBridge; public: + LLSoundBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLItemBridge(inventory, root, uuid) {} virtual void openItem(); virtual void previewItem(); virtual void buildContextMenu(LLMenuGL& menu, U32 flags); static void openSoundPreview(void*); - -protected: - LLSoundBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : - LLItemBridge(inventory, root, uuid) {} }; class LLLandmarkBridge : public LLItemBridge { - friend class LLInvFVBridge; public: + LLLandmarkBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid, + U32 flags = 0x00); virtual void performAction(LLInventoryModel* model, std::string action); virtual void buildContextMenu(LLMenuGL& menu, U32 flags); virtual LLUIImagePtr getIcon() const; virtual void openItem(); - -protected: - LLLandmarkBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid, - U32 flags = 0x00); protected: BOOL mVisited; }; class LLCallingCardBridge : public LLItemBridge { - friend class LLInvFVBridge; public: + LLCallingCardBridge(LLInventoryPanel* inventory, + LLFolderView* folder, + const LLUUID& uuid ); + ~LLCallingCardBridge(); virtual std::string getLabelSuffix() const; //virtual const std::string& getDisplayName() const; virtual LLUIImagePtr getIcon() const; @@ -396,72 +386,58 @@ public: EDragAndDropType cargo_type, void* cargo_data); void refreshFolderViewItem(); -protected: - LLCallingCardBridge(LLInventoryPanel* inventory, - LLFolderView* folder, - const LLUUID& uuid ); - ~LLCallingCardBridge(); protected: LLCallingCardObserver* mObserver; }; - class LLNotecardBridge : public LLItemBridge { - friend class LLInvFVBridge; public: - virtual void openItem(); -protected: LLNotecardBridge(LLInventoryPanel* inventory, LLFolderView* root, const LLUUID& uuid) : LLItemBridge(inventory, root, uuid) {} + virtual void openItem(); }; class LLGestureBridge : public LLItemBridge { - friend class LLInvFVBridge; public: + LLGestureBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLItemBridge(inventory, root, uuid) {} // Only suffix for gesture items, not task items, because only // gestures in your inventory can be active. virtual LLFontGL::StyleFlags getLabelStyle() const; virtual std::string getLabelSuffix() const; - virtual void performAction(LLInventoryModel* model, std::string action); virtual void openItem(); virtual BOOL removeItem(); - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - static void playGesture(const LLUUID& item_id); - -protected: - LLGestureBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) - : LLItemBridge(inventory, root, uuid) {} }; class LLAnimationBridge : public LLItemBridge { - friend class LLInvFVBridge; public: - virtual void performAction(LLInventoryModel* model, std::string action); - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); - - virtual void openItem(); - -protected: LLAnimationBridge(LLInventoryPanel* inventory, LLFolderView* root, const LLUUID& uuid) : LLItemBridge(inventory, root, uuid) {} + virtual void performAction(LLInventoryModel* model, std::string action); + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); + virtual void openItem(); }; class LLObjectBridge : public LLItemBridge { - friend class LLInvFVBridge; public: + LLObjectBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid, + LLInventoryType::EType type, + U32 flags); virtual LLUIImagePtr getIcon() const; virtual void performAction(LLInventoryModel* model, std::string action); virtual void openItem(); @@ -469,14 +445,7 @@ public: virtual std::string getLabelSuffix() const; virtual void buildContextMenu(LLMenuGL& menu, U32 flags); virtual BOOL renameItem(const std::string& new_name); - LLInventoryObject* getObject() const; -protected: - LLObjectBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid, - LLInventoryType::EType type, - U32 flags); protected: static LLUUID sContextMenuItemID; // Only valid while the context menu is open. LLInventoryType::EType mInvType; @@ -486,20 +455,23 @@ protected: class LLLSLTextBridge : public LLItemBridge { - friend class LLInvFVBridge; public: - virtual void openItem(); -protected: LLLSLTextBridge(LLInventoryPanel* inventory, LLFolderView* root, const LLUUID& uuid ) : LLItemBridge(inventory, root, uuid) {} + virtual void openItem(); }; class LLWearableBridge : public LLItemBridge { - friend class LLInvFVBridge; public: + LLWearableBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid, + LLAssetType::EType asset_type, + LLInventoryType::EType inv_type, + LLWearableType::EType wearable_type); virtual LLUIImagePtr getIcon() const; virtual void performAction(LLInventoryModel* model, std::string action); virtual void openItem(); @@ -525,14 +497,6 @@ public: static void removeItemFromAvatar(LLViewerInventoryItem *item); static void removeAllClothesFromAvatar(); void removeFromAvatar(); - -protected: - LLWearableBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid, - LLAssetType::EType asset_type, - LLInventoryType::EType inv_type, - LLWearableType::EType wearable_type); protected: LLAssetType::EType mAssetType; LLInventoryType::EType mInvType; @@ -541,35 +505,31 @@ protected: class LLLinkItemBridge : public LLItemBridge { - friend class LLInvFVBridge; public: - virtual const std::string& getPrefix() { return sPrefix; } - virtual void buildContextMenu(LLMenuGL& menu, U32 flags); -protected: LLLinkItemBridge(LLInventoryPanel* inventory, LLFolderView* root, const LLUUID& uuid) : LLItemBridge(inventory, root, uuid) {} + virtual const std::string& getPrefix() { return sPrefix; } + virtual void buildContextMenu(LLMenuGL& menu, U32 flags); protected: static std::string sPrefix; }; class LLLinkFolderBridge : public LLItemBridge { - friend class LLInvFVBridge; public: + LLLinkFolderBridge(LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : + LLItemBridge(inventory, root, uuid) {} virtual const std::string& getPrefix() { return sPrefix; } virtual LLUIImagePtr getIcon() const; virtual void buildContextMenu(LLMenuGL& menu, U32 flags); virtual void performAction(LLInventoryModel* model, std::string action); virtual void gotoItem(); protected: - LLLinkFolderBridge(LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : - LLItemBridge(inventory, root, uuid) {} const LLUUID &getFolderID() const; -protected: static std::string sPrefix; }; -- cgit v1.2.3 From dbaaebfe5b2847a258019cd82a2abe044e3d3140 Mon Sep 17 00:00:00 2001 From: Loren Shih Date: Wed, 12 May 2010 13:46:21 -0400 Subject: EXT-7319 : FIXED : INFRASTRUCTURE : LLInventoryBridge cleanup Const correctness, logic cleanup, took out subclasses redefining same variable as base class (!!!). --- indra/newview/llinventorybridge.cpp | 45 ++++++---------- indra/newview/llinventorybridge.h | 99 +++++++++++++--------------------- indra/newview/llinventoryfunctions.cpp | 21 +++----- indra/newview/llinventoryfunctions.h | 2 +- 4 files changed, 62 insertions(+), 105 deletions(-) diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 64379f4ce7..973257b19c 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -997,46 +997,36 @@ void LLInvFVBridge::purgeItem(LLInventoryModel *model, const LLUUID &uuid) } } -bool LLInvFVBridge::isInOutfitsSidePanel() const +BOOL LLInvFVBridge::isInOutfitsSidePanel() const { LLInventoryPanel *my_panel = dynamic_cast(mInventoryPanel.get()); LLPanelOutfitsInventory *outfit_panel = dynamic_cast(LLSideTray::getInstance()->getPanel("panel_outfits_inventory")); if (!outfit_panel) - return false; + return FALSE; return outfit_panel->isTabPanel(my_panel); } -bool LLInvFVBridge::canShare() +BOOL LLInvFVBridge::canShare() const { const LLInventoryModel* model = getInventoryModel(); - if(!model) - { - return false; - } + if (!model) return FALSE; - LLViewerInventoryItem *item = model->getItem(mUUID); + const LLViewerInventoryItem *item = model->getItem(mUUID); if (item) { - bool allowed = false; - allowed = LLInventoryCollectFunctor::itemTransferCommonlyAllowed(item); - if (allowed && - !item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID())) - { - allowed = false; - } - if (allowed && - !item->getPermissions().allowCopyBy(gAgent.getID())) - { - allowed = false; - } - return allowed; + if (!LLInventoryCollectFunctor::itemTransferCommonlyAllowed(item)) + return FALSE; + if (!item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID())) + return FALSE; + if (!item->getPermissions().allowCopyBy(gAgent.getID())) + return FALSE; + return TRUE; } - LLViewerInventoryCategory* cat = model->getCategory(mUUID); - // All categories can be given. - return cat != NULL; + const LLViewerInventoryCategory* cat = model->getCategory(mUUID); + return (cat != NULL); } // +=================================================+ @@ -4095,12 +4085,11 @@ LLObjectBridge::LLObjectBridge(LLInventoryPanel* inventory, const LLUUID& uuid, LLInventoryType::EType type, U32 flags) : - LLItemBridge(inventory, root, uuid), - mInvType(type) + LLItemBridge(inventory, root, uuid) { mAttachPt = (flags & 0xff); // low bye of inventory flags - mIsMultiObject = ( flags & LLInventoryItemFlags::II_FLAGS_OBJECT_HAS_MULTIPLE_ITEMS ) ? TRUE: FALSE; + mInvType = type; } LLUIImagePtr LLObjectBridge::getIcon() const @@ -4447,9 +4436,9 @@ LLWearableBridge::LLWearableBridge(LLInventoryPanel* inventory, LLWearableType::EType wearable_type) : LLItemBridge(inventory, root, uuid), mAssetType( asset_type ), - mInvType(inv_type), mWearableType(wearable_type) { + mInvType = inv_type; } // *NOTE: hack to get from avatar inventory to avatar diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index 1c85dc8d7e..c5efefac7e 100644 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -74,13 +74,19 @@ public: U32 flags = 0x00); virtual ~LLInvFVBridge() {} - virtual const LLUUID& getUUID() const { return mUUID; } + BOOL isInOutfitsSidePanel() const; // allow context menus to be customized for side panel + BOOL canShare() const; + //-------------------------------------------------------------------- + // LLInvFVBridge functionality + //-------------------------------------------------------------------- + virtual const LLUUID& getUUID() const { return mUUID; } + virtual void clearDisplayName() {} virtual void restoreItem() {} virtual void restoreToWorld() {} //-------------------------------------------------------------------- - // LLFolderViewEventListener functions + // Inherited LLFolderViewEventListener functions //-------------------------------------------------------------------- virtual const std::string& getName() const; virtual const std::string& getDisplayName() const; @@ -99,7 +105,6 @@ public: virtual BOOL isItemMovable() const; virtual BOOL isItemInTrash() const; virtual BOOL isLink() const; - //virtual BOOL removeItem() = 0; virtual void removeBatch(LLDynamicArray& batch); virtual void move(LLFolderViewEventListener* new_parent_bridge) {} @@ -119,15 +124,6 @@ public: void* cargo_data) { return FALSE; } virtual LLInventoryType::EType getInventoryType() const { return mInvType; } - //-------------------------------------------------------------------- - // LLInvFVBridge functionality - //-------------------------------------------------------------------- - virtual void clearDisplayName() {} - - bool isInOutfitsSidePanel() const; // allow context menus to be customized for side panel - - bool canShare(); - //-------------------------------------------------------------------- // Convenience functions for adding various common menu options. //-------------------------------------------------------------------- @@ -195,7 +191,6 @@ public: LLInvFVBridge(inventory, root, uuid) {} virtual void performAction(LLInventoryModel* model, std::string action); - virtual void selectItem(); virtual void restoreItem(); virtual void restoreToWorld(); @@ -214,20 +209,17 @@ public: virtual BOOL hasChildren() const { return FALSE; } virtual BOOL isUpToDate() const { return TRUE; } - // Override for LLInvFVBridge - virtual void clearDisplayName() { mDisplayName.clear(); } + /*virtual*/ void clearDisplayName() { mDisplayName.clear(); } LLViewerInventoryItem* getItem() const; - bool isAddAction(std::string action) const; bool isRemoveAction(std::string action) const; - protected: + BOOL confirmRemoveItem(const LLSD& notification, const LLSD& response); virtual BOOL isItemPermissive() const; static void buildDisplayName(LLInventoryItem* item, std::string& name); - mutable std::string mDisplayName; - BOOL confirmRemoveItem(const LLSD& notification, const LLSD& response); + mutable std::string mDisplayName; }; class LLFolderBridge : public LLInvFVBridge @@ -240,10 +232,9 @@ public: mCallingCards(FALSE), mWearables(FALSE), mMenu(NULL) {} - BOOL dragItemIntoFolder(LLInventoryItem* inv_item, - BOOL drop); - BOOL dragCategoryIntoFolder(LLInventoryCategory* inv_category, - BOOL drop); + BOOL dragItemIntoFolder(LLInventoryItem* inv_item, BOOL drop); + BOOL dragCategoryIntoFolder(LLInventoryCategory* inv_category, BOOL drop); + virtual void performAction(LLInventoryModel* model, std::string action); virtual void openItem(); virtual void closeItem(); @@ -311,10 +302,15 @@ protected: menuentry_vec_t getMenuItems() { return mItems; } // returns a copy of current menu items + + //-------------------------------------------------------------------- + // Messy hacks for handling folder options + //-------------------------------------------------------------------- public: static LLFolderBridge* sSelf; static void staticFolderOptionsMenu(); void folderOptionsMenu(); + private: BOOL mCallingCards; BOOL mWearables; @@ -330,15 +326,15 @@ public: LLFolderView* root, const LLUUID& uuid, LLInventoryType::EType type) : - LLItemBridge(inventory, root, uuid), - mInvType(type) {} + LLItemBridge(inventory, root, uuid) + { + mInvType = type; + } virtual LLUIImagePtr getIcon() const; virtual void openItem(); virtual void buildContextMenu(LLMenuGL& menu, U32 flags); virtual void performAction(LLInventoryModel* model, std::string action); - bool canSaveTexture(void); - LLInventoryType::EType mInvType; }; class LLSoundBridge : public LLItemBridge @@ -447,8 +443,7 @@ public: virtual BOOL renameItem(const std::string& new_name); LLInventoryObject* getObject() const; protected: - static LLUUID sContextMenuItemID; // Only valid while the context menu is open. - LLInventoryType::EType mInvType; + static LLUUID sContextMenuItemID; // Only valid while the context menu is open. U32 mAttachPt; BOOL mIsMultiObject; }; @@ -499,7 +494,6 @@ public: void removeFromAvatar(); protected: LLAssetType::EType mAssetType; - LLInventoryType::EType mInvType; LLWearableType::EType mWearableType; }; @@ -563,47 +557,33 @@ protected: LLInventoryModel* mModel; }; +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Recent Inventory Panel related classes +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -/************************************************************************/ -/* Recent Inventory Panel related classes */ -/************************************************************************/ -class LLRecentInventoryBridgeBuilder; -/** - * Overridden version of the Inventory-Folder-View-Bridge for Folders - */ +// Overridden version of the Inventory-Folder-View-Bridge for Folders class LLRecentItemsFolderBridge : public LLFolderBridge { - friend class LLRecentInventoryBridgeBuilder; - public: - /** - * Creates context menu for Folders related to Recent Inventory Panel. - * - * It uses base logic and than removes from visible items "New..." menu items. - */ - /*virtual*/ void buildContextMenu(LLMenuGL& menu, U32 flags); - -protected: + // Creates context menu for Folders related to Recent Inventory Panel. + // Uses base logic and than removes from visible items "New..." menu items. LLRecentItemsFolderBridge(LLInventoryType::EType type, - LLInventoryPanel* inventory, - LLFolderView* root, - const LLUUID& uuid) : + LLInventoryPanel* inventory, + LLFolderView* root, + const LLUUID& uuid) : LLFolderBridge(inventory, root, uuid) { mInvType = type; } + /*virtual*/ void buildContextMenu(LLMenuGL& menu, U32 flags); }; -/** - * Bridge builder to create Inventory-Folder-View-Bridge for Recent Inventory Panel - */ +// Bridge builder to create Inventory-Folder-View-Bridge for Recent Inventory Panel class LLRecentInventoryBridgeBuilder : public LLInventoryFVBridgeBuilder { - /** - * Overrides FolderBridge for Recent Inventory Panel. - * - * It use base functionality for bridges other than FolderBridge. - */ +public: + // Overrides FolderBridge for Recent Inventory Panel. + // It use base functionality for bridges other than FolderBridge. virtual LLInvFVBridge* createBridge(LLAssetType::EType asset_type, LLAssetType::EType actual_asset_type, LLInventoryType::EType inv_type, @@ -611,11 +591,8 @@ class LLRecentInventoryBridgeBuilder : public LLInventoryFVBridgeBuilder LLFolderView* root, const LLUUID& uuid, U32 flags = 0x00) const; - }; - - void wear_inventory_item_on_avatar(LLInventoryItem* item); void rez_attachment(LLViewerInventoryItem* item, diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index 35ad8b64da..f1520d34df 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -92,39 +92,30 @@ LLUUID LLInventoryState::sWearNewClothingTransactionID; ///---------------------------------------------------------------------------- // static -bool LLInventoryCollectFunctor::itemTransferCommonlyAllowed(LLInventoryItem* item) +bool LLInventoryCollectFunctor::itemTransferCommonlyAllowed(const LLInventoryItem* item) { if (!item) return false; - bool allowed = false; - switch(item->getType()) { case LLAssetType::AT_CALLINGCARD: - // not allowed + return false; break; - case LLAssetType::AT_OBJECT: if (isAgentAvatarValid() && !gAgentAvatarp->isWearingAttachment(item->getUUID())) - { - allowed = true; - } + return true; break; - case LLAssetType::AT_BODYPART: case LLAssetType::AT_CLOTHING: if(!gAgentWearables.isWearingItem(item->getUUID())) - { - allowed = true; - } + return true; break; default: - allowed = true; + return true; break; } - - return allowed; + return false; } bool LLIsType::operator()(LLInventoryCategory* cat, LLInventoryItem* item) diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h index 1e2b4ff09c..02a5fc9224 100644 --- a/indra/newview/llinventoryfunctions.h +++ b/indra/newview/llinventoryfunctions.h @@ -58,7 +58,7 @@ public: virtual ~LLInventoryCollectFunctor(){}; virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) = 0; - static bool itemTransferCommonlyAllowed(LLInventoryItem* item); + static bool itemTransferCommonlyAllowed(const LLInventoryItem* item); }; //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- cgit v1.2.3 From 491d0f1ae4b3557f36a9c1ba7d0033fdcb7d22e5 Mon Sep 17 00:00:00 2001 From: Eli Linden Date: Wed, 12 May 2010 11:27:09 -0700 Subject: ND-46735 WIP IT linguistic --- indra/newview/skins/default/xui/it/floater_animation_preview.xml | 2 +- indra/newview/skins/default/xui/it/floater_tools.xml | 8 ++++---- indra/newview/skins/default/xui/it/menu_viewer.xml | 4 ++-- indra/newview/skins/default/xui/it/panel_group_general.xml | 2 +- indra/newview/skins/default/xui/it/panel_region_general.xml | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/indra/newview/skins/default/xui/it/floater_animation_preview.xml b/indra/newview/skins/default/xui/it/floater_animation_preview.xml index 5472e32544..77341cad63 100644 --- a/indra/newview/skins/default/xui/it/floater_animation_preview.xml +++ b/indra/newview/skins/default/xui/it/floater_animation_preview.xml @@ -153,7 +153,7 @@ La lunghezza massima è [MAX_LENGTH] secondi. - + diff --git a/indra/newview/skins/default/xui/it/floater_tools.xml b/indra/newview/skins/default/xui/it/floater_tools.xml index 1c611915dc..cd16246f0f 100644 --- a/indra/newview/skins/default/xui/it/floater_tools.xml +++ b/indra/newview/skins/default/xui/it/floater_tools.xml @@ -39,11 +39,11 @@ Accessorio - + + + + + + + + + + + + + Date: Fri, 14 May 2010 11:27:29 -0400 Subject: EXT-4088 FIXED (INFRASTRUCTURE) Change LLFolderView::getSelectionList to return a selection Fixed issue where mCurrentSelectedList was being used before NULL check. --- indra/newview/llpanellandmarks.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/indra/newview/llpanellandmarks.cpp b/indra/newview/llpanellandmarks.cpp index 6634bc948d..4bf4f9eac1 100644 --- a/indra/newview/llpanellandmarks.cpp +++ b/indra/newview/llpanellandmarks.cpp @@ -993,10 +993,13 @@ bool LLLandmarksPanel::isActionEnabled(const LLSD& userdata) const } else if("create_pick" == command_name) { - std::set selection = mCurrentSelectedList->getRootFolder()->getSelectionList(); - if ( mCurrentSelectedList && !selection.empty() ) + if (mCurrentSelectedList) { - return ( 1 == selection.size() && !LLAgentPicksInfo::getInstance()->isPickLimitReached() ); + std::set selection = mCurrentSelectedList->getRootFolder()->getSelectionList(); + if (!selection.empty()) + { + return ( 1 == selection.size() && !LLAgentPicksInfo::getInstance()->isPickLimitReached() ); + } } return false; } -- cgit v1.2.3 From f4cb22f33d03998e41e8cc6f4bbf5e46b39b2aa0 Mon Sep 17 00:00:00 2001 From: Paul Guslisty Date: Fri, 14 May 2010 18:52:20 +0300 Subject: EXT-7203 ADDITIONAL FIX Deleted autoresize of filter panel for proper scaling while resizing --HG-- branch : product-engine --- indra/newview/skins/default/xui/en/panel_outfit_edit.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/skins/default/xui/en/panel_outfit_edit.xml b/indra/newview/skins/default/xui/en/panel_outfit_edit.xml index 7961664516..f661d7e237 100644 --- a/indra/newview/skins/default/xui/en/panel_outfit_edit.xml +++ b/indra/newview/skins/default/xui/en/panel_outfit_edit.xml @@ -260,7 +260,7 @@ top_pad="5" left="0"> Date: Fri, 14 May 2010 17:31:56 -0400 Subject: EXT-7353 FIXED short cuts (links) to textures should show up in the texture picker Links in general will now show up for all filtered inventory views of their associated types. --- indra/newview/llinventorybridge.cpp | 14 +------------- indra/newview/llinventoryfilter.h | 7 ++++++- indra/newview/llinventorymodel.h | 2 +- indra/newview/llinventorypanel.cpp | 5 +++++ indra/newview/llinventorypanel.h | 3 ++- indra/newview/llviewerinventory.cpp | 14 ++++++++++++++ indra/newview/llviewerinventory.h | 1 + 7 files changed, 30 insertions(+), 16 deletions(-) diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index 973257b19c..b9b4fa8b03 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -143,7 +143,6 @@ const std::string& LLInvFVBridge::getDisplayName() const // Folders have full perms PermissionMask LLInvFVBridge::getPermissionMask() const { - return PERM_ALL; } @@ -1219,18 +1218,7 @@ PermissionMask LLItemBridge::getPermissionMask() const { LLViewerInventoryItem* item = getItem(); PermissionMask perm_mask = 0; - if(item) - { - BOOL copy = item->getPermissions().allowCopyBy(gAgent.getID()); - BOOL mod = item->getPermissions().allowModifyBy(gAgent.getID()); - BOOL xfer = item->getPermissions().allowOperationBy(PERM_TRANSFER, - gAgent.getID()); - - if (copy) perm_mask |= PERM_COPY; - if (mod) perm_mask |= PERM_MODIFY; - if (xfer) perm_mask |= PERM_TRANSFER; - - } + if (item) perm_mask = item->getPermissionMask(); return perm_mask; } diff --git a/indra/newview/llinventoryfilter.h b/indra/newview/llinventoryfilter.h index 2376ba5d22..3ef51baefc 100644 --- a/indra/newview/llinventoryfilter.h +++ b/indra/newview/llinventoryfilter.h @@ -98,11 +98,15 @@ public: void setHoursAgo(U32 hours); U32 getHoursAgo() const; + void setIncludeLinks(BOOL include_links); + BOOL getIncludeLinks() const; + // +-------------------------------------------------------------------+ // + Execution And Results // +-------------------------------------------------------------------+ BOOL check(const LLFolderViewItem* item); - BOOL checkAgainstFilterType(const LLFolderViewItem* item); + BOOL checkAgainstFilterType(const LLFolderViewItem* item) const; + BOOL checkAgainstPermissions(const LLFolderViewItem* item) const; std::string::size_type getStringMatchOffset() const; // +-------------------------------------------------------------------+ @@ -172,6 +176,7 @@ private: U32 mHoursAgo; EFolderShow mShowFolderState; PermissionMask mPermissions; + BOOL mIncludeLinks; }; U32 mOrder; diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h index 5f5d4d6118..1f7bd50328 100644 --- a/indra/newview/llinventorymodel.h +++ b/indra/newview/llinventorymodel.h @@ -256,7 +256,7 @@ public: // updateCategory() method to actually modify values. LLViewerInventoryCategory* getCategory(const LLUUID& id) const; - // Get the inventoryID that this item points to, else just return item_id. + // Get the inventoryID or item that this item points to, else just return object_id const LLUUID& getLinkedItemID(const LLUUID& object_id) const; private: mutable LLPointer mLastItem; // cache recent lookups diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index 000bcdd265..4766c1c227 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -248,6 +248,11 @@ void LLInventoryPanel::setHoursAgo(U32 hours) getFilter()->setHoursAgo(hours); } +void LLInventoryPanel::setIncludeLinks(BOOL include_links) +{ + getFilter()->setIncludeLinks(include_links); +} + void LLInventoryPanel::setShowFolderState(LLInventoryFilter::EFolderShow show) { getFilter()->setShowFolderState(show); diff --git a/indra/newview/llinventorypanel.h b/indra/newview/llinventorypanel.h index 160a3d6f23..4373cedf66 100644 --- a/indra/newview/llinventorypanel.h +++ b/indra/newview/llinventorypanel.h @@ -141,7 +141,8 @@ public: void setSinceLogoff(BOOL sl); void setHoursAgo(U32 hours); BOOL getSinceLogoff(); - + void setIncludeLinks(BOOL include_links); + void setShowFolderState(LLInventoryFilter::EFolderShow show); LLInventoryFilter::EFolderShow getShowFolderState(); void setAllowMultiSelect(BOOL allow) { mFolderRoot->setAllowMultiSelect(allow); } diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 49748c59e8..ac70be029a 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -1653,6 +1653,20 @@ bool LLViewerInventoryItem::checkPermissionsSet(PermissionMask mask) const return ((curr_mask & mask) == mask); } +PermissionMask LLViewerInventoryItem::getPermissionMask() const +{ + const LLPermissions& permissions = getPermissions(); + + BOOL copy = permissions.allowCopyBy(gAgent.getID()); + BOOL mod = permissions.allowModifyBy(gAgent.getID()); + BOOL xfer = permissions.allowOperationBy(PERM_TRANSFER, gAgent.getID()); + PermissionMask perm_mask = 0; + if (copy) perm_mask |= PERM_COPY; + if (mod) perm_mask |= PERM_MODIFY; + if (xfer) perm_mask |= PERM_TRANSFER; + return perm_mask; +} + //---------- void LLViewerInventoryItem::onCallingCardNameLookup(const LLUUID& id, const std::string& first_name, const std::string& last_name) diff --git a/indra/newview/llviewerinventory.h b/indra/newview/llviewerinventory.h index f0dc7bcb67..8ab7c9710d 100644 --- a/indra/newview/llviewerinventory.h +++ b/indra/newview/llviewerinventory.h @@ -159,6 +159,7 @@ public: // Checks the items permissions (for owner, group, or everyone) and returns true if all mask bits are set. bool checkPermissionsSet(PermissionMask mask) const; + PermissionMask getPermissionMask() const; // callback void onCallingCardNameLookup(const LLUUID& id, const std::string& first_name, const std::string& last_name); -- cgit v1.2.3 From 53a5b74af12ec772a2a69cff49b2e52da51c5c8f Mon Sep 17 00:00:00 2001 From: Vadim Savchuk Date: Sat, 15 May 2010 14:40:15 +0300 Subject: EXT-7241 FIXED Changed all "Edit Appearance" links to "Change Outfit" and pointed them to the My Outfits tab. Renamed the following menu items to "Change Outfit" and made them open My Outfits tab: - Avatar in-world context menu -> My Appearance. - Avatar inspector menu -> My Appearance. - Me -> My Appearance. To enable the Appearance SP switch to My outfits I cleaned up the logic of switching between my outfits <-> edit outfit <-> edit wearable panels. Also done the following cleanups and minor improvements: - Removed unused LLSidepanelAppearance::mEditBtn and mLookInfoType members. - Removed empty LLSidepanelAppearance::updateVerbs() method. - Made the "params" argument of LLSideTray::showPanel() and togglePanel() optional. Reviewed by Nyx at https://codereview.productengine.com/secondlife/r/389/ --HG-- branch : product-engine --- indra/newview/llpaneloutfitsinventory.cpp | 5 - indra/newview/llsidepanelappearance.cpp | 102 +++++++++------------ indra/newview/llsidepanelappearance.h | 11 +-- indra/newview/llsidetray.h | 4 +- indra/newview/llviewermenu.cpp | 5 +- .../skins/default/xui/en/menu_attachment_self.xml | 4 +- .../skins/default/xui/en/menu_avatar_self.xml | 4 +- .../default/xui/en/menu_inspect_self_gear.xml | 4 +- indra/newview/skins/default/xui/en/menu_viewer.xml | 4 +- 9 files changed, 55 insertions(+), 88 deletions(-) diff --git a/indra/newview/llpaneloutfitsinventory.cpp b/indra/newview/llpaneloutfitsinventory.cpp index 660615df5a..ea75c16c56 100644 --- a/indra/newview/llpaneloutfitsinventory.cpp +++ b/indra/newview/llpaneloutfitsinventory.cpp @@ -134,11 +134,6 @@ void LLPanelOutfitsInventory::onOpen(const LLSD& key) void LLPanelOutfitsInventory::updateVerbs() { - if (mParent) - { - mParent->updateVerbs(); - } - if (mListCommands) { updateListCommands(); diff --git a/indra/newview/llsidepanelappearance.cpp b/indra/newview/llsidepanelappearance.cpp index 0086c6aec4..658a7b52e3 100644 --- a/indra/newview/llsidepanelappearance.cpp +++ b/indra/newview/llsidepanelappearance.cpp @@ -118,9 +118,6 @@ BOOL LLSidepanelAppearance::postBuild() childSetAction("edit_outfit_btn", boost::bind(&LLSidepanelAppearance::onEditOutfitButtonClicked, this)); - mEditBtn = getChild("edit_btn"); - mEditBtn->setClickedCallback(boost::bind(&LLSidepanelAppearance::onEditButtonClicked, this)); - mNewOutfitBtn = getChild("newlook_btn"); mNewOutfitBtn->setClickedCallback(boost::bind(&LLSidepanelAppearance::onNewOutfitButtonClicked, this)); mNewOutfitBtn->setEnabled(false); @@ -172,24 +169,24 @@ void LLSidepanelAppearance::onOpen(const LLSD& key) { fetchInventory(); refreshCurrentOutfitName(); - updateVerbs(); if (mPanelOutfitsInventory) { mPanelOutfitsInventory->onOpen(key); } - if(key.size() == 0) + if (!key.has("type")) return; - - toggleOutfitEditPanel(TRUE); - updateVerbs(); - - mLookInfoType = key["type"].asString(); - if (mLookInfoType == "edit_outfit") + // Switch to the requested panel. + std::string type = key["type"].asString(); + if (type == "my_outfits") { - mOutfitEdit->displayCurrentOutfit(); + showOutfitsInventoryPanel(); + } + else if (type == "edit_outfit") + { + showOutfitEditPanel(/*update = */ true); } } @@ -233,6 +230,7 @@ void LLSidepanelAppearance::onOpenOutfitButtonClicked() } } +// *TODO: obsolete? void LLSidepanelAppearance::onEditAppearanceButtonClicked() { if (gAgentWearables.areWearablesLoaded()) @@ -248,19 +246,6 @@ void LLSidepanelAppearance::onEditOutfitButtonClicked() LLSideTray::getInstance()->showPanel("sidepanel_appearance", key); } -void LLSidepanelAppearance::onEditButtonClicked() -{ - toggleOutfitEditPanel(FALSE); - toggleWearableEditPanel(TRUE, NULL); - /*if (mOutfitEdit->getVisible()) - { - } - else - { - mPanelOutfitsInventory->onEdit(); - }*/ -} - void LLSidepanelAppearance::onNewOutfitButtonClicked() { if (!mOutfitEdit->getVisible()) @@ -271,33 +256,27 @@ void LLSidepanelAppearance::onNewOutfitButtonClicked() void LLSidepanelAppearance::onEditWearBackClicked() { - mEditWearable->saveChanges(); - toggleWearableEditPanel(FALSE, NULL); - toggleOutfitEditPanel(TRUE); + showOutfitEditPanel(/* update = */ false); } void LLSidepanelAppearance::showOutfitsInventoryPanel() { - mOutfitEdit->setVisible(FALSE); - - mPanelOutfitsInventory->setVisible(TRUE); - - mFilterEditor->setVisible(TRUE); - mEditBtn->setVisible(TRUE); - mNewOutfitBtn->setVisible(TRUE); - mCurrOutfitPanel->setVisible(TRUE); + toggleWearableEditPanel(FALSE); + toggleOutfitEditPanel(FALSE); } -void LLSidepanelAppearance::showOutfitEditPanel() +void LLSidepanelAppearance::showOutfitEditPanel(bool update) { - mOutfitEdit->setVisible(TRUE); - - mPanelOutfitsInventory->setVisible(FALSE); + if (!mOutfitEdit) + return; + + toggleWearableEditPanel(FALSE); + toggleOutfitEditPanel(TRUE); - mFilterEditor->setVisible(FALSE); - mEditBtn->setVisible(FALSE); - mNewOutfitBtn->setVisible(FALSE); - mCurrOutfitPanel->setVisible(FALSE); + if (update) + { + mOutfitEdit->displayCurrentOutfit(); + } } void LLSidepanelAppearance::toggleOutfitEditPanel(BOOL visible) @@ -305,16 +284,27 @@ void LLSidepanelAppearance::toggleOutfitEditPanel(BOOL visible) if (!mOutfitEdit) return; + if (mOutfitEdit->getVisible() == visible) + { + // visibility isn't changing, hence nothing to do + return; + } + mOutfitEdit->setVisible(visible); if (mPanelOutfitsInventory) mPanelOutfitsInventory->setVisible(!visible); mFilterEditor->setVisible(!visible); - mEditBtn->setVisible(!visible); mNewOutfitBtn->setVisible(!visible); mCurrOutfitPanel->setVisible(!visible); } void LLSidepanelAppearance::toggleWearableEditPanel(BOOL visible, LLWearable *wearable) { + if (mEditWearable->getVisible() == visible) + { + // visibility isn't changing, hence nothing to do + return; + } + if (!wearable) { wearable = gAgentWearables.getWearable(LLWearableType::WT_SHAPE, 0); @@ -324,6 +314,13 @@ void LLSidepanelAppearance::toggleWearableEditPanel(BOOL visible, LLWearable *we return; } + // Save changes if closing. + if (!visible) + { + mEditWearable->saveChanges(); + } + + // Toggle panel visibility. mCurrOutfitPanel->setVisible(!visible); mEditWearable->setVisible(visible); @@ -332,21 +329,6 @@ void LLSidepanelAppearance::toggleWearableEditPanel(BOOL visible, LLWearable *we mPanelOutfitsInventory->setVisible(!visible); } -void LLSidepanelAppearance::updateVerbs() -{ - bool is_look_info_visible = mOutfitEdit->getVisible(); - - if (mPanelOutfitsInventory && !is_look_info_visible) - { -// const bool is_correct_type = (mPanelOutfitsInventory->getCorrectListenerForAction() != NULL); -// mEditBtn->setEnabled(is_correct_type); - } - else - { - mEditBtn->setEnabled(FALSE); - } -} - void LLSidepanelAppearance::refreshCurrentOutfitName(const std::string& name) { // Set current outfit status (wearing/unsaved). diff --git a/indra/newview/llsidepanelappearance.h b/indra/newview/llsidepanelappearance.h index 2900831099..a919b07ed6 100644 --- a/indra/newview/llsidepanelappearance.h +++ b/indra/newview/llsidepanelappearance.h @@ -60,11 +60,10 @@ public: void fetchInventory(); void inventoryFetched(); - void updateVerbs(); void onNewOutfitButtonClicked(); void showOutfitsInventoryPanel(); - void showOutfitEditPanel(); + void showOutfitEditPanel(bool update); void setWearablesLoading(bool val); private: @@ -73,12 +72,11 @@ private: void onOpenOutfitButtonClicked(); void onEditAppearanceButtonClicked(); void onEditOutfitButtonClicked(); - void onEditButtonClicked(); void onEditWearBackClicked(); //@deprecated use showXXX() methods instead void toggleOutfitEditPanel(BOOL visible); - void toggleWearableEditPanel(BOOL visible, LLWearable* wearable); + void toggleWearableEditPanel(BOOL visible, LLWearable* wearable = NULL); LLFilterEditor* mFilterEditor; LLPanelOutfitsInventory* mPanelOutfitsInventory; @@ -87,7 +85,6 @@ private: LLButton* mOpenOutfitBtn; LLButton* mEditAppearanceBtn; - LLButton* mEditBtn; LLButton* mNewOutfitBtn; LLPanel* mCurrOutfitPanel; @@ -103,10 +100,6 @@ private: // Search string for filtering landmarks and teleport // history locations std::string mFilterSubString; - - // Information type currently shown in Look Information panel - std::string mLookInfoType; - }; #endif //LL_LLSIDEPANELAPPEARANCE_H diff --git a/indra/newview/llsidetray.h b/indra/newview/llsidetray.h index 140a9c818a..e8fdee9430 100644 --- a/indra/newview/llsidetray.h +++ b/indra/newview/llsidetray.h @@ -94,7 +94,7 @@ public: * if no such tab - return NULL, otherwise a pointer to the panel * Pass params as array, or they may be overwritten(example - params["name"]="nearby") */ - LLPanel* showPanel (const std::string& panel_name, const LLSD& params); + LLPanel* showPanel (const std::string& panel_name, const LLSD& params = LLSD()); /** * Toggling Side Tray tab which contains "sub_panel" child of "panel_name" panel. @@ -102,7 +102,7 @@ public: * otherwise Side Tray is collapsed. * params are passed to "panel_name" panel onOpen(). */ - void togglePanel (LLPanel* &sub_panel, const std::string& panel_name, const LLSD& params); + void togglePanel (LLPanel* &sub_panel, const std::string& panel_name, const LLSD& params = LLSD()); /* * get the panel (don't show it or do anything else with it) diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 42428bab03..c245650d51 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -5597,10 +5597,7 @@ void handle_viewer_disable_message_log(void*) void handle_customize_avatar() { - if (gAgentWearables.areWearablesLoaded()) - { - gAgentCamera.changeCameraToCustomizeAvatar(); - } + LLSideTray::getInstance()->showPanel("sidepanel_appearance", LLSD().with("type", "my_outfits")); } void handle_report_abuse() diff --git a/indra/newview/skins/default/xui/en/menu_attachment_self.xml b/indra/newview/skins/default/xui/en/menu_attachment_self.xml index 5c30b9ee94..dc51ae8b92 100644 --- a/indra/newview/skins/default/xui/en/menu_attachment_self.xml +++ b/indra/newview/skins/default/xui/en/menu_attachment_self.xml @@ -68,8 +68,8 @@ function="Self.EnableStandUp" /> + label="Change Outfit" + name="Change Outfit"> + name="Chenge Outfit"> + label="Change Outfit" + name="change_outfit"> + label="Change Outfit" + name="ChangeOutfit"> Date: Mon, 17 May 2010 09:20:57 +0300 Subject: EXT-6941 FIX setup global EditMenu handle setup global EditMenu handle when pressing mouse on FolderView. --HG-- branch : product-engine --- indra/newview/llfolderview.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indra/newview/llfolderview.cpp b/indra/newview/llfolderview.cpp index 2ae11aa2b5..a87f7288fa 100644 --- a/indra/newview/llfolderview.cpp +++ b/indra/newview/llfolderview.cpp @@ -1744,6 +1744,8 @@ BOOL LLFolderView::handleMouseDown( S32 x, S32 y, MASK mask ) mParentPanel->setFocus(TRUE); + LLEditMenuHandler::gEditMenuHandler = this; + return LLView::handleMouseDown( x, y, mask ); } -- cgit v1.2.3 From e462c90db6063ee67b95ea376c65089dd0061f6d Mon Sep 17 00:00:00 2001 From: Yuri Chebotarev Date: Mon, 17 May 2010 09:22:08 +0300 Subject: EXT-7051 FIX Add more server hardcoded strings to localization dict. --HG-- branch : product-engine --- indra/newview/llviewerinventory.cpp | 35 ++++++++++++++++++++++++++ indra/newview/skins/default/xui/en/strings.xml | 30 ++++++++++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index 49748c59e8..da8832b1a2 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -95,6 +95,41 @@ public: mInventoryItemsDict["New Script"] = LLTrans::getString("New Script"); mInventoryItemsDict["New Folder"] = LLTrans::getString("New Folder"); mInventoryItemsDict["Contents"] = LLTrans::getString("Contents"); + + mInventoryItemsDict["Gesture"] = LLTrans::getString("Gesture"); + mInventoryItemsDict["Male Gestures"] = LLTrans::getString("Male Gestures"); + mInventoryItemsDict["Female Gestures"] = LLTrans::getString("Female Gestures"); + mInventoryItemsDict["Other Gestures"] = LLTrans::getString("Other Gestures"); + mInventoryItemsDict["Speech Gestures"] = LLTrans::getString("Speech Gestures"); + + //predefined gestures + + //male + mInventoryItemsDict["Male - Excuse me"] = LLTrans::getString("Male - Excuse me"); + mInventoryItemsDict["Male - Get lost"] = LLTrans::getString("Male - Get lost"); + mInventoryItemsDict["Male - Blow kiss"] = LLTrans::getString("Male - Blow kiss"); + mInventoryItemsDict["Male - Boo"] = LLTrans::getString("Male - Boo"); + mInventoryItemsDict["Male - Bored"] = LLTrans::getString("Male - Bored"); + mInventoryItemsDict["Male - Hey"] = LLTrans::getString("Male - Hey"); + mInventoryItemsDict["Male - Laugh"] = LLTrans::getString("Male - Laugh"); + mInventoryItemsDict["Male - Repulsed"] = LLTrans::getString("Male - Repulsed"); + mInventoryItemsDict["Male - Shrug"] = LLTrans::getString("Male - Shrug"); + mInventoryItemsDict["Male - Stick tougue out"] = LLTrans::getString("Male - Stick tougue out"); + mInventoryItemsDict["Male - Wow"] = LLTrans::getString("Male - Wow"); + + //female + mInventoryItemsDict["FeMale - Excuse me"] = LLTrans::getString("FeMale - Excuse me"); + mInventoryItemsDict["FeMale - Get lost"] = LLTrans::getString("FeMale - Get lost"); + mInventoryItemsDict["FeMale - Blow kiss"] = LLTrans::getString("FeMale - Blow kiss"); + mInventoryItemsDict["FeMale - Boo"] = LLTrans::getString("FeMale - Boo"); + mInventoryItemsDict["Female - Bored"] = LLTrans::getString("Female - Bored"); + mInventoryItemsDict["Female - Hey"] = LLTrans::getString("Female - Hey"); + mInventoryItemsDict["Female - Laugh"] = LLTrans::getString("Female - Laugh"); + mInventoryItemsDict["Female - Repulsed"] = LLTrans::getString("Female - Repulsed"); + mInventoryItemsDict["Female - Shrug"] = LLTrans::getString("Female - Shrug"); + mInventoryItemsDict["Female - Stick tougue out"]= LLTrans::getString("Female - Stick tougue out"); + mInventoryItemsDict["Female - Wow"] = LLTrans::getString("Female - Wow"); + } }; diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index e028ef2077..bf28e78cf6 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -3137,6 +3137,36 @@ Abuse Report New Script New Folder Contents + Gesture + Male Gestures + Female Gestures + Other Gestures + Speech Gestures + + + Male - Excuse me + Male - Get lost + Male - Blow kiss + Male - Boo + Male - Bored + Male - Hey + Male - Laugh + Male - Repulsed + Male - Shrug + Male - Stick tougue out + Male - Wow + + FeMale - Excuse me + FeMale - Get lost + FeMale - Blow kiss + FeMale - Boo + Female - Bored + Female - Hey + Female - Laugh + Female - Repulsed + Female - Shrug + Female - Stick tougue out + Female - Wow [mthnum,datetime,slt]/[day,datetime,slt]/[year,datetime,slt] -- cgit v1.2.3 From f9dff766a8fbd6e8ee8db486c1d76ededdac7ed4 Mon Sep 17 00:00:00 2001 From: Yuri Chebotarev Date: Mon, 17 May 2010 09:49:33 +0300 Subject: EXT-7000 FIX "eat" TAB key while menu is active Two problems: 1. AvatarList didn't hide popup menu when goes invisible. 2. (Real problem) MenuHolder View didn't process TAB key. Which leads to changing active ("focused") control. Which leads to the ability (for example) to switch tabs and hide popup menu parent view. --HG-- branch : product-engine --- indra/llui/llmenugl.cpp | 6 ++++++ indra/newview/llavatarlist.cpp | 9 +++++++++ indra/newview/llavatarlist.h | 2 ++ indra/newview/llavatarlistitem.h | 1 + 4 files changed, 18 insertions(+) diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index e0e86ae228..70c144a832 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -3420,6 +3420,12 @@ BOOL LLMenuHolderGL::handleKey(KEY key, MASK mask, BOOL called_from_parent) if (pMenu) { + //eat TAB key - EXT-7000 + if (key == KEY_TAB && mask == MASK_NONE) + { + return TRUE; + } + //handle ESCAPE and RETURN key handled = LLPanel::handleKey(key, mask, called_from_parent); if (!handled) diff --git a/indra/newview/llavatarlist.cpp b/indra/newview/llavatarlist.cpp index 47ec5270c3..72ee289c91 100644 --- a/indra/newview/llavatarlist.cpp +++ b/indra/newview/llavatarlist.cpp @@ -399,6 +399,15 @@ BOOL LLAvatarList::handleRightMouseDown(S32 x, S32 y, MASK mask) return handled; } +void LLAvatarList::setVisible(BOOL visible) +{ + if ( visible == FALSE && mContextMenu ) + { + mContextMenu->hide(); + } + LLFlatListViewEx::setVisible(visible); +} + void LLAvatarList::computeDifference( const uuid_vec_t& vnew_unsorted, uuid_vec_t& vadded, diff --git a/indra/newview/llavatarlist.h b/indra/newview/llavatarlist.h index ff090f3a34..a9320055ca 100644 --- a/indra/newview/llavatarlist.h +++ b/indra/newview/llavatarlist.h @@ -70,6 +70,8 @@ public: virtual void clear(); + virtual void setVisible(BOOL visible); + void setNameFilter(const std::string& filter); void setDirty(bool val = true, bool force_refresh = false); uuid_vec_t& getIDs() { return mIDs; } diff --git a/indra/newview/llavatarlistitem.h b/indra/newview/llavatarlistitem.h index 3ba2c7a3e3..c6fac7a9f1 100644 --- a/indra/newview/llavatarlistitem.h +++ b/indra/newview/llavatarlistitem.h @@ -71,6 +71,7 @@ public: { public: virtual void show(LLView* spawning_view, const uuid_vec_t& selected_uuids, S32 x, S32 y) = 0; + virtual void hide() = 0; }; /** -- cgit v1.2.3 From 8bd31fa35190bdd99a4167a2bfdd7d3d1a6e5d3f Mon Sep 17 00:00:00 2001 From: Alexei Arabadji Date: Mon, 17 May 2010 09:53:56 +0300 Subject: EXT-7085 FIXED Added check to avoid showing invisible menu items. reviewed by Vadim Savchuk at https://codereview.productengine.com/secondlife/r/388/ --HG-- branch : product-engine --- indra/llui/llmenugl.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index e0e86ae228..779779c7bc 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -3726,10 +3726,14 @@ void LLContextMenuBranch::buildDrawLabel( void ) void LLContextMenuBranch::showSubMenu() { - S32 center_x; - S32 center_y; - localPointToScreen(getRect().getWidth(), getRect().getHeight() , ¢er_x, ¢er_y); - mBranch->show( center_x, center_y); + LLMenuItemGL* menu_item = mBranch->getParentMenuItem(); + if (menu_item != NULL && menu_item->getVisible()) + { + S32 center_x; + S32 center_y; + localPointToScreen(getRect().getWidth(), getRect().getHeight() , ¢er_x, ¢er_y); + mBranch->show(center_x, center_y); + } } // onCommit() - do the primary funcationality of the menu item. -- cgit v1.2.3 From 8739d0469ba5a766693664ca6dae0421d217ee38 Mon Sep 17 00:00:00 2001 From: Yuri Chebotarev Date: Mon, 17 May 2010 09:57:20 +0300 Subject: EXT-7106 FIX Change from commit callback to validate callback, remove some cyclic tab panel switches. Problem was caused by incorrect use on Commit callback (which didn't actually prevent from tab switching) which leads to cyclic tab switching. Validate callback actually prevents tab switching. Reviwed by Vadim Savchuk at https://codereview.productengine.com/secondlife/r/385/ --HG-- branch : product-engine --- indra/newview/llpanelgrouproles.cpp | 63 +++++++++++++------------------------ indra/newview/llpanelgrouproles.h | 3 +- 2 files changed, 22 insertions(+), 44 deletions(-) diff --git a/indra/newview/llpanelgrouproles.cpp b/indra/newview/llpanelgrouproles.cpp index a77ba0c69c..93fbecfd3f 100644 --- a/indra/newview/llpanelgrouproles.cpp +++ b/indra/newview/llpanelgrouproles.cpp @@ -118,8 +118,7 @@ LLPanelGroupRoles::LLPanelGroupRoles() mCurrentTab(NULL), mRequestedTab( NULL ), mSubTabContainer( NULL ), - mFirstUse( TRUE ), - mIgnoreTransition( FALSE ) + mFirstUse( TRUE ) { } @@ -153,8 +152,8 @@ BOOL LLPanelGroupRoles::postBuild() //subtabp->addObserver(this); } - // Add click callbacks to all the tabs. - mSubTabContainer->setCommitCallback(boost::bind(&LLPanelGroupRoles::handleClickSubTab, this)); + // Add click callbacks to tab switching. + mSubTabContainer->setValidateBeforeCommit(boost::bind(&LLPanelGroupRoles::handleSubTabSwitch, this, _1)); // Set the current tab to whatever is currently being shown. mCurrentTab = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); @@ -196,30 +195,20 @@ BOOL LLPanelGroupRoles::isVisibleByAgent(LLAgent* agentp) } -void LLPanelGroupRoles::handleClickSubTab() +bool LLPanelGroupRoles::handleSubTabSwitch(const LLSD& data) { - // If we are already handling a transition, - // ignore this. - if (mIgnoreTransition) - { - return; - } - - mRequestedTab = (LLPanelGroupTab*) mSubTabContainer->getCurrentPanel(); + std::string panel_name = data.asString(); + + LLPanelGroupTab* activating_tab = static_cast(mSubTabContainer->getPanelByName(panel_name)); - // Make sure they aren't just clicking the same tab... - if (mRequestedTab == mCurrentTab) + if(activating_tab == mCurrentTab + || activating_tab == mRequestedTab) { - return; + return true; } - // Try to switch from the current panel to the panel the user selected. - attemptTransition(); -} - -BOOL LLPanelGroupRoles::attemptTransition() -{ - // Check if the current tab needs to be applied. + mRequestedTab = activating_tab; + std::string mesg; if (mCurrentTab && mCurrentTab->needsApply(mesg)) { @@ -235,16 +224,10 @@ BOOL LLPanelGroupRoles::attemptTransition() LLNotificationsUtil::add("PanelGroupApply", args, LLSD(), boost::bind(&LLPanelGroupRoles::handleNotifyCallback, this, _1, _2)); mHasModal = TRUE; - // We need to reselect the current tab, since it isn't finished. - if (mSubTabContainer) - { - mIgnoreTransition = TRUE; - mSubTabContainer->selectTabPanel( mCurrentTab ); - mIgnoreTransition = FALSE; - } + // Returning FALSE will block a close action from finishing until // we get a response back from the user. - return FALSE; + return false; } else { @@ -253,7 +236,7 @@ BOOL LLPanelGroupRoles::attemptTransition() { transitionToTab(); } - return TRUE; + return true; } } @@ -271,6 +254,7 @@ void LLPanelGroupRoles::transitionToTab() // This is now the current tab; mCurrentTab = mRequestedTab; mCurrentTab->activate(); + mRequestedTab = 0; } } @@ -278,6 +262,7 @@ bool LLPanelGroupRoles::handleNotifyCallback(const LLSD& notification, const LLS { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); mHasModal = FALSE; + LLPanelGroupTab* transition_tab = mRequestedTab; switch (option) { case 0: // "Apply Changes" @@ -297,26 +282,20 @@ bool LLPanelGroupRoles::handleNotifyCallback(const LLSD& notification, const LLS // Skip switching tabs. break; } - - // This panel's info successfully applied. - // Switch to the next panel. - // No break! Continue into 'Ignore Changes' which just switches tabs. - mIgnoreTransition = TRUE; - mSubTabContainer->selectTabPanel( mRequestedTab ); - mIgnoreTransition = FALSE; transitionToTab(); + mSubTabContainer->selectTabPanel( transition_tab ); + break; } case 1: // "Ignore Changes" // Switch to the requested panel without applying changes cancel(); - mIgnoreTransition = TRUE; - mSubTabContainer->selectTabPanel( mRequestedTab ); - mIgnoreTransition = FALSE; transitionToTab(); + mSubTabContainer->selectTabPanel( transition_tab ); break; case 2: // "Cancel" default: + mRequestedTab = NULL; // Do nothing. The user is canceling the action. break; } diff --git a/indra/newview/llpanelgrouproles.h b/indra/newview/llpanelgrouproles.h index 98cebe9882..a877402041 100644 --- a/indra/newview/llpanelgrouproles.h +++ b/indra/newview/llpanelgrouproles.h @@ -65,7 +65,7 @@ public: virtual BOOL isVisibleByAgent(LLAgent* agentp); - void handleClickSubTab(); + bool handleSubTabSwitch(const LLSD& data); // Checks if the current tab needs to be applied, and tries to switch to the requested tab. BOOL attemptTransition(); @@ -93,7 +93,6 @@ protected: LLPanelGroupTab* mRequestedTab; LLTabContainer* mSubTabContainer; BOOL mFirstUse; - BOOL mIgnoreTransition; std::string mDefaultNeedsApplyMesg; std::string mWantApplyMesg; -- cgit v1.2.3 From 3d4cb72f327eca1bf6b5af366e9e403392e8abc8 Mon Sep 17 00:00:00 2001 From: Alexei Arabadji Date: Mon, 17 May 2010 12:04:22 +0300 Subject: EXT-7085 FIXED Corrected visibility of "Moderator Options Separator" reviewed by Mike Antipov --HG-- branch : product-engine --- indra/newview/llparticipantlist.cpp | 1 + indra/newview/skins/default/xui/en/menu_participant_list.xml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/indra/newview/llparticipantlist.cpp b/indra/newview/llparticipantlist.cpp index daf116d255..1117ae05d7 100644 --- a/indra/newview/llparticipantlist.cpp +++ b/indra/newview/llparticipantlist.cpp @@ -655,6 +655,7 @@ LLContextMenu* LLParticipantList::LLParticipantListMenu::createMenu() bool is_sort_visible = (mParent.mAvatarList && mParent.mAvatarList->size() > 1); main_menu->setItemVisible("SortByName", is_sort_visible); main_menu->setItemVisible("SortByRecentSpeakers", is_sort_visible); + main_menu->setItemVisible("Moderator Options Separator", isGroupModerator()); main_menu->setItemVisible("Moderator Options", isGroupModerator()); main_menu->setItemVisible("View Icons Separator", mParent.mAvatarListToggleIconsConnection.connected()); main_menu->setItemVisible("View Icons", mParent.mAvatarListToggleIconsConnection.connected()); diff --git a/indra/newview/skins/default/xui/en/menu_participant_list.xml b/indra/newview/skins/default/xui/en/menu_participant_list.xml index f126431263..4ed5807808 100644 --- a/indra/newview/skins/default/xui/en/menu_participant_list.xml +++ b/indra/newview/skins/default/xui/en/menu_participant_list.xml @@ -127,7 +127,8 @@ parameter="can_mute_text" /> + layout="topleft" + name="Moderator Options Separator"/> Date: Mon, 17 May 2010 16:40:08 +0300 Subject: EXT-7363 FIXED Added missing conversion of SLURL parameters string to query parameters map. reviewed by Vadim Savchuk at https://codereview.productengine.com/secondlife/r/397/ --HG-- branch : product-engine --- indra/newview/llurldispatcher.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indra/newview/llurldispatcher.cpp b/indra/newview/llurldispatcher.cpp index a31c3a0f1b..9efa6c4108 100644 --- a/indra/newview/llurldispatcher.cpp +++ b/indra/newview/llurldispatcher.cpp @@ -151,8 +151,9 @@ bool LLURLDispatcherImpl::dispatchApp(const LLSLURL& slurl, bool trusted_browser) { llinfos << "cmd: " << slurl.getAppCmd() << " path: " << slurl.getAppPath() << " query: " << slurl.getAppQuery() << llendl; + const LLSD& query_map = LLURI::queryMap(slurl.getAppQuery()); bool handled = LLCommandDispatcher::dispatch( - slurl.getAppCmd(), slurl.getAppPath(), slurl.getAppQuery(), web, trusted_browser); + slurl.getAppCmd(), slurl.getAppPath(), query_map, web, trusted_browser); // alert if we didn't handle this secondlife:///app/ SLURL // (but still return true because it is a valid app SLURL) -- cgit v1.2.3 From bf197d4f410b686a80bd66ab7a3fcc761e8591ca Mon Sep 17 00:00:00 2001 From: Andrew Polunin Date: Mon, 17 May 2010 16:52:44 +0300 Subject: EXT-7199 FIXED (Enable dragging items from inventory view to current outfit in Outfit Editor) - HandleDragAndDrop() method was overridden in the LLPanelOutfitEdit class to handle drag and drop operations of the Clothing, Body Parts and Attachments. - In panel_outfit_edit.xml parameter allow_multi_select was set to true to allow multiple selections. - Added class LLCOFDragAndDropObserver to implement the drag and drop. - Class LLInventoryMoveFromWorldObserver renamed to LLInventoryAddItemByAssetObserver. Drag and drop reimplemented using functionality of the LLInventoryAddItemByAssetObserver. Reviewed by Neal Orman and Mike Antipov at https://codereview.productengine.com/secondlife/r/373/ --HG-- branch : product-engine --- indra/newview/llinventoryobserver.cpp | 6 +- indra/newview/llinventoryobserver.h | 4 +- indra/newview/llpaneloutfitedit.cpp | 89 +++++++++++++++++++++- indra/newview/llpaneloutfitedit.h | 9 +++ indra/newview/llviewermessage.cpp | 4 +- .../skins/default/xui/en/panel_outfit_edit.xml | 2 +- 6 files changed, 105 insertions(+), 9 deletions(-) diff --git a/indra/newview/llinventoryobserver.cpp b/indra/newview/llinventoryobserver.cpp index c24d2ee0ea..8557548887 100644 --- a/indra/newview/llinventoryobserver.cpp +++ b/indra/newview/llinventoryobserver.cpp @@ -493,7 +493,7 @@ void LLInventoryExistenceObserver::changed(U32 mask) } } -void LLInventoryMoveFromWorldObserver::changed(U32 mask) +void LLInventoryAddItemByAssetObserver::changed(U32 mask) { if(!(mask & LLInventoryObserver::ADD)) { @@ -535,7 +535,7 @@ void LLInventoryMoveFromWorldObserver::changed(U32 mask) } } -void LLInventoryMoveFromWorldObserver::watchAsset(const LLUUID& asset_id) +void LLInventoryAddItemByAssetObserver::watchAsset(const LLUUID& asset_id) { if(asset_id.notNull()) { @@ -551,7 +551,7 @@ void LLInventoryMoveFromWorldObserver::watchAsset(const LLUUID& asset_id) } } -bool LLInventoryMoveFromWorldObserver::isAssetWatched( const LLUUID& asset_id ) +bool LLInventoryAddItemByAssetObserver::isAssetWatched( const LLUUID& asset_id ) { return std::find(mWatchedAssets.begin(), mWatchedAssets.end(), asset_id) != mWatchedAssets.end(); } diff --git a/indra/newview/llinventoryobserver.h b/indra/newview/llinventoryobserver.h index 036e6ca40d..6d5a86a6fc 100644 --- a/indra/newview/llinventoryobserver.h +++ b/indra/newview/llinventoryobserver.h @@ -180,10 +180,10 @@ protected: // something useful. //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -class LLInventoryMoveFromWorldObserver : public LLInventoryObserver +class LLInventoryAddItemByAssetObserver : public LLInventoryObserver { public: - LLInventoryMoveFromWorldObserver() : mIsDirty(false) {} + LLInventoryAddItemByAssetObserver() : mIsDirty(false) {} virtual void changed(U32 mask); void watchAsset(const LLUUID& asset_id); diff --git a/indra/newview/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp index c04be85174..44832ac496 100644 --- a/indra/newview/llpaneloutfitedit.cpp +++ b/indra/newview/llpaneloutfitedit.cpp @@ -169,14 +169,48 @@ protected: S32 mBaseOutfitLastVersion; }; +class LLCOFDragAndDropObserver : public LLInventoryAddItemByAssetObserver +{ +public: + LLCOFDragAndDropObserver(LLInventoryModel* model); + + virtual ~LLCOFDragAndDropObserver(); + + virtual void done(); + +private: + LLInventoryModel* mModel; +}; + +inline LLCOFDragAndDropObserver::LLCOFDragAndDropObserver(LLInventoryModel* model): + mModel(model) +{ + if (model != NULL) + { + model->addObserver(this); + } +} + +inline LLCOFDragAndDropObserver::~LLCOFDragAndDropObserver() +{ + if (mModel != NULL && mModel->containsObserver(this)) + { + mModel->removeObserver(this); + } +} +void LLCOFDragAndDropObserver::done() +{ + LLAppearanceMgr::instance().updateAppearanceFromCOF(); +} LLPanelOutfitEdit::LLPanelOutfitEdit() : LLPanel(), mSearchFilter(NULL), mCOFWearables(NULL), mInventoryItemsPanel(NULL), - mCOFObserver(NULL) + mCOFObserver(NULL), + mCOFDragAndDropObserver(NULL) { mSavedFolderState = new LLSaveFolderState(); mSavedFolderState->setApply(FALSE); @@ -197,6 +231,7 @@ LLPanelOutfitEdit::~LLPanelOutfitEdit() delete mSavedFolderState; delete mCOFObserver; + delete mCOFDragAndDropObserver; } BOOL LLPanelOutfitEdit::postBuild() @@ -234,6 +269,8 @@ BOOL LLPanelOutfitEdit::postBuild() mInventoryItemsPanel->setSelectCallback(boost::bind(&LLPanelOutfitEdit::onInventorySelectionChange, this, _1, _2)); mInventoryItemsPanel->getRootFolder()->setReshapeCallback(boost::bind(&LLPanelOutfitEdit::onInventorySelectionChange, this, _1, _2)); + mCOFDragAndDropObserver = new LLCOFDragAndDropObserver(mInventoryItemsPanel->getModel()); + LLComboBox* type_filter = getChild("filter_wearables_combobox"); type_filter->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onTypeFilterChanged, this, _1)); type_filter->removeall(); @@ -522,6 +559,56 @@ void LLPanelOutfitEdit::update() updateVerbs(); } +BOOL LLPanelOutfitEdit::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg) +{ + if (cargo_data == NULL) + { + llwarns << "cargo_data is NULL" << llendl; + return TRUE; + } + + switch (cargo_type) + { + case DAD_BODYPART: + case DAD_CLOTHING: + case DAD_OBJECT: + case DAD_LINK: + *accept = ACCEPT_YES_MULTI; + break; + default: + *accept = ACCEPT_NO; + } + + if (drop) + { + LLInventoryItem* item = static_cast(cargo_data); + + if (LLAssetType::lookupIsAssetIDKnowable(item->getType())) + { + mCOFDragAndDropObserver->watchAsset(item->getAssetUUID()); + + /* + * Adding request to wear item. If the item is a link, then getLinkedUUID() will + * return the ID of the linked item. Otherwise it will return the item's ID. The + * second argument is used to delay the appearance update until all dragged items + * are added to optimize user experience. + */ + LLAppearanceMgr::instance().addCOFItemLink(item->getLinkedUUID(), false); + } + else + { + // if asset id is not available for the item we must wear it immediately (attachments only) + LLAppearanceMgr::instance().addCOFItemLink(item->getLinkedUUID(), true); + } + } + + return TRUE; +} + void LLPanelOutfitEdit::displayCurrentOutfit() { if (!getVisible()) diff --git a/indra/newview/llpaneloutfitedit.h b/indra/newview/llpaneloutfitedit.h index cb8283fca3..953a70785c 100644 --- a/indra/newview/llpaneloutfitedit.h +++ b/indra/newview/llpaneloutfitedit.h @@ -50,6 +50,7 @@ class LLCOFWearables; class LLTextBox; class LLInventoryCategory; class LLCOFObserver; +class LLCOFDragAndDropObserver; class LLInventoryPanel; class LLSaveFolderState; class LLFolderViewItem; @@ -114,6 +115,12 @@ public: */ bool switchPanels(LLPanel* switch_from_panel, LLPanel* switch_to_panel); + virtual BOOL handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, + EDragAndDropType cargo_type, + void* cargo_data, + EAcceptance* accept, + std::string& tooltip_msg); + private: @@ -134,6 +141,8 @@ private: LLPanel* mWearableItemsPanel; LLCOFObserver* mCOFObserver; + LLCOFDragAndDropObserver* mCOFDragAndDropObserver; + std::vector mLookItemTypes; LLCOFWearables* mCOFWearables; diff --git a/indra/newview/llviewermessage.cpp b/indra/newview/llviewermessage.cpp index 86e040692c..fb87e2d3b9 100644 --- a/indra/newview/llviewermessage.cpp +++ b/indra/newview/llviewermessage.cpp @@ -774,11 +774,11 @@ private: * We can't create it each time items are moved because "drop" event is sent separately for each * element even while multi-dragging. We have to have the only instance of the observer. See EXT-4347. */ -class LLViewerInventoryMoveFromWorldObserver : public LLInventoryMoveFromWorldObserver +class LLViewerInventoryMoveFromWorldObserver : public LLInventoryAddItemByAssetObserver { public: LLViewerInventoryMoveFromWorldObserver() - : LLInventoryMoveFromWorldObserver() + : LLInventoryAddItemByAssetObserver() , mActivePanel(NULL) { diff --git a/indra/newview/skins/default/xui/en/panel_outfit_edit.xml b/indra/newview/skins/default/xui/en/panel_outfit_edit.xml index f661d7e237..9072418329 100644 --- a/indra/newview/skins/default/xui/en/panel_outfit_edit.xml +++ b/indra/newview/skins/default/xui/en/panel_outfit_edit.xml @@ -292,7 +292,7 @@ width="311" user_resize="true"> Date: Mon, 17 May 2010 15:02:34 +0100 Subject: EXT-6964 FIXED Added back the secondlife:///app/appearance SLapp handler. This was removed inadvertantly in change 1fc92fc19c98. --- indra/newview/llappearancemgr.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index bcaba34921..be840cc348 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -55,6 +55,25 @@ char ORDER_NUMBER_SEPARATOR('@'); +// support for secondlife:///app/appearance SLapps +class LLAppearanceHandler : public LLCommandHandler +{ +public: + // requests will be throttled from a non-trusted browser + LLAppearanceHandler() : LLCommandHandler("appearance", UNTRUSTED_THROTTLE) {} + + bool handle(const LLSD& params, const LLSD& query_map, LLMediaCtrl* web) + { + // support secondlife:///app/appearance/show, but for now we just + // make all secondlife:///app/appearance SLapps behave this way + LLSideTray::getInstance()->showPanel("sidepanel_appearance", LLSD()); + return true; + } +}; + +LLAppearanceHandler gAppearanceHandler; + + LLUUID findDescendentCategoryIDByName(const LLUUID& parent_id, const std::string& name) { LLInventoryModel::cat_array_t cat_array; -- cgit v1.2.3 From 4683283a666e3c34b6df52951b96c342a44679a8 Mon Sep 17 00:00:00 2001 From: Yuri Chebotarev Date: Mon, 17 May 2010 18:36:53 +0300 Subject: EXT-7045 FIX miss one more texture setup place. Also remove redundant variable and do some refactoring. Reviwed by Vadim Savchuk at https://codereview.productengine.com/secondlife/r/391/ --HG-- branch : product-engine --- indra/newview/llpanelgroupgeneral.cpp | 14 ++------------ indra/newview/llpanelgroupgeneral.h | 1 - indra/newview/lltexturectrl.cpp | 14 ++++++++++++++ indra/newview/lltexturectrl.h | 4 +++- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/indra/newview/llpanelgroupgeneral.cpp b/indra/newview/llpanelgroupgeneral.cpp index ddd41a1791..555248e31a 100644 --- a/indra/newview/llpanelgroupgeneral.cpp +++ b/indra/newview/llpanelgroupgeneral.cpp @@ -212,7 +212,6 @@ void LLPanelGroupGeneral::setupCtrls(LLPanel* panel_group) if (mInsignia) { mInsignia->setCommitCallback(onCommitAny, this); - mDefaultIconID = mInsignia->getImageAssetID(); } mFounderName = getChild("founder_name"); @@ -656,7 +655,7 @@ void LLPanelGroupGeneral::update(LLGroupChange gc) } else { - mInsignia->setImageAssetID(mDefaultIconID); + mInsignia->setImageAssetName(mInsignia->getDefaultImageName()); } } @@ -846,16 +845,7 @@ void LLPanelGroupGeneral::reset() mInsignia->setEnabled(true); - LLPointer imagep = LLUI::getUIImage(mInsignia->getDefaultImageName()); - if(imagep) - { - LLViewerFetchedTexture* pTexture = dynamic_cast(imagep->getImage().get()); - if(pTexture) - { - LLUUID id = pTexture->getID(); - mInsignia->setImageAssetID(id); - } - } + mInsignia->setImageAssetName(mInsignia->getDefaultImageName()); { std::string empty_str = ""; diff --git a/indra/newview/llpanelgroupgeneral.h b/indra/newview/llpanelgroupgeneral.h index 6245018871..6f4fa994da 100644 --- a/indra/newview/llpanelgroupgeneral.h +++ b/indra/newview/llpanelgroupgeneral.h @@ -95,7 +95,6 @@ private: BOOL mChanged; BOOL mFirstUse; std::string mIncompleteMemberDataStr; - LLUUID mDefaultIconID; // Group information (include any updates in updateChanged) LLLineEditor *mGroupNameEditor; diff --git a/indra/newview/lltexturectrl.cpp b/indra/newview/lltexturectrl.cpp index a1b3c8dabd..6165d309c3 100644 --- a/indra/newview/lltexturectrl.cpp +++ b/indra/newview/lltexturectrl.cpp @@ -1137,6 +1137,20 @@ void LLTextureCtrl::onFloaterCommit(ETexturePickOp op) } } +void LLTextureCtrl::setImageAssetName(const std::string& name) +{ + LLPointer imagep = LLUI::getUIImage(name); + if(imagep) + { + LLViewerFetchedTexture* pTexture = dynamic_cast(imagep->getImage().get()); + if(pTexture) + { + LLUUID id = pTexture->getID(); + setImageAssetID(id); + } + } +} + void LLTextureCtrl::setImageAssetID( const LLUUID& asset_id ) { if( mImageAssetID != asset_id ) diff --git a/indra/newview/lltexturectrl.h b/indra/newview/lltexturectrl.h index 837f837430..bcd0a083f2 100644 --- a/indra/newview/lltexturectrl.h +++ b/indra/newview/lltexturectrl.h @@ -128,7 +128,7 @@ public: virtual void clear(); // Takes a UUID, wraps get/setImageAssetID - virtual void setValue(const LLSD& value ); + virtual void setValue(const LLSD& value); virtual LLSD getValue() const; // LLTextureCtrl interface @@ -142,6 +142,8 @@ public: const LLUUID& getImageItemID() { return mImageItemID; } + virtual void setImageAssetName(const std::string& name); + void setImageAssetID(const LLUUID &image_asset_id); const LLUUID& getImageAssetID() const { return mImageAssetID; } -- cgit v1.2.3 From fcea61df76ce69b438d26037df4bafc3380f129d Mon Sep 17 00:00:00 2001 From: Yuri Chebotarev Date: Mon, 17 May 2010 18:39:11 +0300 Subject: EXT-7266 FIX change default icon id tofrom object icon to id that comes with message Reviwed by Igor Borovkov at https://codereview.productengine.com/secondlife/r/371/ --HG-- branch : product-engine --- indra/newview/llchatitemscontainerctrl.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/indra/newview/llchatitemscontainerctrl.cpp b/indra/newview/llchatitemscontainerctrl.cpp index 5b6a99e793..35a244c461 100644 --- a/indra/newview/llchatitemscontainerctrl.cpp +++ b/indra/newview/llchatitemscontainerctrl.cpp @@ -326,12 +326,14 @@ void LLNearbyChatToastPanel::draw() if(icon) { icon->setDrawTooltip(mSourceType == CHAT_SOURCE_AGENT); - if(mSourceType == CHAT_SOURCE_AGENT) - icon->setValue(mFromID); + if(mSourceType == CHAT_SOURCE_OBJECT) + icon->setValue(LLSD("OBJECT_Icon")); else if(mSourceType == CHAT_SOURCE_SYSTEM) icon->setValue(LLSD("SL_Logo")); - else - icon->setValue(LLSD("OBJECT_Icon")); + else if(mSourceType == CHAT_SOURCE_AGENT) + icon->setValue(mFromID); + else if(!mFromID.isNull()) + icon->setValue(mFromID); } mIsDirty = false; } -- cgit v1.2.3 From d8b989a87b94fd851404b42094e88c0a0c213ac8 Mon Sep 17 00:00:00 2001 From: Sergei Litovchuk Date: Mon, 17 May 2010 18:57:03 +0300 Subject: EXT-7158 ADDITIONAL FIX Optimization and implementation fix - Fixed issue with filter not applied to outfit newly added while filtering is active. - Optimization of refreshing outfit list when filter is applied - list is not refreshed on every list visibility change. Reviewed by Mike Antipov at https://codereview.productengine.com/secondlife/r/394/ --HG-- branch : product-engine --- indra/newview/llinventoryitemslist.cpp | 10 +++++----- indra/newview/llinventoryitemslist.h | 8 +++++++- indra/newview/lloutfitslist.cpp | 35 +++++++++++++++++++++++++++++----- indra/newview/lloutfitslist.h | 2 +- 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/indra/newview/llinventoryitemslist.cpp b/indra/newview/llinventoryitemslist.cpp index 2d1d401cd4..9b592e79af 100644 --- a/indra/newview/llinventoryitemslist.cpp +++ b/indra/newview/llinventoryitemslist.cpp @@ -322,7 +322,7 @@ LLInventoryItemsList::Params::Params() LLInventoryItemsList::LLInventoryItemsList(const LLInventoryItemsList::Params& p) : LLFlatListViewEx(p) , mNeedsRefresh(false) -, mPrevVisibility(false) +, mForceRefresh(false) { // TODO: mCommitOnSelectionChange is set to "false" in LLFlatListView // but reset to true in all derived classes. This settings might need to @@ -356,14 +356,13 @@ boost::signals2::connection LLInventoryItemsList::setRefreshCompleteCallback(con void LLInventoryItemsList::doIdle() { - bool cur_visibility = getVisible(); - if(cur_visibility != mPrevVisibility || mNeedsRefresh) + if (!mNeedsRefresh) return; + + if (isInVisibleChain() || mForceRefresh) { refresh(); mRefreshCompleteSignal(this, LLSD()); - - mPrevVisibility = getVisible(); } } @@ -414,6 +413,7 @@ void LLInventoryItemsList::refresh() bool needs_refresh = add_limit_exceeded; setNeedsRefresh(needs_refresh); + setForceRefresh(needs_refresh); } void LLInventoryItemsList::computeDifference( diff --git a/indra/newview/llinventoryitemslist.h b/indra/newview/llinventoryitemslist.h index 60cccc0f4f..a3863b511c 100644 --- a/indra/newview/llinventoryitemslist.h +++ b/indra/newview/llinventoryitemslist.h @@ -221,6 +221,12 @@ public: bool getNeedsRefresh(){ return mNeedsRefresh; } + /** + * Sets the flag indicating that the list needs to be refreshed even if it is + * not currently visible. + */ + void setForceRefresh(bool force_refresh){ mForceRefresh = force_refresh; } + /** * Idle routine used to refresh the list regardless of the current list * visibility, unlike draw() which is called only for the visible list. @@ -259,7 +265,7 @@ private: // Will be used in refresh() to determine added and removed ids bool mNeedsRefresh; - bool mPrevVisibility; + bool mForceRefresh; commit_signal_t mRefreshCompleteSignal; }; diff --git a/indra/newview/lloutfitslist.cpp b/indra/newview/lloutfitslist.cpp index 12d5203429..43b7e15977 100644 --- a/indra/newview/lloutfitslist.cpp +++ b/indra/newview/lloutfitslist.cpp @@ -170,7 +170,7 @@ void LLOutfitsList::refreshList(const LLUUID& category_id) list->setCommitCallback(boost::bind(&LLOutfitsList::onSelectionChange, this, _1)); // Setting list refresh callback to apply filter on list change. - list->setRefreshCompleteCallback(boost::bind(&LLOutfitsList::onWearableItemsListRefresh, this, _1)); + list->setRefreshCompleteCallback(boost::bind(&LLOutfitsList::onFilteredWearableItemsListRefresh, this, _1)); // Fetch the new outfit contents. cat->fetch(); @@ -178,6 +178,21 @@ void LLOutfitsList::refreshList(const LLUUID& category_id) // Refresh the list of outfit items after fetch(). // Further list updates will be triggered by the category observer. list->updateList(cat_id); + + // If filter is currently applied we store the initial tab state and + // open it to show matched items if any. + if (!mFilterSubString.empty()) + { + tab->notifyChildren(LLSD().with("action","store_state")); + tab->setDisplayChildren(true); + + // Setting mForceRefresh flag will make the list refresh its contents + // even if it is not currently visible. This is required to apply the + // filter to the newly added list. + list->setForceRefresh(true); + + list->setFilterSubString(mFilterSubString); + } } // Handle removed tabs. @@ -327,7 +342,7 @@ void LLOutfitsList::changeOutfitSelection(LLWearableItemsList* list, const LLUUI mSelectedOutfitUUID = category_id; } -void LLOutfitsList::onWearableItemsListRefresh(LLUICtrl* ctrl) +void LLOutfitsList::onFilteredWearableItemsListRefresh(LLUICtrl* ctrl) { if (!ctrl || mFilterSubString.empty()) return; @@ -338,7 +353,7 @@ void LLOutfitsList::onWearableItemsListRefresh(LLUICtrl* ctrl) iter != iter_end; ++iter) { LLAccordionCtrlTab* tab = iter->second; - if (tab) continue; + if (!tab) continue; LLWearableItemsList* list = dynamic_cast(tab->getAccordionView()); if (list != ctrl) continue; @@ -395,8 +410,6 @@ void LLOutfitsList::applyFilter(const std::string& new_filter_substring) if (!new_filter_substring.empty()) { - tab->setDisplayChildren(true); - std::string title = tab->getTitle(); LLStringUtil::toUpper(title); @@ -413,6 +426,18 @@ void LLOutfitsList::applyFilter(const std::string& new_filter_substring) { tab->setTitle(tab->getTitle(), cur_filter); } + + if (tab->getVisible()) + { + // Open tab if it has passed the filter. + tab->setDisplayChildren(true); + } + else + { + // Set force refresh flag to refresh not visible list + // when some changes occur in it. + list->setForceRefresh(true); + } } else { diff --git a/indra/newview/lloutfitslist.h b/indra/newview/lloutfitslist.h index bcb393b12a..8eaa39e6f1 100644 --- a/indra/newview/lloutfitslist.h +++ b/indra/newview/lloutfitslist.h @@ -98,7 +98,7 @@ private: * Called upon list refresh event to update tab visibility depending on * the results of applying filter to the title and list items of the tab. */ - void onWearableItemsListRefresh(LLUICtrl* ctrl); + void onFilteredWearableItemsListRefresh(LLUICtrl* ctrl); /** * Highlights filtered items and hides tabs which haven't passed filter. -- cgit v1.2.3 From 05a44d78ff6f83388945999c4f84954a0a900879 Mon Sep 17 00:00:00 2001 From: Loren Shih Date: Mon, 17 May 2010 12:13:02 -0400 Subject: EXT-7353 FIXED short cuts (links) to textures should show up in the texture picker Added flag for controlling whether links show up in filtered lists. --- indra/newview/llinventoryfilter.cpp | 50 +++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/indra/newview/llinventoryfilter.cpp b/indra/newview/llinventoryfilter.cpp index 901a570487..c90919e8fd 100644 --- a/indra/newview/llinventoryfilter.cpp +++ b/indra/newview/llinventoryfilter.cpp @@ -41,6 +41,7 @@ #include "llinventorymodelbackgroundfetch.h" #include "llviewercontrol.h" #include "llfolderview.h" +#include "llinventorybridge.h" // linden library includes #include "lltrans.h" @@ -54,7 +55,8 @@ LLInventoryFilter::FilterOps::FilterOps() : mShowFolderState(SHOW_NON_EMPTY_FOLDERS), mPermissions(PERM_NONE), mFilterTypes(FILTERTYPE_OBJECT), - mFilterUUID(LLUUID::null) + mFilterUUID(LLUUID::null), + mIncludeLinks(TRUE) { } @@ -97,23 +99,23 @@ BOOL LLInventoryFilter::check(const LLFolderViewItem* item) return TRUE; } - const LLFolderViewEventListener* listener = item->getListener(); mSubStringMatchOffset = mFilterSubString.size() ? item->getSearchableLabel().find(mFilterSubString) : std::string::npos; const BOOL passed_filtertype = checkAgainstFilterType(item); - const BOOL passed = passed_filtertype && - (mFilterSubString.size() == 0 || mSubStringMatchOffset != std::string::npos) && - ((listener->getPermissionMask() & mFilterOps.mPermissions) == mFilterOps.mPermissions); + const BOOL passed_permissions = checkAgainstPermissions(item); + const BOOL passed = (passed_filtertype && + passed_permissions && + (mFilterSubString.size() == 0 || mSubStringMatchOffset != std::string::npos)); return passed; } -BOOL LLInventoryFilter::checkAgainstFilterType(const LLFolderViewItem* item) +BOOL LLInventoryFilter::checkAgainstFilterType(const LLFolderViewItem* item) const { const LLFolderViewEventListener* listener = item->getListener(); if (!listener) return FALSE; - const LLInventoryType::EType object_type = listener->getInventoryType(); + LLInventoryType::EType object_type = listener->getInventoryType(); const LLUUID object_id = listener->getUUID(); const LLInventoryObject *object = gInventory.getObject(object_id); @@ -128,7 +130,9 @@ BOOL LLInventoryFilter::checkAgainstFilterType(const LLFolderViewItem* item) if (object_type == LLInventoryType::IT_NONE) { if (object && object->getIsLinkType()) + { return FALSE; + } } else if ((1LL << object_type & mFilterOps.mFilterObjectTypes) == U64(0)) { @@ -203,6 +207,22 @@ BOOL LLInventoryFilter::checkAgainstFilterType(const LLFolderViewItem* item) return TRUE; } +BOOL LLInventoryFilter::checkAgainstPermissions(const LLFolderViewItem* item) const +{ + const LLFolderViewEventListener* listener = item->getListener(); + if (!listener) return FALSE; + + PermissionMask perm = listener->getPermissionMask(); + const LLInvFVBridge *bridge = dynamic_cast(item->getListener()); + if (bridge && bridge->isLink()) + { + const LLUUID& linked_uuid = gInventory.getLinkedItemID(bridge->getUUID()); + const LLViewerInventoryItem *linked_item = gInventory.getItem(linked_uuid); + if (linked_item) + perm = linked_item->getPermissionMask(); + } + return (perm & mFilterOps.mPermissions) == mFilterOps.mPermissions; +} const std::string& LLInventoryFilter::getFilterSubString(BOOL trim) const { @@ -451,6 +471,18 @@ void LLInventoryFilter::setHoursAgo(U32 hours) mFilterOps.mFilterTypes |= FILTERTYPE_DATE; } +void LLInventoryFilter::setIncludeLinks(BOOL include_links) +{ + if (mFilterOps.mIncludeLinks != include_links) + { + if (!mFilterOps.mIncludeLinks) + setModified(FILTER_LESS_RESTRICTIVE); + else + setModified(FILTER_MORE_RESTRICTIVE); + } + mFilterOps.mIncludeLinks = include_links; +} + void LLInventoryFilter::setShowFolderState(EFolderShow state) { if (mFilterOps.mShowFolderState != state) @@ -826,6 +858,10 @@ U32 LLInventoryFilter::getHoursAgo() const { return mFilterOps.mHoursAgo; } +BOOL LLInventoryFilter::getIncludeLinks() const +{ + return mFilterOps.mIncludeLinks; +} LLInventoryFilter::EFolderShow LLInventoryFilter::getShowFolderState() const { return mFilterOps.mShowFolderState; -- cgit v1.2.3 From 01eca6f45ee3abf599e669cdf90f4316899f8a22 Mon Sep 17 00:00:00 2001 From: Eli Linden Date: Mon, 17 May 2010 16:33:56 -0700 Subject: ND-46735 WIP JA linguistic --- indra/newview/skins/default/xui/ja/strings.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/indra/newview/skins/default/xui/ja/strings.xml b/indra/newview/skins/default/xui/ja/strings.xml index 560465f808..261c49931e 100644 --- a/indra/newview/skins/default/xui/ja/strings.xml +++ b/indra/newview/skins/default/xui/ja/strings.xml @@ -1630,25 +1630,25 @@ が渡しました
- You decline + 拒否: - from + 送信元: 合計 - は購入しました + 購入: - があなたに支払いました + あなたに支払い: paid into - bought pass to + 入場許可を購入: がイベント用の費用を支払いました -- cgit v1.2.3 From f8e01d512af0a05cf5006bc974f1725c7151f785 Mon Sep 17 00:00:00 2001 From: Loren Shih Date: Mon, 17 May 2010 20:40:46 -0400 Subject: EXT-7384 FIXED Aesthetic changes to debug textures floater Texture floater now scrolls appropriately. --- .../default/xui/en/floater_avatar_textures.xml | 47 ++++++++++++++-------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/indra/newview/skins/default/xui/en/floater_avatar_textures.xml b/indra/newview/skins/default/xui/en/floater_avatar_textures.xml index e30e958543..6690e9748a 100644 --- a/indra/newview/skins/default/xui/en/floater_avatar_textures.xml +++ b/indra/newview/skins/default/xui/en/floater_avatar_textures.xml @@ -1,16 +1,39 @@ + width="1240" + can_resize="true"> INVALID AVATAR + + + Baked Textures @@ -43,27 +66,17 @@ top_delta="0" width="150" /> - + width="1230"> + Date: Mon, 17 May 2010 20:41:30 -0400 Subject: EXT-7385 FIXED Show load and discard level for loading textures More debugging information is now shown for loading textures --- indra/newview/lltexturectrl.cpp | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/indra/newview/lltexturectrl.cpp b/indra/newview/lltexturectrl.cpp index a1b3c8dabd..f6479bd004 100644 --- a/indra/newview/lltexturectrl.cpp +++ b/indra/newview/lltexturectrl.cpp @@ -1248,25 +1248,49 @@ void LLTextureCtrl::draw() mTentativeLabel->setVisible( !mTexturep.isNull() && getTentative() ); - // Show "Loading..." string on the top left corner while this texture is loading. // Using the discard level, do not show the string if the texture is almost but not // fully loaded. - if ( mTexturep.notNull() && - (!mTexturep->isFullyLoaded()) && - (mShowLoadingPlaceholder == TRUE) && - (mTexturep->getDiscardLevel() != 1) && - (mTexturep->getDiscardLevel() != 0)) + if (mTexturep.notNull() && + (!mTexturep->isFullyLoaded()) && + (mShowLoadingPlaceholder == TRUE) && + (mTexturep->getDiscardLevel() != 1) && + (mTexturep->getDiscardLevel() != 0)) { + U32 v_offset = 25; LLFontGL* font = LLFontGL::getFontSansSerif(); font->renderUTF8( - mLoadingPlaceholderString, 0, + mLoadingPlaceholderString, + 0, llfloor(interior.mLeft+3), - llfloor(interior.mTop-25), + llfloor(interior.mTop-v_offset), LLColor4::white, LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::DROP_SHADOW); + + // Show more detailed information if this agent is god. + if (gAgent.isGodlike()) + { + LLFontGL* font = LLFontGL::getFontSansSerif(); + std::string tdesc; + // Show what % the texture has loaded (0 to 100%, 100 is highest), and what level of detail (5 to 0, 0 is best). + + v_offset += 12; + tdesc = llformat(" PK : %d%%", U32(mTexturep->getDownloadProgress()*100.0)); + font->renderUTF8(tdesc, 0, llfloor(interior.mLeft+3), llfloor(interior.mTop-v_offset), + LLColor4::white, LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::DROP_SHADOW); + + v_offset += 12; + tdesc = llformat(" LVL: %d", mTexturep->getDiscardLevel()); + font->renderUTF8(tdesc, 0, llfloor(interior.mLeft+3), llfloor(interior.mTop-v_offset), + LLColor4::white, LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::DROP_SHADOW); + + v_offset += 12; + tdesc = llformat(" ID : %s...", (mImageAssetID.asString().substr(0,10)).c_str()); + font->renderUTF8(tdesc, 0, llfloor(interior.mLeft+3), llfloor(interior.mTop-v_offset), + LLColor4::white, LLFontGL::LEFT, LLFontGL::BASELINE, LLFontGL::DROP_SHADOW); + } } LLUICtrl::draw(); -- cgit v1.2.3 From e9d3fcbbb9c99ba2711980f4d70dc76ed05fb65c Mon Sep 17 00:00:00 2001 From: Alexei Arabadji Date: Tue, 18 May 2010 11:47:16 +0300 Subject: EXT-7365 FIXED Added alert toast panel to GLOBAL group of exclude list of transient floater manager(the same as sidebar); reviewed by Mike Antipov at https://codereview.productengine.com/secondlife/r/396/ --HG-- branch : product-engine --- indra/newview/lltoastalertpanel.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/indra/newview/lltoastalertpanel.cpp b/indra/newview/lltoastalertpanel.cpp index 2b529a4e50..6c6eda2e68 100644 --- a/indra/newview/lltoastalertpanel.cpp +++ b/indra/newview/lltoastalertpanel.cpp @@ -50,6 +50,7 @@ #include "llnotifications.h" #include "llfunctorregistry.h" #include "llrootview.h" +#include "lltransientfloatermgr.h" #include "llviewercontrol.h" // for gSavedSettings const S32 MAX_ALLOWED_MSG_WIDTH = 400; @@ -324,6 +325,9 @@ LLToastAlertPanel::LLToastAlertPanel( LLNotificationPtr notification, bool modal mDefaultBtnTimer.start(); mDefaultBtnTimer.setTimerExpirySec(DEFAULT_BUTTON_DELAY); } + + LLTransientFloaterMgr::instance().addControlView( + LLTransientFloaterMgr::GLOBAL, this); } bool LLToastAlertPanel::setCheckBox( const std::string& check_title, const std::string& check_control ) @@ -377,6 +381,8 @@ void LLToastAlertPanel::setVisible( BOOL visible ) LLToastAlertPanel::~LLToastAlertPanel() { + LLTransientFloaterMgr::instance().removeControlView( + LLTransientFloaterMgr::GLOBAL, this); } BOOL LLToastAlertPanel::hasTitleBar() const -- cgit v1.2.3 From 8ea6cc7dc235184267f265234228981d97e835f9 Mon Sep 17 00:00:00 2001 From: Andrew Polunin Date: Tue, 18 May 2010 14:15:52 +0300 Subject: EXT-7362 FIXED (World Map - make teleport button green) Default value for button's attribute 'image_unselected' was overriden: image_unselected=\"PushButton_On\". Reviewed by Mike Antipov at https://codereview.productengine.com/secondlife/r/399/ --HG-- branch : product-engine --- indra/newview/skins/default/xui/en/floater_world_map.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/newview/skins/default/xui/en/floater_world_map.xml b/indra/newview/skins/default/xui/en/floater_world_map.xml index 233ab2c1b2..fa5a0b77f9 100644 --- a/indra/newview/skins/default/xui/en/floater_world_map.xml +++ b/indra/newview/skins/default/xui/en/floater_world_map.xml @@ -538,6 +538,7 @@ + + + + Date: Wed, 19 May 2010 15:26:05 +0300 Subject: EXT-7320 FIXED Moved date time stamp template to localized xml. reviewed by Mike Antipov at https://codereview.productengine.com/secondlife/r/406/ --HG-- branch : product-engine --- indra/newview/llfloaterland.cpp | 7 +++++-- indra/newview/skins/default/xui/en/floater_about_land.xml | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/indra/newview/llfloaterland.cpp b/indra/newview/llfloaterland.cpp index 256796aa80..abdb55ec17 100644 --- a/indra/newview/llfloaterland.cpp +++ b/indra/newview/llfloaterland.cpp @@ -646,9 +646,12 @@ void LLPanelLandGeneral::refresh() } // Display claim date - // *TODO:Localize (Time format may need Translating) time_t claim_date = parcel->getClaimDate(); - mTextClaimDate->setText(formatted_time(claim_date)); + std::string claim_date_str = getString("time_stamp_template"); + LLSD substitution; + substitution["datetime"] = (S32) claim_date; + LLStringUtil::format (claim_date_str, substitution); + mTextClaimDate->setText(claim_date_str); mTextClaimDate->setEnabled(is_leased); BOOL enable_auction = (gAgent.getGodLevel() >= GOD_LIAISON) diff --git a/indra/newview/skins/default/xui/en/floater_about_land.xml b/indra/newview/skins/default/xui/en/floater_about_land.xml index 40c6b14a4a..20e7c28db0 100644 --- a/indra/newview/skins/default/xui/en/floater_about_land.xml +++ b/indra/newview/skins/default/xui/en/floater_about_land.xml @@ -108,6 +108,9 @@ name="no_selection_text"> No parcel selected. + + [wkday,datetime,local] [mth,datetime,local] [day,datetime,local] [hour,datetime,local]:[min,datetime,local]:[second,datetime,local] [year,datetime,local] + Date: Wed, 19 May 2010 15:32:25 +0300 Subject: EXT-7104 WIP Implemented reaction on Sidebar button click Implementation details: * Added new setting SidebarWithButtonsVisibility * Sidebar bottom tray button changes its value on click (toggle state - value is TRUE) * SideTray listens changes of this setting's value and change its and its buttons visibility * expand/collapse Sidebar state is stored between changes of the setting. Reviewed by Yuri Chebotarev at https://codereview.productengine.com/secondlife/r/403/ --HG-- branch : product-engine --- indra/newview/app_settings/settings.xml | 11 ++++ indra/newview/llsidetray.cpp | 59 ++++++++++++++++++++-- indra/newview/llsidetray.h | 10 ++++ .../skins/default/xui/en/panel_bottomtray.xml | 5 +- 4 files changed, 76 insertions(+), 9 deletions(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index cae4a14eed..7156af57ec 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -8682,6 +8682,17 @@ Value 0 + SidebarWithButtonsVisibility + + Comment + Sets visibility of sidebar with its tabs' buttons + Persist + 1 + Type + Boolean + Value + 1 + SkinCurrent Comment diff --git a/indra/newview/llsidetray.cpp b/indra/newview/llsidetray.cpp index 3ec1855484..9159f42968 100644 --- a/indra/newview/llsidetray.cpp +++ b/indra/newview/llsidetray.cpp @@ -66,6 +66,21 @@ static const std::string TAB_PANEL_CAPTION_TITLE_BOX = "sidetray_tab_title"; LLSideTray* LLSideTray::sInstance = 0; +/** + * Updates visibility of sidetray tabs buttons according to "SidebarWithButtonsVisibility" setting + * + * @param force_set_visible if true method ignores setting value and set buttons visible. + */ +static void update_tabs_buttons_visibility(bool force_set_visible = false) +{ + LLView* side_bar_tabs = gViewerWindow->getRootView()->getChildView("side_bar_tabs"); + if (side_bar_tabs) + { + BOOL visible = LLUI::sSettingGroups["config"]->getBOOL("SidebarWithButtonsVisibility"); + side_bar_tabs->setVisible(force_set_visible || visible); + } +} + LLSideTray* LLSideTray::getInstance() { if (!sInstance) @@ -258,6 +273,8 @@ LLSideTray::LLSideTray(Params& params) p.name = "buttons_panel"; p.mouse_opaque = false; mButtonsPanel = LLUICtrlFactory::create(p); + + initControlSettings(); } @@ -547,6 +564,7 @@ void LLSideTray::collapseSideBar() reflectCollapseChange(); setFocus( FALSE ); + update_tabs_buttons_visibility(); } void LLSideTray::expandSideBar() @@ -572,6 +590,7 @@ void LLSideTray::expandSideBar() btn->setImageOverlay( mActiveTab->mImageSelected ); } + update_tabs_buttons_visibility(true); } void LLSideTray::highlightFocused() @@ -638,6 +657,9 @@ LLPanel* LLSideTray::showPanel (const std::string& panel_name, const LLSD& para { panel->onOpen(params); } + + update_tabs_buttons_visibility(true); + return panel; } } @@ -720,11 +742,6 @@ bool LLSideTray::isPanelActive(const std::string& panel_name) return (panel->getName() == panel_name); } - -// *TODO: Eliminate magic constants. -static const S32 fake_offset = 132; -static const S32 fake_top_offset = 18; - void LLSideTray::updateSidetrayVisibility() { // set visibility of parent container based on collapsed state @@ -734,3 +751,35 @@ void LLSideTray::updateSidetrayVisibility() } } +void LLSideTray::initControlSettings() +{ + // set listeners to process runtime setting changes + LLUI::sSettingGroups["config"]->getControl("SidebarWithButtonsVisibility")->getSignal()->connect(boost::bind(&LLSideTray::toggleSidetrayAndTabButtonsVisibility, this, _2)); + + // update visibility according to current value + toggleSidetrayAndTabButtonsVisibility(LLUI::sSettingGroups["config"]->getBOOL("SidebarWithButtonsVisibility")); +} + +// sidebar visibility is implemented via its expanding/collapsing +void LLSideTray::toggleSidetrayAndTabButtonsVisibility(const LLSD::Boolean& new_visibility) +{ + // If new_visibility==FALSE it gets invisible but still can be expanded in other ways (Ctrl+I to see My Inventory) + + // store collapsed state to restore it properly on next call + static bool was_collapsed = false; + + if (!new_visibility && !mCollapsed) + { + collapseSideBar(); + was_collapsed = true; + } + // should be visible: expand only if it was expanded when has been collapsed on previous call + else if (new_visibility && was_collapsed) + { + if (mCollapsed) expandSideBar(); + was_collapsed = false; + } + + update_tabs_buttons_visibility(new_visibility); +} + diff --git a/indra/newview/llsidetray.h b/indra/newview/llsidetray.h index e8fdee9430..ed6b376d5c 100644 --- a/indra/newview/llsidetray.h +++ b/indra/newview/llsidetray.h @@ -179,6 +179,16 @@ private: LLSideTray::getInstance()->setEnabled(FALSE); } + /** + * Initializes listener of SidebarWithButtonsVisibility setting and updates state according to it. + */ + void initControlSettings(); + + /** + * Updates Sidebar and its Tab Buttons visibility according to passed value. + */ + void toggleSidetrayAndTabButtonsVisibility(const LLSD::Boolean& new_visibility); + private: LLPanel* mButtonsPanel; diff --git a/indra/newview/skins/default/xui/en/panel_bottomtray.xml b/indra/newview/skins/default/xui/en/panel_bottomtray.xml index dd813f5813..2ba7bef502 100644 --- a/indra/newview/skins/default/xui/en/panel_bottomtray.xml +++ b/indra/newview/skins/default/xui/en/panel_bottomtray.xml @@ -228,7 +228,7 @@ Disabled for now. --> Date: Wed, 19 May 2010 17:55:58 +0300 Subject: EXT-7157 FIXED Implemented outfit items sorting in "My Outfits" tab. - Moved wearable item name comparator from llcofwearables to llwearableitemslist. - Added wearable item type comparator. - Added item type and description getters to wearable item base class LLPanelInventoryListItemBase. Reviewed by Mike Antipov at https://codereview.productengine.com/secondlife/r/407/ --HG-- branch : product-engine --- indra/llui/llflatlistview.cpp | 2 +- indra/newview/llcofwearables.cpp | 12 ----- indra/newview/llcofwearables.h | 57 +++------------------- indra/newview/llinventoryitemslist.h | 9 ++++ indra/newview/llwearableitemslist.cpp | 82 ++++++++++++++++++++++++++++++- indra/newview/llwearableitemslist.h | 92 +++++++++++++++++++++++++++++++++++ 6 files changed, 190 insertions(+), 64 deletions(-) diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp index 6bd16c9ee6..2433c14315 100644 --- a/indra/llui/llflatlistview.cpp +++ b/indra/llui/llflatlistview.cpp @@ -1285,7 +1285,7 @@ void LLFlatListViewEx::filterItems() } } - rearrangeItems(); + sort(); notifyParentItemsRectChanged(); } diff --git a/indra/newview/llcofwearables.cpp b/indra/newview/llcofwearables.cpp index 8d4430a9ea..7c4ceb3458 100644 --- a/indra/newview/llcofwearables.cpp +++ b/indra/newview/llcofwearables.cpp @@ -49,18 +49,6 @@ const LLSD REARRANGE = LLSD().with("rearrange", LLSD()); static const LLWearableItemNameComparator WEARABLE_NAME_COMPARATOR; -bool LLWearableItemNameComparator::doCompare(const LLPanelWearableListItem* wearable_item1, const LLPanelWearableListItem* wearable_item2) const -{ - std::string name1 = wearable_item1->getItemName(); - std::string name2 = wearable_item2->getItemName(); - - LLStringUtil::toUpper(name1); - LLStringUtil::toUpper(name2); - - return name1 < name2; -} - - LLCOFWearables::LLCOFWearables() : LLPanel(), mAttachments(NULL), mClothing(NULL), diff --git a/indra/newview/llcofwearables.h b/indra/newview/llcofwearables.h index 612bb103d2..583ee96247 100644 --- a/indra/newview/llcofwearables.h +++ b/indra/newview/llcofwearables.h @@ -33,59 +33,16 @@ #ifndef LL_LLCOFWEARABLES_H #define LL_LLCOFWEARABLES_H +// llui +#include "llflatlistview.h" #include "llpanel.h" -#include "llinventorymodel.h" -#include "llappearancemgr.h" -#include "llwearableitemslist.h" - -class LLFlatListView; - - -/** Abstract comparator of wearable list items */ -class LLWearableListItemComparator : public LLFlatListView::ItemComparator -{ - LOG_CLASS(LLWearableListItemComparator); - -public: - LLWearableListItemComparator() {}; - virtual ~LLWearableListItemComparator() {}; - - virtual bool compare(const LLPanel* item1, const LLPanel* item2) const - { - const LLPanelWearableListItem* wearable_item1 = dynamic_cast(item1); - const LLPanelWearableListItem* wearable_item2 = dynamic_cast(item2); - - if (!wearable_item1 || !wearable_item2) - { - llwarning("item1 and item2 cannot be null", 0); - return true; - } - - return doCompare(wearable_item1, wearable_item2); - } -protected: - - /** - * Returns true if wearable_item1 < wearable_item2, false otherwise - * Implement this method in your particular comparator. - */ - virtual bool doCompare(const LLPanelWearableListItem* wearable_item1, const LLPanelWearableListItem* wearable_item2) const = 0; -}; - - -class LLWearableItemNameComparator : public LLWearableListItemComparator -{ - LOG_CLASS(LLWearableItemNameComparator); - -public: - LLWearableItemNameComparator() {}; - virtual ~LLWearableItemNameComparator() {}; - -protected: - virtual bool doCompare(const LLPanelWearableListItem* wearable_item1, const LLPanelWearableListItem* wearable_item2) const; -}; +#include "llappearancemgr.h" +#include "llinventorymodel.h" +class LLPanelClothingListItem; +class LLPanelBodyPartsListItem; +class LLPanelDeletableWearableListItem; /** * Adaptor between LLAccordionCtrlTab and LLFlatListView to facilitate communication between them diff --git a/indra/newview/llinventoryitemslist.h b/indra/newview/llinventoryitemslist.h index a3863b511c..807952948b 100644 --- a/indra/newview/llinventoryitemslist.h +++ b/indra/newview/llinventoryitemslist.h @@ -124,6 +124,15 @@ public: /** Get the name of a corresponding inventory item */ const std::string& getItemName() const { return mItem->getName(); } + /** Get the asset type of a corresponding inventory item */ + LLAssetType::EType getType() const { return mItem->getType(); } + + /** Get the wearable type of a corresponding inventory item */ + LLWearableType::EType getWearableType() const { return mItem->getWearableType(); } + + /** Get the description of a corresponding inventory item */ + const std::string& getDescription() const { return mItem->getDescription(); } + virtual ~LLPanelInventoryListItemBase(){} protected: diff --git a/indra/newview/llwearableitemslist.cpp b/indra/newview/llwearableitemslist.cpp index bac66d966a..b209dfecce 100644 --- a/indra/newview/llwearableitemslist.cpp +++ b/indra/newview/llwearableitemslist.cpp @@ -296,6 +296,84 @@ std::string LLPanelDummyClothingListItem::wearableTypeToString(LLWearableType::E ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// +/*virtual*/ +bool LLWearableItemNameComparator::doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const +{ + std::string name1 = wearable_item1->getItemName(); + std::string name2 = wearable_item2->getItemName(); + + LLStringUtil::toUpper(name1); + LLStringUtil::toUpper(name2); + + return name1 < name2; +} + +/*virtual*/ +bool LLWearableItemTypeNameComparator::doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const +{ + const LLAssetType::EType item_type1 = wearable_item1->getType(); + const LLAssetType::EType item_type2 = wearable_item2->getType(); + + LLWearableItemTypeNameComparator::ETypeListOrder item_type_order1 = getTypeListOrder(item_type1); + LLWearableItemTypeNameComparator::ETypeListOrder item_type_order2 = getTypeListOrder(item_type2); + + if (item_type_order1 != item_type_order2) + { + // If items are of different asset types we can compare them + // by types order in the list. + return item_type_order1 < item_type_order2; + } + + if (item_type_order1 & TLO_NOT_CLOTHING) + { + // If both items are of the same asset type except AT_CLOTHING + // we can compare them by name. + return LLWearableItemNameComparator::doCompare(wearable_item1, wearable_item2); + } + + const LLWearableType::EType item_wearable_type1 = wearable_item1->getWearableType(); + const LLWearableType::EType item_wearable_type2 = wearable_item2->getWearableType(); + + if (item_wearable_type1 != item_wearable_type2) + { + // If items are of different clothing types they are compared + // by clothing types order determined in LLWearableType::EType. + return item_wearable_type1 < item_wearable_type2; + } + else + { + // If both items are of the same clothing type they are compared + // by description and place in reverse order i.e. outer layer item + // on top. + return wearable_item1->getDescription() > wearable_item2->getDescription(); + } +} + +// static +LLWearableItemTypeNameComparator::ETypeListOrder LLWearableItemTypeNameComparator::getTypeListOrder(LLAssetType::EType item_type) +{ + switch (item_type) + { + case LLAssetType::AT_OBJECT: + return TLO_ATTACHMENT; + + case LLAssetType::AT_CLOTHING: + return TLO_CLOTHING; + + case LLAssetType::AT_BODYPART: + return TLO_BODYPART; + + default: + return TLO_UNKNOWN; + } +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +static const LLWearableItemTypeNameComparator WEARABLE_TYPE_NAME_COMPARATOR; + static const LLDefaultChildRegistry::Register r("wearable_items_list"); LLWearableItemsList::Params::Params() @@ -303,7 +381,9 @@ LLWearableItemsList::Params::Params() LLWearableItemsList::LLWearableItemsList(const LLWearableItemsList::Params& p) : LLInventoryItemsList(p) -{} +{ + setComparator(&WEARABLE_TYPE_NAME_COMPARATOR); +} // virtual LLWearableItemsList::~LLWearableItemsList() diff --git a/indra/newview/llwearableitemslist.h b/indra/newview/llwearableitemslist.h index 5e3202c687..2cab5a07a2 100644 --- a/indra/newview/llwearableitemslist.h +++ b/indra/newview/llwearableitemslist.h @@ -171,6 +171,98 @@ private: LLWearableType::EType mWearableType; }; +/** + * @class LLWearableListItemComparator + * + * Abstract comparator of wearable list items. + */ +class LLWearableListItemComparator : public LLFlatListView::ItemComparator +{ + LOG_CLASS(LLWearableListItemComparator); + +public: + LLWearableListItemComparator() {}; + virtual ~LLWearableListItemComparator() {}; + + virtual bool compare(const LLPanel* item1, const LLPanel* item2) const + { + const LLPanelInventoryListItemBase* wearable_item1 = dynamic_cast(item1); + const LLPanelInventoryListItemBase* wearable_item2 = dynamic_cast(item2); + + if (!wearable_item1 || !wearable_item2) + { + llwarning("item1 and item2 cannot be null", 0); + return true; + } + + return doCompare(wearable_item1, wearable_item2); + } + +protected: + + /** + * Returns true if wearable_item1 < wearable_item2, false otherwise + * Implement this method in your particular comparator. + */ + virtual bool doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const = 0; +}; + +/** + * @class LLWearableItemNameComparator + * + * Comparator for sorting wearable list items by name. + */ +class LLWearableItemNameComparator : public LLWearableListItemComparator +{ + LOG_CLASS(LLWearableItemNameComparator); + +public: + LLWearableItemNameComparator() {}; + virtual ~LLWearableItemNameComparator() {}; + +protected: + /*virtual*/ bool doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const; +}; + +/** + * @class LLWearableItemTypeNameComparator + * + * Comparator for sorting wearable list items by type and name. + */ +class LLWearableItemTypeNameComparator : public LLWearableItemNameComparator +{ + LOG_CLASS(LLWearableItemTypeNameComparator); + +public: + LLWearableItemTypeNameComparator() {}; + virtual ~LLWearableItemTypeNameComparator() {}; + +protected: + /** + * Returns "true" if wearable_item1 is placed before wearable_item2 sorted by the following: + * - Attachments (abc order) + * - Clothing + * - by type (types order determined in LLWearableType::EType) + * - outer layer on top + * - Body Parts (abc order), + * "false" otherwise. + */ + /*virtual*/ bool doCompare(const LLPanelInventoryListItemBase* wearable_item1, const LLPanelInventoryListItemBase* wearable_item2) const; + +private: + enum ETypeListOrder + { + TLO_ATTACHMENT = 0x01, + TLO_CLOTHING = 0x02, + TLO_BODYPART = 0x04, + TLO_UNKNOWN = 0x08, + + TLO_NOT_CLOTHING = TLO_ATTACHMENT | TLO_BODYPART | TLO_UNKNOWN + }; + + static LLWearableItemTypeNameComparator::ETypeListOrder getTypeListOrder(LLAssetType::EType item_type); +}; + /** * @class LLWearableItemsList * -- cgit v1.2.3 From 46fc55fca546ee8ac0db8cf69b136bc3e344eed1 Mon Sep 17 00:00:00 2001 From: Nyx Linden Date: Wed, 19 May 2010 12:16:20 -0400 Subject: EXT-7208 FIX add color / tint to tattoos Adds a color swatch to tattoo wearable that allows users to color their tattoos. Defaults to white so should not affect existing tattoo wearables. Code reviewed by Vir. --- indra/newview/character/avatar_lad.xml | 264 +++++++++++++++++++++ indra/newview/llpaneleditwearable.cpp | 3 +- indra/newview/llvoavatar.cpp | 12 +- .../skins/default/xui/en/panel_edit_tattoo.xml | 14 ++ 4 files changed, 290 insertions(+), 3 deletions(-) diff --git a/indra/newview/character/avatar_lad.xml b/indra/newview/character/avatar_lad.xml index 448e20b382..5efd997ab7 100644 --- a/indra/newview/character/avatar_lad.xml +++ b/indra/newview/character/avatar_lad.xml @@ -6623,6 +6623,60 @@ render_pass="bump"> name="head_tattoo"> + + + + + + + + + + + + + + + + + + + + + + + + @@ -6745,6 +6799,61 @@ render_pass="bump"> name="upper_tattoo"> + + + + + + + + + + + + + + + + + + + + + + + + + @@ -7942,6 +8051,61 @@ render_pass="bump"> name="lower_tattoo"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/indra/newview/llpaneleditwearable.cpp b/indra/newview/llpaneleditwearable.cpp index 5ad68ea4db..f8dbc91036 100644 --- a/indra/newview/llpaneleditwearable.cpp +++ b/indra/newview/llpaneleditwearable.cpp @@ -227,7 +227,7 @@ LLEditWearableDictionary::Wearables::Wearables() addEntry(LLWearableType::WT_UNDERPANTS, new WearableEntry(LLWearableType::WT_UNDERPANTS,"edit_underpants_title","underpants_desc_text",1,1,1, TEX_LOWER_UNDERPANTS, TEX_LOWER_UNDERPANTS, SUBPART_UNDERPANTS)); addEntry(LLWearableType::WT_SKIRT, new WearableEntry(LLWearableType::WT_SKIRT,"edit_skirt_title","skirt_desc_text",1,1,1, TEX_SKIRT, TEX_SKIRT, SUBPART_SKIRT)); addEntry(LLWearableType::WT_ALPHA, new WearableEntry(LLWearableType::WT_ALPHA,"edit_alpha_title","alpha_desc_text",0,5,1, TEX_LOWER_ALPHA, TEX_UPPER_ALPHA, TEX_HEAD_ALPHA, TEX_EYES_ALPHA, TEX_HAIR_ALPHA, SUBPART_ALPHA)); - addEntry(LLWearableType::WT_TATTOO, new WearableEntry(LLWearableType::WT_TATTOO,"edit_tattoo_title","tattoo_desc_text",0,3,1, TEX_LOWER_TATTOO, TEX_UPPER_TATTOO, TEX_HEAD_TATTOO, SUBPART_TATTOO)); + addEntry(LLWearableType::WT_TATTOO, new WearableEntry(LLWearableType::WT_TATTOO,"edit_tattoo_title","tattoo_desc_text",1,3,1, TEX_HEAD_TATTOO, TEX_LOWER_TATTOO, TEX_UPPER_TATTOO, TEX_HEAD_TATTOO, SUBPART_TATTOO)); } LLEditWearableDictionary::WearableEntry::WearableEntry(LLWearableType::EType type, @@ -331,6 +331,7 @@ LLEditWearableDictionary::ColorSwatchCtrls::ColorSwatchCtrls() addEntry ( TEX_UPPER_GLOVES, new PickerControlEntry (TEX_UPPER_GLOVES, "Color/Tint" )); addEntry ( TEX_UPPER_UNDERSHIRT, new PickerControlEntry (TEX_UPPER_UNDERSHIRT, "Color/Tint" )); addEntry ( TEX_LOWER_UNDERPANTS, new PickerControlEntry (TEX_LOWER_UNDERPANTS, "Color/Tint" )); + addEntry ( TEX_HEAD_TATTOO, new PickerControlEntry(TEX_HEAD_TATTOO, "Color/Tint" )); } LLEditWearableDictionary::TextureCtrls::TextureCtrls() diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 0f4623c678..4371396629 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -4886,7 +4886,7 @@ BOOL LLVOAvatar::loadAvatar() } // Uncomment to enable avatar_lad.xml debugging. -/* std::ofstream file; + std::ofstream file; file.open("avatar_lad.log"); for( LLViewerVisualParam* param = (LLViewerVisualParam*) getFirstVisualParam(); param; @@ -4896,7 +4896,7 @@ BOOL LLVOAvatar::loadAvatar() file << std::endl; } - file.close();*/ + file.close(); return TRUE; } @@ -6355,6 +6355,14 @@ BOOL LLVOAvatar::teToColorParams( ETextureIndex te, U32 *param_name ) param_name[2] = 923; //"skirt_blue"; break; + case TEX_HEAD_TATTOO: + case TEX_LOWER_TATTOO: + case TEX_UPPER_TATTOO: + param_name[0] = 1071; //"tattoo_red"; + param_name[1] = 1072; //"tattoo_green"; + param_name[2] = 1073; //"tattoo_blue"; + break; + default: llassert(0); return FALSE; diff --git a/indra/newview/skins/default/xui/en/panel_edit_tattoo.xml b/indra/newview/skins/default/xui/en/panel_edit_tattoo.xml index ed990eb095..6d02dd41de 100644 --- a/indra/newview/skins/default/xui/en/panel_edit_tattoo.xml +++ b/indra/newview/skins/default/xui/en/panel_edit_tattoo.xml @@ -57,6 +57,20 @@ tool_tip="Click to choose a picture" top_pad="10" width="94" /> + + + -- cgit v1.2.3 From 0924e3397cf86a3e7e986da9aeb29e98baace35c Mon Sep 17 00:00:00 2001 From: Loren Shih Date: Wed, 19 May 2010 13:54:37 -0400 Subject: AVP-72 FIXED Debug settings for gathering Welcome Island Rez time statistics Added outfit baking notification. Cleaned up some code around notifications. Added some more clarity to notification message wording. --- indra/newview/llvoavatar.h | 13 +++- indra/newview/llvoavatarself.cpp | 87 +++++++++++++--------- indra/newview/llvoavatarself.h | 31 +++++--- .../newview/skins/default/xui/en/notifications.xml | 25 +++++-- 4 files changed, 99 insertions(+), 57 deletions(-) diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index d74b4fe7b2..86a7cdae02 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -260,8 +260,6 @@ private: S32 mFullyLoadedFrameCounter; LLFrameTimer mFullyLoadedTimer; LLFrameTimer mRuthTimer; - LLFrameTimer mRuthDebugTimer; // For tracking how long it takes for av to rez - LLFrameTimer mDebugExistenceTimer; // Debugging for how long the avatar has been in memory. /** State ** ** @@ -894,6 +892,9 @@ private: ** DIAGNOSTICS **/ + //-------------------------------------------------------------------- + // General + //-------------------------------------------------------------------- public: static void dumpArchetypeXML(void*); static void dumpBakedStatus(); @@ -913,6 +914,14 @@ private: F32 mAdjustedPixelArea; std::string mDebugText; + + //-------------------------------------------------------------------- + // Avatar Rez Metrics + //-------------------------------------------------------------------- +protected: + LLFrameTimer mRuthDebugTimer; // For tracking how long it takes for av to rez + LLFrameTimer mDebugExistenceTimer; // Debugging for how long the avatar has been in memory. + /** Diagnostics ** ** *******************************************************************************/ diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index 8bf082ad45..f329ec83b0 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -49,12 +49,14 @@ #include "llhudeffecttrail.h" #include "llhudmanager.h" #include "llinventoryfunctions.h" +#include "llnotificationsutil.h" #include "llselectmgr.h" #include "lltoolgrab.h" // for needsRenderBeam #include "lltoolmgr.h" // for needsRenderBeam #include "lltoolmorph.h" #include "lltrans.h" #include "llviewercamera.h" +#include "llviewercontrol.h" #include "llviewermenu.h" #include "llviewerobjectlist.h" #include "llviewerstats.h" @@ -149,7 +151,7 @@ void LLVOAvatarSelf::initInstance() LLVOAvatar::initInstance(); llinfos << "Self avatar object created. Starting timer." << llendl; - mSelfLoadTimer.reset(); + mDebugSelfLoadTimer.reset(); // clear all times to -1 for debugging for (U32 i =0; i < LLVOAvatarDefines::TEX_NUM_INDICES; ++i) { @@ -1396,6 +1398,7 @@ void LLVOAvatarSelf::invalidateAll() { invalidateComposite(mBakedTextureDatas[i].mTexLayerSet, TRUE); } + mDebugSelfLoadTimer.reset(); } //----------------------------------------------------------------------------- @@ -1780,7 +1783,7 @@ if (index < 0 || index >= TEX_NUM_INDICES) if (discard_level >=0 && discard_level <= MAX_DISCARD_LEVEL) // ignore discard level -1, as it means we have no data. { - mTextureLoadTimes[(U32)index][(U32)discard_level] = mSelfLoadTimer.getElapsedTimeF32(); + mTextureLoadTimes[(U32)index][(U32)discard_level] = mDebugSelfLoadTimer.getElapsedTimeF32(); } if (final) { @@ -1795,7 +1798,7 @@ void LLVOAvatarSelf::bakedTextureUpload(EBakedTextureIndex index, BOOL finished) { done = 1; } - mBakedTextureTimes[index][done] = mSelfLoadTimer.getElapsedTimeF32(); + mBakedTextureTimes[index][done] = mDebugSelfLoadTimer.getElapsedTimeF32(); } const LLUUID& LLVOAvatarSelf::grabLocalTexture(ETextureIndex type, U32 index) const @@ -1969,42 +1972,56 @@ void LLVOAvatarSelf::setNewBakedTexture( ETextureIndex te, const LLUUID& uuid ) if (!hasPendingBakedUploads()) { gAgent.sendAgentSetAppearance(); - F32 final_time = mSelfLoadTimer.getElapsedTimeF32(); - llinfos << "REZTIME: Myself rez stats:" << llendl; - llinfos << "\t Time from avatar creation to load wearables: " << (S32)mTimeWearablesLoaded << llendl; - llinfos << "\t Time from avatar creation to de-cloud: " << (S32)mTimeAvatarVisible << llendl; - llinfos << "\t Time from avatar creation to de-cloud for others: " << (S32)final_time << llendl; - llinfos << "\t Load time for each texture: " << llendl; - for (U32 i = 0; i < LLVOAvatarDefines::TEX_NUM_INDICES; ++i) - { - std::stringstream out; - out << "\t\t (" << i << ") "; - U32 j=0; - for (j=0; j <= MAX_DISCARD_LEVEL; j++) - { - out << "\t"; - S32 load_time = (S32)mTextureLoadTimes[i][j]; - if (load_time == -1) - { - out << "*"; - if (j == 0) - break; - } - else - { - out << load_time; - } - } - // Don't print out non-existent textures. - if (j != 0) - llinfos << out.str() << llendl; + if (gSavedSettings.getBOOL("DebugAvatarRezTime")) + { + LLSD args; + args["EXISTENCE"] = llformat("%d",(U32)mDebugExistenceTimer.getElapsedTimeF32()); + args["TIME"] = (U32)mDebugSelfLoadTimer.getElapsedTimeF32(); + LLNotificationsUtil::add("AvatarRezSelfNotification",args); } - llinfos << "\t Time points for each upload (start / finish)" << llendl; - for (U32 i = 0; i < LLVOAvatarDefines::BAKED_NUM_INDICES; ++i) + + outputRezDiagnostics(); + } +} + +void LLVOAvatarSelf::outputRezDiagnostics() const +{ + const F32 final_time = mDebugSelfLoadTimer.getElapsedTimeF32(); + llinfos << "REZTIME: Myself rez stats:" << llendl; + llinfos << "\t Time from avatar creation to load wearables: " << (S32)mTimeWearablesLoaded << llendl; + llinfos << "\t Time from avatar creation to de-cloud: " << (S32)mTimeAvatarVisible << llendl; + llinfos << "\t Time from avatar creation to de-cloud for others: " << (S32)final_time << llendl; + llinfos << "\t Load time for each texture: " << llendl; + for (U32 i = 0; i < LLVOAvatarDefines::TEX_NUM_INDICES; ++i) + { + std::stringstream out; + out << "\t\t (" << i << ") "; + U32 j=0; + for (j=0; j <= MAX_DISCARD_LEVEL; j++) { - llinfos << "\t\t (" << i << ") \t" << (S32)mBakedTextureTimes[i][0] << " / " << (S32)mBakedTextureTimes[i][1] << llendl; + out << "\t"; + S32 load_time = (S32)mTextureLoadTimes[i][j]; + if (load_time == -1) + { + out << "*"; + if (j == 0) + break; + } + else + { + out << load_time; + } } + + // Don't print out non-existent textures. + if (j != 0) + llinfos << out.str() << llendl; + } + llinfos << "\t Time points for each upload (start / finish)" << llendl; + for (U32 i = 0; i < LLVOAvatarDefines::BAKED_NUM_INDICES; ++i) + { + llinfos << "\t\t (" << i << ") \t" << (S32)mBakedTextureTimes[i][0] << " / " << (S32)mBakedTextureTimes[i][1] << llendl; } } diff --git a/indra/newview/llvoavatarself.h b/indra/newview/llvoavatarself.h index 03856b3a3b..9dc38c953e 100644 --- a/indra/newview/llvoavatarself.h +++ b/indra/newview/llvoavatarself.h @@ -126,25 +126,14 @@ public: /*virtual*/ BOOL getIsCloud(); void bakedTextureUpload(LLVOAvatarDefines::EBakedTextureIndex index, BOOL finished); static void onTimingLocalTexLoaded(BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata); - void wearablesLoaded() { mTimeWearablesLoaded = mSelfLoadTimer.getElapsedTimeF32(); } - void avatarVisible() { mTimeAvatarVisible = mSelfLoadTimer.getElapsedTimeF32(); } - -private: //-------------------------------------------------------------------- // Region state //-------------------------------------------------------------------- +private: U64 mLastRegionHandle; LLFrameTimer mRegionCrossingTimer; S32 mRegionCrossingCount; - LLFrameTimer mSelfLoadTimer; - F32 mTimeWearablesLoaded; - F32 mTimeAvatarVisible; - F32 mTextureLoadTimes[LLVOAvatarDefines::TEX_NUM_INDICES][MAX_DISCARD_LEVEL+1]; // stores load time for each texture, - // at each discard level - F32 mBakedTextureTimes[LLVOAvatarDefines::BAKED_NUM_INDICES][2]; // stores time to start upload and finish upload of each baked texture - - void timingLocalTexLoaded(BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata); /** State ** ** @@ -336,11 +325,29 @@ public: ** DIAGNOSTICS **/ + //-------------------------------------------------------------------- + // General + //-------------------------------------------------------------------- public: static void dumpTotalLocalTextureByteCount(); void dumpLocalTextures() const; static void dumpScratchTextureByteCount(); + //-------------------------------------------------------------------- + // Avatar Rez Metrics + //-------------------------------------------------------------------- +public: + void wearablesLoaded() { mTimeWearablesLoaded = mDebugSelfLoadTimer.getElapsedTimeF32(); } + void avatarVisible() { mTimeAvatarVisible = mDebugSelfLoadTimer.getElapsedTimeF32(); } + void outputRezDiagnostics() const; +private: + LLFrameTimer mDebugSelfLoadTimer; + F32 mTimeWearablesLoaded; + F32 mTimeAvatarVisible; + F32 mTextureLoadTimes[LLVOAvatarDefines::TEX_NUM_INDICES][MAX_DISCARD_LEVEL+1]; // load time for each texture at each discard level + F32 mBakedTextureTimes[LLVOAvatarDefines::BAKED_NUM_INDICES][2]; // time to start upload and finish upload of each baked texture + void timingLocalTexLoaded(BOOL success, LLViewerFetchedTexture *src_vi, LLImageRaw* src, LLImageRaw* aux_src, S32 discard_level, BOOL final, void* userdata); + /** Diagnostics ** ** *******************************************************************************/ diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 300437b3c9..999f804e71 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -6129,15 +6129,24 @@ Deed to group failed. icon="notifytip.tga" name="AvatarRezNotification" type="notifytip"> -( [EXISTENCE] seconds ) -Avatar '[NAME]' rezzed in [TIME] seconds. +( [EXISTENCE] seconds alive ) +Avatar '[NAME]' declouded in [TIME] seconds. + +( [EXISTENCE] seconds alive ) +You finished baking your outfit in [TIME] seconds. + + + -( [EXISTENCE] seconds ) +( [EXISTENCE] seconds alive ) Avatar '[NAME]' became cloud. @@ -6145,7 +6154,7 @@ Avatar '[NAME]' became cloud. icon="notifytip.tga" name="AvatarRezArrivedNotification" type="notifytip"> -( [EXISTENCE] seconds ) +( [EXISTENCE] seconds alive ) Avatar '[NAME]' appeared. @@ -6153,7 +6162,7 @@ Avatar '[NAME]' appeared. icon="notifytip.tga" name="AvatarRezLeftCloudNotification" type="notifytip"> -( [EXISTENCE] seconds ) +( [EXISTENCE] seconds alive ) Avatar '[NAME]' left after [TIME] seconds as cloud. @@ -6161,7 +6170,7 @@ Avatar '[NAME]' left after [TIME] seconds as cloud. icon="notifytip.tga" name="AvatarRezEnteredAppearanceNotification" type="notifytip"> -( [EXISTENCE] seconds ) +( [EXISTENCE] seconds alive ) Avatar '[NAME]' entered appearance mode. @@ -6169,7 +6178,7 @@ Avatar '[NAME]' entered appearance mode. icon="notifytip.tga" name="AvatarRezLeftAppearanceNotification" type="notifytip"> -( [EXISTENCE] seconds ) +( [EXISTENCE] seconds alive ) Avatar '[NAME]' left appearance mode. @@ -6177,7 +6186,7 @@ Avatar '[NAME]' left appearance mode. icon="notifytip.tga" name="AvatarRezLeftNotification" type="notifytip"> -( [EXISTENCE] seconds ) +( [EXISTENCE] seconds alive ) Avatar '[NAME]' left as fully loaded. -- cgit v1.2.3 From d43ac77d1b6d1952fe3369079f6461b124466b38 Mon Sep 17 00:00:00 2001 From: Loren Shih Date: Wed, 19 May 2010 13:56:39 -0400 Subject: AVP-72 FIXED Debug settings for gathering Welcome Island Rez time statistics Fix for ambiguity around LLSD usage. --- indra/newview/llvoavatarself.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index f329ec83b0..da99b212f0 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -1977,7 +1977,7 @@ void LLVOAvatarSelf::setNewBakedTexture( ETextureIndex te, const LLUUID& uuid ) { LLSD args; args["EXISTENCE"] = llformat("%d",(U32)mDebugExistenceTimer.getElapsedTimeF32()); - args["TIME"] = (U32)mDebugSelfLoadTimer.getElapsedTimeF32(); + args["TIME"] = llformat("%d",(U32)mDebugSelfLoadTimer.getElapsedTimeF32()); LLNotificationsUtil::add("AvatarRezSelfNotification",args); } -- cgit v1.2.3 From a4c364561bfe92a0b1610a1a732de7489f54b06e Mon Sep 17 00:00:00 2001 From: Loren Shih Date: Wed, 19 May 2010 16:18:09 -0400 Subject: AVP-72 FIXED Debug settings for gathering Welcome Island Rez time statistics Fix for scoping error leading to compile error. --- indra/newview/llvoavatarself.h | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/indra/newview/llvoavatarself.h b/indra/newview/llvoavatarself.h index 9dc38c953e..32a180be5d 100644 --- a/indra/newview/llvoavatarself.h +++ b/indra/newview/llvoavatarself.h @@ -337,6 +337,15 @@ public: // Avatar Rez Metrics //-------------------------------------------------------------------- public: + struct LLAvatarTexData + { + LLAvatarTexData(const LLUUID& id, LLVOAvatarDefines::ETextureIndex index) : + mAvatarID(id), + mIndex(index) + {} + LLUUID mAvatarID; + LLVOAvatarDefines::ETextureIndex mIndex; + }; void wearablesLoaded() { mTimeWearablesLoaded = mDebugSelfLoadTimer.getElapsedTimeF32(); } void avatarVisible() { mTimeAvatarVisible = mDebugSelfLoadTimer.getElapsedTimeF32(); } void outputRezDiagnostics() const; @@ -352,16 +361,6 @@ private: ** ** *******************************************************************************/ -struct LLAvatarTexData -{ - LLAvatarTexData(const LLUUID& id, LLVOAvatarDefines::ETextureIndex index) : - mAvatarID(id), - mIndex(index) - {} - LLUUID mAvatarID; - LLVOAvatarDefines::ETextureIndex mIndex; -}; - }; extern LLVOAvatarSelf *gAgentAvatarp; -- cgit v1.2.3 From ca9737d6d6efac7ce9aba1f9686a86b7f6863636 Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Wed, 19 May 2010 15:22:46 -0700 Subject: The certificate hostname verification was not taking into account changes in hostname due to a redirect which is handled via curl. I turned off the secapi hostname verification just allowing libcurls hostname verification, as it's better anyway (it handles alt names) --- indra/newview/llsecapi.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/indra/newview/llsecapi.cpp b/indra/newview/llsecapi.cpp index 89b799f297..1caeec5b04 100644 --- a/indra/newview/llsecapi.cpp +++ b/indra/newview/llsecapi.cpp @@ -121,7 +121,10 @@ int secapiSSLCertVerifyCallback(X509_STORE_CTX *ctx, void *param) validation_params[CERT_HOSTNAME] = uri.hostName(); try { - chain->validate(VALIDATION_POLICY_SSL, store, validation_params); + // we rely on libcurl to validate the hostname, as libcurl does more extensive validation + // leaving our hostname validation call mechanism for future additions with respect to + // OS native (Mac keyring, windows CAPI) validation. + chain->validate(VALIDATION_POLICY_SSL & (~VALIDATION_POLICY_HOSTNAME), store, validation_params); } catch (LLCertValidationTrustException& cert_exception) { -- cgit v1.2.3 From f67e0d801ddcfaa1cc154a1c40118718a8067532 Mon Sep 17 00:00:00 2001 From: Richard Nelson Date: Wed, 19 May 2010 16:22:31 -0700 Subject: EXT-6364 - Ctrl-Alt-F1 doesn't currently hide all the viewer UI reviewed by leyla --- indra/newview/llviewerwindow.cpp | 570 ++++++++++++++++++++------------------- 1 file changed, 300 insertions(+), 270 deletions(-) diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 56d22a0608..d8277b0681 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -621,107 +621,112 @@ BOOL LLViewerWindow::handleAnyMouseClick(LLWindow *window, LLCoordGL pos, MASK x = llround((F32)x / mDisplayScale.mV[VX]); y = llround((F32)y / mDisplayScale.mV[VY]); - if (down) - { - buttonstatestr = "down" ; - } - else - { - buttonstatestr = "up" ; - } - - switch (clicktype) - { - case LLMouseHandler::CLICK_LEFT: - mLeftMouseDown = down; - buttonname = "Left"; - break; - case LLMouseHandler::CLICK_RIGHT: - mRightMouseDown = down; - buttonname = "Right"; - break; - case LLMouseHandler::CLICK_MIDDLE: - mMiddleMouseDown = down; - buttonname = "Middle"; - break; - case LLMouseHandler::CLICK_DOUBLELEFT: - mLeftMouseDown = down; - buttonname = "Left Double Click"; - break; - } - - LLView::sMouseHandlerMessage.clear(); + // only send mouse clicks to UI if UI is visible + if(gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) + { - if (gMenuBarView) - { - // stop ALT-key access to menu - gMenuBarView->resetMenuTrigger(); - } + if (down) + { + buttonstatestr = "down" ; + } + else + { + buttonstatestr = "up" ; + } + + switch (clicktype) + { + case LLMouseHandler::CLICK_LEFT: + mLeftMouseDown = down; + buttonname = "Left"; + break; + case LLMouseHandler::CLICK_RIGHT: + mRightMouseDown = down; + buttonname = "Right"; + break; + case LLMouseHandler::CLICK_MIDDLE: + mMiddleMouseDown = down; + buttonname = "Middle"; + break; + case LLMouseHandler::CLICK_DOUBLELEFT: + mLeftMouseDown = down; + buttonname = "Left Double Click"; + break; + } + + LLView::sMouseHandlerMessage.clear(); - if (gDebugClicks) - { - llinfos << "ViewerWindow " << buttonname << " mouse " << buttonstatestr << " at " << x << "," << y << llendl; - } + if (gMenuBarView) + { + // stop ALT-key access to menu + gMenuBarView->resetMenuTrigger(); + } - // Make sure we get a corresponding mouseup event, even if the mouse leaves the window - if (down) - mWindow->captureMouse(); - else - mWindow->releaseMouse(); + if (gDebugClicks) + { + llinfos << "ViewerWindow " << buttonname << " mouse " << buttonstatestr << " at " << x << "," << y << llendl; + } - // Indicate mouse was active - LLUI::resetMouseIdleTimer(); + // Make sure we get a corresponding mouseup event, even if the mouse leaves the window + if (down) + mWindow->captureMouse(); + else + mWindow->releaseMouse(); - // Don't let the user move the mouse out of the window until mouse up. - if( LLToolMgr::getInstance()->getCurrentTool()->clipMouseWhenDown() ) - { - mWindow->setMouseClipping(down); - } + // Indicate mouse was active + LLUI::resetMouseIdleTimer(); - LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture(); - if( mouse_captor ) - { - S32 local_x; - S32 local_y; - mouse_captor->screenPointToLocal( x, y, &local_x, &local_y ); - if (LLView::sDebugMouseHandling) + // Don't let the user move the mouse out of the window until mouse up. + if( LLToolMgr::getInstance()->getCurrentTool()->clipMouseWhenDown() ) { - llinfos << buttonname << " Mouse " << buttonstatestr << " handled by captor " << mouse_captor->getName() << llendl; + mWindow->setMouseClipping(down); } - return mouse_captor->handleAnyMouseClick(local_x, local_y, mask, clicktype, down); - } - - // Topmost view gets a chance before the hierarchy - //LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl(); - //if (top_ctrl) - //{ - // S32 local_x, local_y; - // top_ctrl->screenPointToLocal( x, y, &local_x, &local_y ); - // if (top_ctrl->pointInView(local_x, local_y)) - // { - // return top_ctrl->handleAnyMouseClick(local_x, local_y, mask, clicktype, down) ; - // } - // else - // { - // if (down) - // { - // gFocusMgr.setTopCtrl(NULL); - // } - // } - //} - - // Give the UI views a chance to process the click - if( mRootView->handleAnyMouseClick(x, y, mask, clicktype, down) ) - { - if (LLView::sDebugMouseHandling) + + LLMouseHandler* mouse_captor = gFocusMgr.getMouseCapture(); + if( mouse_captor ) { - llinfos << buttonname << " Mouse " << buttonstatestr << " " << LLView::sMouseHandlerMessage << llendl; + S32 local_x; + S32 local_y; + mouse_captor->screenPointToLocal( x, y, &local_x, &local_y ); + if (LLView::sDebugMouseHandling) + { + llinfos << buttonname << " Mouse " << buttonstatestr << " handled by captor " << mouse_captor->getName() << llendl; + } + return mouse_captor->handleAnyMouseClick(local_x, local_y, mask, clicktype, down); + } + + // Topmost view gets a chance before the hierarchy + //LLUICtrl* top_ctrl = gFocusMgr.getTopCtrl(); + //if (top_ctrl) + //{ + // S32 local_x, local_y; + // top_ctrl->screenPointToLocal( x, y, &local_x, &local_y ); + // if (top_ctrl->pointInView(local_x, local_y)) + // { + // return top_ctrl->handleAnyMouseClick(local_x, local_y, mask, clicktype, down) ; + // } + // else + // { + // if (down) + // { + // gFocusMgr.setTopCtrl(NULL); + // } + // } + //} + + // Give the UI views a chance to process the click + if( mRootView->handleAnyMouseClick(x, y, mask, clicktype, down) ) + { + if (LLView::sDebugMouseHandling) + { + llinfos << buttonname << " Mouse " << buttonstatestr << " " << LLView::sMouseHandlerMessage << llendl; + } + return TRUE; + } + else if (LLView::sDebugMouseHandling) + { + llinfos << buttonname << " Mouse " << buttonstatestr << " not handled by view" << llendl; } - return TRUE; - } - else if (LLView::sDebugMouseHandling) - { - llinfos << buttonname << " Mouse " << buttonstatestr << " not handled by view" << llendl; } // Do not allow tool manager to handle mouseclicks if we have disconnected @@ -2458,7 +2463,9 @@ void LLViewerWindow::updateUI() // animate layout stacks so we have up to date rect for world view LLLayoutStack::updateClass(); - updateWorldViewRect(); + // use full window for world view when not rendering UI + bool world_view_uses_full_window = !gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI); + updateWorldViewRect(world_view_uses_full_window); LLView::sMouseHandlerMessage.clear(); @@ -2508,28 +2515,55 @@ void LLViewerWindow::updateUI() root_view = mRootView; } - // include all ancestors of captor_view as automatically having mouse - if (captor_view) + // only update mouse hover set when UI is visible (since we shouldn't send hover events to invisible UI + if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) { - LLView* captor_parent_view = captor_view->getParent(); - while(captor_parent_view) + // include all ancestors of captor_view as automatically having mouse + if (captor_view) { - mouse_hover_set.insert(captor_parent_view->getHandle()); - captor_parent_view = captor_parent_view->getParent(); + LLView* captor_parent_view = captor_view->getParent(); + while(captor_parent_view) + { + mouse_hover_set.insert(captor_parent_view->getHandle()); + captor_parent_view = captor_parent_view->getParent(); + } } - } - // aggregate visible views that contain mouse cursor in display order - LLPopupView::popup_list_t popups = mPopupView->getCurrentPopups(); + // aggregate visible views that contain mouse cursor in display order + LLPopupView::popup_list_t popups = mPopupView->getCurrentPopups(); - for(LLPopupView::popup_list_t::iterator popup_it = popups.begin(); popup_it != popups.end(); ++popup_it) - { - LLView* popup = popup_it->get(); - if (popup && popup->calcScreenBoundingRect().pointInRect(x, y)) + for(LLPopupView::popup_list_t::iterator popup_it = popups.begin(); popup_it != popups.end(); ++popup_it) + { + LLView* popup = popup_it->get(); + if (popup && popup->calcScreenBoundingRect().pointInRect(x, y)) + { + // iterator over contents of top_ctrl, and throw into mouse_hover_set + for (LLView::tree_iterator_t it = popup->beginTreeDFS(); + it != popup->endTreeDFS(); + ++it) + { + LLView* viewp = *it; + if (viewp->getVisible() + && viewp->calcScreenBoundingRect().pointInRect(x, y)) + { + // we have a view that contains the mouse, add it to the set + mouse_hover_set.insert(viewp->getHandle()); + } + else + { + // skip this view and all of its children + it.skipDescendants(); + } + } + } + } + + // while the top_ctrl contains the mouse cursor, only it and its descendants will receive onMouseEnter events + if (top_ctrl && top_ctrl->calcScreenBoundingRect().pointInRect(x, y)) { // iterator over contents of top_ctrl, and throw into mouse_hover_set - for (LLView::tree_iterator_t it = popup->beginTreeDFS(); - it != popup->endTreeDFS(); + for (LLView::tree_iterator_t it = top_ctrl->beginTreeDFS(); + it != top_ctrl->endTreeDFS(); ++it) { LLView* viewp = *it; @@ -2546,57 +2580,34 @@ void LLViewerWindow::updateUI() } } } - } - - // while the top_ctrl contains the mouse cursor, only it and its descendants will receive onMouseEnter events - if (top_ctrl && top_ctrl->calcScreenBoundingRect().pointInRect(x, y)) - { - // iterator over contents of top_ctrl, and throw into mouse_hover_set - for (LLView::tree_iterator_t it = top_ctrl->beginTreeDFS(); - it != top_ctrl->endTreeDFS(); - ++it) - { - LLView* viewp = *it; - if (viewp->getVisible() - && viewp->calcScreenBoundingRect().pointInRect(x, y)) - { - // we have a view that contains the mouse, add it to the set - mouse_hover_set.insert(viewp->getHandle()); - } - else - { - // skip this view and all of its children - it.skipDescendants(); - } - } - } - else - { - // walk UI tree in depth-first order - for (LLView::tree_iterator_t it = root_view->beginTreeDFS(); - it != root_view->endTreeDFS(); - ++it) + else { - LLView* viewp = *it; - // calculating the screen rect involves traversing the parent, so this is less than optimal - if (viewp->getVisible() - && viewp->calcScreenBoundingRect().pointInRect(x, y)) + // walk UI tree in depth-first order + for (LLView::tree_iterator_t it = root_view->beginTreeDFS(); + it != root_view->endTreeDFS(); + ++it) { + LLView* viewp = *it; + // calculating the screen rect involves traversing the parent, so this is less than optimal + if (viewp->getVisible() + && viewp->calcScreenBoundingRect().pointInRect(x, y)) + { - // if this view is mouse opaque, nothing behind it should be in mouse_hover_set - if (viewp->getMouseOpaque()) + // if this view is mouse opaque, nothing behind it should be in mouse_hover_set + if (viewp->getMouseOpaque()) + { + // constrain further iteration to children of this widget + it = viewp->beginTreeDFS(); + } + + // we have a view that contains the mouse, add it to the set + mouse_hover_set.insert(viewp->getHandle()); + } + else { - // constrain further iteration to children of this widget - it = viewp->beginTreeDFS(); + // skip this view and all of its children + it.skipDescendants(); } - - // we have a view that contains the mouse, add it to the set - mouse_hover_set.insert(viewp->getHandle()); - } - else - { - // skip this view and all of its children - it.skipDescendants(); } } } @@ -2640,156 +2651,170 @@ void LLViewerWindow::updateUI() // store resulting hover set for next frame swap(mMouseHoverViews, mouse_hover_set); - if( mouse_captor ) - { - // Pass hover events to object capturing mouse events. - S32 local_x; - S32 local_y; - mouse_captor->screenPointToLocal( x, y, &local_x, &local_y ); - handled = mouse_captor->handleHover(local_x, local_y, mask); - if (LLView::sDebugMouseHandling) - { - llinfos << "Hover handled by captor " << mouse_captor->getName() << llendl; - } + // only handle hover events when UI is enabled + if (gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) + { - if( !handled ) + if( mouse_captor ) { - lldebugst(LLERR_USER_INPUT) << "hover not handled by mouse captor" << llendl; + // Pass hover events to object capturing mouse events. + S32 local_x; + S32 local_y; + mouse_captor->screenPointToLocal( x, y, &local_x, &local_y ); + handled = mouse_captor->handleHover(local_x, local_y, mask); + if (LLView::sDebugMouseHandling) + { + llinfos << "Hover handled by captor " << mouse_captor->getName() << llendl; + } + + if( !handled ) + { + lldebugst(LLERR_USER_INPUT) << "hover not handled by mouse captor" << llendl; + } } - } - else - { - if (top_ctrl) + else { - S32 local_x, local_y; - top_ctrl->screenPointToLocal( x, y, &local_x, &local_y ); - handled = top_ctrl->pointInView(local_x, local_y) && top_ctrl->handleHover(local_x, local_y, mask); - handled_by_top_ctrl = TRUE; - } + if (top_ctrl) + { + S32 local_x, local_y; + top_ctrl->screenPointToLocal( x, y, &local_x, &local_y ); + handled = top_ctrl->pointInView(local_x, local_y) && top_ctrl->handleHover(local_x, local_y, mask); + handled_by_top_ctrl = TRUE; + } - if ( !handled ) - { - // x and y are from last time mouse was in window - // mMouseInWindow tracks *actual* mouse location - if (mMouseInWindow && mRootView->handleHover(x, y, mask) ) + if ( !handled ) { - if (LLView::sDebugMouseHandling && LLView::sMouseHandlerMessage != last_handle_msg) + // x and y are from last time mouse was in window + // mMouseInWindow tracks *actual* mouse location + if (mMouseInWindow && mRootView->handleHover(x, y, mask) ) + { + if (LLView::sDebugMouseHandling && LLView::sMouseHandlerMessage != last_handle_msg) + { + last_handle_msg = LLView::sMouseHandlerMessage; + llinfos << "Hover" << LLView::sMouseHandlerMessage << llendl; + } + handled = TRUE; + } + else if (LLView::sDebugMouseHandling) { - last_handle_msg = LLView::sMouseHandlerMessage; - llinfos << "Hover" << LLView::sMouseHandlerMessage << llendl; + if (last_handle_msg != LLStringUtil::null) + { + last_handle_msg.clear(); + llinfos << "Hover not handled by view" << llendl; + } } - handled = TRUE; } - else if (LLView::sDebugMouseHandling) + + if (!handled) { - if (last_handle_msg != LLStringUtil::null) + LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); + + if(mMouseInWindow && tool) { - last_handle_msg.clear(); - llinfos << "Hover not handled by view" << llendl; + handled = tool->handleHover(x, y, mask); } } } - if (!handled) + // Show a new tool tip (or update one that is already shown) + BOOL tool_tip_handled = FALSE; + std::string tool_tip_msg; + if( handled + && !mWindow->isCursorHidden()) { - LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); + LLRect screen_sticky_rect = mRootView->getLocalRect(); + S32 local_x, local_y; - if(mMouseInWindow && tool) + if (gSavedSettings.getBOOL("DebugShowXUINames")) { - handled = tool->handleHover(x, y, mask); - } - } - } - - // Show a new tool tip (or update one that is already shown) - BOOL tool_tip_handled = FALSE; - std::string tool_tip_msg; - if( handled - && !mWindow->isCursorHidden()) - { - LLRect screen_sticky_rect = mRootView->getLocalRect(); - S32 local_x, local_y; - - if (gSavedSettings.getBOOL("DebugShowXUINames")) - { - LLToolTip::Params params; + LLToolTip::Params params; - LLView* tooltip_view = mRootView; - LLView::tree_iterator_t end_it = mRootView->endTreeDFS(); - for (LLView::tree_iterator_t it = mRootView->beginTreeDFS(); it != end_it; ++it) - { - LLView* viewp = *it; - LLRect screen_rect; - viewp->localRectToScreen(viewp->getLocalRect(), &screen_rect); - if (!(viewp->getVisible() - && screen_rect.pointInRect(x, y))) - { - it.skipDescendants(); - } - // only report xui names for LLUICtrls, - // and blacklist the various containers we don't care about - else if (dynamic_cast(viewp) - && viewp != gMenuHolder - && viewp != gFloaterView - && viewp != gConsole) + LLView* tooltip_view = mRootView; + LLView::tree_iterator_t end_it = mRootView->endTreeDFS(); + for (LLView::tree_iterator_t it = mRootView->beginTreeDFS(); it != end_it; ++it) { - if (dynamic_cast(viewp)) + LLView* viewp = *it; + LLRect screen_rect; + viewp->localRectToScreen(viewp->getLocalRect(), &screen_rect); + if (!(viewp->getVisible() + && screen_rect.pointInRect(x, y))) { - // constrain search to descendants of this (frontmost) floater - // by resetting iterator - it = viewp->beginTreeDFS(); + it.skipDescendants(); } - - // if we are in a new part of the tree (not a descendent of current tooltip_view) - // then push the results for tooltip_view and start with a new potential view - // NOTE: this emulates visiting only the leaf nodes that meet our criteria - if (!viewp->hasAncestor(tooltip_view)) + // only report xui names for LLUICtrls, + // and blacklist the various containers we don't care about + else if (dynamic_cast(viewp) + && viewp != gMenuHolder + && viewp != gFloaterView + && viewp != gConsole) { - append_xui_tooltip(tooltip_view, params); - screen_sticky_rect.intersectWith(tooltip_view->calcScreenRect()); + if (dynamic_cast(viewp)) + { + // constrain search to descendants of this (frontmost) floater + // by resetting iterator + it = viewp->beginTreeDFS(); + } + + // if we are in a new part of the tree (not a descendent of current tooltip_view) + // then push the results for tooltip_view and start with a new potential view + // NOTE: this emulates visiting only the leaf nodes that meet our criteria + if (!viewp->hasAncestor(tooltip_view)) + { + append_xui_tooltip(tooltip_view, params); + screen_sticky_rect.intersectWith(tooltip_view->calcScreenRect()); + } + tooltip_view = viewp; } - tooltip_view = viewp; } - } - append_xui_tooltip(tooltip_view, params); - screen_sticky_rect.intersectWith(tooltip_view->calcScreenRect()); - - params.sticky_rect = screen_sticky_rect; - params.max_width = 400; + append_xui_tooltip(tooltip_view, params); + screen_sticky_rect.intersectWith(tooltip_view->calcScreenRect()); + + params.sticky_rect = screen_sticky_rect; + params.max_width = 400; - LLToolTipMgr::instance().show(params); - } - // if there is a mouse captor, nothing else gets a tooltip - else if (mouse_captor) - { - mouse_captor->screenPointToLocal(x, y, &local_x, &local_y); - tool_tip_handled = mouse_captor->handleToolTip(local_x, local_y, mask); - } - else - { - // next is top_ctrl - if (!tool_tip_handled && top_ctrl) - { - top_ctrl->screenPointToLocal(x, y, &local_x, &local_y); - tool_tip_handled = top_ctrl->handleToolTip(local_x, local_y, mask ); + LLToolTipMgr::instance().show(params); } - - if (!tool_tip_handled) + // if there is a mouse captor, nothing else gets a tooltip + else if (mouse_captor) { - local_x = x; local_y = y; - tool_tip_handled = mRootView->handleToolTip(local_x, local_y, mask ); + mouse_captor->screenPointToLocal(x, y, &local_x, &local_y); + tool_tip_handled = mouse_captor->handleToolTip(local_x, local_y, mask); } - - LLTool* current_tool = LLToolMgr::getInstance()->getCurrentTool(); - if (!tool_tip_handled && current_tool) + else { - current_tool->screenPointToLocal(x, y, &local_x, &local_y); - tool_tip_handled = current_tool->handleToolTip(local_x, local_y, mask ); + // next is top_ctrl + if (!tool_tip_handled && top_ctrl) + { + top_ctrl->screenPointToLocal(x, y, &local_x, &local_y); + tool_tip_handled = top_ctrl->handleToolTip(local_x, local_y, mask ); + } + + if (!tool_tip_handled) + { + local_x = x; local_y = y; + tool_tip_handled = mRootView->handleToolTip(local_x, local_y, mask ); + } + + LLTool* current_tool = LLToolMgr::getInstance()->getCurrentTool(); + if (!tool_tip_handled && current_tool) + { + current_tool->screenPointToLocal(x, y, &local_x, &local_y); + tool_tip_handled = current_tool->handleToolTip(local_x, local_y, mask ); + } } + } + } + else + { // just have tools handle hover when UI is turned off + LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); + + if(mMouseInWindow && tool) + { + handled = tool->handleHover(x, y, mask); } - } - + } + updateLayout(); mLastMousePoint = mCurrentMousePoint; @@ -2905,6 +2930,11 @@ void LLViewerWindow::updateMouseDelta() void LLViewerWindow::updateKeyboardFocus() { + if (!gPipeline.hasRenderDebugFeatureMask(LLPipeline::RENDER_DEBUG_FEATURE_UI)) + { + gFocusMgr.setKeyboardFocus(NULL); + } + // clean up current focus LLUICtrl* cur_focus = dynamic_cast(gFocusMgr.getKeyboardFocus()); if (cur_focus) -- cgit v1.2.3 From 8487341b0f255e65044c3f7e3dc09461b4e2351e Mon Sep 17 00:00:00 2001 From: Nyx Linden Date: Wed, 19 May 2010 19:48:23 -0400 Subject: AVP-44 WIP Back-end support for multiwearables Changed a lot of areas of code that were not dealing with multiwearables properly. Little functionality changed, as the support mechanisms that were out of date were subtle, but getting closer to no further areas of code that don't properly support multi-wearables. Code reviewed by Vir. --- indra/newview/llagent.cpp | 15 +-- indra/newview/llagentwearables.cpp | 190 ++++-------------------------------- indra/newview/llagentwearables.h | 22 +---- indra/newview/llappearancemgr.cpp | 13 ++- indra/newview/llinventorybridge.cpp | 26 ++--- indra/newview/llviewermenu.cpp | 56 +++++------ indra/newview/llvoavatarself.cpp | 28 +++--- indra/newview/llvoavatarself.h | 6 +- 8 files changed, 100 insertions(+), 256 deletions(-) diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index 529ce950e4..f96a59e97a 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -3583,12 +3583,15 @@ void LLAgent::sendAgentSetAppearance() { // LLWearableType::EType wearable_type = gBakedWearableMap[baked_index][wearable_num]; const LLWearableType::EType wearable_type = baked_dict->mWearables[i]; - // MULTI-WEARABLE: fixed to 0th - extend to everything once messaging works. - const LLWearable* wearable = gAgentWearables.getWearable(wearable_type,0); - if (wearable) - { - hash ^= wearable->getAssetID(); - } + for (U8 wearable_index =0; wearable_index < gAgentWearables.getWearableCount(wearable_type); ++wearable_index) + { + const LLWearable* wearable = gAgentWearables.getWearable(wearable_type,wearable_index); + if (wearable) + { + // MULTI-WEARABLE: make order-dependent (use MD5 hash) + hash ^= wearable->getAssetID(); + } + } } if (hash.notNull()) { diff --git a/indra/newview/llagentwearables.cpp b/indra/newview/llagentwearables.cpp index 3e73bbef15..545c5b845b 100644 --- a/indra/newview/llagentwearables.cpp +++ b/indra/newview/llagentwearables.cpp @@ -125,7 +125,6 @@ void LLAgentWearables::dump() } } -// MULTI-WEARABLE: debugging struct LLAgentDumper { LLAgentDumper(std::string name): @@ -191,7 +190,7 @@ LLAgentWearables::sendAgentWearablesUpdateCallback::~sendAgentWearablesUpdateCal * @param todo Bitmask of actions to take on completion. */ LLAgentWearables::addWearableToAgentInventoryCallback::addWearableToAgentInventoryCallback( - LLPointer cb, S32 type, U32 index, LLWearable* wearable, U32 todo) : + LLPointer cb, LLWearableType::EType type, U32 index, LLWearable* wearable, U32 todo) : mType(type), mIndex(index), mWearable(wearable), @@ -240,7 +239,7 @@ void LLAgentWearables::addWearableToAgentInventoryCallback::fire(const LLUUID& i } } -void LLAgentWearables::addWearabletoAgentInventoryDone(const S32 type, +void LLAgentWearables::addWearabletoAgentInventoryDone(const LLWearableType::EType type, const U32 index, const LLUUID& item_id, LLWearable* wearable) @@ -250,7 +249,7 @@ void LLAgentWearables::addWearabletoAgentInventoryDone(const S32 type, if (item_id.isNull()) return; - LLUUID old_item_id = getWearableItemID((LLWearableType::EType)type,index); + LLUUID old_item_id = getWearableItemID(type,index); if (wearable) { @@ -259,11 +258,11 @@ void LLAgentWearables::addWearabletoAgentInventoryDone(const S32 type, if (old_item_id.notNull()) { gInventory.addChangedMask(LLInventoryObserver::LABEL, old_item_id); - setWearable((LLWearableType::EType)type,index,wearable); + setWearable(type,index,wearable); } else { - pushWearable((LLWearableType::EType)type,wearable); + pushWearable(type,wearable); } } @@ -285,13 +284,12 @@ void LLAgentWearables::addWearabletoAgentInventoryDone(const S32 type, void LLAgentWearables::sendAgentWearablesUpdate() { - // MULTI-WEARABLE: call i "type" or something. // First make sure that we have inventory items for each wearable for (S32 type=0; type < LLWearableType::WT_COUNT; ++type) { - for (U32 j=0; j < getWearableCount((LLWearableType::EType)type); ++j) + for (U32 index=0; index < getWearableCount((LLWearableType::EType)type); ++index) { - LLWearable* wearable = getWearable((LLWearableType::EType)type,j); + LLWearable* wearable = getWearable((LLWearableType::EType)type,index); if (wearable) { if (wearable->getItemID().isNull()) @@ -299,8 +297,8 @@ void LLAgentWearables::sendAgentWearablesUpdate() LLPointer cb = new addWearableToAgentInventoryCallback( LLPointer(NULL), - type, - j, + (LLWearableType::EType)type, + index, wearable, addWearableToAgentInventoryCallback::CALL_NONE); addWearableToAgentInventory(cb, wearable); @@ -325,7 +323,7 @@ void LLAgentWearables::sendAgentWearablesUpdate() gMessageSystem->addUUIDFast(_PREHASH_SessionID, gAgent.getSessionID()); lldebugs << "sendAgentWearablesUpdate()" << llendl; - // MULTI-WEARABLE: update for multi-wearables after server-side support is in. + // MULTI-WEARABLE: DEPRECATED: HACK: index to 0- server database tables don't support concept of multiwearables. for (S32 type=0; type < LLWearableType::WT_COUNT; ++type) { gMessageSystem->nextBlockFast(_PREHASH_WearableData); @@ -333,7 +331,6 @@ void LLAgentWearables::sendAgentWearablesUpdate() U8 type_u8 = (U8)type; gMessageSystem->addU8Fast(_PREHASH_WearableType, type_u8); - // MULTI-WEARABLE: TODO: hacked index to 0, needs to loop over all once messages support this. LLWearable* wearable = getWearable((LLWearableType::EType)type, 0); if (wearable) { @@ -405,7 +402,7 @@ void LLAgentWearables::saveWearable(const LLWearableType::EType type, const U32 LLPointer cb = new addWearableToAgentInventoryCallback( LLPointer(NULL), - (S32)type, + type, index, new_wearable, todo); @@ -871,7 +868,7 @@ BOOL LLAgentWearables::isWearingItem(const LLUUID& item_id) const return FALSE; } -// MULTI-WEARABLE: update for multiple +// MULTI-WEARABLE: DEPRECATED (see backwards compatibility) // static // ! BACKWARDS COMPATIBILITY ! When we stop supporting viewer1.23, we can assume // that viewers have a Current Outfit Folder and won't need this message, and thus @@ -909,7 +906,7 @@ void LLAgentWearables::processAgentInitialWearablesUpdate(LLMessageSystem* mesgs //lldebugs << "processAgentInitialWearablesUpdate()" << llendl; // Add wearables - // MULTI-WEARABLE: TODO: update once messages change. Currently use results to populate the zeroth element. + // MULTI-WEARABLE: DEPRECATED: Message only supports one wearable per type, will be ignored in future. gAgentWearables.mItemsAwaitingWearableUpdate.clear(); for (S32 i=0; i < num_wearables; i++) { @@ -939,10 +936,10 @@ void LLAgentWearables::processAgentInitialWearablesUpdate(LLMessageSystem* mesgs continue; } - // MULTI-WEARABLE: TODO: update once messages change. Currently use results to populate the zeroth element. + // MULTI-WEARABLE: DEPRECATED: this message only supports one wearable per type. Should be ignored in future versions // Store initial wearables data until we know whether we have the current outfit folder or need to use the data. - LLInitialWearablesFetch::InitialWearableData wearable_data(type, item_id, asset_id); // MULTI-WEARABLE: update + LLInitialWearablesFetch::InitialWearableData wearable_data(type, item_id, asset_id); outfit->add(wearable_data); } @@ -977,7 +974,6 @@ void LLAgentWearables::recoverMissingWearable(const LLWearableType::EType type, lldebugs << "Wearable " << LLWearableType::getTypeLabel(type) << " could not be downloaded. Replaced inventory item with default wearable." << llendl; LLWearable* new_wearable = LLWearableList::instance().createNewWearable(type); - S32 type_s32 = (S32) type; setWearable(type,index,new_wearable); //new_wearable->writeToAvatar(TRUE); @@ -988,7 +984,7 @@ void LLAgentWearables::recoverMissingWearable(const LLWearableType::EType type, LLPointer cb = new addWearableToAgentInventoryCallback( LLPointer(NULL), - type_s32, + type, index, new_wearable, addWearableToAgentInventoryCallback::CALL_RECOVERDONE); @@ -1172,158 +1168,6 @@ void LLAgentWearables::createStandardWearablesAllDone() gAgentAvatarp->onFirstTEMessageReceived(); } -// MULTI-WEARABLE: Properly handle multiwearables later. -void LLAgentWearables::getAllWearablesArray(LLDynamicArray& wearables) -{ - for( S32 i = 0; i < LLWearableType::WT_COUNT; ++i ) - { - if (getWearableCount((LLWearableType::EType) i) != 0) - { - wearables.push_back(i); - } - } -} - -// Note: wearables_to_include should be a list of LLWearableType::EType types -// attachments_to_include should be a list of attachment points -void LLAgentWearables::makeNewOutfit(const std::string& new_folder_name, - const LLDynamicArray& wearables_to_include, - const LLDynamicArray& attachments_to_include, - BOOL rename_clothing) -{ - if (!isAgentAvatarValid()) return; - - // First, make a folder in the Clothes directory. - LLUUID folder_id = gInventory.createNewCategory( - gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING), - LLFolderType::FT_NONE, - new_folder_name); - - bool found_first_item = false; - - /////////////////// - // Wearables - - if (wearables_to_include.count()) - { - // Then, iterate though each of the wearables and save copies of them in the folder. - S32 i; - S32 count = wearables_to_include.count(); - LLDynamicArray delete_items; - LLPointer cbdone = NULL; - for (i = 0; i < count; ++i) - { - const S32 type = wearables_to_include[i]; - for (U32 j=0; jgetTypeLabel()); - LLStringUtil::truncate(new_name, DB_INV_ITEM_NAME_STR_LEN); - new_wearable->setName(new_name); - } - - LLViewerInventoryItem* item = gInventory.getItem(getWearableItemID((LLWearableType::EType)type,j)); - S32 todo = addWearableToAgentInventoryCallback::CALL_NONE; - if (!found_first_item) - { - found_first_item = true; - /* set the focus to the first item */ - todo |= addWearableToAgentInventoryCallback::CALL_MAKENEWOUTFITDONE; - /* send the agent wearables update when done */ - cbdone = new sendAgentWearablesUpdateCallback; - } - LLPointer cb = - new addWearableToAgentInventoryCallback( - cbdone, - type, - j, - new_wearable, - todo); - llassert(item); - if (item) - { - if (isWearableCopyable((LLWearableType::EType)type, j)) - { - copy_inventory_item( - gAgent.getID(), - item->getPermissions().getOwner(), - item->getUUID(), - folder_id, - new_name, - cb); - } - else - { - move_inventory_item( - gAgent.getID(), - gAgent.getSessionID(), - item->getUUID(), - folder_id, - new_name, - cb); - } - } - } - } - } - gInventory.notifyObservers(); - } - - - /////////////////// - // Attachments - - if (attachments_to_include.count()) - { - BOOL msg_started = FALSE; - LLMessageSystem* msg = gMessageSystem; - for (S32 i = 0; i < attachments_to_include.count(); i++) - { - S32 attachment_pt = attachments_to_include[i]; - LLViewerJointAttachment* attachment = get_if_there(gAgentAvatarp->mAttachmentPoints, attachment_pt, (LLViewerJointAttachment*)NULL); - if (!attachment) continue; - for (LLViewerJointAttachment::attachedobjs_vec_t::iterator attachment_iter = attachment->mAttachedObjects.begin(); - attachment_iter != attachment->mAttachedObjects.end(); - ++attachment_iter) - { - LLViewerObject *attached_object = (*attachment_iter); - if(!attached_object) continue; - const LLUUID& item_id = (*attachment_iter)->getItemID(); - if(item_id.isNull()) continue; - LLInventoryItem* item = gInventory.getItem(item_id); - if(!item) continue; - if(!msg_started) - { - msg_started = TRUE; - msg->newMessage("CreateNewOutfitAttachments"); - msg->nextBlock("AgentData"); - msg->addUUID("AgentID", gAgent.getID()); - msg->addUUID("SessionID", gAgent.getSessionID()); - msg->nextBlock("HeaderData"); - msg->addUUID("NewFolderID", folder_id); - } - msg->nextBlock("ObjectData"); - msg->addUUID("OldItemID", item_id); - msg->addUUID("OldFolderID", item->getParentUUID()); - } - } - - if (msg_started) - { - gAgent.sendReliableMessage(); - } - - } -} class LLShowCreatedOutfit: public LLInventoryCallback { @@ -2010,7 +1854,7 @@ BOOL LLAgentWearables::areWearablesLoaded() const return mWearablesLoaded; } -// MULTI-WEARABLE: update for multiple indices. +// MULTI-WEARABLE: DEPRECATED: item pending count relies on old messages that don't support multi-wearables. do not trust to be accurate void LLAgentWearables::updateWearablesLoaded() { mWearablesLoaded = (itemUpdatePendingCount()==0); diff --git a/indra/newview/llagentwearables.h b/indra/newview/llagentwearables.h index def16e4e85..27453b5b25 100644 --- a/indra/newview/llagentwearables.h +++ b/indra/newview/llagentwearables.h @@ -62,7 +62,6 @@ public: void cleanup(); void dump(); protected: - // MULTI-WEARABLE: assuming one per type. Type is called index - rename. void createStandardWearablesDone(S32 type, U32 index/* = 0*/); void createStandardWearablesAllDone(); @@ -93,7 +92,6 @@ public: const LLWearable* getWearableFromItemID(const LLUUID& item_id) const; LLWearable* getWearableFromAssetID(const LLUUID& asset_id); LLInventoryItem* getWearableInventoryItem(LLWearableType::EType type, U32 index /*= 0*/); - // MULTI-WEARABLE: assuming one per type. static BOOL selfHasWearable(LLWearableType::EType type); LLWearable* getWearable(const LLWearableType::EType type, U32 index /*= 0*/); const LLWearable* getWearable(const LLWearableType::EType type, U32 index /*= 0*/) const; @@ -128,7 +126,7 @@ protected: LLWearable* wearable, const LLUUID& category_id = LLUUID::null, BOOL notify = TRUE); - void addWearabletoAgentInventoryDone(const S32 type, + void addWearabletoAgentInventoryDone(const LLWearableType::EType type, const U32 index, const LLUUID& item_id, LLWearable* wearable); @@ -163,15 +161,6 @@ protected: // Outfits //-------------------------------------------------------------------- public: - void getAllWearablesArray(LLDynamicArray& wearables); - - // Note: wearables_to_include should be a list of LLWearableType::EType types - // attachments_to_include should be a list of attachment points - void makeNewOutfit(const std::string& new_folder_name, - const LLDynamicArray& wearables_to_include, - const LLDynamicArray& attachments_to_include, - BOOL rename_clothing); - // Should only be called if we *know* we've never done so before, since users may // not want the Library outfits to stay in their quick outfit selector and can delete them. @@ -184,7 +173,6 @@ private: // Save Wearables //-------------------------------------------------------------------- public: - // MULTI-WEARABLE: assumes one per type. void saveWearableAs(const LLWearableType::EType type, const U32 index, const std::string& new_name, BOOL save_in_lost_and_found); void saveWearable(const LLWearableType::EType type, const U32 index, BOOL send_update = TRUE); void saveAllWearables(); @@ -249,7 +237,7 @@ private: class addWearableToAgentInventoryCallback : public LLInventoryCallback { public: - enum EType + enum ETodo { CALL_NONE = 0, CALL_UPDATE = 1, @@ -259,16 +247,14 @@ private: CALL_WEARITEM = 16 }; - // MULTI-WEARABLE: index is an LLWearableType::EType - more confusing usage. - // MULTI-WEARABLE: need to have type and index args both? addWearableToAgentInventoryCallback(LLPointer cb, - S32 type, + LLWearableType::EType type, U32 index, LLWearable* wearable, U32 todo = CALL_NONE); virtual void fire(const LLUUID& inv_item); private: - S32 mType; + LLWearableType::EType mType; U32 mIndex; LLWearable* mWearable; U32 mTodo; diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 4aef72ab0b..7d39ba30f0 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -1469,14 +1469,21 @@ void LLAppearanceMgr::addCOFItemLink(const LLInventoryItem *item, bool do_update { // Are these links to the same object? const LLViewerInventoryItem* inv_item = item_array.get(i).get(); + const LLWearableType::EType wearable_type = inv_item->getWearableType(); + + const bool is_body_part = (wearable_type == LLWearableType::WT_SHAPE) + || (wearable_type == LLWearableType::WT_HAIR) + || (wearable_type == LLWearableType::WT_EYES) + || (wearable_type == LLWearableType::WT_SKIN); + if (inv_item->getLinkedUUID() == vitem->getLinkedUUID()) { linked_already = true; } - // Are these links to different items of the same wearable + // Are these links to different items of the same body part // type? If so, new item will replace old. - // MULTI-WEARABLES: revisit if more than one per type is allowed. - else if (FALSE/*areMatchingWearables(vitem,inv_item)*/) + // TODO: MULTI-WEARABLE: check for wearable limit for clothing types + else if (is_body_part) { if (inv_item->getIsLinkType()) { diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index b9b4fa8b03..ab7eeae3e8 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -4980,18 +4980,20 @@ void LLWearableBridge::removeAllClothesFromAvatar() if (itype == LLWearableType::WT_SHAPE || itype == LLWearableType::WT_SKIN || itype == LLWearableType::WT_HAIR || itype == LLWearableType::WT_EYES) continue; - // MULTI-WEARABLES: fixed to index 0 - LLViewerInventoryItem *item = dynamic_cast( - gAgentWearables.getWearableInventoryItem((LLWearableType::EType)itype, 0)); - if (!item) - continue; - const LLUUID &item_id = gInventory.getLinkedItemID(item->getUUID()); - const LLWearable *wearable = gAgentWearables.getWearableFromItemID(item_id); - if (!wearable) - continue; - - // Find and remove this item from the COF. - LLAppearanceMgr::instance().removeCOFItemLinks(item_id,false); + for (S32 index = gAgentWearables.getWearableCount(itype)-1; index >= 0 ; --index) + { + LLViewerInventoryItem *item = dynamic_cast( + gAgentWearables.getWearableInventoryItem((LLWearableType::EType)itype, index)); + if (!item) + continue; + const LLUUID &item_id = gInventory.getLinkedItemID(item->getUUID()); + const LLWearable *wearable = gAgentWearables.getWearableFromItemID(item_id); + if (!wearable) + continue; + + // Find and remove this item from the COF. + LLAppearanceMgr::instance().removeCOFItemLinks(item_id,false); + } } gInventory.notifyObservers(); diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index c245650d51..729424353f 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -292,8 +292,8 @@ void handle_toggle_pg(void*); void handle_dump_attachments(void *); void handle_dump_avatar_local_textures(void*); void handle_debug_avatar_textures(void*); -void handle_grab_texture(void*); -BOOL enable_grab_texture(void*); +void handle_grab_baked_texture(void*); +BOOL enable_grab_baked_texture(void*); void handle_dump_region_object_cache(void*); BOOL enable_save_into_inventory(void*); @@ -1460,28 +1460,28 @@ class LLAdvancedGrabBakedTexture : public view_listener_t std::string texture_type = userdata.asString(); if ("iris" == texture_type) { - handle_grab_texture( (void*)TEX_EYES_BAKED ); + handle_grab_baked_texture( (void*)BAKED_EYES ); } else if ("head" == texture_type) { - handle_grab_texture( (void*)TEX_HEAD_BAKED ); + handle_grab_baked_texture( (void*)BAKED_HEAD ); } else if ("upper" == texture_type) { - handle_grab_texture( (void*)TEX_UPPER_BAKED ); + handle_grab_baked_texture( (void*)BAKED_UPPER ); } else if ("lower" == texture_type) { - handle_grab_texture( (void*)TEX_SKIRT_BAKED ); + handle_grab_baked_texture( (void*)BAKED_LOWER ); } else if ("skirt" == texture_type) { - handle_grab_texture( (void*)TEX_SKIRT_BAKED ); + handle_grab_baked_texture( (void*)BAKED_SKIRT ); } else if ("hair" == texture_type) { - handle_grab_texture( (void*)TEX_HAIR_BAKED ); -} + handle_grab_baked_texture( (void*)BAKED_HAIR ); + } return true; } @@ -1496,23 +1496,27 @@ class LLAdvancedEnableGrabBakedTexture : public view_listener_t if ("iris" == texture_type) { - new_value = enable_grab_texture( (void*)TEX_EYES_BAKED ); + new_value = enable_grab_baked_texture( (void*)BAKED_EYES ); } else if ("head" == texture_type) { - new_value = enable_grab_texture( (void*)TEX_HEAD_BAKED ); + new_value = enable_grab_baked_texture( (void*)BAKED_HEAD ); } else if ("upper" == texture_type) { - new_value = enable_grab_texture( (void*)TEX_UPPER_BAKED ); + new_value = enable_grab_baked_texture( (void*)BAKED_UPPER ); } else if ("lower" == texture_type) { - new_value = enable_grab_texture( (void*)TEX_LOWER_BAKED ); + new_value = enable_grab_baked_texture( (void*)BAKED_LOWER ); } else if ("skirt" == texture_type) { - new_value = enable_grab_texture( (void*)TEX_SKIRT_BAKED ); + new_value = enable_grab_baked_texture( (void*)BAKED_SKIRT ); + } + else if ("hair" == texture_type) + { + new_value = enable_grab_baked_texture( (void*)BAKED_HAIR ); } return new_value; @@ -6933,27 +6937,20 @@ void handle_debug_avatar_textures(void*) } } -void handle_grab_texture(void* data) +void handle_grab_baked_texture(void* data) { - ETextureIndex tex_index = (ETextureIndex)((intptr_t)data); + EBakedTextureIndex baked_tex_index = (EBakedTextureIndex)((intptr_t)data); if (!isAgentAvatarValid()) return; - // MULTI-WEARABLE: change to support an index - const LLUUID& asset_id = gAgentAvatarp->grabLocalTexture(tex_index, 0); + const LLUUID& asset_id = gAgentAvatarp->grabBakedTexture(baked_tex_index); LL_INFOS("texture") << "Adding baked texture " << asset_id << " to inventory." << llendl; LLAssetType::EType asset_type = LLAssetType::AT_TEXTURE; LLInventoryType::EType inv_type = LLInventoryType::IT_TEXTURE; const LLUUID folder_id = gInventory.findCategoryUUIDForType(LLFolderType::assetTypeToFolderType(asset_type)); if(folder_id.notNull()) { - std::string name = "Unknown"; - const LLVOAvatarDictionary::TextureEntry *texture_dict = LLVOAvatarDictionary::getInstance()->getTexture(tex_index); - if (texture_dict->mIsBakedTexture) - { - EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex; - name = "Baked " + LLVOAvatarDictionary::getInstance()->getBakedTexture(baked_index)->mNameCapitalized; - } - name += " Texture"; + std::string name; + name = "Baked " + LLVOAvatarDictionary::getInstance()->getBakedTexture(baked_tex_index)->mNameCapitalized + " Texture"; LLUUID item_id; item_id.generate(); @@ -7006,13 +7003,12 @@ void handle_grab_texture(void* data) } } -BOOL enable_grab_texture(void* data) +BOOL enable_grab_baked_texture(void* data) { - ETextureIndex index = (ETextureIndex)((intptr_t)data); + EBakedTextureIndex index = (EBakedTextureIndex)((intptr_t)data); if (isAgentAvatarValid()) { - // MULTI-WEARABLE: - return gAgentAvatarp->canGrabLocalTexture(index,0); + return gAgentAvatarp->canGrabBakedTexture(index); } return FALSE; } diff --git a/indra/newview/llvoavatarself.cpp b/indra/newview/llvoavatarself.cpp index 8bf082ad45..ac7adebb3a 100644 --- a/indra/newview/llvoavatarself.cpp +++ b/indra/newview/llvoavatarself.cpp @@ -1798,21 +1798,31 @@ void LLVOAvatarSelf::bakedTextureUpload(EBakedTextureIndex index, BOOL finished) mBakedTextureTimes[index][done] = mSelfLoadTimer.getElapsedTimeF32(); } -const LLUUID& LLVOAvatarSelf::grabLocalTexture(ETextureIndex type, U32 index) const +const LLUUID& LLVOAvatarSelf::grabBakedTexture(EBakedTextureIndex baked_index) const { - if (canGrabLocalTexture(type, index)) + if (canGrabBakedTexture(baked_index)) { - return getTEImage( type )->getID(); + ETextureIndex tex_index = LLVOAvatarDictionary::bakedToLocalTextureIndex(baked_index); + if (tex_index == TEX_NUM_INDICES) + { + return LLUUID::null; + } + return getTEImage( tex_index )->getID(); } return LLUUID::null; } -BOOL LLVOAvatarSelf::canGrabLocalTexture(ETextureIndex type, U32 index) const +BOOL LLVOAvatarSelf::canGrabBakedTexture(EBakedTextureIndex baked_index) const { + ETextureIndex tex_index = LLVOAvatarDictionary::bakedToLocalTextureIndex(baked_index); + if (tex_index == TEX_NUM_INDICES) + { + return FALSE; + } // Check if the texture hasn't been baked yet. - if (!isTextureDefined(type, index)) + if (!isTextureDefined(tex_index, 0)) { - lldebugs << "getTEImage( " << (U32) type << ", " << index << " )->getID() == IMG_DEFAULT_AVATAR" << llendl; + lldebugs << "getTEImage( " << (U32) tex_index << " )->getID() == IMG_DEFAULT_AVATAR" << llendl; return FALSE; } @@ -1822,13 +1832,7 @@ BOOL LLVOAvatarSelf::canGrabLocalTexture(ETextureIndex type, U32 index) const // Check permissions of textures that show up in the // baked texture. We don't want people copying people's // work via baked textures. - /* switch(type) - case TEX_EYES_BAKED: - textures.push_back(TEX_EYES_IRIS); */ - const LLVOAvatarDictionary::TextureEntry *texture_dict = LLVOAvatarDictionary::getInstance()->getTexture(type); - if (!texture_dict->mIsUsedByBakedTexture) return FALSE; - const EBakedTextureIndex baked_index = texture_dict->mBakedTextureIndex; const LLVOAvatarDictionary::BakedEntry *baked_dict = LLVOAvatarDictionary::getInstance()->getBakedTexture(baked_index); for (texture_vec_t::const_iterator iter = baked_dict->mLocalTextures.begin(); iter != baked_dict->mLocalTextures.end(); diff --git a/indra/newview/llvoavatarself.h b/indra/newview/llvoavatarself.h index 03856b3a3b..587c2363f6 100644 --- a/indra/newview/llvoavatarself.h +++ b/indra/newview/llvoavatarself.h @@ -201,8 +201,6 @@ public: LLViewerFetchedTexture* getLocalTextureGL(LLVOAvatarDefines::ETextureIndex type, U32 index) const; const LLUUID& getLocalTextureID(LLVOAvatarDefines::ETextureIndex type, U32 index) const; void setLocalTextureTE(U8 te, LLViewerTexture* image, U32 index); - const LLUUID& grabLocalTexture(LLVOAvatarDefines::ETextureIndex type, U32 index) const; - BOOL canGrabLocalTexture(LLVOAvatarDefines::ETextureIndex type, U32 index) const; /*virtual*/ void setLocalTexture(LLVOAvatarDefines::ETextureIndex type, LLViewerTexture* tex, BOOL baked_version_exits, U32 index); protected: /*virtual*/ void setBakedReady(LLVOAvatarDefines::ETextureIndex type, BOOL baked_version_exists, U32 index); @@ -252,6 +250,10 @@ public: void setupComposites(); void updateComposites(); + const LLUUID& grabBakedTexture(LLVOAvatarDefines::EBakedTextureIndex baked_index) const; + BOOL canGrabBakedTexture(LLVOAvatarDefines::EBakedTextureIndex baked_index) const; + + //-------------------------------------------------------------------- // Scratch textures (used for compositing) //-------------------------------------------------------------------- -- cgit v1.2.3 From e4efbc69ed013294f930fc2fd16ba8f863a18e2d Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Wed, 19 May 2010 17:09:54 -0700 Subject: EXT-7410 Update Vivox SDK to 3.1.0001.8744 --- install.xml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/install.xml b/install.xml index 0c2fc60d3a..12bd7e625b 100644 --- a/install.xml +++ b/install.xml @@ -950,7 +950,7 @@ anguage Infrstructure (CLI) international standard md5sum 1956228a93537f250b92f2929fa4ea40 url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/llqtwebkit-4.6+cookies-darwin-20100415.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/llqtwebkit-4.6+cookies-darwin-20100415.tar.bz2 linux @@ -1386,23 +1386,23 @@ anguage Infrstructure (CLI) international standard darwin md5sum - 3b7fa3a7ac07034a747759f22956b6d5 + 4d29351a842fafe617de65a8183da160 url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/vivox-3.1.0001.8385-darwin-20100412.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/vivox-3.1.0001.8744-darwin-20100519.tar.bz2 linux md5sum - 3f834e00fa06e636814f22ad8685e407 + 7541138c439b1c0312610d18968f27d2 url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/vivox-3.1.0001.8385-linux-20100412.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/vivox-3.1.0001.8744-linux-20100519.tar.bz2 windows md5sum - 089a715a33cb48e030c9206966dfe31b + 5d2b049ca5239da2dcebde91f7f25a43 url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/vivox-3.1.0001.8385-windows-20100412.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/vivox-3.1.0001.8744-windows-20100519.tar.bz2 -- cgit v1.2.3 From 47a398e52f70b9501e1d62512fd40398092b47a6 Mon Sep 17 00:00:00 2001 From: Richard Nelson Date: Wed, 19 May 2010 17:11:28 -0700 Subject: EXT-6534 FIXED Snapshot to Disk ... causes snapshot of the active window with a background to the correct size but the window the same size as original --- indra/newview/llviewerwindow.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index d8277b0681..11e8f0de77 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -4007,9 +4007,6 @@ BOOL LLViewerWindow::rawSnapshot(LLImageRaw *raw, S32 image_width, S32 image_hei // PRE SNAPSHOT gDisplaySwapBuffers = FALSE; - // if not showing ui, use full window to render world view - updateWorldViewRect(!show_ui); - glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); setCursor(UI_CURSOR_WAIT); @@ -4082,6 +4079,9 @@ BOOL LLViewerWindow::rawSnapshot(LLImageRaw *raw, S32 image_width, S32 image_hei //else: keep the current scene scale, re-scale it if necessary after reading out. } + // if not showing ui, use full window to render world view + updateWorldViewRect(!show_ui); + S32 buffer_x_offset = llfloor(((window_width - snapshot_width) * scale_factor) / 2.f); S32 buffer_y_offset = llfloor(((window_height - snapshot_height) * scale_factor) / 2.f); -- cgit v1.2.3 From f6fc09e4fa1d3c6e54f639ae139603a7962c3684 Mon Sep 17 00:00:00 2001 From: Monroe Linden Date: Wed, 19 May 2010 17:26:25 -0700 Subject: Updated Mac build of llqtwebkit, adding some support we'll need to fix EXT-5667 (support window.open() in shared media so google docs will work). revision aacdf69cbf5aa12d77c179296e31ef643ed1ef4a in http://qt.gitorious.org/+lindenqt/qt/lindenqt (currently head of the 'lindenqt' branch) revision e2eaaf8b3e96 in http://bitbucket.org/lindenlab/llqtwebkit/ (currently head of the default branch) --- install.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install.xml b/install.xml index 0c2fc60d3a..07a2a6b58d 100644 --- a/install.xml +++ b/install.xml @@ -948,9 +948,9 @@ anguage Infrstructure (CLI) international standard darwin md5sum - 1956228a93537f250b92f2929fa4ea40 + 1417e04ca4bec1ed281c43d37b613845 url - http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/llqtwebkit-4.6+cookies-darwin-20100415.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/llqtwebkit-4.6+cookies-darwin-20100519.tar.bz2 linux -- cgit v1.2.3 From 42796c938ba713b8f3aaff1b5d43d54021e223b8 Mon Sep 17 00:00:00 2001 From: Monroe Linden Date: Wed, 19 May 2010 17:35:19 -0700 Subject: Updated Windows build of llqtwebkit, adding some support we'll need to fix EXT-5667 (support window.open() in shared media so google docs will work). revision aacdf69cbf5aa12d77c179296e31ef643ed1ef4a in http://qt.gitorious.org/+lindenqt/qt/lindenqt (currently head of the 'lindenqt' branch) revision ff8a72a3078b in http://bitbucket.org/lindenlab/llqtwebkit/ (currently head of the default branch) --- install.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install.xml b/install.xml index 07a2a6b58d..40c58efc47 100644 --- a/install.xml +++ b/install.xml @@ -962,9 +962,9 @@ anguage Infrstructure (CLI) international standard windows md5sum - b873755dff5f4221b5a3ba63129435a7 + d682ed6232193b4eab3170657a54ebf9 url - http://viewer-source-downloads.s3.amazonaws.com/install_pkgs/llqtwebkit-windows-qt4.6-cookies-20100402.tar.bz2 + http://s3.amazonaws.com/viewer-source-downloads/install_pkgs/llqtwebkit-windows-qt4.6-20100519.tar.bz2 -- cgit v1.2.3 From da09b1dde1e09cd5c43114321479da3b22df1355 Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Wed, 19 May 2010 18:36:51 -0700 Subject: EXT-7388 - --grid command line argument doesn't do anything. Grid selection code was looking up the grid by grid host id not grid label. As SLE grids use their dns name as their lable, it worked there. However, the beta grids use a short label. --- indra/newview/llviewernetwork.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/indra/newview/llviewernetwork.cpp b/indra/newview/llviewernetwork.cpp index 7812867667..c76eee80f7 100644 --- a/indra/newview/llviewernetwork.cpp +++ b/indra/newview/llviewernetwork.cpp @@ -257,7 +257,11 @@ void LLGridManager::initialize(const std::string& grid_file) // load a grid from the command line. // if the actual grid name is specified from the command line, // set it as the 'selected' grid. - mGrid = gSavedSettings.getString("CmdLineGridChoice"); + std::string cmd_line_grid = gSavedSettings.getString("CmdLineGridChoice"); + if(!cmd_line_grid.empty()) + { + mGrid = getGridByLabel(cmd_line_grid); + } LL_INFOS("GridManager") << "Grid Name: " << mGrid << LL_ENDL; // If a command line login URI was passed in, so we should add the command -- cgit v1.2.3 From d4b37309cb00108a761259d570ae9057658ad215 Mon Sep 17 00:00:00 2001 From: Roxie Linden Date: Thu, 20 May 2010 00:02:26 -0700 Subject: EXT-6734 - Unable to run several copies of viewer On startup, a piece of code in llappviewer.cpp checks for the existence and validity of a slurl entered on the command line. If there is a slurl, then the slurl is sent to another instance of the viewer if one is running (for windows and linux only.) After the SLE refactor of slurls, the startup slurl was set to LAST by default. As this is a valid slurl the call to pass the slurl to another viewer was made, ignoring the codepath that checks for multiple viewer launches. The fix defaults the startup slurl to INVALID. Now, when no slurl is passed in on the command line, the code that sends the slurl to another instance is skipped and the 'multiple' flag is checked appropriately. --- indra/newview/llslurl.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llslurl.h b/indra/newview/llslurl.h index 28c23561cf..1210c398f1 100644 --- a/indra/newview/llslurl.h +++ b/indra/newview/llslurl.h @@ -65,7 +65,7 @@ public: }; - LLSLURL(): mType(LAST_LOCATION) { } + LLSLURL(): mType(INVALID) { } LLSLURL(const std::string& slurl); LLSLURL(const std::string& grid, const std::string& region); LLSLURL(const std::string& region, const LLVector3& position); -- cgit v1.2.3 From 1e48e25558e657d82f711578e54304c745fa9347 Mon Sep 17 00:00:00 2001 From: Yuri Chebotarev Date: Thu, 20 May 2010 10:45:39 +0300 Subject: EXT-7051 additional fix - add more hardcoded words to translated dictionary reviewed by Mike Antipov at https://codereview.productengine.com/secondlife/r/414/ --HG-- branch : product-engine --- indra/newview/llviewerinventory.cpp | 1 + indra/newview/skins/default/xui/en/strings.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/indra/newview/llviewerinventory.cpp b/indra/newview/llviewerinventory.cpp index a2331bd69a..f0532d5a31 100644 --- a/indra/newview/llviewerinventory.cpp +++ b/indra/newview/llviewerinventory.cpp @@ -101,6 +101,7 @@ public: mInventoryItemsDict["Female Gestures"] = LLTrans::getString("Female Gestures"); mInventoryItemsDict["Other Gestures"] = LLTrans::getString("Other Gestures"); mInventoryItemsDict["Speech Gestures"] = LLTrans::getString("Speech Gestures"); + mInventoryItemsDict["Common Gestures"] = LLTrans::getString("Common Gestures"); //predefined gestures diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index bf28e78cf6..f8bb36b88a 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -3142,7 +3142,7 @@ Abuse Report Female Gestures Other Gestures Speech Gestures - + Common Gestures Male - Excuse me Male - Get lost -- cgit v1.2.3 From 56e88c6b2e656a28b25154e27c05e7ced65d90a8 Mon Sep 17 00:00:00 2001 From: Yuri Chebotarev Date: Thu, 20 May 2010 10:49:15 +0300 Subject: EXT-7367 FIX - eat sroll wheel above gesture popup list reviwed by Mike Antipov at https://codereview.productengine.com/secondlife/r/411 --HG-- branch : product-engine --- indra/newview/llnearbychatbar.cpp | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/indra/newview/llnearbychatbar.cpp b/indra/newview/llnearbychatbar.cpp index bd504906d5..7a525596a0 100644 --- a/indra/newview/llnearbychatbar.cpp +++ b/indra/newview/llnearbychatbar.cpp @@ -69,6 +69,25 @@ static LLChatTypeTrigger sChatTypeTriggers[] = { { "/shout" , CHAT_TYPE_SHOUT} }; +//ext-7367 +//Problem: gesture list control (actually LLScrollListCtrl) didn't actually process mouse wheel message. +// introduce new gesture list subclass to "eat" mouse wheel messages (and probably some other messages) +class LLGestureScrollListCtrl: public LLScrollListCtrl +{ +protected: + friend class LLUICtrlFactory; + LLGestureScrollListCtrl(const LLScrollListCtrl::Params& params) + :LLScrollListCtrl(params) + { + } +public: + BOOL handleScrollWheel(S32 x, S32 y, S32 clicks) + { + LLScrollListCtrl::handleScrollWheel( x, y, clicks ); + return TRUE; + } +}; + LLGestureComboList::Params::Params() : combo_button("combo_button"), combo_list("combo_list") @@ -90,13 +109,14 @@ LLGestureComboList::LLGestureComboList(const LLGestureComboList::Params& p) addChild(mButton); - LLScrollListCtrl::Params params = p.combo_list; + LLGestureScrollListCtrl::Params params(p.combo_list); + params.name("GestureComboList"); params.commit_callback.function(boost::bind(&LLGestureComboList::onItemSelected, this, _2)); params.visible(false); params.commit_on_keyboard_movement(false); - mList = LLUICtrlFactory::create(params); + mList = LLUICtrlFactory::create(params); addChild(mList); //****************************Gesture Part********************************/ -- cgit v1.2.3 From 13d2501c847929ce9ee155dbb4dcfcf166710d3e Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Thu, 20 May 2010 11:38:57 +0100 Subject: SNOW-207/DEV-42245 : FIXED : Texture loading on sculpty plants is failing making content appear to be of poor quality. Patch by Thickbrick, reviewed by me. --- doc/contributions.txt | 1 + indra/newview/llface.cpp | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/doc/contributions.txt b/doc/contributions.txt index 5f209a6675..3f33763ce0 100644 --- a/doc/contributions.txt +++ b/doc/contributions.txt @@ -608,6 +608,7 @@ tenebrous pau Tharax Ferraris VWR-605 Thickbrick Sleaford + SNOW-207 VWR-7109 VWR-9287 VWR-13483 diff --git a/indra/newview/llface.cpp b/indra/newview/llface.cpp index a1336815f7..e0e5b32299 100644 --- a/indra/newview/llface.cpp +++ b/indra/newview/llface.cpp @@ -1376,9 +1376,18 @@ F32 LLFace::getTextureVirtualSize() texel_area = 1.f; } - //apply texel area to face area to get accurate ratio - //face_area /= llclamp(texel_area, 1.f/64.f, 16.f); - F32 face_area = mPixelArea / llclamp(texel_area, 0.015625f, 128.f); + F32 face_area; + if (mVObjp->isSculpted() && texel_area > 1.f) + { + //sculpts can break assumptions about texel area + face_area = mPixelArea; + } + else + { + //apply texel area to face area to get accurate ratio + //face_area /= llclamp(texel_area, 1.f/64.f, 16.f); + face_area = mPixelArea / llclamp(texel_area, 0.015625f, 128.f); + } if(face_area > LLViewerTexture::sMaxSmallImageSize) { -- cgit v1.2.3 From 49a79a3f68dccd985d2f0f2462eabc210d215736 Mon Sep 17 00:00:00 2001 From: Andrew Dyukov Date: Thu, 20 May 2010 14:31:31 +0300 Subject: EXT-7200 FIXED Integrated art missing from appearance. Most changes are in xml, but changing of view buttons overlay icons in lists from add wearables is implemented in code. Dragbar icon was only added as file, it will be integrated in EXT-7255 (There is no dragbar between 'Edit Outfit' and 'Add to Outfit:' panels). Reviewed by Neal Orman at https://codereview.productengine.com/secondlife/r/410/ --HG-- branch : product-engine --- indra/newview/llpaneloutfitedit.cpp | 4 +++ .../skins/default/textures/icons/DownArrow_Off.png | Bin 0 -> 217 bytes .../skins/default/textures/icons/Edit_Wrench.png | Bin 0 -> 724 bytes .../textures/icons/Hierarchy_View_Disabled.png | Bin 0 -> 178 bytes .../default/textures/icons/Hierarchy_View_On.png | Bin 0 -> 183 bytes .../default/textures/icons/List_View_Disabled.png | Bin 0 -> 158 bytes .../skins/default/textures/icons/List_View_On.png | Bin 0 -> 160 bytes .../skins/default/textures/icons/Locked_Icon.png | Bin 0 -> 306 bytes .../skins/default/textures/icons/Search_Icon.png | Bin 0 -> 552 bytes .../skins/default/textures/icons/Shirt_Large.png | Bin 0 -> 471 bytes .../skins/default/textures/icons/UpArrow_Off.png | Bin 0 -> 219 bytes indra/newview/skins/default/textures/textures.xml | 17 +++++++++++ .../skins/default/textures/windows/Dragbar.png | Bin 0 -> 348 bytes .../default/xui/en/panel_body_parts_list_item.xml | 11 ++++--- .../default/xui/en/panel_clothing_list_item.xml | 19 +++++------- .../xui/en/panel_dummy_clothing_list_item.xml | 2 +- .../skins/default/xui/en/panel_outfit_edit.xml | 33 ++++++++++++++------- .../skins/default/xui/en/sidepanel_appearance.xml | 2 +- 18 files changed, 59 insertions(+), 29 deletions(-) create mode 100644 indra/newview/skins/default/textures/icons/DownArrow_Off.png create mode 100644 indra/newview/skins/default/textures/icons/Edit_Wrench.png create mode 100644 indra/newview/skins/default/textures/icons/Hierarchy_View_Disabled.png create mode 100644 indra/newview/skins/default/textures/icons/Hierarchy_View_On.png create mode 100644 indra/newview/skins/default/textures/icons/List_View_Disabled.png create mode 100644 indra/newview/skins/default/textures/icons/List_View_On.png create mode 100644 indra/newview/skins/default/textures/icons/Locked_Icon.png create mode 100644 indra/newview/skins/default/textures/icons/Search_Icon.png create mode 100644 indra/newview/skins/default/textures/icons/Shirt_Large.png create mode 100644 indra/newview/skins/default/textures/icons/UpArrow_Off.png create mode 100644 indra/newview/skins/default/textures/windows/Dragbar.png diff --git a/indra/newview/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp index 4178fd9c90..4f3f73beca 100644 --- a/indra/newview/llpaneloutfitedit.cpp +++ b/indra/newview/llpaneloutfitedit.cpp @@ -350,6 +350,8 @@ void LLPanelOutfitEdit::showFilteredWearablesPanel() if(switchPanels(mInventoryItemsPanel, mWearableItemsPanel)) { mFolderViewBtn->setToggleState(FALSE); + mFolderViewBtn->setImageOverlay(getString("folder_view_off"), mFolderViewBtn->getImageOverlayHAlign()); + mListViewBtn->setImageOverlay(getString("list_view_on"), mListViewBtn->getImageOverlayHAlign()); } mListViewBtn->setToggleState(TRUE); } @@ -359,6 +361,8 @@ void LLPanelOutfitEdit::showFilteredFolderWearablesPanel() if(switchPanels(mWearableItemsPanel, mInventoryItemsPanel)) { mListViewBtn->setToggleState(FALSE); + mListViewBtn->setImageOverlay(getString("list_view_off"), mListViewBtn->getImageOverlayHAlign()); + mFolderViewBtn->setImageOverlay(getString("folder_view_on"), mFolderViewBtn->getImageOverlayHAlign()); } mFolderViewBtn->setToggleState(TRUE); } diff --git a/indra/newview/skins/default/textures/icons/DownArrow_Off.png b/indra/newview/skins/default/textures/icons/DownArrow_Off.png new file mode 100644 index 0000000000..60f62eed4a Binary files /dev/null and b/indra/newview/skins/default/textures/icons/DownArrow_Off.png differ diff --git a/indra/newview/skins/default/textures/icons/Edit_Wrench.png b/indra/newview/skins/default/textures/icons/Edit_Wrench.png new file mode 100644 index 0000000000..1c43960d02 Binary files /dev/null and b/indra/newview/skins/default/textures/icons/Edit_Wrench.png differ diff --git a/indra/newview/skins/default/textures/icons/Hierarchy_View_Disabled.png b/indra/newview/skins/default/textures/icons/Hierarchy_View_Disabled.png new file mode 100644 index 0000000000..fb370f98d5 Binary files /dev/null and b/indra/newview/skins/default/textures/icons/Hierarchy_View_Disabled.png differ diff --git a/indra/newview/skins/default/textures/icons/Hierarchy_View_On.png b/indra/newview/skins/default/textures/icons/Hierarchy_View_On.png new file mode 100644 index 0000000000..c590a5a5d3 Binary files /dev/null and b/indra/newview/skins/default/textures/icons/Hierarchy_View_On.png differ diff --git a/indra/newview/skins/default/textures/icons/List_View_Disabled.png b/indra/newview/skins/default/textures/icons/List_View_Disabled.png new file mode 100644 index 0000000000..155dee5047 Binary files /dev/null and b/indra/newview/skins/default/textures/icons/List_View_Disabled.png differ diff --git a/indra/newview/skins/default/textures/icons/List_View_On.png b/indra/newview/skins/default/textures/icons/List_View_On.png new file mode 100644 index 0000000000..9127c92c3b Binary files /dev/null and b/indra/newview/skins/default/textures/icons/List_View_On.png differ diff --git a/indra/newview/skins/default/textures/icons/Locked_Icon.png b/indra/newview/skins/default/textures/icons/Locked_Icon.png new file mode 100644 index 0000000000..8ead280794 Binary files /dev/null and b/indra/newview/skins/default/textures/icons/Locked_Icon.png differ diff --git a/indra/newview/skins/default/textures/icons/Search_Icon.png b/indra/newview/skins/default/textures/icons/Search_Icon.png new file mode 100644 index 0000000000..541aa8f52a Binary files /dev/null and b/indra/newview/skins/default/textures/icons/Search_Icon.png differ diff --git a/indra/newview/skins/default/textures/icons/Shirt_Large.png b/indra/newview/skins/default/textures/icons/Shirt_Large.png new file mode 100644 index 0000000000..0f3846bccc Binary files /dev/null and b/indra/newview/skins/default/textures/icons/Shirt_Large.png differ diff --git a/indra/newview/skins/default/textures/icons/UpArrow_Off.png b/indra/newview/skins/default/textures/icons/UpArrow_Off.png new file mode 100644 index 0000000000..00015bce20 Binary files /dev/null and b/indra/newview/skins/default/textures/icons/UpArrow_Off.png differ diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index f4206dc2e5..0788a0ca9e 100644 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -110,6 +110,8 @@ with the same filename but different name + + @@ -119,6 +121,7 @@ with the same filename but different name + @@ -145,6 +148,9 @@ with the same filename but different name + + + @@ -255,9 +261,14 @@ with the same filename but different name + + + + + @@ -410,6 +421,8 @@ with the same filename but different name + + @@ -432,6 +445,8 @@ with the same filename but different name + + @@ -523,6 +538,8 @@ with the same filename but different name + + diff --git a/indra/newview/skins/default/textures/windows/Dragbar.png b/indra/newview/skins/default/textures/windows/Dragbar.png new file mode 100644 index 0000000000..d94dda1aae Binary files /dev/null and b/indra/newview/skins/default/textures/windows/Dragbar.png differ diff --git a/indra/newview/skins/default/xui/en/panel_body_parts_list_item.xml b/indra/newview/skins/default/xui/en/panel_body_parts_list_item.xml index 115964e5f2..e3f6045e27 100644 --- a/indra/newview/skins/default/xui/en/panel_body_parts_list_item.xml +++ b/indra/newview/skins/default/xui/en/panel_body_parts_list_item.xml @@ -52,18 +52,17 @@ name="btn_lock" layout="topleft" follows="top|right" - image_name="Lock2" - top="0" + image_name="Locked_Icon" + top="2" left="0" - height="20" - width="20" + height="13" + width="9" tab_stop="false" /> -- cgit v1.2.3 From 265f77747f9ae7719c338bd9b3e76ba59b130b68 Mon Sep 17 00:00:00 2001 From: Yuri Chebotarev Date: Thu, 20 May 2010 16:55:06 +0300 Subject: EXT-6598 FIX Handle tooltip message for separator reviewed by Vadim Savchuk at https://codereview.productengine.com/secondlife/r/417/ --HG-- branch : product-engine --- indra/newview/llnearbychatbar.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/indra/newview/llnearbychatbar.cpp b/indra/newview/llnearbychatbar.cpp index 7a525596a0..680ed35fa2 100644 --- a/indra/newview/llnearbychatbar.cpp +++ b/indra/newview/llnearbychatbar.cpp @@ -70,7 +70,7 @@ static LLChatTypeTrigger sChatTypeTriggers[] = { }; //ext-7367 -//Problem: gesture list control (actually LLScrollListCtrl) didn't actually process mouse wheel message. +//Problem: gesture list control (actually LLScrollListCtrl) didn't actually process mouse wheel message. // introduce new gesture list subclass to "eat" mouse wheel messages (and probably some other messages) class LLGestureScrollListCtrl: public LLScrollListCtrl { @@ -86,6 +86,14 @@ public: LLScrollListCtrl::handleScrollWheel( x, y, clicks ); return TRUE; } + //See EXT-6598 + //Mouse hover over separator will result in not processing tooltip message + //So eat this message + BOOL handleToolTip(S32 x, S32 y, MASK mask) + { + LLScrollListCtrl::handleToolTip( x, y, mask ); + return TRUE; + } }; LLGestureComboList::Params::Params() -- cgit v1.2.3 From 55e26cf05604a4dc71fa6683f1ec576eac779cad Mon Sep 17 00:00:00 2001 From: Yuri Chebotarev Date: Thu, 20 May 2010 16:58:20 +0300 Subject: ext-7403 fix - reset all data before setting new group id reviewed by Vadim Savchuk https://codereview.productengine.com/secondlife/r/419/ --HG-- branch : product-engine --- indra/newview/llpanelgrouproles.cpp | 29 +++++++++++++++++++++++++++++ indra/newview/llpanelgrouproles.h | 4 ++++ 2 files changed, 33 insertions(+) diff --git a/indra/newview/llpanelgrouproles.cpp b/indra/newview/llpanelgrouproles.cpp index 95ec8170f2..7dec2251e8 100644 --- a/indra/newview/llpanelgrouproles.cpp +++ b/indra/newview/llpanelgrouproles.cpp @@ -797,8 +797,37 @@ BOOL LLPanelGroupMembersSubTab::postBuildSubTab(LLView* root) void LLPanelGroupMembersSubTab::setGroupID(const LLUUID& id) { + //clear members list + if(mMembersList) mMembersList->deleteAllItems(); + if(mAssignedRolesList) mAssignedRolesList->deleteAllItems(); + if(mAllowedActionsList) mAllowedActionsList->deleteAllItems(); + LLPanelGroupSubTab::setGroupID(id); +} + +void LLPanelGroupRolesSubTab::setGroupID(const LLUUID& id) +{ + if(mRolesList) mRolesList->deleteAllItems(); + if(mAssignedMembersList) mAssignedMembersList->deleteAllItems(); + if(mAllowedActionsList) mAllowedActionsList->deleteAllItems(); + + if(mRoleName) mRoleName->clear(); + if(mRoleDescription) mRoleDescription->clear(); + if(mRoleTitle) mRoleTitle->clear(); + + setFooterEnabled(FALSE); + LLPanelGroupSubTab::setGroupID(id); +} +void LLPanelGroupActionsSubTab::setGroupID(const LLUUID& id) +{ + if(mActionList) mActionList->deleteAllItems(); + if(mActionRoles) mActionRoles->deleteAllItems(); + if(mActionMembers) mActionMembers->deleteAllItems(); + + if(mActionDescription) mActionDescription->clear(); + + LLPanelGroupSubTab::setGroupID(id); } diff --git a/indra/newview/llpanelgrouproles.h b/indra/newview/llpanelgrouproles.h index a877402041..44aa7cea38 100644 --- a/indra/newview/llpanelgrouproles.h +++ b/indra/newview/llpanelgrouproles.h @@ -257,6 +257,8 @@ public: void handleDeleteRole(); void saveRoleChanges(); + + virtual void setGroupID(const LLUUID& id); protected: void handleActionCheck(LLUICtrl* ctrl, bool force); LLSD createRoleItem(const LLUUID& role_id, std::string name, std::string title, S32 members); @@ -294,6 +296,8 @@ public: virtual void update(LLGroupChange gc); void handleActionSelect(); + + virtual void setGroupID(const LLUUID& id); protected: LLScrollListCtrl* mActionList; LLScrollListCtrl* mActionRoles; -- cgit v1.2.3 From c3a352b219380faa722be6f1ac916a7b95eb836c Mon Sep 17 00:00:00 2001 From: Mike Antipov Date: Thu, 20 May 2010 17:33:26 +0300 Subject: EXT-7371 FIXED Improved new state handling in p2p Voice channel to process session leave event correctly. Reason: * when user leave a p2p voice session himself the session's voice channel is marked as "ignore next leave session event" * but when he re-starts the voice session the channel stays in "ignore next leave session event" state. So, when an other p2p participant ends call this voice channel is not deactivated because of this flag. Fix: reset "ignore next leave session event" flag to FALSE when viewer is joining p2p voice session. Reviewed by Aimee Walton at https://codereview.productengine.com/secondlife/r/418/ --HG-- branch : product-engine --- indra/newview/llvoicechannel.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/indra/newview/llvoicechannel.cpp b/indra/newview/llvoicechannel.cpp index 25b46f8e55..1b4471a9fe 100644 --- a/indra/newview/llvoicechannel.cpp +++ b/indra/newview/llvoicechannel.cpp @@ -791,6 +791,12 @@ void LLVoiceChannelP2P::handleStatusChange(EStatusType type) } mIgnoreNextSessionLeave = FALSE; return; + case STATUS_JOINING: + // because we join session we expect to process session leave event in the future. EXT-7371 + // may be this should be done in the LLVoiceChannel::handleStatusChange. + mIgnoreNextSessionLeave = FALSE; + break; + default: break; } -- cgit v1.2.3 From ae6c2de1e33e15f7a3766007a2b20b16f99c1114 Mon Sep 17 00:00:00 2001 From: Sergei Litovchuk Date: Thu, 20 May 2010 18:57:29 +0300 Subject: EXT-5666 FIXED Changed clicked attachment color. --HG-- branch : product-engine --- indra/newview/skins/default/colors.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/skins/default/colors.xml b/indra/newview/skins/default/colors.xml index 33668f5ed6..e8a893e31b 100644 --- a/indra/newview/skins/default/colors.xml +++ b/indra/newview/skins/default/colors.xml @@ -359,7 +359,7 @@ reference="White"/> + reference="LtGray" /> -- cgit v1.2.3 From 53505bd225608d1f048ca80e0bc475d2c125db3a Mon Sep 17 00:00:00 2001 From: Andrew Polunin Date: Thu, 20 May 2010 21:16:16 +0300 Subject: EXT-7223 FIXED (Add \"Shop\" Button in bottom of Inventory panel) - Added 'MarketplaceURL' setting in settings.xml to represent URL which points to the Marketplace. - Added member mShopBtn in the class LLSidepanelInventory. - Added handler LLSidepanelInventory::onShopButtonClicked() which handles a click on the 'Shop' button - Added corresponding widget in the sidepanel_inventory.xml Reviewed by Mike Antipov at https://codereview.productengine.com/secondlife/r/408/ --HG-- branch : product-engine --- indra/newview/app_settings/settings.xml | 11 +++++++++++ indra/newview/llsidepanelinventory.cpp | 9 +++++++++ indra/newview/llsidepanelinventory.h | 1 + indra/newview/skins/default/xui/en/sidepanel_inventory.xml | 10 ++++++++++ 4 files changed, 31 insertions(+) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 7156af57ec..418032c554 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -4591,6 +4591,17 @@ Value + MarketplaceURL + + Comment + URL to the Marketplace + Persist + 0 + Type + String + Value + https://www.xstreetsl.com/modules.php?name=Marketplace + MaxDragDistance Comment diff --git a/indra/newview/llsidepanelinventory.cpp b/indra/newview/llsidepanelinventory.cpp index 65b9184fe5..fc5143d33b 100644 --- a/indra/newview/llsidepanelinventory.cpp +++ b/indra/newview/llsidepanelinventory.cpp @@ -42,6 +42,7 @@ #include "llsidepaneltaskinfo.h" #include "lltabcontainer.h" #include "llselectmgr.h" +#include "llweb.h" static LLRegisterPanelClassWrapper t_inventory("sidepanel_inventory"); @@ -70,6 +71,9 @@ BOOL LLSidepanelInventory::postBuild() mShareBtn = mInventoryPanel->getChild("share_btn"); mShareBtn->setClickedCallback(boost::bind(&LLSidepanelInventory::onShareButtonClicked, this)); + LLButton* shop_btn = mInventoryPanel->getChild("shop_btn"); + shop_btn->setClickedCallback(boost::bind(&LLSidepanelInventory::onShopButtonClicked, this)); + mWearBtn = mInventoryPanel->getChild("wear_btn"); mWearBtn->setClickedCallback(boost::bind(&LLSidepanelInventory::onWearButtonClicked, this)); @@ -155,6 +159,11 @@ void LLSidepanelInventory::onShareButtonClicked() LLAvatarActions::shareWithAvatars(); } +void LLSidepanelInventory::onShopButtonClicked() +{ + LLWeb::loadURLExternal(gSavedSettings.getString("MarketplaceURL")); +} + void LLSidepanelInventory::performActionOnSelection(const std::string &action) { LLPanelMainInventory *panel_main_inventory = mInventoryPanel->getChild("panel_main_inventory"); diff --git a/indra/newview/llsidepanelinventory.h b/indra/newview/llsidepanelinventory.h index 95eab3571c..2dc17e741d 100644 --- a/indra/newview/llsidepanelinventory.h +++ b/indra/newview/llsidepanelinventory.h @@ -79,6 +79,7 @@ private: protected: void onInfoButtonClicked(); void onShareButtonClicked(); + void onShopButtonClicked(); void onWearButtonClicked(); void onPlayButtonClicked(); void onTeleportButtonClicked(); diff --git a/indra/newview/skins/default/xui/en/sidepanel_inventory.xml b/indra/newview/skins/default/xui/en/sidepanel_inventory.xml index 812d94c55f..4c42d1f750 100644 --- a/indra/newview/skins/default/xui/en/sidepanel_inventory.xml +++ b/indra/newview/skins/default/xui/en/sidepanel_inventory.xml @@ -57,6 +57,16 @@ name="share_btn" top="0" width="102" /> + - - - - - - - - - - - - - - - - - - - - + + + + Rotate Camera Around Focus + + + Zoom Camera Towards Focus + + + Move Camera Up and Down, Left and Right + + + Camera modes + + + Orbit Zoom Pan + + + Preset Views + + + View Object + + + + + + + + + Front View + + + + + + + + Side View + + + + + + + + Rear View + + + + + + + + Object View + + + + + + + + Mouselook View + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3 From a0351c6ea4446bfa8fc85450c2603251cd703c2c Mon Sep 17 00:00:00 2001 From: Andrew Dyukov Date: Thu, 27 May 2010 18:41:28 +0300 Subject: EXT-7200 FIXED Integrated art missing from appearance. Replaced "Shop" button text with icon, changed sizes of buttons according to comments in ticket, also changed heights of hovered and selected icons for items- made them equal to heights of panels they are used on. Reviewed by Neal Orman at https://codereview.productengine.com/secondlife/r/410/ --HG-- branch : product-engine --- .../newview/skins/default/textures/icons/Shop.png | Bin 0 -> 647 bytes indra/newview/skins/default/textures/textures.xml | 1 + .../default/xui/en/panel_body_parts_list_item.xml | 32 +++++++++++------ .../xui/en/panel_bodyparts_list_button_bar.xml | 4 +-- .../xui/en/panel_clothing_list_button_bar.xml | 4 +-- .../default/xui/en/panel_clothing_list_item.xml | 39 ++++++++++++++------- .../xui/en/panel_dummy_clothing_list_item.xml | 10 +++--- .../skins/default/xui/en/sidepanel_appearance.xml | 2 +- 8 files changed, 59 insertions(+), 33 deletions(-) create mode 100644 indra/newview/skins/default/textures/icons/Shop.png diff --git a/indra/newview/skins/default/textures/icons/Shop.png b/indra/newview/skins/default/textures/icons/Shop.png new file mode 100644 index 0000000000..d7e0001dc6 Binary files /dev/null and b/indra/newview/skins/default/textures/icons/Shop.png differ diff --git a/indra/newview/skins/default/textures/textures.xml b/indra/newview/skins/default/textures/textures.xml index 57f23c7e76..29c72a414b 100644 --- a/indra/newview/skins/default/textures/textures.xml +++ b/indra/newview/skins/default/textures/textures.xml @@ -453,6 +453,7 @@ with the same filename but different name + diff --git a/indra/newview/skins/default/xui/en/panel_body_parts_list_item.xml b/indra/newview/skins/default/xui/en/panel_body_parts_list_item.xml index e3f6045e27..2edd643cc5 100644 --- a/indra/newview/skins/default/xui/en/panel_body_parts_list_item.xml +++ b/indra/newview/skins/default/xui/en/panel_body_parts_list_item.xml @@ -1,7 +1,7 @@ - + height="23" + width="23" + tab_stop="false"> + + - - - - - - - - - - - - - + + + + Rotate Camera Around Focus + + + Zoom Camera Towards Focus + + + Move Camera Up and Down, Left and Right + + + Camera modes + + + Orbit Zoom Pan + + + Preset Views + + + View Object + + + + + + + + + Front View + + + + + + + + Side View + + + + + + + + Rear View + + + + + + + + Object View + + + + + + + + Mouselook View + + + + + + + + + + + + + + + + + + + + + + diff --git a/indra/newview/skins/default/xui/en/widgets/panel_camera_item.xml b/indra/newview/skins/default/xui/en/widgets/panel_camera_item.xml index a77b81f76f..98707b8495 100644 --- a/indra/newview/skins/default/xui/en/widgets/panel_camera_item.xml +++ b/indra/newview/skins/default/xui/en/widgets/panel_camera_item.xml @@ -1,70 +1,70 @@ - - - - - - - - Text - - \ No newline at end of file + + + + + + + + Text + + -- cgit v1.2.3 From e25d0d34ba1b88008ef3bfbf40fb43f498bc1bea Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Fri, 28 May 2010 12:01:26 +0100 Subject: CID-481 Checker: FORWARD_NULL Function: LLTaskInvFVBridge::createObjectBridge(LLPanelObjectInventory *, LLInventoryObject *) File: /indra/newview/llpanelobjectinventory.cpp --- indra/newview/llpanelobjectinventory.cpp | 54 +++++++++++++++----------------- 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp index 0d3beaa9a5..c557e9b85d 100644 --- a/indra/newview/llpanelobjectinventory.cpp +++ b/indra/newview/llpanelobjectinventory.cpp @@ -1249,29 +1249,30 @@ LLTaskInvFVBridge* LLTaskInvFVBridge::createObjectBridge(LLPanelObjectInventory* { LLTaskInvFVBridge* new_bridge = NULL; const LLInventoryItem* item = dynamic_cast(object); + const U32 itemflags = ( NULL == item ? 0 : item->getFlags() ); LLAssetType::EType type = object->getType(); switch(type) { case LLAssetType::AT_TEXTURE: new_bridge = new LLTaskTextureBridge(panel, - object->getUUID(), - object->getName()); + object->getUUID(), + object->getName()); break; case LLAssetType::AT_SOUND: new_bridge = new LLTaskSoundBridge(panel, - object->getUUID(), - object->getName()); + object->getUUID(), + object->getName()); break; case LLAssetType::AT_LANDMARK: new_bridge = new LLTaskLandmarkBridge(panel, - object->getUUID(), - object->getName()); + object->getUUID(), + object->getName()); break; case LLAssetType::AT_CALLINGCARD: new_bridge = new LLTaskCallingCardBridge(panel, - object->getUUID(), - object->getName()); + object->getUUID(), + object->getName()); break; case LLAssetType::AT_SCRIPT: // OLD SCRIPTS DEPRECATED - JC @@ -1281,45 +1282,42 @@ LLTaskInvFVBridge* LLTaskInvFVBridge::createObjectBridge(LLPanelObjectInventory* // object->getName()); break; case LLAssetType::AT_OBJECT: - { - U32 flags = ( NULL == item ? 0 : item->getFlags() ); - new_bridge = new LLTaskObjectBridge(panel, - object->getUUID(), - object->getName(), - flags); - } + new_bridge = new LLTaskObjectBridge(panel, + object->getUUID(), + object->getName(), + itemflags); break; case LLAssetType::AT_NOTECARD: new_bridge = new LLTaskNotecardBridge(panel, - object->getUUID(), - object->getName()); + object->getUUID(), + object->getName()); break; case LLAssetType::AT_ANIMATION: new_bridge = new LLTaskAnimationBridge(panel, - object->getUUID(), - object->getName()); + object->getUUID(), + object->getName()); break; case LLAssetType::AT_GESTURE: new_bridge = new LLTaskGestureBridge(panel, - object->getUUID(), - object->getName()); + object->getUUID(), + object->getName()); break; case LLAssetType::AT_CLOTHING: case LLAssetType::AT_BODYPART: new_bridge = new LLTaskWearableBridge(panel, - object->getUUID(), - object->getName(), - item->getFlags()); + object->getUUID(), + object->getName(), + itemflags); break; case LLAssetType::AT_CATEGORY: new_bridge = new LLTaskCategoryBridge(panel, - object->getUUID(), - object->getName()); + object->getUUID(), + object->getName()); break; case LLAssetType::AT_LSL_TEXT: new_bridge = new LLTaskLSLBridge(panel, - object->getUUID(), - object->getName()); + object->getUUID(), + object->getName()); break; default: llinfos << "Unhandled inventory type (llassetstorage.h): " -- cgit v1.2.3 From c68e4c559718740bc19eb3c9c71a11d450bae3b5 Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Fri, 28 May 2010 12:04:49 +0100 Subject: CID-478 Checker: NULL_RETURNS Function: LLCOFWearables::buildBodypartListItem(LLViewerInventoryItem *) File: /indra/newview/llcofwearables.cpp --- indra/newview/llcofwearables.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/indra/newview/llcofwearables.cpp b/indra/newview/llcofwearables.cpp index dfc203111a..661449c60c 100644 --- a/indra/newview/llcofwearables.cpp +++ b/indra/newview/llcofwearables.cpp @@ -340,14 +340,15 @@ LLPanelClothingListItem* LLCOFWearables::buildClothingListItem(LLViewerInventory LLPanelBodyPartsListItem* LLCOFWearables::buildBodypartListItem(LLViewerInventoryItem* item) { llassert(item); - + if (!item) return NULL; LLPanelBodyPartsListItem* item_panel = LLPanelBodyPartsListItem::create(item); if (!item_panel) return NULL; //updating verbs //we don't need to use permissions of a link but of an actual/linked item if (item->getLinkedItem()) item = item->getLinkedItem(); - + llassert(item); + if (!item) return NULL; bool allow_modify = item->getPermissions().allowModifyBy(gAgentID); item_panel->setShowLockButton(!allow_modify); item_panel->setShowEditButton(allow_modify); -- cgit v1.2.3 From 89990d746e7cef9e96c6aa350d25309f7c0c29de Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Fri, 28 May 2010 12:06:03 +0100 Subject: CID-477 Checker: NULL_RETURNS Function: LLCOFWearables::buildClothingListItem(LLViewerInventoryItem *, bool, bool) File: /indra/newview/llcofwearables.cpp --- indra/newview/llcofwearables.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/indra/newview/llcofwearables.cpp b/indra/newview/llcofwearables.cpp index 661449c60c..0864d63919 100644 --- a/indra/newview/llcofwearables.cpp +++ b/indra/newview/llcofwearables.cpp @@ -308,13 +308,15 @@ void LLCOFWearables::populateAttachmentsAndBodypartsLists(const LLInventoryModel LLPanelClothingListItem* LLCOFWearables::buildClothingListItem(LLViewerInventoryItem* item, bool first, bool last) { llassert(item); - + if (!item) return NULL; LLPanelClothingListItem* item_panel = LLPanelClothingListItem::create(item); if (!item_panel) return NULL; //updating verbs //we don't need to use permissions of a link but of an actual/linked item if (item->getLinkedItem()) item = item->getLinkedItem(); + llassert(item); + if (!item) return NULL; bool allow_modify = item->getPermissions().allowModifyBy(gAgentID); -- cgit v1.2.3 From b3f758785dd0ee165276c0ac97e1823e44536cc8 Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Fri, 28 May 2010 12:07:45 +0100 Subject: CID-479 Checker: UNINIT_CTOR Function: LLVivoxVoiceClient::sessionState::sessionState() File: /indra/newview/llvoicevivox.cpp --- indra/newview/llvoicevivox.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index 1597691347..f90d46bc9c 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -5538,6 +5538,7 @@ LLVivoxVoiceClient::sessionState::sessionState() : mVoiceEnabled(false), mReconnect(false), mVolumeDirty(false), + mMuteDirty(false), mParticipantsChanged(false) { } -- cgit v1.2.3 From 14e03b56fc363d83004d0c0ae5d79a9bedf68eae Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Fri, 28 May 2010 12:09:48 +0100 Subject: CID-480 Checker: UNINIT_CTOR Function: LLVivoxVoiceClient::participantState::participantState(const std::basic_string, std::allocator>&) File: /indra/newview/llvoicevivox.cpp --- indra/newview/llvoicevivox.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index f90d46bc9c..c6c155f0f0 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -4088,7 +4088,9 @@ LLVivoxVoiceClient::participantState::participantState(const std::string &uri) : mLastSpokeTimestamp(0.f), mPower(0.f), mVolume(LLVoiceClient::VOLUME_DEFAULT), + mUserVolume(0), mOnMuteList(false), + mVolumeSet(false), mVolumeDirty(false), mAvatarIDValid(false), mIsSelf(false) -- cgit v1.2.3 From 878e1d06d5e9ac3bb6c002d3fd7342f5beda2017 Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Fri, 28 May 2010 12:20:06 +0100 Subject: CID-476 Checker: NULL_RETURNS Function: LLPanelEditWearable::updateScrollingPanelUI() File: /indra/newview/llpaneleditwearable.cpp --- indra/newview/llpaneleditwearable.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indra/newview/llpaneleditwearable.cpp b/indra/newview/llpaneleditwearable.cpp index 9eccceca66..36f2d05fab 100644 --- a/indra/newview/llpaneleditwearable.cpp +++ b/indra/newview/llpaneleditwearable.cpp @@ -1095,6 +1095,8 @@ void LLPanelEditWearable::updateScrollingPanelUI() if(panel && (mWearablePtr->getItemID().notNull())) { const LLEditWearableDictionary::WearableEntry *wearable_entry = LLEditWearableDictionary::getInstance()->getWearable(type); + llassert(wearable_entry); + if (!wearable_entry) return; U8 num_subparts = wearable_entry->mSubparts.size(); LLScrollingPanelParam::sUpdateDelayFrames = 0; -- cgit v1.2.3 From 3927d183278e4cc11396a2c78b9393bcf6b0e802 Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Fri, 28 May 2010 12:31:43 +0100 Subject: CID-466 Checker: NULL_RETURNS Function: LLInventoryItemsList::refresh() File: /indra/newview/llinventoryitemslist.cpp --- indra/newview/llinventoryitemslist.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/indra/newview/llinventoryitemslist.cpp b/indra/newview/llinventoryitemslist.cpp index 1c3eb547bb..750cdfb678 100644 --- a/indra/newview/llinventoryitemslist.cpp +++ b/indra/newview/llinventoryitemslist.cpp @@ -388,7 +388,7 @@ void LLInventoryItemsList::refresh() computeDifference(getIDs(), added_items, removed_items); bool add_limit_exceeded = false; - unsigned nadded = 0; + unsigned int nadded = 0; uuid_vec_t::const_iterator it = added_items.begin(); for( ; added_items.end() != it; ++it) @@ -400,8 +400,12 @@ void LLInventoryItemsList::refresh() } LLViewerInventoryItem* item = gInventory.getItem(*it); // Do not rearrange items on each adding, let's do that on filter call - addNewItem(item, false); - ++nadded; + llassert(item); + if (item) + { + addNewItem(item, false); + ++nadded; + } } it = removed_items.begin(); -- cgit v1.2.3 From 6f8e96159a59b3e519cf7a5abba3ae34abcab725 Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Fri, 28 May 2010 12:41:54 +0100 Subject: CID-448 Checker: UNINIT_CTOR Function: LLVoiceClient::LLVoiceClient() File: /indra/newview/llvoiceclient.cpp --- indra/newview/llvoiceclient.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/indra/newview/llvoiceclient.cpp b/indra/newview/llvoiceclient.cpp index 91353281a8..42e44634b6 100644 --- a/indra/newview/llvoiceclient.cpp +++ b/indra/newview/llvoiceclient.cpp @@ -81,8 +81,10 @@ std::string LLVoiceClientStatusObserver::status2string(LLVoiceClientStatusObserv /////////////////////////////////////////////////////////////////////////////////////////////// LLVoiceClient::LLVoiceClient() + : + mVoiceModule(NULL), + m_servicePump(NULL) { - mVoiceModule = NULL; } //--------------------------------------------------- -- cgit v1.2.3 From 58fb834a6ea3698ec6d89cb07e543d088dc69c00 Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Fri, 28 May 2010 12:46:31 +0100 Subject: CID-443 Checker: FORWARD_NULL Function: LLBasicCertificateVector::BasicIteratorImpl::equals(const LLPointer &) const File: /indra/newview/llsechandler_basic.h --- indra/newview/llsechandler_basic.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indra/newview/llsechandler_basic.h b/indra/newview/llsechandler_basic.h index 4bbb73f062..407e74ad00 100644 --- a/indra/newview/llsechandler_basic.h +++ b/indra/newview/llsechandler_basic.h @@ -116,6 +116,8 @@ public: virtual bool equals(const LLPointer& _iter) const { const BasicIteratorImpl *rhs_iter = dynamic_cast(_iter.get()); + llassert(rhs_iter); + if (!rhs_iter) return 0; return (mIter == rhs_iter->mIter); } virtual LLPointer get() -- cgit v1.2.3 From 7e0b36d6102f5e285296cd8e0dc6961b5c73c7cb Mon Sep 17 00:00:00 2001 From: Tofu Linden Date: Fri, 28 May 2010 12:47:45 +0100 Subject: CID-442 Checker: FORWARD_NULL Function: LLBasicCertificateVector::insert(LLCertificateVector::iterator, LLPointer) File: /indra/newview/llsechandler_basic.cpp --- indra/newview/llsechandler_basic.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/indra/newview/llsechandler_basic.cpp b/indra/newview/llsechandler_basic.cpp index edf5ce9b60..c4c13ccdca 100644 --- a/indra/newview/llsechandler_basic.cpp +++ b/indra/newview/llsechandler_basic.cpp @@ -541,7 +541,7 @@ LLBasicCertificateVector::iterator LLBasicCertificateVector::find(const LLSD& pa // Insert a certificate into the store. If the certificate already // exists in the store, nothing is done. void LLBasicCertificateVector::insert(iterator _iter, - LLPointer cert) + LLPointer cert) { LLSD cert_info = cert->getLLSD(); if (cert_info.isMap() && cert_info.has(CERT_SHA1_DIGEST)) @@ -551,7 +551,11 @@ void LLBasicCertificateVector::insert(iterator _iter, if(find(existing_cert_info) == end()) { BasicIteratorImpl *basic_iter = dynamic_cast(_iter.mImpl.get()); - mCerts.insert(basic_iter->mIter, cert); + llassert(basic_iter); + if (basic_iter) + { + mCerts.insert(basic_iter->mIter, cert); + } } } } -- cgit v1.2.3 From e07deaa3dac6b9bc651034543c174e974b4b9ad2 Mon Sep 17 00:00:00 2001 From: "Nyx (Neal Orman)" Date: Fri, 28 May 2010 11:38:37 -0400 Subject: AVP-44 WIP architectural cleanup for multi-wearables Implemented some resident-suggested tweaks to better support multiwearables code reviewed by Seraph --- indra/newview/llagentwearables.cpp | 14 +++++++------- indra/newview/llagentwearables.h | 5 +++-- indra/newview/llappearancemgr.cpp | 5 +++-- indra/newview/llwearabletype.cpp | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/indra/newview/llagentwearables.cpp b/indra/newview/llagentwearables.cpp index cc9e68d593..e5796f8e63 100644 --- a/indra/newview/llagentwearables.cpp +++ b/indra/newview/llagentwearables.cpp @@ -718,16 +718,16 @@ U32 LLAgentWearables::pushWearable(const LLWearableType::EType type, LLWearable { // no null wearables please! llwarns << "Null wearable sent for type " << type << llendl; - return MAX_WEARABLES_PER_TYPE; + return MAX_CLOTHING_PER_TYPE; } - if (type < LLWearableType::WT_COUNT || mWearableDatas[type].size() < MAX_WEARABLES_PER_TYPE) + if (type < LLWearableType::WT_COUNT || mWearableDatas[type].size() < MAX_CLOTHING_PER_TYPE) { mWearableDatas[type].push_back(wearable); wearableUpdated(wearable); checkWearableAgainstInventory(wearable); return mWearableDatas[type].size()-1; } - return MAX_WEARABLES_PER_TYPE; + return MAX_CLOTHING_PER_TYPE; } void LLAgentWearables::wearableUpdated(LLWearable *wearable) @@ -764,7 +764,7 @@ void LLAgentWearables::popWearable(LLWearable *wearable) U32 index = getWearableIndex(wearable); LLWearableType::EType type = wearable->getType(); - if (index < MAX_WEARABLES_PER_TYPE && index < getWearableCount(type)) + if (index < MAX_CLOTHING_PER_TYPE && index < getWearableCount(type)) { popWearable(type, index); } @@ -785,7 +785,7 @@ U32 LLAgentWearables::getWearableIndex(LLWearable *wearable) { if (wearable == NULL) { - return MAX_WEARABLES_PER_TYPE; + return MAX_CLOTHING_PER_TYPE; } const LLWearableType::EType type = wearable->getType(); @@ -793,7 +793,7 @@ U32 LLAgentWearables::getWearableIndex(LLWearable *wearable) if (wearable_iter == mWearableDatas.end()) { llwarns << "tried to get wearable index with an invalid type!" << llendl; - return MAX_WEARABLES_PER_TYPE; + return MAX_CLOTHING_PER_TYPE; } const wearableentry_vec_t& wearable_vec = wearable_iter->second; for(U32 index = 0; index < wearable_vec.size(); index++) @@ -804,7 +804,7 @@ U32 LLAgentWearables::getWearableIndex(LLWearable *wearable) } } - return MAX_WEARABLES_PER_TYPE; + return MAX_CLOTHING_PER_TYPE; } const LLWearable* LLAgentWearables::getWearable(const LLWearableType::EType type, U32 index) const diff --git a/indra/newview/llagentwearables.h b/indra/newview/llagentwearables.h index c53b1333fc..5d5c5ae371 100644 --- a/indra/newview/llagentwearables.h +++ b/indra/newview/llagentwearables.h @@ -102,6 +102,9 @@ public: U32 getWearableCount(const LLWearableType::EType type) const; U32 getWearableCount(const U32 tex_index) const; + static const U32 MAX_CLOTHING_PER_TYPE = 5; + + //-------------------------------------------------------------------- // Setters //-------------------------------------------------------------------- @@ -274,8 +277,6 @@ private: LLPointer mCB; }; - static const U32 MAX_WEARABLES_PER_TYPE = 1; - }; // LLAgentWearables extern LLAgentWearables gAgentWearables; diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 8cc4436188..c417f8bdf5 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -970,7 +970,7 @@ void LLAppearanceMgr::updateCOF(const LLUUID& category, bool append) getDescendentsOfAssetType(category, wear_items, LLAssetType::AT_CLOTHING, false); // Reduce wearables to max of one per type. removeDuplicateItems(wear_items); - filterWearableItems(wear_items, 5); + filterWearableItems(wear_items, LLAgentWearables::MAX_CLOTHING_PER_TYPE); // - Attachments: include COF contents only if appending. LLInventoryModel::item_array_t obj_items; @@ -1525,11 +1525,12 @@ void LLAppearanceMgr::addCOFItemLink(const LLInventoryItem *item, bool do_update else { LLPointer cb = do_update ? new ModifiedCOFCallback : 0; + const std::string description = vitem->getIsLinkType() ? vitem->getDescription() : ""; link_inventory_item( gAgent.getID(), vitem->getLinkedUUID(), getCOF(), vitem->getName(), - vitem->getDescription(), + description, LLAssetType::AT_LINK, cb); } diff --git a/indra/newview/llwearabletype.cpp b/indra/newview/llwearabletype.cpp index c692df06ad..2a14ace38c 100644 --- a/indra/newview/llwearabletype.cpp +++ b/indra/newview/llwearabletype.cpp @@ -85,7 +85,7 @@ LLWearableDictionary::LLWearableDictionary() addEntry(LLWearableType::WT_TATTOO, new WearableEntry("tattoo", "New Tattoo", LLAssetType::AT_CLOTHING, LLInventoryIcon::ICONNAME_CLOTHING_TATTOO)); addEntry(LLWearableType::WT_INVALID, new WearableEntry("invalid", "Invalid Wearable", LLAssetType::AT_NONE, LLInventoryIcon::ICONNAME_NONE)); addEntry(LLWearableType::WT_NONE, new WearableEntry("none", "Invalid Wearable", LLAssetType::AT_NONE, LLInventoryIcon::ICONNAME_NONE)); - addEntry(LLWearableType::WT_COUNT, NULL); + addEntry(LLWearableType::WT_COUNT, new WearableEntry("count", "Invalid Wearable", LLAssetType::AT_NONE, LLInventoryIcon::ICONNAME_NONE)); } // static -- cgit v1.2.3 From d42fd6cf7fa3ac4d7e2ef35cef50eb7740b4ec6d Mon Sep 17 00:00:00 2001 From: Paul Guslisty Date: Fri, 28 May 2010 20:10:10 +0300 Subject: EXT-7472 FIXED Open add to outfit panel by (+) button click on unwearable items and by selecting 'Replace' menu item click body part context menu Main changes: - Added callback for a '(+) button' to the LLCOFCallbacks and bind it with LLPanelOutfitEdit::onAddWearableClicked - Created the callback(LLPanelOutfitEdit::onReplaceBodyPartMenuItemClicked) for 'Replace' menu item of context menu Related changes: - Changed LLFilteredWearableListManager so that it can use different functors (subclasses of LLInventoryCollectFunctor) as a criterion for LLInventoryItemsList filtering. Before it used only LLFindNonLinksByMask filter. Moved LLFindNonLinksByMask from to the llfilteredwearablelist.cpp to the llinventoryfunctions.h - Created getter 'LLPanelDummyClothingListItem::getWearableType()' for LLPanelDummyClothingListItem - Made 'add wearables panel' a member of LLPanelOutfitEdit so that not to use findChild each time panel is needed Reviewed by Igor Borovkov at http://jira.secondlife.com/browse/EXT-7472 --HG-- branch : product-engine --- indra/newview/llcofwearables.cpp | 16 +++++++++-- indra/newview/llcofwearables.h | 3 ++ indra/newview/llfilteredwearablelist.cpp | 46 +++++++++--------------------- indra/newview/llfilteredwearablelist.h | 9 +++--- indra/newview/llinventoryfunctions.cpp | 5 ++++ indra/newview/llinventoryfunctions.h | 35 ++++++++++++++++++++++- indra/newview/llpaneloutfitedit.cpp | 49 +++++++++++++++++++++++++++++--- indra/newview/llpaneloutfitedit.h | 9 ++++++ indra/newview/llwearableitemslist.cpp | 5 ++++ indra/newview/llwearableitemslist.h | 1 + 10 files changed, 134 insertions(+), 44 deletions(-) diff --git a/indra/newview/llcofwearables.cpp b/indra/newview/llcofwearables.cpp index dfc203111a..6e7a7fe937 100644 --- a/indra/newview/llcofwearables.cpp +++ b/indra/newview/llcofwearables.cpp @@ -43,6 +43,8 @@ #include "llmenugl.h" #include "llviewermenu.h" #include "llwearableitemslist.h" +#include "llpaneloutfitedit.h" +#include "llsidetray.h" static LLRegisterPanelClassWrapper t_cof_accodion_list_adaptor("accordion_list_adaptor"); @@ -135,8 +137,10 @@ protected: LLUICtrl::EnableCallbackRegistry::ScopedRegistrar enable_registrar; LLUUID selected_id = mUUIDs.back(); - registrar.add("BodyPart.Replace", boost::bind(&LLAppearanceMgr::wearItemOnAvatar, - LLAppearanceMgr::getInstance(), selected_id, true, true)); + // *HACK* need to pass pointer to LLPanelOutfitEdit instead of LLSideTray::getInstance()->getPanel(). + // LLSideTray::getInstance()->getPanel() is rather slow variant + LLPanelOutfitEdit* panel_oe = dynamic_cast(LLSideTray::getInstance()->getPanel("panel_outfit_edit")); + registrar.add("BodyPart.Replace", boost::bind(&LLPanelOutfitEdit::onReplaceBodyPartMenuItemClicked, panel_oe, selected_id)); registrar.add("BodyPart.Edit", boost::bind(LLAgentWearables::editWearable, selected_id)); enable_registrar.add("BodyPart.OnEnable", boost::bind(&CofBodyPartContextMenu::onEnable, this, _2)); @@ -416,6 +420,7 @@ void LLCOFWearables::addClothingTypesDummies(const LLAppearanceMgr::wearables_by LLWearableType::EType w_type = static_cast(type); LLPanelInventoryListItemBase* item_panel = LLPanelDummyClothingListItem::create(w_type); if(!item_panel) continue; + item_panel->childSetAction("btn_add", mCOFCallbacks.mAddWearable); mClothing->addItem(item_panel, LLUUID::null, ADD_BOTTOM, false); } } @@ -435,6 +440,13 @@ bool LLCOFWearables::getSelectedUUIDs(uuid_vec_t& selected_ids) return selected_ids.size() != 0; } +LLPanel* LLCOFWearables::getSelectedItem() +{ + if (!mLastSelectedList) return NULL; + + return mLastSelectedList->getSelectedItem(); +} + void LLCOFWearables::clear() { mAttachments->clear(); diff --git a/indra/newview/llcofwearables.h b/indra/newview/llcofwearables.h index 590aa709dd..8f8bda2be8 100644 --- a/indra/newview/llcofwearables.h +++ b/indra/newview/llcofwearables.h @@ -107,6 +107,7 @@ public: typedef boost::function cof_callback_t; + cof_callback_t mAddWearable; cof_callback_t mMoveWearableCloser; cof_callback_t mMoveWearableFurther; cof_callback_t mEditWearable; @@ -123,6 +124,8 @@ public: LLUUID getSelectedUUID(); bool getSelectedUUIDs(uuid_vec_t& selected_ids); + LLPanel* getSelectedItem(); + void refresh(); void clear(); diff --git a/indra/newview/llfilteredwearablelist.cpp b/indra/newview/llfilteredwearablelist.cpp index fd99f673e0..28e159421c 100644 --- a/indra/newview/llfilteredwearablelist.cpp +++ b/indra/newview/llfilteredwearablelist.cpp @@ -37,32 +37,10 @@ #include "llinventoryitemslist.h" #include "llinventorymodel.h" -class LLFindNonLinksByMask : public LLInventoryCollectFunctor -{ -public: - LLFindNonLinksByMask(U64 mask) - : mFilterMask(mask) - {} - - virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) - { - if(item && !item->getIsLinkType() && (mFilterMask & (1LL << item->getInventoryType())) ) - { - return true; - } - - return false; - } -private: - U64 mFilterMask; -}; - -////////////////////////////////////////////////////////////////////////// - -LLFilteredWearableListManager::LLFilteredWearableListManager(LLInventoryItemsList* list, U64 filter_mask) +LLFilteredWearableListManager::LLFilteredWearableListManager(LLInventoryItemsList* list, LLInventoryCollectFunctor* collector) : mWearableList(list) -, mFilterMask(filter_mask) +, mCollector(collector) { llassert(mWearableList); gInventory.addObserver(this); @@ -84,9 +62,9 @@ void LLFilteredWearableListManager::changed(U32 mask) populateList(); } -void LLFilteredWearableListManager::setFilterMask(U64 mask) +void LLFilteredWearableListManager::setFilterCollector(LLInventoryCollectFunctor* collector) { - mFilterMask = mask; + mCollector = collector; populateList(); } @@ -94,14 +72,16 @@ void LLFilteredWearableListManager::populateList() { LLInventoryModel::cat_array_t cat_array; LLInventoryModel::item_array_t item_array; - LLFindNonLinksByMask collector(mFilterMask); - gInventory.collectDescendentsIf( - gInventory.getRootFolderID(), - cat_array, - item_array, - LLInventoryModel::EXCLUDE_TRASH, - collector); + if(mCollector) + { + gInventory.collectDescendentsIf( + gInventory.getRootFolderID(), + cat_array, + item_array, + LLInventoryModel::EXCLUDE_TRASH, + *mCollector); + } // Probably will also need to get items from Library (waiting for reply in EXT-6724). diff --git a/indra/newview/llfilteredwearablelist.h b/indra/newview/llfilteredwearablelist.h index 0780c02442..b7825c07af 100644 --- a/indra/newview/llfilteredwearablelist.h +++ b/indra/newview/llfilteredwearablelist.h @@ -32,6 +32,7 @@ #ifndef LL_LLFILTEREDWEARABLELIST_H #define LL_LLFILTEREDWEARABLELIST_H +#include "llinventoryfunctions.h" #include "llinventoryobserver.h" class LLInventoryItemsList; @@ -42,7 +43,7 @@ class LLFilteredWearableListManager : public LLInventoryObserver LOG_CLASS(LLFilteredWearableListManager); public: - LLFilteredWearableListManager(LLInventoryItemsList* list, U64 filter_mask); + LLFilteredWearableListManager(LLInventoryItemsList* list, LLInventoryCollectFunctor* collector); ~LLFilteredWearableListManager(); /** LLInventoryObserver implementation @@ -51,9 +52,9 @@ public: /*virtual*/ void changed(U32 mask); /** - * Sets new filter and applies it immediately + * Sets new collector and applies it immediately */ - void setFilterMask(U64 mask); + void setFilterCollector(LLInventoryCollectFunctor* collector); /** * Populates wearable list with filtered data. @@ -62,7 +63,7 @@ public: private: LLInventoryItemsList* mWearableList; - U64 mFilterMask; + LLInventoryCollectFunctor* mCollector; }; #endif //LL_LLFILTEREDWEARABLELIST_H diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index c38d45f0f5..6301ff0d6d 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -372,6 +372,11 @@ bool LLFindWearablesOfType::operator()(LLInventoryCategory* cat, LLInventoryItem return true; } +void LLFindWearablesOfType::setType(LLWearableType::EType type) +{ + mWearableType = type; +} + ///---------------------------------------------------------------------------- /// LLAssetIDMatches ///---------------------------------------------------------------------------- diff --git a/indra/newview/llinventoryfunctions.h b/indra/newview/llinventoryfunctions.h index 8b96ba29d9..bb365573d7 100644 --- a/indra/newview/llinventoryfunctions.h +++ b/indra/newview/llinventoryfunctions.h @@ -251,6 +251,37 @@ public: }; +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// Class LLFindNonLinksByMask +// +// +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +class LLFindNonLinksByMask : public LLInventoryCollectFunctor +{ +public: + LLFindNonLinksByMask(U64 mask) + : mFilterMask(mask) + {} + + virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item) + { + if(item && !item->getIsLinkType() && (mFilterMask & (1LL << item->getInventoryType())) ) + { + return true; + } + + return false; + } + + void setFilterMask(U64 mask) + { + mFilterMask = mask; + } + +private: + U64 mFilterMask; +}; + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLFindWearables // @@ -272,8 +303,10 @@ public: LLFindWearablesOfType(LLWearableType::EType type) : mWearableType(type) {} virtual ~LLFindWearablesOfType() {} virtual bool operator()(LLInventoryCategory* cat, LLInventoryItem* item); + void setType(LLWearableType::EType type); - const LLWearableType::EType mWearableType; +private: + LLWearableType::EType mWearableType; }; /** Inventory Collector Functions diff --git a/indra/newview/llpaneloutfitedit.cpp b/indra/newview/llpaneloutfitedit.cpp index ae4b288588..8d7a2b33be 100644 --- a/indra/newview/llpaneloutfitedit.cpp +++ b/indra/newview/llpaneloutfitedit.cpp @@ -67,6 +67,7 @@ #include "llsidepanelappearance.h" #include "lltoggleablemenu.h" #include "llwearablelist.h" +#include "llwearableitemslist.h" static LLRegisterPanelClassWrapper t_outfit_edit("panel_outfit_edit"); @@ -214,7 +215,10 @@ LLPanelOutfitEdit::LLPanelOutfitEdit() mCOFObserver(NULL), mGearMenu(NULL), mCOFDragAndDropObserver(NULL), - mInitialized(false) + mInitialized(false), + mAddWearablesPanel(NULL), + mWearableListMaskCollector(NULL), + mWearableListTypeCollector(NULL) { mSavedFolderState = new LLSaveFolderState(); mSavedFolderState->setApply(FALSE); @@ -236,6 +240,9 @@ LLPanelOutfitEdit::~LLPanelOutfitEdit() delete mCOFObserver; delete mCOFDragAndDropObserver; + + delete mWearableListMaskCollector; + delete mWearableListTypeCollector; } BOOL LLPanelOutfitEdit::postBuild() @@ -261,6 +268,7 @@ BOOL LLPanelOutfitEdit::postBuild() mCOFWearables = getChild("cof_wearables_list"); mCOFWearables->setCommitCallback(boost::bind(&LLPanelOutfitEdit::onOutfitItemSelectionChange, this)); + mCOFWearables->getCOFCallbacks().mAddWearable = boost::bind(&LLPanelOutfitEdit::onAddWearableClicked, this); mCOFWearables->getCOFCallbacks().mEditWearable = boost::bind(&LLPanelOutfitEdit::onEditWearableClicked, this); mCOFWearables->getCOFCallbacks().mDeleteWearable = boost::bind(&LLPanelOutfitEdit::onRemoveFromOutfitClicked, this); mCOFWearables->getCOFCallbacks().mMoveWearableCloser = boost::bind(&LLPanelOutfitEdit::moveWearable, this, true); @@ -268,6 +276,7 @@ BOOL LLPanelOutfitEdit::postBuild() mCOFWearables->childSetAction("add_btn", boost::bind(&LLPanelOutfitEdit::toggleAddWearablesPanel, this)); + mAddWearablesPanel = getChild("add_wearables_panel"); mInventoryItemsPanel = getChild("inventory_items"); mInventoryItemsPanel->setFilterTypes(ALL_ITEMS_MASK); @@ -306,9 +315,12 @@ BOOL LLPanelOutfitEdit::postBuild() save_registar.add("Outfit.SaveAsNew.Action", boost::bind(&LLPanelOutfitEdit::saveOutfit, this, true)); mSaveMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_save_outfit.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); + mWearableListMaskCollector = new LLFindNonLinksByMask(ALL_ITEMS_MASK); + mWearableListTypeCollector = new LLFindWearablesOfType(LLWearableType::WT_NONE); + mWearableItemsPanel = getChild("filtered_wearables_panel"); mWearableItemsList = getChild("filtered_wearables_list"); - mWearableListManager = new LLFilteredWearableListManager(mWearableItemsList, ALL_ITEMS_MASK); + mWearableListManager = new LLFilteredWearableListManager(mWearableItemsList, mWearableListMaskCollector); return TRUE; } @@ -334,7 +346,8 @@ void LLPanelOutfitEdit::moveWearable(bool closer_to_body) void LLPanelOutfitEdit::toggleAddWearablesPanel() { - childSetVisible("add_wearables_panel", !childIsVisible("add_wearables_panel")); + BOOL current_visibility = mAddWearablesPanel->getVisible(); + mAddWearablesPanel->setVisible(!current_visibility); } void LLPanelOutfitEdit::showWearablesFilter() @@ -407,7 +420,9 @@ void LLPanelOutfitEdit::onTypeFilterChanged(LLUICtrl* ctrl) { U32 curr_filter_type = type_filter->getCurrentIndex(); mInventoryItemsPanel->setFilterTypes(mLookItemTypes[curr_filter_type].inventoryMask); - mWearableListManager->setFilterMask(mLookItemTypes[curr_filter_type].inventoryMask); + + mWearableListMaskCollector->setFilterMask(mLookItemTypes[curr_filter_type].inventoryMask); + mWearableListManager->setFilterCollector(mWearableListMaskCollector); } mSavedFolderState->setApply(TRUE); @@ -487,6 +502,25 @@ void LLPanelOutfitEdit::onAddToOutfitClicked(void) LLAppearanceMgr::getInstance()->wearItemOnAvatar(selected_id); } +void LLPanelOutfitEdit::onAddWearableClicked(void) +{ + LLPanelDummyClothingListItem* item = dynamic_cast(mCOFWearables->getSelectedItem()); + + if(item) + { + showFilteredWearableItemsList(item->getWearableType()); + } +} + +void LLPanelOutfitEdit::onReplaceBodyPartMenuItemClicked(LLUUID selected_item_id) +{ + LLViewerInventoryItem* item = gInventory.getLinkedItem(selected_item_id); + + if (item && item->getType() == LLAssetType::AT_BODYPART) + { + showFilteredWearableItemsList(item->getWearableType()); + } +} void LLPanelOutfitEdit::onRemoveFromOutfitClicked(void) { @@ -722,4 +756,11 @@ void LLPanelOutfitEdit::onGearMenuItemClick(const LLSD& data) } } +void LLPanelOutfitEdit::showFilteredWearableItemsList(LLWearableType::EType type) +{ + mWearableListTypeCollector->setType(type); + mWearableListManager->setFilterCollector(mWearableListTypeCollector); + mAddWearablesPanel->setVisible(TRUE); +} + // EOF diff --git a/indra/newview/llpaneloutfitedit.h b/indra/newview/llpaneloutfitedit.h index 1bf69c5606..dbfeb2c375 100644 --- a/indra/newview/llpaneloutfitedit.h +++ b/indra/newview/llpaneloutfitedit.h @@ -59,6 +59,8 @@ class LLToggleableMenu; class LLFilterEditor; class LLFilteredWearableListManager; class LLMenuGL; +class LLFindNonLinksByMask; +class LLFindWearablesOfType; class LLPanelOutfitEdit : public LLPanel { @@ -103,6 +105,8 @@ public: void onOutfitItemSelectionChange(void); void onRemoveFromOutfitClicked(void); void onEditWearableClicked(void); + void onAddWearableClicked(void); + void onReplaceBodyPartMenuItemClicked(LLUUID selected_item_id); void displayCurrentOutfit(); void updateCurrentOutfitName(); @@ -129,6 +133,7 @@ private: void onGearButtonClick(LLUICtrl* clicked_button); void onGearMenuItemClick(const LLSD& data); + void showFilteredWearableItemsList(LLWearableType::EType type); LLTextBox* mCurrentOutfitName; @@ -141,6 +146,10 @@ private: LLButton* mFolderViewBtn; LLButton* mListViewBtn; LLToggleableMenu* mSaveMenu; + LLPanel* mAddWearablesPanel; + + LLFindNonLinksByMask* mWearableListMaskCollector; + LLFindWearablesOfType* mWearableListTypeCollector; LLFilteredWearableListManager* mWearableListManager; LLInventoryItemsList* mWearableItemsList; diff --git a/indra/newview/llwearableitemslist.cpp b/indra/newview/llwearableitemslist.cpp index fb7577c008..161cd40cfc 100644 --- a/indra/newview/llwearableitemslist.cpp +++ b/indra/newview/llwearableitemslist.cpp @@ -251,6 +251,11 @@ BOOL LLPanelDummyClothingListItem::postBuild() return TRUE; } +LLWearableType::EType LLPanelDummyClothingListItem::getWearableType() const +{ + return mWearableType; +} + LLPanelDummyClothingListItem::LLPanelDummyClothingListItem(LLWearableType::EType w_type) : LLPanelWearableListItem(NULL) , mWearableType(w_type) diff --git a/indra/newview/llwearableitemslist.h b/indra/newview/llwearableitemslist.h index 7ad1b5a3ad..de024ed220 100644 --- a/indra/newview/llwearableitemslist.h +++ b/indra/newview/llwearableitemslist.h @@ -162,6 +162,7 @@ public: /*virtual*/ void updateItem(); /*virtual*/ BOOL postBuild(); + LLWearableType::EType getWearableType() const; protected: LLPanelDummyClothingListItem(LLWearableType::EType w_type); -- cgit v1.2.3 From 05626c3371395d76b1f2e6b7fcbe64ef12f7d974 Mon Sep 17 00:00:00 2001 From: Yuri Chebotarev Date: Fri, 28 May 2010 20:13:18 +0300 Subject: EXT-7013 FIX time formatting function didn't work for some parameters for Japanise (like weekdays). reviewed by Vadim Savchuk https://codereview.productengine.com/secondlife/r/457/ --HG-- branch : product-engine --- indra/llcommon/llstring.cpp | 103 ++++++++++++++++++++++++- indra/llcommon/llstring.h | 18 +++++ indra/newview/llappviewer.cpp | 11 ++- indra/newview/skins/default/xui/en/strings.xml | 15 ++++ indra/newview/skins/default/xui/ja/strings.xml | 20 ++++- 5 files changed, 164 insertions(+), 3 deletions(-) diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index b5a73ec1d1..8197e3e642 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -676,6 +676,17 @@ long LLStringOps::sLocalTimeOffset = 0; bool LLStringOps::sPacificDaylightTime = 0; std::map LLStringOps::datetimeToCodes; +std::vector LLStringOps::sWeekDayList; +std::vector LLStringOps::sWeekDayShortList; +std::vector LLStringOps::sMonthList; +std::vector LLStringOps::sMonthShortList; + + +std::string LLStringOps::sDayFormat; +std::string LLStringOps::sAM; +std::string LLStringOps::sPM; + + S32 LLStringOps::collate(const llwchar* a, const llwchar* b) { #if LL_WINDOWS @@ -724,6 +735,50 @@ void LLStringOps::setupDatetimeInfo (bool daylight) datetimeToCodes["timezone"] = "%Z"; // PST } +void tokenizeStringToArray(const std::string& data, std::vector& output) +{ + output.clear(); + size_t length = data.size(); + + // tokenize it and put it in the array + std::string cur_word; + for(size_t i = 0; i < length; ++i) + { + if(data[i] == ':') + { + output.push_back(cur_word); + cur_word.clear(); + } + else + { + cur_word.append(1, data[i]); + } + } + output.push_back(cur_word); +} + +void LLStringOps::setupWeekDaysNames(const std::string& data) +{ + tokenizeStringToArray(data,sWeekDayList); +} +void LLStringOps::setupWeekDaysShortNames(const std::string& data) +{ + tokenizeStringToArray(data,sWeekDayShortList); +} +void LLStringOps::setupMonthNames(const std::string& data) +{ + tokenizeStringToArray(data,sMonthList); +} +void LLStringOps::setupMonthShortNames(const std::string& data) +{ + tokenizeStringToArray(data,sMonthShortList); +} +void LLStringOps::setupDayFormat(const std::string& data) +{ + sDayFormat = data; +} + + std::string LLStringOps::getDatetimeCode (std::string key) { std::map::iterator iter; @@ -998,7 +1053,53 @@ bool LLStringUtil::formatDatetime(std::string& replacement, std::string token, } return true; } - replacement = datetime.toHTTPDateString(code); + + //EXT-7013 + //few codes are not suppotred by strtime function (example - weekdays for Japanise) + //so use predefined ones + + //if sWeekDayList is not empty than current locale doesn't support + //weekday name. + time_t loc_seconds = (time_t) secFromEpoch; + if(LLStringOps::sWeekDayList.size() == 7 && code == "%A") + { + struct tm * gmt = gmtime (&loc_seconds); + replacement = LLStringOps::sWeekDayList[gmt->tm_wday]; + } + else if(LLStringOps::sWeekDayShortList.size() == 7 && code == "%a") + { + struct tm * gmt = gmtime (&loc_seconds); + replacement = LLStringOps::sWeekDayShortList[gmt->tm_wday]; + } + else if(LLStringOps::sMonthList.size() == 12 && code == "%B") + { + struct tm * gmt = gmtime (&loc_seconds); + replacement = LLStringOps::sWeekDayList[gmt->tm_mon]; + } + else if( !LLStringOps::sDayFormat.empty() && code == "%d" ) + { + struct tm * gmt = gmtime (&loc_seconds); + LLStringUtil::format_map_t args; + args["[MDAY]"] = llformat ("%d", gmt->tm_mday); + replacement = LLStringOps::sDayFormat; + LLStringUtil::format(replacement, args); + } + else if( !LLStringOps::sAM.empty() && !LLStringOps::sPM.empty() && code == "%p" ) + { + struct tm * gmt = gmtime (&loc_seconds); + if(gmt->tm_hour<12) + { + replacement = LLStringOps::sAM; + } + else + { + replacement = LLStringOps::sPM; + } + } + else + { + replacement = datetime.toHTTPDateString(code); + } // *HACK: delete leading zero from hour string in case 'hour12' (code = %I) time format // to show time without leading zero, e.g. 08:16 -> 8:16 (EXT-2738). diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 96588b29b9..ad8f8632a2 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -154,9 +154,19 @@ private: static long sPacificTimeOffset; static long sLocalTimeOffset; static bool sPacificDaylightTime; + static std::map datetimeToCodes; public: + static std::vector sWeekDayList; + static std::vector sWeekDayShortList; + static std::vector sMonthList; + static std::vector sMonthShortList; + static std::string sDayFormat; + + static std::string sAM; + static std::string sPM; + static char toUpper(char elem) { return toupper((unsigned char)elem); } static llwchar toUpper(llwchar elem) { return towupper(elem); } @@ -185,6 +195,14 @@ public: static S32 collate(const llwchar* a, const llwchar* b); static void setupDatetimeInfo(bool pacific_daylight_time); + + static void setupWeekDaysNames(const std::string& data); + static void setupWeekDaysShortNames(const std::string& data); + static void setupMonthNames(const std::string& data); + static void setupMonthShortNames(const std::string& data); + static void setupDayFormat(const std::string& data); + + static long getPacificTimeOffset(void) { return sPacificTimeOffset;} static long getLocalTimeOffset(void) { return sLocalTimeOffset;} // Is the Pacific time zone (aka server time zone) diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index f7f7cb599e..7b17933b5c 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -890,7 +890,16 @@ bool LLAppViewer::init() } LLViewerMedia::initClass(); - + + LLStringOps::setupWeekDaysNames(LLTrans::getString("dateTimeWeekdaysNames")); + LLStringOps::setupWeekDaysShortNames(LLTrans::getString("dateTimeWeekdaysShortNames")); + LLStringOps::setupMonthNames(LLTrans::getString("dateTimeMonthNames")); + LLStringOps::setupMonthShortNames(LLTrans::getString("dateTimeMonthShortNames")); + LLStringOps::setupDayFormat(LLTrans::getString("dateTimeDayFormat")); + + LLStringOps::sAM = LLTrans::getString("dateTimeAM"); + LLStringOps::sPM = LLTrans::getString("dateTimePM"); + return true; } diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml index e57b30185d..9247e3f74c 100644 --- a/indra/newview/skins/default/xui/en/strings.xml +++ b/indra/newview/skins/default/xui/en/strings.xml @@ -3183,4 +3183,19 @@ Abuse Report Please check secondlife.com/status to see if there is a known problem with the service. + + + + + + + + diff --git a/indra/newview/skins/default/xui/ja/strings.xml b/indra/newview/skins/default/xui/ja/strings.xml index 57120ff7ec..a06dcc54e4 100644 --- a/indra/newview/skins/default/xui/ja/strings.xml +++ b/indra/newview/skins/default/xui/ja/strings.xml @@ -1,4 +1,4 @@ - + + + Sunday:Monday:Tuesday:Wednesday:Thursday:Friday:Saturday + + + Son:Mon:Tue:Wed:Thu:Fri:Sat + + + January:February:March:April:May:June:July:August:September:October:November:December + + + Jan:Feb:Mar:Apr:May:Jun:Jul:Aug:Sep:Oct:Nov:Dec + + + [MDAY] D + + AM + PM -- cgit v1.2.3 From 0b96ae29cf0b2baf4732e09a28a5f76d3df05727 Mon Sep 17 00:00:00 2001 From: Loren Shih Date: Fri, 28 May 2010 14:02:44 -0400 Subject: Fixed linux compile error due to missing forward declare in lltextureview.h. --- indra/newview/lltextureview.h | 1 + 1 file changed, 1 insertion(+) diff --git a/indra/newview/lltextureview.h b/indra/newview/lltextureview.h index 6072cd3f11..dfd9c42c2c 100644 --- a/indra/newview/lltextureview.h +++ b/indra/newview/lltextureview.h @@ -38,6 +38,7 @@ class LLViewerFetchedTexture; class LLTextureBar; class LLGLTexMemBar; +class LLAvatarTexBar; class LLTextureView : public LLContainerView { -- cgit v1.2.3 From 3f5abfb2bf58032242d6c24328463558a1828b20 Mon Sep 17 00:00:00 2001 From: Eli Linden Date: Fri, 28 May 2010 11:47:38 -0700 Subject: ND-46735 WIP FR JA linguistic --- .../newview/skins/default/xui/fr/floater_preview_gesture.xml | 12 ++++++------ indra/newview/skins/default/xui/fr/floater_tools.xml | 2 +- indra/newview/skins/default/xui/fr/notifications.xml | 2 +- indra/newview/skins/default/xui/fr/panel_edit_pick.xml | 4 ++-- .../skins/default/xui/fr/panel_group_control_panel.xml | 2 +- indra/newview/skins/default/xui/fr/panel_group_notices.xml | 6 +++--- indra/newview/skins/default/xui/fr/panel_notes.xml | 2 +- indra/newview/skins/default/xui/fr/panel_people.xml | 2 +- .../skins/default/xui/fr/panel_preferences_advanced.xml | 2 +- indra/newview/skins/default/xui/fr/panel_profile.xml | 2 +- indra/newview/skins/default/xui/fr/strings.xml | 2 +- .../newview/skins/default/xui/ja/floater_avatar_textures.xml | 2 +- indra/newview/skins/default/xui/ja/floater_god_tools.xml | 2 +- indra/newview/skins/default/xui/ja/floater_moveview.xml | 12 ++++++------ indra/newview/skins/default/xui/ja/menu_avatar_self.xml | 4 ++-- .../skins/default/xui/ja/panel_bodyparts_list_button_bar.xml | 4 ++-- .../skins/default/xui/ja/panel_clothing_list_button_bar.xml | 2 +- indra/newview/skins/default/xui/ja/panel_edit_shape.xml | 2 +- indra/newview/skins/default/xui/ja/panel_region_general.xml | 2 +- indra/newview/skins/default/xui/ja/panel_region_texture.xml | 4 ++-- 20 files changed, 36 insertions(+), 36 deletions(-) diff --git a/indra/newview/skins/default/xui/fr/floater_preview_gesture.xml b/indra/newview/skins/default/xui/fr/floater_preview_gesture.xml index 7133f8754c..1d164ac661 100644 --- a/indra/newview/skins/default/xui/fr/floater_preview_gesture.xml +++ b/indra/newview/skins/default/xui/fr/floater_preview_gesture.xml @@ -31,10 +31,10 @@ Description : - Déclencheur : + Déclench. : - Remplacer par : + Rempl. par : @@ -50,20 +50,20 @@ Étapes : -