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:
komaruworld committed 2026-08-31 21:42:35 +03:00
1 parent d5f9c91358
commit 035c7fe1d4
19 files changed
+244 -31

No files matched your search

+30 -7
View File
@@ -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
View File
@@ -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';
+62
View File
@@ -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) {
+10 -3
View File
@@ -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();
+2 -1
View File
@@ -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 =
+3 -1
View File
@@ -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
+3 -2
View File
@@ -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
View File
@@ -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();
+4
View File
@@ -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
+9
View File
@@ -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