mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2026-01-16 11:33:03 +01:00
256 lines
8.1 KiB
C++
256 lines
8.1 KiB
C++
/*****************************************************************************
|
|
* Copyright (c) 2014-2020 OpenRCT2 developers
|
|
*
|
|
* For a complete list of all authors, please refer to contributors.md
|
|
* Interested in contributing? Visit https://github.com/OpenRCT2/OpenRCT2
|
|
*
|
|
* OpenRCT2 is licensed under the GNU General Public License version 3.
|
|
*****************************************************************************/
|
|
|
|
#include "../common.h"
|
|
|
|
// Despite the name, this file contains support for more OSs besides Linux, provided the necessary ifdefs remain small.
|
|
// Otherwise, they should be spun off into their own files.
|
|
#if defined(__unix__) && !defined(__ANDROID__) && !defined(__APPLE__)
|
|
|
|
# ifdef __FreeBSD__
|
|
# include <sys/sysctl.h>
|
|
# endif
|
|
|
|
# define OPENRCT2_MAX_COMMAND_LENGTH (2 * MAX_PATH)
|
|
|
|
# include <cstring>
|
|
# include <ctype.h>
|
|
# include <dlfcn.h>
|
|
# include <errno.h>
|
|
# ifndef NO_TTF
|
|
# include <fontconfig/fontconfig.h>
|
|
# endif // NO_TTF
|
|
# include "../config/Config.h"
|
|
# include "../localisation/Language.h"
|
|
# include "../localisation/StringIds.h"
|
|
# include "../util/Util.h"
|
|
# include "platform.h"
|
|
|
|
# include <fnmatch.h>
|
|
# include <locale.h>
|
|
# include <pwd.h>
|
|
|
|
uint16_t platform_get_locale_language()
|
|
{
|
|
const char* langString = setlocale(LC_MESSAGES, "");
|
|
if (langString != nullptr)
|
|
{
|
|
// The locale has the following form:
|
|
// language[_territory[.codeset]][@modifier]
|
|
// (see https://www.gnu.org/software/libc/manual/html_node/Locale-Names.html)
|
|
// longest on my system is 29 with codeset and modifier, so 32 for the pattern should be more than enough
|
|
char pattern[32];
|
|
// strip the codeset and modifier part
|
|
int32_t length = strlen(langString);
|
|
{
|
|
for (int32_t i = 0; i < length; ++i)
|
|
{
|
|
if (langString[i] == '.' || langString[i] == '@')
|
|
{
|
|
length = i;
|
|
break;
|
|
}
|
|
}
|
|
} // end strip
|
|
std::memcpy(pattern, langString, length); // copy all until first '.' or '@'
|
|
pattern[length] = '\0';
|
|
// find _ if present
|
|
const char* strip = strchr(pattern, '_');
|
|
if (strip != nullptr)
|
|
{
|
|
// could also use '-', but '?' is more flexible. Maybe LanguagesDescriptors will change.
|
|
// pattern is now "language?territory"
|
|
pattern[strip - pattern] = '?';
|
|
}
|
|
|
|
// Iterate through all available languages
|
|
for (int32_t i = 1; i < LANGUAGE_COUNT; ++i)
|
|
{
|
|
if (!fnmatch(pattern, LanguagesDescriptors[i].locale, 0))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
// special cases :(
|
|
if (!fnmatch(pattern, "en_CA", 0))
|
|
{
|
|
return LANGUAGE_ENGLISH_US;
|
|
}
|
|
if (!fnmatch(pattern, "zh_CN", 0))
|
|
{
|
|
return LANGUAGE_CHINESE_SIMPLIFIED;
|
|
}
|
|
if (!fnmatch(pattern, "zh_TW", 0))
|
|
{
|
|
return LANGUAGE_CHINESE_TRADITIONAL;
|
|
}
|
|
|
|
// no exact match found trying only language part
|
|
if (strip != nullptr)
|
|
{
|
|
pattern[strip - pattern] = '*';
|
|
pattern[strip - pattern + 1] = '\0'; // pattern is now "language*"
|
|
for (int32_t i = 1; i < LANGUAGE_COUNT; ++i)
|
|
{
|
|
if (!fnmatch(pattern, LanguagesDescriptors[i].locale, 0))
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return LANGUAGE_ENGLISH_UK;
|
|
}
|
|
|
|
CurrencyType platform_get_locale_currency()
|
|
{
|
|
char* langstring = setlocale(LC_MONETARY, "");
|
|
|
|
if (langstring == nullptr)
|
|
{
|
|
return platform_get_currency_value(NULL);
|
|
}
|
|
|
|
struct lconv* lc = localeconv();
|
|
|
|
return platform_get_currency_value(lc->int_curr_symbol);
|
|
}
|
|
|
|
MeasurementFormat platform_get_locale_measurement_format()
|
|
{
|
|
// LC_MEASUREMENT is GNU specific.
|
|
# ifdef LC_MEASUREMENT
|
|
const char* langstring = setlocale(LC_MEASUREMENT, "");
|
|
# else
|
|
const char* langstring = setlocale(LC_ALL, "");
|
|
# endif
|
|
|
|
if (langstring != nullptr)
|
|
{
|
|
// using https://en.wikipedia.org/wiki/Metrication#Chronology_and_status_of_conversion_by_country as reference
|
|
if (!fnmatch("*_US*", langstring, 0) || !fnmatch("*_MM*", langstring, 0) || !fnmatch("*_LR*", langstring, 0))
|
|
{
|
|
return MeasurementFormat::Imperial;
|
|
}
|
|
}
|
|
return MeasurementFormat::Metric;
|
|
}
|
|
|
|
bool platform_get_steam_path(utf8* outPath, size_t outSize)
|
|
{
|
|
const char* steamRoot = getenv("STEAMROOT");
|
|
if (steamRoot != nullptr)
|
|
{
|
|
safe_strcpy(outPath, steamRoot, outSize);
|
|
safe_strcat_path(outPath, "ubuntu12_32/steamapps/content", outSize);
|
|
return true;
|
|
}
|
|
|
|
char steamPath[1024] = { 0 };
|
|
const char* localSharePath = getenv("XDG_DATA_HOME");
|
|
if (localSharePath != nullptr)
|
|
{
|
|
safe_strcpy(steamPath, localSharePath, sizeof(steamPath));
|
|
safe_strcat_path(steamPath, "Steam/ubuntu12_32/steamapps/content", sizeof(steamPath));
|
|
if (platform_directory_exists(steamPath))
|
|
{
|
|
safe_strcpy(outPath, steamPath, outSize);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const char* homeDir = getpwuid(getuid())->pw_dir;
|
|
if (homeDir != nullptr)
|
|
{
|
|
safe_strcpy(steamPath, homeDir, sizeof(steamPath));
|
|
safe_strcat_path(steamPath, ".local/share/Steam/ubuntu12_32/steamapps/content", sizeof(steamPath));
|
|
if (platform_directory_exists(steamPath))
|
|
{
|
|
safe_strcpy(outPath, steamPath, outSize);
|
|
return true;
|
|
}
|
|
|
|
std::fill_n(steamPath, sizeof(steamPath), 0x00);
|
|
safe_strcpy(steamPath, homeDir, sizeof(steamPath));
|
|
safe_strcat_path(steamPath, ".steam/steam/ubuntu12_32/steamapps/content", sizeof(steamPath));
|
|
if (platform_directory_exists(steamPath))
|
|
{
|
|
safe_strcpy(outPath, steamPath, outSize);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
# ifndef NO_TTF
|
|
bool platform_get_font_path(TTFFontDescriptor* font, utf8* buffer, size_t size)
|
|
{
|
|
assert(buffer != nullptr);
|
|
assert(font != nullptr);
|
|
|
|
log_verbose("Looking for font %s with FontConfig.", font->font_name);
|
|
FcConfig* config = FcInitLoadConfigAndFonts();
|
|
if (!config)
|
|
{
|
|
log_error("Failed to initialize FontConfig library");
|
|
FcFini();
|
|
return false;
|
|
}
|
|
|
|
FcPattern* pat = FcNameParse(reinterpret_cast<const FcChar8*>(font->font_name));
|
|
|
|
FcConfigSubstitute(config, pat, FcMatchPattern);
|
|
FcDefaultSubstitute(pat);
|
|
|
|
bool found = false;
|
|
FcResult result = FcResultNoMatch;
|
|
FcPattern* match = FcFontMatch(config, pat, &result);
|
|
|
|
if (match)
|
|
{
|
|
bool is_substitute = false;
|
|
|
|
// FontConfig implicitly falls back to any default font it is configured to handle.
|
|
// In our implementation, this cannot account for supported character sets, leading
|
|
// to unrendered characters (tofu) when trying to render e.g. CJK characters using a
|
|
// Western (sans-)serif font. We therefore ignore substitutions FontConfig provides,
|
|
// and instead rely on exact matches on the fonts predefined for each font family.
|
|
FcChar8* matched_font_face = nullptr;
|
|
if (FcPatternGetString(match, FC_FULLNAME, 0, &matched_font_face) == FcResultMatch
|
|
&& strcmp(font->font_name, reinterpret_cast<const char*>(matched_font_face)) != 0)
|
|
{
|
|
log_verbose("FontConfig provided substitute font %s -- disregarding.", matched_font_face);
|
|
is_substitute = true;
|
|
}
|
|
|
|
FcChar8* filename = nullptr;
|
|
if (!is_substitute && FcPatternGetString(match, FC_FILE, 0, &filename) == FcResultMatch)
|
|
{
|
|
found = true;
|
|
safe_strcpy(buffer, reinterpret_cast<utf8*>(filename), size);
|
|
log_verbose("FontConfig provided font %s", filename);
|
|
}
|
|
|
|
FcPatternDestroy(match);
|
|
}
|
|
else
|
|
{
|
|
log_warning("Failed to find required font.");
|
|
}
|
|
|
|
FcPatternDestroy(pat);
|
|
FcConfigDestroy(config);
|
|
FcFini();
|
|
return found;
|
|
}
|
|
# endif // NO_TTF
|
|
|
|
#endif
|