mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-17 17:00:55 +02:00
Import theme from roblox and fix x11
vkAcquireNextImageKHR(): A swapchain no longer matches the surface properties exactly, but can still be used to present to the surface successfully. (VK_SUBOPTIMAL_KHR) (1000001003)
This commit is contained in:
1 parent
d5f9c91358
commit
035c7fe1d4
19 files changed
+244
-31
No files matched your search
@@ -32,7 +32,9 @@ runtime:
|
||||
# roblox_library: /absolute/path/to/libroblox.so
|
||||
|
||||
appearance:
|
||||
theme: system
|
||||
# String (default: roblox): use Roblox's saved account theme. Supported
|
||||
# overrides: dark, light, or system (follow the desktop color scheme).
|
||||
theme: roblox
|
||||
|
||||
graphics:
|
||||
# String (default: direct-vulkan): graphics backend. Use opengl on hardware
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
@@ -38,6 +39,20 @@ PlatformCacheMigrationResult MigratePlatformProfileCaches(
|
||||
const Environment& environment, const RuntimePaths& paths,
|
||||
std::string_view desired_revision);
|
||||
|
||||
struct RobloxThemeCacheResult {
|
||||
std::optional<bool> dark_theme;
|
||||
std::string error;
|
||||
|
||||
explicit operator bool() const { return error.empty(); }
|
||||
};
|
||||
|
||||
// Reads the current account's Roblox theme without changing appStorage. A
|
||||
// missing value leaves dark_theme empty so the caller can use Roblox's own
|
||||
// startup default.
|
||||
RobloxThemeCacheResult ReadRobloxThemeCache(
|
||||
const std::filesystem::path& app_storage_file,
|
||||
std::int64_t authenticated_user_id);
|
||||
|
||||
// Synchronizes only Roblox's local theme keys with the resolved host theme.
|
||||
bool ApplyRobloxThemeCacheOverride(
|
||||
const std::filesystem::path& app_storage_file,
|
||||
|
||||
@@ -20,14 +20,15 @@ struct DesktopAppPolicyResult {
|
||||
explicit operator bool() const { return error.empty(); }
|
||||
};
|
||||
|
||||
// Normalizes only Roblox UniversalApp layout coordinates for a PC profile.
|
||||
// Account eligibility and feature-entitlement fields remain server-owned.
|
||||
// Normalizes Roblox UniversalApp layout coordinates for a PC profile and
|
||||
// applies a fixed theme only when configured. Account eligibility and
|
||||
// feature-entitlement fields remain server-owned.
|
||||
// The guest and authenticated cache keys are both populated so user-switch
|
||||
// startup cannot fall back to the Android Unknown/mobile layout.
|
||||
DesktopAppPolicyResult ApplyDesktopAppPolicy(
|
||||
const std::filesystem::path& app_storage_file,
|
||||
const std::filesystem::path& default_policy_file,
|
||||
std::int64_t authenticated_user_id);
|
||||
std::int64_t authenticated_user_id, std::string_view theme_mode);
|
||||
|
||||
// Routes the normalized policy through Roblox's supported runtime override so
|
||||
// a later GUAC response cannot replace the in-memory desktop layout with the
|
||||
|
||||
@@ -87,8 +87,8 @@ class RuntimeConfig {
|
||||
const WindowConfig& window() const { return window_; }
|
||||
const std::string& theme_mode() const { return theme_mode_; }
|
||||
bool theme_mode_valid() const {
|
||||
return theme_mode_ == "system" || theme_mode_ == "light" ||
|
||||
theme_mode_ == "dark";
|
||||
return theme_mode_ == "roblox" || theme_mode_ == "system" ||
|
||||
theme_mode_ == "light" || theme_mode_ == "dark";
|
||||
}
|
||||
const InputCapabilityConfig& input_capabilities() const {
|
||||
return input_capabilities_;
|
||||
@@ -139,7 +139,7 @@ class RuntimeConfig {
|
||||
GraphicsBackend graphics_backend_ = GraphicsBackend::kVulkan;
|
||||
std::string graphics_backend_name_ = "direct-vulkan";
|
||||
WindowConfig window_;
|
||||
std::string theme_mode_ = "system";
|
||||
std::string theme_mode_ = "roblox";
|
||||
InputCapabilityConfig input_capabilities_;
|
||||
DeviceProfile device_profile_ = *FindDeviceProfile(kDefaultDeviceProfileName);
|
||||
bool device_profile_valid_ = true;
|
||||
|
||||
@@ -4430,8 +4430,9 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options,
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (!runtime_config.theme_mode_valid()) {
|
||||
std::cerr << "[FATAL] Invalid MOCKTAIL_THEME; expected system, light, or "
|
||||
"dark\n";
|
||||
std::cerr
|
||||
<< "[FATAL] Invalid MOCKTAIL_THEME; expected roblox, system, light, "
|
||||
"or dark\n";
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
setenv("MOCKTAIL_TOUCH_ENABLED_INTERNAL",
|
||||
@@ -4713,13 +4714,35 @@ int mocktail::legacy::Run(const runtime::CommandLineOptions& options,
|
||||
const SDL_SystemTheme system_theme =
|
||||
window_initialised ? SDL_GetSystemTheme() : SDL_SYSTEM_THEME_UNKNOWN;
|
||||
const bool system_dark_theme = system_theme == SDL_SYSTEM_THEME_DARK;
|
||||
const bool dark_theme =
|
||||
runtime_config.theme_mode() == "dark" ||
|
||||
(runtime_config.theme_mode() == "system" && system_dark_theme);
|
||||
setenv("MOCKTAIL_RESOLVED_THEME_INTERNAL", dark_theme ? "Dark" : "Light", 1);
|
||||
const char* app_storage_file =
|
||||
std::getenv("MOCKTAIL_APP_STORAGE_FILE_INTERNAL");
|
||||
if (app_storage_file != nullptr) {
|
||||
bool dark_theme = runtime_config.theme_mode() != "light";
|
||||
bool roblox_theme_found = false;
|
||||
if (runtime_config.theme_mode() == "system") {
|
||||
dark_theme = system_dark_theme;
|
||||
} else if (runtime_config.theme_mode() == "roblox" &&
|
||||
app_storage_file != nullptr) {
|
||||
const mocktail::runtime::RobloxThemeCacheResult saved_theme =
|
||||
mocktail::runtime::ReadRobloxThemeCache(
|
||||
app_storage_file, dependencies.account_identity().user_id);
|
||||
if (!saved_theme) {
|
||||
std::cerr << "[FATAL] Cannot read Roblox theme cache: "
|
||||
<< saved_theme.error << '\n';
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
if (saved_theme.dark_theme.has_value()) {
|
||||
dark_theme = *saved_theme.dark_theme;
|
||||
roblox_theme_found = true;
|
||||
}
|
||||
}
|
||||
setenv("MOCKTAIL_RESOLVED_THEME_INTERNAL", dark_theme ? "Dark" : "Light", 1);
|
||||
if (runtime_config.theme_mode() == "roblox") {
|
||||
std::cout << " [theme] "
|
||||
<< (roblox_theme_found ? "Roblox saved theme="
|
||||
: "Roblox theme missing; default=")
|
||||
<< (dark_theme ? "dark" : "light") << '\n'
|
||||
<< std::flush;
|
||||
} else if (app_storage_file != nullptr) {
|
||||
std::string theme_error;
|
||||
if (!mocktail::runtime::ApplyRobloxThemeCacheOverride(
|
||||
app_storage_file, dependencies.account_identity().user_id,
|
||||
|
||||
+2
-1
@@ -949,7 +949,8 @@ int main(int argc, char* argv[]) {
|
||||
: -1;
|
||||
const mocktail::runtime::DesktopAppPolicyResult desktop_policy =
|
||||
mocktail::runtime::ApplyDesktopAppPolicy(
|
||||
app_storage_file, default_app_policy, app_policy_user_id);
|
||||
app_storage_file, default_app_policy, app_policy_user_id,
|
||||
runtime_config.config.theme_mode());
|
||||
if (!desktop_policy) {
|
||||
std::cerr << "[FATAL] Desktop Roblox app-policy failed: "
|
||||
<< desktop_policy.error << '\n';
|
||||
|
||||
@@ -341,6 +341,68 @@ PlatformCacheMigrationResult MigratePlatformProfileCaches(
|
||||
return result;
|
||||
}
|
||||
|
||||
RobloxThemeCacheResult ReadRobloxThemeCache(
|
||||
const std::filesystem::path& app_storage_file,
|
||||
std::int64_t authenticated_user_id) {
|
||||
RobloxThemeCacheResult result;
|
||||
if (!app_storage_file.is_absolute() || authenticated_user_id < -1) {
|
||||
result.error = "Roblox theme cache path or user ID is invalid";
|
||||
return result;
|
||||
}
|
||||
|
||||
const JsonReadResult storage = ReadJson(app_storage_file);
|
||||
if (storage.status == ReadStatus::kInvalid) {
|
||||
result.error = storage.error;
|
||||
return result;
|
||||
}
|
||||
if (storage.status == ReadStatus::kMissing) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const auto read_theme = [&result](const nlohmann::json& value,
|
||||
std::string_view name) {
|
||||
if (!value.is_string()) {
|
||||
result.error = std::string(name) + " is not a string";
|
||||
return;
|
||||
}
|
||||
const std::string& theme = value.get_ref<const std::string&>();
|
||||
if (theme != "dark" && theme != "light") {
|
||||
result.error = std::string(name) + " must be dark or light";
|
||||
return;
|
||||
}
|
||||
result.dark_theme = theme == "dark";
|
||||
};
|
||||
|
||||
if (authenticated_user_id >= 0) {
|
||||
const auto encoded_device_themes = storage.value.find("DeviceLevelTheme");
|
||||
if (encoded_device_themes != storage.value.end()) {
|
||||
if (!encoded_device_themes->is_string()) {
|
||||
result.error = "Roblox DeviceLevelTheme is not an encoded object";
|
||||
return result;
|
||||
}
|
||||
const nlohmann::json device_themes = nlohmann::json::parse(
|
||||
encoded_device_themes->get_ref<const std::string&>(), nullptr, false,
|
||||
true);
|
||||
if (device_themes.is_discarded() || !device_themes.is_object()) {
|
||||
result.error = "Roblox DeviceLevelTheme encoding is invalid";
|
||||
return result;
|
||||
}
|
||||
const auto account_theme =
|
||||
device_themes.find(std::to_string(authenticated_user_id));
|
||||
if (account_theme != device_themes.end()) {
|
||||
read_theme(*account_theme, "Roblox account theme");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const auto authenticated_theme = storage.value.find("AuthenticatedTheme");
|
||||
if (authenticated_theme != storage.value.end()) {
|
||||
read_theme(*authenticated_theme, "Roblox authenticated theme");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ApplyRobloxThemeCacheOverride(
|
||||
const std::filesystem::path& app_storage_file,
|
||||
std::int64_t authenticated_user_id, bool dark_theme, std::string* error) {
|
||||
|
||||
@@ -206,13 +206,18 @@ bool DecodePolicy(const nlohmann::json& encoded, nlohmann::json* policy) {
|
||||
DesktopAppPolicyResult ApplyDesktopAppPolicy(
|
||||
const std::filesystem::path& app_storage_file,
|
||||
const std::filesystem::path& default_policy_file,
|
||||
std::int64_t authenticated_user_id) {
|
||||
std::int64_t authenticated_user_id, std::string_view theme_mode) {
|
||||
DesktopAppPolicyResult result;
|
||||
if (!app_storage_file.is_absolute() || !default_policy_file.is_absolute() ||
|
||||
authenticated_user_id < -1) {
|
||||
result.error = "desktop app-policy paths or user ID are invalid";
|
||||
authenticated_user_id < -1 ||
|
||||
(theme_mode != "roblox" && theme_mode != "system" &&
|
||||
theme_mode != "light" && theme_mode != "dark")) {
|
||||
result.error = "desktop app-policy paths, user ID, or theme are invalid";
|
||||
return result;
|
||||
}
|
||||
const std::string forced_theme =
|
||||
theme_mode == "light" || theme_mode == "dark" ? std::string(theme_mode)
|
||||
: std::string();
|
||||
|
||||
JsonReadResult storage =
|
||||
ReadJsonObject(app_storage_file, "Roblox appStorage");
|
||||
@@ -259,6 +264,7 @@ DesktopAppPolicyResult ApplyDesktopAppPolicy(
|
||||
return result;
|
||||
}
|
||||
NormalizeDesktopLayout(&policy);
|
||||
policy["ForceTheme"] = forced_theme;
|
||||
entry.value() = policy.dump();
|
||||
if (!template_policy.is_object()) {
|
||||
template_policy = policy;
|
||||
@@ -289,6 +295,7 @@ DesktopAppPolicyResult ApplyDesktopAppPolicy(
|
||||
}
|
||||
template_policy = fallback.value;
|
||||
NormalizeDesktopLayout(&template_policy);
|
||||
template_policy["ForceTheme"] = forced_theme;
|
||||
}
|
||||
|
||||
const std::string encoded_policy = template_policy.dump();
|
||||
|
||||
@@ -185,7 +185,8 @@ RuntimeConfig RuntimeConfig::FromEnvironment(const Environment& environment) {
|
||||
config.window_.high_dpi = ReadBoolean(environment, "MOCKTAIL_WIN_HIGH_DPI",
|
||||
config.window_.high_dpi,
|
||||
&config.window_.high_dpi_valid);
|
||||
config.theme_mode_ = environment.GetOr("MOCKTAIL_THEME", "system");
|
||||
config.theme_mode_ =
|
||||
environment.GetOr("MOCKTAIL_THEME", config.theme_mode_);
|
||||
const std::optional<std::string> configured_device =
|
||||
environment.Get("MOCKTAIL_DEVICE_PROFILE");
|
||||
const bool has_explicit_device =
|
||||
|
||||
@@ -50,7 +50,9 @@ runtime:
|
||||
# roblox_library: /absolute/path/to/libroblox.so
|
||||
|
||||
appearance:
|
||||
theme: system
|
||||
# String (default: roblox): use Roblox's saved account theme. Supported
|
||||
# overrides: dark, light, or system (follow the desktop color scheme).
|
||||
theme: roblox
|
||||
|
||||
graphics:
|
||||
# String (default: direct-vulkan): graphics backend. Use opengl on hardware
|
||||
|
||||
@@ -301,8 +301,9 @@ bool ValidateAndMap(const ValueMap& yaml, ValueMap* environment,
|
||||
(*environment)["ROBLOX_LIB_PATH"] = *library;
|
||||
}
|
||||
if (const auto theme = value("appearance.theme"); theme.has_value()) {
|
||||
if (*theme != "system" && *theme != "light" && *theme != "dark") {
|
||||
*error = "appearance.theme must be system, light, or dark";
|
||||
if (*theme != "roblox" && *theme != "system" && *theme != "light" &&
|
||||
*theme != "dark") {
|
||||
*error = "appearance.theme must be roblox, system, light, or dark";
|
||||
return false;
|
||||
}
|
||||
(*environment)["MOCKTAIL_THEME"] = *theme;
|
||||
|
||||
+12
-3
@@ -1052,7 +1052,11 @@ bool Init(int width, int height, const char* title) {
|
||||
if (g_state.native_window == nullptr) {
|
||||
g_state.native_window = g_state.sdl_window;
|
||||
}
|
||||
// Wayland reports undefined currentExtent until the window is mapped.
|
||||
// Wayland reports undefined currentExtent until the window is mapped and
|
||||
// receives its initial configure. X11 has no equivalent requirement, and
|
||||
// SDL_SyncWindow can time out there while waiting for an unrelated WM
|
||||
// state request (notably restored maximization) even though the native
|
||||
// window is already valid for Vulkan WSI.
|
||||
if (!SDL_ShowWindow(g_state.sdl_window)) {
|
||||
fprintf(stderr, " [window] SDL_ShowWindow failed: %s\n", SDL_GetError());
|
||||
SDL_DestroyWindow(g_state.sdl_window);
|
||||
@@ -1062,8 +1066,13 @@ bool Init(int width, int height, const char* title) {
|
||||
g_state.initialised = false;
|
||||
return false;
|
||||
}
|
||||
if (!SDL_SyncWindow(g_state.sdl_window)) {
|
||||
fprintf(stderr, " [window] SDL_SyncWindow failed: %s\n", SDL_GetError());
|
||||
const char* active_video_driver = SDL_GetCurrentVideoDriver();
|
||||
if (DirectVulkanWindowRequiresInitialSync(
|
||||
active_video_driver != nullptr ? active_video_driver : "") &&
|
||||
!SDL_SyncWindow(g_state.sdl_window)) {
|
||||
fprintf(stderr,
|
||||
" [window] SDL_SyncWindow timed out waiting for the initial "
|
||||
"Wayland configure\n");
|
||||
SDL_DestroyWindow(g_state.sdl_window);
|
||||
g_state.sdl_window = nullptr;
|
||||
SDL_Quit();
|
||||
|
||||
@@ -11,5 +11,9 @@ SDL_WindowFlags ApplyHighPixelDensityWindowFlag(SDL_WindowFlags flags,
|
||||
return flags & ~SDL_WINDOW_HIGH_PIXEL_DENSITY;
|
||||
}
|
||||
|
||||
bool DirectVulkanWindowRequiresInitialSync(std::string_view video_driver) {
|
||||
return video_driver == "wayland";
|
||||
}
|
||||
|
||||
} // namespace window
|
||||
} // namespace mocktail
|
||||
@@ -3,12 +3,21 @@
|
||||
|
||||
#include <SDL3/SDL_video.h>
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace mocktail {
|
||||
namespace window {
|
||||
|
||||
SDL_WindowFlags ApplyHighPixelDensityWindowFlag(SDL_WindowFlags flags,
|
||||
bool high_dpi);
|
||||
|
||||
// Native Wayland surfaces must receive their initial configure before Vulkan
|
||||
// can reliably query the surface extent. X11/XWayland windows do not have
|
||||
// that requirement, and waiting for an asynchronous window-manager state
|
||||
// change (for example, restored maximization) can time out even though the
|
||||
// X11 window is already usable.
|
||||
bool DirectVulkanWindowRequiresInitialSync(std::string_view video_driver);
|
||||
|
||||
} // namespace window
|
||||
} // namespace mocktail
|
||||
|
||||
|
||||
@@ -301,6 +301,45 @@ TEST(PlatformCacheMigrationTest,
|
||||
EXPECT_EQ(storage["UnrelatedPreference"], "keep");
|
||||
}
|
||||
|
||||
TEST(PlatformCacheMigrationTest,
|
||||
ReadsAccountThemeWithoutChangingRobloxStorage) {
|
||||
TemporaryDirectory temporary;
|
||||
FixturePaths fixture = PathsFor(temporary);
|
||||
const nlohmann::json device_themes = {
|
||||
{"42", "light"},
|
||||
{"99", "dark"},
|
||||
};
|
||||
ASSERT_TRUE(WriteJson(fixture.app_storage,
|
||||
{{"AuthenticatedTheme", "dark"},
|
||||
{"DeviceLevelTheme", device_themes.dump()},
|
||||
{"UnrelatedPreference", "keep"}}));
|
||||
const std::string original = ReadFile(fixture.app_storage);
|
||||
|
||||
const RobloxThemeCacheResult account =
|
||||
ReadRobloxThemeCache(fixture.app_storage, 42);
|
||||
ASSERT_TRUE(account) << account.error;
|
||||
ASSERT_TRUE(account.dark_theme.has_value());
|
||||
EXPECT_FALSE(*account.dark_theme);
|
||||
|
||||
const RobloxThemeCacheResult fallback =
|
||||
ReadRobloxThemeCache(fixture.app_storage, 7);
|
||||
ASSERT_TRUE(fallback) << fallback.error;
|
||||
ASSERT_TRUE(fallback.dark_theme.has_value());
|
||||
EXPECT_TRUE(*fallback.dark_theme);
|
||||
EXPECT_EQ(ReadFile(fixture.app_storage), original);
|
||||
}
|
||||
|
||||
TEST(PlatformCacheMigrationTest, MissingRobloxThemeUsesCallerDefault) {
|
||||
TemporaryDirectory temporary;
|
||||
FixturePaths fixture = PathsFor(temporary);
|
||||
|
||||
const RobloxThemeCacheResult result =
|
||||
ReadRobloxThemeCache(fixture.app_storage, -1);
|
||||
ASSERT_TRUE(result) << result.error;
|
||||
EXPECT_FALSE(result.dark_theme.has_value());
|
||||
EXPECT_FALSE(std::filesystem::exists(fixture.app_storage));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace runtime
|
||||
} // namespace mocktail
|
||||
@@ -98,7 +98,7 @@ TEST(RobloxDesktopAppPolicyTest,
|
||||
ASSERT_TRUE(WriteJson(default_path, {{"PlatformGroup", "Unknown"}}));
|
||||
|
||||
const DesktopAppPolicyResult result =
|
||||
ApplyDesktopAppPolicy(storage_path, default_path, 42);
|
||||
ApplyDesktopAppPolicy(storage_path, default_path, 42, "dark");
|
||||
ASSERT_TRUE(result) << result.error;
|
||||
EXPECT_TRUE(result.updated);
|
||||
EXPECT_EQ(result.normalized_policy_count, 1U);
|
||||
@@ -115,10 +115,11 @@ TEST(RobloxDesktopAppPolicyTest,
|
||||
ExpectDesktopLayout(user_policy);
|
||||
EXPECT_EQ(user_policy["EligibleForVideoCapture"], false);
|
||||
EXPECT_EQ(user_policy["AccountOwnedMarker"], "preserve");
|
||||
EXPECT_EQ(user_policy["ForceTheme"], "dark");
|
||||
ExpectDesktopLayout(DecodePolicy(updated, "GUAC:-1:app-policy"));
|
||||
|
||||
const DesktopAppPolicyResult repeated =
|
||||
ApplyDesktopAppPolicy(storage_path, default_path, 42);
|
||||
ApplyDesktopAppPolicy(storage_path, default_path, 42, "dark");
|
||||
ASSERT_TRUE(repeated) << repeated.error;
|
||||
EXPECT_FALSE(repeated.updated);
|
||||
EXPECT_EQ(repeated.normalized_policy_count, 2U);
|
||||
@@ -134,7 +135,7 @@ TEST(RobloxDesktopAppPolicyTest, SeedsMissingStorageFromPayloadDefault) {
|
||||
{"PayloadDefaultMarker", 7}}));
|
||||
|
||||
const DesktopAppPolicyResult result =
|
||||
ApplyDesktopAppPolicy(storage_path, default_path, -1);
|
||||
ApplyDesktopAppPolicy(storage_path, default_path, -1, "dark");
|
||||
ASSERT_TRUE(result) << result.error;
|
||||
EXPECT_TRUE(result.app_storage_created);
|
||||
EXPECT_TRUE(result.updated);
|
||||
@@ -145,11 +146,26 @@ TEST(RobloxDesktopAppPolicyTest, SeedsMissingStorageFromPayloadDefault) {
|
||||
const nlohmann::json policy =
|
||||
DecodePolicy(configurations, "GUAC:-1:app-policy");
|
||||
ExpectDesktopLayout(policy);
|
||||
EXPECT_EQ(policy["ForceTheme"], "dark");
|
||||
EXPECT_EQ(policy["PayloadDefaultMarker"], 7);
|
||||
EXPECT_EQ(nlohmann::json::parse(result.policy_json)["PayloadDefaultMarker"],
|
||||
7);
|
||||
}
|
||||
|
||||
TEST(RobloxDesktopAppPolicyTest, RobloxThemeDoesNotForceCachedPolicy) {
|
||||
TemporaryDirectory temporary;
|
||||
const std::filesystem::path storage_path = temporary.path() / "storage.json";
|
||||
const std::filesystem::path default_path = temporary.path() / "default.json";
|
||||
ASSERT_TRUE(WriteJson(default_path,
|
||||
{{"PlatformGroup", "Unknown"},
|
||||
{"ForceTheme", "dark"}}));
|
||||
|
||||
const DesktopAppPolicyResult result =
|
||||
ApplyDesktopAppPolicy(storage_path, default_path, 42, "roblox");
|
||||
ASSERT_TRUE(result) << result.error;
|
||||
EXPECT_EQ(nlohmann::json::parse(result.policy_json)["ForceTheme"], "");
|
||||
}
|
||||
|
||||
TEST(RobloxDesktopAppPolicyTest,
|
||||
SelectsAuthenticatedEntitlementsAndSynchronizesCacheKeys) {
|
||||
TemporaryDirectory temporary;
|
||||
@@ -169,11 +185,12 @@ TEST(RobloxDesktopAppPolicyTest,
|
||||
ASSERT_TRUE(WriteJson(default_path, {{"PlatformGroup", "Unknown"}}));
|
||||
|
||||
const DesktopAppPolicyResult result =
|
||||
ApplyDesktopAppPolicy(storage_path, default_path, 42);
|
||||
ApplyDesktopAppPolicy(storage_path, default_path, 42, "system");
|
||||
ASSERT_TRUE(result) << result.error;
|
||||
const nlohmann::json selected = nlohmann::json::parse(result.policy_json);
|
||||
ExpectDesktopLayout(selected);
|
||||
EXPECT_EQ(selected["Entitlement"], "authenticated");
|
||||
EXPECT_EQ(selected["ForceTheme"], "");
|
||||
|
||||
const nlohmann::json updated = DecodeConfigurations(ReadJson(storage_path));
|
||||
EXPECT_EQ(DecodePolicy(updated, "GUAC:-1:app-policy"), selected);
|
||||
@@ -210,14 +227,16 @@ TEST(RobloxDesktopAppPolicyTest, RejectsMalformedOrSymlinkedStorage) {
|
||||
std::ofstream output(malformed);
|
||||
output << "not-json";
|
||||
}
|
||||
EXPECT_FALSE(ApplyDesktopAppPolicy(malformed, default_path, 42));
|
||||
EXPECT_FALSE(ApplyDesktopAppPolicy(malformed, default_path, 42, "dark"));
|
||||
|
||||
const std::filesystem::path outside = temporary.path() / "outside.json";
|
||||
const std::filesystem::path linked = temporary.path() / "linked.json";
|
||||
ASSERT_TRUE(WriteJson(outside, {{"Preserve", true}}));
|
||||
ASSERT_EQ(symlink(outside.c_str(), linked.c_str()), 0);
|
||||
EXPECT_FALSE(ApplyDesktopAppPolicy(linked, default_path, 42));
|
||||
EXPECT_FALSE(ApplyDesktopAppPolicy(linked, default_path, 42, "dark"));
|
||||
EXPECT_EQ(ReadJson(outside)["Preserve"], true);
|
||||
|
||||
EXPECT_FALSE(ApplyDesktopAppPolicy(outside, default_path, 42, "invalid"));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -100,6 +100,7 @@ TEST(RuntimeConfigBootstrapTest, CreatesCompletePrivateFirstRunFile) {
|
||||
ASSERT_TRUE(loaded) << loaded.error;
|
||||
EXPECT_TRUE(loaded.file_loaded);
|
||||
EXPECT_EQ(loaded.config.graphics_backend(), GraphicsBackend::kVulkan);
|
||||
EXPECT_EQ(loaded.config.theme_mode(), "roblox");
|
||||
EXPECT_EQ(loaded.config.frame_rate().mode, FrameRateLimitMode::kUnmanaged);
|
||||
EXPECT_EQ(loaded.config.vsync_mode(), "auto");
|
||||
EXPECT_FALSE(loaded.config.performance().multithreaded_rendering);
|
||||
@@ -130,6 +131,9 @@ TEST(RuntimeConfigBootstrapTest,
|
||||
"profile.\ndevice: pc-windows-11",
|
||||
"# Boolean (default: false): start without a visible SDL "
|
||||
"window.\n headless: false",
|
||||
"# String (default: roblox): use Roblox's saved account theme. "
|
||||
"Supported\n # overrides: dark, light, or system (follow the "
|
||||
"desktop color scheme).\n theme: roblox",
|
||||
"# Recommended values: direct-vulkan, opengl, system, "
|
||||
"angle-vulkan.\n backend: direct-vulkan",
|
||||
"# DFIntTaskSchedulerTargetFps without a whitelist.\n "
|
||||
@@ -257,6 +261,7 @@ TEST(RuntimeConfigFileTest, UsesDefaultsWhenFileDoesNotExist) {
|
||||
ASSERT_TRUE(loaded) << loaded.error;
|
||||
EXPECT_FALSE(loaded.file_loaded);
|
||||
EXPECT_EQ(loaded.config.graphics_backend(), GraphicsBackend::kVulkan);
|
||||
EXPECT_EQ(loaded.config.theme_mode(), "roblox");
|
||||
EXPECT_EQ(loaded.config.roblox_library_path(), "rbx_bin/libroblox.so");
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ TEST(RuntimeConfigTest, UsesSupportedDefaults) {
|
||||
EXPECT_EQ(config.window().title, "Roblox");
|
||||
EXPECT_FALSE(config.window().high_dpi);
|
||||
EXPECT_TRUE(config.window().high_dpi_valid);
|
||||
EXPECT_EQ(config.theme_mode(), "roblox");
|
||||
EXPECT_FALSE(config.input_capabilities().touch_enabled);
|
||||
EXPECT_TRUE(config.input_capabilities().mouse_enabled);
|
||||
EXPECT_TRUE(config.input_capabilities().keyboard_enabled);
|
||||
@@ -97,6 +98,7 @@ TEST(RuntimeConfigTest, ReadsTypedRuntimeValues) {
|
||||
{"MOCKTAIL_WIN_HEIGHT", "1080"},
|
||||
{"MOCKTAIL_WIN_TITLE", "Mocktail Test"},
|
||||
{"MOCKTAIL_WIN_HIGH_DPI", "true"},
|
||||
{"MOCKTAIL_THEME", "system"},
|
||||
{"MOCKTAIL_TOUCH_MODE", "on"},
|
||||
{"MOCKTAIL_DESKTOP_PLAYABILITY", "0"},
|
||||
{"MOCKTAIL_FRAME_RATE_LIMIT", "144"},
|
||||
@@ -127,6 +129,7 @@ TEST(RuntimeConfigTest, ReadsTypedRuntimeValues) {
|
||||
EXPECT_EQ(config.window().title, "Mocktail Test");
|
||||
EXPECT_TRUE(config.window().high_dpi);
|
||||
EXPECT_TRUE(config.window().high_dpi_valid);
|
||||
EXPECT_EQ(config.theme_mode(), "system");
|
||||
EXPECT_TRUE(config.input_capabilities().touch_enabled);
|
||||
EXPECT_FALSE(config.desktop_playability());
|
||||
EXPECT_FALSE(config.roblox_http_user_agent().has_value());
|
||||
|
||||
@@ -32,6 +32,16 @@ TEST(WindowCreationPolicyTest, EnablesHighDensityForEveryWindowBackend) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST(WindowCreationPolicyTest, SynchronizesInitialDirectVulkanWaylandMap) {
|
||||
EXPECT_TRUE(DirectVulkanWindowRequiresInitialSync("wayland"));
|
||||
}
|
||||
|
||||
TEST(WindowCreationPolicyTest, DoesNotSynchronizeInitialX11Map) {
|
||||
EXPECT_FALSE(DirectVulkanWindowRequiresInitialSync("x11"));
|
||||
EXPECT_FALSE(DirectVulkanWindowRequiresInitialSync("dummy"));
|
||||
EXPECT_FALSE(DirectVulkanWindowRequiresInitialSync(""));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace window
|
||||
} // namespace mocktail
|
||||
Reference in new issue
Block a user