diff --git a/autobuild.xml b/autobuild.xml index 6543fb934e..5885aeb2d4 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -3444,9 +3444,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - c543e73c35c605eaf509bc7651c11227 + e2bef561238448fe512146baa6dacabc url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/27348/219718/viewer_manager-2.0.521526-darwin64-521526.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/28859/240421/viewer_manager-2.0.522507-darwin64-522507.tar.bz2 name darwin64 @@ -3480,9 +3480,9 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors archive hash - fec612973e0d70f82da8c8f22a10c6f3 + 75cdf1afc293423553b6d190ce422412 url - http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/27349/219725/viewer_manager-2.0.521526-windows-521526.tar.bz2 + http://automated-builds-secondlife-com.s3.amazonaws.com/ct2/28858/240427/viewer_manager-2.0.522507-windows-522507.tar.bz2 name windows @@ -3493,7 +3493,7 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors source_type hg version - 2.0.521526 + 2.0.522507 vlc-bin diff --git a/indra/cmake/Linking.cmake b/indra/cmake/Linking.cmake index 861b648bac..2ba997d6e0 100644 --- a/indra/cmake/Linking.cmake +++ b/indra/cmake/Linking.cmake @@ -77,6 +77,7 @@ if (WINDOWS) wldap32 gdi32 user32 + ole32 dbghelp ) else (WINDOWS) diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp index 1fb3f2f381..b400ea070b 100644 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -30,6 +30,7 @@ #if LL_WINDOWS #include "llwin32headerslean.h" #include // Windows errno +#include #else #include #endif @@ -134,8 +135,10 @@ int warnif(const std::string& desc, const std::string& filename, int rc, int acc { // Only do any of this stuff (before LL_ENDL) if it will be logged. LL_DEBUGS("LLFile") << empty; - const char* TEMP = getenv("TEMP"); - if (! TEMP) + // would be nice to use LLDir for this, but dependency goes the + // wrong way + const char* TEMP = LLFile::tmpdir(); + if (! (TEMP && *TEMP)) { LL_CONT << "No $TEMP, not running 'handle'"; } @@ -385,17 +388,13 @@ const char *LLFile::tmpdir() #if LL_WINDOWS sep = '\\'; - DWORD len = GetTempPathW(0, L""); - llutf16string utf16path; - utf16path.resize(len + 1); - len = GetTempPathW(static_cast(utf16path.size()), &utf16path[0]); - utf8path = utf16str_to_utf8str(utf16path); + std::vector utf16path(MAX_PATH + 1); + GetTempPathW(utf16path.size(), &utf16path[0]); + utf8path = ll_convert_wide_to_string(&utf16path[0]); #else sep = '/'; - char *env = getenv("TMPDIR"); - - utf8path = env ? env : "/tmp/"; + utf8path = LLStringUtil::getenv("TMPDIR", "/tmp/"); #endif if (utf8path[utf8path.size() - 1] != sep) { diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index 14b98f872b..6ad90db244 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -111,6 +111,9 @@ #endif +// Although thread_local is now a standard storage class, we can't just +// #define LL_THREAD_LOCAL as thread_local because the *usage* is different. +// We'll have to take the time to change LL_THREAD_LOCAL declarations by hand. #if LL_WINDOWS # define LL_THREAD_LOCAL __declspec(thread) #else @@ -187,6 +190,24 @@ #define LL_DLLIMPORT #endif // LL_WINDOWS +#if ! defined(LL_WINDOWS) +#define LL_WCHAR_T_NATIVE 1 +#else // LL_WINDOWS +// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros +// _WCHAR_T_DEFINED is defined if wchar_t is provided at all. +// Specifically, it has value 1 if wchar_t is an intrinsic type, else empty. +// _NATIVE_WCHAR_T_DEFINED has value 1 if wchar_t is intrinsic, else undefined. +// For years we have compiled with /Zc:wchar_t-, meaning that wchar_t is a +// typedef for unsigned short (in stddef.h). Lore has it that one of our +// proprietary binary-only libraries has traditionally been built that way and +// therefore EVERYTHING ELSE requires it. Therefore, in a typical Linden +// Windows build, _WCHAR_T_DEFINED is defined but empty, while +// _NATIVE_WCHAR_T_DEFINED is undefined. +# if defined(_NATIVE_WCHAR_T_DEFINED) +# define LL_WCHAR_T_NATIVE 1 +# endif // _NATIVE_WCHAR_T_DEFINED +#endif // LL_WINDOWS + #if LL_COMMON_LINK_SHARED // CMake automagically defines llcommon_EXPORTS only when building llcommon // sources, and only when llcommon is a shared library (i.e. when diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 5753efdc59..1fa53f322b 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -1205,30 +1205,9 @@ static LLProcess::Status interpret_status(int status) /// GetLastError()/FormatMessage() boilerplate static std::string WindowsErrorString(const std::string& operation) { - int result = GetLastError(); - - LPTSTR error_str = 0; - if (FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, - NULL, - result, - 0, - (LPTSTR)&error_str, - 0, - NULL) - != 0) - { - // convert from wide-char string to multi-byte string - char message[256]; - wcstombs(message, error_str, sizeof(message)); - message[sizeof(message)-1] = 0; - LocalFree(error_str); - // convert to std::string to trim trailing whitespace - std::string mbsstr(message); - mbsstr.erase(mbsstr.find_last_not_of(" \t\r\n")); - return STRINGIZE(operation << " failed (" << result << "): " << mbsstr); - } - return STRINGIZE(operation << " failed (" << result - << "), but FormatMessage() did not explain"); + auto result = GetLastError(); + return STRINGIZE(operation << " failed (" << result << "): " + << windows_message(result)); } /***************************************************************************** diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index e246d8b1a0..08551a3955 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -30,6 +30,7 @@ #include "llerror.h" #include "llfasttimer.h" #include "llsd.h" +#include #if LL_WINDOWS #include "llwin32headerslean.h" @@ -744,6 +745,11 @@ namespace snprintf_hack } } +std::string ll_convert_wide_to_string(const wchar_t* in) +{ + return ll_convert_wide_to_string(in, CP_UTF8); +} + std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page) { std::string out; @@ -781,7 +787,12 @@ std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page) return out; } -wchar_t* ll_convert_string_to_wide(const std::string& in, unsigned int code_page) +std::wstring ll_convert_string_to_wide(const std::string& in) +{ + return ll_convert_string_to_wide(in, CP_UTF8); +} + +std::wstring ll_convert_string_to_wide(const std::string& in, unsigned int code_page) { // From review: // We can preallocate a wide char buffer that is the same length (in wchar_t elements) as the utf8 input, @@ -791,28 +802,148 @@ wchar_t* ll_convert_string_to_wide(const std::string& in, unsigned int code_page // but we *are* seeing string operations taking a bunch of time, especially when constructing widgets. // int output_str_len = MultiByteToWideChar(code_page, 0, in.c_str(), in.length(), NULL, 0); - // reserve place to NULL terminator - int output_str_len = in.length(); - wchar_t* w_out = new wchar_t[output_str_len + 1]; + // reserve an output buffer that will be destroyed on exit, with a place + // to put NULL terminator + std::vector w_out(in.length() + 1); - memset(w_out, 0, output_str_len + 1); - int real_output_str_len = MultiByteToWideChar (code_page, 0, in.c_str(), in.length(), w_out, output_str_len); + memset(&w_out[0], 0, w_out.size()); + int real_output_str_len = MultiByteToWideChar(code_page, 0, in.c_str(), in.length(), + &w_out[0], w_out.size() - 1); //looks like MultiByteToWideChar didn't add null terminator to converted string, see EXT-4858. w_out[real_output_str_len] = 0; - return w_out; + // construct string from our temporary output buffer + return {&w_out[0]}; +} + +LLWString ll_convert_wide_to_wstring(const std::wstring& in) +{ + // This function, like its converse, is a placeholder, encapsulating a + // guilty little hack: the only "official" way nat has found to convert + // between std::wstring (16 bits on Windows) and LLWString (UTF-32) is + // by using iconv, which we've avoided so far. It kinda sorta works to + // just copy individual characters... + // The point is that if/when we DO introduce some more official way to + // perform such conversions, we should only have to call it here. + return { in.begin(), in.end() }; +} + +std::wstring ll_convert_wstring_to_wide(const LLWString& in) +{ + // See comments in ll_convert_wide_to_wstring() + return { in.begin(), in.end() }; } std::string ll_convert_string_to_utf8_string(const std::string& in) { - wchar_t* w_mesg = ll_convert_string_to_wide(in, CP_ACP); - std::string out_utf8(ll_convert_wide_to_string(w_mesg, CP_UTF8)); - delete[] w_mesg; + auto w_mesg = ll_convert_string_to_wide(in, CP_ACP); + std::string out_utf8(ll_convert_wide_to_string(w_mesg.c_str(), CP_UTF8)); return out_utf8; } -#endif // LL_WINDOWS + +namespace +{ + +void HeapFree_deleter(void* ptr) +{ + // instead of LocalFree(), per https://stackoverflow.com/a/31541205 + HeapFree(GetProcessHeap(), NULL, ptr); +} + +} // anonymous namespace + +template<> +std::wstring windows_message(DWORD error) +{ + // derived from https://stackoverflow.com/a/455533 + wchar_t* rawptr = nullptr; + auto okay = FormatMessageW( + // use system message tables for GetLastError() codes + FORMAT_MESSAGE_FROM_SYSTEM | + // internally allocate buffer and return its pointer + FORMAT_MESSAGE_ALLOCATE_BUFFER | + // you cannot pass insertion parameters (thanks Gandalf) + FORMAT_MESSAGE_IGNORE_INSERTS | + // ignore line breaks in message definition text + FORMAT_MESSAGE_MAX_WIDTH_MASK, + NULL, // lpSource, unused with FORMAT_MESSAGE_FROM_SYSTEM + error, // dwMessageId + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // dwLanguageId + (LPWSTR)&rawptr, // lpBuffer: force-cast wchar_t** to wchar_t* + 0, // nSize, unused with FORMAT_MESSAGE_ALLOCATE_BUFFER + NULL); // Arguments, unused + + // make a unique_ptr from rawptr so it gets cleaned up properly + std::unique_ptr bufferptr(rawptr, HeapFree_deleter); + + if (okay && bufferptr) + { + // got the message, return it ('okay' is length in characters) + return { bufferptr.get(), okay }; + } + + // did not get the message, synthesize one + auto format_message_error = GetLastError(); + std::wostringstream out; + out << L"GetLastError() " << error << L" (FormatMessageW() failed with " + << format_message_error << L")"; + return out.str(); +} + +boost::optional llstring_getoptenv(const std::string& key) +{ + auto wkey = ll_convert_string_to_wide(key); + // Take a wild guess as to how big the buffer should be. + std::vector buffer(1024); + auto n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); + // If our initial guess was too short, n will indicate the size (in + // wchar_t's) that buffer should have been, including the terminating nul. + if (n > (buffer.size() - 1)) + { + // make it big enough + buffer.resize(n); + // and try again + n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); + } + // did that (ultimately) succeed? + if (n) + { + // great, return populated boost::optional + return boost::optional(&buffer[0]); + } + + // not successful + auto last_error = GetLastError(); + // Don't bother warning for NOT_FOUND; that's an expected case + if (last_error != ERROR_ENVVAR_NOT_FOUND) + { + LL_WARNS() << "GetEnvironmentVariableW('" << key << "') failed: " + << windows_message(last_error) << LL_ENDL; + } + // return empty boost::optional + return {}; +} + +#else // ! LL_WINDOWS + +boost::optional llstring_getoptenv(const std::string& key) +{ + auto found = getenv(key.c_str()); + if (found) + { + // return populated boost::optional + return boost::optional(found); + } + else + { + // return empty boost::optional + return {}; + } +} + +#endif // ! LL_WINDOWS long LLStringOps::sPacificTimeOffset = 0; long LLStringOps::sLocalTimeOffset = 0; diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 9c030473fb..ab35612430 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -27,6 +27,7 @@ #ifndef LL_LLSTRING_H #define LL_LLSTRING_H +#include #include #include //#include @@ -341,6 +342,19 @@ public: const string_type& string, const string_type& substr); + /** + * get environment string value with proper Unicode handling + * (key is always UTF-8) + * detect absence by return value == dflt + */ + static string_type getenv(const std::string& key, const string_type& dflt=""); + /** + * get optional environment string value with proper Unicode handling + * (key is always UTF-8) + * detect absence by (! return value) + */ + static boost::optional getoptenv(const std::string& key); + static void addCRLF(string_type& string); static void removeCRLF(string_type& string); static void removeWindowsCR(string_type& string); @@ -500,6 +514,37 @@ LL_COMMON_API bool iswindividual(llwchar elem); * Unicode support */ +/// generic conversion aliases +template +struct ll_convert_impl +{ + // Don't even provide a generic implementation. We specialize for every + // combination we do support. + TO operator()(const FROM& in) const; +}; + +// Use a function template to get the nice ll_convert(from_value) API. +template +TO ll_convert(const FROM& in) +{ + return ll_convert_impl()(in); +} + +// degenerate case +template +struct ll_convert_impl +{ + T operator()(const T& in) const { return in; } +}; + +// specialize ll_convert_impl to return EXPR +#define ll_convert_alias(TO, FROM, EXPR) \ +template<> \ +struct ll_convert_impl \ +{ \ + TO operator()(const FROM& in) const { return EXPR; } \ +} + // Make the incoming string a utf8 string. Replaces any unknown glyph // with the UNKNOWN_CHARACTER. Once any unknown glyph is found, the rest // of the data may not be recovered. @@ -507,30 +552,88 @@ LL_COMMON_API std::string rawstr_to_utf8(const std::string& raw); // // We should never use UTF16 except when communicating with Win32! +// https://docs.microsoft.com/en-us/cpp/cpp/char-wchar-t-char16-t-char32-t +// nat 2018-12-14: I consider the whole llutf16string thing a mistake, because +// the Windows APIs we want to call are all defined in terms of wchar_t* +// (or worse, LPCTSTR). +// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types + +// While there is no point coding for an ASCII-only world (! defined(UNICODE)), +// use of U16 and llutf16string for Windows APIs locks in /Zc:wchar_t-. Going +// forward, we should code in terms of wchar_t and std::wstring so as to +// support either setting of /Zc:wchar_t. + +// The first link above states that char can be used to hold ASCII or any +// multi-byte character set, and distinguishes wchar_t (UTF-16LE), char16_t +// (UTF-16) and char32_t (UTF-32). Nonetheless, within this code base: +// * char and std::string always hold UTF-8 (of which ASCII is a subset). It +// is a BUG if they are used to pass strings in any other multi-byte +// encoding. +// * wchar_t and std::wstring should be our interface to Windows wide-string +// APIs, and therefore hold UTF-16LE. +// * U16 and llutf16string are the previous but DEPRECATED UTF-16LE type. Do +// not introduce new uses of U16 or llutf16string for string data. +// * llwchar and LLWString hold UTF-32 strings. +// * Do not introduce char16_t or std::u16string. +// * Do not introduce char32_t or std::u32string. // +// This typedef may or may not be identical to std::wstring, depending on +// LL_WCHAR_T_NATIVE. typedef std::basic_string llutf16string; +#if ! defined(LL_WCHAR_T_NATIVE) +// wchar_t is identical to U16, and std::wstring is identical to llutf16string. +// Defining an ll_convert alias involving llutf16string would collide with the +// comparable preferred alias involving std::wstring. (In this scenario, if +// you pass llutf16string, it will engage the std::wstring specialization.) +#define ll_convert_u16_alias(TO, FROM, EXPR) // nothing +#else // defined(LL_WCHAR_T_NATIVE) +// wchar_t is a distinct native type, so llutf16string is also a distinct +// type, and there IS a point to converting separately to/from llutf16string. +// (But why? Windows APIs are still defined in terms of wchar_t, and +// in this scenario llutf16string won't work for them!) +#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) + +#if LL_WINDOWS +// LL_WCHAR_T_NATIVE is defined on non-Windows systems because, in fact, +// wchar_t is native. Everywhere but Windows, we use it for llwchar (see +// stdtypes.h). That makes LLWString identical to std::wstring, so these +// aliases for std::wstring would collide with those for LLWString. Only +// define on Windows, where converting between std::wstring and llutf16string +// means copying chars. +ll_convert_alias(llutf16string, std::wstring, llutf16string(in.begin(), in.end())); +ll_convert_alias(std::wstring, llutf16string, std::wstring(in.begin(), in.end())); +#endif // LL_WINDOWS +#endif // defined(LL_WCHAR_T_NATIVE) + LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len); LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str); +ll_convert_u16_alias(LLWString, llutf16string, utf16str_to_wstring(in)); LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len); LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str); +ll_convert_u16_alias(llutf16string, LLWString, wstring_to_utf16str(in)); LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str, S32 len); LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str ); +ll_convert_u16_alias(llutf16string, std::string, utf8str_to_utf16str(in)); LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str, S32 len); LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str); // Same function, better name. JC inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); } +// best name of all +ll_convert_alias(LLWString, std::string, utf8string_to_wstring(in)); // LL_COMMON_API S32 wchar_to_utf8chars(llwchar inchar, char* outchars); LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str, S32 len); LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str); +ll_convert_alias(std::string, LLWString, wstring_to_utf8str(in)); LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str, S32 len); LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str); +ll_convert_u16_alias(std::string, llutf16string, utf16str_to_utf8str(in)); #if LL_WINDOWS inline std::string wstring_to_utf8str(const llutf16string &utf16str) { return utf16str_to_utf8str(utf16str);} @@ -644,22 +747,77 @@ using snprintf_hack::snprintf; * This replaces the unsafe W2A macro from ATL. */ LL_COMMON_API std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page); +LL_COMMON_API std::string ll_convert_wide_to_string(const wchar_t* in); // default CP_UTF8 +inline std::string ll_convert_wide_to_string(const std::wstring& in, unsigned int code_page) +{ + return ll_convert_wide_to_string(in.c_str(), code_page); +} +inline std::string ll_convert_wide_to_string(const std::wstring& in) +{ + return ll_convert_wide_to_string(in.c_str()); +} +ll_convert_alias(std::string, std::wstring, ll_convert_wide_to_string(in)); /** * Converts a string to wide string. - * - * It will allocate memory for result string with "new []". Don't forget to release it with "delete []". */ -LL_COMMON_API wchar_t* ll_convert_string_to_wide(const std::string& in, unsigned int code_page); +LL_COMMON_API std::wstring ll_convert_string_to_wide(const std::string& in, + unsigned int code_page); +LL_COMMON_API std::wstring ll_convert_string_to_wide(const std::string& in); + // default CP_UTF8 +ll_convert_alias(std::wstring, std::string, ll_convert_string_to_wide(in)); /** - * Converts incoming string into urf8 string + * Convert a Windows wide string to our LLWString + */ +LL_COMMON_API LLWString ll_convert_wide_to_wstring(const std::wstring& in); +ll_convert_alias(LLWString, std::wstring, ll_convert_wide_to_wstring(in)); + +/** + * Convert LLWString to Windows wide string + */ +LL_COMMON_API std::wstring ll_convert_wstring_to_wide(const LLWString& in); +ll_convert_alias(std::wstring, LLWString, ll_convert_wstring_to_wide(in)); + +/** + * Converts incoming string into utf8 string * */ LL_COMMON_API std::string ll_convert_string_to_utf8_string(const std::string& in); +/// Get Windows message string for passed GetLastError() code +// VS 2013 doesn't let us forward-declare this template, which is what we +// started with, so the implementation could reference the specialization we +// haven't yet declared. Somewhat weirdly, just stating the generic +// implementation in terms of the specialization works, even in this order... + +// the general case is just a conversion from the sole implementation +// Microsoft says DWORD is a typedef for unsigned long +// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types +// so rather than drag windows.h into everybody's include space... +template +STRING windows_message(unsigned long error) +{ + return ll_convert(windows_message(error)); +} + +/// There's only one real implementation +template<> +LL_COMMON_API std::wstring windows_message(unsigned long error); + +/// Get Windows message string, implicitly calling GetLastError() +template +STRING windows_message() { return windows_message(GetLastError()); } + //@} -#endif // LL_WINDOWS + +LL_COMMON_API boost::optional llstring_getoptenv(const std::string& key); + +#else // ! LL_WINDOWS + +LL_COMMON_API boost::optional llstring_getoptenv(const std::string& key); + +#endif // ! LL_WINDOWS /** * Many of the 'strip' and 'replace' methods of LLStringUtilBase need @@ -1602,6 +1760,37 @@ bool LLStringUtilBase::endsWith( return (idx == (string.size() - substr.size())); } +// static +template +auto LLStringUtilBase::getoptenv(const std::string& key) -> boost::optional +{ + auto found(llstring_getoptenv(key)); + if (found) + { + // return populated boost::optional + return { ll_convert(*found) }; + } + else + { + // empty boost::optional + return {}; + } +} + +// static +template +auto LLStringUtilBase::getenv(const std::string& key, const string_type& dflt) -> string_type +{ + auto found(getoptenv(key)); + if (found) + { + return *found; + } + else + { + return dflt; + } +} template BOOL LLStringUtilBase::convertToBOOL(const string_type& string, BOOL& value) diff --git a/indra/llcommon/stdtypes.h b/indra/llcommon/stdtypes.h index bf3f3f9ee8..6c9871e76c 100644 --- a/indra/llcommon/stdtypes.h +++ b/indra/llcommon/stdtypes.h @@ -37,7 +37,12 @@ typedef signed int S32; typedef unsigned int U32; #if LL_WINDOWS -// Windows wchar_t is 16-bit +// https://docs.microsoft.com/en-us/cpp/build/reference/zc-wchar-t-wchar-t-is-native-type +// https://docs.microsoft.com/en-us/cpp/cpp/fundamental-types-cpp +// Windows wchar_t is 16-bit, whichever way /Zc:wchar_t is set. In effect, +// Windows wchar_t is always a typedef, either for unsigned short or __wchar_t. +// (__wchar_t, available either way, is Microsoft's native 2-byte wchar_t type.) +// In any case, llwchar should be a UTF-32 type. typedef U32 llwchar; #else typedef wchar_t llwchar; diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index c387da6c48..45648536c4 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -26,6 +26,7 @@ #include "wrapllerrs.h" #include "llevents.h" #include "llprocess.h" +#include "llstring.h" #include "stringize.h" #include "StringVec.h" #include @@ -198,14 +199,12 @@ namespace tut // basename. reader_module(LLProcess::basename( reader.getName().substr(0, reader.getName().length()-3))), - pPYTHON(getenv("PYTHON")), - PYTHON(pPYTHON? pPYTHON : "") + PYTHON(LLStringUtil::getenv("PYTHON")) { - ensure("Set PYTHON to interpreter pathname", pPYTHON); + ensure("Set PYTHON to interpreter pathname", !PYTHON.empty()); } NamedExtTempFile reader; const std::string reader_module; - const char* pPYTHON; const std::string PYTHON; }; typedef test_group llleap_group; diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index b27e125d2e..5c87cdabd9 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -34,6 +34,7 @@ #include "stringize.h" #include "llsdutil.h" #include "llevents.h" +#include "llstring.h" #include "wrapllerrs.h" #if defined(LL_WINDOWS) @@ -142,8 +143,8 @@ struct PythonProcessLauncher mDesc(desc), mScript("py", script) { - const char* PYTHON(getenv("PYTHON")); - tut::ensure("Set $PYTHON to the Python interpreter", PYTHON); + auto PYTHON(LLStringUtil::getenv("PYTHON")); + tut::ensure("Set $PYTHON to the Python interpreter", !PYTHON.empty()); mParams.desc = desc + " script"; mParams.executable = PYTHON; diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 745e3a168c..6ac974e659 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -41,6 +41,7 @@ typedef U32 uint32_t; #include #include #include "llprocess.h" +#include "llstring.h" #endif #include "boost/range.hpp" @@ -1705,8 +1706,8 @@ namespace tut template void python(const std::string& desc, const CONTENT& script, int expect=0) { - const char* PYTHON(getenv("PYTHON")); - ensure("Set $PYTHON to the Python interpreter", PYTHON); + auto PYTHON(LLStringUtil::getenv("PYTHON")); + ensure("Set $PYTHON to the Python interpreter", !PYTHON.empty()); NamedTempFile scriptfile("py", script); @@ -1714,7 +1715,7 @@ namespace tut std::string q("\""); std::string qPYTHON(q + PYTHON + q); std::string qscript(q + scriptfile.getName() + q); - int rc = _spawnl(_P_WAIT, PYTHON, qPYTHON.c_str(), qscript.c_str(), NULL); + int rc = _spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(), NULL); if (rc == -1) { char buffer[256]; diff --git a/indra/llmessage/tests/commtest.h b/indra/llmessage/tests/commtest.h index 7c8f27bbd2..0359eba803 100644 --- a/indra/llmessage/tests/commtest.h +++ b/indra/llmessage/tests/commtest.h @@ -34,6 +34,7 @@ #include "llsd.h" #include "llhost.h" #include "llexception.h" +#include "llstring.h" #include "stringize.h" #include #include @@ -46,12 +47,7 @@ struct CommtestError: public LLException static bool query_verbose() { - const char* cbose = getenv("INTEGRATION_TEST_VERBOSE"); - if (! cbose) - { - cbose = "1"; - } - std::string strbose(cbose); + std::string strbose(LLStringUtil::getenv("INTEGRATION_TEST_VERBOSE", "1")); return (! (strbose == "0" || strbose == "off" || strbose == "false" || strbose == "quiet")); } diff --git a/indra/llmessage/tests/llhttpclient_test.cpp b/indra/llmessage/tests/llhttpclient_test.cpp index 9356a14f1f..78faa66a0d 100644 --- a/indra/llmessage/tests/llhttpclient_test.cpp +++ b/indra/llmessage/tests/llhttpclient_test.cpp @@ -41,6 +41,7 @@ #include "llpumpio.h" #include "lliosocket.h" +#include "llstring.h" #include "stringize.h" #include "llcleanup.h" @@ -50,13 +51,13 @@ namespace tut { public: HTTPClientTestData(): - PORT(getenv("PORT")), + PORT(LLStringUtil::getenv("PORT")), // Turning NULL PORT into empty string doesn't make things work; // that's just to keep this initializer from blowing up. We test // PORT separately in the constructor body. - local_server(STRINGIZE("http://127.0.0.1:" << (PORT? PORT : "") << "/")) + local_server(STRINGIZE("http://127.0.0.1:" << PORT << "/")) { - ensure("Set environment variable PORT to local test server port", PORT); + ensure("Set environment variable PORT to local test server port", !PORT.empty()); apr_pool_create(&mPool, NULL); LLCurl::initClass(false); mClientPump = new LLPumpIO(mPool); @@ -87,7 +88,7 @@ namespace tut } } - const char* const PORT; + const std::string PORT; const std::string local_server; private: diff --git a/indra/llrender/llfontgl.cpp b/indra/llrender/llfontgl.cpp index 1f9a6664e1..6fa00b52c0 100644 --- a/indra/llrender/llfontgl.cpp +++ b/indra/llrender/llfontgl.cpp @@ -44,6 +44,12 @@ // Third party library includes #include +#if LL_WINDOWS +#include +#include +#include +#endif // LL_WINDOWS + const S32 BOLD_OFFSET = 1; // static class members @@ -1156,33 +1162,24 @@ LLFontGL* LLFontGL::getFontDefault() // static std::string LLFontGL::getFontPathSystem() { - std::string system_path; +#if LL_DARWIN + // HACK for Mac OS X + return "/System/Library/Fonts/"; - // Try to figure out where the system's font files are stored. - char *system_root = NULL; -#if LL_WINDOWS - system_root = getenv("SystemRoot"); /* Flawfinder: ignore */ - if (!system_root) - { - LL_WARNS() << "SystemRoot not found, attempting to load fonts from default path." << LL_ENDL; - } +#elif LL_WINDOWS + wchar_t *pwstr = NULL; + HRESULT okay = SHGetKnownFolderPath(FOLDERID_Fonts, 0, NULL, &pwstr); + if (SUCCEEDED(okay) && pwstr) + { + std::string fontpath(ll_convert_wide_to_string(pwstr)); + // SHGetKnownFolderPath() contract requires us to free pwstr + CoTaskMemFree(pwstr); + return fontpath; + } #endif - if (system_root) - { - system_path = llformat("%s/fonts/", system_root); - } - else - { -#if LL_WINDOWS - // HACK for windows 98/Me - system_path = "/WINDOWS/FONTS/"; -#elif LL_DARWIN - // HACK for Mac OS X - system_path = "/System/Library/Fonts/"; -#endif - } - return system_path; + LL_WARNS() << "Could not determine system fonts path" << LL_ENDL; + return {}; } diff --git a/indra/llvfs/lldir_linux.cpp b/indra/llvfs/lldir_linux.cpp index f357210022..2e92e13bfd 100644 --- a/indra/llvfs/lldir_linux.cpp +++ b/indra/llvfs/lldir_linux.cpp @@ -29,6 +29,7 @@ #include "lldir_linux.h" #include "llerror.h" #include "llrand.h" +#include "llstring.h" #include #include #include @@ -40,28 +41,24 @@ static std::string getCurrentUserHome(char* fallback) { const uid_t uid = getuid(); struct passwd *pw; - char *result_cstr = fallback; - + pw = getpwuid(uid); if ((pw != NULL) && (pw->pw_dir != NULL)) { - result_cstr = (char*) pw->pw_dir; + return pw->pw_dir; + } + + LL_INFOS() << "Couldn't detect home directory from passwd - trying $HOME" << LL_ENDL; + auto home_env = LLStringUtil::getoptenv("HOME"); + if (home_env) + { + return *home_env; } else { - LL_INFOS() << "Couldn't detect home directory from passwd - trying $HOME" << LL_ENDL; - const char *const home_env = getenv("HOME"); /* Flawfinder: ignore */ - if (home_env) - { - result_cstr = (char*) home_env; - } - else - { - LL_WARNS() << "Couldn't detect home directory! Falling back to " << fallback << LL_ENDL; - } + LL_WARNS() << "Couldn't detect home directory! Falling back to " << fallback << LL_ENDL; + return fallback; } - - return std::string(result_cstr); } @@ -156,18 +153,18 @@ void LLDir_Linux::initAppDirs(const std::string &app_name, if (!app_read_only_data_dir.empty()) { mAppRODataDir = app_read_only_data_dir; - mSkinBaseDir = mAppRODataDir + mDirDelimiter + "skins"; + mSkinBaseDir = add(mAppRODataDir, "skins"); } mAppName = app_name; std::string upper_app_name(app_name); LLStringUtil::toUpper(upper_app_name); - char* app_home_env = getenv((upper_app_name + "_USER_DIR").c_str()); /* Flawfinder: ignore */ + auto app_home_env(LLStringUtil::getoptenv(upper_app_name + "_USER_DIR")); if (app_home_env) { // user has specified own userappdir i.e. $SECONDLIFE_USER_DIR - mOSUserAppDir = app_home_env; + mOSUserAppDir = *app_home_env; } else { diff --git a/indra/llvfs/lldir_solaris.cpp b/indra/llvfs/lldir_solaris.cpp index d3536a12ee..f18560ff20 100644 --- a/indra/llvfs/lldir_solaris.cpp +++ b/indra/llvfs/lldir_solaris.cpp @@ -29,6 +29,7 @@ #include "lldir_solaris.h" #include "llerror.h" #include "llrand.h" +#include "llstring.h" #include #include #include @@ -41,30 +42,28 @@ static std::string getCurrentUserHome(char* fallback) { + // fwiw this exactly duplicates getCurrentUserHome() in lldir_linux.cpp... + // we should either derive both from LLDir_Posix or just axe Solaris. const uid_t uid = getuid(); struct passwd *pw; - char *result_cstr = fallback; - + pw = getpwuid(uid); if ((pw != NULL) && (pw->pw_dir != NULL)) { - result_cstr = (char*) pw->pw_dir; + return pw->pw_dir; + } + + LL_INFOS() << "Couldn't detect home directory from passwd - trying $HOME" << LL_ENDL; + auto home_env = LLStringUtil::getoptenv("HOME"); + if (home_env) + { + return *home_env; } else { - LL_INFOS() << "Couldn't detect home directory from passwd - trying $HOME" << LL_ENDL; - const char *const home_env = getenv("HOME"); /* Flawfinder: ignore */ - if (home_env) - { - result_cstr = (char*) home_env; - } - else - { - LL_WARNS() << "Couldn't detect home directory! Falling back to " << fallback << LL_ENDL; - } + LL_WARNS() << "Couldn't detect home directory! Falling back to " << fallback << LL_ENDL; + return fallback; } - - return std::string(result_cstr); } @@ -135,27 +134,15 @@ LLDir_Solaris::LLDir_Solaris() //NOTE: Why force people to cd into the package directory? // Look for SECONDLIFE env variable and use it, if set. - char *dcf = getenv("SECONDLIFE"); - if(dcf != NULL){ - (void)strcpy(path, dcf); - (void)strcat(path, "/bin"); //NOTE: make sure we point at the bin - mExecutableDir = strdup(path); + auto SECONDLIFE(LLDirUtil::getoptenv("SECONDLIFE")); + if(SECONDLIFE){ + mExecutableDir = add(*SECONDLIFE, "bin"); //NOTE: make sure we point at the bin }else{ - // plunk a null at last '/' to get exec dir - char *s = execpath + strlen(execpath) -1; - while(*s != '/' && s != execpath){ - --s; - } - - if(s != execpath){ - *s = (char)NULL; - - mExecutableDir = strdup(execpath); - LL_INFOS() << "mExecutableDir = [" << mExecutableDir << "]" << LL_ENDL; - } + mExecutableDir = getDirName(execpath); + LL_INFOS() << "mExecutableDir = [" << mExecutableDir << "]" << LL_ENDL; } - - mLLPluginDir = mExecutableDir + mDirDelimiter + "llplugin"; + + mLLPluginDir = add(mExecutableDir, "llplugin"); // *TODO: don't use /tmp, use $HOME/.secondlife/tmp or something. mTempDir = "/tmp"; @@ -175,17 +162,18 @@ void LLDir_Solaris::initAppDirs(const std::string &app_name, if (!app_read_only_data_dir.empty()) { mAppRODataDir = app_read_only_data_dir; + mSkinBaseDir = add(mAppRODataDir, "skins"); } mAppName = app_name; std::string upper_app_name(app_name); LLStringUtil::toUpper(upper_app_name); - char* app_home_env = getenv((upper_app_name + "_USER_DIR").c_str()); /* Flawfinder: ignore */ + auto app_home_env(LLStringUtil::getoptenv(upper_app_name + "_USER_DIR")); if (app_home_env) { // user has specified own userappdir i.e. $SECONDLIFE_USER_DIR - mOSUserAppDir = app_home_env; + mOSUserAppDir = *app_home_env; } else { diff --git a/indra/llvfs/lldir_win32.cpp b/indra/llvfs/lldir_win32.cpp index 88bb4c804b..699b6f5f83 100644 --- a/indra/llvfs/lldir_win32.cpp +++ b/indra/llvfs/lldir_win32.cpp @@ -30,7 +30,9 @@ #include "lldir_win32.h" #include "llerror.h" -#include "llrand.h" // for gLindenLabRandomNumber +#include "llstring.h" +#include "stringize.h" +#include "llfile.h" #include #include @@ -43,15 +45,87 @@ #define PACKVERSION(major,minor) MAKELONG(minor,major) DWORD GetDllVersion(LPCTSTR lpszDllName); +namespace +{ // anonymous + enum class prst { INIT, OPEN, SKIP } state = prst::INIT; + // This is called so early that we can't count on static objects being + // properly constructed yet, so declare a pointer instead of an instance. + std::ofstream* prelogf = nullptr; + + void prelog(const std::string& message) + { + boost::optional prelog_name; + + switch (state) + { + case prst::INIT: + // assume we failed, until we succeed + state = prst::SKIP; + + prelog_name = LLStringUtil::getoptenv("PRELOG"); + if (! prelog_name) + // no PRELOG variable set, carry on + return; + prelogf = new llofstream(*prelog_name, std::ios_base::app); + if (! (prelogf && prelogf->is_open())) + // can't complain to anybody; how? + return; + // got the log file open, cool! + state = prst::OPEN; + (*prelogf) << "========================================================================" + << std::endl; + // fall through, don't break + + case prst::OPEN: + (*prelogf) << message << std::endl; + break; + + case prst::SKIP: + // either PRELOG isn't set, or we failed to open that pathname + break; + } + } +} // anonymous namespace + +#define PRELOG(expression) prelog(STRINGIZE(expression)) + LLDir_Win32::LLDir_Win32() { // set this first: used by append() and add() methods mDirDelimiter = "\\"; + WCHAR w_str[MAX_PATH]; // Application Data is where user settings go. We rely on $APPDATA being - // correct; in fact the VMP makes a point of setting it properly, since - // Windows itself botches the job for non-ASCII usernames (MAINT-8087). - mOSUserDir = ll_safe_string(getenv("APPDATA")); + // correct. + auto APPDATA = LLStringUtil::getoptenv("APPDATA"); + if (APPDATA) + { + mOSUserDir = *APPDATA; + } + PRELOG("APPDATA='" << mOSUserDir << "'"); + // On Windows, we could have received a plain-ASCII pathname in which + // non-ASCII characters have been munged to '?', or the pathname could + // have been badly encoded and decoded such that we now have garbage + // instead of a valid path. Check that mOSUserDir actually exists. + if (mOSUserDir.empty() || ! fileExists(mOSUserDir)) + { + PRELOG("APPDATA does not exist"); + //HRESULT okay = SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, w_str); + wchar_t *pwstr = NULL; + HRESULT okay = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &pwstr); + PRELOG("SHGetKnownFolderPath(FOLDERID_RoamingAppData) returned " << okay); + if (SUCCEEDED(okay) && pwstr) + { + // But of course, only update mOSUserDir if SHGetKnownFolderPath() works. + mOSUserDir = ll_convert_wide_to_string(pwstr); + // Not only that: update our environment so that child processes + // will see a reasonable value as well. + _wputenv_s(L"APPDATA", pwstr); + // SHGetKnownFolderPath() contract requires us to free pwstr + CoTaskMemFree(pwstr); + PRELOG("mOSUserDir='" << mOSUserDir << "'"); + } + } // We want cache files to go on the local disk, even if the // user is on a network with a "roaming profile". @@ -61,9 +135,34 @@ LLDir_Win32::LLDir_Win32() // // We used to store the cache in AppData\Roaming, and the installer // cleans up that version on upgrade. JC - mOSCacheDir = ll_safe_string(getenv("LOCALAPPDATA")); + auto LOCALAPPDATA = LLStringUtil::getoptenv("LOCALAPPDATA"); + if (LOCALAPPDATA) + { + mOSCacheDir = *LOCALAPPDATA; + } + PRELOG("LOCALAPPDATA='" << mOSCacheDir << "'"); + // Windows really does not deal well with pathnames containing non-ASCII + // characters. See above remarks about APPDATA. + if (mOSCacheDir.empty() || ! fileExists(mOSCacheDir)) + { + PRELOG("LOCALAPPDATA does not exist"); + //HRESULT okay = SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, w_str); + wchar_t *pwstr = NULL; + HRESULT okay = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &pwstr); + PRELOG("SHGetKnownFolderPath(FOLDERID_LocalAppData) returned " << okay); + if (SUCCEEDED(okay) && pwstr) + { + // But of course, only update mOSCacheDir if SHGetKnownFolderPath() works. + mOSCacheDir = ll_convert_wide_to_string(pwstr); + // Update our environment so that child processes will see a + // reasonable value as well. + _wputenv_s(L"LOCALAPPDATA", pwstr); + // SHGetKnownFolderPath() contract requires us to free pwstr + CoTaskMemFree(pwstr); + PRELOG("mOSCacheDir='" << mOSCacheDir << "'"); + } + } - WCHAR w_str[MAX_PATH]; if (GetTempPath(MAX_PATH, w_str)) { if (wcslen(w_str)) /* Flawfinder: ignore */ diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index 5c05b1d105..bbd869da6d 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -3343,17 +3343,10 @@ S32 OSMessageBoxWin32(const std::string& text, const std::string& caption, U32 t break; } - // HACK! Doesn't properly handle wide strings! - - // Convert to wchar_t, then use MessageBoxW - - // int retval_win = MessageBoxA(NULL, text.c_str(), caption.c_str(), uType); - llutf16string textW = utf8str_to_utf16str( text ); - llutf16string captionW = utf8str_to_utf16str( caption ); - int retval_win = MessageBoxW(NULL, textW.c_str(), captionW.c_str(), uType); - - // - + int retval_win = MessageBoxW(NULL, // HWND + ll_convert_string_to_wide(text).c_str(), + ll_convert_string_to_wide(caption).c_str(), + uType); S32 retval; switch(retval_win) diff --git a/indra/llxml/tests/llcontrol_test.cpp b/indra/llxml/tests/llcontrol_test.cpp index 2b691ffbb1..f7e43d6def 100644 --- a/indra/llxml/tests/llcontrol_test.cpp +++ b/indra/llxml/tests/llcontrol_test.cpp @@ -27,43 +27,31 @@ #include "linden_common.h" #include "llsdserialize.h" +#include "llfile.h" +#include "stringize.h" #include "../llcontrol.h" #include "../test/lltut.h" +#include +#include namespace tut { - struct control_group { - LLControlGroup* mCG; + std::unique_ptr mCG; std::string mTestConfigDir; std::string mTestConfigFile; + std::vector mCleanups; static bool mListenerFired; control_group() { - mCG = new LLControlGroup("foo"); + mCG.reset(new LLControlGroup("foo")); LLUUID random; random.generate(); // generate temp dir - std::ostringstream oStr; - -#ifdef LL_WINDOWS - char* tmp_dir = getenv("TMP"); - if(tmp_dir) - { - oStr << tmp_dir << "/llcontrol-test-" << random << "/"; - } - else - { - oStr << "c:/tmp/llcontrol-test-" << random << "/"; - } -#else - oStr << "/tmp/llcontrol-test-" << random << "/"; -#endif - - mTestConfigDir = oStr.str(); + mTestConfigDir = STRINGIZE(LLFile::tmpdir() << "llcontrol-test-" << random << "/"); mTestConfigFile = mTestConfigDir + "settings.xml"; LLFile::mkdir(mTestConfigDir); LLSD config; @@ -76,7 +64,12 @@ namespace tut ~control_group() { //Remove test files - delete mCG; + for (auto filename : mCleanups) + { + LLFile::remove(filename); + } + LLFile::remove(mTestConfigFile); + LLFile::rmdir(mTestConfigDir); } void writeSettingsFile(const LLSD& config) { @@ -118,6 +111,7 @@ namespace tut ensure_equals("value of changed setting", mCG->getU32("TestSetting"), 13); LLControlGroup test_cg("foo2"); std::string temp_test_file = (mTestConfigDir + "setting_llsd_temp.xml"); + mCleanups.push_back(temp_test_file); mCG->saveToFile(temp_test_file.c_str(), TRUE); results = test_cg.loadFromFile(temp_test_file.c_str()); ensure("number of changed settings loaded", (results == 1)); @@ -139,6 +133,7 @@ namespace tut ensure_equals("value of changed setting", mCG->getU32("TestSetting"), 13); LLControlGroup test_cg("foo3"); std::string temp_test_file = (mTestConfigDir + "setting_llsd_persist_temp.xml"); + mCleanups.push_back(temp_test_file); mCG->saveToFile(temp_test_file.c_str(), TRUE); results = test_cg.loadFromFile(temp_test_file.c_str()); //If we haven't changed any settings, then we shouldn't have any settings to load @@ -153,7 +148,7 @@ namespace tut ensure("number of settings", (results == 1)); mCG->getControl("TestSetting")->getSignal()->connect(boost::bind(&this->handleListenerTest)); mCG->setU32("TestSetting", 13); - ensure("listener fired on changed setting", mListenerFired); + ensure("listener fired on changed setting", mListenerFired); } } diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 90e2640f77..6326b53606 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -1918,7 +1918,6 @@ if (WINDOWS) kernel32 odbc32 odbccp32 - ole32 oleaut32 shell32 Vfw32 diff --git a/indra/newview/installers/windows/installer_template.nsi b/indra/newview/installers/windows/installer_template.nsi index d668e011c2..b98d7c07cc 100644 --- a/indra/newview/installers/windows/installer_template.nsi +++ b/indra/newview/installers/windows/installer_template.nsi @@ -18,8 +18,7 @@ ;; ;; Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA ;; -;; NSIS Unicode 2.46.5 or higher required -;; http://www.scratchpaper.com/ +;; NSIS 3 or higher required for Unicode support ;; ;; Author: James Cook, TankMaster Finesmith, Don Kjer, Callum Prentice ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -27,6 +26,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Compiler flags ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +Unicode true SetOverwrite on # Overwrite files SetCompress auto # Compress if saves space SetCompressor /solid lzma # Compress whole installer as one block @@ -46,28 +46,33 @@ RequestExecutionLevel highest # match MULTIUSER_EXECUTIONLEVEL ;; (these files are in the same place as the nsi template but the python script generates a new nsi file in the ;; application directory so we have to add a path to these include files) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -!include "%%SOURCE%%\installers\windows\lang_en-us.nsi" # Make sure EN comes first as it will be default! -!include "%%SOURCE%%\installers\windows\lang_da.nsi" +;; Ansariel notes: "Under certain circumstances the installer will fall back +;; to the first defined (aka default) language version. So you want to include +;; en-us as first language file." +!include "%%SOURCE%%\installers\windows\lang_en-us.nsi" + +# Danish and Polish no longer supported by the viewer itself +##!include "%%SOURCE%%\installers\windows\lang_da.nsi" !include "%%SOURCE%%\installers\windows\lang_de.nsi" !include "%%SOURCE%%\installers\windows\lang_es.nsi" !include "%%SOURCE%%\installers\windows\lang_fr.nsi" !include "%%SOURCE%%\installers\windows\lang_ja.nsi" !include "%%SOURCE%%\installers\windows\lang_it.nsi" -!include "%%SOURCE%%\installers\windows\lang_pl.nsi" +##!include "%%SOURCE%%\installers\windows\lang_pl.nsi" !include "%%SOURCE%%\installers\windows\lang_pt-br.nsi" !include "%%SOURCE%%\installers\windows\lang_ru.nsi" !include "%%SOURCE%%\installers\windows\lang_tr.nsi" !include "%%SOURCE%%\installers\windows\lang_zh.nsi" # *TODO: Move these into the language files themselves -LangString LanguageCode ${LANG_DANISH} "da" +##LangString LanguageCode ${LANG_DANISH} "da" LangString LanguageCode ${LANG_GERMAN} "de" LangString LanguageCode ${LANG_ENGLISH} "en" LangString LanguageCode ${LANG_SPANISH} "es" LangString LanguageCode ${LANG_FRENCH} "fr" LangString LanguageCode ${LANG_JAPANESE} "ja" LangString LanguageCode ${LANG_ITALIAN} "it" -LangString LanguageCode ${LANG_POLISH} "pl" +##LangString LanguageCode ${LANG_POLISH} "pl" LangString LanguageCode ${LANG_PORTUGUESEBR} "pt" LangString LanguageCode ${LANG_RUSSIAN} "ru" LangString LanguageCode ${LANG_TURKISH} "tr" @@ -114,12 +119,14 @@ SetOverwrite on # Overwrite files by default # should make MultiUser.nsh initialization read existing INSTDIR from registry !define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY "${INSTNAME_KEY}" !define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME "" -# should make MultiUser.nsh initialization write $MultiUser.InstallMode to registry -!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY "${INSTNAME_KEY}" -!define MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME "InstallMode" +# Don't set MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY and +# MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME to cause the installer to +# write $MultiUser.InstallMode to the registry, because when the user installs +# multiple viewers with the same channel (same ${INSTNAME}, hence same +# ${INSTNAME_KEY}), the registry entry is overwritten. Instead we'll write a +# little file into the install directory -- see .onInstSuccess and un.onInit. !include MultiUser.nsh !include MUI2.nsh - !define MUI_BGCOLOR FFFFFF !insertmacro MUI_FUNCTION_GUIINIT @@ -242,8 +249,6 @@ lbl_configure_default_lang: # StrCmp $SKIP_DIALOGS "true" lbl_return -# Commented out; Warning in build log about not being used -;lbl_build_menu: Push "" # Use separate file so labels can be UTF-16 but we can still merge changes into this ASCII file. JC !include "%%SOURCE%%\installers\windows\language_menu.nsi" @@ -267,7 +272,21 @@ FunctionEnd ;; Prep Uninstaller Section ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Function un.onInit -!insertmacro MULTIUSER_UNINIT + # Save $INSTDIR -- it appears to have the correct value before + # MULTIUSER_UNINIT, but then gets munged by MULTIUSER_UNINIT?! + Push $INSTDIR + !insertmacro MULTIUSER_UNINIT + Pop $INSTDIR + + # Now read InstallMode.txt from $INSTDIR + Push $0 + ClearErrors + FileOpen $0 "$INSTDIR\InstallMode.txt" r + IfErrors skipread + FileRead $0 $MultiUser.InstallMode + FileClose $0 +skipread: + Pop $0 %%ENGAGEREGISTRY%% @@ -276,10 +295,10 @@ Function un.onInit IfErrors lbl_end StrCpy $LANGUAGE $0 lbl_end: - Return - # Does MultiUser.nsh init read back - # MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY into $MultiUser.InstallMode? +## MessageBox MB_OK "After restoring:$\n$$INSTDIR = '$INSTDIR'$\n$$MultiUser.InstallMode = '$MultiUser.InstallMode'$\n$$LANGUAGE = '$LANGUAGE'" + + Return FunctionEnd @@ -474,8 +493,10 @@ StrCpy $INSTSHORTCUT "${SHORTCUT}" # MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME # Couln't get NSIS to expand $MultiUser.InstallMode into the function name at Call time ${If} $MultiUser.InstallMode == 'AllUsers' +##MessageBox MB_OK "Uninstalling for all users" Call un.MultiUser.InstallMode.AllUsers ${Else} +##MessageBox MB_OK "Uninstalling for current user" Call un.MultiUser.InstallMode.CurrentUser ${EndIf} @@ -688,6 +709,9 @@ Function un.ProgramFiles # This placeholder is replaced by the complete list of files to uninstall by viewer_manifest.py %%DELETE_FILES%% +# our InstallMode.txt +Delete "$INSTDIR\InstallMode.txt" + # Optional/obsolete files. Delete won't fail if they don't exist. Delete "$INSTDIR\autorun.bat" Delete "$INSTDIR\dronesettings.ini" @@ -737,7 +761,12 @@ FunctionEnd ;; After install completes, launch app ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; Function .onInstSuccess - Call CheckWindowsServPack # Warn if not on the latest SP before asking to launch. + Push $0 + FileOpen $0 "$INSTDIR\InstallMode.txt" w + # No newline -- this is for our use, not for users to read. + FileWrite $0 "$MultiUser.InstallMode" + FileClose $0 + Pop $0 # Disable VMP #Push $R0 #Push $0 @@ -760,24 +789,21 @@ Function .onInstSuccess #Pop $0 #Pop $R0 # - Push $R0 # Option value, unused# - # Disable autorun - # StrCmp $SKIP_AUTORUN "true" +2; -# Assumes SetOutPath $INSTDIR - # Exec '"$WINDIR\explorer.exe" "$INSTDIR\$INSTEXE"' - # Pop $R0 - StrCmp $SKIP_DIALOGS "true" label_launch - ${GetOptions} $COMMANDLINE "/AUTOSTART" $R0 - # If parameter was there (no error) just launch - # Otherwise ask - IfErrors label_ask_launch label_launch + Call CheckWindowsServPack # Warn if not on the latest SP before asking to launch. + # Disable autorun + #StrCmp $SKIP_AUTORUN "true" +2; + StrCmp $SKIP_DIALOGS "true" label_launch + + ${GetOptions} $COMMANDLINE "/AUTOSTART" $R0 + # If parameter was there (no error) just launch + # Otherwise ask + IfErrors label_ask_launch label_launch label_ask_launch: - # Don't launch by default when silent - IfSilent label_no_launch - MessageBox MB_YESNO $(InstSuccesssQuestion) \ - IDYES label_launch IDNO label_no_launch + # Don't launch by default when silent + IfSilent label_no_launch + MessageBox MB_YESNO $(InstSuccesssQuestion) IDYES label_launch IDNO label_no_launch label_launch: # Assumes SetOutPath $INSTDIR @@ -796,10 +822,9 @@ label_launch: # string because it must decompose into separate command-line tokens. # No updater, thanks! # Exec '"$INSTDIR\$INSTEXE" precheck "$INSTDIR\$VIEWER_EXE" $SHORTCUT_LANG_PARAM' - Exec '"$WINDIR\explorer.exe" "$INSTDIR\$INSTSHORTCUT.lnk"' + Exec '"$WINDIR\explorer.exe" "$INSTDIR\$INSTSHORTCUT.lnk"' label_no_launch: - Pop $R0 - # + # FunctionEnd ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/indra/newview/installers/windows/lang_da.nsi b/indra/newview/installers/windows/lang_da.nsi index 788ef93e44..f462c82078 100644 Binary files a/indra/newview/installers/windows/lang_da.nsi and b/indra/newview/installers/windows/lang_da.nsi differ diff --git a/indra/newview/installers/windows/lang_de.nsi b/indra/newview/installers/windows/lang_de.nsi index 4cf58c91a0..433fa244e7 100644 Binary files a/indra/newview/installers/windows/lang_de.nsi and b/indra/newview/installers/windows/lang_de.nsi differ diff --git a/indra/newview/installers/windows/lang_en-us.nsi b/indra/newview/installers/windows/lang_en-us.nsi index fb03e8b3db..d9fd222af7 100644 Binary files a/indra/newview/installers/windows/lang_en-us.nsi and b/indra/newview/installers/windows/lang_en-us.nsi differ diff --git a/indra/newview/installers/windows/lang_es.nsi b/indra/newview/installers/windows/lang_es.nsi index 038c7b8d4c..ecb7962b85 100644 Binary files a/indra/newview/installers/windows/lang_es.nsi and b/indra/newview/installers/windows/lang_es.nsi differ diff --git a/indra/newview/installers/windows/lang_fr.nsi b/indra/newview/installers/windows/lang_fr.nsi index 5e181167d8..675152db46 100644 Binary files a/indra/newview/installers/windows/lang_fr.nsi and b/indra/newview/installers/windows/lang_fr.nsi differ diff --git a/indra/newview/installers/windows/lang_it.nsi b/indra/newview/installers/windows/lang_it.nsi index f91f40329e..4f3ca25961 100644 Binary files a/indra/newview/installers/windows/lang_it.nsi and b/indra/newview/installers/windows/lang_it.nsi differ diff --git a/indra/newview/installers/windows/lang_ja.nsi b/indra/newview/installers/windows/lang_ja.nsi index 1c5b56ac9e..ba85b4b6f3 100644 Binary files a/indra/newview/installers/windows/lang_ja.nsi and b/indra/newview/installers/windows/lang_ja.nsi differ diff --git a/indra/newview/installers/windows/lang_pl.nsi b/indra/newview/installers/windows/lang_pl.nsi index 4984a66e12..760233de21 100644 Binary files a/indra/newview/installers/windows/lang_pl.nsi and b/indra/newview/installers/windows/lang_pl.nsi differ diff --git a/indra/newview/installers/windows/lang_pt-br.nsi b/indra/newview/installers/windows/lang_pt-br.nsi index 40f0596046..efa8f15471 100644 Binary files a/indra/newview/installers/windows/lang_pt-br.nsi and b/indra/newview/installers/windows/lang_pt-br.nsi differ diff --git a/indra/newview/installers/windows/lang_ru.nsi b/indra/newview/installers/windows/lang_ru.nsi index ab5958a03a..255d906129 100644 Binary files a/indra/newview/installers/windows/lang_ru.nsi and b/indra/newview/installers/windows/lang_ru.nsi differ diff --git a/indra/newview/installers/windows/lang_tr.nsi b/indra/newview/installers/windows/lang_tr.nsi index 5f1a5fb2ce..3486104ba6 100644 Binary files a/indra/newview/installers/windows/lang_tr.nsi and b/indra/newview/installers/windows/lang_tr.nsi differ diff --git a/indra/newview/installers/windows/lang_zh.nsi b/indra/newview/installers/windows/lang_zh.nsi index 72fb8e2d67..23ed22b934 100644 Binary files a/indra/newview/installers/windows/lang_zh.nsi and b/indra/newview/installers/windows/lang_zh.nsi differ diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 93a9578def..cafec39309 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -674,7 +674,9 @@ bool LLAppViewerWin32::init() std::string build_data_fname( gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "build_data.json")); - std::ifstream inf(build_data_fname.c_str()); + // Use llifstream instead of std::ifstream because LL_PATH_EXECUTABLE + // could contain non-ASCII characters, which std::ifstream doesn't handle. + llifstream inf(build_data_fname.c_str()); if (! inf.is_open()) { LL_WARNS() << "Can't initialize BugSplat, can't read '" << build_data_fname diff --git a/indra/newview/llexternaleditor.cpp b/indra/newview/llexternaleditor.cpp index 3fff003393..5dd8f0ad5e 100644 --- a/indra/newview/llexternaleditor.cpp +++ b/indra/newview/llexternaleditor.cpp @@ -31,6 +31,7 @@ #include "llui.h" #include "llprocess.h" #include "llsdutil.h" +#include "llstring.h" #include // static @@ -206,12 +207,12 @@ std::string LLExternalEditor::findCommand( cmd = LLUI::sSettingGroups["config"]->getString(sSetting); LL_INFOS() << "Using setting" << LL_ENDL; } - else // otherwise use the path specified by the environment variable + else // otherwise use the path specified by the environment variable { - char* env_var_val = getenv(env_var.c_str()); + auto env_var_val(LLStringUtil::getoptenv(env_var)); if (env_var_val) { - cmd = env_var_val; + cmd = *env_var_val; LL_INFOS() << "Using env var " << env_var << LL_ENDL; } } diff --git a/indra/newview/llwebprofile.cpp b/indra/newview/llwebprofile.cpp index 2c2daa571f..e35158cb58 100644 --- a/indra/newview/llwebprofile.cpp +++ b/indra/newview/llwebprofile.cpp @@ -33,6 +33,7 @@ #include "llimagepng.h" #include "llsdserialize.h" +#include "llstring.h" // newview #include "llpanelprofile.h" // for getProfileURL(). FIXME: move the method to LLAvatarActions @@ -264,6 +265,5 @@ void LLWebProfile::reportImageUploadStatus(bool ok) std::string LLWebProfile::getAuthCookie() { // This is needed to test image uploads on Linux viewer built with OpenSSL 1.0.0 (0.9.8 works fine). - const char* debug_cookie = getenv("LL_SNAPSHOT_COOKIE"); - return debug_cookie ? debug_cookie : sAuthCookie; + return LLStringUtil::getenv("LL_SNAPSHOT_COOKIE", sAuthCookie); } diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index a7635599c1..42a4a162fa 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -561,6 +561,7 @@ class WindowsManifest(ViewerManifest): #with self.prefix(src=os.path.join(pkgdir, "VMP")): # include the compiled launcher scripts so that it gets included in the file_list # self.path('SLVersionChecker.exe') + # self.path('nextviewer.bat') #with self.prefix(dst="vmp_icons"): # with self.prefix(src=self.icon_path()): @@ -956,12 +957,10 @@ class WindowsManifest(ViewerManifest): # ): # self.sign(exe) - # We use the Unicode version of NSIS, available from - # http://www.scratchpaper.com/ # Check two paths, one for Program Files, and one for Program Files (x86). # Yay 64bit windows. for ProgramFiles in 'ProgramFiles', 'ProgramFiles(x86)': - NSIS_path = os.path.expandvars(r'${%s}\NSIS\Unicode\makensis.exe' % ProgramFiles) + NSIS_path = os.path.expandvars(r'${%s}\NSIS\makensis.exe' % ProgramFiles) if os.path.exists(NSIS_path): break installer_created=False diff --git a/indra/win_crash_logger/CMakeLists.txt b/indra/win_crash_logger/CMakeLists.txt index 151421567d..21dd2a0734 100644 --- a/indra/win_crash_logger/CMakeLists.txt +++ b/indra/win_crash_logger/CMakeLists.txt @@ -95,7 +95,6 @@ target_link_libraries(windows-crash-logger ${GOOGLE_PERFTOOLS_LIBRARIES} user32 gdi32 - ole32 oleaut32 wininet Wldap32