/* Helper to extract values from the CAMOU_CONFIG environment variable(s). Written by daijro. */ #pragma once #include "json.hpp" #include #include #include #include #include #include "mozilla/glue/Debug.h" #include #include #include #include #include #include #include #ifdef _WIN32 # include #endif namespace MaskConfig { // Function to get the value of an environment variable as a UTF-8 string. inline std::optional get_env_utf8(const std::string& name) { #ifdef _WIN32 std::wstring wName(name.begin(), name.end()); DWORD size = GetEnvironmentVariableW(wName.c_str(), nullptr, 0); if (size == 0) return std::nullopt; // Environment variable not found std::vector buffer(size); GetEnvironmentVariableW(wName.c_str(), buffer.data(), size); std::wstring wValue(buffer.data()); // Convert UTF-16 to UTF-8 std::wstring_convert> converter; return converter.to_bytes(wValue); #else const char* value = std::getenv(name.c_str()); if (!value) return std::nullopt; return std::string(value); #endif } inline const nlohmann::json& GetJson() { static std::once_flag initFlag; static nlohmann::json jsonConfig; std::call_once(initFlag, []() { std::string jsonString; int index = 1; while (true) { std::string envName = "CAMOU_CONFIG_" + std::to_string(index); auto partialConfig = get_env_utf8(envName); if (!partialConfig) break; jsonString += *partialConfig; index++; } if (jsonString.empty()) { // Check for the original CAMOU_CONFIG as fallback auto originalConfig = get_env_utf8("CAMOU_CONFIG"); if (originalConfig) jsonString = *originalConfig; } if (jsonString.empty()) { jsonConfig = nlohmann::json{}; return; } // Validate if (!nlohmann::json::accept(jsonString)) { printf_stderr("ERROR: Invalid JSON passed to CAMOU_CONFIG!\n"); jsonConfig = nlohmann::json{}; return; } jsonConfig = nlohmann::json::parse(jsonString); }); return jsonConfig; } inline bool HasKey(const std::string& key, const nlohmann::json& data) { return data.contains(key); } inline std::optional GetString(const std::string& key) { const auto& data = GetJson(); if (!HasKey(key, data)) return std::nullopt; return data[key].get(); } inline std::vector GetStringList(const std::string& key) { std::vector result; const auto& data = GetJson(); if (!HasKey(key, data)) return {}; for (const auto& item : data[key]) { result.push_back(item.get()); } return result; } inline std::vector GetStringListLower(const std::string& key) { std::vector result = GetStringList(key); for (auto& str : result) { std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { return std::tolower(c); }); } return result; } template inline std::optional GetUintImpl(const std::string& key) { const auto& data = GetJson(); if (!HasKey(key, data)) return std::nullopt; if (data[key].is_number_unsigned()) return data[key].get(); printf_stderr("ERROR: Value for key '%s' is not an unsigned integer\n", key.c_str()); return std::nullopt; } inline std::optional GetUint64(const std::string& key) { return GetUintImpl(key); } inline std::optional GetUint32(const std::string& key) { return GetUintImpl(key); } inline std::optional GetInt32(const std::string& key) { const auto& data = GetJson(); if (!HasKey(key, data)) return std::nullopt; if (data[key].is_number_integer()) return data[key].get(); printf_stderr("ERROR: Value for key '%s' is not an integer\n", key.c_str()); return std::nullopt; } inline std::optional GetDouble(const std::string& key) { const auto& data = GetJson(); if (!HasKey(key, data)) return std::nullopt; if (data[key].is_number_float()) return data[key].get(); if (data[key].is_number_unsigned() || data[key].is_number_integer()) return static_cast(data[key].get()); printf_stderr("ERROR: Value for key '%s' is not a double\n", key.c_str()); return std::nullopt; } inline std::optional GetBool(const std::string& key) { const auto& data = GetJson(); if (!HasKey(key, data)) return std::nullopt; if (data[key].is_boolean()) return data[key].get(); printf_stderr("ERROR: Value for key '%s' is not a boolean\n", key.c_str()); return std::nullopt; } inline bool CheckBool(const std::string& key) { return GetBool(key).value_or(false); } inline std::optional> GetRect( const std::string& left, const std::string& top, const std::string& width, const std::string& height) { std::array, 4> values = { GetUint32(left).value_or(0), GetUint32(top).value_or(0), GetUint32(width), GetUint32(height)}; if (!values[2].has_value() || !values[3].has_value()) { if (values[2].has_value() ^ values[3].has_value()) printf_stderr( "Both %s and %s must be provided. Using default behavior.\n", height.c_str(), width.c_str()); return std::nullopt; } std::array result; std::transform(values.begin(), values.end(), result.begin(), [](const auto& value) { return value.value(); }); return result; } inline std::optional> GetInt32Rect( const std::string& left, const std::string& top, const std::string& width, const std::string& height) { if (auto optValue = GetRect(left, top, width, height)) { std::array result; std::transform(optValue->begin(), optValue->end(), result.begin(), [](const auto& val) { return static_cast(val); }); return result; } return std::nullopt; } // Helpers for WebGL inline std::optional GetNested(const std::string& domain, std::string keyStr) { auto data = GetJson(); if (!data.contains(domain)) return std::nullopt; if (!data[domain].contains(keyStr)) return std::nullopt; return data[domain][keyStr]; } template inline std::optional GetAttribute(const std::string attrib, bool isWebGL2) { auto value = MaskConfig::GetNested( isWebGL2 ? "webGl2:contextAttributes" : "webGl:contextAttributes", attrib); if (!value) return std::nullopt; return value.value().get(); } inline std::optional< std::variant> GLParam(uint32_t pname, bool isWebGL2) { auto value = MaskConfig::GetNested(isWebGL2 ? "webGl2:parameters" : "webGl:parameters", std::to_string(pname)); if (!value) return std::nullopt; auto data = value.value(); if (data.is_null()) return std::nullptr_t(); if (data.is_number_integer()) return data.get(); if (data.is_boolean()) return data.get(); if (data.is_number_float()) return data.get(); if (data.is_string()) return data.get(); return std::nullopt; } template inline T MParamGL(uint32_t pname, T defaultValue, bool isWebGL2) { if (auto value = MaskConfig::GetNested( isWebGL2 ? "webGl2:parameters" : "webGl:parameters", std::to_string(pname)); value.has_value()) { return value.value().get(); } return defaultValue; } template inline std::vector MParamGLVector(uint32_t pname, std::vector defaultValue, bool isWebGL2) { if (auto value = MaskConfig::GetNested( isWebGL2 ? "webGl2:parameters" : "webGl:parameters", std::to_string(pname)); value.has_value()) { if (value.value().is_array()) { std::array result = value.value().get>(); return std::vector(result.begin(), result.end()); } } return defaultValue; } inline std::optional> MShaderData( uint32_t shaderType, uint32_t precisionType, bool isWebGL2) { std::string valueName = std::to_string(shaderType) + "," + std::to_string(precisionType); if (auto value = MaskConfig::GetNested(isWebGL2 ? "webGl2:shaderPrecisionFormats" : "webGl:shaderPrecisionFormats", valueName)) { // Convert {rangeMin: int, rangeMax: int, precision: int} to array auto data = value.value(); // Assert rangeMin, rangeMax, and precision are present if (!data.contains("rangeMin") || !data.contains("rangeMax") || !data.contains("precision")) { return std::nullopt; } return std::array{data["rangeMin"].get(), data["rangeMax"].get(), data["precision"].get()}; } return std::nullopt; } inline std::optional< std::vector>> MVoices() { auto data = GetJson(); if (!data.contains("voices") || !data["voices"].is_array()) { return std::nullopt; } std::vector> voices; for (const auto& voice : data["voices"]) { // Check if voice has all required fields if (!voice.contains("lang") || !voice.contains("name") || !voice.contains("voiceUri") || !voice.contains("isDefault") || !voice.contains("isLocalService")) { continue; } voices.emplace_back( voice["lang"].get(), voice["name"].get(), voice["voiceUri"].get(), voice["isDefault"].get(), voice["isLocalService"].get()); } return voices; } } // namespace MaskConfig