diff --git a/frontend/OBSApp.cpp b/frontend/OBSApp.cpp index 0d6d0e28e35fbd..a3a6d480c17563 100644 --- a/frontend/OBSApp.cpp +++ b/frontend/OBSApp.cpp @@ -282,7 +282,7 @@ string CurrentDateTimeString() struct tm tstruct; char buf[80]; tstruct = *localtime(&now); - strftime(buf, sizeof(buf), "%Y-%m-%d, %X", &tstruct); + strftime(buf, sizeof(buf), "%Y-%m-%d, %H:%M:%S", &tstruct); return buf; } @@ -724,11 +724,6 @@ bool OBSApp::InitLocale() locale = lang; - // set basic default application locale - if (!locale.empty()) { - QLocale::setDefault(QLocale(QString::fromStdString(locale).replace('-', '_'))); - } - string englishPath; if (!GetDataFilePath("locale/" DEFAULT_LANG ".ini", englishPath)) { OBSErrorBox(NULL, "Failed to find locale/" DEFAULT_LANG ".ini"); @@ -768,11 +763,6 @@ bool OBSApp::InitLocale() blog(LOG_INFO, "Using preferred locale '%s'", locale_.c_str()); locale = locale_; - // set application default locale to the new chosen one - if (!locale.empty()) { - QLocale::setDefault(QLocale(QString::fromStdString(locale).replace('-', '_'))); - } - return true; } @@ -914,13 +904,6 @@ OBSApp::OBSApp(int &argc, char **argv, profiler_name_store_t *store) { installNativeEventFilter(new OBS::NativeEventFilter); - /* fix float handling */ -#if defined(Q_OS_UNIX) - if (!setlocale(LC_NUMERIC, "C")) { - blog(LOG_WARNING, "Failed to set LC_NUMERIC to C locale"); - } -#endif - #ifndef _WIN32 // Add POSIX signal handlers: // * SIGINT diff --git a/frontend/OBSApp_Themes.cpp b/frontend/OBSApp_Themes.cpp index 7b67a45933aaae..d0c4f72bd39524 100644 --- a/frontend/OBSApp_Themes.cpp +++ b/frontend/OBSApp_Themes.cpp @@ -384,7 +384,7 @@ static vector ParseThemeVariables(const char *themeData) /* Look for a suffix and mark variable as size if it exists */ while (ch < end) { - if (!isdigit(*ch) && !isspace(*ch) && *ch != '.') { + if (!isdigit((unsigned char)*ch) && !isspace((unsigned char)*ch) && *ch != '.') { var.suffix = QString::fromUtf8(ch, end - ch); var.type = OBSThemeVariable::Size; break; @@ -581,14 +581,14 @@ static OBSThemeVariable ParseMathVariable(const QHash const QByteArray utf8 = value.toUtf8(); const char *data = utf8.constData(); - if (isdigit(*data)) { + if (isdigit((unsigned char)*data)) { double f = os_strtod(data); var.type = OBSThemeVariable::Number; var.value = f; const char *dataEnd = data + utf8.size(); while (data < dataEnd) { - if (*data && !isdigit(*data) && *data != '.') { + if (*data && !isdigit((unsigned char)*data) && *data != '.') { var.suffix = QString::fromUtf8(data, dataEnd - data); var.type = OBSThemeVariable::Size; break; diff --git a/frontend/cmake/windows/obs.manifest b/frontend/cmake/windows/obs.manifest index c5d4297d945860..4e309f7b7778ea 100644 --- a/frontend/cmake/windows/obs.manifest +++ b/frontend/cmake/windows/obs.manifest @@ -11,6 +11,11 @@ + + + UTF-8 + + diff --git a/frontend/obs-main.cpp b/frontend/obs-main.cpp index c2532416bfbecc..dfbd52c98d7dca 100644 --- a/frontend/obs-main.cpp +++ b/frontend/obs-main.cpp @@ -45,6 +45,7 @@ #include #define WIN32_LEAN_AND_MEAN #include +#include #else #include #endif @@ -77,6 +78,7 @@ bool opt_disable_missing_files_check = false; string opt_starting_collection; string opt_starting_profile; string opt_starting_scene; +bool using_utf8 = false; bool restart = false; bool restart_safe = false; @@ -416,6 +418,42 @@ static void create_log_file(fstream &logFile) } } +static bool set_utf8_locale(void) +{ + // Use system locale with UTF-8 codepage (available from Windows 10 version 1803) + bool usingUTF8 = !!setlocale(LC_ALL, ".UTF-8"); + + /* + Fallback to minimal C locale + Could use "" for system defaults, but the Windows default ANSI codepages (such as .125x or .9xx) + mismatch with UTF-8 that is assumed by many parts of the codebase. "C" only covers ASCII, so no mismatches. + */ + usingUTF8 = usingUTF8 || !!setlocale(LC_ALL, "C.UTF-8"); + +#ifdef _WIN32 + usingUTF8 = usingUTF8 && (_setmbcp(CP_UTF8) == 0); +#endif + if (!usingUTF8) { + setlocale(LC_ALL, "C"); + } + + // fix float handling + setlocale(LC_NUMERIC, "C"); + + // Copy C runtime locale for C++ + std::locale defaultLocale(setlocale(LC_ALL, nullptr)); + std::locale::global(defaultLocale); + + /* + system() is already the QLocale default, but just to be explicit about the intention. + Unlike CRT and C++ locales above, QLocale doesn't support customization of locale categories and codepages. + Ie. We can't enforce decimal point to be a dot and at the same time use user's preferred locale. + */ + QLocale::setDefault(QLocale::system()); + + return usingUTF8; +} + static auto ProfilerNameStoreRelease = [](profiler_name_store_t *store) { profiler_name_store_free(store); }; @@ -533,6 +571,12 @@ static int run_program(fstream &logFile, int argc, char *argv[]) qputenv("QT_NO_SUBTRACTOPAQUESIBLINGS", "1"); OBSApp program(argc, argv, profilerNameStore.get()); + +#if defined(Q_OS_UNIX) + // OBSApp (QApplication) constructor overwrote the locale for unix, so set it again + set_utf8_locale(); +#endif + try { QAccessible::installFactory(accessibleFactory); QFontDatabase::addApplicationFont(":/fonts/OpenSans-Regular.ttf"); @@ -683,6 +727,15 @@ static int run_program(fstream &logFile, int argc, char *argv[]) blog(LOG_INFO, "Command Line Arguments: %s", stor.str().c_str()); } + if (!using_utf8) { + blog(LOG_DEBUG, "UTF-8 locale is not available on this OS version. " + "Skipping regional formatting and sorting."); + } + + // Use collation (sorting rules) as indicator of regional settings + blog(LOG_DEBUG, "Locale: %s", setlocale(LC_COLLATE, nullptr)); + blog(LOG_DEBUG, "App language: %s", program.GetLocale()); + if (!program.OBSInit()) { return 0; } @@ -903,6 +956,7 @@ static void set_process_mitigation_policies() int main(int argc, char *argv[]) { + using_utf8 = set_utf8_locale(); #ifndef _WIN32 using SignalHandlerCallback = decltype(OBSApp::sigIntSignalHandler); diff --git a/libobs/obs-win-crash-handler.c b/libobs/obs-win-crash-handler.c index 664a9c6eb12305..befc3843a8f391 100644 --- a/libobs/obs-win-crash-handler.c +++ b/libobs/obs-win-crash-handler.c @@ -245,7 +245,7 @@ static inline void write_header(struct exception_handler_data *data) time_t now = time(0); struct tm ts; ts = *localtime(&now); - strftime(date_time, sizeof(date_time), "%Y-%m-%d, %X", &ts); + strftime(date_time, sizeof(date_time), "%Y-%m-%d, %H:%M:%S", &ts); const char *obs_bitness; if (sizeof(void *) == 8) diff --git a/libobs/obs.h b/libobs/obs.h index 4e51eb4488a0ca..a7b5319fbe6560 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -382,8 +382,11 @@ EXPORT const char *obs_get_version_string(void); * and safely copies argv/argc from main(). Subsequent calls do nothing. * * @param argc The count of command line arguments, from main() - * @param argv An array of command line arguments, copied from main() and ends + * @param argv An array of command line arguments in UTF-8, copied from main() and ends * with NULL. + * + * On Windows this requires that active code page is UTF-8. + * For example as a declaration in obs.manifest. */ EXPORT void obs_set_cmdline_args(int argc, const char *const *argv); diff --git a/libobs/util/cf-lexer.c b/libobs/util/cf-lexer.c index 77ccc361d4a3ef..014e4b57851167 100644 --- a/libobs/util/cf-lexer.c +++ b/libobs/util/cf-lexer.c @@ -71,7 +71,7 @@ static inline void cf_convert_from_escape_literal(char **p_dst, const char **p_s /* oct */ default: - if (isdigit(*src)) { + if (isdigit((unsigned char)*src)) { *(dst++) = (char)strtoul(src, NULL, 8); src += 3; } diff --git a/libobs/util/dstr.c b/libobs/util/dstr.c index 4dc415cb356841..c07b12dbe5b182 100644 --- a/libobs/util/dstr.c +++ b/libobs/util/dstr.c @@ -43,8 +43,8 @@ int astrcmpi(const char *str1, const char *str2) str2 = astrblank; do { - char ch1 = (char)toupper(*str1); - char ch2 = (char)toupper(*str2); + unsigned char ch1 = (unsigned char)toupper((unsigned char)*str1); + unsigned char ch2 = (unsigned char)toupper((unsigned char)*str2); if (ch1 < ch2) return -1; @@ -85,8 +85,8 @@ int astrcmp_n(const char *str1, const char *str2, size_t n) str2 = astrblank; do { - char ch1 = *str1; - char ch2 = *str2; + unsigned char ch1 = (unsigned char)*str1; + unsigned char ch2 = (unsigned char)*str2; if (ch1 < ch2) return -1; @@ -129,8 +129,8 @@ int astrcmpi_n(const char *str1, const char *str2, size_t n) str2 = astrblank; do { - char ch1 = (char)toupper(*str1); - char ch2 = (char)toupper(*str2); + unsigned char ch1 = (unsigned char)toupper((unsigned char)*str1); + unsigned char ch2 = (unsigned char)toupper((unsigned char)*str2); if (ch1 < ch2) return -1; diff --git a/libobs/util/lexer.c b/libobs/util/lexer.c index a69634c154fd0a..cada3ba758959d 100644 --- a/libobs/util/lexer.c +++ b/libobs/util/lexer.c @@ -29,7 +29,8 @@ int strref_cmp(const struct strref *str1, const char *str2) str2 = astrblank; do { - char ch1, ch2; + unsigned char ch1; + unsigned char ch2; ch1 = (i < str1->len) ? str1->array[i] : 0; ch2 = *str2; @@ -53,10 +54,11 @@ int strref_cmpi(const struct strref *str1, const char *str2) str2 = astrblank; do { - char ch1, ch2; + unsigned char ch1; + unsigned char ch2; - ch1 = (i < str1->len) ? (char)toupper(str1->array[i]) : 0; - ch2 = (char)toupper(*str2); + ch1 = (i < str1->len) ? (unsigned char)toupper((unsigned char)str1->array[i]) : 0; + ch2 = (unsigned char)toupper((unsigned char)*str2); if (ch1 < ch2) return -1; @@ -77,7 +79,8 @@ int strref_cmp_strref(const struct strref *str1, const struct strref *str2) return -1; do { - char ch1, ch2; + unsigned char ch1; + unsigned char ch2; ch1 = (i < str1->len) ? str1->array[i] : 0; ch2 = (i < str2->len) ? str2->array[i] : 0; @@ -103,10 +106,11 @@ int strref_cmpi_strref(const struct strref *str1, const struct strref *str2) return -1; do { - char ch1, ch2; + unsigned char ch1; + unsigned char ch2; - ch1 = (i < str1->len) ? (char)toupper(str1->array[i]) : 0; - ch2 = (i < str2->len) ? (char)toupper(str2->array[i]) : 0; + ch1 = (i < str1->len) ? (unsigned char)toupper((unsigned char)str1->array[i]) : 0; + ch2 = (i < str2->len) ? (unsigned char)toupper((unsigned char)str2->array[i]) : 0; if (ch1 < ch2) return -1; diff --git a/plugins/text-freetype2/find-font.c b/plugins/text-freetype2/find-font.c index 79312528ad1d9a..217e2f0165abd8 100644 --- a/plugins/text-freetype2/find-font.c +++ b/plugins/text-freetype2/find-font.c @@ -333,8 +333,8 @@ static inline size_t get_rating(struct font_path_info *info, struct dstr *cmp) size_t num = 0; do { - char ch1 = (char)toupper(*src); - char ch2 = (char)toupper(*dst); + unsigned char ch1 = (unsigned char)toupper((unsigned char)*src); + unsigned char ch2 = (unsigned char)toupper((unsigned char)*dst); if (ch1 != ch2) break;