mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-25 21:01:00 +01: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
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user