From 3892a84cfc1c9f7c99b3d1838b9d574a47b77900 Mon Sep 17 00:00:00 2001 From: Dave Parks Date: Wed, 16 Dec 2009 12:31:24 -0600 Subject: Tracking down stalls in llpumpio and agent updates. --- indra/newview/llappviewer.cpp | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 43c8c679c6..6ec4266925 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -922,6 +922,11 @@ static LLFastTimer::DeclareTimer FTM_LFS("LFS Thread"); static LLFastTimer::DeclareTimer FTM_PAUSE_THREADS("Pause Threads"); static LLFastTimer::DeclareTimer FTM_IDLE("Idle"); static LLFastTimer::DeclareTimer FTM_PUMP("Pump"); +static LLFastTimer::DeclareTimer FTM_PUMP_ARES("Ares"); +static LLFastTimer::DeclareTimer FTM_PUMP_SERVICE("Service"); +static LLFastTimer::DeclareTimer FTM_SERVICE_CALLBACK("Callback"); +static LLFastTimer::DeclareTimer FTM_AGENT_AUTOPILOT("Autopilot"); +static LLFastTimer::DeclareTimer FTM_AGENT_UPDATE("Update"); bool LLAppViewer::mainLoop() { @@ -1033,10 +1038,20 @@ bool LLAppViewer::mainLoop() LLMemType mt_ip(LLMemType::MTYPE_IDLE_PUMP); pingMainloopTimeout("Main:ServicePump"); LLFastTimer t4(FTM_PUMP); - gAres->process(); - // this pump is necessary to make the login screen show up - gServicePump->pump(); - gServicePump->callback(); + { + LLFastTimer t(FTM_PUMP_ARES); + gAres->process(); + } + { + LLFastTimer t(FTM_PUMP_SERVICE); + // this pump is necessary to make the login screen show up + gServicePump->pump(); + + { + LLFastTimer t(FTM_SERVICE_CALLBACK); + gServicePump->callback(); + } + } } resumeMainloopTimeout(); @@ -3495,9 +3510,12 @@ void LLAppViewer::idle() gAgent.moveYaw(-1.f); } - // Handle automatic walking towards points - gAgentPilot.updateTarget(); - gAgent.autoPilot(&yaw); + { + LLFastTimer t(FTM_AGENT_AUTOPILOT); + // Handle automatic walking towards points + gAgentPilot.updateTarget(); + gAgent.autoPilot(&yaw); + } static LLFrameTimer agent_update_timer; static U32 last_control_flags; @@ -3508,6 +3526,7 @@ void LLAppViewer::idle() if (flags_changed || (agent_update_time > (1.0f / (F32) AGENT_UPDATES_PER_SECOND))) { + LLFastTimer t(FTM_AGENT_UPDATE); // Send avatar and camera info last_control_flags = gAgent.getControlFlags(); send_agent_update(TRUE); -- cgit v1.3 From 6abecf1228010d83c2df8d14057950fb51e37320 Mon Sep 17 00:00:00 2001 From: palange Date: Mon, 1 Feb 2010 11:23:30 -0800 Subject: pushing to private repo to move to windows development. --- indra/llcommon/llprocessor.cpp | 544 +++++++++++++++++++++++++++++------------ indra/llcommon/llprocessor.h | 3 + indra/newview/llappviewer.cpp | 4 - 3 files changed, 388 insertions(+), 163 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/indra/llcommon/llprocessor.cpp b/indra/llcommon/llprocessor.cpp index 8ac0cdf4e3..09a7004913 100644 --- a/indra/llcommon/llprocessor.cpp +++ b/indra/llcommon/llprocessor.cpp @@ -33,6 +33,8 @@ #include "linden_common.h" #include "llprocessor.h" +#include "llerror.h" + //#include #if LL_WINDOWS @@ -42,28 +44,86 @@ # include #endif -#include #include "llsd.h" -#if LL_MSVC && _M_X64 -# define LL_X86_64 1 -# define LL_X86 1 -#elif LL_MSVC && _M_IX86 -# define LL_X86 1 -#elif LL_GNUC && ( defined(__amd64__) || defined(__x86_64__) ) -# define LL_X86_64 1 -# define LL_X86 1 -#elif LL_GNUC && ( defined(__i386__) ) -# define LL_X86 1 -#elif LL_GNUC && ( defined(__powerpc__) || defined(__ppc__) ) -# define LL_PPC 1 -#endif - -class LLProcessorInfoImpl; // foward declaration for the gImpl; +class LLProcessorInfoImpl; // foward declaration for the mImpl; namespace { - static const char* cpu_feature_names[] = + enum cpu_info + { + eBrandName = 0, + eFrequency, + eVendor, + eStepping, + eFamily, + eExtendedFamily, + eModel, + eExtendedModel, + eType, + eBrandID, + eFamilyName + }; + + + const char* cpu_info_names[] = + { + "Processor Name", + "Frequency", + "Vendor", + "Stepping", + "Family", + "Extended Family", + "Model", + "Extended Model", + "Type", + "Brand ID", + "Family Name" + }; + + enum cpu_config + { + eMaxID, + eMaxExtID, + eCLFLUSHCacheLineSize, + eAPICPhysicalID, + eCacheLineSize, + eL2Associativity, + eCacheSizeK, + eFeatureBits, + eExtFeatureBits + }; + + const char* cpu_config_names[] = + { + "Max Supported CPUID level", + "Max Supported Ext. CPUID level", + "CLFLUSH cache line size", + "APIC Physical ID", + "Cache Line Size", + "L2 Associativity", + "Cache Size", + "Feature Bits", + "Ext. Feature Bits" + }; + + + + // *NOTE:Mani - this contains the elements we reference directly and extensions beyond the first 32. + // The rest of the names are referenced by bit maks returned from cpuid. + enum cpu_features + { + eSSE_Ext=25, + eSSE2_Ext=26, + + eSSE3_Features=32, + eMONTIOR_MWAIT=33, + eCPLDebugStore=34, + eThermalMonitor2=35, + eAltivec=36 + }; + + const char* cpu_feature_names[] = { "x87 FPU On Chip", "Virtual-8086 Mode Enhancement", @@ -96,39 +156,70 @@ namespace "Hyper-threading Technology", "Thermal Monitor", "Unknown4", - "Pend. Brk. EN.", // End of FeatureInfo bits + "Pend. Brk. EN.", // 31 End of FeatureInfo bits "SSE3 New Instructions", // 32 "MONITOR/MWAIT", "CPL Qualified Debug Store", - "Thermal Monitor 2" - }; + "Thermal Monitor 2", - enum cpu_features - { - eSSE_Ext=25, - eSSE2_Ext=26, - eSSE3_Features=32, - eMONTIOR_MWAIT=33, - eCPLDebugStore=34, - eThermalMonitor2=35 + "Altivec" }; - // Pointer to the active impl. - boost::scoped_ptr gImpl; -} + std::string compute_CPUFamilyName(const char* cpu_vendor, int family, int ext_family) + { + const char* intel_string = "GenuineIntel"; + const char* amd_string = "AuthenticAMD"; + if(!strncmp(cpu_vendor, intel_string, strlen(intel_string))) + { + U32 composed_family = family + ext_family; + switch(composed_family) + { + case 3: return "Intel i386"; + case 4: return "Intel i486"; + case 5: return "Intel Pentium"; + case 6: return "Intel Pentium Pro/2/3, Core"; + case 7: return "Intel Itanium (IA-64)"; + case 0xF: return "Intel Pentium 4"; + case 0x10: return "Intel Itanium 2 (IA-64)"; + default: return "Unknown"; + } + } + else if(!strncmp(cpu_vendor, amd_string, strlen(amd_string))) + { + U32 composed_family = (family == 0xF) + ? family + ext_family + : family; + switch(composed_family) + { + case 4: return "AMD 80486/5x86"; + case 5: return "AMD K5/K6"; + case 6: return "AMD K7"; + case 0xF: return "AMD K8"; + case 0x10: return "AMD K8L"; + default: return "Unknown"; + } + } + return "Unknown"; + } +} // end unnamed namespace // The base class for implementations. // Each platform should override this class. class LLProcessorInfoImpl { public: - LLProcessorInfoImpl() {} + LLProcessorInfoImpl() + { + mProcessorInfo["info"] = LLSD::emptyMap(); + mProcessorInfo["config"] = LLSD::emptyMap(); + mProcessorInfo["extension"] = LLSD::emptyMap(); + } virtual ~LLProcessorInfoImpl() {} F64 getCPUFrequency() const { - return getInfo("Frequency", 0).asReal(); + return getInfo(eFrequency, 0).asReal(); } bool hasSSE() const @@ -146,8 +237,9 @@ public: return hasExtension("Altivec"); } - std::string getCPUFamilyName() const { return getInfo("FamilyName", "Unknown").asString(); } - std::string getCPUBrandName() const { return getInfo("BrandName", "Unknown").asString(); } + std::string getCPUFamilyName() const { return getInfo(eFamilyName, "Unknown").asString(); } + std::string getCPUBrandName() const { return getInfo(eBrandName, "Unknown").asString(); } + std::string getCPUFeatureDescription() const { std::ostringstream out; @@ -156,28 +248,29 @@ public: out << "//////////////////////////" << std::endl; out << "Processor Name: " << getCPUBrandName() << std::endl; out << "Frequency: " << getCPUFrequency() / (F64)1000000 << " MHz" << std::endl; - out << "Vendor: " << getInfo("Vendor", "Unknown").asString() << std::endl; - out << "Family: " << getCPUFamilyName() << " (" << getInfo("Family", 0) << ")" << std::endl; - out << "Extended family: " << getInfo("ExtendedFamily", 0) << std::endl; - out << "Model: " << getInfo("Model", 0) << std::endl; - out << "Extended model: " << getInfo("ExtendedModel", 0) << std::endl; - out << "Type: " << getInfo("Type", 0) << std::endl; - out << "Brand ID: " << getInfo("BrandID", 0) << std::endl; + out << "Vendor: " << getInfo(eVendor, "Unknown").asString() << std::endl; + out << "Family: " << getCPUFamilyName() << " (" << getInfo(eFamily, 0) << ")" << std::endl; + out << "Extended family: " << getInfo(eExtendedFamily, 0) << std::endl; + out << "Model: " << getInfo(eModel, 0) << std::endl; + out << "Extended model: " << getInfo(eExtendedModel, 0) << std::endl; + out << "Type: " << getInfo(eType, 0) << std::endl; + out << "Brand ID: " << getInfo(eBrandID, 0) << std::endl; out << std::endl; out << "// CPU Configuration" << std::endl; out << "//////////////////////////" << std::endl; - out << "Max Supported CPUID level = " << getConfig("MaxID", 0) << std::endl; - out << "Max Supported Ext. CPUID level = " << std::hex << getConfig("MaxExtID", 0) << std::dec << std::endl; - out << "CLFLUSH cache line size = " << getConfig("CLFLUSHCacheLineSize", 0) << std::endl; - out << "APIC Physical ID = " << getConfig("APICPhysicalID", 0) << std::endl; - out << "Cache Line Size = " << getConfig("CacheLineSize", 0) << std::endl; - out << "L2 Associativity = " << getConfig("L2Associativity", 0) << std::endl; - out << "Cache Size = " << getConfig("CacheSizeK", 0) << "K" << std::endl; + + // Iterate through the dictionary of configuration options. + LLSD configs = mProcessorInfo["config"]; + for(LLSD::map_const_iterator cfgItr = configs.beginMap(); cfgItr != configs.endMap(); ++cfgItr) + { + out << cfgItr->first << " = " << cfgItr->second << std::endl; + } out << std::endl; + out << "// CPU Extensions" << std::endl; out << "//////////////////////////" << std::endl; - for(LLSD::map_const_iterator itr = mProcessorInfo.beginMap(); itr != mProcessorInfo.endMap(); ++itr) + for(LLSD::map_const_iterator itr = mProcessorInfo["extension"].beginMap(); itr != mProcessorInfo["extension"].endMap(); ++itr) { out << " " << itr->first << std::endl; } @@ -185,28 +278,46 @@ public: } protected: - void setInfo(const std::string& name, const LLSD& value) { mProcessorInfo["info"][name]=value; } - void setConfig(const std::string& name, const LLSD& value) { mProcessorInfo["config"][name]=value; } + void setInfo(cpu_info info_type, const LLSD& value) + { + setInfo(cpu_info_names[info_type], value); + } + LLSD getInfo(cpu_info info_type, const LLSD& defaultVal) const + { + return getInfo(cpu_info_names[info_type], defaultVal); + } + + void setConfig(cpu_config config_type, const LLSD& value) + { + setConfig(cpu_config_names[config_type], value); + } + LLSD getConfig(cpu_config config_type, const LLSD& defaultVal) const + { + return getConfig(cpu_config_names[config_type], defaultVal); + } + void setExtension(const std::string& name) { mProcessorInfo["extension"][name] = "true"; } + bool hasExtension(const std::string& name) const + { + return mProcessorInfo["extension"].has(name); + } +private: + void setInfo(const std::string& name, const LLSD& value) { mProcessorInfo["info"][name]=value; } LLSD getInfo(const std::string& name, const LLSD& defaultVal) const { LLSD r = mProcessorInfo["info"].get(name); return r.isDefined() ? r : defaultVal; } - + void setConfig(const std::string& name, const LLSD& value) { mProcessorInfo["config"][name]=value; } LLSD getConfig(const std::string& name, const LLSD& defaultVal) const { LLSD r = mProcessorInfo["config"].get(name); return r.isDefined() ? r : defaultVal; } - bool hasExtension(const std::string& name) const - { - return mProcessorInfo["extension"].has(name); - } - private: + LLSD mProcessorInfo; }; @@ -247,7 +358,7 @@ static F64 calculate_cpu_frequency(U32 measure_msecs) // After that we declare some vars and check the frequency of the high // resolution timer for the measure process. - // If there's no high-res timer, we exit. + // If there"s no high-res timer, we exit. unsigned __int64 starttime, endtime, timedif, freq, start, end, dif; if (!QueryPerformanceFrequency((LARGE_INTEGER *) &freq)) { @@ -271,8 +382,8 @@ static F64 calculate_cpu_frequency(U32 measure_msecs) //// Now we call a CPUID to ensure, that all other prior called functions are //// completed now (serialization) //__asm cpuid - int cpu_info[4] = {-1}; - __cpuid(cpu_info, 0); + int cpu_info[4] = {-1}; + __cpuid(cpu_info, 0); // We ask the high-res timer for the start time QueryPerformanceCounter((LARGE_INTEGER *) &starttime); @@ -331,44 +442,77 @@ private: int cpu_info[4] = {-1}; __cpuid(cpu_info, 0); unsigned int ids = (unsigned int)cpu_info[0]; - setInfo("MaxIDs", ids); + setInfo(eMaxID, ids); char cpu_vendor[0x20]; memset(cpu_vendor, 0, sizeof(cpu_vendor)); *((int*)cpu_vendor) = cpu_info[1]; *((int*)(cpu_vendor+4)) = cpu_info[3]; *((int*)(cpu_vendor+8)) = cpu_info[2]; + setInfo(eVendor, cpu_vendor); // Get the information associated with each valid Id - for(unsigned int i=0; i<=mIds; ++i) + for(unsigned int i=0; i<=ids; ++i) { __cpuid(cpu_info, i); // Interpret CPU feature information. if (i == 1) { - mSteppingID = cpu_info[0] & 0xf; - mModel = (cpu_info[0] >> 4) & 0xf; - mFamily = (cpu_info[0] >> 8) & 0xf; - mProcessorType = (cpu_info[0] >> 12) & 0x3; - mExtendedModel = (cpu_info[0] >> 16) & 0xf; - mExtendedFamily = (cpu_info[0] >> 20) & 0xff; - mBrandIndex = cpu_info[1] & 0xff; - mCLFLUSHCacheLineSize = ((cpu_info[1] >> 8) & 0xff) * 8; - mAPICPhysicalID = (cpu_info[1] >> 24) & 0xff; - mSSE3NewInstructions = (cpu_info[2] & 0x1) || false; - mMONITOR_MWAIT = (cpu_info[2] & 0x8) || false; - mCPLQualifiedDebugStore = (cpu_info[2] & 0x10) || false; - mThermalMonitor2 = (cpu_info[2] & 0x100) || false; - mFeatureInfo = cpu_info[3]; + setInfo(eStepping, cpu_info[0] & 0xf); + setInfo(eModel, (cpu_info[0] >> 4) & 0xf); + int family = (cpu_info[0] >> 8) & 0xf; + setInfo(eFamily, family); + setInfo(eType, (cpu_info[0] >> 12) & 0x3); + setInfo(eExtendedModel, (cpu_info[0] >> 16) & 0xf); + int ext_family = (cpu_info[0] >> 20) & 0xff; + setInfo(eExtendedFamily, ext_family); + setInfo(eBrandIndex, cpu_info[1] & 0xff); + + setInfo(eFamilyName, compute_CPUFamilyName(family, ext_family)); + + setConfig(eCLFLUSHCacheLineSize, ((cpu_info[1] >> 8) & 0xff) * 8); + setConfig(eAPICPhysicalID, (cpu_info[1] >> 24) & 0xff); + + if(cpu_info[2] & 0x1) + { + setExtension(cpu_feature_names[eSSE3_Features]); + } + + if(cpu_info[2] & 0x8) + { + setExtension(cpu_feature_names[eMONTIOR_MWAIT]); + } + + if(cpu_info[2] & 0x10) || false; + { + setExtension(cpu_feature_names[eCPLDebugStore]); + } + + if(cpu_info[2] & 0x100) + { + setExtension(cpu_feature_names[eThermalMonitor2]); + } + + unsigned int feature_info = (unsigned int) cpu_info[3]; + for(unsigned int index = 0, bit = 1; index < eSSE3_Features; ++index, bit <<= 1) + { + if(feature_info & bit) + { + setExtension(cpu_feature_names[index]); + } + } } } // Calling __cpuid with 0x80000000 as the InfoType argument // gets the number of valid extended IDs. __cpuid(cpu_info, 0x80000000); - mExtIds = cpu_info[0]; - memset(mCPUBrandString, 0, sizeof(mCPUBrandString)); + unsigned int ext_ids = cpu_info[0]; + setConfig(eMaxExtID, 0); + + char cpu_brand_string[0x40]; + memset(cpu_brand_string, 0, sizeof(cpu_brand_string)); // Get the information associated with each extended ID. for(unsigned int i=0x80000000; i<=mExtIds; ++i) @@ -377,67 +521,35 @@ private: // Interpret CPU brand string and cache information. if (i == 0x80000002) - memcpy(mCPUBrandString, cpu_info, sizeof(cpu_info)); + memcpy(cpu_brand_string, cpu_info, sizeof(cpu_info)); else if (i == 0x80000003) - memcpy(mCPUBrandString + 16, cpu_info, sizeof(cpu_info)); + memcpy(cpu_brand_string + 16, cpu_info, sizeof(cpu_info)); else if (i == 0x80000004) - memcpy(mCPUBrandString + 32, cpu_info, sizeof(cpu_info)); - else if (i == 0x80000006) - { - mCacheLineSize = cpu_info[2] & 0xff; - mL2Associativity = (cpu_info[2] >> 12) & 0xf; - mCacheSizeK = (cpu_info[2] >> 16) & 0xffff; - } - } - } - - std::string computeCPUFamilyName() const - { - const char* intel_string = "GenuineIntel"; - const char* amd_string = "AuthenticAMD"; - if(!strncmp(mCPUString, intel_string, strlen(intel_string))) - { - U32 composed_family = mFamily + mExtendedFamily; - switch(composed_family) { - case 3: return "Intel i386"; - case 4: return "Intel i486"; - case 5: return "Intel Pentium"; - case 6: return "Intel Pentium Pro/2/3, Core"; - case 7: return "Intel Itanium (IA-64)"; - case 0xF: return "Intel Pentium 4"; - case 0x10: return "Intel Itanium 2 (IA-64)"; - default: return "Unknown"; + memcpy(cpu_brand_string + 32, cpu_info, sizeof(cpu_info)); + setInfo(eBrandName, cpu_brand_string); } - } - else if(!strncmp(mCPUString, amd_string, strlen(amd_string))) - { - U32 composed_family = (mFamily == 0xF) - ? mFamily + mExtendedFamily - : mFamily; - switch(composed_family) + else if (i == 0x80000006) { - case 4: return "AMD 80486/5x86"; - case 5: return "AMD K5/K6"; - case 6: return "AMD K7"; - case 0xF: return "AMD K8"; - case 0x10: return "AMD K8L"; - default: return "Unknown"; + setConfig(eCacheLineSize, cpu_info[2] & 0xff); + setConfig(eL2Associativity, (cpu_info[2] >> 12) & 0xf); + setConfig(eCacheSizeK, (cpu_info[2] >> 16) & 0xffff); } } - return "Unknown"; } - }; #elif LL_DARWIN -class LLProcessorInfoDarwinImpl +#include +#include + +class LLProcessorInfoDarwinImpl : public LLProcessorInfoImpl { public: LLProcessorInfoDarwinImpl() { - + getCPUIDInfo(); } virtual ~LLProcessorInfoDarwinImpl() {} @@ -449,22 +561,136 @@ public: virtual std::string getCPUFamilyName() const { return "Unknown"; } virtual std::string getCPUBrandName() const { return "Unknown"; } virtual std::string getCPUFeatureDescription() const { return "Unknown"; } -}; -#endif // LL_MSVC +private: + int getSysctlInt(const char* name) + { + int result = 0; + size_t len = sizeof(int); + int error = sysctlbyname(name, (void*)&result, &len, NULL, 0); + return error == -1 ? 0 : result; + } + + uint64_t getSysctlInt64(const char* name) + { + uint64_t value = 0; + size_t size = sizeof(value); + int result = sysctlbyname(name, (void*)&value, &size, NULL, 0); + if ( result == 0 ) + { + if ( size == sizeof( uint64_t ) ) + ; + else if ( size == sizeof( uint32_t ) ) + value = (uint64_t)(( uint32_t *)&value); + else if ( size == sizeof( uint16_t ) ) + value = (uint64_t)(( uint16_t *)&value); + else if ( size == sizeof( uint8_t ) ) + value = (uint64_t)(( uint8_t *)&value); + else + { + LL_ERRS("Unknown type returned from sysctl!") << LL_ENDL; + } + } + + return result == -1 ? 0 : value; + } + + void getCPUIDInfo() + { + size_t len = 0; + + char cpu_brand_string[0x40]; + len = sizeof(cpu_brand_string); + memset(cpu_brand_string, 0, len); + sysctlbyname("machdep.cpu.brand_string", (void*)cpu_brand_string, &len, NULL, 0); + cpu_brand_string[0x3f] = 0; + setInfo(eBrandName, cpu_brand_string); + + char cpu_vendor[0x20]; + len = sizeof(cpu_vendor); + memset(cpu_vendor, 0, len); + sysctlbyname("machdep.cpu.vendor", (void*)cpu_vendor, &len, NULL, 0); + cpu_vendor[0x1f] = 0; + setInfo(eVendor, cpu_vendor); + + setInfo(eStepping, getSysctlInt("machdep.cpu.stepping")); + setInfo(eModel, getSysctlInt("machdep.cpu.model")); + int family = getSysctlInt("machdep.cpu.family"); + int ext_family = getSysctlInt("machdep.cpu.extfamily"); + setInfo(eFamily, family); + setInfo(eExtendedFamily, ext_family); + setInfo(eFamilyName, compute_CPUFamilyName(cpu_vendor, family, ext_family)); + setInfo(eExtendedModel, getSysctlInt("machdep.cpu.extmodel")); + setInfo(eBrandID, getSysctlInt("machdep.cpu.brand")); + setInfo(eType, 0); // ? where to find this? + + //setConfig(eCLFLUSHCacheLineSize, ((cpu_info[1] >> 8) & 0xff) * 8); + //setConfig(eAPICPhysicalID, (cpu_info[1] >> 24) & 0xff); + setConfig(eCacheLineSize, getSysctlInt("machdep.cpu.cache.linesize")); + setConfig(eL2Associativity, getSysctlInt("machdep.cpu.cache.L2_associativity")); + setConfig(eCacheSizeK, getSysctlInt("machdep.cpu.cache.size")); + + uint64_t feature_info = getSysctlInt64("machdep.cpu.feature_bits"); + S32 *feature_infos = (S32*)(&feature_info); + + setConfig(eFeatureBits, feature_infos[0]); + + for(unsigned int index = 0, bit = 1; index < eSSE3_Features; ++index, bit <<= 1) + { + if(feature_info & bit) + { + setExtension(cpu_feature_names[index]); + } + } + + // *NOTE:Mani - I didn't find any docs that assure me that machdep.cpu.feature_bits will always be + // The feature bits I think it is. Here's a test: +#ifndef LL_RELEASE_FOR_DOWNLOAD + #if defined(__i386__) && defined(__PIC__) + /* %ebx may be the PIC register. */ + #define __cpuid(level, a, b, c, d) \ + __asm__ ("xchgl\t%%ebx, %1\n\t" \ + "cpuid\n\t" \ + "xchgl\t%%ebx, %1\n\t" \ + : "=a" (a), "=r" (b), "=c" (c), "=d" (d) \ + : "0" (level)) + #else + #define __cpuid(level, a, b, c, d) \ + __asm__ ("cpuid\n\t" \ + : "=a" (a), "=b" (b), "=c" (c), "=d" (d) \ + : "0" (level)) + #endif + + unsigned int eax, ebx, ecx, edx; + __cpuid(0x1, eax, ebx, ecx, edx); + if(feature_infos[0] != (S32)edx) + { + llerrs << "machdep.cpu.feature_bits doesn't match expected cpuid result!" << llendl; + } +#endif // LL_RELEASE_FOR_DOWNLOAD + + uint64_t ext_feature_info = getSysctlInt64("machdep.cpu.extfeature_bits"); + S32 *ext_feature_infos = (S32*)(&ext_feature_info); + setConfig(eExtFeatureBits, ext_feature_infos[0]); + } +}; +#endif // LL_MSVC -// Interface implementation -LLProcessorInfo::LLProcessorInfo() +////////////////////////////////////////////////////// +// Interface definition +LLProcessorInfo::LLProcessorInfo() : mImpl(NULL) { // *NOTE:Mani - not thread safe. - if(gImpl == NULL) + if(!mImpl) { #ifdef LL_MSVC - gImpl.reset(new LLProcessorInfoWindowsImpl); + static LLProcessorInfoWindowsImpl the_impl; + mImpl = &the_impl; #elif LL_DARWIN - gImpl.reset(new LLProcessorInfoDarwinImpl); + static LLProcessorInfoDarwinImpl the_impl; + mImpl = &the_impl; #else #error "Unimplemented" #endif // LL_MSVC @@ -472,13 +698,13 @@ LLProcessorInfo::LLProcessorInfo() } LLProcessorInfo::~LLProcessorInfo() {} -F64 LLProcessorInfo::getCPUFrequency() const { return gImpl->getCPUFrequency(); } -bool LLProcessorInfo::hasSSE() const { return gImpl->hasSSE(); } -bool LLProcessorInfo::hasSSE2() const { return gImpl->hasSSE2(); } -bool LLProcessorInfo::hasAltivec() const { return gImpl->hasAltivec(); } -std::string LLProcessorInfo::getCPUFamilyName() const { return gImpl->getCPUFamilyName(); } -std::string LLProcessorInfo::getCPUBrandName() const { return gImpl->getCPUBrandName(); } -std::string LLProcessorInfo::getCPUFeatureDescription() const { return gImpl->getCPUFeatureDescription(); } +F64 LLProcessorInfo::getCPUFrequency() const { return mImpl->getCPUFrequency(); } +bool LLProcessorInfo::hasSSE() const { return mImpl->hasSSE(); } +bool LLProcessorInfo::hasSSE2() const { return mImpl->hasSSE2(); } +bool LLProcessorInfo::hasAltivec() const { return mImpl->hasAltivec(); } +std::string LLProcessorInfo::getCPUFamilyName() const { return mImpl->getCPUFamilyName(); } +std::string LLProcessorInfo::getCPUBrandName() const { return mImpl->getCPUBrandName(); } +std::string LLProcessorInfo::getCPUFeatureDescription() const { return mImpl->getCPUFeatureDescription(); } #if 0 // Filename: Processor.cpp @@ -492,14 +718,14 @@ std::string LLProcessorInfo::getCPUFeatureDescription() const { return gImpl->ge // - Optional include of the windows.h header which is // still need for CProcessor::GetCPUFrequency. // 06.03.2002 - My birthday (18th :-)) -// - Replaced the '\r\n' line endings in function -// CProcessor::cpu_infoToText by '\n' +// - Replaced the "\r\n" line endings in function +// CProcessor::cpu_infoToText by "\n" // - Replaced unsigned __int64 by signed __int64 for // solving some compiler conversion problems // - Fixed a bug at family=6, model=6 (Celeron -> P2) ////////////////////////////////////////////////////////////////////////////////// -#include "linden_common.h" + #include "processor.h" @@ -571,7 +797,7 @@ F64 CProcessor::GetCPUFrequency(unsigned int uiMeasureMSecs) return 0; #else // If there are invalid measure time parameters, zero msecs for example, - // we've to exit the function + // we"ve to exit the function if (uiMeasureMSecs < 1) { // If theres already a measured frequency available, we return it @@ -601,7 +827,7 @@ F64 CProcessor::GetCPUFrequency(unsigned int uiMeasureMSecs) // After that we declare some vars and check the frequency of the high // resolution timer for the measure process. - // If there's no high-res timer, we exit. + // If there"s no high-res timer, we exit. __int64 starttime, endtime, timedif, freq, start, end, dif; if (!QueryPerformanceFrequency((LARGE_INTEGER *) &freq)) return 0; @@ -736,7 +962,7 @@ bool CProcessor::AnalyzeIntelProcessor() } // And copy it to the brand id string strncpy(cpu_info.strBrandID, tmp,sizeof(cpu_info.strBrandID)-1); - cpu_info.strBrandID[sizeof(cpu_info.strBrandID)-1]='\0'; + cpu_info.strBrandID[sizeof(cpu_info.strBrandID)-1]="\0"; } else { @@ -892,7 +1118,7 @@ bool CProcessor::AnalyzeIntelProcessor() strcpy(cpu_info.strModel, "Intel Pentium MMX (P55C)"); /*Flawfinder: ignore*/ strncat(strCPUName, "Intel Pentium MMX (P55C core)", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ break; - case 7: // Model = 7: Pentium processor model (don't know difference to Model=2) + case 7: // Model = 7: Pentium processor model (don"t know difference to Model=2) strcpy(cpu_info.strModel, "Intel Pentium (P54C)"); /*Flawfinder: ignore*/ strncat(strCPUName, "Intel Pentium (P54C core)", sizeof(strCPUName)-(strlen(strCPUName)-1)); /*Flawfinder: ignore*/ break; @@ -1095,7 +1321,7 @@ bool CProcessor::AnalyzeIntelProcessor() } else { - // If there's no serial number support we just put "No serial number" + // If there"s no serial number support we just put "No serial number" snprintf( /* Flawfinder: ignore */ cpu_info.strProcessorSerial, sizeof(cpu_info.strProcessorSerial), @@ -1175,7 +1401,7 @@ bool CProcessor::AnalyzeAMDProcessor() } // And copy it to the brand id string strncpy(cpu_info.strBrandID, tmp,sizeof(cpu_info.strBrandID)-1); - cpu_info.strBrandID[sizeof(cpu_info.strBrandID)-1]='\0'; + cpu_info.strBrandID[sizeof(cpu_info.strBrandID)-1]="\0"; } else { @@ -1380,7 +1606,7 @@ bool CProcessor::AnalyzeAMDProcessor() // 0x80000006 if (cpu_info.MaxSupportedExtendedLevel >= 0x80000006) { - // If it's present, we read it out + // If it"s present, we read it out __asm { mov eax, 0x80000005 @@ -1458,8 +1684,8 @@ bool CProcessor::AnalyzeAMDProcessor() cpu_info._L1.Instruction.uiLineSize = edxreg & 0xFF; } - // Note: I'm not absolutely sure that the L1 page size code (the - // 'if/else if/else if' structs above) really detects the real page + // Note: I"m not absolutely sure that the L1 page size code (the + // "if/else if/else if" structs above) really detects the real page // size for the TLB. Somebody should check it.... // Now we read the ext. CPUID level 0x80000006 @@ -1591,8 +1817,8 @@ bool CProcessor::CheckCPUIDPresence() #if LL_WINDOWS unsigned long BitChanged; - // We've to check if we can toggle the flag register bit 21 - // If we can't the processor does not support the CPUID command + // We"ve to check if we can toggle the flag register bit 21 + // If we can"t the processor does not support the CPUID command __asm { pushfd @@ -1620,7 +1846,7 @@ bool CProcessor::CheckCPUIDPresence() ////////////////////////////////////////////////////////////////////////////////// void CProcessor::DecodeProcessorConfiguration(unsigned int cfg) { - // First we ensure that there's only one single byte + // First we ensure that there"s only one single byte cfg &= 0xFF; // Then we do a big switch @@ -1961,7 +2187,7 @@ void CProcessor::GetStandardProcessorConfiguration() mov ecxreg, ecx mov edxreg, edx } - // We have to repeat this reading for 'num' times + // We have to repeat this reading for "num" times num = eaxreg & 0xFF; // Then we call the big decode switch function @@ -2193,7 +2419,7 @@ const ProcessorInfo *CProcessor::Getcpu_info() { // In Solaris the CPU info is in the kstats // try "psrinfo" or "kstat cpu_info" to see all - // that's available + // that"s available int ncpus=0, i; kstat_ctl_t *kc; kstat_t *ks; @@ -2241,7 +2467,7 @@ const ProcessorInfo *CProcessor::Getcpu_info() strncat(cpu_info.strFamily, (char *)KSTAT_NAMED_STR_PTR(ksi), sizeof(cpu_info.strFamily)-strlen(cpu_info.strFamily)-1); strncpy(cpu_info.strBrandID, strCPUName,sizeof(cpu_info.strBrandID)-1); - cpu_info.strBrandID[sizeof(cpu_info.strBrandID)-1]='\0'; + cpu_info.strBrandID[sizeof(cpu_info.strBrandID)-1]="\0"; // DEBUG llinfos << "CPU brand: " << strCPUName << llendl; continue; } @@ -2274,7 +2500,7 @@ const ProcessorInfo *CProcessor::Getcpu_info() // DEBUG llinfo << "The system has " << ncpus << " CPUs with a clock rate of " << uqwFrequency << "MHz." << llendl; -#if defined (__i386) // we really don't care about the CPU extensions on SPARC but on x86... +#if defined (__i386) // we really don"t care about the CPU extensions on SPARC but on x86... // Now get cpu extensions @@ -2585,7 +2811,7 @@ const ProcessorInfo *CProcessor::Getcpu_info() // Terse CPU info uses this string... strncpy(cpu_info.strBrandID, strCPUName,sizeof(cpu_info.strBrandID)-1); /* Flawfinder: ignore */ - cpu_info.strBrandID[sizeof(cpu_info.strBrandID)-1]='\0'; + cpu_info.strBrandID[sizeof(cpu_info.strBrandID)-1]="\0"; // Fun cache config stuff... @@ -2682,7 +2908,7 @@ bool CProcessor::cpu_infoToText(char *strBuffer, unsigned int uiMaxLen) { COPYADD("Processor Serial: Disabled\n"); } -#if !LL_SOLARIS // NOTE: Why bother printing all this when it's irrelavent +#if !LL_SOLARIS // NOTE: Why bother printing all this when it"s irrelavent COPYADD("\n\n// CPU Configuration\n////////////////////\n"); FORMATADD("L1 instruction cache: %s\n", cpu_info._L1.Instruction.strCache); diff --git a/indra/llcommon/llprocessor.h b/indra/llcommon/llprocessor.h index 1cea6245a3..e33af77143 100644 --- a/indra/llcommon/llprocessor.h +++ b/indra/llcommon/llprocessor.h @@ -33,6 +33,7 @@ #ifndef LLPROCESSOR_H #define LLPROCESSOR_H +class LLProcessorInfoImpl; class LLProcessorInfo { @@ -47,6 +48,8 @@ public: std::string getCPUFamilyName() const; std::string getCPUBrandName() const; std::string getCPUFeatureDescription() const; +private: + LLProcessorInfoImpl* mImpl; }; # if 0 diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index becc30832d..21d908035f 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -324,10 +324,6 @@ const char *VFS_INDEX_FILE_BASE = "index.db2.x."; static std::string gWindowTitle; -std::string gLoginPage; -std::vector gLoginURIs; -static std::string gHelperURI; - LLAppViewer::LLUpdaterInfo *LLAppViewer::sUpdaterInfo = NULL ; void idle_afk_check() -- cgit v1.3 From 58f813a136a5a889e01b39e96714af91402321ba Mon Sep 17 00:00:00 2001 From: "Mark Palange (Mani)" Date: Tue, 2 Feb 2010 14:45:42 -0800 Subject: merge --- etc/message.xml | 2 +- indra/lib/python/indra/util/llmanifest.py | 1 + indra/llaudio/llstreamingaudio_fmod.cpp | 2 +- indra/llcommon/llfasttimer.h | 194 ++++++++------ indra/llmessage/llcurl.cpp | 20 +- indra/llmessage/llcurl.h | 11 + indra/llmessage/llhttpclient.cpp | 2 +- indra/llmessage/llurlrequest.cpp | 1 + indra/llplugin/llpluginclassmedia.cpp | 35 ++- indra/llplugin/llpluginclassmedia.h | 18 +- indra/llplugin/llpluginprocesschild.cpp | 10 +- indra/llrender/llfontgl.cpp | 21 +- indra/llrender/llfontgl.h | 8 +- indra/llrender/llgl.cpp | 37 +++ indra/llrender/llglstates.h | 2 + indra/llrender/llimagegl.cpp | 84 +++--- indra/llrender/llimagegl.h | 2 +- indra/llrender/llrender.cpp | 2 + indra/llrender/llvertexbuffer.cpp | 42 +-- indra/llui/CMakeLists.txt | 4 + indra/llui/llcheckboxctrl.cpp | 2 +- indra/llui/llconsole.cpp | 10 +- indra/llui/lldockablefloater.cpp | 9 + indra/llui/lldockablefloater.h | 17 ++ indra/llui/lldockcontrol.cpp | 12 +- indra/llui/llflatlistview.cpp | 12 +- indra/llui/llfloater.cpp | 64 +++-- indra/llui/llfloater.h | 3 +- indra/llui/lllayoutstack.h | 5 + indra/llui/lllineeditor.cpp | 3 +- indra/llui/llmenubutton.h | 1 + indra/llui/llmenugl.cpp | 61 +++-- indra/llui/llmultislider.cpp | 2 +- indra/llui/llpanel.cpp | 61 ++++- indra/llui/llpanel.h | 3 + indra/llui/llresizehandle.cpp | 81 +++--- indra/llui/llscrollcontainer.cpp | 5 + indra/llui/llscrolllistcell.cpp | 2 +- indra/llui/llscrolllistctrl.cpp | 5 +- indra/llui/llscrolllistctrl.h | 3 + indra/llui/lltextbase.cpp | 21 +- indra/llui/lltexteditor.cpp | 3 +- indra/llui/lluictrl.cpp | 8 + indra/llui/llurlentry.cpp | 10 +- indra/llui/tests/llurlentry_test.cpp | 66 +++++ indra/llvfs/lldir.cpp | 15 +- indra/llvfs/lldir.h | 2 +- indra/llvfs/lldir_linux.cpp | 3 +- indra/llvfs/lldir_solaris.cpp | 2 +- indra/llwindow/llkeyboardwin32.cpp | 2 +- indra/llwindow/llwindowwin32.cpp | 24 +- indra/media_plugins/webkit/media_plugin_webkit.cpp | 57 +++- indra/newview/CMakeLists.txt | 38 +-- indra/newview/app_settings/ignorable_dialogs.xml | 15 +- indra/newview/app_settings/settings.xml | 87 +++---- indra/newview/character/avatar_lad.xml | 12 +- indra/newview/llagent.cpp | 27 +- indra/newview/llagent.h | 1 + indra/newview/llagentwearables.cpp | 286 ++++++++++++++++++--- indra/newview/llappearancemgr.cpp | 174 +++++++++++-- indra/newview/llappearancemgr.h | 17 +- indra/newview/llappviewer.cpp | 59 +++-- indra/newview/llavataractions.cpp | 10 +- indra/newview/llavatariconctrl.cpp | 4 +- indra/newview/llavatarlist.cpp | 19 +- indra/newview/llavatarlist.h | 4 + indra/newview/llavatarlistitem.cpp | 266 ++++++++++++------- indra/newview/llavatarlistitem.h | 71 +++-- indra/newview/llbottomtray.cpp | 96 ++++++- indra/newview/llbottomtray.h | 17 +- indra/newview/llcallfloater.cpp | 164 ++++-------- indra/newview/llcallfloater.h | 23 +- indra/newview/llchatbar.cpp | 3 +- indra/newview/llchathistory.cpp | 65 +++-- indra/newview/llchathistory.h | 1 + indra/newview/llchatitemscontainerctrl.cpp | 64 +++-- indra/newview/llchatitemscontainerctrl.h | 1 + indra/newview/llchiclet.cpp | 67 ++++- indra/newview/llchiclet.h | 21 +- indra/newview/llcompilequeue.cpp | 15 +- indra/newview/lldateutil.cpp | 7 +- indra/newview/lldrawpoolbump.cpp | 4 +- indra/newview/lldrawpoolwlsky.cpp | 21 +- indra/newview/lldrawpoolwlsky.h | 2 +- indra/newview/llfasttimerview.cpp | 6 +- indra/newview/llfavoritesbar.cpp | 38 +-- indra/newview/llfavoritesbar.h | 11 +- indra/newview/llfirstuse.cpp | 15 +- indra/newview/llfirstuse.h | 7 + indra/newview/llfloaterabout.cpp | 19 +- indra/newview/llfloaterbulkpermission.cpp | 1 - indra/newview/llfloatercamera.cpp | 25 +- indra/newview/llfloatercamera.h | 4 +- indra/newview/llfloaterchat.cpp | 33 +-- indra/newview/llfloaterchat.h | 2 +- indra/newview/llfloaterchatterbox.cpp | 28 +- indra/newview/llfloatergesture.cpp | 12 +- indra/newview/llfloatergesture.h | 2 +- indra/newview/llfloatergroups.cpp | 9 +- indra/newview/llfloaterinventory.cpp | 4 +- indra/newview/llfloaterland.cpp | 7 +- indra/newview/llfloaterland.h | 1 - indra/newview/llfloatermediasettings.cpp | 10 +- indra/newview/llfloaterpreference.cpp | 60 ++++- indra/newview/llfloaterpreference.h | 6 +- indra/newview/llfloaterregioninfo.cpp | 7 +- indra/newview/llfloaterscriptlimits.cpp | 101 +++++++- indra/newview/llfloaterscriptlimits.h | 14 +- indra/newview/llfloatersearch.cpp | 39 +++ indra/newview/llfloatersettingsdebug.cpp | 2 +- indra/newview/llfloatersnapshot.cpp | 4 +- indra/newview/llfloatertools.cpp | 60 +++-- indra/newview/llfloatervoicedevicesettings.cpp | 38 +-- indra/newview/llfloaterworldmap.cpp | 76 ++---- indra/newview/llfloaterworldmap.h | 5 +- indra/newview/llfolderview.cpp | 36 +-- indra/newview/llfolderview.h | 2 +- indra/newview/llfoldervieweventlistener.h | 1 + indra/newview/llfolderviewitem.cpp | 133 +++++----- indra/newview/llfolderviewitem.h | 20 +- indra/newview/llgesturemgr.cpp | 10 + indra/newview/llgesturemgr.h | 2 + indra/newview/llgroupactions.cpp | 10 +- indra/newview/llgrouplist.cpp | 5 +- indra/newview/llhudtext.cpp | 6 +- indra/newview/llimfloater.cpp | 130 ++++++---- indra/newview/llimfloater.h | 5 +- indra/newview/llimfloatercontainer.cpp | 11 + indra/newview/llimfloatercontainer.h | 6 +- indra/newview/llimpanel.cpp | 2 +- indra/newview/llimview.cpp | 112 +++++--- indra/newview/llimview.h | 17 +- indra/newview/llinspect.h | 2 +- indra/newview/llinspectavatar.cpp | 103 +++++--- indra/newview/llinspectobject.cpp | 20 +- indra/newview/llinventorybridge.cpp | 143 ++++------- indra/newview/llinventorybridge.h | 1 + indra/newview/llinventoryfunctions.cpp | 3 +- indra/newview/llinventorymodel.cpp | 106 +++++++- indra/newview/llinventorymodel.h | 33 +++ indra/newview/llinventoryobserver.cpp | 35 ++- indra/newview/llinventorypanel.cpp | 53 ++-- indra/newview/llinventorypanel.h | 2 +- indra/newview/lllandmarklist.cpp | 7 + indra/newview/lllocationhistory.cpp | 6 + indra/newview/lllogchat.cpp | 4 - indra/newview/llmanip.cpp | 2 +- indra/newview/llmediactrl.cpp | 272 +++++++++++--------- indra/newview/llmediactrl.h | 1 - indra/newview/llmenucommands.cpp | 3 +- indra/newview/llmorphview.cpp | 4 +- indra/newview/llmutelist.cpp | 7 +- indra/newview/llnamebox.cpp | 35 ++- indra/newview/llnamebox.h | 6 + indra/newview/llnamelistctrl.cpp | 80 ++++-- indra/newview/llnamelistctrl.h | 5 +- indra/newview/llnearbychat.cpp | 28 +- indra/newview/llnearbychatbar.cpp | 252 +++++++++++++----- indra/newview/llnearbychatbar.h | 47 +++- indra/newview/llnearbychathandler.cpp | 23 +- indra/newview/llnotificationofferhandler.cpp | 4 +- indra/newview/lloutputmonitorctrl.cpp | 65 ++++- indra/newview/lloutputmonitorctrl.h | 29 ++- indra/newview/llpanelavatar.cpp | 20 +- indra/newview/llpanelclassified.cpp | 15 +- indra/newview/llpanelclassified.h | 1 + indra/newview/llpanelgroup.cpp | 79 +++++- indra/newview/llpanelgroup.h | 4 + indra/newview/llpanelgroupgeneral.cpp | 11 +- indra/newview/llpanelgroupnotices.cpp | 2 +- indra/newview/llpanelgrouproles.cpp | 61 ++--- indra/newview/llpanelgrouproles.h | 13 +- indra/newview/llpanelhome.cpp | 2 +- indra/newview/llpanelimcontrolpanel.cpp | 9 +- indra/newview/llpanellandaudio.cpp | 62 ++--- indra/newview/llpanellandaudio.h | 4 +- indra/newview/llpanellandmarkinfo.cpp | 7 + indra/newview/llpanellandmarks.cpp | 57 +++- indra/newview/llpanellandmarks.h | 16 ++ indra/newview/llpanelmaininventory.cpp | 6 +- indra/newview/llpanelobject.cpp | 4 +- indra/newview/llpanelobjectinventory.cpp | 2 +- indra/newview/llpaneloutfitsinventory.cpp | 175 +++++++++++-- indra/newview/llpaneloutfitsinventory.h | 6 +- indra/newview/llpanelpeople.cpp | 27 +- indra/newview/llpanelpeople.h | 2 +- indra/newview/llpanelpick.cpp | 100 +++---- indra/newview/llpanelpick.h | 23 +- indra/newview/llpanelpicks.cpp | 14 +- indra/newview/llpanelpicks.h | 1 + indra/newview/llpanelplaces.cpp | 31 +++ indra/newview/llpanelplaces.h | 6 + indra/newview/llpanelprimmediacontrols.cpp | 120 +++++---- indra/newview/llpanelprimmediacontrols.h | 4 +- indra/newview/llpanelteleporthistory.cpp | 14 +- indra/newview/llpanelvolume.cpp | 4 +- indra/newview/llparticipantlist.cpp | 23 +- indra/newview/llparticipantlist.h | 2 +- indra/newview/llpreviewgesture.cpp | 6 + indra/newview/llpreviewnotecard.cpp | 1 - indra/newview/llpreviewscript.cpp | 1 - 201 files changed, 4193 insertions(+), 2010 deletions(-) (limited to 'indra/newview/llappviewer.cpp') diff --git a/etc/message.xml b/etc/message.xml index da08e12aa1..c17ae3656d 100644 --- a/etc/message.xml +++ b/etc/message.xml @@ -50,7 +50,7 @@ OpenCircuit flavor - template + llsd trusted-sender false diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py index 8c05210618..7e5b86c53f 100644 --- a/indra/lib/python/indra/util/llmanifest.py +++ b/indra/lib/python/indra/util/llmanifest.py @@ -119,6 +119,7 @@ ARGUMENTS=[ On Linux this would try to use Linux_i686Manifest.""", default=""), dict(name='build', description='Build directory.', default=DEFAULT_SRCTREE), + dict(name='buildtype', description='Build type (i.e. Debug, Release, RelWithDebInfo).', default=None), dict(name='configuration', description="""The build configuration used.""", default="Release"), diff --git a/indra/llaudio/llstreamingaudio_fmod.cpp b/indra/llaudio/llstreamingaudio_fmod.cpp index a71a87203c..a4620fa13c 100644 --- a/indra/llaudio/llstreamingaudio_fmod.cpp +++ b/indra/llaudio/llstreamingaudio_fmod.cpp @@ -174,7 +174,7 @@ void LLStreamingAudio_FMOD::update() break; case -3: // failed to open, file not found, perhaps - llwarns << "InternetSteam - failed to open" << llendl; + llwarns << "InternetStream - failed to open" << llendl; stop(); return; case -4: diff --git a/indra/llcommon/llfasttimer.h b/indra/llcommon/llfasttimer.h index f5c90291b8..8af79c90fd 100644 --- a/indra/llcommon/llfasttimer.h +++ b/indra/llcommon/llfasttimer.h @@ -1,11 +1,11 @@ -/** +/** * @file llfasttimer.h * @brief Declaration of a fast timer. * * $LicenseInfo:firstyear=2004&license=viewergpl$ - * + * * Copyright (c) 2004-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 @@ -13,17 +13,17 @@ * ("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. @@ -39,13 +39,37 @@ #define TIME_FAST_TIMERS 0 #if LL_WINDOWS +#define LL_INLINE __forceinline + +// +// NOTE: put back in when we aren't using platform sdk anymore +// +// because MS has different signatures for these functions in winnt.h +// need to rename them to avoid conflicts +//#define _interlockedbittestandset _renamed_interlockedbittestandset +//#define _interlockedbittestandreset _renamed_interlockedbittestandreset +//#include +//#undef _interlockedbittestandset +//#undef _interlockedbittestandreset + +//inline U32 get_cpu_clock_count_32() +//{ +// U64 time_stamp = __rdtsc(); +// return (U32)(time_stamp >> 8); +//} +// +//// return full timer value, *not* shifted by 8 bits +//inline U64 get_cpu_clock_count_64() +//{ +// return __rdtsc(); +//} // shift off lower 8 bits for lower resolution but longer term timing // on 1Ghz machine, a 32-bit word will hold ~1000 seconds of timing inline U32 get_cpu_clock_count_32() { U32 ret_val; - __asm + __asm { _emit 0x0f _emit 0x31 @@ -61,7 +85,7 @@ inline U32 get_cpu_clock_count_32() inline U64 get_cpu_clock_count_64() { U64 ret_val; - __asm + __asm { _emit 0x0f _emit 0x31 @@ -72,19 +96,20 @@ inline U64 get_cpu_clock_count_64() } return ret_val; } - -#endif // LL_WINDOWS +#else +#define LL_INLINE +#endif #if (LL_LINUX || LL_SOLARIS || LL_DARWIN) && (defined(__i386__) || defined(__amd64__)) inline U32 get_cpu_clock_count_32() -{ - U64 x; - __asm__ volatile (".byte 0x0f, 0x31": "=A"(x)); - return (U32)x >> 8; +{ + U64 x; + __asm__ volatile (".byte 0x0f, 0x31": "=A"(x)); + return (U32)x >> 8; } inline U32 get_cpu_clock_count_64() -{ +{ U64 x; __asm__ volatile (".byte 0x0f, 0x31": "=A"(x)); return x >> 8; @@ -103,7 +128,7 @@ inline U32 get_cpu_clock_count_32() } inline U32 get_cpu_clock_count_64() -{ +{ return get_clock_count(); } #endif @@ -113,12 +138,27 @@ class LLMutex; #include #include "llsd.h" - class LL_COMMON_API LLFastTimer { public: + + class NamedTimer; + + struct LL_COMMON_API FrameState + { + FrameState(NamedTimer* timerp); + + U32 mSelfTimeCounter; + U32 mCalls; + FrameState* mParent; // info for caller timer + FrameState* mLastCaller; // used to bootstrap tree construction + NamedTimer* mTimer; + U16 mActiveCount; // number of timers with this ID active on stack + bool mMoveUpTree; // needs to be moved up the tree of timers at the end of frame + }; + // stores a "named" timer instance to be reused via multiple LLFastTimer stack instances - class LL_COMMON_API NamedTimer + class LL_COMMON_API NamedTimer : public LLInstanceTracker { friend class DeclareTimer; @@ -149,25 +189,11 @@ public: static NamedTimer& getRootNamedTimer(); - struct FrameState - { - FrameState(NamedTimer* timerp); - - U32 mSelfTimeCounter; - U32 mCalls; - FrameState* mParent; // info for caller timer - FrameState* mLastCaller; // used to bootstrap tree construction - NamedTimer* mTimer; - U16 mActiveCount; // number of timers with this ID active on stack - bool mMoveUpTree; // needs to be moved up the tree of timers at the end of frame - }; - S32 getFrameStateIndex() const { return mFrameStateIndex; } FrameState& getFrameState() const; - - private: + private: friend class LLFastTimer; friend class NamedTimerFactory; @@ -178,7 +204,7 @@ public: // recursive call to gather total time from children static void accumulateTimings(); - // updates cumulative times and hierarchy, + // updates cumulative times and hierarchy, // can be called multiple times in a frame, at any point static void processTimes(); @@ -186,7 +212,6 @@ public: static void resetFrame(); static void reset(); - // // members // @@ -207,58 +232,47 @@ public: std::vector mChildren; bool mCollapsed; // don't show children bool mNeedsSorting; // sort children whenever child added - }; // used to statically declare a new named timer class LL_COMMON_API DeclareTimer : public LLInstanceTracker { + friend class LLFastTimer; public: DeclareTimer(const std::string& name, bool open); DeclareTimer(const std::string& name); static void updateCachedPointers(); - // convertable to NamedTimer::FrameState for convenient usage of LLFastTimer(declared_timer) - operator NamedTimer::FrameState&() { return *mFrameState; } private: - NamedTimer& mTimer; - NamedTimer::FrameState* mFrameState; + NamedTimer& mTimer; + FrameState* mFrameState; }; - public: - static LLMutex* sLogLock; - static std::queue sLogQueue; - static BOOL sLog; - static BOOL sMetricLog; - - typedef std::vector info_list_t; - static info_list_t& getFrameStateList(); + LLFastTimer(LLFastTimer::FrameState* state); - enum RootTimerMarker { ROOT }; - LLFastTimer(RootTimerMarker); - - LLFastTimer(NamedTimer::FrameState& timer) - : mFrameState(&timer) + LL_INLINE LLFastTimer(LLFastTimer::DeclareTimer& timer) + : mFrameState(timer.mFrameState) { #if TIME_FAST_TIMERS U64 timer_start = get_cpu_clock_count_64(); #endif #if FAST_TIMER_ON - NamedTimer::FrameState* frame_state = &timer; - U32 cur_time = get_cpu_clock_count_32(); - mStartSelfTime = cur_time; - mStartTotalTime = cur_time; + LLFastTimer::FrameState* frame_state = mFrameState; + mStartTime = get_cpu_clock_count_32(); frame_state->mActiveCount++; frame_state->mCalls++; // keep current parent as long as it is active when we are frame_state->mMoveUpTree |= (frame_state->mParent->mActiveCount == 0); - - mLastTimer = sCurTimer; - sCurTimer = this; + + LLFastTimer::CurTimerData* cur_timer_data = &LLFastTimer::sCurTimerData; + mLastTimerData = *cur_timer_data; + cur_timer_data->mCurTimer = this; + cur_timer_data->mFrameState = frame_state; + cur_timer_data->mChildTime = 0; #endif #if TIME_FAST_TIMERS U64 timer_end = get_cpu_clock_count_64(); @@ -266,41 +280,54 @@ public: #endif } - ~LLFastTimer() + LL_INLINE ~LLFastTimer() { #if TIME_FAST_TIMERS U64 timer_start = get_cpu_clock_count_64(); #endif #if FAST_TIMER_ON - NamedTimer::FrameState* frame_state = mFrameState; - U32 cur_time = get_cpu_clock_count_32(); - frame_state->mSelfTimeCounter += cur_time - mStartSelfTime; + LLFastTimer::FrameState* frame_state = mFrameState; + U32 total_time = get_cpu_clock_count_32() - mStartTime; + frame_state->mSelfTimeCounter += total_time - LLFastTimer::sCurTimerData.mChildTime; frame_state->mActiveCount--; - LLFastTimer* last_timer = mLastTimer; - sCurTimer = last_timer; // store last caller to bootstrap tree creation - frame_state->mLastCaller = last_timer->mFrameState; + // do this in the destructor in case of recursion to get topmost caller + frame_state->mLastCaller = mLastTimerData.mFrameState; // we are only tracking self time, so subtract our total time delta from parents - U32 total_time = cur_time - mStartTotalTime; - last_timer->mStartSelfTime += total_time; + mLastTimerData.mChildTime += total_time; + + LLFastTimer::sCurTimerData = mLastTimerData; #endif #if TIME_FAST_TIMERS U64 timer_end = get_cpu_clock_count_64(); sTimerCycles += timer_end - timer_start; sTimerCalls++; -#endif +#endif } +public: + static LLMutex* sLogLock; + static std::queue sLogQueue; + static BOOL sLog; + static BOOL sMetricLog; + static bool sPauseHistory; + static bool sResetHistory; + static U64 sTimerCycles; + static U32 sTimerCalls; + + typedef std::vector info_list_t; + static info_list_t& getFrameStateList(); + // call this once a frame to reset timers static void nextFrame(); // dumps current cumulative frame stats to log // call nextFrame() to reset timers - static void dumpCurTimes(); + static void dumpCurTimes(); // call this to reset timer hierarchy, averages, etc. static void reset(); @@ -312,23 +339,26 @@ public: static void writeLog(std::ostream& os); static const NamedTimer* getTimerByName(const std::string& name); -public: - static bool sPauseHistory; - static bool sResetHistory; - static U64 sTimerCycles; - static U32 sTimerCalls; - + struct CurTimerData + { + LLFastTimer* mCurTimer; + FrameState* mFrameState; + U32 mChildTime; + }; + static CurTimerData sCurTimerData; + private: - static LLFastTimer* sCurTimer; static S32 sCurFrameIndex; static S32 sLastFrameIndex; static U64 sLastFrameTime; static info_list_t* sTimerInfos; - U32 mStartSelfTime; // start time + time of all child timers - U32 mStartTotalTime; // start time + time of all child timers - NamedTimer::FrameState* mFrameState; - LLFastTimer* mLastTimer; + U32 mStartTime; + LLFastTimer::FrameState* mFrameState; + LLFastTimer::CurTimerData mLastTimerData; + }; +typedef class LLFastTimer LLFastTimer; + #endif // LL_LLFASTTIMER_H diff --git a/indra/llmessage/llcurl.cpp b/indra/llmessage/llcurl.cpp index dc02367a62..024e17a777 100644 --- a/indra/llmessage/llcurl.cpp +++ b/indra/llmessage/llcurl.cpp @@ -89,6 +89,10 @@ 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) @@ -102,6 +106,18 @@ 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() { @@ -465,7 +481,8 @@ void LLCurl::Easy::prepRequest(const std::string& url, setErrorBuffer(); setCA(); - setopt(CURLOPT_SSL_VERIFYPEER, true); + setopt(CURLOPT_SSL_VERIFYPEER, LLCurl::getSSLVerify()); + setopt(CURLOPT_SSL_VERIFYHOST, LLCurl::getSSLVerify()? 2 : 0); setopt(CURLOPT_TIMEOUT, CURL_REQUEST_TIMEOUT); setoptString(CURLOPT_URL, url); @@ -1044,4 +1061,3 @@ void LLCurl::cleanupClass() #endif curl_global_cleanup(); } - diff --git a/indra/llmessage/llcurl.h b/indra/llmessage/llcurl.h index 1bc1767966..caf02cccd9 100644 --- a/indra/llmessage/llcurl.h +++ b/indra/llmessage/llcurl.h @@ -157,6 +157,16 @@ 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 */ @@ -182,6 +192,7 @@ public: private: static std::string sCAPath; static std::string sCAFile; + static bool sSSLVerify; }; namespace boost diff --git a/indra/llmessage/llhttpclient.cpp b/indra/llmessage/llhttpclient.cpp index 12ecbb36eb..dd56e18caf 100644 --- a/indra/llmessage/llhttpclient.cpp +++ b/indra/llmessage/llhttpclient.cpp @@ -222,7 +222,7 @@ static void request( LLPumpIO::chain_t chain; LLURLRequest* req = new LLURLRequest(method, url); - req->checkRootCertificate(true); + req->checkRootCertificate(LLCurl::getSSLVerify()); lldebugs << LLURLRequest::actionAsVerb(method) << " " << url << " " diff --git a/indra/llmessage/llurlrequest.cpp b/indra/llmessage/llurlrequest.cpp index 81b7761ed5..4e7ceff984 100644 --- a/indra/llmessage/llurlrequest.cpp +++ b/indra/llmessage/llurlrequest.cpp @@ -163,6 +163,7 @@ void LLURLRequest::setBodyLimit(U32 size) 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, ""); } diff --git a/indra/llplugin/llpluginclassmedia.cpp b/indra/llplugin/llpluginclassmedia.cpp index 1a382643da..3d2eaed5c5 100644 --- a/indra/llplugin/llpluginclassmedia.cpp +++ b/indra/llplugin/llpluginclassmedia.cpp @@ -37,6 +37,8 @@ #include "llpluginclassmedia.h" #include "llpluginmessageclasses.h" +#include "llqtwebkit.h" + static int LOW_PRIORITY_TEXTURE_SIZE_DEFAULT = 256; static int nextPowerOf2( int value ) @@ -124,7 +126,8 @@ void LLPluginClassMedia::reset() mCanPaste = false; mMediaName.clear(); mMediaDescription.clear(); - + mBackgroundColor = LLColor4(1.0f, 1.0f, 1.0f, 1.0f); + // media_browser class mNavigateURI.clear(); mNavigateResultCode = -1; @@ -133,6 +136,9 @@ void LLPluginClassMedia::reset() mHistoryForwardAvailable = false; mStatusText.clear(); mProgressPercent = 0; + mClickURL.clear(); + mClickTarget.clear(); + mClickTargetType = TARGET_NONE; // media_time class mCurrentTime = 0.0f; @@ -234,6 +240,10 @@ void LLPluginClassMedia::idle(void) message.setValueS32("height", mRequestedMediaHeight); message.setValueS32("texture_width", mRequestedTextureWidth); message.setValueS32("texture_height", mRequestedTextureHeight); + message.setValueReal("background_r", mBackgroundColor.mV[VX]); + message.setValueReal("background_g", mBackgroundColor.mV[VY]); + message.setValueReal("background_b", mBackgroundColor.mV[VZ]); + message.setValueReal("background_a", mBackgroundColor.mV[VW]); mPlugin->sendMessage(message); // DO NOT just use sendMessage() here -- we want this to jump ahead of the queue. LL_DEBUGS("Plugin") << "Sending size_change" << LL_ENDL; @@ -664,6 +674,26 @@ void LLPluginClassMedia::paste() sendMessage(message); } +LLPluginClassMedia::ETargetType getTargetTypeFromLLQtWebkit(int target_type) +{ + // convert a LinkTargetType value from llqtwebkit to an ETargetType + // so that we don't expose the llqtwebkit header in viewer code + switch (target_type) + { + case LinkTargetType::LTT_TARGET_NONE: + return LLPluginClassMedia::TARGET_NONE; + + case LinkTargetType::LTT_TARGET_BLANK: + return LLPluginClassMedia::TARGET_BLANK; + + case LinkTargetType::LTT_TARGET_EXTERNAL: + return LLPluginClassMedia::TARGET_EXTERNAL; + + default: + return LLPluginClassMedia::TARGET_OTHER; + } +} + /* virtual */ void LLPluginClassMedia::receivePluginMessage(const LLPluginMessage &message) { @@ -916,12 +946,15 @@ void LLPluginClassMedia::receivePluginMessage(const LLPluginMessage &message) { mClickURL = message.getValue("uri"); mClickTarget = message.getValue("target"); + U32 target_type = message.getValueU32("target_type"); + mClickTargetType = ::getTargetTypeFromLLQtWebkit(target_type); mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_HREF); } else if(message_name == "click_nofollow") { mClickURL = message.getValue("uri"); mClickTarget.clear(); + mClickTargetType = TARGET_NONE; mediaEvent(LLPluginClassMediaOwner::MEDIA_EVENT_CLICK_LINK_NOFOLLOW); } else diff --git a/indra/llplugin/llpluginclassmedia.h b/indra/llplugin/llpluginclassmedia.h index b58067733b..ebb9099576 100644 --- a/indra/llplugin/llpluginclassmedia.h +++ b/indra/llplugin/llpluginclassmedia.h @@ -39,7 +39,7 @@ #include "llrect.h" #include "llpluginclassmediaowner.h" #include - +#include "v4color.h" class LLPluginClassMedia : public LLPluginProcessParentOwner { @@ -86,6 +86,8 @@ public: void setSize(int width, int height); void setAutoScale(bool auto_scale); + void setBackgroundColor(LLColor4 color) { mBackgroundColor = color; }; + // Returns true if all of the texture parameters (depth, format, size, and texture size) are set up and consistent. // This will initially be false, and will also be false for some time after setSize while the resize is processed. // Note that if this returns true, it is safe to use all the get() functions above without checking for invalid return values @@ -212,6 +214,17 @@ public: // This is valid after MEDIA_EVENT_CLICK_LINK_HREF std::string getClickTarget() const { return mClickTarget; }; + typedef enum + { + TARGET_NONE, // empty href target string + TARGET_BLANK, // target to open link in user's preferred browser + TARGET_EXTERNAL, // target to open link in external browser + TARGET_OTHER // nonempty and unsupported target type + }ETargetType; + + // This is valid after MEDIA_EVENT_CLICK_LINK_HREF + ETargetType getClickTargetType() const { return mClickTargetType; }; + std::string getMediaName() const { return mMediaName; }; std::string getMediaDescription() const { return mMediaDescription; }; @@ -328,6 +341,8 @@ protected: std::string mMediaName; std::string mMediaDescription; + LLColor4 mBackgroundColor; + ///////////////////////////////////////// // media_browser class std::string mNavigateURI; @@ -340,6 +355,7 @@ protected: std::string mLocation; std::string mClickURL; std::string mClickTarget; + ETargetType mClickTargetType; ///////////////////////////////////////// // media_time class diff --git a/indra/llplugin/llpluginprocesschild.cpp b/indra/llplugin/llpluginprocesschild.cpp index ccf6dab942..07fc82c770 100644 --- a/indra/llplugin/llpluginprocesschild.cpp +++ b/indra/llplugin/llpluginprocesschild.cpp @@ -54,8 +54,14 @@ LLPluginProcessChild::~LLPluginProcessChild() if(mInstance != NULL) { sendMessageToPlugin(LLPluginMessage("base", "cleanup")); - delete mInstance; - mInstance = NULL; + + // IMPORTANT: under some (unknown) circumstances the apr_dso_unload() triggered when mInstance is deleted + // appears to fail and lock up which means that a given instance of the slplugin process never exits. + // This is bad, especially when users try to update their version of SL - it fails because the slplugin + // process as well as a bunch of plugin specific files are locked and cannot be overwritten. + exit( 0 ); + //delete mInstance; + //mInstance = NULL; } } diff --git a/indra/llrender/llfontgl.cpp b/indra/llrender/llfontgl.cpp index 37a28ac721..1de1d6ded4 100644 --- a/indra/llrender/llfontgl.cpp +++ b/indra/llrender/llfontgl.cpp @@ -472,7 +472,7 @@ F32 LLFontGL::getWidthF32(const llwchar* wchars, S32 begin_offset, S32 max_chars } // Returns the max number of complete characters from text (up to max_chars) that can be drawn in max_pixels -S32 LLFontGL::maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_chars, BOOL end_on_word_boundary) const +S32 LLFontGL::maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_chars, EWordWrapStyle end_on_word_boundary) const { if (!wchars || !wchars[0] || max_chars == 0) { @@ -562,9 +562,24 @@ S32 LLFontGL::maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_ch drawn_x = cur_x; } - if( clip && end_on_word_boundary && (start_of_last_word != 0) ) + if( clip ) { - i = start_of_last_word; + switch (end_on_word_boundary) + { + case ONLY_WORD_BOUNDARIES: + i = start_of_last_word; + break; + case WORD_BOUNDARY_IF_POSSIBLE: + if (start_of_last_word != 0) + { + i = start_of_last_word; + } + break; + default: + case ANYWHERE: + // do nothing + break; + } } return i; } diff --git a/indra/llrender/llfontgl.h b/indra/llrender/llfontgl.h index ea8eee7690..dfa4cf8ce5 100644 --- a/indra/llrender/llfontgl.h +++ b/indra/llrender/llfontgl.h @@ -122,7 +122,13 @@ public: // The following are called often, frequently with large buffers, so do not use a string interface // Returns the max number of complete characters from text (up to max_chars) that can be drawn in max_pixels - S32 maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_chars = S32_MAX, BOOL end_on_word_boundary = FALSE) const; + typedef enum e_word_wrap_style + { + ONLY_WORD_BOUNDARIES, + WORD_BOUNDARY_IF_POSSIBLE, + ANYWHERE + } EWordWrapStyle ; + S32 maxDrawableChars(const llwchar* wchars, F32 max_pixels, S32 max_chars = S32_MAX, EWordWrapStyle end_on_word_boundary = ANYWHERE) const; // Returns the index of the first complete characters from text that can be drawn in max_pixels // given that the character at start_pos should be the last character (or as close to last as possible). diff --git a/indra/llrender/llgl.cpp b/indra/llrender/llgl.cpp index 3400a72385..187a9a984e 100644 --- a/indra/llrender/llgl.cpp +++ b/indra/llrender/llgl.cpp @@ -1919,6 +1919,16 @@ LLGLDepthTest::LLGLDepthTest(GLboolean depth_enabled, GLboolean write_enabled, G : mPrevDepthEnabled(sDepthEnabled), mPrevDepthFunc(sDepthFunc), mPrevWriteEnabled(sWriteEnabled) { stop_glerror(); + + checkState(); + + if (!depth_enabled) + { // always disable depth writes if depth testing is disabled + // GL spec defines this as a requirement, but some implementations allow depth writes with testing disabled + // The proper way to write to depth buffer with testing disabled is to enable testing and use a depth_func of GL_ALWAYS + write_enabled = FALSE; + } + if (depth_enabled != sDepthEnabled) { gGL.flush(); @@ -1942,6 +1952,7 @@ LLGLDepthTest::LLGLDepthTest(GLboolean depth_enabled, GLboolean write_enabled, G LLGLDepthTest::~LLGLDepthTest() { + checkState(); if (sDepthEnabled != mPrevDepthEnabled ) { gGL.flush(); @@ -1963,6 +1974,32 @@ LLGLDepthTest::~LLGLDepthTest() } } +void LLGLDepthTest::checkState() +{ + if (gDebugGL) + { + GLint func = 0; + GLboolean mask = FALSE; + + glGetIntegerv(GL_DEPTH_FUNC, &func); + glGetBooleanv(GL_DEPTH_WRITEMASK, &mask); + + if (glIsEnabled(GL_DEPTH_TEST) != sDepthEnabled || + sWriteEnabled != mask || + sDepthFunc != func) + { + if (gDebugSession) + { + gFailLog << "Unexpected depth testing state." << std::endl; + } + else + { + LL_GL_ERRS << "Unexpected depth testing state." << LL_ENDL; + } + } + } +} + LLGLClampToFarClip::LLGLClampToFarClip(glh::matrix4f P) { for (U32 i = 0; i < 4; i++) diff --git a/indra/llrender/llglstates.h b/indra/llrender/llglstates.h index 4a51cac438..968a37cab0 100644 --- a/indra/llrender/llglstates.h +++ b/indra/llrender/llglstates.h @@ -46,6 +46,8 @@ public: ~LLGLDepthTest(); + void checkState(); + GLboolean mPrevDepthEnabled; GLenum mPrevDepthFunc; GLboolean mPrevWriteEnabled; diff --git a/indra/llrender/llimagegl.cpp b/indra/llrender/llimagegl.cpp index f8d7ea00e0..cd493481d5 100644 --- a/indra/llrender/llimagegl.cpp +++ b/indra/llrender/llimagegl.cpp @@ -428,49 +428,56 @@ LLImageGL::~LLImageGL() void LLImageGL::init(BOOL usemipmaps) { -#ifdef DEBUG_MISS - mMissed = FALSE; -#endif + // keep these members in the same order as declared in llimagehl.h + // so that it is obvious by visual inspection if we forgot to + // init a field. + + mTextureMemory = 0; + mLastBindTime = 0.f; + + mPickMask = NULL; + mUseMipMaps = usemipmaps; + mHasExplicitFormat = FALSE; + mAutoGenMips = FALSE; + + mIsMask = FALSE; + mNeedsAlphaAndPickMask = TRUE ; + mAlphaStride = 0 ; + mAlphaOffset = 0 ; - mPickMask = NULL; - mTextureMemory = 0; - mLastBindTime = 0.f; + mGLTextureCreated = FALSE ; + mTexName = 0; + mWidth = 0; + mHeight = 0; + mCurrentDiscardLevel = -1; + + mDiscardLevelInAtlas = -1 ; + mTexelsInAtlas = 0 ; + mTexelsInGLTexture = 0 ; - mTarget = GL_TEXTURE_2D; - mBindTarget = LLTexUnit::TT_TEXTURE; - mUseMipMaps = usemipmaps; - mHasMipMaps = false; - mAutoGenMips = FALSE; - mTexName = 0; - mIsResident = 0; + mTarget = GL_TEXTURE_2D; + mBindTarget = LLTexUnit::TT_TEXTURE; + mHasMipMaps = false; + + mIsResident = 0; + + mComponents = 0; + mMaxDiscardLevel = MAX_DISCARD_LEVEL; mTexOptionsDirty = true; mAddressMode = LLTexUnit::TAM_WRAP; mFilterOption = LLTexUnit::TFO_ANISOTROPIC; - mWidth = 0; - mHeight = 0; - mComponents = 0; - - mMaxDiscardLevel = MAX_DISCARD_LEVEL; - mCurrentDiscardLevel = -1; mFormatInternal = -1; mFormatPrimary = (LLGLenum) 0; mFormatType = GL_UNSIGNED_BYTE; mFormatSwapBytes = FALSE; - mHasExplicitFormat = FALSE; - - mGLTextureCreated = FALSE ; - mIsMask = FALSE; - mCategory = -1 ; - mAlphaStride = 0 ; - mAlphaOffset = 0 ; - mNeedsAlphaAndPickMask = TRUE ; +#ifdef DEBUG_MISS + mMissed = FALSE; +#endif - mDiscardLevelInAtlas = -1 ; - mTexelsInAtlas = 0 ; - mTexelsInGLTexture = 0 ; + mCategory = -1; } void LLImageGL::cleanup() @@ -1669,7 +1676,7 @@ void LLImageGL::updatePickMask(S32 width, S32 height, const U8* data_in) } if (mFormatType != GL_UNSIGNED_BYTE || - mFormatPrimary != GL_RGBA) + mFormatPrimary != GL_RGBA) { //cannot generate a pick mask for this texture delete [] mPickMask; @@ -1729,12 +1736,25 @@ BOOL LLImageGL::getMask(const LLVector2 &tc) if (u < 0.f || u > 1.f || v < 0.f || v > 1.f) { - llerrs << "WTF?" << llendl; + LL_WARNS_ONCE("render") << "Ugh, u/v out of range in image mask pick" << LL_ENDL; + u = v = 0.f; + llassert(false); } S32 x = (S32)(u * width); S32 y = (S32)(v * height); + if (x >= width) + { + LL_WARNS_ONCE("render") << "Ooh, width overrun on pick mask read, that coulda been bad." << LL_ENDL; + x = llmax(0, width-1); + } + if (y >= height) + { + LL_WARNS_ONCE("render") << "Ooh, height overrun on pick mask read, that woulda been bad." << LL_ENDL; + y = llmax(0, height-1); + } + S32 idx = y*width+x; S32 offset = idx%8; diff --git a/indra/llrender/llimagegl.h b/indra/llrender/llimagegl.h index 937065043c..facfb7bd62 100644 --- a/indra/llrender/llimagegl.h +++ b/indra/llrender/llimagegl.h @@ -224,7 +224,7 @@ protected: bool mTexOptionsDirty; LLTexUnit::eTextureAddressMode mAddressMode; // Defaults to TAM_WRAP - LLTexUnit::eTextureFilterOptions mFilterOption; // Defaults to TFO_TRILINEAR + LLTexUnit::eTextureFilterOptions mFilterOption; // Defaults to TFO_ANISOTROPIC LLGLint mFormatInternal; // = GL internalformat LLGLenum mFormatPrimary; // = GL format (pixel data format) diff --git a/indra/llrender/llrender.cpp b/indra/llrender/llrender.cpp index fc45df8153..f97d81126e 100644 --- a/indra/llrender/llrender.cpp +++ b/indra/llrender/llrender.cpp @@ -162,6 +162,8 @@ void LLTexUnit::enable(eTextureType type) disable(); // Force a disable of a previous texture type if it's enabled. } mCurrTexType = type; + + gGL.flush(); glEnable(sGLTextureType[type]); } } diff --git a/indra/llrender/llvertexbuffer.cpp b/indra/llrender/llvertexbuffer.cpp index db4189dfea..572ae13909 100644 --- a/indra/llrender/llvertexbuffer.cpp +++ b/indra/llrender/llvertexbuffer.cpp @@ -1061,17 +1061,20 @@ void LLVertexBuffer::setBuffer(U32 data_mask) } } - glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB, &buff); - if ((GLuint)buff != mGLIndices) + if (mGLIndices) { - if (gDebugSession) - { - error = TRUE; - gFailLog << "Invalid GL index buffer bound: " << buff << std::endl; - } - else + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB, &buff); + if ((GLuint)buff != mGLIndices) { - llerrs << "Invalid GL index buffer bound: " << buff << llendl; + if (gDebugSession) + { + error = TRUE; + gFailLog << "Invalid GL index buffer bound: " << buff << std::endl; + } + else + { + llerrs << "Invalid GL index buffer bound: " << buff << llendl; + } } } } @@ -1095,17 +1098,20 @@ void LLVertexBuffer::setBuffer(U32 data_mask) } } - glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB, &buff); - if ((GLuint)buff != mGLIndices) + if (mGLIndices != 0) { - if (gDebugSession) - { - error = TRUE; - gFailLog << "Invalid GL index buffer bound: "<< std::endl; - } - else + glGetIntegerv(GL_ELEMENT_ARRAY_BUFFER_BINDING_ARB, &buff); + if ((GLuint)buff != mGLIndices) { - llerrs << "Invalid GL index buffer bound: " << buff << llendl; + if (gDebugSession) + { + error = TRUE; + gFailLog << "Invalid GL index buffer bound: "<< std::endl; + } + else + { + llerrs << "Invalid GL index buffer bound: " << buff << llendl; + } } } } diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 82ec02d2eb..ce068618e2 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -26,6 +26,8 @@ include_directories( ) set(llui_SOURCE_FILES + llaccordionctrl.cpp + llaccordionctrltab.cpp llbutton.cpp llcheckboxctrl.cpp llclipboard.cpp @@ -111,6 +113,8 @@ set(llui_SOURCE_FILES set(llui_HEADER_FILES CMakeLists.txt + llaccordionctrl.h + llaccordionctrltab.h llbutton.h llcallbackmap.h llcheckboxctrl.h diff --git a/indra/llui/llcheckboxctrl.cpp b/indra/llui/llcheckboxctrl.cpp index cd10dfdb1c..3d32157406 100644 --- a/indra/llui/llcheckboxctrl.cpp +++ b/indra/llui/llcheckboxctrl.cpp @@ -107,8 +107,8 @@ LLCheckBoxCtrl::LLCheckBoxCtrl(const LLCheckBoxCtrl::Params& p) { tbparams.font(p.font); } + tbparams.text_color( p.enabled() ? p.text_enabled_color() : p.text_disabled_color() ); mLabel = LLUICtrlFactory::create (tbparams); - addChild(mLabel); // Button diff --git a/indra/llui/llconsole.cpp b/indra/llui/llconsole.cpp index fa0abd55d0..59499f987b 100644 --- a/indra/llui/llconsole.cpp +++ b/indra/llui/llconsole.cpp @@ -180,7 +180,7 @@ void LLConsole::draw() // draw remaining lines F32 y_pos = 0.f; - LLUIImagePtr imagep = LLUI::getUIImage("rounded_square.tga"); + LLUIImagePtr imagep = LLUI::getUIImage("Rounded_Square"); // F32 console_opacity = llclamp(gSavedSettings.getF32("ConsoleBackgroundOpacity"), 0.f, 1.f); F32 console_opacity = llclamp(LLUI::sSettingGroups["config"]->getF32("ConsoleBackgroundOpacity"), 0.f, 1.f); @@ -330,7 +330,7 @@ void LLConsole::Paragraph::updateLines(F32 screen_width, const LLFontGL* font, b skip_chars = 0; } - U32 drawable = font->maxDrawableChars(mParagraphText.c_str()+paragraph_offset, screen_width, line_end - paragraph_offset, TRUE); + U32 drawable = font->maxDrawableChars(mParagraphText.c_str()+paragraph_offset, screen_width, line_end - paragraph_offset, LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); if (drawable != 0) { @@ -392,4 +392,10 @@ void LLConsole::addLine(const LLWString& wline, F32 size, const LLColor4 &color) Paragraph paragraph(wline, color, mTimer.getElapsedTimeF32(), mFont, (F32)getRect().getWidth() ); mParagraphs.push_back ( paragraph ); + + // remove old paragraphs which can't possibly be visible any more. ::draw() will do something similar but more conservative - we do this here because ::draw() isn't guaranteed to ever be called! (i.e. the console isn't visible) + while ((S32)mParagraphs.size() > llmax((S32)0, (S32)(mMaxLines))) + { + mParagraphs.pop_front(); + } } diff --git a/indra/llui/lldockablefloater.cpp b/indra/llui/lldockablefloater.cpp index 6a5b475134..74438b184a 100644 --- a/indra/llui/lldockablefloater.cpp +++ b/indra/llui/lldockablefloater.cpp @@ -57,11 +57,20 @@ LLDockableFloater::LLDockableFloater(LLDockControl* dockControl, , mOverlapsScreenChannel(false) { init(this); + mUseTongue = true; } LLDockableFloater::LLDockableFloater(LLDockControl* dockControl, bool uniqueDocking, const LLSD& key, const Params& params) : LLFloater(key, params), mDockControl(dockControl), mUniqueDocking(uniqueDocking) +{ + init(this); + mUseTongue = true; +} + +LLDockableFloater::LLDockableFloater(LLDockControl* dockControl, bool uniqueDocking, + bool useTongue, const LLSD& key, const Params& params) : + LLFloater(key, params), mDockControl(dockControl), mUseTongue(useTongue), mUniqueDocking(uniqueDocking) { init(this); } diff --git a/indra/llui/lldockablefloater.h b/indra/llui/lldockablefloater.h index ae4f99e205..2b1ce99ae2 100644 --- a/indra/llui/lldockablefloater.h +++ b/indra/llui/lldockablefloater.h @@ -62,6 +62,20 @@ public: */ LLDockableFloater(LLDockControl* dockControl, bool uniqueDocking, const LLSD& key, const Params& params = getDefaultParams()); + + /** + * Constructor. + * @param dockControl a pointer to the doc control instance + * @param uniqueDocking - a flag defines is docking should work as tab(at one + * moment only one docked floater can be shown). + * @praram useTongue - a flag defines is dock tongue should be used. + * @params key a floater key. + * @params params a floater parameters + */ + LLDockableFloater(LLDockControl* dockControl, bool uniqueDocking, + bool useTongue, const LLSD& key, + const Params& params = getDefaultParams()); + virtual ~LLDockableFloater(); static LLHandle getInstanceHandle() { return sInstanceHandle; } @@ -104,6 +118,7 @@ public: virtual void setOverlapsScreenChannel(bool overlaps) { mOverlapsScreenChannel = overlaps; } bool getUniqueDocking() { return mUniqueDocking; } + bool getUseTongue() { return mUseTongue; } private: /** * Provides unique of dockable floater. @@ -125,6 +140,8 @@ private: */ bool mUniqueDocking; + bool mUseTongue; + bool mOverlapsScreenChannel; }; diff --git a/indra/llui/lldockcontrol.cpp b/indra/llui/lldockcontrol.cpp index 1c3c8449c5..0d8e54aa48 100644 --- a/indra/llui/lldockcontrol.cpp +++ b/indra/llui/lldockcontrol.cpp @@ -182,12 +182,12 @@ void LLDockControl::moveDockable() LLRect rootRect; mGetAllowedRectCallback(rootRect); - bool unique_docking = false; + bool use_tongue = false; LLDockableFloater* dockable_floater = dynamic_cast (mDockableFloater); if (dockable_floater != NULL) { - unique_docking = dockable_floater->getUniqueDocking(); + use_tongue = dockable_floater->getUseTongue(); } LLRect dockableRect = mDockableFloater->calcScreenRect(); @@ -218,7 +218,7 @@ void LLDockControl::moveDockable() x = dockRect.getCenterX() - dockableRect.getWidth() / 2; y = dockRect.mTop + dockableRect.getHeight(); // unique docking used with dock tongue, so add tongue height o the Y coordinate - if (unique_docking) + if (use_tongue) { y += mDockTongue->getHeight(); } @@ -287,15 +287,15 @@ void LLDockControl::forceRecalculatePosition() void LLDockControl::drawToungue() { - bool unique_docking = false; + bool use_tongue = false; LLDockableFloater* dockable_floater = dynamic_cast (mDockableFloater); if (dockable_floater != NULL) { - unique_docking = dockable_floater->getUniqueDocking(); + use_tongue = dockable_floater->getUseTongue(); } - if (mEnabled && unique_docking) + if (mEnabled && use_tongue) { mDockTongue->draw(mDockTongueX, mDockTongueY); } diff --git a/indra/llui/llflatlistview.cpp b/indra/llui/llflatlistview.cpp index 3754d155cf..3694ecd4f4 100644 --- a/indra/llui/llflatlistview.cpp +++ b/indra/llui/llflatlistview.cpp @@ -711,19 +711,12 @@ void LLFlatListView::selectLastItem () void LLFlatListView::ensureSelectedVisible() { - LLRect visible_rc = getVisibleContentRect(); LLRect selected_rc = getLastSelectedItemRect(); - if ( !visible_rc.contains (selected_rc) ) + if ( selected_rc.isValid() ) { - // But scroll in Items panel coordinates scrollToShowRect(selected_rc); } - - // In case we are in accordion tab notify parent to show selected rectangle - LLRect screen_rc; - localRectToScreen(selected_rc, &screen_rc); - notifyParent(LLSD().with("scrollToShowRect",screen_rc.getValue())); } @@ -906,7 +899,8 @@ void LLFlatListView::notifyParentItemsRectChanged() params["width"] = req_rect.getWidth(); params["height"] = req_rect.getHeight(); - getParent()->notifyParent(params); + if (getParent()) // dummy widgets don't have a parent + getParent()->notifyParent(params); } void LLFlatListView::setNoItemsCommentVisible(bool visible) const diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 845203b420..79d8f90fec 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -233,6 +233,7 @@ LLFloater::LLFloater(const LLSD& key, const LLFloater::Params& p) mAutoFocus(TRUE), // automatically take focus when opened mCanDock(false), mDocked(false), + mTornOff(false), mHasBeenDraggedWhileMinimized(FALSE), mPreviousMinimizedBottom(0), mPreviousMinimizedLeft(0) @@ -878,9 +879,11 @@ void LLFloater::setSnappedTo(const LLView* snap_view) else { //RN: assume it's a floater as it must be a sibling to our parent floater - LLFloater* floaterp = (LLFloater*)snap_view; - - setSnapTarget(floaterp->getHandle()); + const LLFloater* floaterp = dynamic_cast(snap_view); + if (floaterp) + { + setSnapTarget(floaterp->getHandle()); + } } } @@ -1065,10 +1068,6 @@ void LLFloater::setMinimized(BOOL minimize) reshape( mExpandedRect.getWidth(), mExpandedRect.getHeight(), TRUE ); } - // don't show the help button while minimized - it's - // not very useful when minimized and uses up space - mButtonsEnabled[BUTTON_HELP] = !minimize; - applyTitle (); make_ui_sound("UISndWindowClose"); @@ -1361,6 +1360,7 @@ void LLFloater::bringToFront( S32 x, S32 y ) // virtual void LLFloater::setVisibleAndFrontmost(BOOL take_focus) { + gFocusMgr.setTopCtrl(NULL); setVisible(TRUE); setFrontmost(take_focus); } @@ -1458,6 +1458,7 @@ void LLFloater::onClickTearOff(LLFloater* self) } self->setTornOff(false); } + self->updateButtons(); } // static @@ -1741,14 +1742,32 @@ void LLFloater::updateButtons() S32 button_count = 0; for (S32 i = 0; i < BUTTON_COUNT; i++) { - if(!mButtons[i]) continue; - mButtons[i]->setEnabled(mButtonsEnabled[i]); + if (!mButtons[i]) + { + continue; + } + + bool enabled = mButtonsEnabled[i]; + if (i == BUTTON_HELP) + { + // don't show the help button if the floater is minimized + // or if it is a docked tear-off floater + if (isMinimized() || (mButtonsEnabled[BUTTON_TEAR_OFF] && ! mTornOff)) + { + enabled = false; + } + } + if (i == BUTTON_CLOSE && mButtonScale != 1.f) + { + //*HACK: always render close button for hosted floaters so + //that users don't accidentally hit the button when + //closing multiple windows in the chatterbox + enabled = true; + } - if (mButtonsEnabled[i] - //*HACK: always render close button for hosted floaters - // so that users don't accidentally hit the button when closing multiple windows - // in the chatterbox - || (i == BUTTON_CLOSE && mButtonScale != 1.f)) + mButtons[i]->setEnabled(enabled); + + if (enabled) { button_count++; @@ -1775,7 +1794,7 @@ void LLFloater::updateButtons() // the restore button should have a tab stop so that it takes action when you Ctrl-Tab to a minimized floater mButtons[i]->setTabStop(i == BUTTON_RESTORE); } - else if (mButtons[i]) + else { mButtons[i]->setVisible(FALSE); } @@ -2344,7 +2363,7 @@ void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_out LLRect::tCoordType screen_width = getSnapRect().getWidth(); LLRect::tCoordType screen_height = getSnapRect().getHeight(); - + // only automatically resize non-minimized, resizable floaters if( floater->isResizable() && !floater->isMinimized() ) { @@ -2369,16 +2388,13 @@ void LLFloaterView::adjustToFitScreen(LLFloater* floater, BOOL allow_partial_out new_width = llmax(new_width, min_width); new_height = llmax(new_height, min_height); + LLRect new_rect; + new_rect.setLeftTopAndSize(view_rect.mLeft,view_rect.mTop,new_width, new_height); + floater->reshape( new_width, new_height, TRUE ); - if (floater->followsRight()) - { - floater->translate(old_width - new_width, 0); - } + floater->setRect(new_rect); - if (floater->followsTop()) - { - floater->translate(0, old_height - new_height); - } + floater->translateIntoRect( getLocalRect(), false ); } } diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index daf558de24..f70495c0f0 100644 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -256,7 +256,7 @@ public: bool isDocked() const { return mDocked; } virtual void setDocked(bool docked, bool pop_on_undock = true); - virtual void setTornOff(bool torn_off) {} + virtual void setTornOff(bool torn_off) { mTornOff = torn_off; } // Return a closeable floater, if any, given the current focus. static LLFloater* getClosableFloaterFromFocus(); @@ -387,6 +387,7 @@ private: bool mCanDock; bool mDocked; + bool mTornOff; static LLMultiFloater* sHostp; static BOOL sQuitting; diff --git a/indra/llui/lllayoutstack.h b/indra/llui/lllayoutstack.h index cde383b047..c4f10038f8 100644 --- a/indra/llui/lllayoutstack.h +++ b/indra/llui/lllayoutstack.h @@ -91,6 +91,11 @@ public: bool getPanelMinSize(const std::string& panel_name, S32* min_widthp, S32* min_heightp); void updateLayout(BOOL force_resize = FALSE); + + S32 getPanelSpacing() const { return mPanelSpacing; } + BOOL getAnimate () const { return mAnimate; } + void setAnimate (BOOL animate) { mAnimate = animate; } + static void updateClass(); protected: diff --git a/indra/llui/lllineeditor.cpp b/indra/llui/lllineeditor.cpp index 8a21155cc3..73e4d126f3 100644 --- a/indra/llui/lllineeditor.cpp +++ b/indra/llui/lllineeditor.cpp @@ -1583,7 +1583,6 @@ void LLLineEditor::draw() F32 alpha = getDrawContext().mAlpha; S32 text_len = mText.length(); static LLUICachedControl lineeditor_cursor_thickness ("UILineEditorCursorThickness", 0); - static LLUICachedControl lineeditor_v_pad ("UILineEditorVPad", 0); static LLUICachedControl preedit_marker_brightness ("UIPreeditMarkerBrightness", 0); static LLUICachedControl preedit_marker_gap ("UIPreeditMarkerGap", 0); static LLUICachedControl preedit_marker_position ("UIPreeditMarkerPosition", 0); @@ -1609,6 +1608,8 @@ void LLLineEditor::draw() LLRect background( 0, getRect().getHeight(), getRect().getWidth(), 0 ); background.stretch( -mBorderThickness ); + S32 lineeditor_v_pad = llround((background.getHeight() - mGLFont->getLineHeight())/2); + drawBackground(); // draw text diff --git a/indra/llui/llmenubutton.h b/indra/llui/llmenubutton.h index 02eb9d3806..d0e99d9f40 100644 --- a/indra/llui/llmenubutton.h +++ b/indra/llui/llmenubutton.h @@ -55,6 +55,7 @@ public: /*virtual*/ BOOL handleMouseDown(S32 x, S32 y, MASK mask); /*virtual*/ BOOL handleKeyHere(KEY key, MASK mask ); void hideMenu(); + LLMenuGL* getMenu() { return mMenu; } protected: friend class LLUICtrlFactory; diff --git a/indra/llui/llmenugl.cpp b/indra/llui/llmenugl.cpp index 2648cbf08d..c172a2b714 100644 --- a/indra/llui/llmenugl.cpp +++ b/indra/llui/llmenugl.cpp @@ -46,6 +46,7 @@ #include "llmenugl.h" +#include "llgl.h" #include "llmath.h" #include "llrender.h" #include "llfocusmgr.h" @@ -98,7 +99,7 @@ const S32 TEAROFF_SEPARATOR_HEIGHT_PIXELS = 10; const S32 MENU_ITEM_PADDING = 4; const std::string BOOLEAN_TRUE_PREFIX( "\xE2\x9C\x94" ); // U+2714 HEAVY CHECK MARK -const std::string BRANCH_SUFFIX( ">" ); +const std::string BRANCH_SUFFIX( "\xE2\x96\xB6" ); // U+25B6 BLACK RIGHT-POINTING TRIANGLE const std::string ARROW_UP ("^^^^^^^"); const std::string ARROW_DOWN("vvvvvvv"); @@ -477,6 +478,7 @@ void LLMenuItemGL::draw( void ) if (dynamic_cast(this)) debug_count++; gGL.color4fv( mHighlightBackground.get().mV ); + gl_rect_2d( 0, getRect().getHeight(), getRect().getWidth(), 0 ); } @@ -1143,37 +1145,41 @@ BOOL LLMenuItemBranchGL::handleKeyHere( KEY key, MASK mask ) if (!branch) return LLMenuItemGL::handleKeyHere(key, mask); - if (getMenu()->getVisible() && branch->getVisible() && key == KEY_LEFT) + // an item is highlighted, my menu is open, and I have an active sub menu or we are in + // keyboard navigation mode + if (getHighlight() + && getMenu()->isOpen() + && (isActive() || LLMenuGL::getKeyboardMode())) { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(TRUE); - - BOOL handled = branch->clearHoverItem(); - if (branch->getTornOff()) - { - ((LLFloater*)branch->getParent())->setFocus(FALSE); - } - if (handled && getMenu()->getTornOff()) + if (branch->getVisible() && key == KEY_LEFT) { - ((LLFloater*)getMenu()->getParent())->setFocus(TRUE); - } - return handled; - } + // switch to keyboard navigation mode + LLMenuGL::setKeyboardMode(TRUE); - if (getHighlight() && - getMenu()->isOpen() && - key == KEY_RIGHT && !branch->getHighlightedItem()) - { - // switch to keyboard navigation mode - LLMenuGL::setKeyboardMode(TRUE); + BOOL handled = branch->clearHoverItem(); + if (branch->getTornOff()) + { + ((LLFloater*)branch->getParent())->setFocus(FALSE); + } + if (handled && getMenu()->getTornOff()) + { + ((LLFloater*)getMenu()->getParent())->setFocus(TRUE); + } + return handled; + } - LLMenuItemGL* itemp = branch->highlightNextItem(NULL); - if (itemp) + if (key == KEY_RIGHT && !branch->getHighlightedItem()) { - return TRUE; + // switch to keyboard navigation mode + LLMenuGL::setKeyboardMode(TRUE); + + LLMenuItemGL* itemp = branch->highlightNextItem(NULL); + if (itemp) + { + return TRUE; + } } } - return LLMenuItemGL::handleKeyHere(key, mask); } @@ -1431,7 +1437,7 @@ BOOL LLMenuItemBranchDownGL::handleKeyHere(KEY key, MASK mask) { BOOL menu_open = getBranch()->getVisible(); // don't do keyboard navigation of top-level menus unless in keyboard mode, or menu expanded - if (getHighlight() && getMenu()->getVisible() && (isActive() || LLMenuGL::getKeyboardMode())) + if (getHighlight() && getMenu()->isOpen() && (isActive() || LLMenuGL::getKeyboardMode())) { if (key == KEY_LEFT) { @@ -2836,6 +2842,7 @@ BOOL LLMenuGL::handleScrollWheel( S32 x, S32 y, S32 clicks ) return TRUE; } + void LLMenuGL::draw( void ) { if (mNeedsArrange) @@ -2959,7 +2966,7 @@ void LLMenuGL::showPopup(LLView* spawning_view, LLMenuGL* menu, S32 x, S32 y) LLUI::getMousePositionLocal(menu->getParent(), &mouse_x, &mouse_y); LLMenuHolderGL::sContextMenuSpawnPos.set(mouse_x,mouse_y); - const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getMenuRect(); + const LLRect menu_region_rect = LLMenuGL::sMenuContainer->getRect(); const S32 HPAD = 2; LLRect rect = menu->getRect(); diff --git a/indra/llui/llmultislider.cpp b/indra/llui/llmultislider.cpp index aea7c5f87c..27a727fdf5 100644 --- a/indra/llui/llmultislider.cpp +++ b/indra/llui/llmultislider.cpp @@ -490,7 +490,7 @@ void LLMultiSlider::draw() F32 opacity = getEnabled() ? 1.f : 0.3f; // Track - LLUIImagePtr thumb_imagep = LLUI::getUIImage("rounded_square.tga"); + LLUIImagePtr thumb_imagep = LLUI::getUIImage("Rounded_Square"); static LLUICachedControl multi_track_height ("UIMultiTrackHeight", 0); S32 height_offset = (getRect().getHeight() - multi_track_height) / 2; diff --git a/indra/llui/llpanel.cpp b/indra/llui/llpanel.cpp index db32882438..7f23fe2671 100644 --- a/indra/llui/llpanel.cpp +++ b/indra/llui/llpanel.cpp @@ -43,6 +43,7 @@ #include "llerror.h" #include "lltimer.h" +#include "llaccordionctrltab.h" #include "llbutton.h" #include "llmenugl.h" //#include "llstatusbar.h" @@ -851,14 +852,26 @@ static LLPanel *childGetVisibleTabWithHelp(LLView *parent) // look through immediate children first for an active tab with help for (child = parent->getFirstChild(); child; child = parent->findNextSibling(child)) { + LLPanel *curTabPanel = NULL; + + // do we have a tab container? LLTabContainer *tab = dynamic_cast(child); if (tab && tab->getVisible()) { - LLPanel *curTabPanel = tab->getCurrentPanel(); - if (curTabPanel && !curTabPanel->getHelpTopic().empty()) - { - return curTabPanel; - } + curTabPanel = tab->getCurrentPanel(); + } + + // do we have an accordion tab? + LLAccordionCtrlTab* accordion = dynamic_cast(child); + if (accordion && accordion->getDisplayChildren()) + { + curTabPanel = dynamic_cast(accordion->getAccordionView()); + } + + // if we found a valid tab, does it have a help topic? + if (curTabPanel && !curTabPanel->getHelpTopic().empty()) + { + return curTabPanel; } } @@ -885,6 +898,44 @@ LLPanel *LLPanel::childGetVisibleTabWithHelp() return ::childGetVisibleTabWithHelp(this); } +static LLPanel *childGetVisiblePanelWithHelp(LLView *parent) +{ + LLView *child; + + // look through immediate children first for an active panel with help + for (child = parent->getFirstChild(); child; child = parent->findNextSibling(child)) + { + // do we have a panel with a help topic? + LLPanel *panel = dynamic_cast(child); + if (panel && panel->getVisible() && !panel->getHelpTopic().empty()) + { + return panel; + } + } + + // then try a bit harder and recurse through all children + for (child = parent->getFirstChild(); child; child = parent->findNextSibling(child)) + { + if (child->getVisible()) + { + LLPanel* panel = ::childGetVisiblePanelWithHelp(child); + if (panel) + { + return panel; + } + } + } + + // couldn't find any active panels with a help topic string + return NULL; +} + +LLPanel *LLPanel::childGetVisiblePanelWithHelp() +{ + // find a visible tab with a help topic (to determine help context) + return ::childGetVisiblePanelWithHelp(this); +} + void LLPanel::childSetPrevalidate(const std::string& id, BOOL (*func)(const LLWString &) ) { LLLineEditor* child = findChild(id); diff --git a/indra/llui/llpanel.h b/indra/llui/llpanel.h index d0986a06d3..6de83fe3a7 100644 --- a/indra/llui/llpanel.h +++ b/indra/llui/llpanel.h @@ -214,7 +214,10 @@ public: // LLTabContainer void childShowTab(const std::string& id, const std::string& tabname, bool visible = true); LLPanel *childGetVisibleTab(const std::string& id) const; + + // Find a child with a nonempty Help topic LLPanel *childGetVisibleTabWithHelp(); + LLPanel *childGetVisiblePanelWithHelp(); // LLTextBox/LLTextEditor/LLLineEditor void childSetText(const std::string& id, const LLStringExplicit& text) { childSetValue(id, LLSD(text)); } diff --git a/indra/llui/llresizehandle.cpp b/indra/llui/llresizehandle.cpp index 6239a8f721..3df09d124a 100644 --- a/indra/llui/llresizehandle.cpp +++ b/indra/llui/llresizehandle.cpp @@ -124,7 +124,7 @@ BOOL LLResizeHandle::handleHover(S32 x, S32 y, MASK mask) { // Make sure the mouse in still over the application. We don't want to make the parent // so big that we can't see the resize handle any more. - + S32 screen_x; S32 screen_y; localPointToScreen(x, y, &screen_x, &screen_y); @@ -146,68 +146,61 @@ BOOL LLResizeHandle::handleHover(S32 x, S32 y, MASK mask) LLRect scaled_rect = orig_rect; S32 delta_x = screen_x - mDragLastScreenX; S32 delta_y = screen_y - mDragLastScreenY; + + if(delta_x == 0 && delta_y == 0) + return FALSE; + LLCoordGL mouse_dir; // use hysteresis on mouse motion to preserve user intent when mouse stops moving mouse_dir.mX = (screen_x == mLastMouseScreenX) ? mLastMouseDir.mX : screen_x - mLastMouseScreenX; mouse_dir.mY = (screen_y == mLastMouseScreenY) ? mLastMouseDir.mY : screen_y - mLastMouseScreenY; + mLastMouseScreenX = screen_x; mLastMouseScreenY = screen_y; mLastMouseDir = mouse_dir; - S32 x_multiple = 1; - S32 y_multiple = 1; - switch( mCorner ) - { - case LEFT_TOP: - x_multiple = -1; - y_multiple = 1; - break; - case LEFT_BOTTOM: - x_multiple = -1; - y_multiple = -1; - break; - case RIGHT_TOP: - x_multiple = 1; - y_multiple = 1; - break; - case RIGHT_BOTTOM: - x_multiple = 1; - y_multiple = -1; - break; - } + S32 new_width = orig_rect.getWidth(); + S32 new_height = orig_rect.getHeight(); - S32 new_width = orig_rect.getWidth() + x_multiple * delta_x; - if( new_width < mMinWidth ) - { - new_width = mMinWidth; - delta_x = x_multiple * (mMinWidth - orig_rect.getWidth()); - } - - S32 new_height = orig_rect.getHeight() + y_multiple * delta_y; - if( new_height < mMinHeight ) - { - new_height = mMinHeight; - delta_y = y_multiple * (mMinHeight - orig_rect.getHeight()); - } + S32 new_pos_x = orig_rect.mLeft; + S32 new_pos_y = orig_rect.mTop; switch( mCorner ) { - case LEFT_TOP: - scaled_rect.translate(delta_x, 0); + case LEFT_TOP: + new_width-=delta_x; + new_height+=delta_y; + new_pos_x+=delta_x; + new_pos_y+=delta_y; break; case LEFT_BOTTOM: - scaled_rect.translate(delta_x, delta_y); + new_width-=delta_x; + new_height-=delta_y; + new_pos_x+=delta_x; break; case RIGHT_TOP: + new_width+=delta_x; + new_height+=delta_y; + new_pos_y+=delta_y; break; case RIGHT_BOTTOM: - scaled_rect.translate(0, delta_y); + new_width+=delta_x; + new_height-=delta_y; break; } + new_width = llmax(new_width,mMinWidth); + new_height = llmax(new_height,mMinHeight); + + LLRect::tCoordType screen_width = resizing_view->getParent()->getSnapRect().getWidth(); + LLRect::tCoordType screen_height = resizing_view->getParent()->getSnapRect().getHeight(); + + new_width = llmin(new_width, screen_width); + new_height = llmin(new_height, screen_height); + // temporarily set new parent rect - scaled_rect.mRight = scaled_rect.mLeft + new_width; - scaled_rect.mTop = scaled_rect.mBottom + new_height; + scaled_rect.setLeftTopAndSize(new_pos_x,new_pos_y,new_width,new_height); + resizing_view->setRect(scaled_rect); LLView* snap_view = NULL; @@ -258,7 +251,11 @@ BOOL LLResizeHandle::handleHover(S32 x, S32 y, MASK mask) resizing_view->setRect(orig_rect); // translate and scale to new shape - resizing_view->setShape(scaled_rect, true); + resizing_view->reshape(scaled_rect.getWidth(),scaled_rect.getHeight()); + resizing_view->setRect(scaled_rect); + //set shape to handle dependent floaters... + resizing_view->handleReshape(scaled_rect, false); + // update last valid mouse cursor position based on resized view's actual size LLRect new_rect = resizing_view->getRect(); diff --git a/indra/llui/llscrollcontainer.cpp b/indra/llui/llscrollcontainer.cpp index a5e47e8547..94465a67ce 100644 --- a/indra/llui/llscrollcontainer.cpp +++ b/indra/llui/llscrollcontainer.cpp @@ -668,6 +668,11 @@ void LLScrollContainer::scrollToShowRect(const LLRect& rect, const LLRect& const // propagate scroll to document updateScroll(); + + // In case we are in accordion tab notify parent to show selected rectangle + LLRect screen_rc; + localRectToScreen(rect_to_constrain, &screen_rc); + notifyParent(LLSD().with("scrollToShowRect",screen_rc.getValue())); } void LLScrollContainer::pageUp(S32 overlap) diff --git a/indra/llui/llscrolllistcell.cpp b/indra/llui/llscrolllistcell.cpp index 7238d903a3..3cc92baa8d 100644 --- a/indra/llui/llscrolllistcell.cpp +++ b/indra/llui/llscrolllistcell.cpp @@ -188,7 +188,7 @@ LLScrollListText::LLScrollListText(const LLScrollListCell::Params& p) // initialize rounded rect image if (!mRoundedRectImage) { - mRoundedRectImage = LLUI::getUIImage("rounded_square.tga"); + mRoundedRectImage = LLUI::getUIImage("Rounded_Square"); } } diff --git a/indra/llui/llscrolllistctrl.cpp b/indra/llui/llscrolllistctrl.cpp index a53a30b501..4e84013db0 100644 --- a/indra/llui/llscrolllistctrl.cpp +++ b/indra/llui/llscrolllistctrl.cpp @@ -142,6 +142,7 @@ LLScrollListCtrl::Params::Params() contents(""), scroll_bar_bg_visible("scroll_bar_bg_visible"), scroll_bar_bg_color("scroll_bar_bg_color") + , border("border") { name = "scroll_list"; mouse_opaque = true; @@ -231,10 +232,8 @@ LLScrollListCtrl::LLScrollListCtrl(const LLScrollListCtrl::Params& p) if (p.has_border) { LLRect border_rect = getLocalRect(); - LLViewBorder::Params params; - params.name("dig border"); + LLViewBorder::Params params = p.border; params.rect(border_rect); - params.bevel_style(LLViewBorder::BEVEL_IN); mBorder = LLUICtrlFactory::create (params); addChild(mBorder); } diff --git a/indra/llui/llscrolllistctrl.h b/indra/llui/llscrolllistctrl.h index 78bc60db6e..907dc90bea 100644 --- a/indra/llui/llscrolllistctrl.h +++ b/indra/llui/llscrolllistctrl.h @@ -51,6 +51,7 @@ #include "lldate.h" #include "llscrolllistitem.h" #include "llscrolllistcolumn.h" +#include "llviewborder.h" class LLScrollListCell; class LLTextBox; @@ -109,6 +110,8 @@ public: scroll_bar_bg_color; Optional contents; + + Optional border; Params(); }; diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 7447a984ac..17aecaf32f 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -346,7 +346,8 @@ void LLTextBase::drawSelectionBackground() S32 segment_line_start = segmentp->getStart() + segment_offset; S32 segment_line_end = llmin(segmentp->getEnd(), line_iter->mDocIndexEnd); - S32 segment_width, segment_height; + S32 segment_width = 0; + S32 segment_height = 0; // if selection after beginning of segment if(selection_left >= segment_line_start) @@ -433,7 +434,8 @@ void LLTextBase::drawCursor() if (LL_KIM_OVERWRITE == gKeyboard->getInsertMode() && !hasSelection()) { - S32 segment_width, segment_height; + S32 segment_width = 0; + S32 segment_height = 0; segmentp->getDimensions(mCursorPos - segmentp->getStart(), 1, segment_width, segment_height); S32 width = llmax(CURSOR_THICKNESS, segment_width); cursor_rect.mRight = cursor_rect.mLeft + width; @@ -2443,10 +2445,12 @@ void LLNormalTextSegment::setToolTip(const std::string& tooltip) bool LLNormalTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& width, S32& height) const { - height = mFontHeight; + height = 0; + width = 0; bool force_newline = false; if (num_chars > 0) { + height = mFontHeight; LLWString text = mEditor.getWText(); // if last character is a newline, then return true, forcing line break llwchar last_char = text[mStart + first_char + num_chars - 1]; @@ -2461,10 +2465,6 @@ bool LLNormalTextSegment::getDimensions(S32 first_char, S32 num_chars, S32& widt width = mStyle->getFont()->getWidth(text.c_str(), mStart + first_char, num_chars); } } - else - { - width = 0; - } LLUIImagePtr image = mStyle->getImage(); if( image.notNull()) @@ -2509,10 +2509,15 @@ S32 LLNormalTextSegment::getNumChars(S32 num_pixels, S32 segment_offset, S32 lin // set max characters to length of segment, or to first newline max_chars = llmin(max_chars, last_char - (mStart + segment_offset)); + // if no character yet displayed on this line, don't require word wrapping since + // we can just move to the next line, otherwise insist on it so we make forward progress + LLFontGL::EWordWrapStyle word_wrap_style = (line_offset == 0) + ? LLFontGL::WORD_BOUNDARY_IF_POSSIBLE + : LLFontGL::ONLY_WORD_BOUNDARIES; S32 num_chars = mStyle->getFont()->maxDrawableChars(text.c_str() + segment_offset + mStart, (F32)num_pixels, max_chars, - TRUE); + word_wrap_style); if (num_chars == 0 && line_offset == 0 diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index e8fc9475a5..f2c3879a6c 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -308,7 +308,8 @@ LLTextEditor::~LLTextEditor() // Scrollbar is deleted by LLView std::for_each(mUndoStack.begin(), mUndoStack.end(), DeletePointer()); - delete mContextMenu; + // context menu is owned by menu holder, not us + //delete mContextMenu; } //////////////////////////////////////////////////////////// diff --git a/indra/llui/lluictrl.cpp b/indra/llui/lluictrl.cpp index 6044908ca7..3ade46d367 100644 --- a/indra/llui/lluictrl.cpp +++ b/indra/llui/lluictrl.cpp @@ -868,6 +868,14 @@ bool LLUICtrl::findHelpTopic(std::string& help_topic_out) if (panel) { + // does the panel have a sub-panel with a help topic? + LLPanel *subpanel = panel->childGetVisiblePanelWithHelp(); + if (subpanel) + { + help_topic_out = subpanel->getHelpTopic(); + return true; // success (subpanel) + } + // does the panel have an active tab with a help topic? LLPanel *tab = panel->childGetVisibleTabWithHelp(); if (tab) diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 983f0a2d49..4927e57a52 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -101,7 +101,7 @@ std::string LLUrlEntryBase::getLabelFromWikiLink(const std::string &url) { start++; } - return url.substr(start, url.size()-start-1); + return unescapeUrl(url.substr(start, url.size()-start-1)); } std::string LLUrlEntryBase::getUrlFromWikiLink(const std::string &string) @@ -201,8 +201,12 @@ std::string LLUrlEntryHTTPLabel::getUrl(const std::string &string) // LLUrlEntryHTTPNoProtocol::LLUrlEntryHTTPNoProtocol() { - mPattern = boost::regex("(\\bwww\\.\\S+\\.\\S+|\\b[^ \t\n\r\f\v:/]+.com\\S*|\\b[^ \t\n\r\f\v:/]+.net\\S*|\\b[^ \t\n\r\f\v:/]+.edu\\S*|\\b[^ \t\n\r\f\v:/]+.org\\S*)", - boost::regex::perl|boost::regex::icase); + mPattern = boost::regex("(" + "\\bwww\\.\\S+\\.\\S+" // i.e. www.FOO.BAR + "|" // or + "(? template<> + void object::test<11>() + { + // + // test LLUrlEntryHTTPNoProtocol - general URLs without a protocol + // + LLUrlEntryHTTPNoProtocol url; + boost::regex r = url.getPattern(); + + testRegex("naked .com URL", r, + "see google.com", + "google.com"); + + testRegex("naked .org URL", r, + "see en.wikipedia.org for details", + "en.wikipedia.org"); + + testRegex("naked .net URL", r, + "example.net", + "example.net"); + + testRegex("naked .edu URL (2 instances)", r, + "MIT web site is at web.mit.edu and also www.mit.edu", + "web.mit.edu"); + + testRegex("don't match e-mail addresses", r, + "test@lindenlab.com", + ""); + + testRegex(".com URL with path", r, + "see secondlife.com/status for grid status", + "secondlife.com/status"); + + testRegex(".com URL with port", r, + "secondlife.com:80", + "secondlife.com:80"); + + testRegex(".com URL with port and path", r, + "see secondlife.com:80/status", + "secondlife.com:80/status"); + + testRegex("www.*.com URL with port and path", r, + "see www.secondlife.com:80/status", + "www.secondlife.com:80/status"); + + testRegex("invalid .com URL [1]", r, + "..com", + ""); + + testRegex("invalid .com URL [2]", r, + "you.come", + ""); + + testRegex("invalid .com URL [3]", r, + "recommended", + ""); + + testRegex("invalid .edu URL", r, + "hi there scheduled maitenance has begun", + ""); + + testRegex("invalid .net URL", r, + "foo.netty", + ""); + } } diff --git a/indra/llvfs/lldir.cpp b/indra/llvfs/lldir.cpp index b2b17fdd56..da4abde451 100644 --- a/indra/llvfs/lldir.cpp +++ b/indra/llvfs/lldir.cpp @@ -200,6 +200,11 @@ const std::string &LLDir::getOSUserAppDir() const const std::string &LLDir::getLindenUserDir() const { + if (mLindenUserDir.empty()) + { + lldebugs << "getLindenUserDir() called early, we don't have the user name yet - returning empty string to caller" << llendl; + } + return mLindenUserDir; } @@ -337,7 +342,7 @@ std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subd break; case LL_PATH_CACHE: - prefix = getCacheDir(); + prefix = getCacheDir(); break; case LL_PATH_USER_SETTINGS: @@ -348,6 +353,11 @@ std::string LLDir::getExpandedFilename(ELLPath location, const std::string& subd case LL_PATH_PER_SL_ACCOUNT: prefix = getLindenUserDir(); + if (prefix.empty()) + { + // if we're asking for the per-SL-account directory but we haven't logged in yet (or otherwise don't know the account name from which to build this string), then intentionally return a blank string to the caller and skip the below warning about a blank prefix. + return std::string(); + } break; case LL_PATH_CHAT_LOGS: @@ -557,7 +567,7 @@ std::string LLDir::getForbiddenFileChars() void LLDir::setLindenUserDir(const std::string &first, const std::string &last) { - // if both first and last aren't set, assume we're grabbing the cached dir + // if both first and last aren't set, that's bad. if (!first.empty() && !last.empty()) { // some platforms have case-sensitive filesystems, so be @@ -571,6 +581,7 @@ void LLDir::setLindenUserDir(const std::string &first, const std::string &last) mLindenUserDir += firstlower; mLindenUserDir += "_"; mLindenUserDir += lastlower; + llinfos << "Got name for LLDir::setLindenUserDir(first='" << first << "', last='" << last << "')" << llendl; } else { diff --git a/indra/llvfs/lldir.h b/indra/llvfs/lldir.h index 206e3223e3..9067d75bac 100644 --- a/indra/llvfs/lldir.h +++ b/indra/llvfs/lldir.h @@ -44,7 +44,7 @@ typedef enum ELLPath LL_PATH_NONE = 0, LL_PATH_USER_SETTINGS = 1, LL_PATH_APP_SETTINGS = 2, - LL_PATH_PER_SL_ACCOUNT = 3, + LL_PATH_PER_SL_ACCOUNT = 3, // returns/expands to blank string if we don't know the account name yet LL_PATH_CACHE = 4, LL_PATH_CHARACTER = 5, LL_PATH_HELP = 6, diff --git a/indra/llvfs/lldir_linux.cpp b/indra/llvfs/lldir_linux.cpp index ee902d1de7..a9736560ec 100644 --- a/indra/llvfs/lldir_linux.cpp +++ b/indra/llvfs/lldir_linux.cpp @@ -112,9 +112,10 @@ LLDir_Linux::LLDir_Linux() // ...normal installation running mSkinBaseDir = mAppRODataDir + mDirDelimiter + "skins"; } + mOSUserDir = getCurrentUserHome(tmp_str); mOSUserAppDir = ""; - mLindenUserDir = tmp_str; + mLindenUserDir = ""; char path [32]; /* Flawfinder: ignore */ diff --git a/indra/llvfs/lldir_solaris.cpp b/indra/llvfs/lldir_solaris.cpp index a8fad8e5bd..8ac5a41e93 100644 --- a/indra/llvfs/lldir_solaris.cpp +++ b/indra/llvfs/lldir_solaris.cpp @@ -100,7 +100,7 @@ LLDir_Solaris::LLDir_Solaris() mAppRODataDir = strdup(tmp_str); mOSUserDir = getCurrentUserHome(tmp_str); mOSUserAppDir = ""; - mLindenUserDir = tmp_str; + mLindenUserDir = ""; char path [LL_MAX_PATH]; /* Flawfinder: ignore */ diff --git a/indra/llwindow/llkeyboardwin32.cpp b/indra/llwindow/llkeyboardwin32.cpp index 35a3e7621a..ab5ecb4e63 100644 --- a/indra/llwindow/llkeyboardwin32.cpp +++ b/indra/llwindow/llkeyboardwin32.cpp @@ -80,7 +80,7 @@ LLKeyboardWin32::LLKeyboardWin32() mTranslateKeyMap[VK_OEM_COMMA] = ','; mTranslateKeyMap[VK_OEM_MINUS] = '-'; mTranslateKeyMap[VK_OEM_PERIOD] = '.'; - mTranslateKeyMap[VK_OEM_2] = KEY_PAD_DIVIDE; + mTranslateKeyMap[VK_OEM_2] = '/';//This used to be KEY_PAD_DIVIDE, but that breaks typing into text fields in media prims mTranslateKeyMap[VK_OEM_3] = '`'; mTranslateKeyMap[VK_OEM_4] = '['; mTranslateKeyMap[VK_OEM_5] = '\\'; diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index 3b9c840e72..b591111b75 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -2184,7 +2184,7 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ { window_imp->mCallbacks->handlePingWatchdog(window_imp, "Main:WM_MBUTTONUP"); LLFastTimer t2(FTM_MOUSEHANDLER); - // Because we move the cursor position in tllviewerhe app, we need to query + // Because we move the cursor position in the llviewer app, we need to query // to find out where the cursor at the time the event is handled. // If we don't do this, many clicks could get buffered up, and if the // first click changes the cursor position, all subsequent clicks @@ -2214,7 +2214,27 @@ LRESULT CALLBACK LLWindowWin32::mainWindowProc(HWND h_wnd, UINT u_msg, WPARAM w_ window_imp->mCallbacks->handlePingWatchdog(window_imp, "Main:WM_MOUSEWHEEL"); static short z_delta = 0; - z_delta += HIWORD(w_param); + RECT client_rect; + + // eat scroll events that occur outside our window, since we use mouse position to direct scroll + // instead of keyboard focus + // NOTE: mouse_coord is in *window* coordinates for scroll events + POINT mouse_coord = {(S32)(S16)LOWORD(l_param), (S32)(S16)HIWORD(l_param)}; + + if (ScreenToClient(window_imp->mWindowHandle, &mouse_coord) + && GetClientRect(window_imp->mWindowHandle, &client_rect)) + { + // we have a valid mouse point and client rect + if (mouse_coord.x < client_rect.left || client_rect.right < mouse_coord.x + || mouse_coord.y < client_rect.top || client_rect.bottom < mouse_coord.y) + { + // mouse is outside of client rect, so don't do anything + return 0; + } + } + + S16 incoming_z_delta = HIWORD(w_param); + z_delta += incoming_z_delta; // cout << "z_delta " << z_delta << endl; // current mouse wheels report changes in increments of zDelta (+120, -120) diff --git a/indra/media_plugins/webkit/media_plugin_webkit.cpp b/indra/media_plugins/webkit/media_plugin_webkit.cpp index 4b6da552cf..42d680ade6 100644 --- a/indra/media_plugins/webkit/media_plugin_webkit.cpp +++ b/indra/media_plugins/webkit/media_plugin_webkit.cpp @@ -84,6 +84,7 @@ private: INIT_STATE_NAVIGATING, // Browser instance has been set up and initial navigate to about:blank has been issued INIT_STATE_NAVIGATE_COMPLETE, // initial navigate to about:blank has completed INIT_STATE_WAIT_REDRAW, // First real navigate begin has been received, waiting for page changed event to start handling redraws + INIT_STATE_WAIT_COMPLETE, // Waiting for first real navigate complete event INIT_STATE_RUNNING // All initialization gymnastics are complete. }; int mBrowserWindowId; @@ -97,6 +98,9 @@ private: int mLastMouseX; int mLastMouseY; bool mFirstFocus; + F32 mBackgroundR; + F32 mBackgroundG; + F32 mBackgroundB; void setInitState(int state) { @@ -122,7 +126,7 @@ private: } } - if ( (mInitState == INIT_STATE_RUNNING) && mNeedsUpdate ) + if ( (mInitState > INIT_STATE_WAIT_REDRAW) && mNeedsUpdate ) { const unsigned char* browser_pixels = LLQtWebKit::getInstance()->grabBrowserWindow( mBrowserWindowId ); @@ -171,6 +175,15 @@ private: } std::string application_dir = std::string( cwd ); +#if LL_DARWIN + // When running under the Xcode debugger, there's a setting called "Break on Debugger()/DebugStr()" which defaults to being turned on. + // This causes the environment variable USERBREAK to be set to 1, which causes these legacy calls to break into the debugger. + // This wouldn't cause any problems except for the fact that the current release version of the Flash plugin has a call to Debugger() in it + // which gets hit when the plugin is probed by webkit. + // Unsetting the environment variable here works around this issue. + unsetenv("USERBREAK"); +#endif + #if LL_WINDOWS //*NOTE:Mani - On windows, at least, the component path is the // location of this dll's image file. @@ -236,8 +249,9 @@ private: // don't flip bitmap LLQtWebKit::getInstance()->flipWindow( mBrowserWindowId, true ); - // set background color to be black - mostly for initial login page - LLQtWebKit::getInstance()->setBackgroundColor( mBrowserWindowId, 0x00, 0x00, 0x00 ); + // set background color + // convert background color channels from [0.0, 1.0] to [0, 255]; + LLQtWebKit::getInstance()->setBackgroundColor( mBrowserWindowId, int(mBackgroundR * 255.0f), int(mBackgroundG * 255.0f), int(mBackgroundB * 255.0f) ); // Set state _before_ starting the navigate, since onNavigateBegin might get called before this call returns. setInitState(INIT_STATE_NAVIGATING); @@ -245,7 +259,21 @@ private: // Don't do this here -- it causes the dreaded "white flash" when loading a browser instance. // FIXME: Re-added this because navigating to a "page" initializes things correctly - especially // for the HTTP AUTH dialog issues (DEV-41731). Will fix at a later date. - LLQtWebKit::getInstance()->navigateTo( mBrowserWindowId, "about:blank" ); + // Build a data URL like this: "data:text/html,%3Chtml%3E%3Cbody bgcolor=%22#RRGGBB%22%3E%3C/body%3E%3C/html%3E" + // where RRGGBB is the background color in HTML style + std::stringstream url; + + url << "data:text/html,%3Chtml%3E%3Cbody%20bgcolor=%22#"; + // convert background color channels from [0.0, 1.0] to [0, 255]; + url << std::setfill('0') << std::setw(2) << std::hex << int(mBackgroundR * 255.0f); + url << std::setfill('0') << std::setw(2) << std::hex << int(mBackgroundG * 255.0f); + url << std::setfill('0') << std::setw(2) << std::hex << int(mBackgroundB * 255.0f); + url << "%22%3E%3C/body%3E%3C/html%3E"; + + lldebugs << "data url is: " << url.str() << llendl; + + LLQtWebKit::getInstance()->navigateTo( mBrowserWindowId, url.str() ); +// LLQtWebKit::getInstance()->navigateTo( mBrowserWindowId, "about:blank" ); return true; }; @@ -295,7 +323,7 @@ private: { if(mInitState == INIT_STATE_WAIT_REDRAW) { - setInitState(INIT_STATE_RUNNING); + setInitState(INIT_STATE_WAIT_COMPLETE); } // flag that an update is required @@ -317,7 +345,9 @@ private: if(mInitState == INIT_STATE_NAVIGATE_COMPLETE) { - setInitState(INIT_STATE_WAIT_REDRAW); + // Skip the WAIT_REDRAW state now -- with the right background color set, it should no longer be necessary. +// setInitState(INIT_STATE_WAIT_REDRAW); + setInitState(INIT_STATE_WAIT_COMPLETE); } } @@ -328,6 +358,14 @@ private: { if(mInitState >= INIT_STATE_NAVIGATE_COMPLETE) { + if(mInitState < INIT_STATE_RUNNING) + { + setInitState(INIT_STATE_RUNNING); + + // Clear the history, so the "back" button doesn't take you back to "about:blank". + LLQtWebKit::getInstance()->clearHistory(mBrowserWindowId); + } + LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "navigate_complete"); message.setValue("uri", event.getEventUri()); message.setValueS32("result_code", event.getIntValue()); @@ -400,6 +438,7 @@ private: LLPluginMessage message(LLPLUGIN_MESSAGE_CLASS_MEDIA_BROWSER, "click_href"); message.setValue("uri", event.getStringValue()); message.setValue("target", event.getStringValue2()); + message.setValueU32("target_type", event.getLinkType()); sendMessage(message); } @@ -695,7 +734,11 @@ void MediaPluginWebKit::receiveMessage(const char *message_string) S32 height = message_in.getValueS32("height"); S32 texture_width = message_in.getValueS32("texture_width"); S32 texture_height = message_in.getValueS32("texture_height"); - + mBackgroundR = message_in.getValueReal("background_r"); + mBackgroundG = message_in.getValueReal("background_g"); + mBackgroundB = message_in.getValueReal("background_b"); +// mBackgroundA = message_in.setValueReal("background_a"); // Ignore any alpha + if(!name.empty()) { // Find the shared memory region with this name diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 8918fc3018..5373556c20 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -63,8 +63,6 @@ include_directories( ) set(viewer_SOURCE_FILES - llaccordionctrl.cpp - llaccordionctrltab.cpp llagent.cpp llagentaccess.cpp llagentdata.cpp @@ -158,7 +156,6 @@ set(viewer_SOURCE_FILES llfloaterbuycurrency.cpp llfloaterbuyland.cpp llfloatercamera.cpp - llfloaterchat.cpp llfloaterchatterbox.cpp llfloatercolorpicker.cpp llfloatercustomize.cpp @@ -214,7 +211,6 @@ set(viewer_SOURCE_FILES llfloaterurldisplay.cpp llfloaterurlentry.cpp llfloatervoicedevicesettings.cpp - llfloatervolumepulldown.cpp llfloaterwater.cpp llfloaterwhitelistentry.cpp llfloaterwindlight.cpp @@ -252,6 +248,7 @@ set(viewer_SOURCE_FILES llinspectgroup.cpp llinspectobject.cpp llinspectremoteobject.cpp + llinspecttoast.cpp llinventorybridge.cpp llinventoryclipboard.cpp llinventoryfilter.cpp @@ -349,6 +346,7 @@ set(viewer_SOURCE_FILES llpanelshower.cpp llpanelteleporthistory.cpp llpanelvolume.cpp + llpanelvolumepulldown.cpp llparcelselection.cpp llparticipantlist.cpp llpatchvertexarray.cpp @@ -387,6 +385,7 @@ set(viewer_SOURCE_FILES llspatialpartition.cpp llspeakbutton.cpp llspeakers.cpp + llspeakingindicatormanager.cpp llsplitbutton.cpp llsprite.cpp llstartup.cpp @@ -569,8 +568,6 @@ endif (LINUX) set(viewer_HEADER_FILES CMakeLists.txt ViewerInstall.cmake - llaccordionctrl.h - llaccordionctrltab.h llagent.h llagentaccess.h llagentdata.h @@ -667,7 +664,6 @@ set(viewer_HEADER_FILES llfloaterbuycurrency.h llfloaterbuyland.h llfloatercamera.h - llfloaterchat.h llfloaterchatterbox.h llfloatercolorpicker.h llfloatercustomize.h @@ -759,6 +755,7 @@ set(viewer_HEADER_FILES llinspectgroup.h llinspectobject.h llinspectremoteobject.h + llinspecttoast.h llinventorybridge.h llinventoryclipboard.h llinventoryfilter.h @@ -852,6 +849,7 @@ set(viewer_HEADER_FILES llpanelshower.h llpanelteleporthistory.h llpanelvolume.h + llpanelvolumepulldown.h llparcelselection.h llparticipantlist.h llpatchvertexarray.h @@ -892,6 +890,7 @@ set(viewer_HEADER_FILES llspatialpartition.h llspeakbutton.h llspeakers.h + llspeakingindicatormanager.h llsplitbutton.h llsprite.h llstartup.h @@ -1406,11 +1405,11 @@ if (WINDOWS) # Note the need to specify multiple names explicitly. set(GOOGLE_PERF_TOOLS_SOURCE ${SHARED_LIB_STAGING_DIR}/Release/libtcmalloc_minimal.dll - ${SHARED_LIB_STAGING_DIR}/RelWithDebInfo/libtcmalloc_minimal.dll - ${SHARED_LIB_STAGING_DIR}/Debug/libtcmalloc_minimal-debug.dll - ) + ${SHARED_LIB_STAGING_DIR}/RelWithDebInfo/libtcmalloc_minimal.dll + ${SHARED_LIB_STAGING_DIR}/Debug/libtcmalloc_minimal-debug.dll + ) endif(USE_GOOGLE_PERFTOOLS) - + set(COPY_INPUT_DEPENDECIES # The following commented dependencies are determined at variably at build time. Can't do this here. @@ -1499,15 +1498,16 @@ if (WINDOWS) --actions=copy --artwork=${ARTWORK_DIR} --build=${CMAKE_CURRENT_BINARY_DIR} + --buildtype=${CMAKE_BUILD_TYPE} --configuration=${CMAKE_CFG_INTDIR} --dest=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR} --grid=${GRID} --source=${CMAKE_CURRENT_SOURCE_DIR} - --touch=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/copy_touched.bat + --touch=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/copy_touched.bat DEPENDS - ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py - stage_third_party_libs - ${COPY_INPUT_DEPENDECIES} + ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py + stage_third_party_libs + ${COPY_INPUT_DEPENDECIES} COMMENT "Performing viewer_manifest copy" ) @@ -1569,6 +1569,7 @@ if (WINDOWS) ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py --artwork=${ARTWORK_DIR} --build=${CMAKE_CURRENT_BINARY_DIR} + --buildtype=${CMAKE_BUILD_TYPE} --channel=${VIEWER_CHANNEL} --configuration=${CMAKE_CFG_INTDIR} --dest=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR} @@ -1650,6 +1651,7 @@ if (LINUX) --arch=${ARCH} --artwork=${ARTWORK_DIR} --build=${CMAKE_CURRENT_BINARY_DIR} + --buildtype=${CMAKE_BUILD_TYPE} --channel=${VIEWER_CHANNEL} --configuration=${CMAKE_CFG_INTDIR} --dest=${CMAKE_CURRENT_BINARY_DIR}/packaged @@ -1695,6 +1697,7 @@ if (DARWIN) --actions=copy --artwork=${ARTWORK_DIR} --build=${CMAKE_CURRENT_BINARY_DIR} + --buildtype=${CMAKE_BUILD_TYPE} --configuration=${CMAKE_CFG_INTDIR} --dest=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${product}.app --grid=${GRID} @@ -1715,6 +1718,7 @@ if (DARWIN) ARGS ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py --grid=${GRID} + --buildtype=${CMAKE_BUILD_TYPE} --configuration=${CMAKE_CFG_INTDIR} --channel=${VIEWER_CHANNEL} --login_channel=${VIEWER_LOGIN_CHANNEL} @@ -1734,6 +1738,7 @@ if (DARWIN) ARGS ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py --grid=${GRID} + --buildtype=${CMAKE_BUILD_TYPE} --configuration=${CMAKE_CFG_INTDIR} --channel=${VIEWER_CHANNEL} --login_channel=${VIEWER_LOGIN_CHANNEL} @@ -1761,9 +1766,10 @@ if (LL_TESTS) llagentaccess.cpp lldateutil.cpp llmediadataclient.cpp - llviewerhelputil.cpp lllogininstance.cpp + llviewerhelputil.cpp ) + ################################################## # DISABLING PRECOMPILED HEADERS USAGE FOR TESTS ################################################## diff --git a/indra/newview/app_settings/ignorable_dialogs.xml b/indra/newview/app_settings/ignorable_dialogs.xml index ab18febccc..e825f13e82 100644 --- a/indra/newview/app_settings/ignorable_dialogs.xml +++ b/indra/newview/app_settings/ignorable_dialogs.xml @@ -177,21 +177,10 @@ Value 1 - FirstStreamingMusic + FirstStreamingMedia Comment - Enables FirstStreamingMusic warning dialog - Persist - 1 - Type - Boolean - Value - 1 - - FirstStreamingVideo - - Comment - Enables FirstStreamingVideo warning dialog + Enables FirstStreamingMedia warning dialog Persist 1 Type diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index e24e1a8605..a4fc095727 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -276,7 +276,7 @@ Value 0.5 - AudioSteamingMedia + AudioStreamingMedia Comment Enable streaming @@ -285,7 +285,7 @@ Type Boolean Value - 0 + 1 AudioStreamingMusic @@ -296,7 +296,7 @@ Type Boolean Value - 0 + 1 AudioStreamingVideo @@ -307,7 +307,7 @@ Type Boolean Value - 0 + 1 AuditTexture @@ -408,6 +408,17 @@ Value 1 + AvalinePhoneSeparator + + Comment + Separator of phone parts to have Avaline numbers human readable in Voice Control Panel + Persist + 1 + Type + String + Value + - + AvatarAxisDeadZone0 Comment @@ -1130,9 +1141,9 @@ Persist 1 Type - Boolean + S32 Value - 1 + 5 CameraAngle @@ -3246,17 +3257,6 @@ Value 0.75 - FolderIndentation - - Comment - Number of pixels to indent subfolders in inventory - Persist - 1 - Type - S32 - Value - 8 - FolderLoadingMessageWaitTime Comment @@ -3596,7 +3596,7 @@ Type String Value - http://viewer-help.secondlife.com/[LANGUAGE]/[CHANNEL]/[VERSION]/[TOPIC] + http://viewer-help.secondlife.com/[LANGUAGE]/[CHANNEL]/[VERSION]/[TOPIC][DEBUG_MODE] HomeSidePanelURL @@ -3607,7 +3607,7 @@ Type String Value - http://lecs.viewer-sidebar.secondlife.com.s3.amazonaws.com/sidebar.html + http://lecs.viewer-sidebar.secondlife.com.s3.amazonaws.com/sidebar.html?p=[AUTH_TOKEN]&lang=[LANGUAGE]&channel=[CHANNEL]&version=[VERSION]&major=[VERSION_MAJOR]&minor=[VERSION_MINOR] SearchURL @@ -3618,7 +3618,7 @@ Type String Value - http://int.searchwww-phx0.damballah.lindenlab.com/viewer/[CATEGORY]?q=[QUERY]&p=[AUTH_TOKEN]&r=[MATURITY]&lang=[LANGUAGE]&g=[GODLIKE]&sid=[SESSION_ID]&rid=[REGION_ID]&pid=[PARCEL_ID] + http://search.secondlife.com/viewer/[CATEGORY]?q=[QUERY]&p=[AUTH_TOKEN]&r=[MATURITY]&lang=[LANGUAGE]&g=[GODLIKE]&sid=[SESSION_ID]&rid=[REGION_ID]&pid=[PARCEL_ID] HighResSnapshot @@ -3653,17 +3653,6 @@ Value - IMInChat - - Comment - Copy IM into chat console - Persist - 1 - Type - Boolean - Value - 0 - IMShowTimestamps Comment @@ -4809,6 +4798,17 @@ Value 0 + MyOutfitsAutofill + + Comment + Always autofill My Outfits from library when empty (else happens just once). + Persist + 1 + Type + Boolean + Value + 0 + NearMeRange Comment @@ -5040,6 +5040,18 @@ Value 5 + ToastButtonWidth + + Comment + Default width of buttons in the toast. + Note if required width will be less then this one, a button will be reshaped to default size , otherwise to required + Persist + 1 + Type + S32 + Value + 90 + ChannelBottomPanelMargin Comment @@ -9320,18 +9332,7 @@ S32 Value 2 - - UILineEditorVPad - - Comment - UI Line Editor Vertical Pad - Persist - 1 - Type - S32 - Value - 5 - + UIMaxComboWidth Comment diff --git a/indra/newview/character/avatar_lad.xml b/indra/newview/character/avatar_lad.xml index ae89eb4413..448e20b382 100644 --- a/indra/newview/character/avatar_lad.xml +++ b/indra/newview/character/avatar_lad.xml @@ -5608,9 +5608,7 @@ + height="512"> + + + @@ -6620,6 +6625,7 @@ render_pass="bump"> local_texture="head_tattoo" /> + diff --git a/indra/newview/llagent.cpp b/indra/newview/llagent.cpp index d2a56f65dd..da0e9238d6 100644 --- a/indra/newview/llagent.cpp +++ b/indra/newview/llagent.cpp @@ -43,7 +43,7 @@ #include "llcallingcard.h" #include "llchannelmanager.h" #include "llconsole.h" -#include "llfirstuse.h" +//#include "llfirstuse.h" #include "llfloatercamera.h" #include "llfloatercustomize.h" #include "llfloaterreg.h" @@ -514,6 +514,8 @@ void LLAgent::resetView(BOOL reset_camera, BOOL change_camera) } setFocusOnAvatar(TRUE, ANIMATE); + + mCameraFOVZoomFactor = 0.f; } mHUDTargetZoom = 1.f; @@ -2804,6 +2806,7 @@ void LLAgent::endAnimationUpdateUI() gStatusBar->setVisibleForMouselook(true); LLBottomTray::getInstance()->setVisible(TRUE); + LLBottomTray::getInstance()->onMouselookModeOut(); LLSideTray::getInstance()->getButtonsPanel()->setVisible(TRUE); LLSideTray::getInstance()->updateSidetrayVisibility(); @@ -2812,7 +2815,7 @@ void LLAgent::endAnimationUpdateUI() LLToolMgr::getInstance()->setCurrentToolset(gBasicToolset); - LLFloaterCamera::toPrevModeIfInAvatarViewMode(); + LLFloaterCamera::onLeavingMouseLook(); // Only pop if we have pushed... if (TRUE == mViewsPushed) @@ -2902,6 +2905,7 @@ void LLAgent::endAnimationUpdateUI() LLNavigationBar::getInstance()->setVisible(FALSE); gStatusBar->setVisibleForMouselook(false); + LLBottomTray::getInstance()->onMouselookModeIn(); LLBottomTray::getInstance()->setVisible(FALSE); LLSideTray::getInstance()->getButtonsPanel()->setVisible(FALSE); @@ -2915,10 +2919,6 @@ void LLAgent::endAnimationUpdateUI() // JC - Added for always chat in third person option gFocusMgr.setKeyboardFocus(NULL); - //Making sure Camera Controls floater is in the right state - //when entering Mouse Look using wheel scrolling - LLFloaterCamera::updateIfNotInAvatarViewMode(); - LLToolMgr::getInstance()->setCurrentToolset(gMouselookToolset); mViewsPushed = TRUE; @@ -3588,7 +3588,7 @@ F32 LLAgent::calcCameraFOVZoomFactor() { return 0.f; } - else if (mFocusObject.notNull() && !mFocusObject->isAvatar()) + else if (mFocusObject.notNull() && !mFocusObject->isAvatar() && !mFocusOnAvatar) { // don't FOV zoom on mostly transparent objects LLVector3 focus_offset = mFocusObjectOffset; @@ -5161,6 +5161,11 @@ BOOL LLAgent::setUserGroupFlags(const LLUUID& group_id, BOOL accept_notices, BOO return FALSE; } +BOOL LLAgent::canJoinGroups() const +{ + return mGroups.count() < MAX_AGENT_GROUPS; +} + LLQuaternion LLAgent::getHeadRotation() { if (mAvatarObject.isNull() || !mAvatarObject->mPelvisp || !mAvatarObject->mHeadp) @@ -5694,10 +5699,10 @@ void LLAgent::processScriptControlChange(LLMessageSystem *msg, void **) } // Any control taken? If so, might be first time. - if (total_count > 0) - { - LLFirstUse::useOverrideKeys(); - } + //if (total_count > 0) + //{ + //LLFirstUse::useOverrideKeys(); + //} } else { diff --git a/indra/newview/llagent.h b/indra/newview/llagent.h index 2e95dc72be..beede7fbe3 100644 --- a/indra/newview/llagent.h +++ b/indra/newview/llagent.h @@ -972,6 +972,7 @@ public: BOOL setGroupContribution(const LLUUID& group_id, S32 contribution); BOOL setUserGroupFlags(const LLUUID& group_id, BOOL accept_notices, BOOL list_in_profile); const std::string &getGroupName() const { return mGroupName; } + BOOL canJoinGroups() const; private: std::string mGroupName; LLUUID mGroupID; diff --git a/indra/newview/llagentwearables.cpp b/indra/newview/llagentwearables.cpp index dc1598aacd..c21cdf9508 100644 --- a/indra/newview/llagentwearables.cpp +++ b/indra/newview/llagentwearables.cpp @@ -95,19 +95,38 @@ public: enum ELibraryOutfitFetchStep { LOFS_FOLDER = 0, LOFS_OUTFITS, + LOFS_LIBRARY, + LOFS_IMPORTED, LOFS_CONTENTS }; - LLLibraryOutfitsFetch() : mCurrFetchStep(LOFS_FOLDER), mOutfitsPopulated(false) {} + LLLibraryOutfitsFetch() : mCurrFetchStep(LOFS_FOLDER), mOutfitsPopulated(false) + { + mMyOutfitsID = LLUUID::null; + mClothingID = LLUUID::null; + mLibraryClothingID = LLUUID::null; + mImportedClothingID = LLUUID::null; + mImportedClothingName = "Imported Library Clothing"; + } ~LLLibraryOutfitsFetch() {} - virtual void done(); + virtual void done(); void doneIdle(); + LLUUID mMyOutfitsID; + void importedFolderFetch(); protected: void folderDone(void); void outfitsDone(void); + void libraryDone(void); + void importedFolderDone(void); void contentsDone(void); enum ELibraryOutfitFetchStep mCurrFetchStep; - std::vector< std::pair< LLUUID, std::string > > mOutfits; + typedef std::vector< std::pair< LLUUID, std::string > > cloth_folder_vec_t; + cloth_folder_vec_t mLibraryClothingFolders; + cloth_folder_vec_t mImportedClothingFolders; bool mOutfitsPopulated; + LLUUID mClothingID; + LLUUID mLibraryClothingID; + LLUUID mImportedClothingID; + std::string mImportedClothingName; }; LLAgentWearables gAgentWearables; @@ -116,6 +135,39 @@ BOOL LLAgentWearables::mInitialWearablesUpdateReceived = FALSE; using namespace LLVOAvatarDefines; +// HACK: For EXT-3923: Pants item shows in inventory with skin icon and messes with "current look" +// Some db items are corrupted, have inventory flags = 0, implying wearable type = shape, even though +// wearable type stored in asset is some other value. +// Calling this function whenever a wearable is added to increase visibility if this problem +// turns up in other inventories. +void checkWearableAgainstInventory(LLWearable *wearable) +{ + if (wearable->getItemID().isNull()) + return; + + // Check for wearable type consistent with inventory item wearable type. + LLViewerInventoryItem *item = gInventory.getItem(wearable->getItemID()); + if (item) + { + if (!item->isWearableType()) + { + llwarns << "wearable associated with non-wearable item" << llendl; + } + if (item->getWearableType() != wearable->getType()) + { + llwarns << "type mismatch: wearable " << wearable->getName() + << " has type " << wearable->getType() + << " but inventory item " << item->getName() + << " has type " << item->getWearableType() << llendl; + } + } + else + { + llwarns << "wearable inventory item not found" << wearable->getName() + << " itemID " << wearable->getItemID().asString() << llendl; + } +} + void LLAgentWearables::dump() { llinfos << "LLAgentWearablesDump" << llendl; @@ -657,6 +709,7 @@ LLWearable* LLAgentWearables::getWearable(const EWearableType type, U32 index) void LLAgentWearables::setWearable(const EWearableType type, U32 index, LLWearable *wearable) { + LLWearable *old_wearable = getWearable(type,index); if (!old_wearable) { @@ -680,6 +733,7 @@ void LLAgentWearables::setWearable(const EWearableType type, U32 index, LLWearab wearable_vec[index] = wearable; old_wearable->setLabelUpdated(); wearableUpdated(wearable); + checkWearableAgainstInventory(wearable); } } @@ -695,6 +749,7 @@ U32 LLAgentWearables::pushWearable(const EWearableType type, LLWearable *wearabl { mWearableDatas[type].push_back(wearable); wearableUpdated(wearable); + checkWearableAgainstInventory(wearable); return mWearableDatas[type].size()-1; } return MAX_WEARABLES_PER_TYPE; @@ -875,7 +930,7 @@ void LLAgentWearables::processAgentInitialWearablesUpdate(LLMessageSystem* mesgs // If this is the very first time the user has logged into viewer2+ (from a legacy viewer, or new account) // then auto-populate outfits from the library into the My Outfits folder. - if (LLInventoryModel::getIsFirstTimeInViewer2()) + if (LLInventoryModel::getIsFirstTimeInViewer2() || gSavedSettings.getBOOL("MyOutfitsAutofill")) { gAgentWearables.populateMyOutfitsFolder(); } @@ -1309,15 +1364,15 @@ void LLAgentWearables::makeNewOutfit(const std::string& new_folder_name, } } -class LLAutoRenameFolder: public LLInventoryCallback +class LLShowCreatedOutfit: public LLInventoryCallback { public: - LLAutoRenameFolder(LLUUID& folder_id): + LLShowCreatedOutfit(LLUUID& folder_id): mFolderID(folder_id) { } - virtual ~LLAutoRenameFolder() + virtual ~LLShowCreatedOutfit() { LLSD key; LLSideTray::getInstance()->showPanel("panel_outfits_inventory", key); @@ -1327,13 +1382,15 @@ public: { outfit_panel->getRootFolder()->clearSelection(); outfit_panel->getRootFolder()->setSelectionByID(mFolderID, TRUE); - outfit_panel->getRootFolder()->setNeedsAutoRename(TRUE); } LLAccordionCtrlTab* tab_outfits = outfit_panel ? outfit_panel->findChild("tab_outfits") : 0; if (tab_outfits && !tab_outfits->getDisplayChildren()) { tab_outfits->changeOpenClose(tab_outfits->getDisplayChildren()); } + + LLAppearanceManager::instance().updateIsDirty(); + LLAppearanceManager::instance().updatePanelOutfitName(""); } virtual void fire(const LLUUID&) @@ -1358,9 +1415,10 @@ LLUUID LLAgentWearables::makeNewOutfitLinks(const std::string& new_folder_name) LLFolderType::FT_OUTFIT, new_folder_name); - LLPointer cb = new LLAutoRenameFolder(folder_id); + LLPointer cb = new LLShowCreatedOutfit(folder_id); LLAppearanceManager::instance().shallowCopyCategory(LLAppearanceManager::instance().getCOF(),folder_id, cb); - + LLAppearanceManager::instance().createBaseOutfitLink(folder_id, cb); + return folder_id; } @@ -1705,6 +1763,7 @@ void LLAgentWearables::setWearableFinal(LLInventoryItem* new_item, LLWearable* n mWearableDatas[type].push_back(new_wearable); llinfos << "Added additional wearable for type " << type << " size is now " << mWearableDatas[type].size() << llendl; + checkWearableAgainstInventory(new_wearable); } else { @@ -2088,11 +2147,15 @@ void LLAgentWearables::populateMyOutfitsFolder(void) // Get the complete information on the items in the inventory and // setup an observer that will wait for that to happen. LLInventoryFetchDescendentsObserver::folder_ref_t folders; - const LLUUID my_outfits_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); + outfits->mMyOutfitsID = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - folders.push_back(my_outfits_id); + folders.push_back(outfits->mMyOutfitsID); gInventory.addObserver(outfits); outfits->fetchDescendents(folders); + if (outfits->isEverythingComplete()) + { + outfits->done(); + } } void LLLibraryOutfitsFetch::done() @@ -2106,13 +2169,24 @@ void LLLibraryOutfitsFetch::done() void LLLibraryOutfitsFetch::doneIdle() { gInventory.addObserver(this); // Add this back in since it was taken out during ::done() + switch (mCurrFetchStep) { case LOFS_FOLDER: folderDone(); + mCurrFetchStep = LOFS_OUTFITS; break; case LOFS_OUTFITS: outfitsDone(); + mCurrFetchStep = LOFS_LIBRARY; + break; + case LOFS_LIBRARY: + libraryDone(); + mCurrFetchStep = LOFS_IMPORTED; + break; + case LOFS_IMPORTED: + importedFolderDone(); + mCurrFetchStep = LOFS_CONTENTS; break; case LOFS_CONTENTS: contentsDone(); @@ -2134,67 +2208,217 @@ void LLLibraryOutfitsFetch::doneIdle() void LLLibraryOutfitsFetch::folderDone(void) { - // Early out if we already have items in My Outfits. LLInventoryModel::cat_array_t cat_array; LLInventoryModel::item_array_t wearable_array; - gInventory.collectDescendents(mCompleteFolders.front(), cat_array, wearable_array, + gInventory.collectDescendents(mMyOutfitsID, cat_array, wearable_array, LLInventoryModel::EXCLUDE_TRASH); + + // Early out if we already have items in My Outfits. if (cat_array.count() > 0 || wearable_array.count() > 0) { mOutfitsPopulated = true; return; } - // Get the UUID of the library's clothing folder - const LLUUID library_clothing_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING, false, true); + mClothingID = gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING); + mLibraryClothingID = gInventory.findCategoryUUIDForType(LLFolderType::FT_CLOTHING, false, true); mCompleteFolders.clear(); // Get the complete information on the items in the inventory. LLInventoryFetchDescendentsObserver::folder_ref_t folders; - folders.push_back(library_clothing_id); - mCurrFetchStep = LOFS_OUTFITS; + folders.push_back(mClothingID); + folders.push_back(mLibraryClothingID); fetchDescendents(folders); + if (isEverythingComplete()) + { + done(); + } } void LLLibraryOutfitsFetch::outfitsDone(void) { LLInventoryModel::cat_array_t cat_array; LLInventoryModel::item_array_t wearable_array; - gInventory.collectDescendents(mCompleteFolders.front(), cat_array, wearable_array, - LLInventoryModel::EXCLUDE_TRASH); - LLInventoryFetchDescendentsObserver::folder_ref_t folders; + // Collect the contents of the Library's Clothing folder + gInventory.collectDescendents(mLibraryClothingID, cat_array, wearable_array, + LLInventoryModel::EXCLUDE_TRASH); + llassert(cat_array.count() > 0); for (LLInventoryModel::cat_array_t::const_iterator iter = cat_array.begin(); iter != cat_array.end(); ++iter) { const LLViewerInventoryCategory *cat = iter->get(); + + // Get the names and id's of every outfit in the library, except for ruth and other "misc" outfits. if (cat->getName() != "More Outfits" && cat->getName() != "Ruth") { + // Get the name of every outfit in the library folders.push_back(cat->getUUID()); - mOutfits.push_back(std::make_pair(cat->getUUID(), cat->getName())); + mLibraryClothingFolders.push_back(std::make_pair(cat->getUUID(), cat->getName())); } } + + // Collect the contents of your Inventory Clothing folder + cat_array.clear(); + wearable_array.clear(); + gInventory.collectDescendents(mClothingID, cat_array, wearable_array, + LLInventoryModel::EXCLUDE_TRASH); + + // Check if you already have an "Imported Library Clothing" folder + for (LLInventoryModel::cat_array_t::const_iterator iter = cat_array.begin(); + iter != cat_array.end(); + ++iter) + { + const LLViewerInventoryCategory *cat = iter->get(); + if (cat->getName() == mImportedClothingName) + { + mImportedClothingID = cat->getUUID(); + } + } + mCompleteFolders.clear(); + + fetchDescendents(folders); + if (isEverythingComplete()) + { + done(); + } +} - mCurrFetchStep = LOFS_CONTENTS; +class LLLibraryOutfitsCopyDone: public LLInventoryCallback +{ +public: + LLLibraryOutfitsCopyDone(LLLibraryOutfitsFetch * fetcher): + mFireCount(0), mLibraryOutfitsFetcher(fetcher) + { + } + + virtual ~LLLibraryOutfitsCopyDone() + { + if (mLibraryOutfitsFetcher) + { + gInventory.addObserver(mLibraryOutfitsFetcher); + mLibraryOutfitsFetcher->done(); + } + } + + /* virtual */ void fire(const LLUUID& inv_item) + { + mFireCount++; + } +private: + U32 mFireCount; + LLLibraryOutfitsFetch * mLibraryOutfitsFetcher; +}; + +void LLLibraryOutfitsFetch::libraryDone(void) +{ + // Copy the clothing folders from the library into the imported clothing folder if necessary. + if (mImportedClothingID == LLUUID::null) + { + gInventory.removeObserver(this); + LLPointer copy_waiter = new LLLibraryOutfitsCopyDone(this); + mImportedClothingID = gInventory.createNewCategory(mClothingID, + LLFolderType::FT_NONE, + mImportedClothingName); + + for (cloth_folder_vec_t::const_iterator iter = mLibraryClothingFolders.begin(); + iter != mLibraryClothingFolders.end(); + ++iter) + { + LLUUID folder_id = gInventory.createNewCategory(mImportedClothingID, + LLFolderType::FT_NONE, + iter->second); + LLAppearanceManager::getInstance()->shallowCopyCategory(iter->first, folder_id, copy_waiter); + } + } + else + { + // Skip straight to fetching the contents of the imported folder + importedFolderFetch(); + } +} + +void LLLibraryOutfitsFetch::importedFolderFetch(void) +{ + // Fetch the contents of the Imported Clothing Folder + LLInventoryFetchDescendentsObserver::folder_ref_t folders; + folders.push_back(mImportedClothingID); + + mCompleteFolders.clear(); + fetchDescendents(folders); + if (isEverythingComplete()) + { + done(); + } } -void LLLibraryOutfitsFetch::contentsDone(void) +void LLLibraryOutfitsFetch::importedFolderDone(void) { - for(S32 i = 0; i < (S32)mOutfits.size(); ++i) + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t wearable_array; + LLInventoryFetchDescendentsObserver::folder_ref_t folders; + + // Collect the contents of the Imported Clothing folder + gInventory.collectDescendents(mImportedClothingID, cat_array, wearable_array, + LLInventoryModel::EXCLUDE_TRASH); + + for (LLInventoryModel::cat_array_t::const_iterator iter = cat_array.begin(); + iter != cat_array.end(); + ++iter) + { + const LLViewerInventoryCategory *cat = iter->get(); + + // Get the name of every imported outfit + folders.push_back(cat->getUUID()); + mImportedClothingFolders.push_back(std::make_pair(cat->getUUID(), cat->getName())); + } + + mCompleteFolders.clear(); + fetchDescendents(folders); + if (isEverythingComplete()) + { + done(); + } +} + +void LLLibraryOutfitsFetch::contentsDone(void) +{ + LLInventoryModel::cat_array_t cat_array; + LLInventoryModel::item_array_t wearable_array; + + for (cloth_folder_vec_t::const_iterator folder_iter = mImportedClothingFolders.begin(); + folder_iter != mImportedClothingFolders.end(); + ++folder_iter) { // First, make a folder in the My Outfits directory. - const LLUUID parent_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MY_OUTFITS); - LLUUID folder_id = gInventory.createNewCategory(parent_id, - LLFolderType::FT_OUTFIT, - mOutfits[i].second); - LLAppearanceManager::getInstance()->shallowCopyCategory(mOutfits[i].first, folder_id, NULL); + LLUUID new_outfit_folder_id = gInventory.createNewCategory(mMyOutfitsID, LLFolderType::FT_OUTFIT, folder_iter->second); + + cat_array.clear(); + wearable_array.clear(); + // Collect the contents of each imported clothing folder, so we can create new outfit links for it + gInventory.collectDescendents(folder_iter->first, cat_array, wearable_array, + LLInventoryModel::EXCLUDE_TRASH); + + for (LLInventoryModel::item_array_t::const_iterator wearable_iter = wearable_array.begin(); + wearable_iter != wearable_array.end(); + ++wearable_iter) + { + const LLViewerInventoryItem *item = wearable_iter->get(); + link_inventory_item(gAgent.getID(), + item->getLinkedUUID(), + new_outfit_folder_id, + item->getName(), + LLAssetType::AT_LINK, + NULL); + } } + mOutfitsPopulated = true; } @@ -2236,6 +2460,8 @@ void LLInitialWearablesFetch::processContents() } else { + // if we're constructing the COF from the wearables message, we don't have a proper outfit link + LLAppearanceManager::instance().setOutfitDirty(true); processWearablesMessage(); } delete this; diff --git a/indra/newview/llappearancemgr.cpp b/indra/newview/llappearancemgr.cpp index 43b2f34ecd..748d8bdfbf 100644 --- a/indra/newview/llappearancemgr.cpp +++ b/indra/newview/llappearancemgr.cpp @@ -364,7 +364,7 @@ LLUUID LLAppearanceManager::getCOF() } -const LLViewerInventoryItem* LLAppearanceManager::getCurrentOutfitLink() +const LLViewerInventoryItem* LLAppearanceManager::getBaseOutfitLink() { const LLUUID& current_outfit_cat = getCOF(); LLInventoryModel::cat_array_t cat_array; @@ -392,6 +392,21 @@ const LLViewerInventoryItem* LLAppearanceManager::getCurrentOutfitLink() return NULL; } +bool LLAppearanceManager::getBaseOutfitName(std::string& name) +{ + const LLViewerInventoryItem* outfit_link = getBaseOutfitLink(); + if(outfit_link) + { + const LLViewerInventoryCategory *cat = outfit_link->getLinkedCategory(); + if (cat) + { + name = cat->getName(); + return true; + } + } + return false; +} + // Update appearance from outfit folder. void LLAppearanceManager::changeOutfit(bool proceed, const LLUUID& category, bool append) { @@ -444,6 +459,28 @@ void LLAppearanceManager::shallowCopyCategory(const LLUUID& src_id, const LLUUID } } +void LLAppearanceManager::purgeBaseOutfitLink(const LLUUID& category) +{ + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + gInventory.collectDescendents(category, cats, items, + LLInventoryModel::EXCLUDE_TRASH); + for (S32 i = 0; i < items.count(); ++i) + { + LLViewerInventoryItem *item = items.get(i); + if (item->getActualType() != LLAssetType::AT_LINK_FOLDER) + continue; + if (item->getIsLinkType()) + { + LLViewerInventoryCategory* catp = item->getLinkedCategory(); + if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) + { + gInventory.purgeObject(item->getUUID()); + } + } + } +} + void LLAppearanceManager::purgeCategory(const LLUUID& category, bool keep_outfit_links) { LLInventoryModel::cat_array_t cats; @@ -578,17 +615,9 @@ void LLAppearanceManager::updateCOF(const LLUUID& category, bool append) linkAll(cof, gest_items, link_waiter); // Add link to outfit if category is an outfit. - LLViewerInventoryCategory* catp = gInventory.getCategory(category); if (!append) { - std::string new_outfit_name = ""; - if (catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) - { - link_inventory_item(gAgent.getID(), category, cof, catp->getName(), - LLAssetType::AT_LINK_FOLDER, link_waiter); - new_outfit_name = catp->getName(); - } - updatePanelOutfitName(new_outfit_name); + createBaseOutfitLink(category, link_waiter); } } @@ -602,6 +631,23 @@ void LLAppearanceManager::updatePanelOutfitName(const std::string& name) } } +void LLAppearanceManager::createBaseOutfitLink(const LLUUID& category, LLPointer link_waiter) +{ + const LLUUID cof = getCOF(); + LLViewerInventoryCategory* catp = gInventory.getCategory(category); + std::string new_outfit_name = ""; + + purgeBaseOutfitLink(cof); + + if (catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) + { + link_inventory_item(gAgent.getID(), category, cof, catp->getName(), + LLAssetType::AT_LINK_FOLDER, link_waiter); + new_outfit_name = catp->getName(); + } + + updatePanelOutfitName(new_outfit_name); +} void LLAppearanceManager::updateAgentWearables(LLWearableHoldingPattern* holder, bool append) { @@ -644,6 +690,10 @@ void LLAppearanceManager::updateAgentWearables(LLWearableHoldingPattern* holder, void LLAppearanceManager::updateAppearanceFromCOF() { + // update dirty flag to see if the state of the COF matches + // the saved outfit stored as a folder link + updateIsDirty(); + dumpCat(getCOF(),"COF, start"); bool follow_folder_links = true; @@ -693,14 +743,30 @@ void LLAppearanceManager::updateAppearanceFromCOF() LLDynamicArray found_container; for(S32 i = 0; i < wear_items.count(); ++i) { - found = new LLFoundData(wear_items.get(i)->getLinkedUUID(), // Wear the base item, not the link - wear_items.get(i)->getAssetUUID(), - wear_items.get(i)->getName(), - wear_items.get(i)->getType()); - holder->mFoundList.push_front(found); - found_container.put(found); + LLViewerInventoryItem *item = wear_items.get(i); + LLViewerInventoryItem *linked_item = item ? item->getLinkedItem() : NULL; + if (item && linked_item) + { + found = new LLFoundData(linked_item->getUUID(), + linked_item->getAssetUUID(), + linked_item->getName(), + linked_item->getType()); + holder->mFoundList.push_front(found); + found_container.put(found); + } + else + { + if (!item) + { + llwarns << "attempt to wear a null item " << llendl; + } + else if (!linked_item) + { + llwarns << "attempt to wear a broken link " << item->getName() << llendl; + } + } } - for(S32 i = 0; i < wear_items.count(); ++i) + for(S32 i = 0; i < found_container.count(); ++i) { holder->append = false; found = found_container.get(i); @@ -959,7 +1025,9 @@ void LLAppearanceManager::addCOFItemLink(const LLInventoryItem *item, bool do_up if (linked_already) { if (do_update) + { LLAppearanceManager::updateAppearanceFromCOF(); + } return; } else @@ -1013,6 +1081,75 @@ void LLAppearanceManager::removeCOFItemLinks(const LLUUID& item_id, bool do_upda } } +void LLAppearanceManager::updateIsDirty() +{ + LLUUID cof = getCOF(); + LLUUID base_outfit; + + // find base outfit link + const LLViewerInventoryItem* base_outfit_item = getBaseOutfitLink(); + LLViewerInventoryCategory* catp = NULL; + if (base_outfit_item && base_outfit_item->getIsLinkType()) + { + catp = base_outfit_item->getLinkedCategory(); + } + if(catp && catp->getPreferredType() == LLFolderType::FT_OUTFIT) + { + base_outfit = catp->getUUID(); + } + + if(base_outfit.isNull()) + { + // no outfit link found, display "unsaved outfit" + mOutfitIsDirty = true; + } + else + { + LLInventoryModel::cat_array_t cof_cats; + LLInventoryModel::item_array_t cof_items; + gInventory.collectDescendents(cof, cof_cats, cof_items, + LLInventoryModel::EXCLUDE_TRASH); + + LLInventoryModel::cat_array_t outfit_cats; + LLInventoryModel::item_array_t outfit_items; + gInventory.collectDescendents(base_outfit, outfit_cats, outfit_items, + LLInventoryModel::EXCLUDE_TRASH); + + if(outfit_items.count() != cof_items.count() -1) + { + // Current outfit folder should have one more item than the outfit folder. + // this one item is the link back to the outfit folder itself. + mOutfitIsDirty = true; + } + else + { + typedef std::set item_set_t; + item_set_t cof_set; + item_set_t outfit_set; + + // sort COF items by UUID + for (S32 i = 0; i < cof_items.count(); ++i) + { + LLViewerInventoryItem *item = cof_items.get(i); + // don't add the base outfit link to the list of objects we're comparing + if(item != base_outfit_item) + { + cof_set.insert(item->getLinkedUUID()); + } + } + + // sort outfit folder by UUID + for (S32 i = 0; i < outfit_items.count(); ++i) + { + LLViewerInventoryItem *item = outfit_items.get(i); + outfit_set.insert(item->getLinkedUUID()); + } + + mOutfitIsDirty = (outfit_set != cof_set); + } + } +} + //#define DUMP_CAT_VERBOSE void LLAppearanceManager::dumpCat(const LLUUID& cat_id, const std::string& msg) @@ -1049,7 +1186,8 @@ void LLAppearanceManager::dumpItemArray(const LLInventoryModel::item_array_t& it } LLAppearanceManager::LLAppearanceManager(): - mAttachmentInvLinkEnabled(false) + mAttachmentInvLinkEnabled(false), + mOutfitIsDirty(false) { } diff --git a/indra/newview/llappearancemgr.h b/indra/newview/llappearancemgr.h index b625d42a50..20745b70e4 100644 --- a/indra/newview/llappearancemgr.h +++ b/indra/newview/llappearancemgr.h @@ -62,11 +62,14 @@ public: LLUUID getCOF(); // Finds the folder link to the currently worn outfit - const LLViewerInventoryItem *getCurrentOutfitLink(); + const LLViewerInventoryItem *getBaseOutfitLink(); + bool getBaseOutfitName(std::string &name); // Update the displayed outfit name in UI. void updatePanelOutfitName(const std::string& name); + void createBaseOutfitLink(const LLUUID& category, LLPointer link_waiter); + void updateAgentWearables(LLWearableHoldingPattern* holder, bool append); // For debugging - could be moved elsewhere. @@ -94,6 +97,16 @@ public: // Add COF link to ensemble folder. void addEnsembleLink(LLInventoryCategory* item, bool do_update = true); + //has the current outfit changed since it was loaded? + bool isOutfitDirty() { return mOutfitIsDirty; } + + // set false if you just loaded the outfit, true otherwise + void setOutfitDirty(bool isDirty) { mOutfitIsDirty = isDirty; } + + // manually compare ouftit folder link to COF to see if outfit has changed. + // should only be necessary to do on initial login. + void updateIsDirty(); + protected: LLAppearanceManager(); ~LLAppearanceManager(); @@ -114,9 +127,11 @@ private: bool follow_folder_links); void purgeCategory(const LLUUID& category, bool keep_outfit_links); + void purgeBaseOutfitLink(const LLUUID& category); std::set mRegisteredAttachments; bool mAttachmentInvLinkEnabled; + bool mOutfitIsDirty; }; #define SUPPORT_ENSEMBLES 0 diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index 21d908035f..8507c62d27 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -71,7 +71,7 @@ #include "lluicolortable.h" #include "llurldispatcher.h" #include "llurlhistory.h" -#include "llfirstuse.h" +//#include "llfirstuse.h" #include "llrender.h" #include "llteleporthistory.h" #include "lllocationhistory.h" @@ -313,6 +313,7 @@ void init_default_trans_args() { default_trans_args.insert("SECOND_LIFE"); // World default_trans_args.insert("APP_NAME"); + default_trans_args.insert("CAPITALIZED_APP_NAME"); default_trans_args.insert("SECOND_LIFE_GRID"); default_trans_args.insert("SUPPORT_SITE"); } @@ -921,7 +922,6 @@ bool LLAppViewer::mainLoop() { LLMemType mt1(LLMemType::MTYPE_MAIN); mMainloopTimeout = new LLWatchdogTimeout(); - // *FIX:Mani - Make this a setting, once new settings exist in this branch. //------------------------------------------- // Run main loop until time to quit @@ -931,12 +931,13 @@ 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); - + LLTimer frameTimer,idleTimer; LLTimer debugTime; LLViewerJoystick* joystick(LLViewerJoystick::getInstance()); @@ -1371,7 +1372,7 @@ bool LLAppViewer::cleanup() if( gViewerWindow) gViewerWindow->shutdownViews(); - llinfos << "Cleaning up Inevntory" << llendflush; + llinfos << "Cleaning up Inventory" << llendflush; // Cleanup Inventory after the UI since it will delete any remaining observers // (Deleted observers should have already removed themselves) @@ -1473,10 +1474,17 @@ bool LLAppViewer::cleanup() LLUIColorTable::instance().saveUserSettings(); - // PerAccountSettingsFile should be empty if no use has been logged on. + // PerAccountSettingsFile should be empty if no user has been logged on. // *FIX:Mani This should get really saved in a "logoff" mode. - gSavedPerAccountSettings.saveToFile(gSavedSettings.getString("PerAccountSettingsFile"), TRUE); - llinfos << "Saved settings" << llendflush; + if (gSavedSettings.getString("PerAccountSettingsFile").empty()) + { + llinfos << "Not saving per-account settings; don't know the account name yet." << llendl; + } + else + { + gSavedPerAccountSettings.saveToFile(gSavedSettings.getString("PerAccountSettingsFile"), TRUE); + llinfos << "Saved settings" << llendflush; + } std::string crash_settings_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, CRASH_SETTINGS_FILE); // save all settings, even if equals defaults @@ -1910,26 +1918,25 @@ bool LLAppViewer::initConfiguration() // These are warnings that appear on the first experience of that condition. // They are already set in the settings_default.xml file, but still need to be added to LLFirstUse // for disable/reset ability - LLFirstUse::addConfigVariable("FirstBalanceIncrease"); - LLFirstUse::addConfigVariable("FirstBalanceDecrease"); - LLFirstUse::addConfigVariable("FirstSit"); - LLFirstUse::addConfigVariable("FirstMap"); - LLFirstUse::addConfigVariable("FirstGoTo"); - LLFirstUse::addConfigVariable("FirstBuild"); +// LLFirstUse::addConfigVariable("FirstBalanceIncrease"); +// LLFirstUse::addConfigVariable("FirstBalanceDecrease"); +// LLFirstUse::addConfigVariable("FirstSit"); +// LLFirstUse::addConfigVariable("FirstMap"); +// LLFirstUse::addConfigVariable("FirstGoTo"); +// LLFirstUse::addConfigVariable("FirstBuild"); // LLFirstUse::addConfigVariable("FirstLeftClickNoHit"); - LLFirstUse::addConfigVariable("FirstTeleport"); - LLFirstUse::addConfigVariable("FirstOverrideKeys"); - LLFirstUse::addConfigVariable("FirstAttach"); - LLFirstUse::addConfigVariable("FirstAppearance"); - LLFirstUse::addConfigVariable("FirstInventory"); - LLFirstUse::addConfigVariable("FirstSandbox"); - LLFirstUse::addConfigVariable("FirstFlexible"); - LLFirstUse::addConfigVariable("FirstDebugMenus"); - LLFirstUse::addConfigVariable("FirstStreamingMusic"); - LLFirstUse::addConfigVariable("FirstStreamingVideo"); - LLFirstUse::addConfigVariable("FirstSculptedPrim"); - LLFirstUse::addConfigVariable("FirstVoice"); - LLFirstUse::addConfigVariable("FirstMedia"); +// LLFirstUse::addConfigVariable("FirstTeleport"); +// LLFirstUse::addConfigVariable("FirstOverrideKeys"); +// LLFirstUse::addConfigVariable("FirstAttach"); +// LLFirstUse::addConfigVariable("FirstAppearance"); +// LLFirstUse::addConfigVariable("FirstInventory"); +// LLFirstUse::addConfigVariable("FirstSandbox"); +// LLFirstUse::addConfigVariable("FirstFlexible"); +// LLFirstUse::addConfigVariable("FirstDebugMenus"); +// LLFirstUse::addConfigVariable("FirstStreamingMedia"); +// LLFirstUse::addConfigVariable("FirstSculptedPrim"); +// LLFirstUse::addConfigVariable("FirstVoice"); +// LLFirstUse::addConfigVariable("FirstMedia"); // - read command line settings. LLControlGroupCLP clp; diff --git a/indra/newview/llavataractions.cpp b/indra/newview/llavataractions.cpp index 2a8c55e5db..40c9bb6afa 100644 --- a/indra/newview/llavataractions.cpp +++ b/indra/newview/llavataractions.cpp @@ -168,8 +168,10 @@ void LLAvatarActions::offerTeleport(const LLUUID& invitee) // static void LLAvatarActions::offerTeleport(const std::vector& ids) { - if (ids.size() > 0) - handle_lure(ids); + if (ids.size() == 0) + return; + + handle_lure(ids); } // static @@ -595,9 +597,11 @@ void LLAvatarActions::requestFriendship(const LLUUID& target_id, const std::stri LLSD args; args["TO_NAME"] = target_name; + LLSD payload; + payload["from_id"] = target_id; payload["SESSION_NAME"] = target_name; - payload["SUPPRES_TOST"] = true; + payload["SUPPRESS_TOAST"] = true; LLNotificationsUtil::add("FriendshipOffered", args, payload); } diff --git a/indra/newview/llavatariconctrl.cpp b/indra/newview/llavatariconctrl.cpp index 42ae122ff9..87b8d807c4 100644 --- a/indra/newview/llavatariconctrl.cpp +++ b/indra/newview/llavatariconctrl.cpp @@ -64,7 +64,7 @@ void LLAvatarIconIDCache::load () llinfos << "Loading avatar icon id cache." << llendl; // build filename for each user - std::string resolved_filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, mFilename); + std::string resolved_filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, mFilename); llifstream file(resolved_filename); if (!file.is_open()) @@ -97,7 +97,7 @@ void LLAvatarIconIDCache::load () void LLAvatarIconIDCache::save () { - std::string resolved_filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, mFilename); + std::string resolved_filename = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, mFilename); // open a file for writing llofstream file (resolved_filename); diff --git a/indra/newview/llavatarlist.cpp b/indra/newview/llavatarlist.cpp index 5df73a994e..6784e6693b 100644 --- a/indra/newview/llavatarlist.cpp +++ b/indra/newview/llavatarlist.cpp @@ -82,7 +82,7 @@ void LLAvatarList::setSpeakingIndicatorsVisible(bool visible) getItems(items); for( std::vector::const_iterator it = items.begin(); it != items.end(); it++) { - static_cast(*it)->setSpeakingIndicatorVisible(mShowSpeakingIndicator); + static_cast(*it)->showSpeakingIndicator(mShowSpeakingIndicator); } } @@ -320,21 +320,25 @@ boost::signals2::connection LLAvatarList::setRefreshCompleteCallback(const commi return mRefreshCompleteSignal.connect(cb); } +boost::signals2::connection LLAvatarList::setItemDoubleClickCallback(const mouse_signal_t::slot_type& cb) +{ + return mItemDoubleClickSignal.connect(cb); +} + void LLAvatarList::addNewItem(const LLUUID& id, const std::string& name, BOOL is_online, EAddPosition pos) { LLAvatarListItem* item = new LLAvatarListItem(); - item->showInfoBtn(true); - item->showSpeakingIndicator(true); item->setName(name); item->setAvatarId(id, mIgnoreOnlineStatus); item->setOnline(mIgnoreOnlineStatus ? true : is_online); item->showLastInteractionTime(mShowLastInteractionTime); - item->childSetVisible("info_btn", false); item->setAvatarIconVisible(mShowIcons); item->setShowInfoBtn(mShowInfoBtn); item->setShowProfileBtn(mShowProfileBtn); - item->setSpeakingIndicatorVisible(mShowSpeakingIndicator); + item->showSpeakingIndicator(mShowSpeakingIndicator); + + item->setDoubleClickCallback(boost::bind(&LLAvatarList::onItemDoucleClicked, this, _1, _2, _3, _4)); addItem(item, id, pos); } @@ -403,6 +407,11 @@ void LLAvatarList::updateLastInteractionTimes() } } +void LLAvatarList::onItemDoucleClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask) +{ + mItemDoubleClickSignal(ctrl, x, y, mask); +} + bool LLAvatarItemComparator::compare(const LLPanel* item1, const LLPanel* item2) const { const LLAvatarListItem* avatar_item1 = dynamic_cast(item1); diff --git a/indra/newview/llavatarlist.h b/indra/newview/llavatarlist.h index 0d2ce884ae..a58a562378 100644 --- a/indra/newview/llavatarlist.h +++ b/indra/newview/llavatarlist.h @@ -92,6 +92,8 @@ public: boost::signals2::connection setRefreshCompleteCallback(const commit_signal_t::slot_type& cb); + boost::signals2::connection setItemDoubleClickCallback(const mouse_signal_t::slot_type& cb); + protected: void refresh(); @@ -101,6 +103,7 @@ protected: std::vector& vadded, std::vector& vremoved); void updateLastInteractionTimes(); + void onItemDoucleClicked(LLUICtrl* ctrl, S32 x, S32 y, MASK mask); private: @@ -120,6 +123,7 @@ private: LLAvatarListItem::ContextMenu* mContextMenu; commit_signal_t mRefreshCompleteSignal; + mouse_signal_t mItemDoubleClickSignal; }; /** Abstract comparator for avatar items */ diff --git a/indra/newview/llavatarlistitem.cpp b/indra/newview/llavatarlistitem.cpp index 6945ac6932..66ab32f3e8 100644 --- a/indra/newview/llavatarlistitem.cpp +++ b/indra/newview/llavatarlistitem.cpp @@ -44,10 +44,10 @@ #include "llbutton.h" bool LLAvatarListItem::sStaticInitialized = false; -S32 LLAvatarListItem::sIconWidth = 0; -S32 LLAvatarListItem::sInfoBtnWidth = 0; -S32 LLAvatarListItem::sProfileBtnWidth = 0; -S32 LLAvatarListItem::sSpeakingIndicatorWidth = 0; +S32 LLAvatarListItem::sLeftPadding = 0; +S32 LLAvatarListItem::sRightNamePadding = 0; +S32 LLAvatarListItem::sChildrenWidths[LLAvatarListItem::ALIC_COUNT]; + LLAvatarListItem::LLAvatarListItem(bool not_from_ui_factory/* = true*/) : LLPanel(), @@ -91,43 +91,25 @@ BOOL LLAvatarListItem::postBuild() mProfileBtn->setVisible(false); mProfileBtn->setClickedCallback(boost::bind(&LLAvatarListItem::onProfileBtnClick, this)); - // Remember avatar icon width including its padding from the name text box, - // so that we can hide and show the icon again later. if (!sStaticInitialized) { - sIconWidth = mAvatarName->getRect().mLeft - mAvatarIcon->getRect().mLeft; - sInfoBtnWidth = mInfoBtn->getRect().mRight - mSpeakingIndicator->getRect().mRight; - sProfileBtnWidth = mProfileBtn->getRect().mRight - mInfoBtn->getRect().mRight; - sSpeakingIndicatorWidth = mSpeakingIndicator->getRect().mRight - mAvatarName->getRect().mRight; + // Remember children widths including their padding from the next sibling, + // so that we can hide and show them again later. + initChildrenWidths(this); sStaticInitialized = true; } -/* - if(!p.buttons.profile) - { - delete mProfile; - mProfile = NULL; - - LLRect rect; - - rect.setLeftTopAndSize(mName->getRect().mLeft, mName->getRect().mTop, mName->getRect().getWidth() + 30, mName->getRect().getHeight()); - mName->setRect(rect); - - if(mLocator) - { - rect.setLeftTopAndSize(mLocator->getRect().mLeft + 30, mLocator->getRect().mTop, mLocator->getRect().getWidth(), mLocator->getRect().getHeight()); - mLocator->setRect(rect); - } + return TRUE; +} - if(mInfo) - { - rect.setLeftTopAndSize(mInfo->getRect().mLeft + 30, mInfo->getRect().mTop, mInfo->getRect().getWidth(), mInfo->getRect().getHeight()); - mInfo->setRect(rect); - } +S32 LLAvatarListItem::notifyParent(const LLSD& info) +{ + if (info.has("visibility_changed")) + { + updateChildren(); } -*/ - return TRUE; + return 0; } void LLAvatarListItem::onMouseEnter(S32 x, S32 y, MASK mask) @@ -137,6 +119,8 @@ void LLAvatarListItem::onMouseEnter(S32 x, S32 y, MASK mask) mProfileBtn->setVisible(mShowProfileBtn); LLPanel::onMouseEnter(x, y, mask); + + updateChildren(); } void LLAvatarListItem::onMouseLeave(S32 x, S32 y, MASK mask) @@ -146,6 +130,8 @@ void LLAvatarListItem::onMouseLeave(S32 x, S32 y, MASK mask) mProfileBtn->setVisible(false); LLPanel::onMouseLeave(x, y, mask); + + updateChildren(); } // virtual, called by LLAvatarTracker @@ -215,12 +201,8 @@ void LLAvatarListItem::showLastInteractionTime(bool show) if (show) return; - LLRect name_rect = mAvatarName->getRect(); - LLRect time_rect = mLastInteractionTime->getRect(); - mLastInteractionTime->setVisible(false); - name_rect.mRight += (time_rect.mRight - name_rect.mRight); - mAvatarName->setRect(name_rect); + updateChildren(); } void LLAvatarListItem::setLastInteractionTime(U32 secs_since) @@ -234,12 +216,6 @@ void LLAvatarListItem::setShowInfoBtn(bool show) if(mShowInfoBtn == show) return; mShowInfoBtn = show; - S32 width_delta = show ? - sInfoBtnWidth : sInfoBtnWidth; - - //Translating speaking indicator - mSpeakingIndicator->translate(width_delta, 0); - //Reshaping avatar name - mAvatarName->reshape(mAvatarName->getRect().getWidth() + width_delta, mAvatarName->getRect().getHeight()); } void LLAvatarListItem::setShowProfileBtn(bool show) @@ -248,24 +224,17 @@ void LLAvatarListItem::setShowProfileBtn(bool show) if(mShowProfileBtn == show) return; mShowProfileBtn = show; - S32 width_delta = show ? - sProfileBtnWidth : sProfileBtnWidth; - - //Translating speaking indicator - mSpeakingIndicator->translate(width_delta, 0); - //Reshaping avatar name - mAvatarName->reshape(mAvatarName->getRect().getWidth() + width_delta, mAvatarName->getRect().getHeight()); } -void LLAvatarListItem::setSpeakingIndicatorVisible(bool visible) +void LLAvatarListItem::showSpeakingIndicator(bool visible) { // Already done? Then do nothing. if (mSpeakingIndicator->getVisible() == (BOOL)visible) return; - mSpeakingIndicator->setVisible(visible); - S32 width_delta = visible ? - sSpeakingIndicatorWidth : sSpeakingIndicatorWidth; - - //Reshaping avatar name - mAvatarName->reshape(mAvatarName->getRect().getWidth() + width_delta, mAvatarName->getRect().getHeight()); +// Disabled to not contradict with SpeakingIndicatorManager functionality. EXT-3976 +// probably this method should be totally removed. +// mSpeakingIndicator->setVisible(visible); +// updateChildren(); } void LLAvatarListItem::setAvatarIconVisible(bool visible) @@ -276,36 +245,12 @@ void LLAvatarListItem::setAvatarIconVisible(bool visible) // Show/hide avatar icon. mAvatarIcon->setVisible(visible); - - // Move the avatar name horizontally by icon size + its distance from the avatar name. - LLRect name_rect = mAvatarName->getRect(); - name_rect.mLeft += visible ? sIconWidth : -sIconWidth; - mAvatarName->setRect(name_rect); + updateChildren(); } void LLAvatarListItem::onInfoBtnClick() { LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", mAvatarId)); - - /* TODO fix positioning of inspector - localPointToScreen(mXPos, mYPos, &mXPos, &mYPos); - - - LLRect rect; - - // *TODO Vadim: rewrite this. "+= -" looks weird. - S32 delta = mYPos - inspector->getRect().getHeight(); - if(delta < 0) - { - mYPos += -delta; - } - - rect.setLeftTopAndSize(mXPos, mYPos, - inspector->getRect().getWidth(), inspector->getRect().getHeight()); - inspector->setRect(rect); - inspector->setFrontmost(true); - inspector->setVisible(true); - */ } void LLAvatarListItem::onProfileBtnClick() @@ -313,6 +258,21 @@ void LLAvatarListItem::onProfileBtnClick() LLAvatarActions::showProfile(mAvatarId); } +BOOL LLAvatarListItem::handleDoubleClick(S32 x, S32 y, MASK mask) +{ + if(mInfoBtn->getRect().pointInRect(x, y)) + { + onInfoBtnClick(); + return TRUE; + } + if(mProfileBtn->getRect().pointInRect(x, y)) + { + onProfileBtnClick(); + return TRUE; + } + return LLPanel::handleDoubleClick(x, y, mask); +} + void LLAvatarListItem::setValue( const LLSD& value ) { if (!value.isMap()) return;; @@ -344,21 +304,6 @@ void LLAvatarListItem::onNameCache(const std::string& first_name, const std::str setName(name); } -void LLAvatarListItem::reshapeAvatarName() -{ - S32 width_delta = 0; - width_delta += mShowProfileBtn ? sProfileBtnWidth : 0; - width_delta += mSpeakingIndicator->getVisible() ? sSpeakingIndicatorWidth : 0; - width_delta += mAvatarIcon->getVisible() ? sIconWidth : 0; - width_delta += mShowInfoBtn ? sInfoBtnWidth : 0; - width_delta += mLastInteractionTime->getVisible() ? mLastInteractionTime->getRect().getWidth() : 0; - - S32 height = mAvatarName->getRect().getHeight(); - S32 width = getRect().getWidth() - width_delta; - - mAvatarName->reshape(width, height); -} - // Convert given number of seconds to a string like "23 minutes", "15 hours" or "3 years", // taking i18n into account. The format string to use is taken from the panel XML. std::string LLAvatarListItem::formatSeconds(U32 secs) @@ -492,4 +437,133 @@ LLAvatarListItem::icon_color_map_t& LLAvatarListItem::getItemIconColorMap() return item_icon_color_map; } +// static +void LLAvatarListItem::initChildrenWidths(LLAvatarListItem* avatar_item) +{ + //profile btn width + padding + S32 profile_btn_width = avatar_item->getRect().getWidth() - avatar_item->mProfileBtn->getRect().mLeft; + + //info btn width + padding + S32 info_btn_width = avatar_item->mProfileBtn->getRect().mLeft - avatar_item->mInfoBtn->getRect().mLeft; + + //speaking indicator width + padding + S32 speaking_indicator_width = avatar_item->mInfoBtn->getRect().mLeft - avatar_item->mSpeakingIndicator->getRect().mLeft; + + // last interaction time textbox width + padding + S32 last_interaction_time_width = avatar_item->mSpeakingIndicator->getRect().mLeft - avatar_item->mLastInteractionTime->getRect().mLeft; + + // icon width + padding + S32 icon_width = avatar_item->mAvatarName->getRect().mLeft - avatar_item->mAvatarIcon->getRect().mLeft; + + sLeftPadding = avatar_item->mAvatarIcon->getRect().mLeft; + sRightNamePadding = avatar_item->mLastInteractionTime->getRect().mLeft - avatar_item->mAvatarName->getRect().mRight; + + S32 index = ALIC_COUNT; + sChildrenWidths[--index] = icon_width; + sChildrenWidths[--index] = 0; // for avatar name we don't need its width, it will be calculated as "left available space" + sChildrenWidths[--index] = last_interaction_time_width; + sChildrenWidths[--index] = speaking_indicator_width; + sChildrenWidths[--index] = info_btn_width; + sChildrenWidths[--index] = profile_btn_width; +} + +void LLAvatarListItem::updateChildren() +{ + LL_DEBUGS("AvatarItemReshape") << LL_ENDL; + LL_DEBUGS("AvatarItemReshape") << "Updating for: " << getAvatarName() << LL_ENDL; + + S32 name_new_width = getRect().getWidth(); + S32 ctrl_new_left = name_new_width; + S32 name_new_left = sLeftPadding; + + // iterate through all children and set them into correct position depend on each child visibility + // assume that child indexes are in back order: the first in Enum is the last (right) in the item + // iterate & set child views starting from right to left + for (S32 i = 0; i < ALIC_COUNT; ++i) + { + // skip "name" textbox, it will be processed out of loop later + if (ALIC_NAME == i) continue; + + LLView* control = getItemChildView((EAvatarListItemChildIndex)i); + + LL_DEBUGS("AvatarItemReshape") << "Processing control: " << control->getName() << LL_ENDL; + // skip invisible views + if (!control->getVisible()) continue; + + S32 ctrl_width = sChildrenWidths[i]; // including space between current & left controls + + // decrease available for + name_new_width -= ctrl_width; + LL_DEBUGS("AvatarItemReshape") << "width: " << ctrl_width << ", name_new_width: " << name_new_width << LL_ENDL; + + LLRect control_rect = control->getRect(); + LL_DEBUGS("AvatarItemReshape") << "rect before: " << control_rect << LL_ENDL; + + if (ALIC_ICON == i) + { + // assume that this is the last iteration, + // so it is not necessary to save "ctrl_new_left" value calculated on previous iterations + ctrl_new_left = sLeftPadding; + name_new_left = ctrl_new_left + ctrl_width; + } + else + { + ctrl_new_left -= ctrl_width; + } + + LL_DEBUGS("AvatarItemReshape") << "ctrl_new_left: " << ctrl_new_left << LL_ENDL; + + control_rect.setLeftTopAndSize( + ctrl_new_left, + control_rect.mTop, + control_rect.getWidth(), + control_rect.getHeight()); + + LL_DEBUGS("AvatarItemReshape") << "rect after: " << control_rect << LL_ENDL; + control->setShape(control_rect); + } + + // set size and position of the "name" child + LLView* name_view = getItemChildView(ALIC_NAME); + LLRect name_view_rect = name_view->getRect(); + LL_DEBUGS("AvatarItemReshape") << "name rect before: " << name_view_rect << LL_ENDL; + + // apply paddings + name_new_width -= sLeftPadding; + name_new_width -= sRightNamePadding; + + name_view_rect.setLeftTopAndSize( + name_new_left, + name_view_rect.mTop, + name_new_width, + name_view_rect.getHeight()); + + name_view->setShape(name_view_rect); + + LL_DEBUGS("AvatarItemReshape") << "name rect after: " << name_view_rect << LL_ENDL; +} + +LLView* LLAvatarListItem::getItemChildView(EAvatarListItemChildIndex child_view_index) +{ + LLView* child_view = mAvatarName; + if (child_view_index < 0 || ALIC_COUNT <= child_view_index) + { + LL_WARNS("AvatarItemReshape") << "Child view index is out of range: " << child_view_index << LL_ENDL; + return child_view; + } + switch (child_view_index) + { + case ALIC_ICON: child_view = mAvatarIcon; break; + case ALIC_NAME: child_view = mAvatarName; break; + case ALIC_INTERACTION_TIME: child_view = mLastInteractionTime; break; + case ALIC_SPEAKER_INDICATOR: child_view = mSpeakingIndicator; break; + case ALIC_INFO_BUTTON: child_view = mInfoBtn; break; + case ALIC_PROFILE_BUTTON: child_view = mProfileBtn; break; + default: + LL_WARNS("AvatarItemReshape") << "Unexpected child view index is passed: " << child_view_index << LL_ENDL; + } + + return child_view; +} + // EOF diff --git a/indra/newview/llavatarlistitem.h b/indra/newview/llavatarlistitem.h index 96097bc9b5..479a4833cb 100644 --- a/indra/newview/llavatarlistitem.h +++ b/indra/newview/llavatarlistitem.h @@ -74,6 +74,11 @@ public: virtual ~LLAvatarListItem(); virtual BOOL postBuild(); + + /** + * Processes notification from speaker indicator to update children when indicator's visibility is changed. + */ + virtual S32 notifyParent(const LLSD& info); virtual void onMouseLeave(S32 x, S32 y, MASK mask); virtual void onMouseEnter(S32 x, S32 y, MASK mask); virtual void setValue(const LLSD& value); @@ -88,7 +93,8 @@ public: //Show/hide profile/info btn, translating speaker indicator and avatar name coordinates accordingly void setShowProfileBtn(bool show); void setShowInfoBtn(bool show); - void setSpeakingIndicatorVisible(bool visible); + void showSpeakingIndicator(bool show); + void showLastInteractionTime(bool show); void setAvatarIconVisible(bool visible); const LLUUID& getAvatarId() const; @@ -97,16 +103,7 @@ public: void onInfoBtnClick(); void onProfileBtnClick(); - void showSpeakingIndicator(bool show) { mSpeakingIndicator->setVisible(show); } - void showInfoBtn(bool show_info_btn) {mInfoBtn->setVisible(show_info_btn); } - void showLastInteractionTime(bool show); - - /** - * This method was added to fix EXT-2364 (Items in group/ad-hoc IM participant list (avatar names) should be reshaped when adding/removing the "(Moderator)" label) - * But this is a *HACK. The real reason of it was in incorrect logic while hiding profile/info/speaker buttons - * *TODO: new reshape method should be provided in lieu of this one to be called when visibility if those buttons is changed - */ - void reshapeAvatarName(); + /*virtual*/ BOOL handleDoubleClick(S32 x, S32 y, MASK mask); protected: /** @@ -124,6 +121,23 @@ private: E_UNKNOWN, } EOnlineStatus; + /** + * Enumeration of item elements in order from right to left. + * + * updateChildren() assumes that indexes are in the such order to process avatar icon easier. + * + * @see updateChildren() + */ + typedef enum e_avatar_item_child { + ALIC_PROFILE_BUTTON, + ALIC_INFO_BUTTON, + ALIC_SPEAKER_INDICATOR, + ALIC_INTERACTION_TIME, + ALIC_NAME, + ALIC_ICON, + ALIC_COUNT, + } EAvatarListItemChildIndex; + void setNameInternal(const std::string& name, const std::string& highlight); void onNameCache(const std::string& first_name, const std::string& last_name); @@ -135,6 +149,26 @@ private: typedef std::map icon_color_map_t; static icon_color_map_t& getItemIconColorMap(); + /** + * Initializes widths of all children to use them while changing visibility of any of them. + * + * @see updateChildren() + */ + static void initChildrenWidths(LLAvatarListItem* self); + + /** + * Updates position and rectangle of visible children to fit all available item's width. + */ + void updateChildren(); + + /** + * Gets child view specified by index. + * + * This method implemented via switch by all EAvatarListItemChildIndex values. + * It is used to not store children in array or vector to avoid of increasing memory usage. + */ + LLView* getItemChildView(EAvatarListItemChildIndex child_index); + LLTextBox* mAvatarName; LLTextBox* mLastInteractionTime; LLStyle::Params mAvatarNameStyle; @@ -151,10 +185,17 @@ private: bool mShowProfileBtn; static bool sStaticInitialized; // this variable is introduced to improve code readability - static S32 sIconWidth; // icon width + padding - static S32 sInfoBtnWidth; //info btn width + padding - static S32 sProfileBtnWidth; //profile btn width + padding - static S32 sSpeakingIndicatorWidth; //speaking indicator width + padding + static S32 sLeftPadding; // padding to first left visible child (icon or name) + static S32 sRightNamePadding; // right padding from name to next visible child + + /** + * Contains widths of each child specified by EAvatarListItemChildIndex + * including padding to the next right one. + * + * @see initChildrenWidths() + */ + static S32 sChildrenWidths[ALIC_COUNT]; + }; #endif //LL_LLAVATARLISTITEM_H diff --git a/indra/newview/llbottomtray.cpp b/indra/newview/llbottomtray.cpp index 976b312509..bd68d52868 100644 --- a/indra/newview/llbottomtray.cpp +++ b/indra/newview/llbottomtray.cpp @@ -48,10 +48,20 @@ #include "llsyswellwindow.h" #include "llfloatercamera.h" #include "lltexteditor.h" +#include "llnotifications.h" // Build time optimization, generate extern template once in .cpp file template class LLBottomTray* LLSingleton::getInstance(); +namespace +{ + const std::string& PANEL_CHICLET_NAME = "chiclet_list_panel"; + const std::string& PANEL_CHATBAR_NAME = "chat_bar"; + const std::string& PANEL_MOVEMENT_NAME = "movement_panel"; + const std::string& PANEL_CAMERA_NAME = "cam_panel"; + const std::string& PANEL_GESTURE_NAME = "gesture_panel"; +} + LLBottomTray::LLBottomTray(const LLSD&) : mChicletPanel(NULL), mSpeakPanel(NULL), @@ -161,6 +171,9 @@ void LLBottomTray::sessionAdded(const LLUUID& session_id, const std::string& nam { chiclet->setIMSessionName(name); chiclet->setOtherParticipantId(other_participant_id); + + LLIMFloater::onIMChicletCreated(session_id); + } else { @@ -233,6 +246,61 @@ void LLBottomTray::onFocusLost() } } +void LLBottomTray::savePanelsShape() +{ + mSavedShapeList.clear(); + for (child_list_const_iter_t + child_it = mToolbarStack->beginChild(), + child_it_end = mToolbarStack->endChild(); + child_it != child_it_end; ++child_it) + { + mSavedShapeList.push_back( (*child_it)->getRect() ); + } +} + +void LLBottomTray::restorePanelsShape() +{ + if (mSavedShapeList.size() != mToolbarStack->getChildCount()) + return; + int i = 0; + for (child_list_const_iter_t + child_it = mToolbarStack->beginChild(), + child_it_end = mToolbarStack->endChild(); + child_it != child_it_end; ++child_it) + { + (*child_it)->setShape(mSavedShapeList[i++]); + } +} + +void LLBottomTray::onMouselookModeOut() +{ + // Apply the saved settings when we are not in mouselook mode, see EXT-3988. + { + setTrayButtonVisibleIfPossible (RS_BUTTON_GESTURES, gSavedSettings.getBOOL("ShowGestureButton"), false); + setTrayButtonVisibleIfPossible (RS_BUTTON_MOVEMENT, gSavedSettings.getBOOL("ShowMoveButton"), false); + setTrayButtonVisibleIfPossible (RS_BUTTON_CAMERA, gSavedSettings.getBOOL("ShowCameraButton"), false); + setTrayButtonVisibleIfPossible (RS_BUTTON_SNAPSHOT, gSavedSettings.getBOOL("ShowSnapshotButton"),false); + } + // HACK: To avoid usage the LLLayoutStack logic of resizing, we force the updateLayout + // and then restore children saved shapes. See EXT-4309. + BOOL saved_anim = mToolbarStack->getAnimate(); + mToolbarStack->updatePanelAutoResize(PANEL_CHATBAR_NAME, FALSE); + // Disable animation to prevent layout updating in several frames. + mToolbarStack->setAnimate(FALSE); + // Force the updating of layout to reset panels collapse factor. + mToolbarStack->updateLayout(); + // Restore animate state. + mToolbarStack->setAnimate(saved_anim); + // Restore saved shapes. + restorePanelsShape(); +} + +void LLBottomTray::onMouselookModeIn() +{ + savePanelsShape(); + mToolbarStack->updatePanelAutoResize(PANEL_CHATBAR_NAME, TRUE); +} + //virtual // setVisible used instead of onVisibilityChange, since LLAgent calls it on entering/leaving mouselook mode. // If bottom tray is already visible in mouselook mode, then onVisibilityChange will not be called from setVisible(true), @@ -251,8 +319,10 @@ void LLBottomTray::setVisible(BOOL visible) { LLView* viewp = *child_it; std::string name = viewp->getName(); - - if ("chat_bar" == name || "movement_panel" == name || "cam_panel" == name || "snapshot_panel" == name || "gesture_panel" == name) + + // Chat bar and gesture button are shown even in mouselook mode. + // But the move, camera and snapshot buttons shouldn't be displayed. See EXT-3988. + if ("chat_bar" == name || "gesture_panel" == name || (visibility && ("movement_panel" == name || "cam_panel" == name || "snapshot_panel" == name))) continue; else { @@ -260,6 +330,11 @@ void LLBottomTray::setVisible(BOOL visible) } } } + + if(visible) + gFloaterView->setSnapOffsetBottom(getRect().getHeight()); + else + gFloaterView->setSnapOffsetBottom(0); } void LLBottomTray::showBottomTrayContextMenu(S32 x, S32 y, MASK mask) @@ -324,15 +399,6 @@ void LLBottomTray::showSnapshotButton(BOOL visible) setTrayButtonVisibleIfPossible(RS_BUTTON_SNAPSHOT, visible); } -namespace -{ - const std::string& PANEL_CHICLET_NAME = "chiclet_list_panel"; - const std::string& PANEL_CHATBAR_NAME = "chat_bar"; - const std::string& PANEL_MOVEMENT_NAME = "movement_panel"; - const std::string& PANEL_CAMERA_NAME = "cam_panel"; - const std::string& PANEL_GESTURE_NAME = "gesture_panel"; -} - BOOL LLBottomTray::postBuild() { @@ -1005,7 +1071,7 @@ void LLBottomTray::setTrayButtonVisible(EResizeState shown_object_type, bool vis panel->setVisible(visible); } -void LLBottomTray::setTrayButtonVisibleIfPossible(EResizeState shown_object_type, bool visible) +void LLBottomTray::setTrayButtonVisibleIfPossible(EResizeState shown_object_type, bool visible, bool raise_notification) { bool can_be_set = true; @@ -1045,7 +1111,11 @@ void LLBottomTray::setTrayButtonVisibleIfPossible(EResizeState shown_object_type { // mark this button to show it while future bottom tray extending mResizeState |= shown_object_type; - LLNotificationsUtil::add("BottomTrayButtonCanNotBeShown"); + if ( raise_notification ) + LLNotificationsUtil::add("BottomTrayButtonCanNotBeShown", + LLSD(), + LLSD(), + LLNotificationFunctorRegistry::instance().DONOTHING); } } diff --git a/indra/newview/llbottomtray.h b/indra/newview/llbottomtray.h index 9be0e5810f..562ee56912 100644 --- a/indra/newview/llbottomtray.h +++ b/indra/newview/llbottomtray.h @@ -92,7 +92,10 @@ public: void showMoveButton(BOOL visible); void showCameraButton(BOOL visible); void showSnapshotButton(BOOL visible); - + + void onMouselookModeIn(); + void onMouselookModeOut(); + /** * Creates IM Chiclet based on session type (IM chat or Group chat) */ @@ -167,7 +170,14 @@ private: * - if hidden via context menu button should be shown but there is no enough room for now * it will be shown while extending. */ - void setTrayButtonVisibleIfPossible(EResizeState shown_object_type, bool visible); + void setTrayButtonVisibleIfPossible(EResizeState shown_object_type, bool visible, bool raise_notification = true); + + /** + * Save and restore children shapes. + * Used to avoid the LLLayoutStack resizing logic between mouse look mode switching. + */ + void savePanelsShape(); + void restorePanelsShape(); MASK mResizeState; @@ -177,6 +187,9 @@ private: typedef std::map state_object_width_map_t; state_object_width_map_t mObjectDefaultWidthMap; + typedef std::vector shape_list_t; + shape_list_t mSavedShapeList; + protected: LLBottomTray(const LLSD& key = LLSD()); diff --git a/indra/newview/llcallfloater.cpp b/indra/newview/llcallfloater.cpp index a402f59fa1..f346a4b8c2 100644 --- a/indra/newview/llcallfloater.cpp +++ b/indra/newview/llcallfloater.cpp @@ -47,11 +47,13 @@ #include "llfloaterreg.h" #include "llparticipantlist.h" #include "llspeakers.h" +#include "lltextutil.h" #include "lltransientfloatermgr.h" #include "llviewerwindow.h" #include "llvoicechannel.h" static void get_voice_participants_uuids(std::vector& speakers_uuids); +void reshape_floater(LLCallFloater* floater, S32 delta_height); class LLNonAvatarCaller : public LLAvatarListItem { @@ -76,6 +78,12 @@ public: return rv; } + void setName(const std::string& name) + { + const std::string& formatted_phone = LLTextUtil::formatPhoneNumber(name); + LLAvatarListItem::setName(formatted_phone); + } + void setSpeakerId(const LLUUID& id) { mSpeakingIndicator->setSpeakerId(id); } }; @@ -217,16 +225,6 @@ void LLCallFloater::onChange() } } -S32 LLCallFloater::notifyParent(const LLSD& info) -{ - if("size_changes" == info["action"]) - { - reshapeToFitContent(); - return 1; - } - return LLDockableFloater::notifyParent(info); -} - ////////////////////////////////////////////////////////////////////////// /// PRIVATE SECTION ////////////////////////////////////////////////////////////////////////// @@ -270,6 +268,11 @@ void LLCallFloater::updateSession() case IM_NOTHING_SPECIAL: case IM_SESSION_P2P_INVITE: mVoiceType = VC_PEER_TO_PEER; + + if (!im_session->mOtherParticipantIsAvatar) + { + mVoiceType = VC_PEER_TO_PEER_AVALINE; + } break; case IM_SESSION_CONFERENCE_START: case IM_SESSION_GROUP_START: @@ -302,8 +305,8 @@ void LLCallFloater::updateSession() //hide "Leave Call" button for nearby chat bool is_local_chat = mVoiceType == VC_LOCAL_CHAT; - childSetVisible("leave_call_btn", !is_local_chat); - + childSetVisible("leave_call_btn_panel", !is_local_chat); + refreshParticipantList(); updateAgentModeratorState(); @@ -321,16 +324,13 @@ void LLCallFloater::updateSession() void LLCallFloater::refreshParticipantList() { - bool non_avatar_caller = false; - if (VC_PEER_TO_PEER == mVoiceType) + bool non_avatar_caller = VC_PEER_TO_PEER_AVALINE == mVoiceType; + + if (non_avatar_caller) { LLIMModel::LLIMSession* session = LLIMModel::instance().findIMSession(mSpeakerManager->getSessionID()); - non_avatar_caller = !session->mOtherParticipantIsAvatar; - if (non_avatar_caller) - { - mNonAvatarCaller->setSpeakerId(session->mOtherParticipantID); - mNonAvatarCaller->setName(session->mName); - } + mNonAvatarCaller->setSpeakerId(session->mOtherParticipantID); + mNonAvatarCaller->setName(session->mName); } mNonAvatarCaller->setVisible(non_avatar_caller); @@ -390,9 +390,17 @@ void LLCallFloater::updateTitle() title = getString("title_nearby"); break; case VC_PEER_TO_PEER: + case VC_PEER_TO_PEER_AVALINE: { + title = voice_channel->getSessionName(); + + if (VC_PEER_TO_PEER_AVALINE == mVoiceType) + { + title = LLTextUtil::formatPhoneNumber(title); + } + LLStringUtil::format_map_t args; - args["[NAME]"] = voice_channel->getSessionName(); + args["[NAME]"] = title; title = getString("title_peer_2_peer", args); } break; @@ -706,13 +714,28 @@ void LLCallFloater::removeVoiceRemoveTimer(const LLUUID& voice_speaker_id) bool LLCallFloater::validateSpeaker(const LLUUID& speaker_id) { - if (mVoiceType != VC_LOCAL_CHAT) - return true; + bool is_valid = true; + switch (mVoiceType) + { + case VC_LOCAL_CHAT: + { + // A nearby chat speaker is considered valid it it's known to LLVoiceClient (i.e. has enabled voice). + std::vector speakers; + get_voice_participants_uuids(speakers); + is_valid = std::find(speakers.begin(), speakers.end(), speaker_id) != speakers.end(); + } + break; + case VC_GROUP_CHAT: + // if participant had left this call before do not allow add her again. See EXT-4216. + // but if she Join she will be added into the list from the LLCallFloater::onChange() + is_valid = STATE_LEFT != getState(speaker_id); + break; + default: + // do nothing. required for Linux build + break; + } - // A nearby chat speaker is considered valid it it's known to LLVoiceClient (i.e. has enabled voice). - std::vector speakers; - get_voice_participants_uuids(speakers); - return std::find(speakers.begin(), speakers.end(), speaker_id) != speakers.end(); + return is_valid; } void LLCallFloater::connectToChannel(LLVoiceChannel* channel) @@ -765,91 +788,4 @@ void LLCallFloater::reset() mSpeakerManager = NULL; } -void reshape_floater(LLCallFloater* floater, S32 delta_height) -{ - // Try to update floater top side if it is docked(to bottom bar). - // Try to update floater bottom side or top side if it is un-docked. - // If world rect is too small, floater will not be reshaped at all. - - LLRect floater_rect = floater->getRect(); - LLRect world_rect = gViewerWindow->getWorldViewRectScaled(); - - // floater is docked to bottom bar - if(floater->isDocked()) - { - // can update floater top side - if(floater_rect.mTop + delta_height < world_rect.mTop) - { - floater_rect.set(floater_rect.mLeft, floater_rect.mTop + delta_height, - floater_rect.mRight, floater_rect.mBottom); - } - } - // floater is un-docked - else - { - // can update floater bottom side - if( floater_rect.mBottom - delta_height >= world_rect.mBottom ) - { - floater_rect.set(floater_rect.mLeft, floater_rect.mTop, - floater_rect.mRight, floater_rect.mBottom - delta_height); - } - // could not update floater bottom side, check if we can update floater top side - else if( floater_rect.mTop + delta_height < world_rect.mTop ) - { - floater_rect.set(floater_rect.mLeft, floater_rect.mTop + delta_height, - floater_rect.mRight, floater_rect.mBottom); - } - } - - floater->reshape(floater_rect.getWidth(), floater_rect.getHeight()); - floater->setRect(floater_rect); -} - -void LLCallFloater::reshapeToFitContent() -{ - const S32 ITEM_HEIGHT = getParticipantItemHeight(); - static const S32 MAX_VISIBLE_ITEMS = getMaxVisibleItems(); - - static S32 items_pad = mAvatarList->getItemsPad(); - S32 list_height = mAvatarList->getRect().getHeight(); - S32 items_height = mAvatarList->getItemsRect().getHeight(); - if(items_height <= 0) - { - // make "no one near" text visible - items_height = ITEM_HEIGHT + items_pad; - } - S32 max_list_height = MAX_VISIBLE_ITEMS * ITEM_HEIGHT + items_pad * (MAX_VISIBLE_ITEMS - 1); - max_list_height += 2* mAvatarList->getBorderWidth(); - - S32 delta = items_height - list_height; - // too many items, don't reshape floater anymore, let scroll bar appear. - if(items_height > max_list_height) - { - delta = max_list_height - list_height; - } - - reshape_floater(this, delta); -} - -S32 LLCallFloater::getParticipantItemHeight() -{ - std::vector items; - mAvatarList->getItems(items); - if(items.size() > 0) - { - return items[0]->getRect().getHeight(); - } - else - { - return getChild("non_avatar_caller")->getRect().getHeight(); - } -} - -S32 LLCallFloater::getMaxVisibleItems() -{ - S32 value = 5; // default value, in case convertToS32() fails. - LLStringUtil::convertToS32(getString("max_visible_items"), value); - return value; -} - //EOF diff --git a/indra/newview/llcallfloater.h b/indra/newview/llcallfloater.h index 8aba93fc43..096594aaa2 100644 --- a/indra/newview/llcallfloater.h +++ b/indra/newview/llcallfloater.h @@ -75,11 +75,6 @@ public: */ /*virtual*/ void onChange(); - /** - * Will reshape floater when participant list size changes - */ - /*virtual*/ S32 notifyParent(const LLSD& info); - static void sOnCurrentChannelChanged(const LLUUID& session_id); private: @@ -88,7 +83,8 @@ private: VC_LOCAL_CHAT, VC_GROUP_CHAT, VC_AD_HOC_CHAT, - VC_PEER_TO_PEER + VC_PEER_TO_PEER, + VC_PEER_TO_PEER_AVALINE }EVoiceControls; typedef enum e_speaker_state @@ -220,21 +216,6 @@ private: */ void reset(); - /** - * Reshapes floater to fit participant list height - */ - void reshapeToFitContent(); - - /** - * Returns height of participant list item - */ - S32 getParticipantItemHeight(); - - /** - * Returns predefined max visible participants. - */ - S32 getMaxVisibleItems(); - private: speaker_state_map_t mSpeakerStateMap; LLSpeakerMgr* mSpeakerManager; diff --git a/indra/newview/llchatbar.cpp b/indra/newview/llchatbar.cpp index 442dc660cd..b32a955038 100644 --- a/indra/newview/llchatbar.cpp +++ b/indra/newview/llchatbar.cpp @@ -48,7 +48,6 @@ #include "llcombobox.h" #include "llcommandhandler.h" // secondlife:///app/chat/ support #include "llviewercontrol.h" -#include "llfloaterchat.h" #include "llgesturemgr.h" #include "llkeyboard.h" #include "lllineeditor.h" @@ -548,7 +547,7 @@ void LLChatBar::onInputEditorFocusLost() // static void LLChatBar::onInputEditorGainFocus() { - LLFloaterChat::setHistoryCursorAndScrollToEnd(); + //LLFloaterChat::setHistoryCursorAndScrollToEnd(); } void LLChatBar::onClickSay( LLUICtrl* ctrl ) diff --git a/indra/newview/llchathistory.cpp b/indra/newview/llchathistory.cpp index 21cadda6e3..a46cd84b60 100644 --- a/indra/newview/llchathistory.cpp +++ b/indra/newview/llchathistory.cpp @@ -499,7 +499,24 @@ void LLChatHistory::appendMessage(const LLChat& chat, const bool use_plain_text_ style_params.font.name(font_name); style_params.font.size(font_size); style_params.font.style(input_append_params.font.style); - + + std::string prefix = chat.mText.substr(0, 4); + + //IRC styled /me messages. + bool irc_me = prefix == "/me " || prefix == "/me'"; + + // Delimiter after a name in header copy/past and in plain text mode + std::string delimiter = (chat.mChatType != CHAT_TYPE_SHOUT && chat.mChatType != CHAT_TYPE_WHISPER) + ? ": " + : " "; + + // Don't add any delimiter after name in irc styled messages + if (irc_me || chat.mChatStyle == CHAT_STYLE_IRC) + { + delimiter = LLStringUtil::null; + style_params.font.style = "ITALIC"; + } + if (use_plain_text_chat_history) { mEditor->appendText("[" + chat.mTimeStr + "] ", mEditor->getText().size() != 0, style_params); @@ -512,11 +529,11 @@ void LLChatHistory::appendMessage(const LLChat& chat, const bool use_plain_text_ LLStyle::Params link_params(style_params); link_params.fillFrom(LLStyleMap::instance().lookupAgent(chat.mFromID)); // Convert the name to a hotlink and add to message. - mEditor->appendText(chat.mFromName + ": ", false, link_params); + mEditor->appendText(chat.mFromName + delimiter, false, link_params); } else { - mEditor->appendText(chat.mFromName + ": ", false, style_params); + mEditor->appendText(chat.mFromName + delimiter, false, style_params); } } } @@ -560,39 +577,25 @@ void LLChatHistory::appendMessage(const LLChat& chat, const bool use_plain_text_ view->reshape(target_rect.getWidth(), view->getRect().getHeight()); view->setOrigin(target_rect.mLeft, view->getRect().mBottom); - std::string header_text = "[" + chat.mTimeStr + "] "; + std::string widget_associated_text = "\n[" + chat.mTimeStr + "] "; if (utf8str_trim(chat.mFromName).size() != 0 && chat.mFromName != SYSTEM_FROM) - header_text += chat.mFromName + ": "; + widget_associated_text += chat.mFromName + delimiter; - mEditor->appendWidget(p, header_text, false); + mEditor->appendWidget(p, widget_associated_text, false); mLastFromName = chat.mFromName; mLastFromID = chat.mFromID; mLastMessageTime = new_message_time; } - //Handle IRC styled /me messages. - std::string prefix = chat.mText.substr(0, 4); - if (prefix == "/me " || prefix == "/me'") - { - style_params.font.style = "ITALIC"; - if (chat.mFromName.size() > 0) - mEditor->appendText(chat.mFromName + " ", TRUE, style_params); - // Ensure that message ends with NewLine, to avoid losing of new lines - // while copy/paste from text chat. See EXT-3263. - mEditor->appendText(chat.mText.substr(4) + NEW_LINE, FALSE, style_params); - } - else + std::string message = irc_me ? chat.mText.substr(3) : chat.mText; + mEditor->appendText(message, FALSE, style_params); + mEditor->blockUndo(); + + // automatically scroll to end when receiving chat from myself + if (chat.mFromID == gAgentID) { - std::string message(chat.mText); - if ( message.size() > 0 && !LLStringOps::isSpace(message[message.size() - 1]) ) - { - // Ensure that message ends with NewLine, to avoid losing of new lines - // while copy/paste from text chat. See EXT-3263. - message += NEW_LINE; - } - mEditor->appendText(message, FALSE, style_params); + mEditor->setCursorAndScrollToEnd(); } - mEditor->blockUndo(); } void LLChatHistory::draw() @@ -606,3 +609,11 @@ void LLChatHistory::draw() LLUICtrl::draw(); } +void LLChatHistory::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + bool is_scrolled_to_end = mEditor->scrolledToEnd(); + LLUICtrl::reshape( width, height, called_from_parent ); + // update scroll + if (is_scrolled_to_end) + mEditor->setCursorAndScrollToEnd(); +} diff --git a/indra/newview/llchathistory.h b/indra/newview/llchathistory.h index 260015e2dc..f2d403f639 100644 --- a/indra/newview/llchathistory.h +++ b/indra/newview/llchathistory.h @@ -119,6 +119,7 @@ class LLChatHistory : public LLUICtrl */ void appendMessage(const LLChat& chat, const bool use_plain_text_chat_history = false, const LLStyle::Params& input_append_params = LLStyle::Params()); /*virtual*/ void clear(); + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); private: std::string mLastFromName; diff --git a/indra/newview/llchatitemscontainerctrl.cpp b/indra/newview/llchatitemscontainerctrl.cpp index 92df281307..f7f7ee83af 100644 --- a/indra/newview/llchatitemscontainerctrl.cpp +++ b/indra/newview/llchatitemscontainerctrl.cpp @@ -168,30 +168,30 @@ void LLNearbyChatToastPanel::init(LLSD& notification) msg_text->setText(std::string("")); - std::string str_sender; - - if(gAgentID != mFromID) + if ( notification["chat_style"].asInteger() != CHAT_STYLE_IRC ) + { + std::string str_sender; + str_sender = fromName; - else - str_sender = LLTrans::getString("You"); - str_sender+=" "; + str_sender+=" "; - //append user name - { - LLStyle::Params style_params_name; + //append user name + { + LLStyle::Params style_params_name; - LLColor4 userNameColor = LLUIColorTable::instance().getColor("ChatToastAgentNameColor"); + LLColor4 userNameColor = LLUIColorTable::instance().getColor("ChatToastAgentNameColor"); - style_params_name.color(userNameColor); - - std::string font_name = LLFontGL::nameFromFont(messageFont); - std::string font_style_size = LLFontGL::sizeFromFont(messageFont); - style_params_name.font.name(font_name); - style_params_name.font.size(font_style_size); - - msg_text->appendText(str_sender, FALSE, style_params_name); - + style_params_name.color(userNameColor); + + std::string font_name = LLFontGL::nameFromFont(messageFont); + std::string font_style_size = LLFontGL::sizeFromFont(messageFont); + style_params_name.font.name(font_name); + style_params_name.font.size(font_style_size); + + msg_text->appendText(str_sender, FALSE, style_params_name); + + } } //append text @@ -252,11 +252,33 @@ void LLNearbyChatToastPanel::onMouseEnter (S32 x, S32 y, MASK mask) } BOOL LLNearbyChatToastPanel::handleMouseDown (S32 x, S32 y, MASK mask) +{ + return LLPanel::handleMouseDown(x,y,mask); +} + +BOOL LLNearbyChatToastPanel::handleMouseUp (S32 x, S32 y, MASK mask) { if(mSourceType != CHAT_SOURCE_AGENT) - return LLPanel::handleMouseDown(x,y,mask); + return LLPanel::handleMouseUp(x,y,mask); + + LLChatMsgBox* text_box = getChild("msg_text", false); + S32 local_x = x - text_box->getRect().mLeft; + S32 local_y = y - text_box->getRect().mBottom; + + //if text_box process mouse up (ussually this is click on url) - we didn't show nearby_chat. + if (text_box->pointInView(local_x, local_y) ) + { + if (text_box->handleMouseUp(local_x,local_y,mask) == TRUE) + return TRUE; + else + { + LLFloaterReg::showInstance("nearby_chat",LLSD()); + return FALSE; + } + } + LLFloaterReg::showInstance("nearby_chat",LLSD()); - return LLPanel::handleMouseDown(x,y,mask); + return LLPanel::handleMouseUp(x,y,mask); } void LLNearbyChatToastPanel::setHeaderVisibility(EShowItemHeader e) diff --git a/indra/newview/llchatitemscontainerctrl.h b/indra/newview/llchatitemscontainerctrl.h index 0a85c52401..f4b8655054 100644 --- a/indra/newview/llchatitemscontainerctrl.h +++ b/indra/newview/llchatitemscontainerctrl.h @@ -68,6 +68,7 @@ public: void onMouseLeave (S32 x, S32 y, MASK mask); void onMouseEnter (S32 x, S32 y, MASK mask); BOOL handleMouseDown (S32 x, S32 y, MASK mask); + BOOL handleMouseUp (S32 x, S32 y, MASK mask); virtual BOOL postBuild(); diff --git a/indra/newview/llchiclet.cpp b/indra/newview/llchiclet.cpp index dc2e22f899..8da207f887 100644 --- a/indra/newview/llchiclet.cpp +++ b/indra/newview/llchiclet.cpp @@ -47,6 +47,7 @@ #include "llnotificationsutil.h" #include "lloutputmonitorctrl.h" #include "llscriptfloater.h" +#include "llspeakers.h" #include "lltextbox.h" #include "llvoiceclient.h" #include "llgroupmgr.h" @@ -453,6 +454,7 @@ LLIMChiclet::LLIMChiclet(const LLIMChiclet::Params& p) , mNewMessagesIcon(NULL) , mSpeakerCtrl(NULL) , mCounterCtrl(NULL) +, mChicletButton(NULL) { enableCounterControl(p.enable_counter); } @@ -540,6 +542,11 @@ void LLIMChiclet::toggleSpeakerControl() void LLIMChiclet::setCounter(S32 counter) { + if (mCounterCtrl->getCounter() == counter) + { + return; + } + mCounterCtrl->setCounter(counter); setShowCounter(counter); setShowNewMessagesIcon(counter); @@ -571,6 +578,11 @@ void LLIMChiclet::onMouseDown() setCounter(0); } +void LLIMChiclet::setToggleState(bool toggle) +{ + mChicletButton->setToggleState(toggle); +} + BOOL LLIMChiclet::handleMouseDown(S32 x, S32 y, MASK mask) { onMouseDown(); @@ -629,6 +641,7 @@ LLIMChiclet::EType LLIMChiclet::getIMSessionType(const LLUUID& session_id) LLIMP2PChiclet::Params::Params() : avatar_icon("avatar_icon") +, chiclet_button("chiclet_button") , unread_notifications("unread_notifications") , speaker("speaker") , new_message_icon("new_message_icon") @@ -641,6 +654,10 @@ LLIMP2PChiclet::LLIMP2PChiclet(const Params& p) , mChicletIconCtrl(NULL) , mPopupMenu(NULL) { + LLButton::Params button_params = p.chiclet_button; + mChicletButton = LLUICtrlFactory::create(button_params); + addChild(mChicletButton); + LLIconCtrl::Params new_msg_params = p.new_message_icon; mNewMessagesIcon = LLUICtrlFactory::create(new_msg_params); addChild(mNewMessagesIcon); @@ -755,6 +772,7 @@ void LLIMP2PChiclet::onMenuItemClicked(const LLSD& user_data) LLAdHocChiclet::Params::Params() : avatar_icon("avatar_icon") +, chiclet_button("chiclet_button") , unread_notifications("unread_notifications") , speaker("speaker") , new_message_icon("new_message_icon") @@ -768,6 +786,10 @@ LLAdHocChiclet::LLAdHocChiclet(const Params& p) , mChicletIconCtrl(NULL) , mPopupMenu(NULL) { + LLButton::Params button_params = p.chiclet_button; + mChicletButton = LLUICtrlFactory::create(button_params); + addChild(mChicletButton); + LLIconCtrl::Params new_msg_params = p.new_message_icon; mNewMessagesIcon = LLUICtrlFactory::create(new_msg_params); addChild(mNewMessagesIcon); @@ -882,6 +904,7 @@ BOOL LLAdHocChiclet::handleRightMouseDown(S32 x, S32 y, MASK mask) LLIMGroupChiclet::Params::Params() : group_icon("group_icon") +, chiclet_button("chiclet_button") , unread_notifications("unread_notifications") , speaker("speaker") , new_message_icon("new_message_icon") @@ -895,6 +918,10 @@ LLIMGroupChiclet::LLIMGroupChiclet(const Params& p) , mChicletIconCtrl(NULL) , mPopupMenu(NULL) { + LLButton::Params button_params = p.chiclet_button; + mChicletButton = LLUICtrlFactory::create(button_params); + addChild(mChicletButton); + LLIconCtrl::Params new_msg_params = p.new_message_icon; mNewMessagesIcon = LLUICtrlFactory::create(new_msg_params); addChild(mNewMessagesIcon); @@ -1267,10 +1294,12 @@ void LLChicletPanel::onChicletClick(LLUICtrl*ctrl,const LLSD¶m) void LLChicletPanel::removeChiclet(chiclet_list_t::iterator it) { - mScrollArea->removeChild(*it); + LLChiclet* chiclet = *it; + mScrollArea->removeChild(chiclet); mChicletList.erase(it); arrange(); + chiclet->die(); } void LLChicletPanel::removeChiclet(S32 index) @@ -1409,6 +1438,32 @@ S32 LLChicletPanel::notifyParent(const LLSD& info) return LLPanel::notifyParent(info); } +void LLChicletPanel::setChicletToggleState(const LLUUID& session_id, bool toggle) +{ + if(session_id.isNull()) + { + llwarns << "Null Session ID" << llendl; + } + + // toggle off all chiclets, except specified + S32 size = getChicletCount(); + for(int n = 0; n < size; ++n) + { + LLIMChiclet* chiclet = getChiclet(n); + if(chiclet && chiclet->getSessionId() != session_id) + { + chiclet->setToggleState(false); + } + } + + // toggle specified chiclet + LLIMChiclet* chiclet = findChiclet(session_id); + if(chiclet) + { + chiclet->setToggleState(toggle); + } +} + void LLChicletPanel::arrange() { if(mChicletList.empty()) @@ -1801,6 +1856,7 @@ LLChicletSpeakerCtrl::LLChicletSpeakerCtrl(const Params&p) LLScriptChiclet::Params::Params() : icon("icon") + , chiclet_button("chiclet_button") , new_message_icon("new_message_icon") { } @@ -1809,6 +1865,10 @@ LLScriptChiclet::LLScriptChiclet(const Params&p) : LLIMChiclet(p) , mChicletIconCtrl(NULL) { + LLButton::Params button_params = p.chiclet_button; + mChicletButton = LLUICtrlFactory::create(button_params); + addChild(mChicletButton); + LLIconCtrl::Params new_msg_params = p.new_message_icon; mNewMessagesIcon = LLUICtrlFactory::create(new_msg_params); addChild(mNewMessagesIcon); @@ -1857,6 +1917,7 @@ static const std::string INVENTORY_USER_OFFER ("UserGiveItem"); LLInvOfferChiclet::Params::Params() : icon("icon") + , chiclet_button("chiclet_button") , new_message_icon("new_message_icon") { } @@ -1865,6 +1926,10 @@ LLInvOfferChiclet::LLInvOfferChiclet(const Params&p) : LLIMChiclet(p) , mChicletIconCtrl(NULL) { + LLButton::Params button_params = p.chiclet_button; + mChicletButton = LLUICtrlFactory::create(button_params); + addChild(mChicletButton); + LLIconCtrl::Params new_msg_params = p.new_message_icon; mNewMessagesIcon = LLUICtrlFactory::create(new_msg_params); addChild(mNewMessagesIcon); diff --git a/indra/newview/llchiclet.h b/indra/newview/llchiclet.h index 3665e4d093..bb4846aa57 100644 --- a/indra/newview/llchiclet.h +++ b/indra/newview/llchiclet.h @@ -325,7 +325,7 @@ public: }; - /*virtual*/ ~LLIMChiclet() {}; + virtual ~LLIMChiclet() {}; /** * Sets IM session name. This name will be displayed in chiclet tooltip. @@ -422,6 +422,8 @@ public: */ virtual void onMouseDown(); + virtual void setToggleState(bool toggle); + protected: LLIMChiclet(const LLIMChiclet::Params& p); @@ -438,7 +440,7 @@ protected: LLIconCtrl* mNewMessagesIcon; LLChicletNotificationCounterCtrl* mCounterCtrl; LLChicletSpeakerCtrl* mSpeakerCtrl; - + LLButton* mChicletButton; /** the id of another participant, either an avatar id or a group id*/ LLUUID mOtherParticipantId; @@ -473,6 +475,8 @@ class LLIMP2PChiclet : public LLIMChiclet public: struct Params : public LLInitParam::Block { + Optional chiclet_button; + Optional avatar_icon; Optional unread_notifications; @@ -538,6 +542,8 @@ class LLAdHocChiclet : public LLIMChiclet public: struct Params : public LLInitParam::Block { + Optional chiclet_button; + Optional avatar_icon; Optional unread_notifications; @@ -614,6 +620,8 @@ public: struct Params : public LLInitParam::Block { + Optional chiclet_button; + Optional icon; Optional new_message_icon; @@ -656,6 +664,8 @@ public: struct Params : public LLInitParam::Block { + Optional chiclet_button; + Optional icon; Optional new_message_icon; @@ -697,6 +707,8 @@ public: struct Params : public LLInitParam::Block { + Optional chiclet_button; + Optional group_icon; Optional unread_notifications; @@ -1040,6 +1052,11 @@ public: S32 notifyParent(const LLSD& info); + /** + * Toggle chiclet by session id ON and toggle OFF all other chiclets. + */ + void setChicletToggleState(const LLUUID& session_id, bool toggle); + protected: LLChicletPanel(const Params&p); friend class LLUICtrlFactory; diff --git a/indra/newview/llcompilequeue.cpp b/indra/newview/llcompilequeue.cpp index eb9a2fec2f..47f1b7c9f5 100644 --- a/indra/newview/llcompilequeue.cpp +++ b/indra/newview/llcompilequeue.cpp @@ -59,7 +59,6 @@ #include "llbutton.h" #include "lldir.h" -#include "llfloaterchat.h" #include "llnotificationsutil.h" #include "llviewerstats.h" #include "llvfile.h" @@ -447,14 +446,20 @@ void LLFloaterCompileQueue::scriptArrived(LLVFS *vfs, const LLUUID& asset_id, if( LL_ERR_ASSET_REQUEST_NOT_IN_DATABASE == status ) { - LLChat chat(LLTrans::getString("CompileQueueScriptNotFound")); - LLFloaterChat::addChat(chat); + //TODO* CHAT: how to show this? + //LLSD args; + //args["MESSAGE"] = LLTrans::getString("CompileQueueScriptNotFound); + //LLNotificationsUtil::add("SystemMessage", args); + buffer = LLTrans::getString("CompileQueueProblemDownloading") + (": ") + data->mScriptName; } else if (LL_ERR_INSUFFICIENT_PERMISSIONS == status) { - LLChat chat(LLTrans::getString("CompileQueueInsufficientPermDownload")); - LLFloaterChat::addChat(chat); + //TODO* CHAT: how to show this? + //LLSD args; + //args["MESSAGE"] = LLTrans::getString("CompileQueueScriptNotFound); + //LLNotificationsUtil::add("SystemMessage", args); + buffer = LLTrans::getString("CompileQueueInsufficientPermFor") + (": ") + data->mScriptName; } else diff --git a/indra/newview/lldateutil.cpp b/indra/newview/lldateutil.cpp index 10b7935caf..abb2fdeb9a 100644 --- a/indra/newview/lldateutil.cpp +++ b/indra/newview/lldateutil.cpp @@ -44,15 +44,18 @@ static S32 DAYS_PER_MONTH_LEAP[] = static S32 days_from_month(S32 year, S32 month) { + llassert_always(1 <= month); + llassert_always(month <= 12); + if (year % 4 == 0 && year % 100 != 0) { // leap year - return DAYS_PER_MONTH_LEAP[month]; + return DAYS_PER_MONTH_LEAP[month - 1]; } else { - return DAYS_PER_MONTH_NOLEAP[month]; + return DAYS_PER_MONTH_NOLEAP[month - 1]; } } diff --git a/indra/newview/lldrawpoolbump.cpp b/indra/newview/lldrawpoolbump.cpp index 5f845c3721..03a8b108e2 100644 --- a/indra/newview/lldrawpoolbump.cpp +++ b/indra/newview/lldrawpoolbump.cpp @@ -1149,14 +1149,14 @@ void LLBumpImageList::onSourceLoaded( BOOL success, LLViewerTexture *src_vi, LLI if (!LLPipeline::sRenderDeferred) { bump->setExplicitFormat(GL_ALPHA8, GL_ALPHA); - bump->createGLTexture(bump->getDiscardLevel(), dst_image); + bump->createGLTexture(0, dst_image); } else { LLPointer nrm_image = new LLImageRaw(src->getWidth(), src->getHeight(), 4); generateNormalMapFromAlpha(src, nrm_image); bump->setExplicitFormat(GL_RGBA, GL_RGBA); - bump->createGLTexture(bump->getDiscardLevel(), nrm_image); + bump->createGLTexture(0, nrm_image); } diff --git a/indra/newview/lldrawpoolwlsky.cpp b/indra/newview/lldrawpoolwlsky.cpp index c14ca2473b..c7cd77cb65 100644 --- a/indra/newview/lldrawpoolwlsky.cpp +++ b/indra/newview/lldrawpoolwlsky.cpp @@ -159,15 +159,10 @@ void LLDrawPoolWLSky::renderStars(void) const // *NOTE: have to have bound the cloud noise texture already since register // combiners blending below requires something to be bound // and we might as well only bind once. - //gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); + gGL.getTexUnit(0)->enable(LLTexUnit::TT_TEXTURE); gPipeline.disableLights(); - - if (!LLPipeline::sReflectionRender) - { - glPointSize(2.f); - } - + // *NOTE: we divide by two here and GL_ALPHA_SCALE by two below to avoid // clamping and allow the star_alpha param to brighten the stars. bool error; @@ -175,16 +170,20 @@ void LLDrawPoolWLSky::renderStars(void) const star_alpha.mV[3] = LLWLParamManager::instance()->mCurParams.getFloat("star_brightness", error) / 2.f; llassert_always(!error); + gGL.getTexUnit(0)->bind(gSky.mVOSkyp->getBloomTex()); + + gGL.pushMatrix(); + glRotatef(gFrameTimeSeconds*0.01f, 0.f, 0.f, 1.f); // gl_FragColor.rgb = gl_Color.rgb; // gl_FragColor.a = gl_Color.a * star_alpha.a; - gGL.getTexUnit(0)->setTextureColorBlend(LLTexUnit::TBO_REPLACE, LLTexUnit::TBS_PREV_COLOR); - gGL.getTexUnit(0)->setTextureAlphaBlend(LLTexUnit::TBO_MULT_X2, LLTexUnit::TBS_PREV_ALPHA, LLTexUnit::TBS_CONST_ALPHA); + gGL.getTexUnit(0)->setTextureColorBlend(LLTexUnit::TBO_MULT, LLTexUnit::TBS_TEX_COLOR, LLTexUnit::TBS_VERT_COLOR); + gGL.getTexUnit(0)->setTextureAlphaBlend(LLTexUnit::TBO_MULT_X2, LLTexUnit::TBS_CONST_ALPHA, LLTexUnit::TBS_TEX_ALPHA); glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, star_alpha.mV); gSky.mVOWLSkyp->drawStars(); - glPointSize(1.f); - + gGL.popMatrix(); + // and disable the combiner states gGL.getTexUnit(0)->setTextureBlendType(LLTexUnit::TB_MULT); } diff --git a/indra/newview/lldrawpoolwlsky.h b/indra/newview/lldrawpoolwlsky.h index 7ff760ac39..9059f6382f 100644 --- a/indra/newview/lldrawpoolwlsky.h +++ b/indra/newview/lldrawpoolwlsky.h @@ -43,7 +43,7 @@ public: static const U32 SKY_VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | LLVertexBuffer::MAP_TEXCOORD0; static const U32 STAR_VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX | - LLVertexBuffer::MAP_COLOR; + LLVertexBuffer::MAP_COLOR | LLVertexBuffer::MAP_TEXCOORD0; LLDrawPoolWLSky(void); /*virtual*/ ~LLDrawPoolWLSky(); diff --git a/indra/newview/llfasttimerview.cpp b/indra/newview/llfasttimerview.cpp index effa57b1ef..4fa97e789b 100644 --- a/indra/newview/llfasttimerview.cpp +++ b/indra/newview/llfasttimerview.cpp @@ -314,7 +314,7 @@ void LLFastTimerView::draw() S32 left, top, right, bottom; S32 x, y, barw, barh, dx, dy; S32 texth, textw; - LLPointer box_imagep = LLUI::getUIImage("rounded_square.tga"); + LLPointer box_imagep = LLUI::getUIImage("Rounded_Square"); // Draw the window background gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); @@ -323,7 +323,9 @@ void LLFastTimerView::draw() S32 xleft = margin; S32 ytop = margin; - mAverageCyclesPerTimer = llround(lerp((F32)mAverageCyclesPerTimer, (F32)(LLFastTimer::sTimerCycles / (U64)LLFastTimer::sTimerCalls), 0.1f)); + mAverageCyclesPerTimer = LLFastTimer::sTimerCalls == 0 + ? 0 + : llround(lerp((F32)mAverageCyclesPerTimer, (F32)(LLFastTimer::sTimerCycles / (U64)LLFastTimer::sTimerCalls), 0.1f)); LLFastTimer::sTimerCycles = 0; LLFastTimer::sTimerCalls = 0; diff --git a/indra/newview/llfavoritesbar.cpp b/indra/newview/llfavoritesbar.cpp index 6ae6b4877a..fb94657278 100644 --- a/indra/newview/llfavoritesbar.cpp +++ b/indra/newview/llfavoritesbar.cpp @@ -483,6 +483,10 @@ BOOL LLFavoritesBarCtrl::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop, if (drop) { + if (mItems.empty()) + { + setLandingTab(NULL); + } handleNewFavoriteDragAndDrop(item, favorites_id, x, y); showDragMarker(FALSE); } @@ -508,14 +512,14 @@ void LLFavoritesBarCtrl::handleExistingFavoriteDragAndDrop(S32 x, S32 y) if (dest) { - updateItemsOrder(mItems, mDragItemId, dest->getLandmarkId()); + LLInventoryModel::updateItemsOrder(mItems, mDragItemId, dest->getLandmarkId()); } else { mItems.push_back(gInventory.getItem(mDragItemId)); } - saveItemsOrder(mItems); + gInventory.saveItemsOrder(mItems); LLToggleableMenu* menu = (LLToggleableMenu*) mPopupMenuHandle.get(); @@ -644,7 +648,7 @@ LLXMLNodePtr LLFavoritesBarCtrl::getButtonXMLNode() bool success = LLUICtrlFactory::getLayeredXMLNode("favorites_bar_button.xml", buttonXMLNode); if (!success) { - llwarns << "Unable to read xml file with button for Favorites Bar: favorites_bar_button.xml" << llendl; + llwarns << "Failed to create Favorites Bar button from favorites_bar_button.xml" << llendl; buttonXMLNode = NULL; } return buttonXMLNode; @@ -1193,25 +1197,6 @@ BOOL LLFavoritesBarCtrl::needToSaveItemsOrder(const LLInventoryModel::item_array return result; } -void LLFavoritesBarCtrl::saveItemsOrder(LLInventoryModel::item_array_t& items) -{ - int sortField = 0; - - // current order is saved by setting incremental values (1, 2, 3, ...) for the sort field - for (LLInventoryModel::item_array_t::iterator i = items.begin(); i != items.end(); ++i) - { - LLViewerInventoryItem* item = *i; - - item->setSortField(++sortField); - item->setComplete(TRUE); - item->updateServer(FALSE); - - gInventory.updateItem(item); - } - - gInventory.notifyObservers(); -} - LLInventoryModel::item_array_t::iterator LLFavoritesBarCtrl::findItemByUUID(LLInventoryModel::item_array_t& items, const LLUUID& id) { LLInventoryModel::item_array_t::iterator result = items.end(); @@ -1228,15 +1213,6 @@ LLInventoryModel::item_array_t::iterator LLFavoritesBarCtrl::findItemByUUID(LLIn return result; } -void LLFavoritesBarCtrl::updateItemsOrder(LLInventoryModel::item_array_t& items, const LLUUID& srcItemId, const LLUUID& destItemId) -{ - LLViewerInventoryItem* srcItem = gInventory.getItem(srcItemId); - LLViewerInventoryItem* destItem = gInventory.getItem(destItemId); - - items.erase(findItemByUUID(items, srcItem->getUUID())); - items.insert(findItemByUUID(items, destItem->getUUID()), srcItem); -} - void LLFavoritesBarCtrl::insertBeforeItem(LLInventoryModel::item_array_t& items, const LLUUID& beforeItemId, LLViewerInventoryItem* insertedItem) { LLViewerInventoryItem* beforeItem = gInventory.getItem(beforeItemId); diff --git a/indra/newview/llfavoritesbar.h b/indra/newview/llfavoritesbar.h index 9ac734baff..40dd551eef 100644 --- a/indra/newview/llfavoritesbar.h +++ b/indra/newview/llfavoritesbar.h @@ -126,16 +126,7 @@ private: // checks if the current order of the favorites items must be saved BOOL needToSaveItemsOrder(const LLInventoryModel::item_array_t& items); - // saves current order of the favorites items - void saveItemsOrder(LLInventoryModel::item_array_t& items); - - /* - * changes favorites items order by insertion of the item identified by srcItemId - * BEFORE the item identified by destItemId. both items must exist in items array. - */ - void updateItemsOrder(LLInventoryModel::item_array_t& items, const LLUUID& srcItemId, const LLUUID& destItemId); - - /* + /** * inserts an item identified by insertedItemId BEFORE an item identified by beforeItemId. * this function assumes that an item identified by insertedItemId doesn't exist in items array. */ diff --git a/indra/newview/llfirstuse.cpp b/indra/newview/llfirstuse.cpp index 7fd0e070be..b3fdf60b11 100644 --- a/indra/newview/llfirstuse.cpp +++ b/indra/newview/llfirstuse.cpp @@ -45,6 +45,7 @@ #include "llappviewer.h" #include "lltracker.h" +/* // static std::set LLFirstUse::sConfigVariables; @@ -75,7 +76,8 @@ void LLFirstUse::resetFirstUse() gWarningSettings.setBOOL(*iter, TRUE); } } - +*/ +/* // Called whenever the viewer detects that your balance went up void LLFirstUse::useBalanceIncrease(S32 delta) @@ -145,6 +147,8 @@ void LLFirstUse::useBuild() LLNotificationsUtil::add("FirstBuild"); } } + + */ /* // static void LLFirstUse::useLeftClickNoHit() @@ -157,6 +161,7 @@ void LLFirstUse::useLeftClickNoHit() } } */ +/* // static void LLFirstUse::useTeleport() { @@ -171,7 +176,7 @@ void LLFirstUse::useTeleport() } } } - +*/ // static void LLFirstUse::useOverrideKeys() { @@ -187,7 +192,7 @@ void LLFirstUse::useOverrideKeys() } } } - +/* // static void LLFirstUse::useAttach() { @@ -216,6 +221,7 @@ void LLFirstUse::useInventory() } } +*/ // static void LLFirstUse::useSandbox() @@ -230,7 +236,7 @@ void LLFirstUse::useSandbox() LLNotificationsUtil::add("FirstSandbox", args); } } - +/* // static void LLFirstUse::useFlexible() { @@ -277,3 +283,4 @@ void LLFirstUse::useMedia() //LLNotificationsUtil::add("FirstMedia"); } } +*/ diff --git a/indra/newview/llfirstuse.h b/indra/newview/llfirstuse.h index 7b4f9f516f..3c7551f6cb 100644 --- a/indra/newview/llfirstuse.h +++ b/indra/newview/llfirstuse.h @@ -79,6 +79,7 @@ object or from inventory. class LLFirstUse { public: +/* // Add a config variable to be reset on resetFirstUse() static void addConfigVariable(const std::string& var); @@ -97,11 +98,16 @@ public: static void useBuild(); // static void useLeftClickNoHit(); static void useTeleport(); +*/ static void useOverrideKeys(); +/* static void useAttach(); static void useAppearance(); static void useInventory(); + */ static void useSandbox(); + +/* static void useFlexible(); static void useDebugMenus(); static void useSculptedPrim(); @@ -109,6 +115,7 @@ public: protected: static std::set sConfigVariables; +*/ }; #endif diff --git a/indra/newview/llfloaterabout.cpp b/indra/newview/llfloaterabout.cpp index e80499688e..ef69f39ad2 100644 --- a/indra/newview/llfloaterabout.cpp +++ b/indra/newview/llfloaterabout.cpp @@ -167,20 +167,21 @@ BOOL LLFloaterAbout::postBuild() // Now build the various pieces support << getString("AboutHeader", args); - if (info.has("COMPILER")) - { - support << "\n\n" << getString("AboutCompiler", args); - } if (info.has("REGION")) { support << "\n\n" << getString("AboutPosition", args); } support << "\n\n" << getString("AboutSystem", args); + support << "\n"; if (info.has("GRAPHICS_DRIVER_VERSION")) { - support << "\n\n" << getString("AboutDriver", args); + support << "\n" << getString("AboutDriver", args); + } + support << "\n" << getString("AboutLibs", args); + if (info.has("COMPILER")) + { + support << "\n" << getString("AboutCompiler", args); } - support << "\n\n" << getString("AboutLibs", args); if (info.has("PACKETS_IN")) { support << '\n' << getString("AboutTraffic", args); @@ -193,11 +194,11 @@ BOOL LLFloaterAbout::postBuild() support_widget->blockUndo(); // Fix views - support_widget->setCursorPos(0); support_widget->setEnabled(FALSE); + support_widget->startOfDoc(); - credits_widget->setCursorPos(0); credits_widget->setEnabled(FALSE); + credits_widget->startOfDoc(); return TRUE; } @@ -268,7 +269,7 @@ LLSD LLFloaterAbout::getInfo() info["VIVOX_VERSION"] = gVoiceClient ? gVoiceClient->getAPIVersion() : LLTrans::getString("NotConnected"); // TODO: Implement media plugin version query - info["QT_WEBKIT_VERSION"] = "4.5.2 (version number hard-coded)"; + info["QT_WEBKIT_VERSION"] = "4.6 (version number hard-coded)"; if (gPacketsIn > 0) { diff --git a/indra/newview/llfloaterbulkpermission.cpp b/indra/newview/llfloaterbulkpermission.cpp index 538b44c056..5c3a54e34b 100644 --- a/indra/newview/llfloaterbulkpermission.cpp +++ b/indra/newview/llfloaterbulkpermission.cpp @@ -48,7 +48,6 @@ #include "llresmgr.h" #include "llbutton.h" #include "lldir.h" -#include "llfloaterchat.h" #include "llviewerstats.h" #include "lluictrlfactory.h" #include "llselectmgr.h" diff --git a/indra/newview/llfloatercamera.cpp b/indra/newview/llfloatercamera.cpp index 764aff68c9..9496e94780 100644 --- a/indra/newview/llfloatercamera.cpp +++ b/indra/newview/llfloatercamera.cpp @@ -144,6 +144,11 @@ void LLPanelCameraZoom::onSliderValueChanged() mSavedSliderVal = val; } +void activate_camera_tool() +{ + LLToolMgr::getInstance()->setTransientTool(LLToolCamera::getInstance()); +}; + // // Member functions // @@ -151,7 +156,7 @@ void LLPanelCameraZoom::onSliderValueChanged() /*static*/ bool LLFloaterCamera::inFreeCameraMode() { LLFloaterCamera* floater_camera = LLFloaterCamera::findInstance(); - if (floater_camera && floater_camera->mCurrMode == CAMERA_CTRL_MODE_FREE_CAMERA) + if (floater_camera && floater_camera->mCurrMode == CAMERA_CTRL_MODE_FREE_CAMERA && gAgent.getCameraMode() != CAMERA_MODE_MOUSELOOK) { return true; } @@ -177,27 +182,17 @@ void LLFloaterCamera::update() } -/*static*/ void LLFloaterCamera::updateIfNotInAvatarViewMode() -{ - LLFloaterCamera* floater_camera = LLFloaterCamera::findInstance(); - if (floater_camera && !floater_camera->inAvatarViewMode()) - { - floater_camera->update(); - } -} - - void LLFloaterCamera::toPrevMode() { switchMode(mPrevMode); } -/*static*/ void LLFloaterCamera::toPrevModeIfInAvatarViewMode() +/*static*/ void LLFloaterCamera::onLeavingMouseLook() { LLFloaterCamera* floater_camera = LLFloaterCamera::findInstance(); - if (floater_camera && floater_camera->inAvatarViewMode()) + if (floater_camera && floater_camera->inFreeCameraMode()) { - floater_camera->toPrevMode(); + activate_camera_tool(); } } @@ -325,7 +320,7 @@ void LLFloaterCamera::switchMode(ECameraControlMode mode) break; case CAMERA_CTRL_MODE_FREE_CAMERA: - LLToolMgr::getInstance()->setTransientTool(LLToolCamera::getInstance()); + activate_camera_tool(); break; case CAMERA_CTRL_MODE_AVATAR_VIEW: diff --git a/indra/newview/llfloatercamera.h b/indra/newview/llfloatercamera.h index 5d44b4944d..45d5e9a845 100644 --- a/indra/newview/llfloatercamera.h +++ b/indra/newview/llfloatercamera.h @@ -61,7 +61,7 @@ public: /* callback for camera presets changing */ static void onClickCameraPresets(const LLSD& param); - static void toPrevModeIfInAvatarViewMode(); + static void onLeavingMouseLook(); /** resets current camera mode to orbit mode */ static void resetCameraMode(); @@ -69,8 +69,6 @@ public: /* determines actual mode and updates ui */ void update(); - static void updateIfNotInAvatarViewMode(); - virtual void onOpen(const LLSD& key); virtual void onClose(bool app_quitting); diff --git a/indra/newview/llfloaterchat.cpp b/indra/newview/llfloaterchat.cpp index b9e0f928f1..cdb9b8edb8 100644 --- a/indra/newview/llfloaterchat.cpp +++ b/indra/newview/llfloaterchat.cpp @@ -37,8 +37,6 @@ #include "llviewerprecompiledheaders.h" -#include "llfloaterchat.h" - // project include #include "llagent.h" #include "llappviewer.h" @@ -190,7 +188,14 @@ void LLFloaterChat::addChatHistory(const LLChat& chat, bool log_to_file) { if (log_to_file && (gSavedPerAccountSettings.getBOOL("LogChat"))) { - LLLogChat::saveHistory("chat", chat.mFromName, chat.mFromID, chat.mText); + if (chat.mChatType != CHAT_TYPE_WHISPER && chat.mChatType != CHAT_TYPE_SHOUT) + { + LLLogChat::saveHistory("chat", chat.mFromName, chat.mFromID, chat.mText); + } + else + { + LLLogChat::saveHistory("chat", "", chat.mFromID, chat.mFromName + " " + chat.mText); + } } LLColor4 color = get_text_color(chat); @@ -306,27 +311,17 @@ void LLFloaterChat::onClickToggleShowMute(LLUICtrl* caller, void *data) } // Put a line of chat in all the right places -void LLFloaterChat::addChat(const LLChat& chat, BOOL from_instant_message, BOOL local_agent) +void LLFloaterChat::addChat(const LLChat& chat, BOOL local_agent) { triggerAlerts(chat.mText); // Add the sender to the list of people with which we've recently interacted. - if(chat.mSourceType == CHAT_SOURCE_AGENT && chat.mFromID.notNull()) - LLRecentPeople::instance().add(chat.mFromID); + // this is not the best place to add _all_ messages to recent list + // comment this for now, may remove later on code cleanup + //if(chat.mSourceType == CHAT_SOURCE_AGENT && chat.mFromID.notNull()) + // LLRecentPeople::instance().add(chat.mFromID); - bool add_chat = true; - bool log_chat = true; - if(from_instant_message) - { - if (!gSavedSettings.getBOOL("IMInChat")) - add_chat = false; - //log_chat = false; -} - - if (add_chat) - { - addChatHistory(chat, log_chat); - } + addChatHistory(chat, true); } // Moved from lltextparser.cpp to break llui/llaudio library dependency. diff --git a/indra/newview/llfloaterchat.h b/indra/newview/llfloaterchat.h index 84fc199bfa..4437a0a5c2 100644 --- a/indra/newview/llfloaterchat.h +++ b/indra/newview/llfloaterchat.h @@ -61,7 +61,7 @@ public: // *TODO:Skinning - move these to LLChat (or LLViewerChat?) // Add chat to console and history list. // Color based on source, type, distance. - static void addChat(const LLChat& chat, BOOL from_im = FALSE, BOOL local_agent = FALSE); + static void addChat(const LLChat& chat, BOOL local_agent = FALSE); // Add chat to history alone. static void addChatHistory(const LLChat& chat, bool log_to_file = true); diff --git a/indra/newview/llfloaterchatterbox.cpp b/indra/newview/llfloaterchatterbox.cpp index 9108cfb72b..84b423399e 100644 --- a/indra/newview/llfloaterchatterbox.cpp +++ b/indra/newview/llfloaterchatterbox.cpp @@ -38,7 +38,6 @@ #include "llfloaterreg.h" #include "llfloaterchatterbox.h" #include "lluictrlfactory.h" -#include "llfloaterchat.h" #include "llfloaterfriends.h" #include "llfloatergroups.h" #include "llviewercontrol.h" @@ -134,22 +133,6 @@ BOOL LLFloaterChatterBox::postBuild() addFloater(LLFloaterMyFriends::getInstance(), TRUE); } - if (gSavedSettings.getBOOL("ChatHistoryTornOff")) - { - LLFloaterChat* floater_chat = LLFloaterChat::getInstance(); - if(floater_chat) - { - // add then remove to set up relationship for re-attach - addFloater(floater_chat, FALSE); - removeFloater(floater_chat); - // reparent to floater view - gFloaterView->addChild(floater_chat); - } - } - else - { - addFloater(LLFloaterChat::getInstance(), FALSE); - } mTabContainer->lockTabs(); return TRUE; } @@ -230,8 +213,6 @@ void LLFloaterChatterBox::onOpen(const LLSD& key) //*TODO:Skinning show the session id associated with key if (key.asString() == "local") { - LLFloaterChat* chat = LLFloaterReg::findTypedInstance("chat"); - chat->openFloater(); } else if (key.isDefined()) { @@ -245,12 +226,6 @@ void LLFloaterChatterBox::onOpen(const LLSD& key) void LLFloaterChatterBox::onVisibilityChange ( const LLSD& new_visibility ) { - // HACK: potentially need to toggle console - LLFloaterChat* instance = LLFloaterChat::getInstance(); - if(instance) - { - instance->updateConsoleVisibility(); - } } void LLFloaterChatterBox::removeFloater(LLFloater* floaterp) @@ -349,8 +324,7 @@ LLFloater* LLFloaterChatterBox::getCurrentVoiceFloater() } if (LLVoiceChannelProximal::getInstance() == LLVoiceChannel::getCurrentVoiceChannel()) { - // show near me tab if in proximal channel - return LLFloaterChat::getInstance(); + return NULL; } else { diff --git a/indra/newview/llfloatergesture.cpp b/indra/newview/llfloatergesture.cpp index 5072bc8c82..de65c6f876 100644 --- a/indra/newview/llfloatergesture.cpp +++ b/indra/newview/llfloatergesture.cpp @@ -110,7 +110,7 @@ LLFloaterGesture::LLFloaterGesture(const LLSD& key) mCommitCallbackRegistrar.add("Gesture.Action.ToogleActiveState", boost::bind(&LLFloaterGesture::onActivateBtnClick, this)); mCommitCallbackRegistrar.add("Gesture.Action.ShowPreview", boost::bind(&LLFloaterGesture::onClickEdit, this)); - mCommitCallbackRegistrar.add("Gesture.Action.CopyPast", boost::bind(&LLFloaterGesture::onCopyPastAction, this, _2)); + mCommitCallbackRegistrar.add("Gesture.Action.CopyPaste", boost::bind(&LLFloaterGesture::onCopyPasteAction, this, _2)); mCommitCallbackRegistrar.add("Gesture.Action.SaveToCOF", boost::bind(&LLFloaterGesture::addToCurrentOutFit, this)); mEnableCallbackRegistrar.add("Gesture.EnableAction", boost::bind(&LLFloaterGesture::isActionEnabled, this, _2)); @@ -245,6 +245,7 @@ void LLFloaterGesture::refreshAll() void LLFloaterGesture::buildGestureList() { + S32 scroll_pos = mGestureList->getScrollPos(); std::vector selected_items; getSelectedIds(selected_items); LL_DEBUGS("Gesture")<< "Rebuilding gesture list "<< LL_ENDL; @@ -274,13 +275,14 @@ void LLFloaterGesture::buildGestureList() } } } + // attempt to preserve scroll position through re-builds - // since we do re-build any time anything dirties + // since we do re-build whenever something gets dirty for(std::vector::iterator it = selected_items.begin(); it != selected_items.end(); it++) { mGestureList->selectByID(*it); } - mGestureList->scrollToShowSelected(); + mGestureList->setScrollPos(scroll_pos); } void LLFloaterGesture::addGesture(const LLUUID& item_id , LLMultiGesture* gesture,LLCtrlListInterface * list ) @@ -415,7 +417,7 @@ void LLFloaterGesture::onClickPlay() LL_DEBUGS("Gesture")<<" Trying to play gesture id: "<< item_id <refresh(); mPanelMedia->refresh(); mPanelAccess->refresh(); + mPanelCovenant->refresh(); } @@ -2795,12 +2796,6 @@ LLPanelLandCovenant::~LLPanelLandCovenant() { } -BOOL LLPanelLandCovenant::postBuild() -{ - refresh(); - return TRUE; -} - // virtual void LLPanelLandCovenant::refresh() { diff --git a/indra/newview/llfloaterland.h b/indra/newview/llfloaterland.h index d7d02ba1a3..a4785e8f5b 100644 --- a/indra/newview/llfloaterland.h +++ b/indra/newview/llfloaterland.h @@ -391,7 +391,6 @@ class LLPanelLandCovenant public: LLPanelLandCovenant(LLSafeHandle& parcelp); virtual ~LLPanelLandCovenant(); - virtual BOOL postBuild(); void refresh(); static void updateCovenantText(const std::string& string); static void updateEstateName(const std::string& name); diff --git a/indra/newview/llfloatermediasettings.cpp b/indra/newview/llfloatermediasettings.cpp index 5cfd56193e..976af121ae 100644 --- a/indra/newview/llfloatermediasettings.cpp +++ b/indra/newview/llfloatermediasettings.cpp @@ -192,6 +192,9 @@ void LLFloaterMediaSettings::initValues( const LLSD& media_settings, bool editab sInstance->mPanelMediaSettingsGeneral->getValues( sInstance->mInitialValues ); sInstance->mPanelMediaSettingsSecurity->getValues( sInstance->mInitialValues ); sInstance->mPanelMediaSettingsPermissions->getValues( sInstance->mInitialValues ); + + sInstance->mApplyBtn->setEnabled(editable); + sInstance->mOKBtn->setEnabled(editable); } //////////////////////////////////////////////////////////////////////////////// @@ -266,8 +269,11 @@ const std::string LLFloaterMediaSettings::getHomeUrl() // virtual void LLFloaterMediaSettings::draw() { - // Set the enabled state of the "Apply" button if values changed - childSetEnabled( "Apply", haveValuesChanged() ); + if (NULL != mApplyBtn) + { + // Set the enabled state of the "Apply" button if values changed + mApplyBtn->setEnabled( haveValuesChanged() ); + } LLFloater::draw(); } diff --git a/indra/newview/llfloaterpreference.cpp b/indra/newview/llfloaterpreference.cpp index 11dd48056c..fc036cb354 100644 --- a/indra/newview/llfloaterpreference.cpp +++ b/indra/newview/llfloaterpreference.cpp @@ -51,7 +51,7 @@ #include "lldirpicker.h" #include "llfeaturemanager.h" #include "llfocusmgr.h" -#include "llfirstuse.h" +//#include "llfirstuse.h" #include "llfloaterreg.h" #include "llfloaterabout.h" #include "llfloaterhardwaresettings.h" @@ -185,8 +185,8 @@ void handleNameTagOptionChanged(const LLSD& newvalue); viewer_media_t get_web_media(); bool callback_clear_browser_cache(const LLSD& notification, const LLSD& response); -bool callback_skip_dialogs(const LLSD& notification, const LLSD& response, LLFloaterPreference* floater); -bool callback_reset_dialogs(const LLSD& notification, const LLSD& response, LLFloaterPreference* floater); +//bool callback_skip_dialogs(const LLSD& notification, const LLSD& response, LLFloaterPreference* floater); +//bool callback_reset_dialogs(const LLSD& notification, const LLSD& response, LLFloaterPreference* floater); void fractionFromDecimal(F32 decimal_val, S32& numerator, S32& denominator); @@ -236,7 +236,7 @@ void handleNameTagOptionChanged(const LLSD& newvalue) } } -bool callback_skip_dialogs(const LLSD& notification, const LLSD& response, LLFloaterPreference* floater) +/*bool callback_skip_dialogs(const LLSD& notification, const LLSD& response, LLFloaterPreference* floater) { S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if (0 == option && floater ) @@ -244,7 +244,7 @@ bool callback_skip_dialogs(const LLSD& notification, const LLSD& response, LLFlo if ( floater ) { floater->setAllIgnored(); - LLFirstUse::disableFirstUse(); + // LLFirstUse::disableFirstUse(); floater->buildPopupLists(); } } @@ -259,13 +259,13 @@ bool callback_reset_dialogs(const LLSD& notification, const LLSD& response, LLFl if ( floater ) { floater->resetAllIgnored(); - LLFirstUse::resetFirstUse(); + //LLFirstUse::resetFirstUse(); floater->buildPopupLists(); } } return false; } - +*/ void fractionFromDecimal(F32 decimal_val, S32& numerator, S32& denominator) { @@ -313,8 +313,8 @@ LLFloaterPreference::LLFloaterPreference(const LLSD& key) mCommitCallbackRegistrar.add("Pref.SelectSkin", boost::bind(&LLFloaterPreference::onSelectSkin, this)); mCommitCallbackRegistrar.add("Pref.VoiceSetKey", boost::bind(&LLFloaterPreference::onClickSetKey, this)); mCommitCallbackRegistrar.add("Pref.VoiceSetMiddleMouse", boost::bind(&LLFloaterPreference::onClickSetMiddleMouse, this)); - mCommitCallbackRegistrar.add("Pref.ClickSkipDialogs", boost::bind(&LLFloaterPreference::onClickSkipDialogs, this)); - mCommitCallbackRegistrar.add("Pref.ClickResetDialogs", boost::bind(&LLFloaterPreference::onClickResetDialogs, this)); +// mCommitCallbackRegistrar.add("Pref.ClickSkipDialogs", boost::bind(&LLFloaterPreference::onClickSkipDialogs, this)); +// mCommitCallbackRegistrar.add("Pref.ClickResetDialogs", boost::bind(&LLFloaterPreference::onClickResetDialogs, this)); mCommitCallbackRegistrar.add("Pref.ClickEnablePopup", boost::bind(&LLFloaterPreference::onClickEnablePopup, this)); mCommitCallbackRegistrar.add("Pref.ClickDisablePopup", boost::bind(&LLFloaterPreference::onClickDisablePopup, this)); mCommitCallbackRegistrar.add("Pref.LogPath", boost::bind(&LLFloaterPreference::onClickLogPath, this)); @@ -325,6 +325,8 @@ LLFloaterPreference::LLFloaterPreference(const LLSD& key) mCommitCallbackRegistrar.add("Pref.WindowedMod", boost::bind(&LLFloaterPreference::onCommitWindowedMode, this)); mCommitCallbackRegistrar.add("Pref.UpdateSliderText", boost::bind(&LLFloaterPreference::onUpdateSliderText,this, _1,_2)); mCommitCallbackRegistrar.add("Pref.AutoDetectAspect", boost::bind(&LLFloaterPreference::onCommitAutoDetectAspect, this)); + mCommitCallbackRegistrar.add("Pref.ParcelMediaAutoPlayEnable", boost::bind(&LLFloaterPreference::onCommitParcelMediaAutoPlayEnable, this)); + mCommitCallbackRegistrar.add("Pref.MediaEnabled", boost::bind(&LLFloaterPreference::onCommitMediaEnabled, this)); mCommitCallbackRegistrar.add("Pref.onSelectAspectRatio", boost::bind(&LLFloaterPreference::onKeystrokeAspectRatio, this)); mCommitCallbackRegistrar.add("Pref.QualityPerformance", boost::bind(&LLFloaterPreference::onChangeQuality, this, _2)); mCommitCallbackRegistrar.add("Pref.applyUIColor", boost::bind(&LLFloaterPreference::applyUIColor, this ,_1, _2)); @@ -601,8 +603,8 @@ void LLFloaterPreference::onBtnOK() apply(); closeFloater(false); - gSavedSettings.saveToFile( gSavedSettings.getString("ClientSettingsFile"), TRUE ); LLUIColorTable::instance().saveUserSettings(); + gSavedSettings.saveToFile( gSavedSettings.getString("ClientSettingsFile"), TRUE ); std::string crash_settings_filename = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, CRASH_SETTINGS_FILE); // save all settings, even if equals defaults gCrashSettings.saveToFile(crash_settings_filename, FALSE); @@ -986,6 +988,27 @@ void LLFloaterPreference::onCommitAutoDetectAspect() } } +void LLFloaterPreference::onCommitParcelMediaAutoPlayEnable() +{ + BOOL autoplay = getChild("autoplay_enabled")->get(); + + gSavedSettings.setBOOL(LLViewerMedia::AUTO_PLAY_MEDIA_SETTING, autoplay); + + lldebugs << "autoplay now = " << int(autoplay) << llendl; +} + +void LLFloaterPreference::onCommitMediaEnabled() +{ + LLCheckBoxCtrl *media_enabled_ctrl = getChild("media_enabled"); + bool enabled = media_enabled_ctrl->get(); + gSavedSettings.setBOOL("AudioStreamingVideo", enabled); + gSavedSettings.setBOOL("AudioStreamingMusic", enabled); + gSavedSettings.setBOOL("AudioStreamingMedia", enabled); + media_enabled_ctrl->setTentative(false); + // Update enabled state of the "autoplay" checkbox + getChild("autoplay_enabled")->setEnabled(enabled); +} + void LLFloaterPreference::refresh() { LLPanel::refresh(); @@ -1050,7 +1073,7 @@ void LLFloaterPreference::onClickSetMiddleMouse() // update the control right away since we no longer wait for apply getChild("modifier_combo")->onCommit(); } - +/* void LLFloaterPreference::onClickSkipDialogs() { LLNotificationsUtil::add("SkipShowNextTimeDialogs", LLSD(), LLSD(), boost::bind(&callback_skip_dialogs, _1, _2, this)); @@ -1060,6 +1083,7 @@ void LLFloaterPreference::onClickResetDialogs() { LLNotificationsUtil::add("ResetShowNextTimeDialogs", LLSD(), LLSD(), boost::bind(&callback_reset_dialogs, _1, _2, this)); } + */ void LLFloaterPreference::onClickEnablePopup() { @@ -1399,6 +1423,20 @@ BOOL LLPanelPreference::postBuild() refresh(); } + //////////////////////PanelPrivacy /////////////////// + if(hasChild("media_enabled")) + { + bool video_enabled = gSavedSettings.getBOOL("AudioStreamingVideo"); + bool music_enabled = gSavedSettings.getBOOL("AudioStreamingMusic"); + bool media_enabled = gSavedSettings.getBOOL("AudioStreamingMedia"); + bool enabled = video_enabled || music_enabled || media_enabled; + + LLCheckBoxCtrl *media_enabled_ctrl = getChild("media_enabled"); + media_enabled_ctrl->set(enabled); + media_enabled_ctrl->setTentative(!(video_enabled == music_enabled == media_enabled)); + getChild("autoplay_enabled")->setEnabled(enabled); + } + apply(); return true; } diff --git a/indra/newview/llfloaterpreference.h b/indra/newview/llfloaterpreference.h index 74a53d673c..6f382620ee 100644 --- a/indra/newview/llfloaterpreference.h +++ b/indra/newview/llfloaterpreference.h @@ -110,8 +110,8 @@ public: void onClickSetKey(); void setKey(KEY key); void onClickSetMiddleMouse(); - void onClickSkipDialogs(); - void onClickResetDialogs(); +// void onClickSkipDialogs(); +// void onClickResetDialogs(); void onClickEnablePopup(); void onClickDisablePopup(); void resetAllIgnored(); @@ -132,6 +132,8 @@ public: // void fractionFromDecimal(F32 decimal_val, S32& numerator, S32& denominator); void onCommitAutoDetectAspect(); + void onCommitParcelMediaAutoPlayEnable(); + void onCommitMediaEnabled(); void applyResolution(); void applyUIColor(LLUICtrl* ctrl, const LLSD& param); void getUIColor(LLUICtrl* ctrl, const LLSD& param); diff --git a/indra/newview/llfloaterregioninfo.cpp b/indra/newview/llfloaterregioninfo.cpp index c4b87c1b2d..03ff2cc370 100644 --- a/indra/newview/llfloaterregioninfo.cpp +++ b/indra/newview/llfloaterregioninfo.cpp @@ -406,6 +406,11 @@ LLPanelEstateCovenant* LLFloaterRegionInfo::getPanelCovenant() void LLFloaterRegionInfo::refreshFromRegion(LLViewerRegion* region) { + if (!region) + { + return; + } + // call refresh from region on all panels std::for_each( mInfoPanels.begin(), @@ -1598,7 +1603,7 @@ std::string all_estates_text() } else if (region && region->getOwner() == gAgent.getID()) { - return LLTrans::getString("AllEstatesYouOwn"); + return LLTrans::getString("RegionInfoAllEstatesYouOwn"); } else if (region && region->isEstateManager()) { diff --git a/indra/newview/llfloaterscriptlimits.cpp b/indra/newview/llfloaterscriptlimits.cpp index 3042fbc6ec..0964ad7f91 100644 --- a/indra/newview/llfloaterscriptlimits.cpp +++ b/indra/newview/llfloaterscriptlimits.cpp @@ -64,6 +64,8 @@ // summary which only shows available & correct information #define USE_SIMPLE_SUMMARY +const S32 SIZE_OF_ONE_KB = 1024; + LLFloaterScriptLimits::LLFloaterScriptLimits(const LLSD& seed) : LLFloater(seed) { @@ -130,7 +132,6 @@ void LLFloaterScriptLimits::refresh() } } - ///---------------------------------------------------------------------------- // Base class for panels ///---------------------------------------------------------------------------- @@ -331,6 +332,57 @@ void LLPanelScriptLimitsRegionMemory::setErrorStatus(U32 status, const std::stri llerrs << "Can't handle remote parcel request."<< " Http Status: "<< status << ". Reason : "<< reason<("scripts_list"); + std::vector::iterator id_itor; + for (id_itor = mObjectListItems.begin(); id_itor != mObjectListItems.end(); ++id_itor) + { + LLSD element = *id_itor; + if(element["owner_id"].asUUID() == id) + { + LLScrollListItem* item = list->getItem(element["id"].asUUID()); + + if(item) + { + item->getColumn(2)->setValue(LLSD(name)); + element["columns"][2]["value"] = name; + } + } + } + + // fill in the url's tab if needed, all urls must have memory so we can do it all here + LLFloaterScriptLimits* instance = LLFloaterReg::getTypedInstance("script_limits"); + if(instance) + { + LLTabContainer* tab = instance->getChild("scriptlimits_panels"); + LLPanelScriptLimitsRegionMemory* panel = (LLPanelScriptLimitsRegionMemory*)tab->getChild("script_limits_region_urls_panel"); + + LLScrollListCtrl *list = panel->getChild("scripts_list"); + std::vector::iterator id_itor; + for (id_itor = mObjectListItems.begin(); id_itor != mObjectListItems.end(); ++id_itor) + { + LLSD element = *id_itor; + if(element["owner_id"].asUUID() == id) + { + LLScrollListItem* item = list->getItem(element["id"].asUUID()); + + if(item) + { + item->getColumn(2)->setValue(LLSD(name)); + element["columns"][2]["value"] = name; + } + } + } + } +} + void LLPanelScriptLimitsRegionMemory::setRegionDetails(LLSD content) { LLScrollListCtrl *list = getChild("scripts_list"); @@ -345,22 +397,40 @@ void LLPanelScriptLimitsRegionMemory::setRegionDetails(LLSD content) S32 total_objects = 0; S32 total_size = 0; + std::vector names_requested; + for(S32 i = 0; i < number_parcels; i++) { std::string parcel_name = content["parcels"][i]["name"].asString(); - + LLUUID parcel_id = content["parcels"][i]["id"].asUUID(); S32 number_objects = content["parcels"][i]["objects"].size(); for(S32 j = 0; j < number_objects; j++) { - S32 size = content["parcels"][i]["objects"][j]["resources"]["memory"].asInteger() / 1024; + S32 size = content["parcels"][i]["objects"][j]["resources"]["memory"].asInteger() / SIZE_OF_ONE_KB; total_size += size; std::string name_buf = content["parcels"][i]["objects"][j]["name"].asString(); LLUUID task_id = content["parcels"][i]["objects"][j]["id"].asUUID(); + LLUUID owner_id = content["parcels"][i]["objects"][j]["owner_id"].asUUID(); + + std::string owner_buf; + + BOOL name_is_cached = gCacheName->getFullName(owner_id, owner_buf); + if(!name_is_cached) + { + if(std::find(names_requested.begin(), names_requested.end(), owner_id) == names_requested.end()) + { + names_requested.push_back(owner_id); + gCacheName->get(owner_id, TRUE, + boost::bind(&LLPanelScriptLimitsRegionMemory::onNameCache, + this, _1, _2, _3)); + } + } LLSD element; element["id"] = task_id; + element["owner_id"] = owner_id; element["columns"][0]["column"] = "size"; element["columns"][0]["value"] = llformat("%d", size); element["columns"][0]["font"] = "SANSSERIF"; @@ -368,18 +438,18 @@ void LLPanelScriptLimitsRegionMemory::setRegionDetails(LLSD content) element["columns"][1]["value"] = name_buf; element["columns"][1]["font"] = "SANSSERIF"; element["columns"][2]["column"] = "owner"; - element["columns"][2]["value"] = ""; + element["columns"][2]["value"] = owner_buf; element["columns"][2]["font"] = "SANSSERIF"; element["columns"][3]["column"] = "location"; element["columns"][3]["value"] = parcel_name; element["columns"][3]["font"] = "SANSSERIF"; - list->addElement(element); - mObjectListIDs.push_back(task_id); + list->addElement(element, ADD_SORTED); + mObjectListItems.push_back(element); total_objects++; } } - + mParcelMemoryUsed =total_size; mGotParcelMemoryUsed = TRUE; populateParcelMemoryText(); @@ -556,7 +626,7 @@ void LLPanelScriptLimitsRegionMemory::clearList() childSetValue("memory_used", LLSD(msg_empty_string)); childSetValue("parcels_listed", LLSD(msg_empty_string)); - mObjectListIDs.clear(); + mObjectListItems.clear(); } // static @@ -728,7 +798,7 @@ void LLPanelScriptLimitsRegionURLs::setRegionDetails(LLSD content) S32 total_objects = 0; S32 total_size = 0; - + for(S32 i = 0; i < number_parcels; i++) { std::string parcel_name = content["parcels"][i]["name"].asString(); @@ -744,6 +814,10 @@ void LLPanelScriptLimitsRegionURLs::setRegionDetails(LLSD content) std::string name_buf = content["parcels"][i]["objects"][j]["name"].asString(); LLUUID task_id = content["parcels"][i]["objects"][j]["id"].asUUID(); + LLUUID owner_id = content["parcels"][i]["objects"][j]["owner_id"].asUUID(); + + std::string owner_buf; + gCacheName->getFullName(owner_id, owner_buf); //dont care if this fails as the memory tab will request and fill the field LLSD element; @@ -755,14 +829,14 @@ void LLPanelScriptLimitsRegionURLs::setRegionDetails(LLSD content) element["columns"][1]["value"] = name_buf; element["columns"][1]["font"] = "SANSSERIF"; element["columns"][2]["column"] = "owner"; - element["columns"][2]["value"] = ""; + element["columns"][2]["value"] = owner_buf; element["columns"][2]["font"] = "SANSSERIF"; element["columns"][3]["column"] = "location"; element["columns"][3]["value"] = parcel_name; element["columns"][3]["font"] = "SANSSERIF"; list->addElement(element); - mObjectListIDs.push_back(task_id); + mObjectListItems.push_back(element); total_objects++; } } @@ -868,7 +942,7 @@ void LLPanelScriptLimitsRegionURLs::clearList() childSetValue("urls_used", LLSD(msg_empty_string)); childSetValue("parcels_listed", LLSD(msg_empty_string)); - mObjectListIDs.clear(); + mObjectListItems.clear(); } // static @@ -982,7 +1056,7 @@ void LLPanelScriptLimitsAttachment::setAttachmentDetails(LLSD content) S32 size = 0; if(content["attachments"][i]["objects"][j]["resources"].has("memory")) { - size = content["attachments"][i]["objects"][j]["resources"]["memory"].asInteger(); + size = content["attachments"][i]["objects"][j]["resources"]["memory"].asInteger() / SIZE_OF_ONE_KB; } S32 urls = 0; if(content["attachments"][i]["objects"][j]["resources"].has("urls")) @@ -1059,3 +1133,4 @@ void LLPanelScriptLimitsAttachment::onClickRefresh(void* userdata) return; } } + diff --git a/indra/newview/llfloaterscriptlimits.h b/indra/newview/llfloaterscriptlimits.h index 88239136e3..7e2b536eb6 100644 --- a/indra/newview/llfloaterscriptlimits.h +++ b/indra/newview/llfloaterscriptlimits.h @@ -54,12 +54,12 @@ public: // from LLPanel virtual void refresh(); - + private: - + LLFloaterScriptLimits(const LLSD& seed); ~LLFloaterScriptLimits(); - + protected: LLTabContainer* mTab; @@ -167,13 +167,17 @@ public: private: + void onNameCache( const LLUUID& id, + const std::string& first_name, + const std::string& last_name); + LLUUID mParcelId; BOOL mGotParcelMemoryUsed; BOOL mGotParcelMemoryMax; S32 mParcelMemoryMax; S32 mParcelMemoryUsed; - std::vector mObjectListIDs; + std::vector mObjectListItems; protected: @@ -218,7 +222,7 @@ private: S32 mParcelURLsMax; S32 mParcelURLsUsed; - std::vector mObjectListIDs; + std::vector mObjectListItems; protected: diff --git a/indra/newview/llfloatersearch.cpp b/indra/newview/llfloatersearch.cpp index c6d9fee630..a7401fdb6f 100644 --- a/indra/newview/llfloatersearch.cpp +++ b/indra/newview/llfloatersearch.cpp @@ -32,6 +32,9 @@ */ #include "llviewerprecompiledheaders.h" + +#include "llcommandhandler.h" +#include "llfloaterreg.h" #include "llfloatersearch.h" #include "llmediactrl.h" #include "lllogininstance.h" @@ -41,6 +44,42 @@ #include "llviewercontrol.h" #include "llweb.h" +// support secondlife:///app/search/{CATEGORY}/{QUERY} SLapps +class LLSearchHandler : public LLCommandHandler +{ +public: + // requires trusted browser to trigger + LLSearchHandler() : LLCommandHandler("search", UNTRUSTED_THROTTLE) { } + bool handle(const LLSD& tokens, const LLSD& query_map, LLMediaCtrl* web) + { + const size_t parts = tokens.size(); + + // get the (optional) category for the search + std::string category; + if (parts > 0) + { + category = tokens[0].asString(); + } + + // get the (optional) search string + std::string search_text; + if (parts > 1) + { + search_text = tokens[1].asString(); + } + + // create the LLSD arguments for the search floater + LLSD args; + args["category"] = category; + args["id"] = LLURI::unescape(search_text); + + // open the search floater and perform the requested search + LLFloaterReg::showInstance("search", args); + return true; + } +}; +LLSearchHandler gSearchHandler; + LLFloaterSearch::LLFloaterSearch(const LLSD& key) : LLFloater(key), LLViewerMediaObserver(), diff --git a/indra/newview/llfloatersettingsdebug.cpp b/indra/newview/llfloatersettingsdebug.cpp index 8979575ef7..a6ffa5ec09 100644 --- a/indra/newview/llfloatersettingsdebug.cpp +++ b/indra/newview/llfloatersettingsdebug.cpp @@ -34,7 +34,7 @@ #include "llfloatersettingsdebug.h" #include "llfloater.h" #include "lluictrlfactory.h" -#include "llfirstuse.h" +//#include "llfirstuse.h" #include "llcombobox.h" #include "llspinctrl.h" #include "llcolorswatch.h" diff --git a/indra/newview/llfloatersnapshot.cpp b/indra/newview/llfloatersnapshot.cpp index f53b62e490..afb58c9407 100644 --- a/indra/newview/llfloatersnapshot.cpp +++ b/indra/newview/llfloatersnapshot.cpp @@ -2078,8 +2078,10 @@ void LLFloaterSnapshot::draw() { if(previewp->getThumbnailImage()) { + LLRect thumbnail_rect = getChild("thumbnail_placeholder")->getRect(); + S32 offset_x = (getRect().getWidth() - previewp->getThumbnailWidth()) / 2 ; - S32 offset_y = getRect().getHeight() - 205 + (90 - previewp->getThumbnailHeight()) / 2 ; + S32 offset_y = thumbnail_rect.mBottom + (thumbnail_rect.getHeight() - previewp->getThumbnailHeight()) / 2 ; glMatrixMode(GL_MODELVIEW); gl_draw_scaled_image(offset_x, offset_y, diff --git a/indra/newview/llfloatertools.cpp b/indra/newview/llfloatertools.cpp index babef5b63d..241497aeaf 100644 --- a/indra/newview/llfloatertools.cpp +++ b/indra/newview/llfloatertools.cpp @@ -1134,7 +1134,8 @@ void LLFloaterTools::getMediaState() } // XXX DISABLE this for now, because when the fetch finally // does come in, the state of this floater doesn't properly - // update. This needs more thought. + // update. Re-selecting fixes the problem, but there is + // contention as to whether this is a sufficient solution. // if (object->isMediaDataBeingFetched()) // { // LL_INFOS("LLFloaterTools: media") @@ -1221,10 +1222,10 @@ void LLFloaterTools::getMediaState() mNeedMediaTitle = false; } - childSetEnabled("media_tex", bool_has_media & editable); - childSetEnabled( "edit_media", bool_has_media & editable ); - childSetEnabled( "delete_media", bool_has_media & editable ); - childSetEnabled( "add_media", ( ! bool_has_media ) & editable ); + childSetEnabled("media_tex", bool_has_media && editable); + childSetEnabled( "edit_media", bool_has_media && LLFloaterMediaSettings::getInstance()->mIdenticalHasMediaInfo && editable ); + childSetEnabled( "delete_media", bool_has_media && editable ); + childSetEnabled( "add_media", ( ! bool_has_media ) && editable ); // TODO: display a list of all media on the face - use 'identical' flag } else // not all face has media but at least one does. @@ -1252,7 +1253,7 @@ void LLFloaterTools::getMediaState() } childSetEnabled("media_tex", TRUE); - childSetEnabled( "edit_media", TRUE); + childSetEnabled( "edit_media", LLFloaterMediaSettings::getInstance()->mIdenticalHasMediaInfo); childSetEnabled( "delete_media", TRUE); childSetEnabled( "add_media", FALSE ); } @@ -1269,18 +1270,15 @@ void LLFloaterTools::getMediaState() // called when a user wants to add media to a prim or prim face void LLFloaterTools::onClickBtnAddMedia() { - // check for the edit tool and now many faces are selected - LLTool *tool = LLToolMgr::getInstance()->getCurrentTool(); - if((tool != LLToolFace::getInstance()) || LLSelectMgr::getInstance()->getSelection()->isMultipleTESelected()) + // check if multiple faces are selected + if(LLSelectMgr::getInstance()->getSelection()->isMultipleTESelected()) { - LLNotificationsUtil::add("MultipleFacesSelected",LLSD(), LLSD(), multipleFacesSelectedConfirm); - + LLNotificationsUtil::add("MultipleFacesSelected", LLSD(), LLSD(), multipleFacesSelectedConfirm); } else { onClickBtnEditMedia(); } - } // static @@ -1423,7 +1421,7 @@ void LLFloaterTools::updateMediaSettings() return mMediaEntry.getControls(); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_controls(default_media_data); identical = selected_objects->getSelectedTEValue( &func_controls, value_u8 ); @@ -1446,7 +1444,7 @@ void LLFloaterTools::updateMediaSettings() return mMediaEntry.getFirstClickInteract(); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_first_click(default_media_data); identical = selected_objects->getSelectedTEValue( &func_first_click, value_bool ); @@ -1469,7 +1467,7 @@ void LLFloaterTools::updateMediaSettings() return mMediaEntry.getHomeURL(); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_home_url(default_media_data); identical = selected_objects->getSelectedTEValue( &func_home_url, value_str ); @@ -1492,7 +1490,7 @@ void LLFloaterTools::updateMediaSettings() return mMediaEntry.getCurrentURL(); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_current_url(default_media_data); identical = selected_objects->getSelectedTEValue( &func_current_url, value_str ); @@ -1516,7 +1514,7 @@ void LLFloaterTools::updateMediaSettings() return mMediaEntry.getAutoZoom(); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_auto_zoom(default_media_data); identical = selected_objects->getSelectedTEValue( &func_auto_zoom, value_bool ); @@ -1539,7 +1537,7 @@ void LLFloaterTools::updateMediaSettings() return mMediaEntry.getAutoPlay(); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_auto_play(default_media_data); identical = selected_objects->getSelectedTEValue( &func_auto_play, value_bool ); @@ -1563,7 +1561,7 @@ void LLFloaterTools::updateMediaSettings() return mMediaEntry.getAutoScale();; }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_auto_scale(default_media_data); identical = selected_objects->getSelectedTEValue( &func_auto_scale, value_bool ); @@ -1586,7 +1584,7 @@ void LLFloaterTools::updateMediaSettings() return mMediaEntry.getAutoLoop(); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_auto_loop(default_media_data); identical = selected_objects->getSelectedTEValue( &func_auto_loop, value_bool ); @@ -1609,7 +1607,7 @@ void LLFloaterTools::updateMediaSettings() return mMediaEntry.getWidthPixels(); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_width_pixels(default_media_data); identical = selected_objects->getSelectedTEValue( &func_width_pixels, value_int ); @@ -1632,7 +1630,7 @@ void LLFloaterTools::updateMediaSettings() return mMediaEntry.getHeightPixels(); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_height_pixels(default_media_data); identical = selected_objects->getSelectedTEValue( &func_height_pixels, value_int ); @@ -1655,7 +1653,7 @@ void LLFloaterTools::updateMediaSettings() return mMediaEntry.getAltImageEnable(); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_enable_alt_image(default_media_data); identical = selected_objects->getSelectedTEValue( &func_enable_alt_image, value_bool ); @@ -1678,7 +1676,7 @@ void LLFloaterTools::updateMediaSettings() return 0 != ( mMediaEntry.getPermsInteract() & LLMediaEntry::PERM_OWNER ); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_perms_owner_interact(default_media_data); identical = selected_objects->getSelectedTEValue( &func_perms_owner_interact, value_bool ); @@ -1701,7 +1699,7 @@ void LLFloaterTools::updateMediaSettings() return 0 != ( mMediaEntry.getPermsControl() & LLMediaEntry::PERM_OWNER ); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_perms_owner_control(default_media_data); identical = selected_objects ->getSelectedTEValue( &func_perms_owner_control, value_bool ); @@ -1724,7 +1722,7 @@ void LLFloaterTools::updateMediaSettings() return 0 != ( mMediaEntry.getPermsInteract() & LLMediaEntry::PERM_GROUP ); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_perms_group_interact(default_media_data); identical = selected_objects->getSelectedTEValue( &func_perms_group_interact, value_bool ); @@ -1747,7 +1745,7 @@ void LLFloaterTools::updateMediaSettings() return 0 != ( mMediaEntry.getPermsControl() & LLMediaEntry::PERM_GROUP ); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_perms_group_control(default_media_data); identical = selected_objects->getSelectedTEValue( &func_perms_group_control, value_bool ); @@ -1770,7 +1768,7 @@ void LLFloaterTools::updateMediaSettings() return 0 != ( mMediaEntry.getPermsInteract() & LLMediaEntry::PERM_ANYONE ); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_perms_anyone_interact(default_media_data); identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &func_perms_anyone_interact, value_bool ); @@ -1793,7 +1791,7 @@ void LLFloaterTools::updateMediaSettings() return 0 != ( mMediaEntry.getPermsControl() & LLMediaEntry::PERM_ANYONE ); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_perms_anyone_control(default_media_data); identical = selected_objects->getSelectedTEValue( &func_perms_anyone_control, value_bool ); @@ -1816,7 +1814,7 @@ void LLFloaterTools::updateMediaSettings() return mMediaEntry.getWhiteListEnable(); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_whitelist_enable(default_media_data); identical = selected_objects->getSelectedTEValue( &func_whitelist_enable, value_bool ); @@ -1839,7 +1837,7 @@ void LLFloaterTools::updateMediaSettings() return mMediaEntry.getWhiteList(); }; - const LLMediaEntry & mMediaEntry; + const LLMediaEntry &mMediaEntry; } func_whitelist_urls(default_media_data); identical = selected_objects->getSelectedTEValue( &func_whitelist_urls, value_vector_str ); diff --git a/indra/newview/llfloatervoicedevicesettings.cpp b/indra/newview/llfloatervoicedevicesettings.cpp index 43024a4bd0..638c9f1b8c 100644 --- a/indra/newview/llfloatervoicedevicesettings.cpp +++ b/indra/newview/llfloatervoicedevicesettings.cpp @@ -110,32 +110,33 @@ void LLPanelVoiceDeviceSettings::draw() LLPanel::draw(); - F32 voice_power = gVoiceClient->tuningGetEnergy(); - S32 discrete_power = 0; - - if (!is_in_tuning_mode) - { - discrete_power = 0; - } - else - { - discrete_power = llmin(4, llfloor((voice_power / LLVoiceClient::OVERDRIVEN_POWER_LEVEL) * 4.f)); - } - if (is_in_tuning_mode) { - for(S32 power_bar_idx = 0; power_bar_idx < 5; power_bar_idx++) + const S32 num_bars = 5; + F32 voice_power = gVoiceClient->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++) { std::string view_name = llformat("%s%d", "bar", power_bar_idx); LLView* bar_view = getChild(view_name); if (bar_view) { + gl_rect_2d(bar_view->getRect(), LLColor4::grey, TRUE); + + LLColor4 color; if (power_bar_idx < discrete_power) { - LLColor4 color = (power_bar_idx >= 3) ? LLUIColorTable::instance().getColor("OverdrivenColor") : LLUIColorTable::instance().getColor("SpeakingColor"); - gl_rect_2d(bar_view->getRect(), color, TRUE); + color = (power_bar_idx >= 3) ? LLUIColorTable::instance().getColor("OverdrivenColor") : LLUIColorTable::instance().getColor("SpeakingColor"); + } + else + { + color = LLUIColorTable::instance().getColor("PanelFocusBackgroundColor"); } - gl_rect_2d(bar_view->getRect(), LLColor4::grey, FALSE); + + LLRect color_rect = bar_view->getRect(); + color_rect.stretch(-1); + gl_rect_2d(color_rect, color, TRUE); } } } @@ -276,7 +277,10 @@ void LLPanelVoiceDeviceSettings::initialize() void LLPanelVoiceDeviceSettings::cleanup() { - gVoiceClient->tuningStop(); + if (gVoiceClient) + { + gVoiceClient->tuningStop(); + } LLVoiceChannel::resume(); } diff --git a/indra/newview/llfloaterworldmap.cpp b/indra/newview/llfloaterworldmap.cpp index 98f9171237..f4d4ea3553 100644 --- a/indra/newview/llfloaterworldmap.cpp +++ b/indra/newview/llfloaterworldmap.cpp @@ -47,7 +47,7 @@ #include "llviewercontrol.h" #include "llcommandhandler.h" #include "lldraghandle.h" -#include "llfirstuse.h" +//#include "llfirstuse.h" #include "llfloaterreg.h" // getTypedInstance() #include "llfocusmgr.h" #include "llinventorymodel.h" @@ -100,8 +100,6 @@ enum EPanDirection // Values in pixels per region static const F32 ZOOM_MAX = 128.f; -static const F32 SIM_COORD_DEFAULT = 128.f; - //--------------------------------------------------------------------------- // Globals //--------------------------------------------------------------------------- @@ -118,9 +116,12 @@ public: { if (params.size() == 0) { - return false; + // support the secondlife:///app/worldmap SLapp + LLFloaterReg::showInstance("world_map", "center"); + return true; } + // support the secondlife:///app/worldmap/{LOCATION}/{COORDS} SLapp const std::string region_name = params[0].asString(); S32 x = (params.size() > 1) ? params[1].asInteger() : 128; S32 y = (params.size() > 2) ? params[2].asInteger() : 128; @@ -189,7 +190,8 @@ LLFloaterWorldMap::LLFloaterWorldMap(const LLSD& key) mInventory(NULL), mInventoryObserver(NULL), mFriendObserver(NULL), - mCompletingRegionName(""), + mCompletingRegionName(), + mCompletingRegionPos(), mWaitingForTracker(FALSE), mIsClosing(FALSE), mSetToUserPosition(TRUE), @@ -205,7 +207,6 @@ LLFloaterWorldMap::LLFloaterWorldMap(const LLSD& key) mCommitCallbackRegistrar.add("WMap.AvatarCombo", boost::bind(&LLFloaterWorldMap::onAvatarComboCommit, this)); mCommitCallbackRegistrar.add("WMap.Landmark", boost::bind(&LLFloaterWorldMap::onLandmarkComboCommit, this)); mCommitCallbackRegistrar.add("WMap.SearchResult", boost::bind(&LLFloaterWorldMap::onCommitSearchResult, this)); - mCommitCallbackRegistrar.add("WMap.CommitLocation", boost::bind(&LLFloaterWorldMap::onCommitLocation, this)); mCommitCallbackRegistrar.add("WMap.GoHome", boost::bind(&LLFloaterWorldMap::onGoHome, this)); mCommitCallbackRegistrar.add("WMap.Teleport", boost::bind(&LLFloaterWorldMap::onClickTeleportBtn, this)); mCommitCallbackRegistrar.add("WMap.ShowTarget", boost::bind(&LLFloaterWorldMap::onShowTargetBtn, this)); @@ -316,7 +317,7 @@ void LLFloaterWorldMap::onOpen(const LLSD& key) adjustZoomSliderBounds(); // Could be first show - LLFirstUse::useMap(); + //LLFirstUse::useMap(); // Start speculative download of landmarks const LLUUID landmark_folder_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_LANDMARK); @@ -664,10 +665,6 @@ void LLFloaterWorldMap::updateLocation() S32 agent_y = llround( (F32)fmod( agentPos.mdV[VY], (F64)REGION_WIDTH_METERS ) ); S32 agent_z = llround( (F32)agentPos.mdV[VZ] ); - childSetValue("spin x", LLSD(agent_x) ); - childSetValue("spin y", LLSD(agent_y) ); - childSetValue("spin z", LLSD(agent_z) ); - // Set the current SLURL mSLURL = LLSLURL::buildSLURL(agent_sim_name, agent_x, agent_y, agent_z); } @@ -699,9 +696,6 @@ void LLFloaterWorldMap::updateLocation() 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 ); - childSetValue("spin x", LLSD(region_x) ); - childSetValue("spin y", LLSD(region_y) ); - childSetValue("spin z", LLSD((F32)pos_global.mdV[VZ]) ); // simNameFromPosGlobal can fail, so don't give the user an invalid SLURL if ( gotSimName ) @@ -733,9 +727,11 @@ void LLFloaterWorldMap::trackURL(const std::string& region_name, S32 x_coord, S3 { // fill in UI based on URL gFloaterWorldMap->childSetValue("location", region_name); - childSetValue("spin x", LLSD((F32)x_coord)); - childSetValue("spin y", LLSD((F32)y_coord)); - childSetValue("spin z", LLSD((F32)z_coord)); + + // Save local coords to highlight position after region global + // position is returned. + gFloaterWorldMap->mCompletingRegionPos.set( + (F32)x_coord, (F32)y_coord, (F32)z_coord); // pass sim name to combo box gFloaterWorldMap->mCompletingRegionName = region_name; @@ -899,18 +895,6 @@ void LLFloaterWorldMap::clearLocationSelection(BOOL clear_ui) { list->operateOnAll(LLCtrlListInterface::OP_DELETE); } - if (!childHasKeyboardFocus("spin x")) - { - childSetValue("spin x", SIM_COORD_DEFAULT); - } - if (!childHasKeyboardFocus("spin y")) - { - childSetValue("spin y", SIM_COORD_DEFAULT); - } - if (!childHasKeyboardFocus("spin z")) - { - childSetValue("spin z", 0); - } LLWorldMap::getInstance()->cancelTracking(); mCompletingRegionName = ""; } @@ -1466,21 +1450,6 @@ void LLFloaterWorldMap::updateSims(bool found_null_sim) } } -void LLFloaterWorldMap::onCommitLocation() -{ - LLTracker::ETrackingStatus tracking_status = LLTracker::getTrackingStatus(); - if ( LLTracker::TRACKING_LOCATION == tracking_status) - { - LLVector3d pos_global = LLTracker::getTrackedPositionGlobal(); - F64 local_x = childGetValue("spin x"); - F64 local_y = childGetValue("spin y"); - F64 local_z = childGetValue("spin z"); - pos_global.mdV[VX] += -fmod(pos_global.mdV[VX], 256.0) + local_x; - pos_global.mdV[VY] += -fmod(pos_global.mdV[VY], 256.0) + local_y; - pos_global.mdV[VZ] = local_z; - trackLocation(pos_global); - } -} void LLFloaterWorldMap::onCommitSearchResult() { @@ -1503,12 +1472,19 @@ void LLFloaterWorldMap::onCommitSearchResult() if (info->isName(sim_name)) { LLVector3d pos_global = info->getGlobalOrigin(); - F64 local_x = childGetValue("spin x"); - F64 local_y = childGetValue("spin y"); - F64 local_z = childGetValue("spin z"); - pos_global.mdV[VX] += local_x; - pos_global.mdV[VY] += local_y; - pos_global.mdV[VZ] = local_z; + + const F64 SIM_COORD_DEFAULT = 128.0; + LLVector3 pos_local(SIM_COORD_DEFAULT, SIM_COORD_DEFAULT, 0.0f); + + // Did this value come from a trackURL() request? + if (!mCompletingRegionPos.isExactlyZero()) + { + pos_local = mCompletingRegionPos; + mCompletingRegionPos.clear(); + } + pos_global.mdV[VX] += (F64)pos_local.mV[VX]; + pos_global.mdV[VY] += (F64)pos_local.mV[VY]; + pos_global.mdV[VZ] = (F64)pos_local.mV[VZ]; childSetValue("location", sim_name); trackLocation(pos_global); diff --git a/indra/newview/llfloaterworldmap.h b/indra/newview/llfloaterworldmap.h index 7feebb583d..00f5e788fb 100644 --- a/indra/newview/llfloaterworldmap.h +++ b/indra/newview/llfloaterworldmap.h @@ -148,7 +148,6 @@ protected: void updateSearchEnabled(); void onLocationFocusChanged( LLFocusableElement* ctrl ); void onLocationCommit(); - void onCommitLocation(); void onCommitSearchResult(); void cacheLandmarkPosition(); @@ -170,6 +169,10 @@ private: LLFriendObserver* mFriendObserver; std::string mCompletingRegionName; + // Local position from trackURL() request, used to select final + // position once region lookup complete. + LLVector3 mCompletingRegionPos; + std::string mLastRegionName; BOOL mWaitingForTracker; diff --git a/indra/newview/llfolderview.cpp b/indra/newview/llfolderview.cpp index 112b23d2df..a63fb73032 100644 --- a/indra/newview/llfolderview.cpp +++ b/indra/newview/llfolderview.cpp @@ -78,7 +78,7 @@ ///---------------------------------------------------------------------------- const S32 RENAME_WIDTH_PAD = 4; -const S32 RENAME_HEIGHT_PAD = 2; +const S32 RENAME_HEIGHT_PAD = 1; const S32 AUTO_OPEN_STACK_DEPTH = 16; const S32 MIN_ITEM_WIDTH_VISIBLE = LLFolderViewItem::ICON_WIDTH + LLFolderViewItem::ICON_PAD @@ -206,7 +206,9 @@ LLFolderView::LLFolderView(const Params& p) mAutoOpenCandidate = NULL; mAutoOpenTimer.stop(); mKeyboardSelection = FALSE; - static LLUICachedControl indentation("FolderIndentation", 0); + const LLFolderViewItem::Params& item_params = + LLUICtrlFactory::getDefaultParams(); + S32 indentation = item_params.folder_indentation(); mIndentation = -indentation; // children start at indentation 0 gIdleCallbacks.addFunction(idle, this); @@ -395,7 +397,7 @@ S32 LLFolderView::arrange( S32* unused_width, S32* unused_height, S32 filter_gen getRoot()->getFilter()->getShowFolderState(); S32 total_width = LEFT_PAD; - S32 running_height = mDebugFilters ? llceil(sSmallFont->getLineHeight()) : 0; + S32 running_height = mDebugFilters ? llceil(LLFontGL::getFontMonospace()->getLineHeight()) : 0; S32 target_height = running_height; S32 parent_item_height = getRect().getHeight(); @@ -569,6 +571,8 @@ LLFolderViewItem* LLFolderView::getCurSelectedItem( void ) BOOL LLFolderView::setSelection(LLFolderViewItem* selection, BOOL openitem, BOOL take_keyboard_focus) { + mSignalSelectCallback = take_keyboard_focus ? SIGNAL_KEYBOARD_FOCUS : SIGNAL_NO_KEYBOARD_FOCUS; + if( selection == this ) { return FALSE; @@ -596,8 +600,6 @@ BOOL LLFolderView::setSelection(LLFolderViewItem* selection, BOOL openitem, llassert(mSelectedItems.size() <= 1); - mSignalSelectCallback = take_keyboard_focus ? SIGNAL_KEYBOARD_FOCUS : SIGNAL_NO_KEYBOARD_FOCUS; - return rv; } @@ -820,10 +822,11 @@ void LLFolderView::clearSelection() mSelectThisID.setNull(); } -BOOL LLFolderView::getSelectionList(std::set &selection) +BOOL LLFolderView::getSelectionList(std::set &selection) const { - selected_items_t::iterator item_it; - for (item_it = mSelectedItems.begin(); item_it != mSelectedItems.end(); ++item_it) + for (selected_items_t::const_iterator item_it = mSelectedItems.begin(); + item_it != mSelectedItems.end(); + ++item_it) { selection.insert((*item_it)->getListener()->getUUID()); } @@ -866,8 +869,8 @@ void LLFolderView::draw() { std::string current_filter_string = llformat("Current Filter: %d, Least Filter: %d, Auto-accept Filter: %d", mFilter->getCurrentGeneration(), mFilter->getMinRequiredGeneration(), mFilter->getMustPassGeneration()); - sSmallFont->renderUTF8(current_filter_string, 0, 2, - getRect().getHeight() - sSmallFont->getLineHeight(), LLColor4(0.5f, 0.5f, 0.8f, 1.f), + LLFontGL::getFontMonospace()->renderUTF8(current_filter_string, 0, 2, + getRect().getHeight() - LLFontGL::getFontMonospace()->getLineHeight(), LLColor4(0.5f, 0.5f, 0.8f, 1.f), LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, NULL, FALSE ); } @@ -1882,8 +1885,8 @@ void LLFolderView::scrollToShowItem(LLFolderViewItem* item, const LLRect& constr S32 icon_height = mIcon.isNull() ? 0 : mIcon->getHeight(); S32 label_height = llround(getLabelFontForStyle(mLabelStyle)->getLineHeight()); - // when navigating with keyboard, only move top of folders on screen, otherwise show whole folder - S32 max_height_to_show = mScrollContainer->hasFocus() ? (llmax( icon_height, label_height ) + ICON_PAD) : local_rect.getHeight(); + // when navigating with keyboard, only move top of opened folder on screen, otherwise show whole folder + S32 max_height_to_show = item->isOpen() && mScrollContainer->hasFocus() ? (llmax( icon_height, label_height ) + ICON_PAD) : local_rect.getHeight(); // get portion of item that we want to see... LLRect item_local_rect = LLRect(item->getIndentation(), @@ -2218,10 +2221,9 @@ void LLFolderView::updateRenamerPosition() { if(mRenameItem) { - LLFontGL* font = getLabelFontForStyle(mLabelStyle); - - S32 x = ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD - 1 + mRenameItem->getIndentation(); - S32 y = llfloor(mRenameItem->getRect().getHeight() - font->getLineHeight()-2); + // See also LLFolderViewItem::draw() + S32 x = ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + mRenameItem->getIndentation(); + S32 y = mRenameItem->getRect().getHeight() - mRenameItem->getItemHeight() - RENAME_HEIGHT_PAD; mRenameItem->localPointToScreen( x, y, &x, &y ); screenPointToLocal( x, y, &x, &y ); mRenamer->setOrigin( x, y ); @@ -2233,7 +2235,7 @@ void LLFolderView::updateRenamerPosition() } S32 width = llmax(llmin(mRenameItem->getRect().getWidth() - x, scroller_rect.getWidth() - x - getRect().mLeft), MINIMUM_RENAMER_WIDTH); - S32 height = llfloor(font->getLineHeight() + RENAME_HEIGHT_PAD); + S32 height = mRenameItem->getItemHeight() - RENAME_HEIGHT_PAD; mRenamer->reshape( width, height, TRUE ); } } diff --git a/indra/newview/llfolderview.h b/indra/newview/llfolderview.h index 2598af4df4..89e1865e35 100644 --- a/indra/newview/llfolderview.h +++ b/indra/newview/llfolderview.h @@ -162,7 +162,7 @@ public: virtual S32 extendSelection(LLFolderViewItem* selection, LLFolderViewItem* last_selected, LLDynamicArray& items); - virtual BOOL getSelectionList(std::set &selection); + virtual BOOL getSelectionList(std::set &selection) const; // make sure if ancestor is selected, descendents are not void sanitizeSelection(); diff --git a/indra/newview/llfoldervieweventlistener.h b/indra/newview/llfoldervieweventlistener.h index 473d0be912..d6c4459e6f 100644 --- a/indra/newview/llfoldervieweventlistener.h +++ b/indra/newview/llfoldervieweventlistener.h @@ -62,6 +62,7 @@ public: virtual PermissionMask getPermissionMask() const = 0; virtual LLFolderType::EType getPreferredType() const = 0; virtual LLPointer getIcon() const = 0; + virtual LLPointer getOpenIcon() const { return getIcon(); } virtual LLFontGL::StyleFlags getLabelStyle() const = 0; virtual std::string getLabelSuffix() const = 0; virtual void openItem( void ) = 0; diff --git a/indra/newview/llfolderviewitem.cpp b/indra/newview/llfolderviewitem.cpp index 9d54aafd67..4b48626b22 100644 --- a/indra/newview/llfolderviewitem.cpp +++ b/indra/newview/llfolderviewitem.cpp @@ -51,11 +51,10 @@ /// Class LLFolderViewItem ///---------------------------------------------------------------------------- +static LLDefaultChildRegistry::Register r("folder_view_item"); + // statics std::map LLFolderViewItem::sFonts; // map of styles to fonts -const LLFontGL* LLFolderViewItem::sSmallFont = NULL; -LLUIImagePtr LLFolderViewItem::sArrowImage; -LLUIImagePtr LLFolderViewItem::sBoxImage; // only integers can be initialized in header const F32 LLFolderViewItem::FOLDER_CLOSE_TIME_CONSTANT = 0.02f; @@ -84,33 +83,34 @@ LLFontGL* LLFolderViewItem::getLabelFontForStyle(U8 style) //static void LLFolderViewItem::initClass() { - sSmallFont = LLFontGL::getFontMonospace(); - sArrowImage = LLUI::getUIImage("folder_arrow.tga"); - sBoxImage = LLUI::getUIImage("rounded_square.tga"); } //static void LLFolderViewItem::cleanupClass() { sFonts.clear(); - sArrowImage = NULL; - sBoxImage = NULL; } // NOTE: Optimize this, we call it a *lot* when opening a large inventory LLFolderViewItem::Params::Params() -: icon("icon"), - folder_arrow_image("folder_arrow_image", LLUI::getUIImage("folder_arrow.tga")), - selection_image("selection_image", LLUI::getUIImage("rounded_square.tga")) +: icon(), + icon_open(), + root(), + listener(), + folder_arrow_image("folder_arrow_image"), + folder_indentation("folder_indentation"), + selection_image("selection_image"), + item_height("item_height"), + item_top_pad("item_top_pad"), + creation_date() { mouse_opaque(true); follows.flags(FOLLOWS_LEFT|FOLLOWS_TOP|FOLLOWS_RIGHT); - // JAMESDEBUG tab_stop(false); } // Default constructor -LLFolderViewItem::LLFolderViewItem(LLFolderViewItem::Params p) +LLFolderViewItem::LLFolderViewItem(const LLFolderViewItem::Params& p) : LLView(p), mLabelWidth(0), mLabelWidthDirty(false), @@ -121,6 +121,7 @@ LLFolderViewItem::LLFolderViewItem(LLFolderViewItem::Params p) mLabelStyle( LLFontGL::NORMAL ), mHasVisibleChildren(FALSE), mIndentation(0), + mItemHeight(p.item_height), mNumDescendantsSelected(0), mPassedFilter(FALSE), mLastFilterGeneration(-1), @@ -134,8 +135,6 @@ LLFolderViewItem::LLFolderViewItem(LLFolderViewItem::Params p) mIcon(p.icon), mIconOpen(p.icon_open), mListener(p.listener), - mArrowImage(p.folder_arrow_image), - mBoxImage(p.selection_image), mHidden(false), mShowLoadStatus(false) { @@ -392,10 +391,11 @@ BOOL LLFolderViewItem::addToFolder(LLFolderViewFolder* folder, LLFolderView* roo // makes sure that this view and it's children are the right size. S32 LLFolderViewItem::arrange( S32* width, S32* height, S32 filter_generation) { - static LLUICachedControl indentation("FolderIndentation", 0); + const Params& p = LLUICtrlFactory::getDefaultParams(); + S32 indentation = p.folder_indentation(); + // Only indent deeper items in hierarchy mIndentation = (getParentFolder() - && getParentFolder()->getParentFolder() - && getParentFolder()->getParentFolder()->getParentFolder()) + && getParentFolder()->getParentFolder() ) ? mParentFolder->getIndentation() + indentation : 0; if (mLabelWidthDirty) @@ -421,9 +421,10 @@ S32 LLFolderViewItem::getItemHeight() { if (mHidden) return 0; - S32 icon_height = mIcon->getHeight(); - S32 label_height = llround(getLabelFontForStyle(mLabelStyle)->getLineHeight()); - return llmax( icon_height, label_height ) + ICON_PAD; + //S32 icon_height = mIcon->getHeight(); + //S32 label_height = llround(getLabelFontForStyle(mLabelStyle)->getLineHeight()); + //return llmax( icon_height, label_height ) + ICON_PAD; + return mItemHeight; } void LLFolderViewItem::filter( LLInventoryFilter& filter) @@ -829,11 +830,16 @@ void LLFolderViewItem::draw() static LLUIColor sFgColor = LLUIColorTable::instance().getColor("MenuItemEnabledColor", DEFAULT_WHITE); static LLUIColor sHighlightBgColor = LLUIColorTable::instance().getColor("MenuItemHighlightBgColor", DEFAULT_WHITE); static LLUIColor sHighlightFgColor = LLUIColorTable::instance().getColor("MenuItemHighlightFgColor", DEFAULT_WHITE); + static LLUIColor sFocusOutlineColor = + LLUIColorTable::instance().getColor("InventoryFocusOutlineColor", DEFAULT_WHITE); static LLUIColor sFilterBGColor = LLUIColorTable::instance().getColor("FilterBackgroundColor", DEFAULT_WHITE); static LLUIColor sFilterTextColor = LLUIColorTable::instance().getColor("FilterTextColor", DEFAULT_WHITE); static LLUIColor sSuffixColor = LLUIColorTable::instance().getColor("InventoryItemSuffixColor", DEFAULT_WHITE); static LLUIColor sSearchStatusColor = LLUIColorTable::instance().getColor("InventorySearchStatusColor", DEFAULT_WHITE); + const Params& default_params = LLUICtrlFactory::getDefaultParams(); + const S32 TOP_PAD = default_params.item_top_pad; + bool possibly_has_children = false; bool up_to_date = mListener && mListener->isUpToDate(); if((up_to_date && hasVisibleChildren() ) || // we fetched our children and some of them have passed the filter... @@ -843,13 +849,13 @@ void LLFolderViewItem::draw() } if(/*mControlLabel[0] != '\0' && */possibly_has_children) { - if (sArrowImage) - { - gl_draw_scaled_rotated_image(mIndentation, getRect().getHeight() - ARROW_SIZE - TEXT_PAD, - ARROW_SIZE, ARROW_SIZE, mControlLabelRotation, sArrowImage->getImage(), sFgColor); - } + LLUIImage* arrow_image = default_params.folder_arrow_image; + gl_draw_scaled_rotated_image( + mIndentation, getRect().getHeight() - ARROW_SIZE - TEXT_PAD - TOP_PAD, + ARROW_SIZE, ARROW_SIZE, mControlLabelRotation, arrow_image->getImage(), sFgColor); } + // See also LLFolderView::updateRenamerPosition() F32 text_left = (F32)(ARROW_SIZE + TEXT_PAD + ICON_WIDTH + ICON_PAD + mIndentation); LLFontGL* font = getLabelFontForStyle(mLabelStyle); @@ -857,6 +863,10 @@ void LLFolderViewItem::draw() // If we have keyboard focus, draw selection filled BOOL show_context = getRoot()->getShowSelectionContext(); BOOL filled = show_context || (getRoot()->getParentPanel()->hasFocus()); + const S32 FOCUS_LEFT = 1; + S32 focus_top = getRect().getHeight(); + S32 focus_bottom = getRect().getHeight() - mItemHeight; + bool folder_open = (getRect().getHeight() > mItemHeight + 4); // always render "current" item, only render other selected items if // mShowSingleSelection is FALSE @@ -864,7 +874,6 @@ void LLFolderViewItem::draw() { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); LLColor4 bg_color = sHighlightBgColor; - //const S32 TRAILING_PAD = 5; // It just looks better with this. if (!mIsCurSelection) { // do time-based fade of extra objects @@ -882,35 +891,35 @@ void LLFolderViewItem::draw() } gl_rect_2d( - 0, - getRect().getHeight(), + FOCUS_LEFT, + focus_top, getRect().getWidth() - 2, - llfloor(getRect().getHeight() - font->getLineHeight() - ICON_PAD), + focus_bottom, bg_color, filled); if (mIsCurSelection) { gl_rect_2d( - 0, - getRect().getHeight(), + FOCUS_LEFT, + focus_top, getRect().getWidth() - 2, - llfloor(getRect().getHeight() - font->getLineHeight() - ICON_PAD), - sHighlightFgColor, FALSE); + focus_bottom, + sFocusOutlineColor, FALSE); } - if (getRect().getHeight() > llround(font->getLineHeight()) + ICON_PAD + 4) + if (folder_open) { gl_rect_2d( - 0, - llfloor(getRect().getHeight() - font->getLineHeight() - ICON_PAD) - 4, + FOCUS_LEFT, + focus_bottom + 1, // overlap with bottom edge of above rect getRect().getWidth() - 2, - 2, - sHighlightFgColor, FALSE); + 0, + sFocusOutlineColor, FALSE); if (show_context) { gl_rect_2d( - 0, - llfloor(getRect().getHeight() - font->getLineHeight() - ICON_PAD) - 4, + FOCUS_LEFT, + focus_bottom + 1, getRect().getWidth() - 2, - 2, + 0, sHighlightBgColor, TRUE); } } @@ -919,32 +928,32 @@ void LLFolderViewItem::draw() { gGL.getTexUnit(0)->unbind(LLTexUnit::TT_TEXTURE); gl_rect_2d( - 0, - getRect().getHeight(), + FOCUS_LEFT, + focus_top, getRect().getWidth() - 2, - llfloor(getRect().getHeight() - font->getLineHeight() - ICON_PAD), + focus_bottom, sHighlightBgColor, FALSE); - - if (getRect().getHeight() > llround(font->getLineHeight()) + ICON_PAD + 2) + if (folder_open) { gl_rect_2d( - 0, - llfloor(getRect().getHeight() - font->getLineHeight() - ICON_PAD) - 2, + FOCUS_LEFT, + focus_bottom + 1, // overlap with bottom edge of above rect getRect().getWidth() - 2, - 2, + 0, sHighlightBgColor, FALSE); } mDragAndDropTarget = FALSE; } + S32 icon_x = mIndentation + ARROW_SIZE + TEXT_PAD; // First case is used for open folders if (!mIconOpen.isNull() && (llabs(mControlLabelRotation) > 80)) { - mIconOpen->draw(mIndentation + ARROW_SIZE + TEXT_PAD, getRect().getHeight() - mIcon->getHeight()); + mIconOpen->draw(icon_x, getRect().getHeight() - mIconOpen->getHeight() - TOP_PAD + 1); } else if(mIcon) { - mIcon->draw(mIndentation + ARROW_SIZE + TEXT_PAD, getRect().getHeight() - mIcon->getHeight()); + mIcon->draw(icon_x, getRect().getHeight() - mIcon->getHeight() - TOP_PAD + 1); } if (!mLabel.empty()) @@ -953,7 +962,7 @@ void LLFolderViewItem::draw() BOOL debug_filters = getRoot()->getDebugFilters(); LLColor4 color = ( (mIsSelected && filled) ? sHighlightFgColor : sFgColor ); F32 right_x; - F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)TEXT_PAD; + F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)TEXT_PAD - (F32)TOP_PAD; if (debug_filters) { @@ -963,7 +972,8 @@ void LLFolderViewItem::draw() } LLColor4 filter_color = mLastFilterGeneration >= getRoot()->getFilter()->getCurrentGeneration() ? LLColor4(0.5f, 0.8f, 0.5f, 1.f) : LLColor4(0.8f, 0.5f, 0.5f, 1.f); - sSmallFont->renderUTF8(mStatusText, 0, text_left, y, filter_color, + LLFontGL::getFontMonospace()->renderUTF8( + mStatusText, 0, text_left, y, filter_color, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, S32_MAX, S32_MAX, &right_x, FALSE ); text_left = right_x; @@ -1004,7 +1014,7 @@ void LLFolderViewItem::draw() S32_MAX, S32_MAX, &right_x, FALSE ); } - if (sBoxImage.notNull() && mStringMatchOffset != std::string::npos) + if (mStringMatchOffset != std::string::npos) { // don't draw backgrounds for zero-length strings S32 filter_string_length = getRoot()->getFilterSubString().size(); @@ -1013,14 +1023,15 @@ void LLFolderViewItem::draw() std::string combined_string = mLabel + mLabelSuffix; S32 left = llround(text_left) + font->getWidth(combined_string, 0, mStringMatchOffset) - 1; S32 right = left + font->getWidth(combined_string, mStringMatchOffset, filter_string_length) + 2; - S32 bottom = llfloor(getRect().getHeight() - font->getLineHeight() - 3); - S32 top = getRect().getHeight(); - + S32 bottom = llfloor(getRect().getHeight() - font->getLineHeight() - 3 - TOP_PAD); + S32 top = getRect().getHeight() - TOP_PAD; + + LLUIImage* box_image = default_params.selection_image; LLRect box_rect(left, top, right, bottom); - sBoxImage->draw(box_rect, sFilterBGColor); + box_image->draw(box_rect, sFilterBGColor); F32 match_string_left = text_left + font->getWidthF32(combined_string, 0, mStringMatchOffset); - F32 y = (F32)getRect().getHeight() - font->getLineHeight() - (F32)TEXT_PAD; - font->renderUTF8( combined_string, mStringMatchOffset, match_string_left, y, + F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)TEXT_PAD - (F32)TOP_PAD; + font->renderUTF8( combined_string, mStringMatchOffset, match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, filter_string_length, S32_MAX, &right_x, FALSE ); } diff --git a/indra/newview/llfolderviewitem.h b/indra/newview/llfolderviewitem.h index 6f8c738a59..be8e73a5a9 100644 --- a/indra/newview/llfolderviewitem.h +++ b/indra/newview/llfolderviewitem.h @@ -101,7 +101,10 @@ public: Optional listener; Optional folder_arrow_image; + Optional folder_indentation; // pixels Optional selection_image; + Optional item_height; // pixels + Optional item_top_pad; // pixels Optional creation_date; //UTC seconds @@ -110,7 +113,7 @@ public: // layout constants static const S32 LEFT_PAD = 5; - // LEFT_INDENTATION is set via settings.xml FolderIndentation + // LEFT_INDENTATION is set via folder_indentation above static const S32 ICON_PAD = 2; static const S32 ICON_WIDTH = 16; static const S32 TEXT_PAD = 1; @@ -127,11 +130,7 @@ protected: friend class LLUICtrlFactory; friend class LLFolderViewEventListener; - LLFolderViewItem(Params p = LLFolderViewItem::Params()); - - static const LLFontGL* sSmallFont; - static LLUIImagePtr sArrowImage; - static LLUIImagePtr sBoxImage; + LLFolderViewItem(const Params& p); std::string mLabel; std::string mSearchableLabel; @@ -150,6 +149,7 @@ protected: LLUIImagePtr mIconOpen; BOOL mHasVisibleChildren; S32 mIndentation; + S32 mItemHeight; S32 mNumDescendantsSelected; BOOL mPassedFilter; S32 mLastFilterGeneration; @@ -157,8 +157,6 @@ protected: F32 mControlLabelRotation; LLFolderView* mRoot; BOOL mDragAndDropTarget; - LLUIImagePtr mArrowImage; - LLUIImagePtr mBoxImage; BOOL mIsLoading; LLTimer mTimeSinceRequestStart; bool mHidden; @@ -237,7 +235,7 @@ public: virtual void recursiveDeselect(BOOL deselect_self); // gets multiple-element selection - virtual BOOL getSelectionList(std::set &selection){return TRUE;} + virtual BOOL getSelectionList(std::set &selection) const {return TRUE;} // Returns true is this object and all of its children can be removed (deleted by user) virtual BOOL isRemovable(); @@ -304,7 +302,7 @@ public: // Show children (unfortunate that this is called "open") virtual void setOpen(BOOL open = TRUE) {}; - virtual BOOL isOpen() { return FALSE; } + virtual BOOL isOpen() const { return FALSE; } virtual LLFolderView* getRoot(); BOOL isDescendantOf( const LLFolderViewFolder* potential_ancestor ); @@ -499,7 +497,7 @@ public: virtual void setOpenArrangeRecursively(BOOL openitem, ERecurseType recurse = RECURSE_NO); // Get the current state of the folder. - virtual BOOL isOpen() { return mIsOpen; } + virtual BOOL isOpen() const { return mIsOpen; } // special case if an object is dropped on the child. BOOL handleDragAndDropFromChild(MASK mask, diff --git a/indra/newview/llgesturemgr.cpp b/indra/newview/llgesturemgr.cpp index df7aa9eabf..82293b4aa0 100644 --- a/indra/newview/llgesturemgr.cpp +++ b/indra/newview/llgesturemgr.cpp @@ -417,6 +417,16 @@ BOOL LLGestureManager::isGesturePlaying(const LLUUID& item_id) return gesture->mPlaying; } +BOOL LLGestureManager::isGesturePlaying(LLMultiGesture* gesture) +{ + if(!gesture) + { + return FALSE; + } + + return gesture->mPlaying; +} + void LLGestureManager::replaceGesture(const LLUUID& item_id, LLMultiGesture* new_gesture, const LLUUID& asset_id) { const LLUUID& base_item_id = get_linked_uuid(item_id); diff --git a/indra/newview/llgesturemgr.h b/indra/newview/llgesturemgr.h index e80eea9ae9..c562587c6f 100644 --- a/indra/newview/llgesturemgr.h +++ b/indra/newview/llgesturemgr.h @@ -103,6 +103,8 @@ public: BOOL isGesturePlaying(const LLUUID& item_id); + BOOL isGesturePlaying(LLMultiGesture* gesture); + const item_map_t& getActiveGestures() const { return mActive; } // Force a gesture to be played, for example, if it is being // previewed. diff --git a/indra/newview/llgroupactions.cpp b/indra/newview/llgroupactions.cpp index 7dd8ea694e..d6e2bb0445 100644 --- a/indra/newview/llgroupactions.cpp +++ b/indra/newview/llgroupactions.cpp @@ -146,6 +146,12 @@ void LLGroupActions::startCall(const LLUUID& group_id) // static void LLGroupActions::join(const LLUUID& group_id) { + if (!gAgent.canJoinGroups()) + { + LLNotificationsUtil::add("JoinedTooManyGroups"); + return; + } + LLGroupMgrGroupData* gdatap = LLGroupMgr::getInstance()->getGroupData(group_id); @@ -226,7 +232,9 @@ void LLGroupActions::activate(const LLUUID& group_id) static bool isGroupUIVisible() { - LLPanel* panel = LLSideTray::getInstance()->findChild("panel_group_info_sidetray"); + static LLPanel* panel = 0; + if(!panel) + panel = LLSideTray::getInstance()->findChild("panel_group_info_sidetray"); if(!panel) return false; return panel->isInVisibleChain(); diff --git a/indra/newview/llgrouplist.cpp b/indra/newview/llgrouplist.cpp index 3ca459a403..e75d35bea4 100644 --- a/indra/newview/llgrouplist.cpp +++ b/indra/newview/llgrouplist.cpp @@ -210,7 +210,6 @@ void LLGroupList::addNewItem(const LLUUID& id, const std::string& name, const LL item->setGroupID(id); item->setName(name, mNameFilter); item->setGroupIconID(icon_id); -// item->setContextMenu(mContextMenu); item->childSetVisible("info_btn", false); item->childSetVisible("profile_btn", false); @@ -268,8 +267,9 @@ bool LLGroupList::onContextMenuItemEnable(const LLSD& userdata) LLUUID selected_group_id = getSelectedUUID(); bool real_group_selected = selected_group_id.notNull(); // a "real" (not "none") group is selected + // each group including "none" can be activated if (userdata.asString() == "activate") - return real_group_selected && gAgent.getGroupID() != selected_group_id; + return gAgent.getGroupID() != selected_group_id; return real_group_selected; } @@ -283,7 +283,6 @@ LLGroupListItem::LLGroupListItem() mGroupIcon(NULL), mGroupNameBox(NULL), mInfoBtn(NULL), -//mContextMenu(NULL), //TODO: mGroupID(LLUUID::null) { LLUICtrlFactory::getInstance()->buildPanel(this, "panel_group_list_item.xml"); diff --git a/indra/newview/llhudtext.cpp b/indra/newview/llhudtext.cpp index 0b5da40be4..8ad94b957d 100644 --- a/indra/newview/llhudtext.cpp +++ b/indra/newview/llhudtext.cpp @@ -287,7 +287,7 @@ void LLHUDText::renderText(BOOL for_select) mOffsetY = lltrunc(mHeight * ((mVertAlignment == ALIGN_VERT_CENTER) ? 0.5f : 1.f)); // *TODO: cache this image - LLUIImagePtr imagep = LLUI::getUIImage("rounded_square.tga"); + LLUIImagePtr imagep = LLUI::getUIImage("Rounded_Square"); // *TODO: make this a per-text setting LLColor4 bg_color = LLUIColorTable::instance().getColor("BackgroundChatColor"); @@ -606,7 +606,7 @@ void LLHUDText::addLine(const LLWString &wstr, const LLColor4& color, const LLFo U32 line_length = 0; do { - S32 segment_length = mFontp->maxDrawableChars(iter->substr(line_length).c_str(), mUseBubble ? HUD_TEXT_MAX_WIDTH : HUD_TEXT_MAX_WIDTH_NO_BUBBLE, wline.length(), TRUE); + S32 segment_length = mFontp->maxDrawableChars(iter->substr(line_length).c_str(), mUseBubble ? HUD_TEXT_MAX_WIDTH : HUD_TEXT_MAX_WIDTH_NO_BUBBLE, wline.length(), LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); mTextSegments.push_back(LLHUDTextSegment(iter->substr(line_length, segment_length), style, color)); line_length += segment_length; } @@ -642,7 +642,7 @@ void LLHUDText::setLabel(const LLWString &wlabel) U32 line_length = 0; do { - S32 segment_length = mFontp->maxDrawableChars(iter->substr(line_length).c_str(), mUseBubble ? HUD_TEXT_MAX_WIDTH : HUD_TEXT_MAX_WIDTH_NO_BUBBLE, wstr.length(), TRUE); + S32 segment_length = mFontp->maxDrawableChars(iter->substr(line_length).c_str(), mUseBubble ? HUD_TEXT_MAX_WIDTH : HUD_TEXT_MAX_WIDTH_NO_BUBBLE, wstr.length(), LLFontGL::WORD_BOUNDARY_IF_POSSIBLE); mLabelSegments.push_back(LLHUDTextSegment(iter->substr(line_length, segment_length), LLFontGL::NORMAL, mColor)); line_length += segment_length; } diff --git a/indra/newview/llimfloater.cpp b/indra/newview/llimfloater.cpp index 259f629bdd..e06e0c94ec 100644 --- a/indra/newview/llimfloater.cpp +++ b/indra/newview/llimfloater.cpp @@ -42,7 +42,6 @@ #include "llbottomtray.h" #include "llchannelmanager.h" #include "llchiclet.h" -#include "llfloaterchat.h" #include "llfloaterreg.h" #include "llimfloatercontainer.h" // to replace separate IM Floaters with multifloater container #include "lllayoutstack.h" @@ -59,6 +58,7 @@ #include "llinventorymodel.h" #include "llrootview.h" +#include "llspeakers.h" LLIMFloater::LLIMFloater(const LLUUID& session_id) @@ -115,11 +115,21 @@ LLIMFloater::LLIMFloater(const LLUUID& session_id) void LLIMFloater::onFocusLost() { LLIMModel::getInstance()->resetActiveSessionID(); + + LLBottomTray::getInstance()->getChicletPanel()->setChicletToggleState(mSessionID, false); } void LLIMFloater::onFocusReceived() { LLIMModel::getInstance()->setActiveSessionID(mSessionID); + + // return focus to the input field when active tab in the multitab container is clicked. + if (isChatMultiTab() && mInputEditor) + { + mInputEditor->setFocus(TRUE); + } + + LLBottomTray::getInstance()->getChicletPanel()->setChicletToggleState(mSessionID, true); } // virtual @@ -341,13 +351,15 @@ void* LLIMFloater::createPanelAdHocControl(void* userdata) void LLIMFloater::onSlide() { - LLPanel* im_control_panel = getChild("panel_im_control_panel"); - im_control_panel->setVisible(!im_control_panel->getVisible()); + mControlPanel->setVisible(!mControlPanel->getVisible()); + + gSavedSettings.setBOOL("IMShowControlPanel", mControlPanel->getVisible()); - gSavedSettings.setBOOL("IMShowControlPanel", im_control_panel->getVisible()); + getChild("slide_left_btn")->setVisible(mControlPanel->getVisible()); + getChild("slide_right_btn")->setVisible(!mControlPanel->getVisible()); - getChild("slide_left_btn")->setVisible(im_control_panel->getVisible()); - getChild("slide_right_btn")->setVisible(!im_control_panel->getVisible()); + LLLayoutStack* stack = getChild("im_panels"); + if (stack) stack->setAnimate(true); } //static @@ -355,35 +367,7 @@ LLIMFloater* LLIMFloater::show(const LLUUID& session_id) { if (!gIMMgr->hasSession(session_id)) return NULL; - // we should make sure all related chiclets are in place when the session is a voice call - // chiclets come firts, then comes IM window - if (gIMMgr->isVoiceCall(session_id)) - { - LLIMModel* im_model = LLIMModel::getInstance(); - LLBottomTray* b_tray = LLBottomTray::getInstance(); - - //*TODO hide that into Bottom tray - if (!b_tray->getChicletPanel()->findChiclet(session_id)) - { - LLIMChiclet* chiclet = b_tray->createIMChiclet(session_id); - if(chiclet) - { - chiclet->setIMSessionName(im_model->getName(session_id)); - chiclet->setOtherParticipantId(im_model->getOtherParticipantID(session_id)); - } - } - - LLIMWellWindow::getInstance()->addIMRow(session_id); - } - - bool not_existed = true; - - if(isChatMultiTab()) - { - LLIMFloater* target_floater = findInstance(session_id); - not_existed = NULL == target_floater; - } - else + if(!isChatMultiTab()) { //hide all LLFloaterReg::const_instance_list_t& inst_list = LLFloaterReg::getFloaterList("impanel"); @@ -398,19 +382,33 @@ LLIMFloater* LLIMFloater::show(const LLUUID& session_id) } } - LLIMFloater* floater = LLFloaterReg::showTypedInstance("impanel", session_id); + bool exist = findInstance(session_id); + + LLIMFloater* floater = getInstance(session_id); + if (!floater) return NULL; if(isChatMultiTab()) { + LLIMFloaterContainer* floater_container = LLIMFloaterContainer::getInstance(); + // do not add existed floaters to avoid adding torn off instances - if (not_existed) + if (!exist) { // LLTabContainer::eInsertionPoint i_pt = user_initiated ? LLTabContainer::RIGHT_OF_CURRENT : LLTabContainer::END; // TODO: mantipov: use LLTabContainer::RIGHT_OF_CURRENT if it exists LLTabContainer::eInsertionPoint i_pt = LLTabContainer::END; + + if (floater_container) + { + floater_container->addFloater(floater, TRUE, i_pt); + } + } - LLIMFloaterContainer* floater_container = LLFloaterReg::showTypedInstance("im_container"); - floater_container->addFloater(floater, TRUE, i_pt); + if (floater_container) + { + //selecting the panel resets a chiclet's counter + floater_container->selectFloater(floater); + floater_container->setVisible(TRUE); } } else @@ -437,8 +435,8 @@ LLIMFloater* LLIMFloater::show(const LLUUID& session_id) } // window is positioned, now we can show it. - floater->setVisible(true); } + floater->setVisible(TRUE); return floater; } @@ -478,16 +476,6 @@ void LLIMFloater::setDocked(bool docked, bool pop_on_undock) } } -void LLIMFloater::setTornOff(bool torn_off) -{ - // When IM Floater isn't torn off, "close" button should be hidden. - // This call will just disables it, since there is a hack in LLFloater::updateButton, - // which prevents hiding of close button in that case. - setCanClose(torn_off); - - LLTransientDockableFloater::setTornOff(torn_off); -} - void LLIMFloater::setVisible(BOOL visible) { LLNotificationsUI::LLScreenChannel* channel = dynamic_cast @@ -508,6 +496,15 @@ void LLIMFloater::setVisible(BOOL visible) updateMessages(); mInputEditor->setFocus(TRUE); } + + if(!visible) + { + LLIMChiclet* chiclet = LLBottomTray::getInstance()->getChicletPanel()->findChiclet(mSessionID); + if(chiclet) + { + chiclet->setToggleState(false); + } + } } //static @@ -542,6 +539,11 @@ LLIMFloater* LLIMFloater::findInstance(const LLUUID& session_id) return LLFloaterReg::findTypedInstance("impanel", session_id); } +LLIMFloater* LLIMFloater::getInstance(const LLUUID& session_id) +{ + return LLFloaterReg::getTypedInstance("impanel", session_id); +} + void LLIMFloater::sessionInitReplyReceived(const LLUUID& im_session_id) { mSessionInitialized = true; @@ -589,7 +591,7 @@ void LLIMFloater::updateMessages() std::string time = msg["time"].asString(); LLUUID from_id = msg["from_id"].asUUID(); - std::string from = from_id != gAgentID ? msg["from"].asString() : LLTrans::getString("You"); + std::string from = msg["from"].asString(); std::string message = msg["message"].asString(); LLChat chat; @@ -618,6 +620,15 @@ void LLIMFloater::onInputEditorFocusReceived( LLFocusableElement* caller, void* //in disconnected state IM input editor should be disabled self->mInputEditor->setEnabled(!gDisconnected); } + + // when IM Floater is a part of the multitab container LLTabContainer set focus to the first + // child on tab button's mouse up. This leads input field lost focus. See EXT-3852. + if (isChatMultiTab()) + { + // So, clear control captured mouse to prevent LLTabContainer set focus on the panel's first child. + // do not pass self->mInputEditor, this leads to have "Edit Text" mouse pointer wherever it is. + gFocusMgr.setMouseCapture(NULL); + } } // static @@ -1011,3 +1022,20 @@ void LLIMFloater::sRemoveTypingIndicator(const LLSD& data) floater->removeTypingIndicator(); } + +void LLIMFloater::onIMChicletCreated( const LLUUID& session_id ) +{ + + if (isChatMultiTab()) + { + LLIMFloaterContainer* im_box = LLIMFloaterContainer::getInstance(); + if (!im_box) return; + + if (LLIMFloater::findInstance(session_id)) return; + + LLIMFloater* new_tab = LLIMFloater::getInstance(session_id); + + im_box->addFloater(new_tab, FALSE, LLTabContainer::END); + } + +} diff --git a/indra/newview/llimfloater.h b/indra/newview/llimfloater.h index bc7a43e852..d9db385d06 100644 --- a/indra/newview/llimfloater.h +++ b/indra/newview/llimfloater.h @@ -64,7 +64,6 @@ public: // LLFloater overrides /*virtual*/ void onClose(bool app_quitting); /*virtual*/ void setDocked(bool docked, bool pop_on_undock = true); - /*virtual*/ void setTornOff(bool torn_off); // Make IM conversion visible and update the message history static LLIMFloater* show(const LLUUID& session_id); @@ -75,6 +74,8 @@ public: static LLIMFloater* findInstance(const LLUUID& session_id); + static LLIMFloater* getInstance(const LLUUID& session_id); + void sessionInitReplyReceived(const LLUUID& im_session_id); // get new messages from LLIMModel @@ -113,6 +114,8 @@ public: //used as a callback on receiving new IM message static void sRemoveTypingIndicator(const LLSD& data); + static void onIMChicletCreated(const LLUUID& session_id); + private: // process focus events to set a currently active session /* virtual */ void onFocusLost(); diff --git a/indra/newview/llimfloatercontainer.cpp b/indra/newview/llimfloatercontainer.cpp index 2d7333f7e4..6cc985aef4 100644 --- a/indra/newview/llimfloatercontainer.cpp +++ b/indra/newview/llimfloatercontainer.cpp @@ -34,6 +34,7 @@ #include "llviewerprecompiledheaders.h" #include "llimfloatercontainer.h" +#include "llfloaterreg.h" // // LLIMFloaterContainer @@ -93,4 +94,14 @@ void LLIMFloaterContainer::addFloater(LLFloater* floaterp, } } +LLIMFloaterContainer* LLIMFloaterContainer::findInstance() +{ + return LLFloaterReg::findTypedInstance("im_container"); +} + +LLIMFloaterContainer* LLIMFloaterContainer::getInstance() +{ + return LLFloaterReg::getTypedInstance("im_container"); +} + // EOF diff --git a/indra/newview/llimfloatercontainer.h b/indra/newview/llimfloatercontainer.h index ead7cf4730..d4a542dfc2 100644 --- a/indra/newview/llimfloatercontainer.h +++ b/indra/newview/llimfloatercontainer.h @@ -52,7 +52,11 @@ public: LLTabContainer::eInsertionPoint insertion_point = LLTabContainer::END); static LLFloater* getCurrentVoiceFloater(); - + + static LLIMFloaterContainer* findInstance(); + + static LLIMFloaterContainer* getInstance(); + protected: LLFloater* mActiveVoiceFloater; diff --git a/indra/newview/llimpanel.cpp b/indra/newview/llimpanel.cpp index 029ddbaf2c..4bdf5f42dc 100644 --- a/indra/newview/llimpanel.cpp +++ b/indra/newview/llimpanel.cpp @@ -40,6 +40,7 @@ #include "llfontgl.h" #include "llrect.h" #include "llerror.h" +#include "llmultifloater.h" #include "llstring.h" #include "message.h" #include "lltextbox.h" @@ -59,7 +60,6 @@ #include "llinventory.h" #include "llinventorymodel.h" #include "llfloaterinventory.h" -#include "llfloaterchat.h" #include "lliconctrl.h" #include "llkeyboard.h" #include "lllineeditor.h" diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp index 40227539d0..ff20a55358 100644 --- a/indra/newview/llimview.cpp +++ b/indra/newview/llimview.cpp @@ -42,46 +42,30 @@ #include "llhttpclient.h" #include "llsdutil_math.h" #include "llstring.h" +#include "lltrans.h" #include "lluictrlfactory.h" #include "llagent.h" +#include "llagentui.h" +#include "llappviewer.h" #include "llavatariconctrl.h" #include "llbottomtray.h" #include "llcallingcard.h" #include "llchat.h" -#include "llchiclet.h" -#include "llresmgr.h" -#include "llfloaterchat.h" #include "llfloaterchatterbox.h" -#include "llavataractions.h" -#include "llhttpnode.h" #include "llimfloater.h" -#include "llimpanel.h" #include "llgroupiconctrl.h" -#include "llresizebar.h" -#include "lltabcontainer.h" -#include "llviewercontrol.h" -#include "llfloater.h" #include "llmutelist.h" -#include "llresizehandle.h" -#include "llkeyboard.h" -#include "llui.h" -#include "llviewermenu.h" -#include "llcallingcard.h" -#include "lltoolbar.h" +#include "llrecentpeople.h" #include "llviewermessage.h" #include "llviewerwindow.h" #include "llnotifications.h" #include "llnotificationsutil.h" #include "llnearbychat.h" -#include "llviewerregion.h" -#include "llvoicechannel.h" -#include "lltrans.h" -#include "llrecentpeople.h" -#include "llsyswellwindow.h" +#include "llspeakers.h" //for LLIMSpeakerMgr +#include "lltextutil.h" +#include "llviewercontrol.h" -#include "llfirstuse.h" -#include "llagentui.h" const static std::string IM_TIME("time"); const static std::string IM_TEXT("message"); @@ -92,6 +76,7 @@ const static std::string NO_SESSION("(IM Session Doesn't Exist)"); const static std::string ADHOC_NAME_SUFFIX(" Conference"); std::string LLCallDialogManager::sPreviousSessionlName = ""; +LLIMModel::LLIMSession::SType LLCallDialogManager::sPreviousSessionType = LLIMModel::LLIMSession::P2P_SESSION; std::string LLCallDialogManager::sCurrentSessionlName = ""; LLIMModel::LLIMSession* LLCallDialogManager::sSession = NULL; LLVoiceChannel::EState LLCallDialogManager::sOldState = LLVoiceChannel::STATE_READY; @@ -178,6 +163,7 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& if (IM_NOTHING_SPECIAL == type || IM_SESSION_P2P_INVITE == type) { mVoiceChannel = new LLVoiceChannelP2P(session_id, name, other_participant_id); + mOtherParticipantIsAvatar = LLVoiceClient::getInstance()->isParticipantAvatar(mSessionID); // check if it was AVALINE call if (!mOtherParticipantIsAvatar) @@ -208,7 +194,10 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& mSpeakers = new LLIMSpeakerMgr(mVoiceChannel); // All participants will be added to the list of people we've recently interacted with. - mSpeakers->addListener(&LLRecentPeople::instance(), "add"); + + // we need to add only _active_ speakers...so comment this. + // may delete this later on cleanup + //mSpeakers->addListener(&LLRecentPeople::instance(), "add"); //we need to wait for session initialization for outgoing ad-hoc and group chat session //correct session id for initiated ad-hoc chat will be received from the server @@ -224,7 +213,6 @@ LLIMModel::LLIMSession::LLIMSession(const LLUUID& session_id, const std::string& { mCallBackEnabled = LLVoiceClient::getInstance()->isSessionCallBackPossible(mSessionID); mTextIMPossible = LLVoiceClient::getInstance()->isSessionTextIMPossible(mSessionID); - mOtherParticipantIsAvatar = LLVoiceClient::getInstance()->isParticipantAvatar(mSessionID); } if ( gSavedPerAccountSettings.getBOOL("LogShowHistory") ) @@ -675,6 +663,12 @@ bool LLIMModel::addMessage(const LLUUID& session_id, const std::string& from, co LLIMSession* session = addMessageSilently(session_id, from, from_id, utf8_text, log2file); if (!session) return false; + //good place to add some1 to recent list + //other places may be called from message history. + if( !from_id.isNull() && + ( session->isP2PSessionType() || session->isAdHocSessionType() ) ) + LLRecentPeople::instance().add(from_id); + // notify listeners LLSD arg; arg["session_id"] = session_id; @@ -781,7 +775,7 @@ LLIMSpeakerMgr* LLIMModel::getSpeakerManager( const LLUUID& session_id ) const LLIMSession* session = findIMSession(session_id); if (!session) { - llwarns << "session " << session_id << "does not exist " << llendl; + llwarns << "session " << session_id << " does not exist " << llendl; return NULL; } @@ -1360,8 +1354,15 @@ void LLCallDialogManager::onVoiceChannelChanged(const LLUUID &session_id) sCurrentSessionlName = ""; // Empty string results in "Nearby Voice Chat" after substitution return; } + + if (sSession) + { + // store previous session type to process Avaline calls in dialogs + sPreviousSessionType = sSession->mSessionType; + } + sSession = session; - sSession->mVoiceChannel->setStateChangedCallback(LLCallDialogManager::onVoiceChannelStateChanged); + sSession->mVoiceChannel->setStateChangedCallback(boost::bind(LLCallDialogManager::onVoiceChannelStateChanged, _1, _2, _3)); if(sCurrentSessionlName != session->mName) { sPreviousSessionlName = sCurrentSessionlName; @@ -1378,6 +1379,7 @@ void LLCallDialogManager::onVoiceChannelChanged(const LLUUID &session_id) mCallDialogPayload["session_name"] = sSession->mName; mCallDialogPayload["other_user_id"] = sSession->mOtherParticipantID; mCallDialogPayload["old_channel_name"] = sPreviousSessionlName; + mCallDialogPayload["old_session_type"] = sPreviousSessionType; mCallDialogPayload["state"] = LLVoiceChannel::STATE_CALL_STARTED; mCallDialogPayload["disconnected_channel_name"] = sSession->mName; mCallDialogPayload["session_type"] = sSession->mSessionType; @@ -1407,6 +1409,7 @@ void LLCallDialogManager::onVoiceChannelStateChanged(const LLVoiceChannel::EStat mCallDialogPayload["session_name"] = sSession->mName; mCallDialogPayload["other_user_id"] = sSession->mOtherParticipantID; mCallDialogPayload["old_channel_name"] = sPreviousSessionlName; + mCallDialogPayload["old_session_type"] = sPreviousSessionType; mCallDialogPayload["state"] = new_state; mCallDialogPayload["disconnected_channel_name"] = sSession->mName; mCallDialogPayload["session_type"] = sSession->mSessionType; @@ -1421,6 +1424,11 @@ void LLCallDialogManager::onVoiceChannelStateChanged(const LLVoiceChannel::EStat } break; + case LLVoiceChannel::STATE_HUNG_UP: + // this state is coming before session is changed, so, put it into payload map + mCallDialogPayload["old_session_type"] = sSession->mSessionType; + break; + case LLVoiceChannel::STATE_CONNECTED : ocd = LLFloaterReg::findTypedInstance("outgoing_call", LLOutgoingCallDialog::OCD_KEY); if (ocd) @@ -1561,7 +1569,15 @@ void LLOutgoingCallDialog::show(const LLSD& key) // tell the user which voice channel they are leaving if (!mPayload["old_channel_name"].asString().empty()) { - childSetTextArg("leaving", "[CURRENT_CHAT]", mPayload["old_channel_name"].asString()); + bool was_avaline_call = LLIMModel::LLIMSession::AVALINE_SESSION == mPayload["old_session_type"].asInteger(); + + std::string old_caller_name = mPayload["old_channel_name"].asString(); + if (was_avaline_call) + { + old_caller_name = LLTextUtil::formatPhoneNumber(old_caller_name); + } + + childSetTextArg("leaving", "[CURRENT_CHAT]", old_caller_name); } else { @@ -1570,15 +1586,28 @@ void LLOutgoingCallDialog::show(const LLSD& key) if (!mPayload["disconnected_channel_name"].asString().empty()) { - childSetTextArg("nearby", "[VOICE_CHANNEL_NAME]", mPayload["disconnected_channel_name"].asString()); + std::string channel_name = mPayload["disconnected_channel_name"].asString(); + if (LLIMModel::LLIMSession::AVALINE_SESSION == mPayload["session_type"].asInteger()) + { + channel_name = LLTextUtil::formatPhoneNumber(channel_name); + } + childSetTextArg("nearby", "[VOICE_CHANNEL_NAME]", channel_name); childSetTextArg("nearby_P2P", "[VOICE_CHANNEL_NAME]", mPayload["disconnected_channel_name"].asString()); } std::string callee_name = mPayload["session_name"].asString(); + + LLUUID session_id = mPayload["session_id"].asUUID(); + bool is_avatar = LLVoiceClient::getInstance()->isParticipantAvatar(session_id); + if (callee_name == "anonymous") { callee_name = getString("anonymous"); } + else if (!is_avatar) + { + callee_name = LLTextUtil::formatPhoneNumber(callee_name); + } setTitle(callee_name); @@ -1728,16 +1757,21 @@ BOOL LLIncomingCallDialog::postBuild() call_type = getString(mPayload["notify_box_type"]); } + + // check to see if this is an Avaline call + bool is_avatar = LLVoiceClient::getInstance()->isParticipantAvatar(session_id); + childSetVisible("Start IM", is_avatar); // no IM for avaline + if (caller_name == "anonymous") { caller_name = getString("anonymous"); } - - setTitle(caller_name + " " + call_type); + else if (!is_avatar) + { + caller_name = LLTextUtil::formatPhoneNumber(caller_name); + } - // check to see if this is an Avaline call - bool is_avatar = LLVoiceClient::getInstance()->isParticipantAvatar(session_id); - childSetVisible("Start IM", is_avatar); // no IM for avaline + setTitle(caller_name + " " + call_type); LLUICtrl* caller_name_widget = getChild("caller name"); caller_name_widget->setValue(caller_name + " " + call_type); @@ -1805,7 +1839,7 @@ void LLIncomingCallDialog::onStartIM(void* user_data) void LLIncomingCallDialog::processCallResponse(S32 response) { - if (!gIMMgr) + if (!gIMMgr || gDisconnected) return; LLUUID session_id = mPayload["session_id"].asUUID(); @@ -2185,7 +2219,6 @@ void LLIMMgr::addSystemMessage(const LLUUID& session_id, const std::string& mess LLChat chat(message); chat.mSourceType = CHAT_SOURCE_SYSTEM; - LLFloaterChat::addChatHistory(chat); LLNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat", LLSD()); if(nearby_chat) @@ -2353,7 +2386,9 @@ LLUUID LLIMMgr::addSession( //we don't need to show notes about online/offline, mute/unmute users' statuses for existing sessions if (!new_session) return session_id; - noteOfflineUsers(session_id, floater, ids); + //Per Plan's suggestion commented "explicit offline status warning" out to make Dessie happier (see EXT-3609) + //*TODO After February 2010 remove this commented out line if no one will be missing that warning + //noteOfflineUsers(session_id, floater, ids); // Only warn for regular IMs - not group IMs if( dialog == IM_NOTHING_SPECIAL ) @@ -3085,9 +3120,6 @@ public: ll_vector3_from_sd(message_params["position"]), true); - chat.mText = std::string("IM: ") + name + separator_string + saved + message; - LLFloaterChat::addChat(chat, TRUE, is_this_agent); - //K now we want to accept the invitation std::string url = gAgent.getRegion()->getCapability( "ChatSessionRequest"); diff --git a/indra/newview/llimview.h b/indra/newview/llimview.h index 11860d0efb..a226d66b12 100644 --- a/indra/newview/llimview.h +++ b/indra/newview/llimview.h @@ -33,22 +33,19 @@ #ifndef LL_LLIMVIEW_H #define LL_LLIMVIEW_H -#include "lldarray.h" -#include "lldockablefloater.h" -#include "llspeakers.h" //for LLIMSpeakerMgr -#include "llimpanel.h" //for voice channels -#include "llmodaldialog.h" #include "lldockablefloater.h" #include "llinstantmessage.h" -#include "lluuid.h" -#include "llmultifloater.h" + #include "lllogchat.h" +#include "llvoicechannel.h" class LLFloaterChatterBox; class LLUUID; class LLFloaterIMPanel; class LLFriendObserver; class LLCallDialogManager; +class LLIMSpeakerMgr; + class LLIMModel : public LLSingleton { @@ -78,6 +75,11 @@ public: bool isP2P(); bool isOtherParticipantAvaline(); + bool isP2PSessionType() const { return mSessionType == P2P_SESSION;} + bool isAdHocSessionType() const { return mSessionType == ADHOC_SESSION;} + bool isGroupSessionType() const { return mSessionType == GROUP_SESSION;} + bool isAvalineSessionType() const { return mSessionType == AVALINE_SESSION;} + LLUUID mSessionID; std::string mName; EInstantMessage mType; @@ -475,6 +477,7 @@ public: protected: static std::string sPreviousSessionlName; + static LLIMModel::LLIMSession::SType sPreviousSessionType; static std::string sCurrentSessionlName; static LLIMModel::LLIMSession* sSession; static LLVoiceChannel::EState sOldState; diff --git a/indra/newview/llinspect.h b/indra/newview/llinspect.h index 731e99534b..a1cb9cd71c 100644 --- a/indra/newview/llinspect.h +++ b/indra/newview/llinspect.h @@ -55,7 +55,7 @@ public: /// Inspectors close themselves when they lose focus /*virtual*/ void onFocusLost(); -private: +protected: LLFrameTimer mCloseTimer; LLFrameTimer mOpenTimer; }; diff --git a/indra/newview/llinspectavatar.cpp b/indra/newview/llinspectavatar.cpp index dae980feb1..0374a1d25b 100644 --- a/indra/newview/llinspectavatar.cpp +++ b/indra/newview/llinspectavatar.cpp @@ -93,6 +93,10 @@ public: // Update view based on information from avatar properties processor void processAvatarData(LLAvatarData* data); + // override the inspector mouse leave so timer is only paused if + // gear menu is not open + /* virtual */ void onMouseLeave(S32 x, S32 y, MASK mask); + private: // Make network requests for all the data to display in this view. // Used on construction and if avatar id changes. @@ -112,6 +116,7 @@ private: void onClickAddFriend(); void onClickViewProfile(); void onClickIM(); + void onClickCall(); void onClickTeleport(); void onClickInviteToGroup(); void onClickPay(); @@ -204,6 +209,7 @@ LLInspectAvatar::LLInspectAvatar(const LLSD& sd) mCommitCallbackRegistrar.add("InspectAvatar.AddFriend", boost::bind(&LLInspectAvatar::onClickAddFriend, this)); mCommitCallbackRegistrar.add("InspectAvatar.IM", boost::bind(&LLInspectAvatar::onClickIM, this)); + mCommitCallbackRegistrar.add("InspectAvatar.Call", boost::bind(&LLInspectAvatar::onClickCall, this)); mCommitCallbackRegistrar.add("InspectAvatar.Teleport", boost::bind(&LLInspectAvatar::onClickTeleport, this)); mCommitCallbackRegistrar.add("InspectAvatar.InviteToGroup", boost::bind(&LLInspectAvatar::onClickInviteToGroup, this)); mCommitCallbackRegistrar.add("InspectAvatar.Pay", boost::bind(&LLInspectAvatar::onClickPay, this)); @@ -257,8 +263,6 @@ BOOL LLInspectAvatar::postBuild(void) } - - // Multiple calls to showInstance("inspect_avatar", foo) will provide different // LLSD for foo, which we will catch here. //virtual @@ -274,7 +278,7 @@ void LLInspectAvatar::onOpen(const LLSD& data) getChild("gear_self_btn")->setVisible(self); getChild("gear_btn")->setVisible(!self); - + // Position the inspector relative to the mouse cursor // Similar to how tooltips are positioned // See LLToolTipMgr::createToolTip @@ -382,11 +386,25 @@ void LLInspectAvatar::processAvatarData(LLAvatarData* data) mPropertiesRequest = NULL; } +// For the avatar inspector, we only want to unpause the fade timer +// if neither the gear menu or self gear menu are open +void LLInspectAvatar::onMouseLeave(S32 x, S32 y, MASK mask) +{ + LLMenuGL* gear_menu = getChild("gear_btn")->getMenu(); + LLMenuGL* gear_menu_self = getChild("gear_self_btn")->getMenu(); + if ( !(gear_menu && gear_menu->getVisible()) && + !(gear_menu_self && gear_menu_self->getVisible())) + { + mOpenTimer.unpause(); + } +} + void LLInspectAvatar::updateModeratorPanel() { bool enable_moderator_panel = false; - if (LLVoiceChannel::getCurrentVoiceChannel()) + if (LLVoiceChannel::getCurrentVoiceChannel() && + mAvatarID != gAgent.getID()) { LLUUID session_id = LLVoiceChannel::getCurrentVoiceChannel()->getSessionID(); @@ -400,6 +418,7 @@ void LLInspectAvatar::updateModeratorPanel() LLPointer selected_speakerp = speaker_mgr->findSpeaker(mAvatarID); if(speaker_mgr->isVoiceActive() && selected_speakerp && + selected_speakerp->isInVoiceChannel() && ((self_speakerp && self_speakerp->mIsModerator) || gAgent.isGodlike())) { getChild("enable_voice")->setVisible(selected_speakerp->mModeratorMutedVoice); @@ -497,42 +516,58 @@ void LLInspectAvatar::toggleSelectedVoice(bool enabled) void LLInspectAvatar::updateVolumeSlider() { - // By convention, we only display and toggle voice mutes, not all mutes - bool is_muted = LLMuteList::getInstance()-> - isMuted(mAvatarID, LLMute::flagVoiceChat); - bool voice_enabled = gVoiceClient->getVoiceEnabled(mAvatarID); - LLUICtrl* mute_btn = getChild("mute_btn"); - mute_btn->setEnabled( voice_enabled ); - mute_btn->setValue( is_muted ); + bool voice_enabled = gVoiceClient->getVoiceEnabled(mAvatarID); - LLUICtrl* volume_slider = getChild("volume_slider"); - volume_slider->setEnabled( voice_enabled && !is_muted ); - const F32 DEFAULT_VOLUME = 0.5f; - F32 volume; - if (is_muted) + // Do not display volume slider and mute button if it + // is ourself or we are not in a voice channel together + if (!voice_enabled || (mAvatarID == gAgent.getID())) { - // it's clearer to display their volume as zero - volume = 0.f; + getChild("mute_btn")->setVisible(false); + getChild("volume_slider")->setVisible(false); } - else if (!voice_enabled) - { - // use nominal value rather than 0 - volume = DEFAULT_VOLUME; - } - else + + else { - // actual volume - volume = gVoiceClient->getUserVolume(mAvatarID); + getChild("mute_btn")->setVisible(true); + getChild("volume_slider")->setVisible(true); - // *HACK: Voice client doesn't have any data until user actually - // says something. - if (volume == 0.f) + // By convention, we only display and toggle voice mutes, not all mutes + bool is_muted = LLMuteList::getInstance()-> + isMuted(mAvatarID, LLMute::flagVoiceChat); + + LLUICtrl* mute_btn = getChild("mute_btn"); + + bool is_linden = LLStringUtil::endsWith(mAvatarName, " Linden"); + + mute_btn->setEnabled( !is_linden); + mute_btn->setValue( is_muted ); + + LLUICtrl* volume_slider = getChild("volume_slider"); + volume_slider->setEnabled( !is_muted ); + + const F32 DEFAULT_VOLUME = 0.5f; + F32 volume; + if (is_muted) { - volume = DEFAULT_VOLUME; + // it's clearer to display their volume as zero + volume = 0.f; } + else + { + // actual volume + volume = gVoiceClient->getUserVolume(mAvatarID); + + // *HACK: Voice client doesn't have any data until user actually + // says something. + if (volume == 0.f) + { + volume = DEFAULT_VOLUME; + } + } + volume_slider->setValue( (F64)volume ); } - volume_slider->setValue( (F64)volume ); + } void LLInspectAvatar::onClickMuteVolume() @@ -611,6 +646,12 @@ void LLInspectAvatar::onClickIM() closeFloater(); } +void LLInspectAvatar::onClickCall() +{ + LLAvatarActions::startCall(mAvatarID); + closeFloater(); +} + void LLInspectAvatar::onClickTeleport() { LLAvatarActions::offerTeleport(mAvatarID); diff --git a/indra/newview/llinspectobject.cpp b/indra/newview/llinspectobject.cpp index cb35a287e9..dd313c528d 100644 --- a/indra/newview/llinspectobject.cpp +++ b/indra/newview/llinspectobject.cpp @@ -41,6 +41,7 @@ #include "llslurl.h" #include "llviewermenu.h" // handle_object_touch(), handle_buy() #include "llviewermedia.h" +#include "llviewermediafocus.h" #include "llviewerobjectlist.h" // to select the requested object // Linden libraries @@ -82,6 +83,10 @@ public: // Release the selection and do other cleanup /*virtual*/ void onClose(bool app_quitting); + // override the inspector mouse leave so timer is only paused if + // gear menu is not open + /* virtual */ void onMouseLeave(S32 x, S32 y, MASK mask); + private: // Refresh displayed data with information from selection manager void update(); @@ -181,7 +186,6 @@ BOOL LLInspectObject::postBuild(void) return TRUE; } - // Multiple calls to showInstance("inspect_avatar", foo) will provide different // LLSD for foo, which we will catch here. //virtual @@ -214,6 +218,10 @@ void LLInspectObject::onOpen(const LLSD& data) LLViewerObject* obj = gObjectList.findObject( mObjectID ); if (obj) { + // Media focus and this code fight over the select manager. + // Make sure any media is unfocused before changing the selection here. + LLViewerMediaFocus::getInstance()->clearFocus(); + LLSelectMgr::instance().deselectAll(); mObjectSelection = LLSelectMgr::instance().selectObjectAndFamily(obj); @@ -562,6 +570,16 @@ void LLInspectObject::updateSecureBrowsing() getChild("secure_browsing")->setVisible(is_secure_browsing); } +// For the object inspector, only unpause the fade timer +// if the gear menu is not open +void LLInspectObject::onMouseLeave(S32 x, S32 y, MASK mask) +{ + LLMenuGL* gear_menu = getChild("gear_btn")->getMenu(); + if ( !(gear_menu && gear_menu->getVisible())) + { + mOpenTimer.unpause(); + } +} void LLInspectObject::onClickBuy() { diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index d70221b22a..099f863dc9 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -2074,7 +2074,12 @@ void LLFolderBridge::performAction(LLFolderView* folder, LLInventoryModel* model { if ("open" == action) { - openItem(); + LLFolderViewFolder *f = dynamic_cast(folder->getItemByID(mUUID)); + if (f) + { + f->setOpen(TRUE); + } + return; } else if ("paste" == action) @@ -2228,9 +2233,22 @@ LLUIImagePtr LLFolderBridge::getIcon() const LLUIImagePtr LLFolderBridge::getIcon(LLFolderType::EType preferred_type) { // we only have one folder image now + if (preferred_type == LLFolderType::FT_OUTFIT) + { + return LLUI::getUIImage("Inv_LookFolderClosed"); + } return LLUI::getUIImage("Inv_FolderClosed"); } +LLUIImagePtr LLFolderBridge::getOpenIcon() const +{ + if (getPreferredType() == LLFolderType::FT_OUTFIT) + { + return LLUI::getUIImage("Inv_LookFolderOpen"); + } + return LLUI::getUIImage("Inv_FolderOpen"); +} + BOOL LLFolderBridge::renameItem(const std::string& new_name) { if(!isItemRenameable()) @@ -2430,7 +2448,7 @@ void LLFolderBridge::folderOptionsMenu() const LLInventoryCategory* category = model->getCategory(mUUID); LLFolderType::EType type = category->getPreferredType(); - const bool is_default_folder = category && LLFolderType::lookupIsProtectedType(type); + const bool is_system_folder = category && LLFolderType::lookupIsProtectedType(type); // BAP change once we're no longer treating regular categories as ensembles. const bool is_ensemble = category && (type == LLFolderType::FT_NONE || LLFolderType::lookupIsEnsembleType(type)); @@ -2444,8 +2462,8 @@ void LLFolderBridge::folderOptionsMenu() mItems.push_back("Delete"); } - // Only enable calling-card related options for non-default folders. - if (!is_sidepanel && !is_default_folder) + // Only enable calling-card related options for non-system folders. + if (!is_sidepanel && !is_system_folder) { LLIsType is_callingcard(LLAssetType::AT_CALLINGCARD); if (mCallingCards || checkFolderForContentsOfType(model, is_callingcard)) @@ -2479,10 +2497,14 @@ void LLFolderBridge::folderOptionsMenu() mItems.push_back(std::string("Folder Wearables Separator")); } - // Only enable add/replace outfit for non-default folders. - if (!is_default_folder) + // Only enable add/replace outfit for non-system folders. + if (!is_system_folder) { - mItems.push_back(std::string("Add To Outfit")); + // Adding an outfit onto another (versus replacing) doesn't make sense. + if (type != LLFolderType::FT_OUTFIT) + { + mItems.push_back(std::string("Add To Outfit")); + } mItems.push_back(std::string("Replace Outfit")); } if (is_ensemble) @@ -2614,6 +2636,13 @@ void LLFolderBridge::buildContextMenu(LLMenuGL& menu, U32 flags) { mItems.push_back(std::string("Rename")); mItems.push_back(std::string("Delete")); + + // EXT-4030: disallow deletion of currently worn outfit + const LLViewerInventoryItem *base_outfit_link = LLAppearanceManager::instance().getBaseOutfitLink(); + if (base_outfit_link && (cat == base_outfit_link->getLinkedCategory())) + { + mDisabledItems.push_back(std::string("Delete")); + } } } @@ -2902,80 +2931,6 @@ bool move_task_inventory_callback(const LLSD& notification, const LLSD& response return false; } -/* -Next functions intended to reorder items in the inventory folder and save order on server -Is now used for Favorites folder. - -*TODO: refactoring is needed with Favorites Bar functionality. Probably should be moved in LLInventoryModel -*/ -void saveItemsOrder(LLInventoryModel::item_array_t& items) -{ - int sortField = 0; - - // current order is saved by setting incremental values (1, 2, 3, ...) for the sort field - for (LLInventoryModel::item_array_t::iterator i = items.begin(); i != items.end(); ++i) - { - LLViewerInventoryItem* item = *i; - - item->setSortField(++sortField); - item->setComplete(TRUE); - item->updateServer(FALSE); - - gInventory.updateItem(item); - - // Tell the parent folder to refresh its sort order. - gInventory.addChangedMask(LLInventoryObserver::SORT, item->getParentUUID()); - } - - gInventory.notifyObservers(); -} - -LLInventoryModel::item_array_t::iterator findItemByUUID(LLInventoryModel::item_array_t& items, const LLUUID& id) -{ - LLInventoryModel::item_array_t::iterator result = items.end(); - - for (LLInventoryModel::item_array_t::iterator i = items.begin(); i != items.end(); ++i) - { - if ((*i)->getUUID() == id) - { - result = i; - break; - } - } - - return result; -} - -// See also LLInventorySort where landmarks in the Favorites folder are sorted. -class LLViewerInventoryItemSort -{ -public: - bool operator()(const LLPointer& a, const LLPointer& b) - { - return a->getSortField() < b->getSortField(); - } -}; - -/** - * Sorts passed items by LLViewerInventoryItem sort field. - * - * @param[in, out] items - array of items, not sorted. - */ -void rearrange_item_order_by_sort_field(LLInventoryModel::item_array_t& items) -{ - static LLViewerInventoryItemSort sort_functor; - std::sort(items.begin(), items.end(), sort_functor); -} - -void updateItemsOrder(LLInventoryModel::item_array_t& items, const LLUUID& srcItemId, const LLUUID& destItemId) -{ - LLViewerInventoryItem* srcItem = gInventory.getItem(srcItemId); - LLViewerInventoryItem* destItem = gInventory.getItem(destItemId); - - items.erase(findItemByUUID(items, srcItem->getUUID())); - items.insert(findItemByUUID(items, destItem->getUUID()), srcItem); -} - BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, BOOL drop) { @@ -3058,36 +3013,34 @@ BOOL LLFolderBridge::dragItemIntoFolder(LLInventoryItem* inv_item, // if dragging from/into favorites folder only reorder items if ((mUUID == inv_item->getParentUUID()) && folder_allows_reorder) { - LLInventoryModel::cat_array_t cats; - LLInventoryModel::item_array_t items; - LLIsType is_type(LLAssetType::AT_LANDMARK); - model->collectDescendentsIf(mUUID, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); - LLInventoryPanel* panel = dynamic_cast(mInventoryPanel.get()); LLFolderViewItem* itemp = panel ? panel->getRootFolder()->getDraggingOverItem() : NULL; if (itemp) { LLUUID srcItemId = inv_item->getUUID(); LLUUID destItemId = itemp->getListener()->getUUID(); - - // ensure items are sorted properly before changing order. EXT-3498 - rearrange_item_order_by_sort_field(items); - - // update order - updateItemsOrder(items, srcItemId, destItemId); - - saveItemsOrder(items); + gInventory.rearrangeFavoriteLandmarks(srcItemId, destItemId); } } else if (favorites_id == mUUID) // if target is the favorites folder we use copy { + // use callback to rearrange favorite landmarks after adding + // to have new one placed before target (on which it was dropped). See EXT-4312. + LLPointer cb = new AddFavoriteLandmarkCallback(); + LLInventoryPanel* panel = dynamic_cast(mInventoryPanel.get()); + LLFolderViewItem* drag_over_item = panel ? panel->getRootFolder()->getDraggingOverItem() : NULL; + if (drag_over_item && drag_over_item->getListener()) + { + cb.get()->setTargetLandmarkId(drag_over_item->getListener()->getUUID()); + } + copy_inventory_item( gAgent.getID(), inv_item->getPermissions().getOwner(), inv_item->getUUID(), mUUID, std::string(), - LLPointer(NULL)); + cb); } else if (move_is_into_current_outfit || move_is_into_outfit) { diff --git a/indra/newview/llinventorybridge.h b/indra/newview/llinventorybridge.h index cc1fa45b26..fced0047e8 100644 --- a/indra/newview/llinventorybridge.h +++ b/indra/newview/llinventorybridge.h @@ -289,6 +289,7 @@ public: virtual LLFolderType::EType getPreferredType() const; virtual LLUIImagePtr getIcon() const; + virtual LLUIImagePtr getOpenIcon() const; static LLUIImagePtr getIcon(LLFolderType::EType preferred_type); virtual BOOL renameItem(const std::string& new_name); diff --git a/indra/newview/llinventoryfunctions.cpp b/indra/newview/llinventoryfunctions.cpp index 8f4136c01f..2885ba13fa 100644 --- a/indra/newview/llinventoryfunctions.cpp +++ b/indra/newview/llinventoryfunctions.cpp @@ -50,8 +50,7 @@ // newview includes #include "llappearancemgr.h" #include "llappviewer.h" -#include "llfirstuse.h" -#include "llfloaterchat.h" +//#include "llfirstuse.h" #include "llfloatercustomize.h" #include "llfocusmgr.h" #include "llfolderview.h" diff --git a/indra/newview/llinventorymodel.cpp b/indra/newview/llinventorymodel.cpp index 711114173c..e44adfb511 100644 --- a/indra/newview/llinventorymodel.cpp +++ b/indra/newview/llinventorymodel.cpp @@ -1994,21 +1994,23 @@ void LLInventoryModel::accountForUpdate(const LLCategoryUpdate& update) const descendents_actual += update.mDescendentDelta; cat->setDescendentCount(descendents_actual); cat->setVersion(++version); - llinfos << "accounted: '" << cat->getName() << "' " - << version << " with " << descendents_actual - << " descendents." << llendl; + lldebugs << "accounted: '" << cat->getName() << "' " + << version << " with " << descendents_actual + << " descendents." << llendl; } } if(!accounted) { - lldebugs << "No accounting for: '" << cat->getName() << "' " + // Error condition, this means that the category did not register that + // it got new descendents (perhaps because it is still being loaded) + // which means its descendent count will be wrong. + llwarns << "Accounting failed for '" << cat->getName() << "' version:" << version << llendl; } } else { - llwarns << "No category found for update " << update.mCategoryID - << llendl; + llwarns << "No category found for update " << update.mCategoryID << llendl; } } @@ -3620,6 +3622,98 @@ BOOL LLInventoryModel::getIsFirstTimeInViewer2() return sFirstTimeInViewer2; } +static LLInventoryModel::item_array_t::iterator find_item_iter_by_uuid(LLInventoryModel::item_array_t& items, const LLUUID& id) +{ + LLInventoryModel::item_array_t::iterator result = items.end(); + + for (LLInventoryModel::item_array_t::iterator i = items.begin(); i != items.end(); ++i) + { + if ((*i)->getUUID() == id) + { + result = i; + break; + } + } + + return result; +} + +// static +void LLInventoryModel::updateItemsOrder(LLInventoryModel::item_array_t& items, const LLUUID& src_item_id, const LLUUID& dest_item_id) +{ + LLInventoryModel::item_array_t::iterator it_src = find_item_iter_by_uuid(items, src_item_id); + LLInventoryModel::item_array_t::iterator it_dest = find_item_iter_by_uuid(items, dest_item_id); + + if (it_src == items.end() || it_dest == items.end()) return; + + LLViewerInventoryItem* src_item = *it_src; + items.erase(it_src); + + // target iterator can not be valid because the container was changed, so update it. + it_dest = find_item_iter_by_uuid(items, dest_item_id); + items.insert(it_dest, src_item); +} + +void LLInventoryModel::saveItemsOrder(const LLInventoryModel::item_array_t& items) +{ + int sortField = 0; + + // current order is saved by setting incremental values (1, 2, 3, ...) for the sort field + for (item_array_t::const_iterator i = items.begin(); i != items.end(); ++i) + { + LLViewerInventoryItem* item = *i; + + item->setSortField(++sortField); + item->setComplete(TRUE); + item->updateServer(FALSE); + + updateItem(item); + + // Tell the parent folder to refresh its sort order. + addChangedMask(LLInventoryObserver::SORT, item->getParentUUID()); + } + + notifyObservers(); +} + +// See also LLInventorySort where landmarks in the Favorites folder are sorted. +class LLViewerInventoryItemSort +{ +public: + bool operator()(const LLPointer& a, const LLPointer& b) + { + return a->getSortField() < b->getSortField(); + } +}; + +/** + * Sorts passed items by LLViewerInventoryItem sort field. + * + * @param[in, out] items - array of items, not sorted. + */ +static void rearrange_item_order_by_sort_field(LLInventoryModel::item_array_t& items) +{ + static LLViewerInventoryItemSort sort_functor; + std::sort(items.begin(), items.end(), sort_functor); +} + +void LLInventoryModel::rearrangeFavoriteLandmarks(const LLUUID& source_item_id, const LLUUID& target_item_id) +{ + LLInventoryModel::cat_array_t cats; + LLInventoryModel::item_array_t items; + LLIsType is_type(LLAssetType::AT_LANDMARK); + LLUUID favorites_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_FAVORITE); + gInventory.collectDescendentsIf(favorites_id, cats, items, LLInventoryModel::EXCLUDE_TRASH, is_type); + + // ensure items are sorted properly before changing order. EXT-3498 + rearrange_item_order_by_sort_field(items); + + // update order + updateItemsOrder(items, source_item_id, target_item_id); + + saveItemsOrder(items); +} + //---------------------------------------------------------------------------- // *NOTE: DEBUG functionality diff --git a/indra/newview/llinventorymodel.h b/indra/newview/llinventorymodel.h index 39377b4ae2..2a2b48ce3c 100644 --- a/indra/newview/llinventorymodel.h +++ b/indra/newview/llinventorymodel.h @@ -384,6 +384,39 @@ public: void setLibraryOwnerID(const LLUUID& id); void setLibraryRootFolderID(const LLUUID& id); + + /** + * Changes items order by insertion of the item identified by src_item_id + * BEFORE the item identified by dest_item_id. Both items must exist in items array. + * + * Sorting is stored after method is finished. Only src_item_id is moved before dest_item_id. + * + * @param[in, out] items - vector with items to be updated. It should be sorted in a right way + * before calling this method. + * @param src_item_id - LLUUID of inventory item to be moved in new position + * @param dest_item_id - LLUUID of inventory item before which source item should be placed. + */ + static void updateItemsOrder(LLInventoryModel::item_array_t& items, const LLUUID& src_item_id, const LLUUID& dest_item_id); + + /** + * Saves current order of the passed items using inventory item sort field. + * + * It reset items' sort fields and saves them on server. + * Is used to save order for Favorites folder. + * + * @param[in] items vector of items in order to be saved. + */ + void saveItemsOrder(const LLInventoryModel::item_array_t& items); + + /** + * Rearranges Landmarks inside Favorites folder. + * Moves source landmark before target one. + * + * @param source_item_id - LLUUID of the source item to be moved into new position + * @param target_item_id - LLUUID of the target item before which source item should be placed. + */ + void rearrangeFavoriteLandmarks(const LLUUID& source_item_id, const LLUUID& target_item_id); + protected: // Internal methods which add inventory and make sure that all of diff --git a/indra/newview/llinventoryobserver.cpp b/indra/newview/llinventoryobserver.cpp index 2d9ea21b5f..2fb8aea4e9 100644 --- a/indra/newview/llinventoryobserver.cpp +++ b/indra/newview/llinventoryobserver.cpp @@ -311,10 +311,10 @@ bool LLInventoryFetchDescendentsObserver::isEverythingComplete() const bool LLInventoryFetchDescendentsObserver::isComplete(LLViewerInventoryCategory* cat) { - S32 version = cat->getVersion(); - S32 descendents = cat->getDescendentCount(); - if((LLViewerInventoryCategory::VERSION_UNKNOWN == version) - || (LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN == descendents)) + const S32 version = cat->getVersion(); + const S32 expected_num_descendents = cat->getDescendentCount(); + if ((version == LLViewerInventoryCategory::VERSION_UNKNOWN) || + (expected_num_descendents == LLViewerInventoryCategory::DESCENDENT_COUNT_UNKNOWN)) { return false; } @@ -325,15 +325,28 @@ bool LLInventoryFetchDescendentsObserver::isComplete(LLViewerInventoryCategory* gInventory.getDirectDescendentsOf(cat->getUUID(), cats, items); if(!cats || !items) { - // bit of a hack - pretend we're done if they are gone or - // incomplete. should never know, but it would suck if this - // kept tight looping because of a corrupt memory state. + llwarns << "Category '" << cat->getName() << "' descendents corrupted, fetch failed." << llendl; + // NULL means the call failed -- cats/items map doesn't exist (note: this does NOT mean + // that the cat just doesn't have any items or subfolders). + // Unrecoverable, so just return done so that this observer can be cleared + // from memory. return true; } - S32 known = cats->count() + items->count(); - if(descendents == known) + const S32 current_num_known_descendents = cats->count() + items->count(); + + // Got the number of descendents that we were expecting, so we're done. + if (current_num_known_descendents == expected_num_descendents) + { + return true; + } + + // Error condition, but recoverable. This happens if something was added to the + // category before it was initialized, so accountForUpdate didn't update descendent + // count and thus the category thinks it has fewer descendents than it actually has. + if (current_num_known_descendents >= expected_num_descendents) { - // hey - we're done. + llwarns << "Category '" << cat->getName() << "' expected descendentcount:" << expected_num_descendents << " descendents but got descendentcount:" << current_num_known_descendents << llendl; + cat->setDescendentCount(current_num_known_descendents); return true; } return false; @@ -352,7 +365,7 @@ void LLInventoryFetchComboObserver::changed(U32 mask) continue; } if(item->isComplete()) - { + { mCompleteItems.push_back(*it); it = mIncompleteItems.erase(it); continue; diff --git a/indra/newview/llinventorypanel.cpp b/indra/newview/llinventorypanel.cpp index 164e72e621..9141d50829 100644 --- a/indra/newview/llinventorypanel.cpp +++ b/indra/newview/llinventorypanel.cpp @@ -98,10 +98,6 @@ LLInventoryPanel::LLInventoryPanel(const LLInventoryPanel::Params& p) : mCommitCallbackRegistrar.add("Inventory.AttachObject", boost::bind(&LLInventoryPanel::attachObject, this, _2)); mCommitCallbackRegistrar.add("Inventory.BeginIMSession", boost::bind(&LLInventoryPanel::beginIMSession, this)); - setBackgroundColor(LLUIColorTable::instance().getColor("InventoryBackgroundColor")); - setBackgroundVisible(TRUE); - setBackgroundOpaque(TRUE); - if (mStartFolderString != "") { mBuildDefaultHierarchy = false; @@ -433,11 +429,10 @@ void LLInventoryPanel::initializeViews() { mStartFolderID = (preferred_type != LLFolderType::FT_NONE ? gInventory.findCategoryUUIDForType(preferred_type) : LLUUID::null); } - llinfos << this << " Generating views for start folder " << mStartFolderString << llendl; rebuildViewsFor(mStartFolderID); mViewsInitialized = true; - defaultOpenInventory(); + openStartFolderOrMyInventory(); } void LLInventoryPanel::rebuildViewsFor(const LLUUID& id) @@ -491,13 +486,14 @@ void LLInventoryPanel::buildNewViews(const LLUUID& id) if (new_listener) { - LLFolderViewFolder::Params p; - p.name = new_listener->getDisplayName(); - p.icon = new_listener->getIcon(); - p.root = mFolders; - p.listener = new_listener; - p.tool_tip = p.name; - LLFolderViewFolder* folderp = LLUICtrlFactory::create(p); + LLFolderViewFolder::Params params; + params.name = new_listener->getDisplayName(); + params.icon = new_listener->getIcon(); + params.icon_open = new_listener->getOpenIcon(); + params.root = mFolders; + params.listener = new_listener; + params.tool_tip = params.name; + LLFolderViewFolder* folderp = LLUICtrlFactory::create(params); folderp->setItemSortOrder(mFolders->getSortOrder()); itemp = folderp; @@ -523,12 +519,13 @@ void LLInventoryPanel::buildNewViews(const LLUUID& id) if (new_listener) { LLFolderViewItem::Params params; - params.name(new_listener->getDisplayName()); - params.icon(new_listener->getIcon()); - params.creation_date(new_listener->getCreationDate()); - params.root(mFolders); - params.listener(new_listener); - params.rect(LLRect (0, 0, 0, 0)); + params.name = new_listener->getDisplayName(); + params.icon = new_listener->getIcon(); + params.icon_open = new_listener->getOpenIcon(); + params.creation_date = new_listener->getCreationDate(); + params.root = mFolders; + params.listener = new_listener; + params.rect = LLRect (0, 0, 0, 0); params.tool_tip = params.name; itemp = LLUICtrlFactory::create (params); } @@ -575,7 +572,7 @@ void LLInventoryPanel::buildNewViews(const LLUUID& id) } // bit of a hack to make sure the inventory is open. -void LLInventoryPanel::defaultOpenInventory() +void LLInventoryPanel::openStartFolderOrMyInventory() { if (mStartFolderString != "") { @@ -583,13 +580,17 @@ void LLInventoryPanel::defaultOpenInventory() } else { - // Get the first child (it should be "My Inventory") and - // open it up by name (just to make sure the first child is actually a folder). - LLView* first_child = mFolders->getFirstChild(); - if (first_child) + // Find My Inventory folder and open it up by name + for (LLView *child = mFolders->getFirstChild(); child; child = mFolders->findNextSibling(child)) { - const std::string& first_child_name = first_child->getName(); - mFolders->openFolder(first_child_name); + LLFolderViewFolder *fchild = dynamic_cast(child); + if (fchild && fchild->getListener() && + (fchild->getListener()->getUUID() == gInventory.getRootFolderID())) + { + const std::string& child_name = child->getName(); + mFolders->openFolder(child_name); + break; + } } } } diff --git a/indra/newview/llinventorypanel.h b/indra/newview/llinventorypanel.h index 4f7f0a79f6..09533b52f1 100644 --- a/indra/newview/llinventorypanel.h +++ b/indra/newview/llinventorypanel.h @@ -167,7 +167,7 @@ public: static LLInventoryPanel *getActiveInventoryPanel(BOOL auto_open = TRUE); protected: - void defaultOpenInventory(); // open the first level of inventory + void openStartFolderOrMyInventory(); // open the first level of inventory LLInventoryModel* mInventory; LLInventoryObserver* mInventoryObserver; diff --git a/indra/newview/lllandmarklist.cpp b/indra/newview/lllandmarklist.cpp index bd9d22c327..ce84474c05 100644 --- a/indra/newview/lllandmarklist.cpp +++ b/indra/newview/lllandmarklist.cpp @@ -59,6 +59,13 @@ LLLandmark* LLLandmarkList::getAsset(const LLUUID& asset_uuid, loaded_callback_t LLLandmark* landmark = get_ptr_in_map(mList, asset_uuid); if(landmark) { + LLVector3d dummy; + if(cb && !landmark->getGlobalPos(dummy)) + { + // landmark is not completely loaded yet + loaded_callback_map_t::value_type vt(asset_uuid, cb); + mLoadedCallbackMap.insert(vt); + } return landmark; } else diff --git a/indra/newview/lllocationhistory.cpp b/indra/newview/lllocationhistory.cpp index d910dbf718..ae1b8f8540 100644 --- a/indra/newview/lllocationhistory.cpp +++ b/indra/newview/lllocationhistory.cpp @@ -123,6 +123,12 @@ void LLLocationHistory::save() const // build filename for each user std::string resolved_filename = gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, mFilename); + if (resolved_filename.empty()) + { + llinfos << "can't get path to location history filename - probably not logged in yet." << llendl; + return; + } + // open a file for writing llofstream file (resolved_filename); if (!file.is_open()) diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp index fc9654e9ad..4e5aaeb66a 100644 --- a/indra/newview/lllogchat.cpp +++ b/indra/newview/lllogchat.cpp @@ -35,7 +35,6 @@ #include "llagent.h" #include "llagentui.h" #include "lllogchat.h" -#include "llfloaterchat.h" #include "lltrans.h" #include "llviewercontrol.h" @@ -59,9 +58,6 @@ const static std::string NEW_LINE_SPACE_PREFIX("\n "); const static std::string TWO_SPACES(" "); const static std::string MULTI_LINE_PREFIX(" "); -//viewer 1.23 may have used "You" for Agent's entries -const static std::string YOU("You"); - /** * Chat log lines - timestamp and name are optional but message text is mandatory. * diff --git a/indra/newview/llmanip.cpp b/indra/newview/llmanip.cpp index f30821cacf..a96240e31c 100644 --- a/indra/newview/llmanip.cpp +++ b/indra/newview/llmanip.cpp @@ -436,7 +436,7 @@ void LLManip::renderXYZ(const LLVector3 &vec) glPushMatrix(); { - LLUIImagePtr imagep = LLUI::getUIImage("rounded_square.tga"); + LLUIImagePtr imagep = LLUI::getUIImage("Rounded_Square"); gViewerWindow->setup2DRender(); const LLVector2& display_scale = gViewerWindow->getDisplayScale(); glScalef(display_scale.mV[VX], display_scale.mV[VY], 1.f); diff --git a/indra/newview/llmediactrl.cpp b/indra/newview/llmediactrl.cpp index 93f926b5d0..87ebce1d34 100644 --- a/indra/newview/llmediactrl.cpp +++ b/indra/newview/llmediactrl.cpp @@ -84,7 +84,6 @@ LLMediaCtrl::LLMediaCtrl( const Params& p) : mHomePageUrl( "" ), mIgnoreUIScale( true ), mAlwaysRefresh( false ), - mExternalUrl( "" ), mMediaSource( 0 ), mTakeFocusOnClick( true ), mCurrentNavUrl( "" ), @@ -632,6 +631,7 @@ bool LLMediaCtrl::ensureMediaSourceExists() mMediaSource->setHomeURL(mHomePageUrl); mMediaSource->setVisible( getVisible() ); mMediaSource->addObserver( this ); + mMediaSource->setBackgroundColor( getBackgroundColor() ); if(mClearCache) { mMediaSource->clearCache(); @@ -671,34 +671,12 @@ LLPluginClassMedia* LLMediaCtrl::getMediaPlugin() // void LLMediaCtrl::draw() { - LLPluginClassMedia* media_plugin = NULL; - - if(mMediaSource && mMediaSource->hasMedia()) - { - media_plugin = mMediaSource->getMediaPlugin(); - } - else - { - return; - } - - if(!media_plugin || (!media_plugin->textureValid())) - { - // Don't try to draw without a valid texture - return; - } - - LLViewerMediaTexture* media_texture = LLViewerTextureManager::findMediaTexture(mMediaTextureID); - - if (!media_texture ) - return; - if ( gRestoreGL == 1 ) { LLRect r = getRect(); reshape( r.getWidth(), r.getHeight(), FALSE ); return; - }; + } // NOTE: optimization needed here - probably only need to do this once // unless tearoffs change the parent which they probably do. @@ -712,125 +690,161 @@ void LLMediaCtrl::draw() setFrequentUpdates( false ); }; + bool draw_media = false; + + LLPluginClassMedia* media_plugin = NULL; + LLViewerMediaTexture* media_texture = NULL; + + if(mMediaSource && mMediaSource->hasMedia()) + { + media_plugin = mMediaSource->getMediaPlugin(); + + if(media_plugin && (media_plugin->textureValid())) + { + media_texture = LLViewerTextureManager::findMediaTexture(mMediaTextureID); + if(media_texture) + { + draw_media = true; + } + } + } + if(mHidingInitialLoad) { // If we're hiding loading, don't draw at all. - return; + draw_media = false; } - // alpha off for this - LLGLSUIDefault gls_ui; - LLGLDisable gls_alphaTest( GL_ALPHA_TEST ); - - gGL.pushMatrix(); + bool background_visible = isBackgroundVisible(); + bool background_opaque = isBackgroundOpaque(); + + if(draw_media) { - if (mIgnoreUIScale) + // alpha off for this + LLGLSUIDefault gls_ui; + LLGLDisable gls_alphaTest( GL_ALPHA_TEST ); + + gGL.pushMatrix(); { - glLoadIdentity(); - // font system stores true screen origin, need to scale this by UI scale factor - // to get render origin for this view (with unit scale) - gGL.translatef(floorf(LLFontGL::sCurOrigin.mX * LLUI::sGLScaleFactor.mV[VX]), - floorf(LLFontGL::sCurOrigin.mY * LLUI::sGLScaleFactor.mV[VY]), - LLFontGL::sCurOrigin.mZ); - } + if (mIgnoreUIScale) + { + glLoadIdentity(); + // font system stores true screen origin, need to scale this by UI scale factor + // to get render origin for this view (with unit scale) + gGL.translatef(floorf(LLFontGL::sCurOrigin.mX * LLUI::sGLScaleFactor.mV[VX]), + floorf(LLFontGL::sCurOrigin.mY * LLUI::sGLScaleFactor.mV[VY]), + LLFontGL::sCurOrigin.mZ); + } - // scale texture to fit the space using texture coords - gGL.getTexUnit(0)->bind(media_texture); - gGL.color4fv( LLColor4::white.mV ); - F32 max_u = ( F32 )media_plugin->getWidth() / ( F32 )media_plugin->getTextureWidth(); - F32 max_v = ( F32 )media_plugin->getHeight() / ( F32 )media_plugin->getTextureHeight(); + // scale texture to fit the space using texture coords + gGL.getTexUnit(0)->bind(media_texture); + gGL.color4fv( LLColor4::white.mV ); + F32 max_u = ( F32 )media_plugin->getWidth() / ( F32 )media_plugin->getTextureWidth(); + F32 max_v = ( F32 )media_plugin->getHeight() / ( F32 )media_plugin->getTextureHeight(); - LLRect r = getRect(); - S32 width, height; - S32 x_offset = 0; - S32 y_offset = 0; - - if(mStretchToFill) - { - if(mMaintainAspectRatio) + LLRect r = getRect(); + S32 width, height; + S32 x_offset = 0; + S32 y_offset = 0; + + if(mStretchToFill) { - F32 media_aspect = (F32)(media_plugin->getWidth()) / (F32)(media_plugin->getHeight()); - F32 view_aspect = (F32)(r.getWidth()) / (F32)(r.getHeight()); - if(media_aspect > view_aspect) + if(mMaintainAspectRatio) { - // max width, adjusted height - width = r.getWidth(); - height = llmin(llmax(llround(width / media_aspect), 0), r.getHeight()); + F32 media_aspect = (F32)(media_plugin->getWidth()) / (F32)(media_plugin->getHeight()); + F32 view_aspect = (F32)(r.getWidth()) / (F32)(r.getHeight()); + if(media_aspect > view_aspect) + { + // max width, adjusted height + width = r.getWidth(); + height = llmin(llmax(llround(width / media_aspect), 0), r.getHeight()); + } + else + { + // max height, adjusted width + height = r.getHeight(); + width = llmin(llmax(llround(height * media_aspect), 0), r.getWidth()); + } } else { - // max height, adjusted width + width = r.getWidth(); height = r.getHeight(); - width = llmin(llmax(llround(height * media_aspect), 0), r.getWidth()); } } else { - width = r.getWidth(); - height = r.getHeight(); + width = llmin(media_plugin->getWidth(), r.getWidth()); + height = llmin(media_plugin->getHeight(), r.getHeight()); } - } - else - { - width = llmin(media_plugin->getWidth(), r.getWidth()); - height = llmin(media_plugin->getHeight(), r.getHeight()); - } - - x_offset = (r.getWidth() - width) / 2; - y_offset = (r.getHeight() - height) / 2; + + x_offset = (r.getWidth() - width) / 2; + y_offset = (r.getHeight() - height) / 2; - if(mIgnoreUIScale) - { - x_offset = llround((F32)x_offset * LLUI::sGLScaleFactor.mV[VX]); - y_offset = llround((F32)y_offset * LLUI::sGLScaleFactor.mV[VY]); - width = llround((F32)width * LLUI::sGLScaleFactor.mV[VX]); - height = llround((F32)height * LLUI::sGLScaleFactor.mV[VY]); - } + if(mIgnoreUIScale) + { + x_offset = llround((F32)x_offset * LLUI::sGLScaleFactor.mV[VX]); + y_offset = llround((F32)y_offset * LLUI::sGLScaleFactor.mV[VY]); + width = llround((F32)width * LLUI::sGLScaleFactor.mV[VX]); + height = llround((F32)height * LLUI::sGLScaleFactor.mV[VY]); + } - // draw the browser - gGL.setSceneBlendType(LLRender::BT_REPLACE); - gGL.begin( LLRender::QUADS ); - if (! media_plugin->getTextureCoordsOpenGL()) - { - // render using web browser reported width and height, instead of trying to invert GL scale - gGL.texCoord2f( max_u, 0.f ); - gGL.vertex2i( x_offset + width, y_offset + height ); + // draw the browser + gGL.setSceneBlendType(LLRender::BT_REPLACE); + gGL.begin( LLRender::QUADS ); + if (! media_plugin->getTextureCoordsOpenGL()) + { + // render using web browser reported width and height, instead of trying to invert GL scale + gGL.texCoord2f( max_u, 0.f ); + gGL.vertex2i( x_offset + width, y_offset + height ); - gGL.texCoord2f( 0.f, 0.f ); - gGL.vertex2i( x_offset, y_offset + height ); + gGL.texCoord2f( 0.f, 0.f ); + gGL.vertex2i( x_offset, y_offset + height ); - gGL.texCoord2f( 0.f, max_v ); - gGL.vertex2i( x_offset, y_offset ); + gGL.texCoord2f( 0.f, max_v ); + gGL.vertex2i( x_offset, y_offset ); - gGL.texCoord2f( max_u, max_v ); - gGL.vertex2i( x_offset + width, y_offset ); - } - else - { - // render using web browser reported width and height, instead of trying to invert GL scale - gGL.texCoord2f( max_u, max_v ); - gGL.vertex2i( x_offset + width, y_offset + height ); + gGL.texCoord2f( max_u, max_v ); + gGL.vertex2i( x_offset + width, y_offset ); + } + else + { + // render using web browser reported width and height, instead of trying to invert GL scale + gGL.texCoord2f( max_u, max_v ); + gGL.vertex2i( x_offset + width, y_offset + height ); - gGL.texCoord2f( 0.f, max_v ); - gGL.vertex2i( x_offset, y_offset + height ); + gGL.texCoord2f( 0.f, max_v ); + gGL.vertex2i( x_offset, y_offset + height ); - gGL.texCoord2f( 0.f, 0.f ); - gGL.vertex2i( x_offset, y_offset ); + gGL.texCoord2f( 0.f, 0.f ); + gGL.vertex2i( x_offset, y_offset ); - gGL.texCoord2f( max_u, 0.f ); - gGL.vertex2i( x_offset + width, y_offset ); + gGL.texCoord2f( max_u, 0.f ); + gGL.vertex2i( x_offset + width, y_offset ); + } + gGL.end(); + gGL.setSceneBlendType(LLRender::BT_ALPHA); } - gGL.end(); - gGL.setSceneBlendType(LLRender::BT_ALPHA); + gGL.popMatrix(); + } - gGL.popMatrix(); - + else + { + // Setting these will make LLPanel::draw draw the opaque background color. + setBackgroundVisible(true); + setBackgroundOpaque(true); + } + // highlight if keyboard focus here. (TODO: this needs some work) if ( mBorder && mBorder->getVisible() ) mBorder->setKeyboardFocusHighlight( gFocusMgr.childHasKeyboardFocus( this ) ); LLPanel::draw(); + + // Restore the previous values + setBackgroundVisible(background_visible); + setBackgroundOpaque(background_opaque); } //////////////////////////////////////////////////////////////////////////////// @@ -862,9 +876,27 @@ bool LLMediaCtrl::onClickLinkExternalTarget(const LLSD& notification, const LLSD S32 option = LLNotificationsUtil::getSelectedOption(notification, response); if ( 0 == option ) { - // open in external browser because we don't support - // creation of our own secondary browser windows - LLWeb::loadURLExternal( notification["payload"]["external_url"].asString() ); + LLSD payload = notification["payload"]; + std::string url = payload["url"].asString(); + S32 target_type = payload["target_type"].asInteger(); + + switch (target_type) + { + case LLPluginClassMedia::TARGET_EXTERNAL: + // load target in an external browser + LLWeb::loadURLExternal(url); + break; + + case LLPluginClassMedia::TARGET_BLANK: + // load target in the user's preferred browser + LLWeb::loadURL(url); + break; + + default: + // unsupported link target - shouldn't happen + LL_WARNS("LinkTarget") << "Unsupported link target type" << LL_ENDL; + break; + } } return false; } @@ -978,20 +1010,18 @@ void LLMediaCtrl::handleMediaEvent(LLPluginClassMedia* self, EMediaEvent event) void LLMediaCtrl::onClickLinkHref( LLPluginClassMedia* self ) { // retrieve the event parameters - std::string target = self->getClickTarget(); std::string url = self->getClickURL(); + U32 target_type = self->getClickTargetType(); - // if there is a value for the target - if ( !target.empty() ) + // is there is a target specified for the link? + if (target_type == LLPluginClassMedia::TARGET_EXTERNAL || + target_type == LLPluginClassMedia::TARGET_BLANK) { - if ( target == "_external" ) - { - mExternalUrl = url; - LLSD payload; - payload["external_url"] = mExternalUrl; - LLNotificationsUtil::add( "WebLaunchExternalTarget", LLSD(), payload, onClickLinkExternalTarget); - return; - } + LLSD payload; + payload["url"] = url; + payload["target_type"] = LLSD::Integer(target_type); + LLNotificationsUtil::add( "WebLaunchExternalTarget", LLSD(), payload, onClickLinkExternalTarget); + return; } const std::string protocol1( "http://" ); diff --git a/indra/newview/llmediactrl.h b/indra/newview/llmediactrl.h index 8f9e6e7179..b0aca3cfa4 100644 --- a/indra/newview/llmediactrl.h +++ b/indra/newview/llmediactrl.h @@ -182,7 +182,6 @@ public: bool mOpenLinksInInternalBrowser; bool mTrusted; std::string mHomePageUrl; - std::string mExternalUrl; std::string mCurrentNavUrl; bool mIgnoreUIScale; bool mAlwaysRefresh; diff --git a/indra/newview/llmenucommands.cpp b/indra/newview/llmenucommands.cpp index a2aef9ba63..8d950f072d 100644 --- a/indra/newview/llmenucommands.cpp +++ b/indra/newview/llmenucommands.cpp @@ -45,8 +45,7 @@ #include "llagent.h" #include "llcallingcard.h" #include "llviewercontrol.h" -#include "llfirstuse.h" -#include "llfloaterchat.h" +//#include "llfirstuse.h" #include "llfloaterworldmap.h" #include "lllineeditor.h" #include "llstatusbar.h" diff --git a/indra/newview/llmorphview.cpp b/indra/newview/llmorphview.cpp index f562e45770..b95e8bd3a2 100644 --- a/indra/newview/llmorphview.cpp +++ b/indra/newview/llmorphview.cpp @@ -40,7 +40,7 @@ #include "lldrawable.h" #include "lldrawpoolavatar.h" #include "llface.h" -#include "llfirstuse.h" +//#include "llfirstuse.h" #include "llfloatercustomize.h" #include "llfloatertools.h" #include "llresmgr.h" @@ -143,7 +143,7 @@ void LLMorphView::setVisible(BOOL visible) initialize(); // First run dialog - LLFirstUse::useAppearance(); + //LLFirstUse::useAppearance(); } else { diff --git a/indra/newview/llmutelist.cpp b/indra/newview/llmutelist.cpp index b520bc1c2d..7ee4c64f8f 100644 --- a/indra/newview/llmutelist.cpp +++ b/indra/newview/llmutelist.cpp @@ -64,7 +64,7 @@ #include "llviewerwindow.h" #include "llworld.h" //for particle system banning #include "llchat.h" -#include "llfloaterchat.h" +#include "llimpanel.h" #include "llimview.h" #include "llnotifications.h" #include "lluistring.h" @@ -258,7 +258,7 @@ LLMuteList::~LLMuteList() { // 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\ JC + // C:\Program Files\SecondLife\ or similar. JC std::string user_dir = gDirUtilp->getLindenUserDir(); if (!user_dir.empty()) { @@ -532,9 +532,6 @@ void notify_automute_callback(const LLUUID& agent_id, const std::string& first_n LLIMModel::getInstance()->addMessage(agent_id, SYSTEM_FROM, LLUUID::null, message); } - - LLChat auto_chat(message); - LLFloaterChat::addChat(auto_chat, FALSE, FALSE); } } diff --git a/indra/newview/llnamebox.cpp b/indra/newview/llnamebox.cpp index 2f4a266198..cd810b9793 100644 --- a/indra/newview/llnamebox.cpp +++ b/indra/newview/llnamebox.cpp @@ -52,6 +52,8 @@ LLNameBox::LLNameBox(const Params& p) : LLTextBox(p) { mNameID = LLUUID::null; + mLink = p.link; + mInitialValue = p.initial_value().asString(); LLNameBox::sInstances.insert(this); setText(LLStringUtil::null); } @@ -66,17 +68,23 @@ void LLNameBox::setNameID(const LLUUID& name_id, BOOL is_group) mNameID = name_id; std::string name; + BOOL got_name = FALSE; if (!is_group) { - gCacheName->getFullName(name_id, name); + got_name = gCacheName->getFullName(name_id, name); } else { - gCacheName->getGroupName(name_id, name); + got_name = gCacheName->getGroupName(name_id, name); } - setText(name); + // Got the name already? Set it. + // Otherwise it will be set later in refresh(). + if (got_name) + setName(name, is_group); + else + setText(mInitialValue); } void LLNameBox::refresh(const LLUUID& id, const std::string& firstname, @@ -93,7 +101,7 @@ void LLNameBox::refresh(const LLUUID& id, const std::string& firstname, { name = firstname; } - setText(name); + setName(name, is_group); } } @@ -109,3 +117,22 @@ void LLNameBox::refreshAll(const LLUUID& id, const std::string& firstname, box->refresh(id, firstname, lastname, is_group); } } + +void LLNameBox::setName(const std::string& name, BOOL is_group) +{ + if (mLink) + { + std::string url; + + if (is_group) + url = "[secondlife:///app/group/" + LLURI::escape(name) + "/about " + name + "]"; + else + url = "[secondlife:///app/agent/" + mNameID.asString() + "/about " + name + "]"; + + setText(url); + } + else + { + setText(name); + } +} diff --git a/indra/newview/llnamebox.h b/indra/newview/llnamebox.h index 3edb36883f..48b54faec8 100644 --- a/indra/newview/llnamebox.h +++ b/indra/newview/llnamebox.h @@ -47,9 +47,11 @@ public: struct Params : public LLInitParam::Block { Optional is_group; + Optional link; Params() : is_group("is_group", false) + , link("link", false) {} }; @@ -67,10 +69,14 @@ protected: friend class LLUICtrlFactory; private: + void setName(const std::string& name, BOOL is_group); + static std::set sInstances; private: LLUUID mNameID; + BOOL mLink; + std::string mInitialValue; }; diff --git a/indra/newview/llnamelistctrl.cpp b/indra/newview/llnamelistctrl.cpp index 7e6145f578..6375362ae2 100644 --- a/indra/newview/llnamelistctrl.cpp +++ b/indra/newview/llnamelistctrl.cpp @@ -74,12 +74,12 @@ void LLNameListCtrl::addNameItem(const LLUUID& agent_id, EAddPosition pos, { //llinfos << "LLNameListCtrl::addNameItem " << agent_id << llendl; - std::string fullname; - gCacheName->getFullName(agent_id, fullname); - - fullname.append(suffix); + NameItem item; + item.value = agent_id; + item.enabled = enabled; + item.target = INDIVIDUAL; - addStringUUIDItem(fullname, agent_id, pos, enabled); + addNameItemRow(item, pos); } // virtual, public @@ -130,11 +130,12 @@ BOOL LLNameListCtrl::handleDragAndDrop( return handled; } -void LLNameListCtrl::showAvatarInspector(const LLUUID& avatar_id) +void LLNameListCtrl::showInspector(const LLUUID& avatar_id, bool is_group) { - LLSD key; - key["avatar_id"] = avatar_id; - LLFloaterReg::showInstance("inspect_avatar", key); + if (is_group) + LLFloaterReg::showInstance("inspect_group", LLSD().with("group_id", avatar_id)); + else + LLFloaterReg::showInstance("inspect_avatar", LLSD().with("avatar_id", avatar_id)); } //virtual @@ -147,7 +148,7 @@ BOOL LLNameListCtrl::handleToolTip(S32 x, S32 y, MASK mask) && column_index == mNameColumnIndex) { // ...this is the column with the avatar name - LLUUID avatar_id = hit_item->getValue().asUUID(); + LLUUID avatar_id = getItemId(hit_item); if (avatar_id.notNull()) { // ...valid avatar id @@ -164,9 +165,12 @@ BOOL LLNameListCtrl::handleToolTip(S32 x, S32 y, MASK mask) LLCoordGL pos( sticky_rect.mRight - 16, sticky_rect.mTop ); LLPointer icon = LLUI::getUIImage("Info_Small"); + // Should we show a group or an avatar inspector? + bool is_group = hit_item->getValue()["is_group"].asBoolean(); + LLToolTip::Params params; params.background_visible( false ); - params.click_callback( boost::bind(&LLNameListCtrl::showAvatarInspector, this, avatar_id) ); + params.click_callback( boost::bind(&LLNameListCtrl::showInspector, this, avatar_id, is_group) ); params.delay_time(0.0f); // spawn instantly on hover params.image( icon ); params.message(""); @@ -220,9 +224,22 @@ LLScrollListItem* LLNameListCtrl::addElement(const LLSD& element, EAddPosition p } -LLScrollListItem* LLNameListCtrl::addNameItemRow(const LLNameListCtrl::NameItem& name_item, EAddPosition pos) +LLScrollListItem* LLNameListCtrl::addNameItemRow( + const LLNameListCtrl::NameItem& name_item, + EAddPosition pos, + std::string& suffix) { - LLScrollListItem* item = LLScrollListCtrl::addRow(name_item, pos); + LLUUID id = name_item.value().asUUID(); + LLScrollListItem* item = NULL; + + // Store item type so that we can invoke the proper inspector. + // *TODO Vadim: Is there a more proper way of storing additional item data? + { + LLNameListCtrl::NameItem name_item_(name_item); + name_item_.value = LLSD().with("uuid", id).with("is_group", name_item.target() == GROUP); + item = LLScrollListCtrl::addRow(name_item_, pos); + } + if (!item) return NULL; // use supplied name by default @@ -230,7 +247,7 @@ LLScrollListItem* LLNameListCtrl::addNameItemRow(const LLNameListCtrl::NameItem& switch(name_item.target) { case GROUP: - gCacheName->getGroupName(name_item.value().asUUID(), fullname); + gCacheName->getGroupName(id, fullname); // fullname will be "nobody" if group not found break; case SPECIAL: @@ -239,7 +256,7 @@ LLScrollListItem* LLNameListCtrl::addNameItemRow(const LLNameListCtrl::NameItem& case INDIVIDUAL: { std::string name; - if (gCacheName->getFullName(name_item.value().asUUID(), name)) + if (gCacheName->getFullName(id, name)) { fullname = name; } @@ -249,6 +266,12 @@ LLScrollListItem* LLNameListCtrl::addNameItemRow(const LLNameListCtrl::NameItem& break; } + // Append optional suffix. + if (!suffix.empty()) + { + fullname.append(suffix); + } + LLScrollListCell* cell = item->getColumn(mNameColumnIndex); if (cell) { @@ -270,15 +293,24 @@ LLScrollListItem* LLNameListCtrl::addNameItemRow(const LLNameListCtrl::NameItem& // public void LLNameListCtrl::removeNameItem(const LLUUID& agent_id) { - BOOL item_exists = selectByID( agent_id ); - if(item_exists) + // Find the item specified with agent_id. + S32 idx = -1; + for (item_list::iterator it = getItemList().begin(); it != getItemList().end(); it++) { - S32 index = getItemIndex(getFirstSelected()); - if(index >= 0) + LLScrollListItem* item = *it; + if (getItemId(item) == agent_id) { - deleteSingleItem(index); + idx = getItemIndex(item); + break; } } + + // Remove it. + if (idx >= 0) + { + selectNthItem(idx); // not sure whether this is needed, taken from previous implementation + deleteSingleItem(idx); + } } // public @@ -303,7 +335,7 @@ void LLNameListCtrl::refresh(const LLUUID& id, const std::string& first, for (iter = getItemList().begin(); iter != getItemList().end(); iter++) { LLScrollListItem* item = *iter; - if (item->getUUID() == id) + if (getItemId(item) == id) { LLScrollListCell* cell = (LLScrollListCell*)item->getColumn(0); cell = item->getColumn(mNameColumnIndex); @@ -343,3 +375,9 @@ void LLNameListCtrl::updateColumns() } } } + +// static +LLUUID LLNameListCtrl::getItemId(LLScrollListItem* item) +{ + return item->getValue()["uuid"].asUUID(); +} diff --git a/indra/newview/llnamelistctrl.h b/indra/newview/llnamelistctrl.h index d0f0ec4d21..192a3a5afa 100644 --- a/indra/newview/llnamelistctrl.h +++ b/indra/newview/llnamelistctrl.h @@ -94,7 +94,7 @@ public: void addNameItem(NameItem& item, EAddPosition pos = ADD_BOTTOM); /*virtual*/ LLScrollListItem* addElement(const LLSD& element, EAddPosition pos = ADD_BOTTOM, void* userdata = NULL); - LLScrollListItem* addNameItemRow(const NameItem& value, EAddPosition pos = ADD_BOTTOM); + LLScrollListItem* addNameItemRow(const NameItem& value, EAddPosition pos = ADD_BOTTOM, std::string& suffix = LLStringUtil::null); // Add a user to the list by name. It will be added, the name // requested from the cache, and updated as necessary. @@ -121,7 +121,8 @@ public: /*virtual*/ void updateColumns(); private: - void showAvatarInspector(const LLUUID& avatar_id); + void showInspector(const LLUUID& avatar_id, bool is_group); + static LLUUID getItemId(LLScrollListItem* item); private: S32 mNameColumnIndex; diff --git a/indra/newview/llnearbychat.cpp b/indra/newview/llnearbychat.cpp index 2ad82d3e8e..a7c1e73328 100644 --- a/indra/newview/llnearbychat.cpp +++ b/indra/newview/llnearbychat.cpp @@ -63,7 +63,7 @@ static const S32 RESIZE_BAR_THICKNESS = 3; LLNearbyChat::LLNearbyChat(const LLSD& key) - : LLDockableFloater(NULL, false, key) + : LLDockableFloater(NULL, false, false, key) ,mChatHistory(NULL) { @@ -137,7 +137,7 @@ std::string appendTime() time_t utc_time; utc_time = time_corrected(); std::string timeStr ="["+ LLTrans::getString("TimeHour")+"]:[" - +LLTrans::getString("TimeMin")+"] "; + +LLTrans::getString("TimeMin")+"]"; LLSD substitution; @@ -178,28 +178,8 @@ void LLNearbyChat::addMessage(const LLChat& chat,bool archive) if (!chat.mMuted) { - tmp_chat.mFromName = chat.mFromID != gAgentID ? chat.mFromName : LLTrans::getString("You"); - - if (chat.mChatStyle == CHAT_STYLE_IRC) - { - LLColor4 txt_color = LLUIColorTable::instance().getColor("White"); - LLViewerChat::getChatColor(chat,txt_color); - LLFontGL* fontp = LLViewerChat::getChatFont(); - std::string font_name = LLFontGL::nameFromFont(fontp); - std::string font_size = LLFontGL::sizeFromFont(fontp); - LLStyle::Params append_style_params; - append_style_params.color(txt_color); - append_style_params.readonly_color(txt_color); - append_style_params.font.name(font_name); - append_style_params.font.size(font_size); - append_style_params.font.style = "ITALIC"; - - mChatHistory->appendMessage(chat, use_plain_text_chat_history, append_style_params); - } - else - { - mChatHistory->appendMessage(chat, use_plain_text_chat_history); - } + tmp_chat.mFromName = chat.mFromName; + mChatHistory->appendMessage(chat, use_plain_text_chat_history); } if(archive) diff --git a/indra/newview/llnearbychatbar.cpp b/indra/newview/llnearbychatbar.cpp index 8dbaa5ac53..6cf8bcb417 100644 --- a/indra/newview/llnearbychatbar.cpp +++ b/indra/newview/llnearbychatbar.cpp @@ -48,13 +48,16 @@ #include "llcommandhandler.h" #include "llviewercontrol.h" #include "llnavigationbar.h" +#include "llwindow.h" +#include "llviewerwindow.h" +#include "llrootview.h" S32 LLNearbyChatBar::sLastSpecialChatChannel = 0; // legacy callback glue void send_chat_from_viewer(const std::string& utf8_out_text, EChatType type, S32 channel); -static LLDefaultChildRegistry::Register r("gesture_combo_box"); +static LLDefaultChildRegistry::Register r("gesture_combo_list"); struct LLChatTypeTrigger { std::string name; @@ -66,13 +69,42 @@ static LLChatTypeTrigger sChatTypeTriggers[] = { { "/shout" , CHAT_TYPE_SHOUT} }; -LLGestureComboBox::LLGestureComboBox(const LLGestureComboBox::Params& p) - : LLComboBox(p) - , mGestureLabelTimer() +LLGestureComboList::Params::Params() +: combo_button("combo_button"), + combo_list("combo_list") +{ +} + +LLGestureComboList::LLGestureComboList(const LLGestureComboList::Params& p) +: LLUICtrl(p) , mLabel(p.label) , mViewAllItemIndex(0) { - setCommitCallback(boost::bind(&LLGestureComboBox::onCommitGesture, this)); + LLButton::Params button_params = p.combo_button; + button_params.follows.flags(FOLLOWS_LEFT|FOLLOWS_BOTTOM|FOLLOWS_RIGHT); + + mButton = LLUICtrlFactory::create(button_params); + mButton->reshape(getRect().getWidth(),getRect().getHeight()); + mButton->setCommitCallback(boost::bind(&LLGestureComboList::onButtonCommit, this)); + + addChild(mButton); + + LLScrollListCtrl::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); + + // *HACK: adding list as a child to NonSideTrayView to make it fully visible without + // making it top control (because it would cause problems). + gViewerWindow->getNonSideTrayView()->addChild(mList); + mList->setVisible(FALSE); + + //****************************Gesture Part********************************/ + + setCommitCallback(boost::bind(&LLGestureComboList::onCommitGesture, this)); // now register us as observer since we have a place to put the results LLGestureManager::instance().addObserver(this); @@ -80,23 +112,139 @@ LLGestureComboBox::LLGestureComboBox(const LLGestureComboBox::Params& p) // refresh list from current active gestures refreshGestures(); - // This forces using of halign from xml, since LLComboBox - // sets it to LLFontGL::LEFT, if text entry is disabled - mButton->setHAlign(p.drop_down_button.font_halign); + setFocusLostCallback(boost::bind(&LLGestureComboList::hideList, this)); } -LLGestureComboBox::~LLGestureComboBox() +BOOL LLGestureComboList::handleKey(KEY key, MASK mask, BOOL called_from_parent) { - LLGestureManager::instance().removeObserver(this); + BOOL handled = FALSE; + + if (key == KEY_ESCAPE && mask == MASK_NONE ) + { + hideList(); + handled = TRUE; + } + else + { + handled = mList->handleKey(key, mask, called_from_parent); + } + + return handled; +} + +void LLGestureComboList::showList() +{ + LLRect rect = mList->getRect(); + LLRect screen; + mButton->localRectToScreen(getRect(), &screen); + + // Calculating amount of space between the navigation bar and gestures combo + LLNavigationBar* nb = LLNavigationBar::getInstance(); + + S32 x, nb_bottom; + nb->localPointToScreen(0, 0, &x, &nb_bottom); + + S32 max_height = nb_bottom - screen.mTop; + mList->calcColumnWidths(); + rect.setOriginAndSize(screen.mLeft, screen.mTop, llmax(mList->getMaxContentWidth(),mButton->getRect().getWidth()), max_height); + + mList->setRect(rect); + mList->fitContents( llmax(mList->getMaxContentWidth(),mButton->getRect().getWidth()), max_height); + + gFocusMgr.setKeyboardFocus(this); + + // Show the list and push the button down + mButton->setToggleState(TRUE); + mList->setVisible(TRUE); +} + +void LLGestureComboList::onButtonCommit() +{ + if (!mList->getVisible()) + { + // highlight the last selected item from the original selection before potentially selecting a new item + // as visual cue to original value of combo box + LLScrollListItem* last_selected_item = mList->getLastSelectedItem(); + if (last_selected_item) + { + mList->mouseOverHighlightNthItem(mList->getItemIndex(last_selected_item)); + } + + if (mList->getItemCount() != 0) + { + showList(); + } + } + else + { + hideList(); + } +} + +void LLGestureComboList::hideList() +{ + if (mList->getVisible()) + { + mButton->setToggleState(FALSE); + mList->setVisible(FALSE); + mList->mouseOverHighlightNthItem(-1); + gFocusMgr.setKeyboardFocus(NULL); + } +} + +S32 LLGestureComboList::getCurrentIndex() const +{ + LLScrollListItem* item = mList->getFirstSelected(); + if( item ) + { + return mList->getItemIndex( item ); + } + return -1; +} + +void LLGestureComboList::onItemSelected(const LLSD& data) +{ + const std::string name = mList->getSelectedItemLabel(); + + S32 cur_id = getCurrentIndex(); + mLastSelectedIndex = cur_id; + if (cur_id != mList->getItemCount()-1 && cur_id != -1) + { + mButton->setLabel(name); + } + + // hiding the list reasserts the old value stored in the text editor/dropdown button + hideList(); + + // commit does the reverse, asserting the value in the list + onCommit(); +} + +void LLGestureComboList::sortByName(bool ascending) +{ + mList->sortOnce(0, ascending); +} + +LLSD LLGestureComboList::getValue() const +{ + LLScrollListItem* item = mList->getFirstSelected(); + if( item ) + { + return item->getValue(); + } + else + { + return LLSD(); + } } -void LLGestureComboBox::refreshGestures() +void LLGestureComboList::refreshGestures() { //store current selection so we can maintain it LLSD cur_gesture = getValue(); - selectFirstItem(); - // clear - clearRows(); + + mList->selectFirstItem(); + mList->clearRows(); mGestures.clear(); LLGestureManager::item_map_t::const_iterator it; @@ -107,7 +255,7 @@ void LLGestureComboBox::refreshGestures() LLMultiGesture* gesture = (*it).second; if (gesture) { - addSimpleElement(gesture->mName, ADD_BOTTOM, LLSD(idx)); + mList->addSimpleElement(gesture->mName, ADD_BOTTOM, LLSD(idx)); mGestures.push_back(gesture); idx++; } @@ -117,23 +265,42 @@ void LLGestureComboBox::refreshGestures() // store index followed by the last added Gesture and add View All item at bottom mViewAllItemIndex = idx; - addSimpleElement(LLTrans::getString("ViewAllGestures"), ADD_BOTTOM, LLSD(mViewAllItemIndex)); + + mList->addSimpleElement(LLTrans::getString("ViewAllGestures"), ADD_BOTTOM, LLSD(mViewAllItemIndex)); // Insert label after sorting, at top, with separator below it - addSeparator(ADD_TOP); - addSimpleElement(mLabel, ADD_TOP); + mList->addSeparator(ADD_TOP); + mList->addSimpleElement(mLabel, ADD_TOP); if (cur_gesture.isDefined()) { - selectByValue(cur_gesture); + mList->selectByValue(cur_gesture); + } else { - selectFirstItem(); + mList->selectFirstItem(); } + + LLCtrlListInterface* gestures = getListInterface(); + LLMultiGesture* gesture = NULL; + + if (gestures) + { + S32 index = gestures->getSelectedValue().asInteger(); + if(index > 0) + gesture = mGestures.at(index); + } + + if(gesture && LLGestureManager::instance().isGesturePlaying(gesture)) + { + return; + } + + mButton->setLabel(mLabel); } -void LLGestureComboBox::onCommitGesture() +void LLGestureComboList::onCommitGesture() { LLCtrlListInterface* gestures = getListInterface(); if (gestures) @@ -164,50 +331,11 @@ void LLGestureComboBox::onCommitGesture() } } } - - mGestureLabelTimer.start(); - // free focus back to chat bar - setFocus(FALSE); } -//virtual -void LLGestureComboBox::draw() +LLGestureComboList::~LLGestureComboList() { - // HACK: Leave the name of the gesture in place for a few seconds. - const F32 SHOW_GESTURE_NAME_TIME = 2.f; - if (mGestureLabelTimer.getStarted() && mGestureLabelTimer.getElapsedTimeF32() > SHOW_GESTURE_NAME_TIME) - { - LLCtrlListInterface* gestures = getListInterface(); - if (gestures) gestures->selectFirstItem(); - mGestureLabelTimer.stop(); - } - - LLComboBox::draw(); -} - -//virtual -void LLGestureComboBox::showList() -{ - LLComboBox::showList(); - - // Calculating amount of space between the navigation bar and gestures combo - LLNavigationBar* nb = LLNavigationBar::getInstance(); - S32 x, nb_bottom; - nb->localPointToScreen(0, 0, &x, &nb_bottom); - - S32 list_bottom; - mList->localPointToScreen(0, 0, &x, &list_bottom); - - S32 max_height = nb_bottom - list_bottom; - - LLRect rect = mList->getRect(); - // List overlapped navigation bar, downsize it - if (rect.getHeight() > max_height) - { - rect.setOriginAndSize(rect.mLeft, rect.mBottom, rect.getWidth(), max_height); - mList->setRect(rect); - mList->reshape(rect.getWidth(), rect.getHeight()); - } + LLGestureManager::instance().removeObserver(this); } LLNearbyChatBar::LLNearbyChatBar() diff --git a/indra/newview/llnearbychatbar.h b/indra/newview/llnearbychatbar.h index 224118e088..d9a7403611 100644 --- a/indra/newview/llnearbychatbar.h +++ b/indra/newview/llnearbychatbar.h @@ -42,33 +42,52 @@ #include "llspeakers.h" -class LLGestureComboBox - : public LLComboBox - , public LLGestureManagerObserver +class LLGestureComboList + : public LLGestureManagerObserver + , public LLUICtrl { public: - struct Params : public LLInitParam::Block { }; + struct Params : public LLInitParam::Block + { + Optional combo_button; + Optional combo_list; + + Params(); + }; + protected: - LLGestureComboBox(const Params&); + friend class LLUICtrlFactory; + LLGestureComboList(const Params&); + std::vector mGestures; + std::string mLabel; + LLSD::Integer mViewAllItemIndex; + public: - ~LLGestureComboBox(); + ~LLGestureComboList(); + + LLCtrlListInterface* getListInterface() { return (LLCtrlListInterface*)mList; }; + virtual void showList(); + virtual void hideList(); + virtual BOOL handleKey(KEY key, MASK mask, BOOL called_from_parent); + + S32 getCurrentIndex() const; + void onItemSelected(const LLSD& data); + void sortByName(bool ascending = true); void refreshGestures(); void onCommitGesture(); - virtual void draw(); + void onButtonCommit(); + virtual LLSD getValue() const; // LLGestureManagerObserver trigger virtual void changed() { refreshGestures(); } -protected: - - virtual void showList(); +private: - LLFrameTimer mGestureLabelTimer; - std::vector mGestures; - std::string mLabel; - LLSD::Integer mViewAllItemIndex; + LLButton* mButton; + LLScrollListCtrl* mList; + S32 mLastSelectedIndex; }; class LLNearbyChatBar diff --git a/indra/newview/llnearbychathandler.cpp b/indra/newview/llnearbychathandler.cpp index 9e13a626b4..c50e049d4c 100644 --- a/indra/newview/llnearbychathandler.cpp +++ b/indra/newview/llnearbychathandler.cpp @@ -302,7 +302,6 @@ LLNearbyChatHandler::LLNearbyChatHandler(e_notification_type type, const LLSD& i channel->setCreatePanelCallback(callback); mChannel = LLChannelManager::getInstance()->addChannel(channel); - mChannel->setOverflowFormatString("You have %d unread nearby chat messages"); } LLNearbyChatHandler::~LLNearbyChatHandler() @@ -332,25 +331,25 @@ void LLNearbyChatHandler::processChat(const LLChat& chat_msg) LLChat& tmp_chat = const_cast(chat_msg); - if (tmp_chat.mChatStyle == CHAT_STYLE_IRC) - { - if(!tmp_chat.mFromName.empty()) - tmp_chat.mText = tmp_chat.mFromName + tmp_chat.mText.substr(3); - else - tmp_chat.mText = tmp_chat.mText.substr(3); - } - + LLNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat", LLSD()); { //sometimes its usefull to have no name at all... //if(tmp_chat.mFromName.empty() && tmp_chat.mFromID!= LLUUID::null) // tmp_chat.mFromName = tmp_chat.mFromID.asString(); } - - LLNearbyChat* nearby_chat = LLFloaterReg::getTypedInstance("nearby_chat", LLSD()); nearby_chat->addMessage(chat_msg); if(nearby_chat->getVisible()) return;//no need in toast if chat is visible - + + // Handle irc styled messages for toast panel + if (tmp_chat.mChatStyle == CHAT_STYLE_IRC) + { + if(!tmp_chat.mFromName.empty()) + tmp_chat.mText = tmp_chat.mFromName + tmp_chat.mText.substr(3); + else + tmp_chat.mText = tmp_chat.mText.substr(3); + } + // arrange a channel on a screen if(!mChannel->getVisible()) { diff --git a/indra/newview/llnotificationofferhandler.cpp b/indra/newview/llnotificationofferhandler.cpp index dd66a6c507..fad0c6a91e 100644 --- a/indra/newview/llnotificationofferhandler.cpp +++ b/indra/newview/llnotificationofferhandler.cpp @@ -112,8 +112,8 @@ bool LLOfferHandler::processNotification(const LLSD& notify) LLHandlerUtil::spawnIMSession(name, from_id); } - if (notification->getPayload().has("SUPPRES_TOST") - && notification->getPayload()["SUPPRES_TOST"]) + if (notification->getPayload().has("SUPPRESS_TOAST") + && notification->getPayload()["SUPPRESS_TOAST"]) { LLNotificationsUtil::cancel(notification); } diff --git a/indra/newview/lloutputmonitorctrl.cpp b/indra/newview/lloutputmonitorctrl.cpp index 63803469dd..f816dc589d 100644 --- a/indra/newview/lloutputmonitorctrl.cpp +++ b/indra/newview/lloutputmonitorctrl.cpp @@ -77,7 +77,9 @@ LLOutputMonitorCtrl::LLOutputMonitorCtrl(const LLOutputMonitorCtrl::Params& p) mImageLevel3(p.image_level_3), mAutoUpdate(p.auto_update), mSpeakerId(p.speaker_id), - mIsAgentControl(false) + mIsAgentControl(false), + mIsSwitchDirty(false), + mShouldSwitchOn(false) { //static LLUIColor output_monitor_muted_color = LLUIColorTable::instance().getColor("OutputMonitorMutedColor", LLColor4::orange); //static LLUIColor output_monitor_overdriven_color = LLUIColorTable::instance().getColor("OutputMonitorOverdrivenColor", LLColor4::red); @@ -108,6 +110,7 @@ LLOutputMonitorCtrl::LLOutputMonitorCtrl(const LLOutputMonitorCtrl::Params& p) LLOutputMonitorCtrl::~LLOutputMonitorCtrl() { LLMuteList::getInstance()->removeObserver(this); + LLSpeakingIndicatorManager::unregisterSpeakingIndicator(mSpeakerId, this); } void LLOutputMonitorCtrl::setPower(F32 val) @@ -117,6 +120,26 @@ void LLOutputMonitorCtrl::setPower(F32 val) void LLOutputMonitorCtrl::draw() { + // see also switchIndicator() + if (mIsSwitchDirty) + { + mIsSwitchDirty = false; + if (mShouldSwitchOn) + { + // just notify parent visibility may have changed + notifyParentVisibilityChanged(); + } + else + { + // make itself invisible and notify parent about this + setVisible(FALSE); + notifyParentVisibilityChanged(); + + // no needs to render for invisible element + return; + } + } + // 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 @@ -229,6 +252,7 @@ void LLOutputMonitorCtrl::setSpeakerId(const LLUUID& speaker_id) if (speaker_id.isNull() || speaker_id == mSpeakerId) return; mSpeakerId = speaker_id; + LLSpeakingIndicatorManager::registerSpeakingIndicator(mSpeakerId, this); //mute management if (mAutoUpdate) @@ -251,3 +275,42 @@ void LLOutputMonitorCtrl::onChange() // check only blocking on voice. EXT-3542 setIsMuted(LLMuteList::getInstance()->isMuted(mSpeakerId, LLMute::flagVoiceChat)); } + +// virtual +void LLOutputMonitorCtrl::switchIndicator(bool switch_on) +{ + // ensure indicator is visible in case it is not in visible chain + // to be called when parent became visible next time to notify parent that visibility is changed. + setVisible(TRUE); + + // if parent is in visible chain apply switch_on state and notify it immediately + if (getParent() && getParent()->isInVisibleChain()) + { + LL_DEBUGS("SpeakingIndicator") << "Indicator is in visible chain, notifying parent: " << mSpeakerId << LL_ENDL; + setVisible((BOOL)switch_on); + notifyParentVisibilityChanged(); + } + + // otherwise remember necessary state and mark itself as dirty. + // State will be applied i next draw when parents chain became visible. + else + { + LL_DEBUGS("SpeakingIndicator") << "Indicator is not in visible chain, parent won't be notified: " << mSpeakerId << LL_ENDL; + mIsSwitchDirty = true; + mShouldSwitchOn = switch_on; + } +} + +////////////////////////////////////////////////////////////////////////// +// PRIVATE SECTION +////////////////////////////////////////////////////////////////////////// +void LLOutputMonitorCtrl::notifyParentVisibilityChanged() +{ + LL_DEBUGS("SpeakingIndicator") << "Notify parent that visibility was changed: " << mSpeakerId << " ,new_visibility: " << getVisible() << LL_ENDL; + + LLSD params = LLSD().with("visibility_changed", getVisible()); + + notifyParent(params); +} + +// EOF diff --git a/indra/newview/lloutputmonitorctrl.h b/indra/newview/lloutputmonitorctrl.h index 85ea552a57..2bbfa251e9 100644 --- a/indra/newview/lloutputmonitorctrl.h +++ b/indra/newview/lloutputmonitorctrl.h @@ -36,6 +36,7 @@ #include "v4color.h" #include "llview.h" #include "llmutelist.h" +#include "llspeakingindicatormanager.h" class LLTextBox; class LLUICtrlFactory; @@ -45,7 +46,7 @@ class LLUICtrlFactory; // class LLOutputMonitorCtrl -: public LLView, LLMuteListObserver +: public LLView, public LLSpeakingIndicator, LLMuteListObserver { public: struct Params : public LLInitParam::Block @@ -90,7 +91,29 @@ public: //called by mute list virtual void onChange(); + /** + * Implementation of LLSpeakingIndicator interface. + * Behavior is implemented via changing visibility. + * + * If instance is in visible chain now (all parents are visible) it changes visibility + * and notify parent about this. + * + * Otherwise it marks an instance as dirty and stores necessary visibility. + * It will be applied in next draw and parent will be notified. + */ + virtual void switchIndicator(bool switch_on); + private: + + /** + * Notifies parent about changed visibility. + * + * Passes LLSD with "visibility_changed" => value. + * For now it is processed by LLAvatarListItem to update (reshape) its children. + * Implemented fo complete EXT-3976 + */ + void notifyParentVisibilityChanged(); + //static LLColor4 sColorMuted; //static LLColor4 sColorNormal; //static LLColor4 sColorOverdriven; @@ -117,6 +140,10 @@ private: /** uuid of a speaker being monitored */ LLUUID mSpeakerId; + + /** indicates if the instance is dirty and should notify parent */ + bool mIsSwitchDirty; + bool mShouldSwitchOn; }; #endif diff --git a/indra/newview/llpanelavatar.cpp b/indra/newview/llpanelavatar.cpp index f3d6dbbb46..85e95ca1d6 100644 --- a/indra/newview/llpanelavatar.cpp +++ b/indra/newview/llpanelavatar.cpp @@ -52,6 +52,7 @@ #include "llfloaterreg.h" #include "llnotificationsutil.h" #include "llvoiceclient.h" +#include "llnamebox.h" //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Class LLDropTarget @@ -594,8 +595,11 @@ void LLPanelAvatarProfile::processGroupProperties(const LLAvatarGroups* avatar_g if (it != mGroups.begin()) groups += ", "; - - std::string group_url="[secondlife:///app/group/" + it->second.asString() + "/about " + it->first + "]"; + std::string group_name = LLURI::escape(it->first); + std::string group_url= it->second.notNull() + ? "[secondlife:///app/group/" + it->second.asString() + "/about " + group_name + "]" + : getString("no_group_text"); + groups += group_url; } @@ -621,19 +625,15 @@ void LLPanelAvatarProfile::fillCommonData(const LLAvatarData* avatar_data) void LLPanelAvatarProfile::fillPartnerData(const LLAvatarData* avatar_data) { + LLNameBox* name_box = getChild("partner_text"); if (avatar_data->partner_id.notNull()) { - std::string first, last; - BOOL found = gCacheName->getName(avatar_data->partner_id, first, last); - if (found) - { - childSetTextArg("partner_text", "[FIRST]", first); - childSetTextArg("partner_text", "[LAST]", last); - } + name_box->setNameID(avatar_data->partner_id, FALSE); } else { - childSetTextArg("partner_text", "[FIRST]", getString("no_partner_text")); + name_box->setNameID(LLUUID::null, FALSE); + name_box->setText(getString("no_partner_text")); } } diff --git a/indra/newview/llpanelclassified.cpp b/indra/newview/llpanelclassified.cpp index e29320ffc2..3f5d80c123 100644 --- a/indra/newview/llpanelclassified.cpp +++ b/indra/newview/llpanelclassified.cpp @@ -72,6 +72,7 @@ #include "llviewerwindow.h" // for window width, height #include "llappviewer.h" // abortQuit() #include "lltrans.h" +#include "llstatusbar.h" const S32 MINIMUM_PRICE_FOR_LISTING = 50; // L$ const S32 MATURE_UNDEFINED = -1; @@ -1364,6 +1365,7 @@ static const S32 CB_ITEM_PG = 1; LLPanelClassifiedEdit::LLPanelClassifiedEdit() : LLPanelClassifiedInfo() , mIsNew(false) + , mCanClose(false) { } @@ -1559,7 +1561,7 @@ void LLPanelClassifiedEdit::resetControls() bool LLPanelClassifiedEdit::canClose() { - return isValidName(); + return mCanClose; } void LLPanelClassifiedEdit::sendUpdate() @@ -1676,12 +1678,23 @@ void LLPanelClassifiedEdit::onChange() void LLPanelClassifiedEdit::onSaveClick() { + mCanClose = false; + if(!isValidName()) { notifyInvalidName(); return; } + if(isNew()) + { + if(gStatusBar->getBalance() < getPriceForListing()) + { + LLNotificationsUtil::add("ClassifiedInsufficientFunds"); + return; + } + } + mCanClose = true; sendUpdate(); resetDirty(); } diff --git a/indra/newview/llpanelclassified.h b/indra/newview/llpanelclassified.h index 10fdf60bbe..e46806f576 100644 --- a/indra/newview/llpanelclassified.h +++ b/indra/newview/llpanelclassified.h @@ -340,6 +340,7 @@ protected: private: bool mIsNew; + bool mCanClose; }; #endif // LL_LLPANELCLASSIFIED_H diff --git a/indra/newview/llpanelgroup.cpp b/indra/newview/llpanelgroup.cpp index fff2575893..569d3001bf 100644 --- a/indra/newview/llpanelgroup.cpp +++ b/indra/newview/llpanelgroup.cpp @@ -151,6 +151,11 @@ BOOL LLPanelGroup::postBuild() button->setVisible(true); button->setEnabled(false); + button = getChild("btn_call"); + button->setClickedCallback(onBtnGroupCallClicked, this); + + button = getChild("btn_chat"); + button->setClickedCallback(onBtnGroupChatClicked, this); button = getChild("btn_join"); button->setVisible(false); @@ -215,6 +220,8 @@ void LLPanelGroup::reposButtons() reposButton("btn_create"); reposButton("btn_refresh"); reposButton("btn_cancel"); + reposButton("btn_chat"); + reposButton("btn_call"); } void LLPanelGroup::reshape(S32 width, S32 height, BOOL called_from_parent ) @@ -262,6 +269,18 @@ void LLPanelGroup::onBtnApply(void* user_data) self->apply(); } +void LLPanelGroup::onBtnGroupCallClicked(void* user_data) +{ + LLPanelGroup* self = static_cast(user_data); + self->callGroup(); +} + +void LLPanelGroup::onBtnGroupChatClicked(void* user_data) +{ + LLPanelGroup* self = static_cast(user_data); + self->chatGroup(); +} + void LLPanelGroup::onBtnJoin() { lldebugs << "joining group: " << mID << llendl; @@ -349,6 +368,8 @@ void LLPanelGroup::setGroupID(const LLUUID& group_id) LLButton* button_create = findChild("btn_create"); LLButton* button_join = findChild("btn_join"); LLButton* button_cancel = findChild("btn_cancel"); + LLButton* button_call = findChild("btn_call"); + LLButton* button_chat = findChild("btn_chat"); bool is_null_group_id = group_id == LLUUID::null; @@ -362,6 +383,11 @@ void LLPanelGroup::setGroupID(const LLUUID& group_id) if(button_cancel) button_cancel->setVisible(!is_null_group_id); + if(button_call) + button_call->setVisible(!is_null_group_id); + if(button_chat) + button_chat->setVisible(!is_null_group_id); + getChild("prepend_founded_by")->setVisible(!is_null_group_id); LLAccordionCtrl* tab_ctrl = findChild("group_accordion"); @@ -393,12 +419,17 @@ void LLPanelGroup::setGroupID(const LLUUID& group_id) if(tab_land->getDisplayChildren()) tab_land->changeOpenClose(tab_land->getDisplayChildren()); - tab_roles->canOpenClose(false); - tab_notices->canOpenClose(false); - tab_land->canOpenClose(false); + tab_roles->setVisible(false); + tab_notices->setVisible(false); + tab_land->setVisible(false); getChild("group_name")->setVisible(false); getChild("group_name_editor")->setVisible(true); + + if(button_call) + button_call->setVisible(false); + if(button_chat) + button_chat->setVisible(false); } else { @@ -413,19 +444,29 @@ void LLPanelGroup::setGroupID(const LLUUID& group_id) if(tab_land->getDisplayChildren()) tab_land->changeOpenClose(tab_land->getDisplayChildren()); } + + LLGroupData agent_gdatap; + bool is_member = gAgent.getGroupData(mID,agent_gdatap); - tab_roles->canOpenClose(true); - tab_notices->canOpenClose(true); - tab_land->canOpenClose(true); + tab_roles->setVisible(is_member); + tab_notices->setVisible(is_member); + tab_land->setVisible(is_member); getChild("group_name")->setVisible(true); getChild("group_name_editor")->setVisible(false); + + if(button_apply) + button_apply->setVisible(is_member); + if(button_call) + button_call->setVisible(is_member); + if(button_chat) + button_chat->setVisible(is_member); } reposButtons(); } -bool LLPanelGroup::apply(LLPanelGroupTab* tab) +bool LLPanelGroup::apply(LLPanelGroupTab* tab) { if(!tab) return false; @@ -468,12 +509,17 @@ void LLPanelGroup::draw() childEnable("btn_refresh"); } - bool enable = false; - std::string mesg; - for(std::vector::iterator it = mTabs.begin();it!=mTabs.end();++it) - enable = enable || (*it)->needsApply(mesg); + LLButton* button_apply = findChild("btn_apply"); + + if(button_apply && button_apply->getVisible()) + { + bool enable = false; + std::string mesg; + for(std::vector::iterator it = mTabs.begin();it!=mTabs.end();++it) + enable = enable || (*it)->needsApply(mesg); - childSetEnabled("btn_apply", enable); + childSetEnabled("btn_apply", enable); + } } void LLPanelGroup::refreshData() @@ -488,6 +534,15 @@ void LLPanelGroup::refreshData() mRefreshTimer.setTimerExpirySec(5); } +void LLPanelGroup::callGroup() +{ + LLGroupActions::startCall(getID()); +} + +void LLPanelGroup::chatGroup() +{ + LLGroupActions::startIM(getID()); +} void LLPanelGroup::showNotice(const std::string& subject, const std::string& message, diff --git a/indra/newview/llpanelgroup.h b/indra/newview/llpanelgroup.h index f6aefdb676..7ea5e67b44 100644 --- a/indra/newview/llpanelgroup.h +++ b/indra/newview/llpanelgroup.h @@ -74,6 +74,8 @@ public: bool apply(); void refreshData(); + void callGroup(); + void chatGroup(); virtual void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); @@ -103,6 +105,8 @@ protected: static void onBtnApply(void*); static void onBtnRefresh(void*); + static void onBtnGroupCallClicked(void*); + static void onBtnGroupChatClicked(void*); void reposButton(const std::string& name); void reposButtons(); diff --git a/indra/newview/llpanelgroupgeneral.cpp b/indra/newview/llpanelgroupgeneral.cpp index 31dfdde887..21b253223f 100644 --- a/indra/newview/llpanelgroupgeneral.cpp +++ b/indra/newview/llpanelgroupgeneral.cpp @@ -580,7 +580,6 @@ void LLPanelGroupGeneral::update(LLGroupChange gc) } } - mComboActiveTitle->resetDirty(); } // If this was just a titles update, we are done. @@ -595,8 +594,6 @@ void LLPanelGroupGeneral::update(LLGroupChange gc) { mCtrlShowInGroupList->set(gdatap->mShowInList); mCtrlShowInGroupList->setEnabled(mAllowEdit && can_change_ident); - mCtrlShowInGroupList->resetDirty(); - } if (mComboMature) { @@ -610,19 +607,16 @@ void LLPanelGroupGeneral::update(LLGroupChange gc) } mComboMature->setEnabled(mAllowEdit && can_change_ident); mComboMature->setVisible( !gAgent.isTeen() ); - mComboMature->resetDirty(); } if (mCtrlOpenEnrollment) { mCtrlOpenEnrollment->set(gdatap->mOpenEnrollment); mCtrlOpenEnrollment->setEnabled(mAllowEdit && can_change_member_opts); - mCtrlOpenEnrollment->resetDirty(); } if (mCtrlEnrollmentFee) { mCtrlEnrollmentFee->set(gdatap->mMembershipFee > 0); mCtrlEnrollmentFee->setEnabled(mAllowEdit && can_change_member_opts); - mCtrlEnrollmentFee->resetDirty(); } if (mSpinEnrollmentFee) @@ -632,7 +626,6 @@ void LLPanelGroupGeneral::update(LLGroupChange gc) mSpinEnrollmentFee->setEnabled( mAllowEdit && (fee > 0) && can_change_member_opts); - mSpinEnrollmentFee->resetDirty(); } if (mCtrlReceiveNotices) { @@ -641,7 +634,6 @@ void LLPanelGroupGeneral::update(LLGroupChange gc) { mCtrlReceiveNotices->setEnabled(mAllowEdit); } - mCtrlReceiveNotices->resetDirty(); } @@ -665,7 +657,6 @@ void LLPanelGroupGeneral::update(LLGroupChange gc) if (mEditCharter) { mEditCharter->setText(gdatap->mCharter); - mEditCharter->resetDirty(); } if (mListVisibleMembers) @@ -693,6 +684,8 @@ void LLPanelGroupGeneral::update(LLGroupChange gc) mListVisibleMembers->addElement(row); } } + + resetDirty(); } void LLPanelGroupGeneral::updateMembers() diff --git a/indra/newview/llpanelgroupnotices.cpp b/indra/newview/llpanelgroupnotices.cpp index 6210973dae..45fc3d4688 100644 --- a/indra/newview/llpanelgroupnotices.cpp +++ b/indra/newview/llpanelgroupnotices.cpp @@ -614,7 +614,7 @@ void LLPanelGroupNotices::showNotice(const std::string& subject, mViewInventoryIcon->setVisible(TRUE); std::stringstream ss; - ss << " " << inventory_name; + ss << " " << LLViewerInventoryItem::getDisplayName(inventory_name); mViewInventoryName->setText(ss.str()); mBtnOpenAttachment->setEnabled(TRUE); diff --git a/indra/newview/llpanelgrouproles.cpp b/indra/newview/llpanelgrouproles.cpp index 29b647415c..0e55ff3214 100644 --- a/indra/newview/llpanelgrouproles.cpp +++ b/indra/newview/llpanelgrouproles.cpp @@ -460,17 +460,8 @@ LLPanelGroupSubTab::~LLPanelGroupSubTab() { } -BOOL LLPanelGroupSubTab::postBuild() -{ - // Hook up the search widgets. - bool recurse = true; - mSearchEditor = getChild("filter_input", recurse); - - if (!mSearchEditor) - return FALSE; - - mSearchEditor->setCommitCallback(boost::bind(&LLPanelGroupSubTab::setSearchFilter, this, _2)); - +BOOL LLPanelGroupSubTab::postBuildSubTab(LLView* root) +{ // Get icons for later use. mActionIcons.clear(); @@ -488,6 +479,19 @@ BOOL LLPanelGroupSubTab::postBuild() { mActionIcons["partial"] = getString("power_partial_icon"); } + return TRUE; +} + +BOOL LLPanelGroupSubTab::postBuild() +{ + // Hook up the search widgets. + bool recurse = true; + mSearchEditor = getChild("filter_input", recurse); + + if (!mSearchEditor) + return FALSE; + + mSearchEditor->setCommitCallback(boost::bind(&LLPanelGroupSubTab::setSearchFilter, this, _2)); return LLPanelGroupTab::postBuild(); } @@ -567,7 +571,6 @@ bool LLPanelGroupSubTab::matchesActionSearchFilter(std::string action) void LLPanelGroupSubTab::buildActionsList(LLScrollListCtrl* ctrl, U64 allowed_by_some, U64 allowed_by_all, - icon_map_t& icons, LLUICtrl::commit_callback_t commit_callback, BOOL show_all, BOOL filter, @@ -588,7 +591,6 @@ void LLPanelGroupSubTab::buildActionsList(LLScrollListCtrl* ctrl, allowed_by_some, allowed_by_all, (*ras_it), - icons, commit_callback, show_all, filter, @@ -600,7 +602,6 @@ void LLPanelGroupSubTab::buildActionCategory(LLScrollListCtrl* ctrl, U64 allowed_by_some, U64 allowed_by_all, LLRoleActionSet* action_set, - icon_map_t& icons, LLUICtrl::commit_callback_t commit_callback, BOOL show_all, BOOL filter, @@ -614,26 +615,26 @@ void LLPanelGroupSubTab::buildActionCategory(LLScrollListCtrl* ctrl, LLSD row; row["columns"][0]["column"] = "icon"; - icon_map_t::iterator iter = icons.find("folder"); - if (iter != icons.end()) + row["columns"][0]["type"] = "icon"; + + icon_map_t::iterator iter = mActionIcons.find("folder"); + if (iter != mActionIcons.end()) { - row["columns"][0]["type"] = "icon"; row["columns"][0]["value"] = (*iter).second; } row["columns"][1]["column"] = "action"; + row["columns"][1]["type"] = "text"; row["columns"][1]["value"] = action_set->mActionSetData->mName; row["columns"][1]["font"]["name"] = "SANSSERIF_SMALL"; - row["columns"][1]["font"]["style"] = "BOLD"; + LLScrollListItem* title_row = ctrl->addElement(row, ADD_BOTTOM, action_set->mActionSetData); - LLScrollListText* name_textp = dynamic_cast(title_row->getColumn(1)); + LLScrollListText* name_textp = dynamic_cast(title_row->getColumn(2)); //?? I have no idea fix getColumn(1) return column spacer... if (name_textp) name_textp->setFontStyle(LLFontGL::BOLD); - - bool category_matches_filter = (filter) ? matchesActionSearchFilter(action_set->mActionSetData->mName) : true; std::vector::iterator ra_it = action_set->mActions.begin(); @@ -686,8 +687,8 @@ void LLPanelGroupSubTab::buildActionCategory(LLScrollListCtrl* ctrl, { if (show_full_strength) { - icon_map_t::iterator iter = icons.find("full"); - if (iter != icons.end()) + icon_map_t::iterator iter = mActionIcons.find("full"); + if (iter != mActionIcons.end()) { row["columns"][column_index]["column"] = "checkbox"; row["columns"][column_index]["type"] = "icon"; @@ -697,8 +698,8 @@ void LLPanelGroupSubTab::buildActionCategory(LLScrollListCtrl* ctrl, } else { - icon_map_t::iterator iter = icons.find("partial"); - if (iter != icons.end()) + icon_map_t::iterator iter = mActionIcons.find("partial"); + if (iter != mActionIcons.end()) { row["columns"][column_index]["column"] = "checkbox"; row["columns"][column_index]["type"] = "icon"; @@ -792,6 +793,8 @@ LLPanelGroupMembersSubTab::~LLPanelGroupMembersSubTab() BOOL LLPanelGroupMembersSubTab::postBuildSubTab(LLView* root) { + LLPanelGroupSubTab::postBuildSubTab(root); + // Upcast parent so we can ask it for sibling controls. LLPanelGroupRoles* parent = (LLPanelGroupRoles*) root; @@ -888,7 +891,6 @@ void LLPanelGroupMembersSubTab::handleMemberSelect() buildActionsList(mAllowedActionsList, allowed_by_some, allowed_by_all, - mActionIcons, NULL, FALSE, FALSE, @@ -1211,7 +1213,6 @@ void LLPanelGroupMembersSubTab::handleRoleCheck(const LLUUID& role_id, buildActionsList(mAllowedActionsList, powers_some_have, powers_all_have, - mActionIcons, NULL, FALSE, FALSE, @@ -1684,6 +1685,8 @@ LLPanelGroupRolesSubTab::~LLPanelGroupRolesSubTab() BOOL LLPanelGroupRolesSubTab::postBuildSubTab(LLView* root) { + LLPanelGroupSubTab::postBuildSubTab(root); + // Upcast parent so we can ask it for sibling controls. LLPanelGroupRoles* parent = (LLPanelGroupRoles*) root; @@ -1994,7 +1997,6 @@ void LLPanelGroupRolesSubTab::handleRoleSelect() buildActionsList(mAllowedActionsList, rd.mRolePowers, 0LL, - mActionIcons, boost::bind(&LLPanelGroupRolesSubTab::handleActionCheck, this, _1, false), TRUE, FALSE, @@ -2381,6 +2383,8 @@ LLPanelGroupActionsSubTab::~LLPanelGroupActionsSubTab() BOOL LLPanelGroupActionsSubTab::postBuildSubTab(LLView* root) { + LLPanelGroupSubTab::postBuildSubTab(root); + // Upcast parent so we can ask it for sibling controls. LLPanelGroupRoles* parent = (LLPanelGroupRoles*) root; @@ -2448,7 +2452,6 @@ void LLPanelGroupActionsSubTab::update(LLGroupChange gc) buildActionsList(mActionList, GP_ALL_POWERS, GP_ALL_POWERS, - mActionIcons, NULL, FALSE, TRUE, diff --git a/indra/newview/llpanelgrouproles.h b/indra/newview/llpanelgrouproles.h index bb3c9096cf..2f81900e60 100644 --- a/indra/newview/llpanelgrouproles.h +++ b/indra/newview/llpanelgrouproles.h @@ -108,7 +108,7 @@ public: virtual BOOL postBuild(); // This allows sub-tabs to collect child widgets from a higher level in the view hierarchy. - virtual BOOL postBuildSubTab(LLView* root) { return TRUE; } + virtual BOOL postBuildSubTab(LLView* root); virtual void setSearchFilter( const std::string& filter ); @@ -117,10 +117,15 @@ public: // Helper functions bool matchesActionSearchFilter(std::string action); + + + void setFooterEnabled(BOOL enable); + + virtual void setGroupID(const LLUUID& id); +protected: void buildActionsList(LLScrollListCtrl* ctrl, U64 allowed_by_some, U64 allowed_by_all, - icon_map_t& icons, LLUICtrl::commit_callback_t commit_callback, BOOL show_all, BOOL filter, @@ -129,15 +134,11 @@ public: U64 allowed_by_some, U64 allowed_by_all, LLRoleActionSet* action_set, - icon_map_t& icons, LLUICtrl::commit_callback_t commit_callback, BOOL show_all, BOOL filter, BOOL is_owner_role); - void setFooterEnabled(BOOL enable); - - virtual void setGroupID(const LLUUID& id); protected: LLPanel* mHeader; LLPanel* mFooter; diff --git a/indra/newview/llpanelhome.cpp b/indra/newview/llpanelhome.cpp index 92b6d2f619..713d2d79b4 100644 --- a/indra/newview/llpanelhome.cpp +++ b/indra/newview/llpanelhome.cpp @@ -37,7 +37,7 @@ #include "llmediactrl.h" #include "llviewerhome.h" -static LLRegisterPanelClassWrapper t_people("panel_sidetray_home"); +static LLRegisterPanelClassWrapper t_home("panel_sidetray_home"); LLPanelHome::LLPanelHome() : LLPanel(), diff --git a/indra/newview/llpanelimcontrolpanel.cpp b/indra/newview/llpanelimcontrolpanel.cpp index 279818d52f..b1cdb4d81f 100644 --- a/indra/newview/llpanelimcontrolpanel.cpp +++ b/indra/newview/llpanelimcontrolpanel.cpp @@ -46,6 +46,7 @@ #include "llimview.h" #include "llvoicechannel.h" #include "llsidetray.h" +#include "llspeakers.h" #include "lltrans.h" void LLPanelChatControlPanel::onCallButtonClicked() @@ -70,9 +71,9 @@ void LLPanelChatControlPanel::onVoiceChannelStateChanged(const LLVoiceChannel::E void LLPanelChatControlPanel::updateButtons(bool is_call_started) { - childSetVisible("end_call_btn", is_call_started); - childSetVisible("voice_ctrls_btn", is_call_started); - childSetVisible("call_btn", ! is_call_started); + childSetVisible("end_call_btn_panel", is_call_started); + childSetVisible("voice_ctrls_btn_panel", is_call_started); + childSetVisible("call_btn_panel", ! is_call_started); } LLPanelChatControlPanel::~LLPanelChatControlPanel() @@ -302,7 +303,7 @@ void LLPanelGroupControlPanel::setSessionId(const LLUUID& session_id) { LLPanelChatControlPanel::setSessionId(session_id); - mGroupID = LLIMModel::getInstance()->getOtherParticipantID(session_id); + mGroupID = session_id; // for group and Ad-hoc chat we need to include agent into list if(!mParticipantList) diff --git a/indra/newview/llpanellandaudio.cpp b/indra/newview/llpanellandaudio.cpp index 920fca66f2..6a4c909759 100644 --- a/indra/newview/llpanellandaudio.cpp +++ b/indra/newview/llpanellandaudio.cpp @@ -37,6 +37,7 @@ // viewer includes #include "llmimetypes.h" #include "llviewerparcelmgr.h" +#include "llviewerregion.h" #include "lluictrlfactory.h" // library includes @@ -83,8 +84,14 @@ BOOL LLPanelLandAudio::postBuild() mCheckSoundLocal = getChild("check sound local"); childSetCommitCallback("check sound local", onCommitAny, this); - mRadioVoiceChat = getChild("parcel_voice_channel"); - childSetCommitCallback("parcel_voice_channel", onCommitAny, this); + mCheckParcelEnableVoice = getChild("parcel_enable_voice_channel"); + childSetCommitCallback("parcel_enable_voice_channel", onCommitAny, this); + + // This one is always disabled so no need for a commit callback + mCheckEstateDisabledVoice = getChild("parcel_enable_voice_channel_is_estate_disabled"); + + mCheckParcelVoiceLocal = getChild("parcel_enable_voice_channel_local"); + childSetCommitCallback("parcel_enable_voice_channel_local", onCommitAny, this); mMusicURLEdit = getChild("music_url"); childSetCommitCallback("music_url", onCommitAny, this); @@ -118,19 +125,33 @@ void LLPanelLandAudio::refresh() mMusicUrlCheck->set( parcel->getObscureMusic() ); mMusicUrlCheck->setEnabled( can_change_media ); - if(parcel->getParcelFlagAllowVoice()) + bool allow_voice = parcel->getParcelFlagAllowVoice(); + + LLViewerRegion* region = LLViewerParcelMgr::getInstance()->getSelectionRegion(); + if (region && region->isVoiceEnabled()) { - if(parcel->getParcelFlagUseEstateVoiceChannel()) - mRadioVoiceChat->setSelectedIndex(kRadioVoiceChatEstate); - else - mRadioVoiceChat->setSelectedIndex(kRadioVoiceChatPrivate); + mCheckEstateDisabledVoice->setVisible(false); + + mCheckParcelEnableVoice->setVisible(true); + mCheckParcelEnableVoice->setEnabled( can_change_media ); + mCheckParcelEnableVoice->set(allow_voice); + + mCheckParcelVoiceLocal->setEnabled( can_change_media && allow_voice ); } else { - mRadioVoiceChat->setSelectedIndex(kRadioVoiceChatDisable); + // Voice disabled at estate level, overrides parcel settings + // Replace the parcel voice checkbox with a disabled one + // labelled with an explanatory message + mCheckEstateDisabledVoice->setVisible(true); + + mCheckParcelEnableVoice->setVisible(false); + mCheckParcelEnableVoice->setEnabled(false); + mCheckParcelVoiceLocal->setEnabled(false); } - mRadioVoiceChat->setEnabled( can_change_media ); + mCheckParcelEnableVoice->set(allow_voice); + mCheckParcelVoiceLocal->set(!parcel->getParcelFlagUseEstateVoiceChannel()); mMusicURLEdit->setText(parcel->getMusicURL()); mMusicURLEdit->setEnabled( can_change_media ); @@ -149,30 +170,11 @@ void LLPanelLandAudio::onCommitAny(LLUICtrl*, void *userdata) // Extract data from UI BOOL sound_local = self->mCheckSoundLocal->get(); - int voice_setting = self->mRadioVoiceChat->getSelectedIndex(); std::string music_url = self->mMusicURLEdit->getText(); U8 obscure_music = self->mMusicUrlCheck->get(); - - BOOL voice_enabled; - BOOL voice_estate_chan; - - switch(voice_setting) - { - default: - case kRadioVoiceChatEstate: - voice_enabled = TRUE; - voice_estate_chan = TRUE; - break; - case kRadioVoiceChatPrivate: - voice_enabled = TRUE; - voice_estate_chan = FALSE; - break; - case kRadioVoiceChatDisable: - voice_enabled = FALSE; - voice_estate_chan = FALSE; - break; - } + BOOL voice_enabled = self->mCheckParcelEnableVoice->get(); + BOOL voice_estate_chan = !self->mCheckParcelVoiceLocal->get(); // Remove leading/trailing whitespace (common when copying/pasting) LLStringUtil::trim(music_url); diff --git a/indra/newview/llpanellandaudio.h b/indra/newview/llpanellandaudio.h index de5da95fa4..19766a40b6 100644 --- a/indra/newview/llpanellandaudio.h +++ b/indra/newview/llpanellandaudio.h @@ -52,7 +52,9 @@ private: private: LLCheckBoxCtrl* mCheckSoundLocal; - LLRadioGroup* mRadioVoiceChat; + LLCheckBoxCtrl* mCheckParcelEnableVoice; + LLCheckBoxCtrl* mCheckEstateDisabledVoice; + LLCheckBoxCtrl* mCheckParcelVoiceLocal; LLLineEditor* mMusicURLEdit; LLCheckBoxCtrl* mMusicUrlCheck; diff --git a/indra/newview/llpanellandmarkinfo.cpp b/indra/newview/llpanellandmarkinfo.cpp index 597b8bdb2d..9654e17659 100644 --- a/indra/newview/llpanellandmarkinfo.cpp +++ b/indra/newview/llpanellandmarkinfo.cpp @@ -185,6 +185,13 @@ void LLPanelLandmarkInfo::processParcelInfo(const LLParcelData& parcel_data) region_z = llround(mPosRegion.mV[VZ]); } + LLSD info; + info["update_verbs"] = true; + info["global_x"] = parcel_data.global_x; + info["global_y"] = parcel_data.global_y; + info["global_z"] = parcel_data.global_z; + notifyParent(info); + if (mInfoType == CREATE_LANDMARK) { if (parcel_data.name.empty()) diff --git a/indra/newview/llpanellandmarks.cpp b/indra/newview/llpanellandmarks.cpp index 30acf37f82..47feef496a 100644 --- a/indra/newview/llpanellandmarks.cpp +++ b/indra/newview/llpanellandmarks.cpp @@ -35,6 +35,7 @@ #include "llbutton.h" #include "llfloaterreg.h" +#include "llnotificationsutil.h" #include "llsdutil.h" #include "llsdutil_math.h" #include "llregionhandle.h" @@ -304,6 +305,29 @@ void LLLandmarksPanel::updateShowFolderState() ); } +void LLLandmarksPanel::setItemSelected(const LLUUID& obj_id, BOOL take_keyboard_focus) +{ + if (selectItemInAccordionTab(mFavoritesInventoryPanel, "tab_favorites", obj_id, take_keyboard_focus)) + { + return; + } + + if (selectItemInAccordionTab(mLandmarksInventoryPanel, "tab_landmarks", obj_id, take_keyboard_focus)) + { + return; + } + + if (selectItemInAccordionTab(mMyInventoryPanel, "tab_inventory", obj_id, take_keyboard_focus)) + { + return; + } + + if (selectItemInAccordionTab(mLibraryInventoryPanel, "tab_library", obj_id, take_keyboard_focus)) + { + return; + } +} + ////////////////////////////////////////////////////////////////////////// // PROTECTED METHODS ////////////////////////////////////////////////////////////////////////// @@ -349,6 +373,36 @@ LLFolderViewItem* LLLandmarksPanel::getCurSelectedItem() const return mCurrentSelectedList ? mCurrentSelectedList->getRootFolder()->getCurSelectedItem() : NULL; } +LLFolderViewItem* LLLandmarksPanel::selectItemInAccordionTab(LLPlacesInventoryPanel* inventory_list, + const std::string& tab_name, + const LLUUID& obj_id, + BOOL take_keyboard_focus) const +{ + if (!inventory_list) + return NULL; + + LLFolderView* folder_view = inventory_list->getRootFolder(); + + LLFolderViewItem* item = folder_view->getItemByID(obj_id); + if (!item) + return NULL; + + LLAccordionCtrlTab* tab = getChild(tab_name); + if (!tab->isExpanded()) + { + tab->changeOpenClose(false); + } + + folder_view->setSelection(item, FALSE, take_keyboard_focus); + + LLAccordionCtrl* accordion = getChild("landmarks_accordion"); + LLRect screen_rc; + localRectToScreen(item->getRect(), &screen_rc); + accordion->notifyParent(LLSD().with("scrollToShowRect", screen_rc.getValue())); + + return item; +} + void LLLandmarksPanel::updateSortOrder(LLInventoryPanel* panel, bool byDate) { if(!panel) return; @@ -632,8 +686,7 @@ void LLLandmarksPanel::onAddAction(const LLSD& userdata) const LLViewerInventoryItem* landmark = LLLandmarkActions::findLandmarkForAgentPos(); if(landmark) { - LLSideTray::getInstance()->showPanel("panel_places", - LLSD().with("type", "landmark").with("id",landmark->getUUID())); + LLNotificationsUtil::add("LandmarkAlreadyExists"); } else { diff --git a/indra/newview/llpanellandmarks.h b/indra/newview/llpanellandmarks.h index 569739237d..96b790844c 100644 --- a/indra/newview/llpanellandmarks.h +++ b/indra/newview/llpanellandmarks.h @@ -73,6 +73,11 @@ public: */ void updateShowFolderState(); + /** + * Selects item with "obj_id" in one of accordion tabs. + */ + void setItemSelected(const LLUUID& obj_id, BOOL take_keyboard_focus); + protected: /** * @return true - if current selected panel is not null and selected item is a landmark @@ -81,6 +86,17 @@ protected: bool isReceivedFolderSelected() const; void doActionOnCurSelectedLandmark(LLLandmarkList::loaded_callback_t cb); LLFolderViewItem* getCurSelectedItem() const; + + /** + * Selects item with "obj_id" in "inventory_list" and scrolls accordion + * scrollbar to show the item. + * Returns pointer to the item if it is found in "inventory_list", otherwise NULL. + */ + LLFolderViewItem* selectItemInAccordionTab(LLPlacesInventoryPanel* inventory_list, + const std::string& tab_name, + const LLUUID& obj_id, + BOOL take_keyboard_focus) const; + void updateSortOrder(LLInventoryPanel* panel, bool byDate); //LLRemoteParcelInfoObserver interface diff --git a/indra/newview/llpanelmaininventory.cpp b/indra/newview/llpanelmaininventory.cpp index e74a39c85c..a5a61f0c7b 100644 --- a/indra/newview/llpanelmaininventory.cpp +++ b/indra/newview/llpanelmaininventory.cpp @@ -51,6 +51,8 @@ #include "llviewermenu.h" #include "llviewertexturelist.h" +const std::string FILTERS_FILENAME("filters.xml"); + static LLRegisterPanelClassWrapper t_inventory("panel_main_inventory"); void on_file_loaded_for_save(BOOL success, @@ -160,7 +162,7 @@ BOOL LLPanelMainInventory::postBuild() // Now load the stored settings from disk, if available. std::ostringstream filterSaveName; - filterSaveName << gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "filters.xml"); + filterSaveName << gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, FILTERS_FILENAME); llinfos << "LLPanelMainInventory::init: reading from " << filterSaveName << llendl; llifstream file(filterSaveName.str()); LLSD savedFilterState; @@ -230,7 +232,7 @@ LLPanelMainInventory::~LLPanelMainInventory( void ) } std::ostringstream filterSaveName; - filterSaveName << gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, "filters.xml"); + filterSaveName << gDirUtilp->getExpandedFilename(LL_PATH_PER_SL_ACCOUNT, FILTERS_FILENAME); llofstream filtersFile(filterSaveName.str()); if(!LLSDSerialize::toPrettyXML(filterRoot, filtersFile)) { diff --git a/indra/newview/llpanelobject.cpp b/indra/newview/llpanelobject.cpp index 6a61e0f02f..d17c287cc7 100644 --- a/indra/newview/llpanelobject.cpp +++ b/indra/newview/llpanelobject.cpp @@ -73,7 +73,7 @@ #include "pipeline.h" #include "llviewercontrol.h" #include "lluictrlfactory.h" -#include "llfirstuse.h" +//#include "llfirstuse.h" #include "lldrawpool.h" @@ -682,7 +682,7 @@ void LLPanelObject::getState( ) if (objectp->getParameterEntryInUse(LLNetworkData::PARAMS_SCULPT)) { selected_item = MI_SCULPT; - LLFirstUse::useSculptedPrim(); + //LLFirstUse::useSculptedPrim(); } diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp index 43366ef814..d4376550d6 100644 --- a/indra/newview/llpanelobjectinventory.cpp +++ b/indra/newview/llpanelobjectinventory.cpp @@ -445,7 +445,7 @@ bool remove_task_inventory_callback(const LLSD& notification, const LLSD& respon } // helper for remove -// ! REFACTOR ! two_uuids_list_t is also defined in llinevntorybridge.h, but differently. +// ! REFACTOR ! two_uuids_list_t is also defined in llinventorybridge.h, but differently. typedef std::pair > panel_two_uuids_list_t; typedef std::pair remove_data_t; BOOL LLTaskInvFVBridge::removeItem() diff --git a/indra/newview/llpaneloutfitsinventory.cpp b/indra/newview/llpaneloutfitsinventory.cpp index 8e14074de1..fd5ce7a46d 100644 --- a/indra/newview/llpaneloutfitsinventory.cpp +++ b/indra/newview/llpaneloutfitsinventory.cpp @@ -35,6 +35,7 @@ #include "llagent.h" #include "llagentwearables.h" +#include "llappearancemgr.h" #include "llbutton.h" #include "llfloaterreg.h" @@ -44,6 +45,8 @@ #include "llinventoryfunctions.h" #include "llinventorypanel.h" #include "lllandmark.h" +#include "lllineeditor.h" +#include "llmodaldialog.h" #include "llsidepanelappearance.h" #include "llsidetray.h" #include "lltabcontainer.h" @@ -61,12 +64,75 @@ static LLRegisterPanelClassWrapper t_inventory("panel_outfits_inventory"); bool LLPanelOutfitsInventory::sShowDebugEditor = false; +class LLOutfitSaveAsDialog : public LLModalDialog +{ +private: + std::string mItemName; + std::string mTempItemName; + + boost::signals2::signal mSaveAsSignal; + +public: + LLOutfitSaveAsDialog( const LLSD& key ) + : LLModalDialog( key ), + mTempItemName(key.asString()) + { + } + + BOOL postBuild() + { + getChild("Save")->setCommitCallback(boost::bind(&LLOutfitSaveAsDialog::onSave, this )); + getChild("Cancel")->setCommitCallback(boost::bind(&LLOutfitSaveAsDialog::onCancel, this )); + + childSetTextArg("name ed", "[DESC]", mTempItemName); + return TRUE; + } + + void setSaveAsCommit( const boost::signals2::signal::slot_type& cb ) + { + mSaveAsSignal.connect(cb); + } + + virtual void onOpen(const LLSD& key) + { + LLLineEditor* edit = getChild("name ed"); + if (edit) + { + edit->setFocus(TRUE); + edit->selectAll(); + } + } + + void onSave() + { + mItemName = childGetValue("name ed").asString(); + LLStringUtil::trim(mItemName); + if( !mItemName.empty() ) + { + mSaveAsSignal(mItemName); + closeFloater(); // destroys this object + } + } + + void onCancel() + { + closeFloater(); // destroys this object + } +}; + LLPanelOutfitsInventory::LLPanelOutfitsInventory() : mActivePanel(NULL), mParent(NULL) { mSavedFolderState = new LLSaveFolderState(); mSavedFolderState->setApply(FALSE); + + static bool registered_dialog = false; + if (!registered_dialog) + { + LLFloaterReg::add("outfit_save_as", "floater_outfit_save_as.xml", (LLFloaterBuildFunc)&LLFloaterReg::build); + registered_dialog = true; + } } LLPanelOutfitsInventory::~LLPanelOutfitsInventory() @@ -84,6 +150,14 @@ BOOL LLPanelOutfitsInventory::postBuild() return TRUE; } +// virtual +void LLPanelOutfitsInventory::onOpen(const LLSD& key) +{ + // Make sure we know which tab is selected, update the filter, + // and update verbs. + onTabChange(); +} + void LLPanelOutfitsInventory::updateVerbs() { if (mParent) @@ -94,6 +168,7 @@ void LLPanelOutfitsInventory::updateVerbs() if (mListCommands) { mListCommands->childSetVisible("look_edit_btn",sShowDebugEditor); + updateListCommands(); } } @@ -168,15 +243,36 @@ void LLPanelOutfitsInventory::onEdit() { } -void LLPanelOutfitsInventory::onNew() +void LLPanelOutfitsInventory::onSave() +{ + std::string outfit_name; + + if (!LLAppearanceManager::getInstance()->getBaseOutfitName(outfit_name)) + { + outfit_name = LLViewerFolderType::lookupNewCategoryName(LLFolderType::FT_OUTFIT); + } + + LLOutfitSaveAsDialog* save_as_dialog = LLFloaterReg::showTypedInstance("outfit_save_as", LLSD(outfit_name), TRUE); + if (save_as_dialog) + { + save_as_dialog->setSaveAsCommit(boost::bind(&LLPanelOutfitsInventory::onSaveCommit, this, _1 )); + } +} + +void LLPanelOutfitsInventory::onSaveCommit(const std::string& outfit_name) { - const std::string& outfit_name = LLViewerFolderType::lookupNewCategoryName(LLFolderType::FT_OUTFIT); LLUUID outfit_folder = gAgentWearables.makeNewOutfitLinks(outfit_name); + LLSD key; + LLSideTray::getInstance()->showPanel("panel_outfits_inventory", key); + + if (mAppearanceTabs) + { + mAppearanceTabs->selectTabByName("outfitslist_tab"); + } } void LLPanelOutfitsInventory::onSelectionChange(const std::deque &items, BOOL user_action) { - updateListCommands(); updateVerbs(); if (getRootFolder()->needsAutoRename() && items.size()) { @@ -264,9 +360,12 @@ void LLPanelOutfitsInventory::updateListCommands() { bool trash_enabled = isActionEnabled("delete"); bool wear_enabled = isActionEnabled("wear"); + bool make_outfit_enabled = isActionEnabled("make_outfit"); mListCommands->childSetEnabled("trash_btn", trash_enabled); mListCommands->childSetEnabled("wear_btn", wear_enabled); + mListCommands->childSetVisible("wear_btn", wear_enabled); + mListCommands->childSetEnabled("make_outfit_btn", make_outfit_enabled); } void LLPanelOutfitsInventory::onGearButtonClick() @@ -276,7 +375,7 @@ void LLPanelOutfitsInventory::onGearButtonClick() void LLPanelOutfitsInventory::onAddButtonClick() { - onNew(); + onSave(); } void LLPanelOutfitsInventory::showActionMenu(LLMenuGL* menu, std::string spawning_view_name) @@ -303,6 +402,8 @@ void LLPanelOutfitsInventory::onClipboardAction(const LLSD& userdata) { std::string command_name = userdata.asString(); getActivePanel()->getRootFolder()->doToSelected(getActivePanel()->getModel(),command_name); + updateListCommands(); + updateVerbs(); } void LLPanelOutfitsInventory::onCustomAction(const LLSD& userdata) @@ -313,7 +414,7 @@ void LLPanelOutfitsInventory::onCustomAction(const LLSD& userdata) const std::string command_name = userdata.asString(); if (command_name == "new") { - onNew(); + onSave(); } if (command_name == "edit") { @@ -323,6 +424,7 @@ void LLPanelOutfitsInventory::onCustomAction(const LLSD& userdata) { onWearButtonClick(); } + // Note: This option has been removed from the gear menu. if (command_name == "add") { onAdd(); @@ -343,20 +445,22 @@ void LLPanelOutfitsInventory::onCustomAction(const LLSD& userdata) { onClipboardAction("delete"); } + updateListCommands(); + updateVerbs(); } BOOL LLPanelOutfitsInventory::isActionEnabled(const LLSD& userdata) { const std::string command_name = userdata.asString(); - if (command_name == "delete") + if (command_name == "delete" || command_name == "remove") { BOOL can_delete = FALSE; LLFolderView *folder = getActivePanel()->getRootFolder(); if (folder) { - can_delete = TRUE; std::set selection_set; folder->getSelectionList(selection_set); + can_delete = (selection_set.size() > 0); for (std::set::iterator iter = selection_set.begin(); iter != selection_set.end(); ++iter) @@ -375,9 +479,9 @@ BOOL LLPanelOutfitsInventory::isActionEnabled(const LLSD& userdata) LLFolderView *folder = getActivePanel()->getRootFolder(); if (folder) { - can_delete = TRUE; std::set selection_set; folder->getSelectionList(selection_set); + can_delete = (selection_set.size() > 0); for (std::set::iterator iter = selection_set.begin(); iter != selection_set.end(); ++iter) @@ -391,10 +495,27 @@ BOOL LLPanelOutfitsInventory::isActionEnabled(const LLSD& userdata) } return FALSE; } + if (command_name == "rename" || + command_name == "delete_outfit") + { + return (getCorrectListenerForAction() != NULL) && hasItemsSelected(); + } + + if (command_name == "wear") + { + const BOOL is_my_outfits = (mActivePanel->getName() == "outfitslist_tab"); + if (!is_my_outfits) + { + return FALSE; + } + } + if (command_name == "make_outfit") + { + return TRUE; + } + if (command_name == "edit" || - command_name == "wear" || - command_name == "add" || - command_name == "remove" + command_name == "add" ) { return (getCorrectListenerForAction() != NULL); @@ -402,6 +523,19 @@ BOOL LLPanelOutfitsInventory::isActionEnabled(const LLSD& userdata) return TRUE; } +bool LLPanelOutfitsInventory::hasItemsSelected() +{ + bool has_items_selected = false; + LLFolderView *folder = getActivePanel()->getRootFolder(); + if (folder) + { + std::set selection_set; + folder->getSelectionList(selection_set); + has_items_selected = (selection_set.size() > 0); + } + return has_items_selected; +} + bool LLPanelOutfitsInventory::handleDragAndDropToTrash(BOOL drop, EDragAndDropType cargo_type, EAcceptance* accept) { *accept = ACCEPT_NO; @@ -425,17 +559,15 @@ bool LLPanelOutfitsInventory::handleDragAndDropToTrash(BOOL drop, EDragAndDropTy void LLPanelOutfitsInventory::initTabPanels() { mTabPanels.resize(2); + + LLInventoryPanel *cof_panel = getChild("cof_tab"); + cof_panel->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); + mTabPanels[0] = cof_panel; - LLInventoryPanel *myoutfits_panel = getChild("outfitslist_accordionpanel"); + LLInventoryPanel *myoutfits_panel = getChild("outfitslist_tab"); myoutfits_panel->setFilterTypes(1LL << LLFolderType::FT_OUTFIT, LLInventoryFilter::FILTERTYPE_CATEGORY); myoutfits_panel->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); - mTabPanels[0] = myoutfits_panel; - mActivePanel = myoutfits_panel; - - - LLInventoryPanel *cof_panel = getChild("cof_accordionpanel"); - cof_panel->setShowFolderState(LLInventoryFilter::SHOW_NON_EMPTY_FOLDERS); - mTabPanels[1] = cof_panel; + mTabPanels[1] = myoutfits_panel; for (tabpanels_vec_t::iterator iter = mTabPanels.begin(); iter != mTabPanels.end(); @@ -447,6 +579,7 @@ void LLPanelOutfitsInventory::initTabPanels() mAppearanceTabs = getChild("appearance_tabs"); mAppearanceTabs->setCommitCallback(boost::bind(&LLPanelOutfitsInventory::onTabChange, this)); + mActivePanel = (LLInventoryPanel*)mAppearanceTabs->getCurrentPanel(); } void LLPanelOutfitsInventory::onTabSelectionChange(LLInventoryPanel* tab_panel, const std::deque &items, BOOL user_action) @@ -479,9 +612,7 @@ void LLPanelOutfitsInventory::onTabChange() return; } mActivePanel->setFilterSubString(mFilterSubString); - - bool is_my_outfits = (mActivePanel->getName() == "outfitslist_accordionpanel"); - mListCommands->childSetEnabled("make_outfit_btn", is_my_outfits); + updateVerbs(); } LLInventoryPanel* LLPanelOutfitsInventory::getActivePanel() diff --git a/indra/newview/llpaneloutfitsinventory.h b/indra/newview/llpaneloutfitsinventory.h index 1e084750a0..76110e2a3f 100644 --- a/indra/newview/llpaneloutfitsinventory.h +++ b/indra/newview/llpaneloutfitsinventory.h @@ -53,12 +53,15 @@ public: virtual ~LLPanelOutfitsInventory(); /*virtual*/ BOOL postBuild(); + /*virtual*/ void onOpen(const LLSD& key); void onSearchEdit(const std::string& string); void onAdd(); void onRemove(); void onEdit(); - void onNew(); + void onSave(); + + void onSaveCommit(const std::string& item_name); void onSelectionChange(const std::deque &items, BOOL user_action); void onSelectorButtonClicked(); @@ -114,6 +117,7 @@ protected: BOOL isActionEnabled(const LLSD& command_name); void onCustomAction(const LLSD& command_name); bool handleDragAndDropToTrash(BOOL drop, EDragAndDropType cargo_type, EAcceptance* accept); + bool hasItemsSelected(); private: LLPanel* mListCommands; LLMenuGL* mMenuGearDefault; diff --git a/indra/newview/llpanelpeople.cpp b/indra/newview/llpanelpeople.cpp index e14a5778ad..c14b282488 100644 --- a/indra/newview/llpanelpeople.cpp +++ b/indra/newview/llpanelpeople.cpp @@ -35,6 +35,7 @@ // libs #include "llfloaterreg.h" #include "llmenugl.h" +#include "llnotificationsutil.h" #include "llfiltereditor.h" #include "lltabcontainer.h" #include "lluictrlfactory.h" @@ -531,10 +532,10 @@ BOOL LLPanelPeople::postBuild() friends_panel->childSetAction("add_btn", boost::bind(&LLPanelPeople::onAddFriendWizButtonClicked, this)); friends_panel->childSetAction("del_btn", boost::bind(&LLPanelPeople::onDeleteFriendButtonClicked, this)); - mOnlineFriendList->setDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, mOnlineFriendList)); - mAllFriendList->setDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, mAllFriendList)); - mNearbyList->setDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, mNearbyList)); - mRecentList->setDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, mRecentList)); + mOnlineFriendList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); + mAllFriendList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); + mNearbyList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); + mRecentList->setItemDoubleClickCallback(boost::bind(&LLPanelPeople::onAvatarListDoubleClicked, this, _1)); mOnlineFriendList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mOnlineFriendList)); mAllFriendList->setCommitCallback(boost::bind(&LLPanelPeople::onAvatarListCommitted, this, mAllFriendList)); @@ -750,7 +751,6 @@ void LLPanelPeople::updateButtons() LLPanel* groups_panel = mTabContainer->getCurrentPanel(); groups_panel->childSetEnabled("activate_btn", item_selected && !cur_group_active); // "none" or a non-active group selected - groups_panel->childSetEnabled("plus_btn", item_selected); groups_panel->childSetEnabled("minus_btn", item_selected && selected_id.notNull()); } else @@ -1005,12 +1005,15 @@ void LLPanelPeople::onTabSelected(const LLSD& param) mFilterEditor->setLabel(getString("people_filter_label")); } -void LLPanelPeople::onAvatarListDoubleClicked(LLAvatarList* list) +void LLPanelPeople::onAvatarListDoubleClicked(LLUICtrl* ctrl) { - LLUUID clicked_id = list->getSelectedUUID(); - - if (clicked_id.isNull()) + LLAvatarListItem* item = dynamic_cast(ctrl); + if(!item) + { return; + } + + LLUUID clicked_id = item->getAvatarId(); #if 0 // SJB: Useful for testing, but not currently functional or to spec LLAvatarActions::showProfile(clicked_id); @@ -1138,6 +1141,12 @@ void LLPanelPeople::onAvatarPicked( void LLPanelPeople::onGroupPlusButtonClicked() { + if (!gAgent.canJoinGroups()) + { + LLNotificationsUtil::add("JoinedTooManyGroups"); + return; + } + LLMenuGL* plus_menu = (LLMenuGL*)mGroupPlusMenuHandle.get(); if (!plus_menu) return; diff --git a/indra/newview/llpanelpeople.h b/indra/newview/llpanelpeople.h index da2c0e368c..7580fdbeef 100644 --- a/indra/newview/llpanelpeople.h +++ b/indra/newview/llpanelpeople.h @@ -109,7 +109,7 @@ private: void onNearbyViewSortButtonClicked(); void onFriendsViewSortButtonClicked(); void onGroupsViewSortButtonClicked(); - void onAvatarListDoubleClicked(LLAvatarList* list); + void onAvatarListDoubleClicked(LLUICtrl* ctrl); void onAvatarListCommitted(LLAvatarList* list); void onGroupPlusButtonClicked(); void onGroupMinusButtonClicked(); diff --git a/indra/newview/llpanelpick.cpp b/indra/newview/llpanelpick.cpp index 7a4dd3569d..5ac0587550 100644 --- a/indra/newview/llpanelpick.cpp +++ b/indra/newview/llpanelpick.cpp @@ -35,24 +35,30 @@ // profile. #include "llviewerprecompiledheaders.h" -#include "llpanel.h" + +#include "llpanelpick.h" + #include "message.h" -#include "llagent.h" -#include "llagentpicksinfo.h" + +#include "llparcel.h" + #include "llbutton.h" +#include "llfloaterreg.h" #include "lliconctrl.h" #include "lllineeditor.h" -#include "llparcel.h" -#include "llviewerparcelmgr.h" +#include "llpanel.h" +#include "llscrollcontainer.h" #include "lltexteditor.h" + +#include "llagent.h" +#include "llagentpicksinfo.h" +#include "llavatarpropertiesprocessor.h" +#include "llfloaterworldmap.h" #include "lltexturectrl.h" #include "lluiconstants.h" +#include "llviewerparcelmgr.h" #include "llviewerregion.h" #include "llworldmap.h" -#include "llfloaterworldmap.h" -#include "llfloaterreg.h" -#include "llavatarpropertiesprocessor.h" -#include "llpanelpick.h" #define XML_PANEL_EDIT_PICK "panel_edit_pick.xml" @@ -93,6 +99,10 @@ LLPanelPickInfo::LLPanelPickInfo() , mPickId(LLUUID::null) , mParcelId(LLUUID::null) , mRequestedId(LLUUID::null) + , mScrollingPanelMinHeight(0) + , mScrollingPanelWidth(0) + , mScrollingPanel(NULL) + , mScrollContainer(NULL) { } @@ -146,9 +156,35 @@ BOOL LLPanelPickInfo::postBuild() childSetAction("show_on_map_btn", boost::bind(&LLPanelPickInfo::onClickMap, this)); childSetAction("back_btn", boost::bind(&LLPanelPickInfo::onClickBack, this)); + mScrollingPanel = getChild("scroll_content_panel"); + mScrollContainer = getChild("profile_scroll"); + + mScrollingPanelMinHeight = mScrollContainer->getScrolledViewRect().getHeight(); + mScrollingPanelWidth = mScrollingPanel->getRect().getWidth(); + return TRUE; } +void LLPanelPickInfo::reshape(S32 width, S32 height, BOOL called_from_parent) +{ + LLPanel::reshape(width, height, called_from_parent); + + if (!mScrollContainer || !mScrollingPanel) + return; + + static LLUICachedControl scrollbar_size ("UIScrollbarSize", 0); + + S32 scroll_height = mScrollContainer->getRect().getHeight(); + if (mScrollingPanelMinHeight >= scroll_height) + { + mScrollingPanel->reshape(mScrollingPanelWidth, mScrollingPanelMinHeight); + } + else + { + mScrollingPanel->reshape(mScrollingPanelWidth + scrollbar_size, scroll_height); + } +} + void LLPanelPickInfo::processProperties(void* data, EAvatarProcessorType type) { if(APT_PICK_INFO != type) @@ -284,7 +320,6 @@ void LLPanelPickInfo::setPickName(const std::string& name) void LLPanelPickInfo::setPickDesc(const std::string& desc) { childSetValue(XML_DESC, desc); - updateContentPanelRect(); } void LLPanelPickInfo::setPickLocation(const std::string& location) @@ -292,31 +327,6 @@ void LLPanelPickInfo::setPickLocation(const std::string& location) childSetValue(XML_LOCATION, location); } -void LLPanelPickInfo::updateContentPanelRect() -{ - LLTextBox* desc = getChild(XML_DESC); - - S32 text_height = desc->getTextPixelHeight(); - LLRect text_rect = desc->getRect(); - - // let text-box height fit text height - text_rect.set(text_rect.mLeft, text_rect.mTop, text_rect.mRight, text_rect.mTop - text_height); - desc->setRect(text_rect); - desc->reshape(text_rect.getWidth(), text_rect.getHeight()); - // force reflow - desc->setText(desc->getText()); - - // bottom of description text-box will be bottom of content panel - desc->localRectToOtherView(desc->getLocalRect(), &text_rect, getChild("profile_scroll")); - - LLPanel* content_panel = getChild("scroll_content_panel"); - LLRect content_rect = content_panel->getRect(); - content_rect.set(content_rect.mLeft, content_rect.mTop, content_rect.mRight, text_rect.mBottom); - // Somehow setRect moves all elements down. - // Single reshape() updates rect and does not move anything. - content_panel->reshape(content_rect.getWidth(), content_rect.getHeight()); -} - void LLPanelPickInfo::onClickMap() { LLFloaterWorldMap::getInstance()->trackLocation(getPosGlobal()); @@ -438,7 +448,7 @@ BOOL LLPanelPickEdit::postBuild() { LLPanelPickInfo::postBuild(); - mSnapshotCtrl->setOnSelectCallback(boost::bind(&LLPanelPickEdit::onPickChanged, this, _1)); + mSnapshotCtrl->setCommitCallback(boost::bind(&LLPanelPickEdit::onSnapshotChanged, this)); LLLineEditor* line_edit = getChild("pick_name"); line_edit->setKeystrokeCallback(boost::bind(&LLPanelPickEdit::onPickChanged, this, _1), NULL); @@ -527,16 +537,14 @@ void LLPanelPickEdit::sendUpdate() } } +void LLPanelPickEdit::onSnapshotChanged() +{ + enableSaveButton(true); +} + void LLPanelPickEdit::onPickChanged(LLUICtrl* ctrl) { - if(isDirty()) - { - enableSaveButton(true); - } - else - { - enableSaveButton(false); - } + enableSaveButton(isDirty()); } void LLPanelPickEdit::resetData() @@ -603,10 +611,6 @@ void LLPanelPickEdit::initTexturePickerMouseEvents() mSnapshotCtrl->setMouseEnterCallback(boost::bind(&LLPanelPickEdit::onTexturePickerMouseEnter, this, _1)); mSnapshotCtrl->setMouseLeaveCallback(boost::bind(&LLPanelPickEdit::onTexturePickerMouseLeave, this, _1)); - // *WORKAROUND: Needed for EXT-1625: enabling save button each time when picker is opened, even if - // texture wasn't changed (see Steve's comment). - mSnapshotCtrl->setMouseDownCallback(boost::bind(&LLPanelPickEdit::enableSaveButton, this, true)); - text_icon->setVisible(FALSE); } diff --git a/indra/newview/llpanelpick.h b/indra/newview/llpanelpick.h index 12b5a116b4..4f27760a8d 100644 --- a/indra/newview/llpanelpick.h +++ b/indra/newview/llpanelpick.h @@ -43,6 +43,7 @@ class LLIconCtrl; class LLTextureCtrl; +class LLScrollContainer; class LLMessageSystem; class LLAvatarPropertiesObserver; @@ -69,6 +70,8 @@ public: /*virtual*/ BOOL postBuild(); + /*virtual*/ void reshape(S32 width, S32 height, BOOL called_from_parent = TRUE); + /*virtual*/ void processProperties(void* data, EAvatarProcessorType type); /** @@ -139,15 +142,6 @@ protected: virtual void setPosGlobal(const LLVector3d& pos) { mPosGlobal = pos; } virtual LLVector3d& getPosGlobal() { return mPosGlobal; } - /** - * Reshapes content panel to fit all elements. - * - * Assume that description text-box is the last element of panel. - * Reshape text-box to fit text height and then reshape content panel to fit - * text-box bottom. EXT-1326 - */ - void updateContentPanelRect(); - /** * Callback for "Map" button, opens Map */ @@ -162,7 +156,11 @@ protected: protected: - LLTextureCtrl* mSnapshotCtrl; + S32 mScrollingPanelMinHeight; + S32 mScrollingPanelWidth; + LLScrollContainer* mScrollContainer; + LLPanel* mScrollingPanel; + LLTextureCtrl* mSnapshotCtrl; LLUUID mAvatarId; LLVector3d mPosGlobal; @@ -223,6 +221,11 @@ protected: */ void sendUpdate(); + /** + * Called when snapshot image changes. + */ + void onSnapshotChanged(); + /** * Callback for Pick snapshot, name and description changed event. */ diff --git a/indra/newview/llpanelpicks.cpp b/indra/newview/llpanelpicks.cpp index 751705dd57..ada65c98a4 100644 --- a/indra/newview/llpanelpicks.cpp +++ b/indra/newview/llpanelpicks.cpp @@ -411,6 +411,7 @@ BOOL LLPanelPicks::postBuild() LLUICtrl::CommitCallbackRegistry::ScopedRegistrar plus_registar; plus_registar.add("Picks.Plus.Action", boost::bind(&LLPanelPicks::onPlusMenuItemClicked, this, _2)); + mEnableCallbackRegistrar.add("Picks.Plus.Enable", boost::bind(&LLPanelPicks::isActionEnabled, this, _2)); mPlusMenu = LLUICtrlFactory::getInstance()->createFromFile("menu_picks_plus.xml", gMenuHolder, LLViewerMenuHolderGL::child_registry_t::instance()); return TRUE; @@ -430,6 +431,18 @@ void LLPanelPicks::onPlusMenuItemClicked(const LLSD& param) } } +bool LLPanelPicks::isActionEnabled(const LLSD& userdata) const +{ + std::string command_name = userdata.asString(); + + if (command_name == "new_pick" && LLAgentPicksInfo::getInstance()->isPickLimitReached()) + { + return false; + } + + return true; +} + void LLPanelPicks::onAccordionStateChanged(const LLAccordionCtrlTab* acc_tab) { if(!mPicksAccTab->getDisplayChildren()) @@ -652,7 +665,6 @@ void LLPanelPicks::updateButtons() if (getAvatarId() == gAgentID) { - childSetEnabled(XML_BTN_NEW, !LLAgentPicksInfo::getInstance()->isPickLimitReached()); childSetEnabled(XML_BTN_DELETE, has_selected); } diff --git a/indra/newview/llpanelpicks.h b/indra/newview/llpanelpicks.h index 1b2e35ca46..3f757e482e 100644 --- a/indra/newview/llpanelpicks.h +++ b/indra/newview/llpanelpicks.h @@ -97,6 +97,7 @@ private: void onClickMap(); void onPlusMenuItemClicked(const LLSD& param); + bool isActionEnabled(const LLSD& userdata) const; void onListCommit(const LLFlatListView* f_list); void onAccordionStateChanged(const LLAccordionCtrlTab* acc_tab); diff --git a/indra/newview/llpanelplaces.cpp b/indra/newview/llpanelplaces.cpp index 685104a8b1..b037674c37 100644 --- a/indra/newview/llpanelplaces.cpp +++ b/indra/newview/llpanelplaces.cpp @@ -449,6 +449,22 @@ void LLPanelPlaces::setItem(LLInventoryItem* item) } } +S32 LLPanelPlaces::notifyParent(const LLSD& info) +{ + if(info.has("update_verbs")) + { + if(mPosGlobal.isExactlyZero()) + { + mPosGlobal.setVec(info["global_x"], info["global_y"], info["global_z"]); + } + + updateVerbs(); + + return 1; + } + return LLPanel::notifyParent(info); +} + void LLPanelPlaces::onLandmarkLoaded(LLLandmark* landmark) { if (!mLandmarkInfo) @@ -460,6 +476,8 @@ void LLPanelPlaces::onLandmarkLoaded(LLLandmark* landmark) mLandmarkInfo->displayParcelInfo(region_id, mPosGlobal); mSaveBtn->setEnabled(TRUE); + + updateVerbs(); } void LLPanelPlaces::onFilterEdit(const std::string& search_string, bool force_filter) @@ -824,6 +842,19 @@ void LLPanelPlaces::togglePlaceInfoPanel(BOOL visible) mPlaceProfile->setVisible(FALSE); } + else + { + LLLandmarksPanel* landmarks_panel = + dynamic_cast(mTabContainer->getPanelByName("Landmarks")); + if (landmarks_panel && mItem.notNull()) + { + // If a landmark info is being closed we open the landmarks tab + // and set this landmark selected. + mTabContainer->selectTabPanel(landmarks_panel); + + landmarks_panel->setItemSelected(mItem->getUUID(), TRUE); + } + } } } diff --git a/indra/newview/llpanelplaces.h b/indra/newview/llpanelplaces.h index 5ee8704992..27b5911ebb 100644 --- a/indra/newview/llpanelplaces.h +++ b/indra/newview/llpanelplaces.h @@ -71,6 +71,12 @@ public: void setItem(LLInventoryItem* item); + LLInventoryItem* getItem() { return mItem; } + + std::string getPlaceInfoType() { return mPlaceInfoType; } + + /*virtual*/ S32 notifyParent(const LLSD& info); + private: void onLandmarkLoaded(LLLandmark* landmark); void onFilterEdit(const std::string& search_string, bool force_filter); diff --git a/indra/newview/llpanelprimmediacontrols.cpp b/indra/newview/llpanelprimmediacontrols.cpp index 5cc9c1951b..2dc3a62637 100644 --- a/indra/newview/llpanelprimmediacontrols.cpp +++ b/indra/newview/llpanelprimmediacontrols.cpp @@ -127,7 +127,11 @@ LLPanelPrimMediaControls::LLPanelPrimMediaControls() : mScrollState = SCROLL_NONE; mPanelHandle.bind(this); + + mInactiveTimeout = gSavedSettings.getF32("MediaControlTimeout"); + mControlFadeTime = gSavedSettings.getF32("MediaControlFadeTime"); } + LLPanelPrimMediaControls::~LLPanelPrimMediaControls() { } @@ -156,8 +160,6 @@ BOOL LLPanelPrimMediaControls::postBuild() mSkipBackCtrl = getChild("skip_back"); mVolumeCtrl = getChild("media_volume"); mMuteBtn = getChild("media_mute_button"); - mVolumeUpCtrl = getChild("volume_up"); - mVolumeDownCtrl = getChild("volume_down"); mVolumeSliderCtrl = getChild("volume_slider"); mWhitelistIcon = getChild("media_whitelist_flag"); mSecureLockIcon = getChild("media_secure_lock_flag"); @@ -172,6 +174,7 @@ BOOL LLPanelPrimMediaControls::postBuild() LLStringUtil::convertToF32(getString("zoom_near_padding"), mZoomNearPadding); LLStringUtil::convertToF32(getString("zoom_medium_padding"), mZoomMediumPadding); LLStringUtil::convertToF32(getString("zoom_far_padding"), mZoomFarPadding); + LLStringUtil::convertToS32(getString("top_world_view_avoid_zone"), mTopWorldViewAvoidZone); // These are currently removed...but getChild creates a "dummy" widget. // This class handles them missing. @@ -207,11 +210,9 @@ BOOL LLPanelPrimMediaControls::postBuild() } mMediaAddress->setFocusReceivedCallback(boost::bind(&LLPanelPrimMediaControls::onInputURL, _1, this )); - mInactiveTimeout = gSavedSettings.getF32("MediaControlTimeout"); - mControlFadeTime = gSavedSettings.getF32("MediaControlFadeTime"); - + mCurrentZoom = ZOOM_NONE; - // clicks on HUD buttons do not remove keyboard focus from media + // clicks on buttons do not remove keyboard focus from media setIsChrome(TRUE); return TRUE; } @@ -336,8 +337,6 @@ void LLPanelPrimMediaControls::updateShape() mMediaAddressCtrl->setVisible(has_focus && !mini_controls); mMediaPlaySliderPanel->setVisible(has_focus && !mini_controls); mVolumeCtrl->setVisible(false); - mVolumeUpCtrl->setVisible(false); - mVolumeDownCtrl->setVisible(false); mWhitelistIcon->setVisible(!mini_controls && (media_data)?media_data->getWhiteListEnable():false); // Disable zoom if HUD @@ -370,11 +369,9 @@ void LLPanelPrimMediaControls::updateShape() mSkipBackCtrl->setEnabled(has_focus && !mini_controls); mVolumeCtrl->setVisible(has_focus); - mVolumeUpCtrl->setVisible(has_focus); - mVolumeDownCtrl->setVisible(has_focus); mVolumeCtrl->setEnabled(has_focus); - mVolumeSliderCtrl->setEnabled(has_focus && mVolumeSliderVisible > 0); - mVolumeSliderCtrl->setVisible(has_focus && mVolumeSliderVisible > 0); + mVolumeSliderCtrl->setEnabled(has_focus && shouldVolumeSliderBeVisible()); + mVolumeSliderCtrl->setVisible(has_focus && shouldVolumeSliderBeVisible()); mWhitelistIcon->setVisible(false); mSecureLockIcon->setVisible(false); @@ -414,21 +411,15 @@ void LLPanelPrimMediaControls::updateShape() // video vloume if(volume <= 0.0) { - mVolumeUpCtrl->setEnabled(TRUE); - mVolumeDownCtrl->setEnabled(FALSE); mMuteBtn->setToggleState(true); } else if (volume >= 1.0) { - mVolumeUpCtrl->setEnabled(FALSE); - mVolumeDownCtrl->setEnabled(TRUE); mMuteBtn->setToggleState(false); } else { mMuteBtn->setToggleState(false); - mVolumeUpCtrl->setEnabled(TRUE); - mVolumeDownCtrl->setEnabled(TRUE); } switch(result) @@ -473,12 +464,8 @@ void LLPanelPrimMediaControls::updateShape() mSkipBackCtrl->setEnabled(FALSE); mVolumeCtrl->setVisible(FALSE); - mVolumeUpCtrl->setVisible(FALSE); - mVolumeDownCtrl->setVisible(FALSE); mVolumeSliderCtrl->setVisible(FALSE); mVolumeCtrl->setEnabled(FALSE); - mVolumeUpCtrl->setEnabled(FALSE); - mVolumeDownCtrl->setEnabled(FALSE); mVolumeSliderCtrl->setEnabled(FALSE); if (mMediaPanelScroll) @@ -627,36 +614,45 @@ void LLPanelPrimMediaControls::updateShape() update_min_max(min, max, LLVector3(screen_vert.v)); } + // convert screenspace bbox to pixels (in screen coords) + LLRect window_rect = gViewerWindow->getWorldViewRectScaled(); LLCoordGL screen_min; - screen_min.mX = llround((F32)gViewerWindow->getWorldViewWidthScaled() * (min.mV[VX] + 1.f) * 0.5f); - screen_min.mY = llround((F32)gViewerWindow->getWorldViewHeightScaled() * (min.mV[VY] + 1.f) * 0.5f); + screen_min.mX = llround((F32)window_rect.getWidth() * (min.mV[VX] + 1.f) * 0.5f); + screen_min.mY = llround((F32)window_rect.getHeight() * (min.mV[VY] + 1.f) * 0.5f); LLCoordGL screen_max; - screen_max.mX = llround((F32)gViewerWindow->getWorldViewWidthScaled() * (max.mV[VX] + 1.f) * 0.5f); - screen_max.mY = llround((F32)gViewerWindow->getWorldViewHeightScaled() * (max.mV[VY] + 1.f) * 0.5f); + screen_max.mX = llround((F32)window_rect.getWidth() * (max.mV[VX] + 1.f) * 0.5f); + screen_max.mY = llround((F32)window_rect.getHeight() * (max.mV[VY] + 1.f) * 0.5f); - // grow panel so that screenspace bounding box fits inside "media_region" element of HUD - LLRect media_controls_rect; - S32 volume_slider_height = mVolumeSliderCtrl->getRect().getHeight() - /*fudge*/ 2; - getParent()->screenRectToLocal(LLRect(screen_min.mX, screen_max.mY, screen_max.mX, screen_min.mY), &media_controls_rect); - media_controls_rect.mLeft -= mMediaRegion->getRect().mLeft; - media_controls_rect.mBottom -= mMediaRegion->getRect().mBottom - volume_slider_height; - media_controls_rect.mTop += getRect().getHeight() - mMediaRegion->getRect().mTop; - media_controls_rect.mRight += getRect().getWidth() - mMediaRegion->getRect().mRight; + // grow panel so that screenspace bounding box fits inside "media_region" element of panel + LLRect media_panel_rect; + // Get the height of the controls (less the volume slider) + S32 controls_height = mMediaControlsStack->getRect().getHeight() - mVolumeSliderCtrl->getRect().getHeight(); + getParent()->screenRectToLocal(LLRect(screen_min.mX, screen_max.mY, screen_max.mX, screen_min.mY), &media_panel_rect); + media_panel_rect.mTop += controls_height; - // keep all parts of HUD on-screen - LLRect window_rect = getParent()->getLocalRect(); - media_controls_rect.intersectWith(window_rect); + // keep all parts of panel on-screen + // Area of the top of the world view to avoid putting the controls + window_rect.mTop -= mTopWorldViewAvoidZone; + // Don't include "spacing" bookends on left & right of the media controls + window_rect.mLeft -= mLeftBookend->getRect().getWidth(); + window_rect.mRight += mRightBookend->getRect().getWidth(); + // Don't include the volume slider + window_rect.mBottom -= mVolumeSliderCtrl->getRect().getHeight(); + media_panel_rect.intersectWith(window_rect); // clamp to minimum size, keeping rect inside window - S32 centerX = media_controls_rect.getCenterX(); - S32 centerY = media_controls_rect.getCenterY(); + S32 centerX = media_panel_rect.getCenterX(); + S32 centerY = media_panel_rect.getCenterY(); + // Shrink screen rect by min width and height, to ensure containment window_rect.stretch(-mMinWidth/2, -mMinHeight/2); window_rect.clampPointToRect(centerX, centerY); - media_controls_rect.setCenterAndSize(centerX, centerY, - llmax(mMinWidth, media_controls_rect.getWidth()), llmax(mMinHeight, media_controls_rect.getHeight())); + media_panel_rect.setCenterAndSize(centerX, centerY, + llmax(mMinWidth, media_panel_rect.getWidth()), + llmax(mMinHeight, media_panel_rect.getHeight())); - setShape(media_controls_rect, true); + // Finally set the size of the panel + setShape(media_panel_rect, true); // Test mouse position to see if the cursor is stationary LLCoordWindow cursor_pos_window; @@ -699,13 +695,13 @@ void LLPanelPrimMediaControls::updateShape() /*virtual*/ void LLPanelPrimMediaControls::draw() { - F32 alpha = 1.f; + F32 alpha = getDrawContext().mAlpha; if(mFadeTimer.getStarted()) { F32 time = mFadeTimer.getElapsedTimeF32(); - alpha = llmax(lerp(1.0, 0.0, time / mControlFadeTime), 0.0f); + alpha *= llmax(lerp(1.0, 0.0, time / mControlFadeTime), 0.0f); - if(mFadeTimer.getElapsedTimeF32() >= mControlFadeTime) + if(time >= mControlFadeTime) { if(mClearFaceOnFade) { @@ -726,27 +722,30 @@ void LLPanelPrimMediaControls::draw() // Build rect for icon area in coord system of this panel // Assumes layout_stack is a direct child of this panel mMediaControlsStack->updateLayout(); - LLRect icon_area = mMediaControlsStack->getRect(); - + + // adjust for layout stack spacing + S32 space = mMediaControlsStack->getPanelSpacing() + 1; + LLRect controls_bg_area = mMediaControlsStack->getRect(); + + controls_bg_area.mTop += space; + // adjust to ignore space from volume slider - icon_area.mTop -= mVolumeSliderCtrl->getRect().getHeight(); + controls_bg_area.mBottom += mVolumeSliderCtrl->getRect().getHeight(); // adjust to ignore space from left bookend padding - icon_area.mLeft += mLeftBookend->getRect().getWidth(); + controls_bg_area.mLeft += mLeftBookend->getRect().getWidth() - space; // ignore space from right bookend padding - icon_area.mRight -= mRightBookend->getRect().getWidth(); + controls_bg_area.mRight -= mRightBookend->getRect().getWidth() - space; // draw control background UI image - mBackgroundImage->draw( icon_area, UI_VERTEX_COLOR % alpha); + mBackgroundImage->draw( controls_bg_area, UI_VERTEX_COLOR % alpha); // draw volume slider background UI image if (mVolumeSliderCtrl->getVisible()) { - LLRect volume_slider_rect = mVolumeSliderCtrl->getRect(); - // For some reason the rect is not in the right place (??) - // This translates the bg to under the slider - volume_slider_rect.translate(mVolumeSliderCtrl->getParent()->getRect().mLeft, icon_area.getHeight()); + LLRect volume_slider_rect; + screenRectToLocal(mVolumeSliderCtrl->calcScreenRect(), &volume_slider_rect); mVolumeSliderBackgroundImage->draw(volume_slider_rect, UI_VERTEX_COLOR % alpha); } @@ -1259,6 +1258,11 @@ void LLPanelPrimMediaControls::onToggleMute() { media_impl->setVolume(0.0); } + else if (mVolumeSliderCtrl->getValueF32() == 0.0) + { + media_impl->setVolume(1.0); + mVolumeSliderCtrl->setValue(1.0); + } else { media_impl->setVolume(mVolumeSliderCtrl->getValueF32()); @@ -1271,8 +1275,12 @@ void LLPanelPrimMediaControls::showVolumeSlider() mVolumeSliderVisible++; } - void LLPanelPrimMediaControls::hideVolumeSlider() { mVolumeSliderVisible--; } + +bool LLPanelPrimMediaControls::shouldVolumeSliderBeVisible() +{ + return mVolumeSliderVisible > 0; +} diff --git a/indra/newview/llpanelprimmediacontrols.h b/indra/newview/llpanelprimmediacontrols.h index d899ee4473..743cec70a1 100644 --- a/indra/newview/llpanelprimmediacontrols.h +++ b/indra/newview/llpanelprimmediacontrols.h @@ -111,6 +111,7 @@ private: void onToggleMute(); void showVolumeSlider(); void hideVolumeSlider(); + bool shouldVolumeSliderBeVisible(); static void onScrollUp(void* user_data); static void onScrollUpHeld(void* user_data); @@ -155,8 +156,6 @@ private: LLUICtrl *mMediaPlaySliderCtrl; LLUICtrl *mVolumeCtrl; LLButton *mMuteBtn; - LLUICtrl *mVolumeUpCtrl; - LLUICtrl *mVolumeDownCtrl; LLSliderCtrl *mVolumeSliderCtrl; LLIconCtrl *mWhitelistIcon; LLIconCtrl *mSecureLockIcon; @@ -171,6 +170,7 @@ private: F32 mZoomNearPadding; F32 mZoomMediumPadding; F32 mZoomFarPadding; + S32 mTopWorldViewAvoidZone; LLUICtrl *mMediaPanelScroll; LLButton *mScrollUpCtrl; diff --git a/indra/newview/llpanelteleporthistory.cpp b/indra/newview/llpanelteleporthistory.cpp index 245f694ac6..1b8fb49641 100644 --- a/indra/newview/llpanelteleporthistory.cpp +++ b/indra/newview/llpanelteleporthistory.cpp @@ -550,7 +550,7 @@ void LLTeleportHistoryPanel::updateVerbs() LLTeleportHistoryFlatItem* itemp = dynamic_cast (mLastSelectedFlatlList->getSelectedItem()); - mTeleportBtn->setEnabled(NULL != itemp && itemp->getIndex() < (S32)mTeleportHistory->getItems().size() - 1); + mTeleportBtn->setEnabled(NULL != itemp); mShowOnMapBtn->setEnabled(NULL != itemp); } @@ -723,7 +723,10 @@ void LLTeleportHistoryPanel::onTeleportHistoryChange(S32 removed_index) if (-1 == removed_index) showTeleportHistory(); // recreate all items else + { replaceItem(removed_index); // replace removed item by most recent + updateVerbs(); + } } void LLTeleportHistoryPanel::replaceItem(S32 removed_index) @@ -1033,7 +1036,7 @@ void LLTeleportHistoryPanel::setAccordionCollapsedByUser(LLUICtrl* acc_tab, bool bool LLTeleportHistoryPanel::isAccordionCollapsedByUser(LLUICtrl* acc_tab) { LLSD param = acc_tab->getValue(); - if(!param.has("acc_collapsed")) + if(!param.has(COLLAPSED_BY_USER)) { return false; } @@ -1045,4 +1048,11 @@ void LLTeleportHistoryPanel::onAccordionExpand(LLUICtrl* ctrl, const LLSD& param bool expanded = param.asBoolean(); // Save accordion tab state to restore it in refresh() setAccordionCollapsedByUser(ctrl, !expanded); + + // Reset selection upon accordion being collapsed + // to disable "Teleport" and "Map" buttons for hidden item. + if (!expanded && mLastSelectedFlatlList) + { + mLastSelectedFlatlList->resetSelection(); + } } diff --git a/indra/newview/llpanelvolume.cpp b/indra/newview/llpanelvolume.cpp index 5a70842a73..fbe68b4d92 100644 --- a/indra/newview/llpanelvolume.cpp +++ b/indra/newview/llpanelvolume.cpp @@ -54,7 +54,7 @@ #include "llcolorswatch.h" #include "lltexturectrl.h" #include "llcombobox.h" -#include "llfirstuse.h" +//#include "llfirstuse.h" #include "llfocusmgr.h" #include "llmanipscale.h" #include "llpreviewscript.h" @@ -470,7 +470,7 @@ void LLPanelVolume::sendIsFlexible() if (is_flexible) { - LLFirstUse::useFlexible(); + //LLFirstUse::useFlexible(); if (objectp->getClickAction() == CLICK_ACTION_SIT) { diff --git a/indra/newview/llparticipantlist.cpp b/indra/newview/llparticipantlist.cpp index e2da4c4475..b049f914ad 100644 --- a/indra/newview/llparticipantlist.cpp +++ b/indra/newview/llparticipantlist.cpp @@ -70,7 +70,7 @@ LLParticipantList::LLParticipantList(LLSpeakerMgr* data_source, LLAvatarList* av mSpeakerMgr->addListener(mSpeakerModeratorListener, "update_moderator"); mAvatarList->setNoItemsCommentText(LLTrans::getString("LoadingData")); - mAvatarListDoubleClickConnection = mAvatarList->setDoubleClickCallback(boost::bind(&LLParticipantList::onAvatarListDoubleClicked, this, mAvatarList)); + mAvatarListDoubleClickConnection = mAvatarList->setItemDoubleClickCallback(boost::bind(&LLParticipantList::onAvatarListDoubleClicked, this, _1)); mAvatarListRefreshConnection = mAvatarList->setRefreshCompleteCallback(boost::bind(&LLParticipantList::onAvatarListRefreshed, this, _1, _2)); // Set onAvatarListDoubleClicked as default on_return action. mAvatarListReturnConnection = mAvatarList->setReturnCallback(boost::bind(&LLParticipantList::onAvatarListDoubleClicked, this, mAvatarList)); @@ -132,10 +132,15 @@ void LLParticipantList::setSpeakingIndicatorsVisible(BOOL visible) mAvatarList->setSpeakingIndicatorsVisible(visible); }; -void LLParticipantList::onAvatarListDoubleClicked(LLAvatarList* list) +void LLParticipantList::onAvatarListDoubleClicked(LLUICtrl* ctrl) { - // NOTE(EM): Should we check if there is multiple selection and start conference if it is so? - LLUUID clicked_id = list->getSelectedUUID(); + LLAvatarListItem* item = dynamic_cast(ctrl); + if(!item) + { + return; + } + + LLUUID clicked_id = item->getAvatarId(); if (clicked_id.isNull() || clicked_id == gAgent.getID()) return; @@ -166,7 +171,6 @@ void LLParticipantList::onAvatarListRefreshed(LLUICtrl* ctrl, const LLSD& param) { name.erase(found, moderator_indicator_len); item->setName(name); - item->reshapeAvatarName(); } } } @@ -188,7 +192,6 @@ void LLParticipantList::onAvatarListRefreshed(LLUICtrl* ctrl, const LLSD& param) name += " "; name += moderator_indicator; item->setName(name); - item->reshapeAvatarName(); } } } @@ -279,6 +282,9 @@ bool LLParticipantList::onModeratorUpdateEvent(LLPointer e mModeratorList.erase(id); } } + + // apply changes immediately + onAvatarListRefreshed(mAvatarList, LLSD()); } } return true; @@ -576,7 +582,7 @@ void LLParticipantList::LLParticipantListMenu::moderateVoiceOtherParticipants(co bool LLParticipantList::LLParticipantListMenu::enableContextMenuItem(const LLSD& userdata) { std::string item = userdata.asString(); - if (item == "can_mute_text" || "can_block" == item) + if (item == "can_mute_text" || "can_block" == item || "can_share" == item || "can_im" == item) { return mUUIDs.front() != gAgentID; } @@ -592,8 +598,7 @@ bool LLParticipantList::LLParticipantListMenu::enableContextMenuItem(const LLSD& if (speakerp.notNull()) { // not in voice participants can not be moderated - return speakerp->mStatus == LLSpeaker::STATUS_VOICE_ACTIVE - || speakerp->mStatus == LLSpeaker::STATUS_MUTED; + return speakerp->isInVoiceChannel(); } } return false; diff --git a/indra/newview/llparticipantlist.h b/indra/newview/llparticipantlist.h index 70badbc40d..e1b1b5af00 100644 --- a/indra/newview/llparticipantlist.h +++ b/indra/newview/llparticipantlist.h @@ -232,7 +232,7 @@ class LLParticipantList }; private: - void onAvatarListDoubleClicked(LLAvatarList* list); + void onAvatarListDoubleClicked(LLUICtrl* ctrl); void onAvatarListRefreshed(LLUICtrl* ctrl, const LLSD& param); /** diff --git a/indra/newview/llpreviewgesture.cpp b/indra/newview/llpreviewgesture.cpp index 2a40cbaba0..84bdaafacf 100644 --- a/indra/newview/llpreviewgesture.cpp +++ b/indra/newview/llpreviewgesture.cpp @@ -364,6 +364,12 @@ BOOL LLPreviewGesture::postBuild() LLTextBox* text; LLCheckBoxCtrl* check; + edit = getChild("name"); + edit->setKeystrokeCallback(onKeystrokeCommit, this); + + edit = getChild("desc"); + edit->setKeystrokeCallback(onKeystrokeCommit, this); + edit = getChild("trigger_editor"); edit->setKeystrokeCallback(onKeystrokeCommit, this); edit->setCommitCallback(onCommitSetDirty, this); diff --git a/indra/newview/llpreviewnotecard.cpp b/indra/newview/llpreviewnotecard.cpp index 95756ac5f3..cc70360528 100644 --- a/indra/newview/llpreviewnotecard.cpp +++ b/indra/newview/llpreviewnotecard.cpp @@ -55,7 +55,6 @@ #include "llviewerobjectlist.h" #include "llviewerregion.h" #include "lldir.h" -//#include "llfloaterchat.h" #include "llviewerstats.h" #include "llviewercontrol.h" // gSavedSettings #include "llappviewer.h" // app_abort_quit() diff --git a/indra/newview/llpreviewscript.cpp b/indra/newview/llpreviewscript.cpp index 646c9fb6a4..fccf71f3cb 100644 --- a/indra/newview/llpreviewscript.cpp +++ b/indra/newview/llpreviewscript.cpp @@ -79,7 +79,6 @@ #include "llslider.h" #include "lldir.h" #include "llcombobox.h" -//#include "llfloaterchat.h" #include "llviewerstats.h" #include "llviewertexteditor.h" #include "llviewerwindow.h" -- cgit v1.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 (limited to 'indra/newview/llappviewer.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: -