From 284f8534ab6e4e2166e95cb9c2e27f7d6782da7c Mon Sep 17 00:00:00 2001 From: WerWolv Date: Tue, 28 Nov 2023 00:19:42 +0100 Subject: [PATCH] refactor: Move the builtin plugin specific init tasks to the plugin --- .../include/hex/api/event_manager.hpp | 2 + lib/libimhex/include/hex/api/imhex_api.hpp | 7 + lib/libimhex/source/api/imhex_api.cpp | 4 + main/gui/include/init/splash_window.hpp | 24 +- main/gui/include/init/tasks.hpp | 10 +- main/gui/source/init/splash_window.cpp | 130 ++++---- main/gui/source/init/tasks.cpp | 307 ------------------ plugins/builtin/CMakeLists.txt | 1 + plugins/builtin/source/content/init_tasks.cpp | 299 +++++++++++++++++ plugins/builtin/source/plugin_builtin.cpp | 3 + 10 files changed, 411 insertions(+), 376 deletions(-) create mode 100644 plugins/builtin/source/content/init_tasks.cpp diff --git a/lib/libimhex/include/hex/api/event_manager.hpp b/lib/libimhex/include/hex/api/event_manager.hpp index efdc60c0b..0b87448d2 100644 --- a/lib/libimhex/include/hex/api/event_manager.hpp +++ b/lib/libimhex/include/hex/api/event_manager.hpp @@ -256,6 +256,8 @@ namespace hex { EVENT_DEF_NO_LOG(EventFrameEnd); EVENT_DEF_NO_LOG(EventSetTaskBarIconState, u32, u32, u32); + EVENT_DEF(RequestAddInitTask, std::string, bool, std::function); + EVENT_DEF(RequestAddExitTask, std::string, std::function); EVENT_DEF(RequestOpenWindow, std::string); EVENT_DEF(RequestSelectionChange, Region); EVENT_DEF(RequestAddBookmark, Region, std::string, std::string, color_t, u64*); diff --git a/lib/libimhex/include/hex/api/imhex_api.hpp b/lib/libimhex/include/hex/api/imhex_api.hpp index d0e31793a..3fed93d02 100644 --- a/lib/libimhex/include/hex/api/imhex_api.hpp +++ b/lib/libimhex/include/hex/api/imhex_api.hpp @@ -571,7 +571,14 @@ namespace hex { Nightly }; + /** + * @brief Triggers the update process + * @param updateType The update channel + * @return If the update process was successfully started + */ bool updateImHex(UpdateType updateType); + + void addStartupTask(const std::string &name, bool async, const std::function &function); } /** diff --git a/lib/libimhex/source/api/imhex_api.cpp b/lib/libimhex/source/api/imhex_api.cpp index 8d29b9092..52f0b29ae 100644 --- a/lib/libimhex/source/api/imhex_api.cpp +++ b/lib/libimhex/source/api/imhex_api.cpp @@ -713,6 +713,10 @@ namespace hex { return true; } + void addStartupTask(const std::string &name, bool async, const std::function &function) { + EventManager::post(name, async, function); + } + } namespace ImHexApi::Messaging { diff --git a/main/gui/include/init/splash_window.hpp b/main/gui/include/init/splash_window.hpp index a8ac11c23..6ef8456df 100644 --- a/main/gui/include/init/splash_window.hpp +++ b/main/gui/include/init/splash_window.hpp @@ -2,16 +2,27 @@ #include #include +#include +#include #include #include +#include +#include + struct GLFWwindow; namespace hex::init { using TaskFunction = std::function; + struct Task { + std::string name; + std::function callback; + bool async; + }; + enum FrameResult{ success, failure, wait }; struct Highlight { @@ -30,8 +41,12 @@ namespace hex::init { FrameResult fullFrame(); void startStartupTasks(); - void addStartupTask(const std::string &taskName, const TaskFunction &task, bool async) { - this->m_tasks.emplace_back(taskName, task, async); + void createTask(const Task &task); + + void addStartupTask(const std::string &taskName, const TaskFunction &function, bool async) { + std::scoped_lock lock(this->m_tasksMutex); + + this->m_tasks.emplace_back(taskName, function, async); } private: @@ -49,7 +64,10 @@ namespace hex::init { std::future processTasksAsync(); - std::vector> m_tasks; + std::atomic m_totalTaskCount, m_completedTaskCount; + std::atomic m_taskStatus; + std::vector m_tasks; + std::mutex m_tasksMutex; std::string m_gpuVendor; diff --git a/main/gui/include/init/tasks.hpp b/main/gui/include/init/tasks.hpp index 86ea4c2e2..934c9bfd7 100644 --- a/main/gui/include/init/tasks.hpp +++ b/main/gui/include/init/tasks.hpp @@ -1,16 +1,10 @@ #pragma once -#include -#include #include -namespace hex::init { +#include - struct Task { - std::string name; - std::function function; - bool async; - }; +namespace hex::init { /** * @brief Runs the exit tasks and print them to console diff --git a/main/gui/source/init/splash_window.cpp b/main/gui/source/init/splash_window.cpp index beb7da794..eaf4deca2 100644 --- a/main/gui/source/init/splash_window.cpp +++ b/main/gui/source/init/splash_window.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -50,6 +51,10 @@ namespace hex::init { this->initMyself(); ImHexApi::System::impl::setGPUVendor(reinterpret_cast(glGetString(GL_VENDOR))); + + EventManager::subscribe([this](const std::string& name, bool async, const TaskFunction &function){ + this->createTask(Task { name, function, async }); + }); } WindowSplash::~WindowSplash() { @@ -83,79 +88,88 @@ namespace hex::init { std::future WindowSplash::processTasksAsync() { return std::async(std::launch::async, [this] { - bool status = true; - std::atomic tasksCompleted = 0; - // Loop over all registered init tasks - for (const auto &[name, task, async] : this->m_tasks) { + for (const auto &task : this->m_tasks) { // Construct a new task callback - auto runTask = [&, task, name] { - try { - // Save an iterator to the current task name - decltype(this->m_currTaskNames)::iterator taskNameIter; - { - std::lock_guard guard(this->m_progressMutex); - this->m_currTaskNames.push_back(name + "..."); - taskNameIter = std::prev(this->m_currTaskNames.end()); - } - - // When the task finished, increment the progress bar - ON_SCOPE_EXIT { - tasksCompleted += 1; - this->m_progress = float(tasksCompleted) / this->m_tasks.size(); - }; - - // Execute the actual task and track the amount of time it took to run - auto startTime = std::chrono::high_resolution_clock::now(); - bool taskStatus = task(); - auto endTime = std::chrono::high_resolution_clock::now(); - - log::info("Task '{}' finished {} in {} ms", - name, - taskStatus ? "successfully" : "unsuccessfully", - std::chrono::duration_cast(endTime - startTime).count() - ); - - // Track the overall status of the tasks - status = status && taskStatus; - - // Erase the task name from the list of running tasks - { - std::lock_guard guard(this->m_progressMutex); - this->m_currTaskNames.erase(taskNameIter); - } - } catch (const std::exception &e) { - log::error("Init task '{}' threw an exception: {}", name, e.what()); - status = false; - } catch (...) { - log::error("Init task '{}' threw an unidentifiable exception", name); - status = false; - } - }; - - - // If the task can be run asynchronously, run it in a separate thread - // otherwise run it in this thread and wait for it to finish - if (async) { - std::thread([runTask = std::move(runTask)]{ runTask(); }).detach(); - } else { - runTask(); - } + this->createTask(task); } // Check every 100ms if all tasks have run - while (tasksCompleted < this->m_tasks.size()) { + while (true) { + { + std::scoped_lock lock(this->m_tasksMutex); + if (this->m_completedTaskCount >= this->m_totalTaskCount) + break; + } + std::this_thread::sleep_for(100ms); } // Small extra delay so the last progress step is visible std::this_thread::sleep_for(100ms); - return status; + return this->m_taskStatus.load(); }); } + void WindowSplash::createTask(const Task& task) { + auto runTask = [&, task] { + try { + // Save an iterator to the current task name + decltype(this->m_currTaskNames)::iterator taskNameIter; + { + std::lock_guard guard(this->m_progressMutex); + this->m_currTaskNames.push_back(task.name + "..."); + taskNameIter = std::prev(this->m_currTaskNames.end()); + } + + // When the task finished, increment the progress bar + ON_SCOPE_EXIT { + this->m_completedTaskCount += 1; + this->m_progress = float(this->m_completedTaskCount) / this->m_totalTaskCount; + }; + + // Execute the actual task and track the amount of time it took to run + auto startTime = std::chrono::high_resolution_clock::now(); + bool taskStatus = task.callback(); + auto endTime = std::chrono::high_resolution_clock::now(); + + log::info("Task '{}' finished {} in {} ms", + task.name, + taskStatus ? "successfully" : "unsuccessfully", + std::chrono::duration_cast(endTime - startTime).count() + ); + + // Track the overall status of the tasks + this->m_taskStatus = this->m_taskStatus && taskStatus; + + // Erase the task name from the list of running tasks + { + std::lock_guard guard(this->m_progressMutex); + this->m_currTaskNames.erase(taskNameIter); + } + } catch (const std::exception &e) { + log::error("Init task '{}' threw an exception: {}", task.name, e.what()); + this->m_taskStatus = false; + } catch (...) { + log::error("Init task '{}' threw an unidentifiable exception", task.name); + this->m_taskStatus = false; + } + }; + + this->m_totalTaskCount += 1; + + // If the task can be run asynchronously, run it in a separate thread + // otherwise run it in this thread and wait for it to finish + if (task.async) { + std::thread([runTask = std::move(runTask)]{ runTask(); }).detach(); + } else { + runTask(); + } + } + + FrameResult WindowSplash::fullFrame() { glfwSetWindowSize(this->m_window, 640_scaled, 400_scaled); centerWindow(this->m_window); diff --git a/main/gui/source/init/tasks.cpp b/main/gui/source/init/tasks.cpp index 04c178dfb..341809448 100644 --- a/main/gui/source/init/tasks.cpp +++ b/main/gui/source/init/tasks.cpp @@ -34,89 +34,6 @@ namespace hex::init { using namespace std::literals::string_literals; - static bool checkForUpdatesSync() { - int checkForUpdates = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.server_contact", 2); - - // Check if we should check for updates - if (checkForUpdates == 1){ - HttpRequest request("GET", GitHubApiURL + "/releases/latest"s); - - // Query the GitHub API for the latest release version - auto response = request.execute().get(); - if (response.getStatusCode() != 200) - return false; - - nlohmann::json releases; - try { - releases = nlohmann::json::parse(response.getData()); - } catch (const std::exception &) { - return false; - } - - // Check if the response is valid - if (!releases.contains("tag_name") || !releases["tag_name"].is_string()) - return false; - - // Convert the current version string to a format that can be compared to the latest release - auto versionString = ImHexApi::System::getImHexVersion(); - size_t versionLength = std::min(versionString.find_first_of('-'), versionString.length()); - auto currVersion = "v" + versionString.substr(0, versionLength); - - // Get the latest release version string - auto latestVersion = releases["tag_name"].get(); - - // Check if the latest release is different from the current version - if (latestVersion != currVersion) - ImHexApi::System::impl::addInitArgument("update-available", latestVersion.data()); - - // Check if there is a telemetry uuid - std::string uuid = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.uuid", "").get(); - if (uuid.empty()) { - // Generate a new uuid - uuid = wolv::hash::generateUUID(); - // Save - ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.uuid", uuid); - } - - TaskManager::createBackgroundTask("Sending statistics...", [uuid, versionString](auto&) { - // To avoid potentially flooding our database with lots of dead users - // from people just visiting the website, don't send telemetry data from - // the web version - #if defined(OS_WEB) - return; - #endif - - // Make telemetry request - nlohmann::json telemetry = { - { "uuid", uuid }, - { "format_version", "1" }, - { "imhex_version", versionString }, - { "imhex_commit", fmt::format("{}@{}", ImHexApi::System::getCommitHash(true), ImHexApi::System::getCommitBranch()) }, - { "install_type", ImHexApi::System::isPortableVersion() ? "Portable" : "Installed" }, - { "os", ImHexApi::System::getOSName() }, - { "os_version", ImHexApi::System::getOSVersion() }, - { "arch", ImHexApi::System::getArchitecture() }, - { "gpu_vendor", ImHexApi::System::getGPUVendor() } - }; - - HttpRequest telemetryRequest("POST", ImHexApiURL + "/telemetry"s); - telemetryRequest.setTimeout(500); - - telemetryRequest.setBody(telemetry.dump()); - telemetryRequest.addHeader("Content-Type", "application/json"); - - // Execute request - telemetryRequest.execute(); - }); - } - return true; - } - - static bool checkForUpdates() { - TaskManager::createBackgroundTask("Checking for updates", [](auto&) { checkForUpdatesSync(); }); - return true; - } - bool setupEnvironment() { hex::log::debug("Using romfs: '{}'", romfs::name()); @@ -146,210 +63,6 @@ namespace hex::init { return result; } - bool migrateConfig(){ - - // Check if there is a new config in folder - auto configPaths = hex::fs::getDefaultPaths(hex::fs::ImHexPath::Config, false); - - // There should always be exactly one config path on Linux - std::fs::path newConfigPath = configPaths[0]; - wolv::io::File newConfigFile(newConfigPath / "settings.json", wolv::io::File::Mode::Read); - if (!newConfigFile.isValid()) { - - // Find an old config - std::fs::path oldConfigPath; - for (const auto &dir : hex::fs::appendPath(hex::fs::getDataPaths(), "config")) { - wolv::io::File oldConfigFile(dir / "settings.json", wolv::io::File::Mode::Read); - if (oldConfigFile.isValid()) { - oldConfigPath = dir; - break; - } - } - - if (!oldConfigPath.empty()) { - log::info("Found config file in {}! Migrating to {}", oldConfigPath.string(), newConfigPath.string()); - - std::fs::rename(oldConfigPath / "settings.json", newConfigPath / "settings.json"); - wolv::io::File oldIniFile(oldConfigPath / "interface.ini", wolv::io::File::Mode::Read); - if (oldIniFile.isValid()) { - std::fs::rename(oldConfigPath / "interface.ini", newConfigPath / "interface.ini"); - } - - std::fs::remove(oldConfigPath); - } - } - return true; - } - - static bool loadFontsImpl(bool loadUnicode) { - const float defaultFontSize = ImHexApi::System::DefaultFontSize * std::round(ImHexApi::System::getGlobalScale()); - - // Load custom font related settings - if (ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.custom_font_enable", false).get()) { - std::fs::path fontFile = ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_path", "").get(); - if (!fontFile.empty()) { - if (!wolv::io::fs::exists(fontFile) || !wolv::io::fs::isRegularFile(fontFile)) { - log::warn("Custom font file {} not found! Falling back to default font.", wolv::util::toUTF8String(fontFile)); - fontFile.clear(); - } - - log::info("Loading custom font from {}", wolv::util::toUTF8String(fontFile)); - } - - // If no custom font has been specified, search for a file called "font.ttf" in one of the resource folders - if (fontFile.empty()) { - for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Resources)) { - auto path = dir / "font.ttf"; - if (wolv::io::fs::exists(path)) { - log::info("Loading custom font from {}", wolv::util::toUTF8String(path)); - - fontFile = path; - break; - } - } - } - - ImHexApi::System::impl::setCustomFontPath(fontFile); - - // If a custom font has been loaded now, also load the font size - float fontSize = defaultFontSize; - if (!fontFile.empty()) { - fontSize = ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_size", 13).get() * ImHexApi::System::getGlobalScale(); - } - - ImHexApi::System::impl::setFontSize(fontSize); - } - - float fontSize = ImHexApi::System::getFontSize(); - - const auto &fontFile = ImHexApi::System::getCustomFontPath(); - - // Setup basic font configuration - auto fonts = IM_NEW(ImFontAtlas)(); - ImFontConfig cfg = {}; - cfg.OversampleH = cfg.OversampleV = 1, cfg.PixelSnapH = true; - cfg.SizePixels = fontSize; - - fonts->Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight; - fonts->TexDesiredWidth = 4096; - - // Configure font glyph ranges that should be loaded from the default font and unifont - static ImVector ranges; - { - ImFontGlyphRangesBuilder glyphRangesBuilder; - - { - constexpr static std::array controlCodeRange = { 0x0001, 0x001F, 0 }; - constexpr static std::array extendedAsciiRange = { 0x007F, 0x00FF, 0 }; - - glyphRangesBuilder.AddRanges(controlCodeRange.data()); - glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesDefault()); - glyphRangesBuilder.AddRanges(extendedAsciiRange.data()); - } - - if (loadUnicode) { - constexpr static std::array fullRange = { 0x0100, 0xFFEF, 0 }; - - glyphRangesBuilder.AddRanges(fullRange.data()); - } else { - glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesJapanese()); - glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesChineseFull()); - glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesCyrillic()); - glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesKorean()); - glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesThai()); - glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesVietnamese()); - } - - glyphRangesBuilder.BuildRanges(&ranges); - } - - // Glyph range for font awesome icons - constexpr static std::array fontAwesomeRange = { - ICON_MIN_FA, ICON_MAX_FA, 0 - }; - - // Glyph range for codicons icons - constexpr static std::array codiconsRange = { - ICON_MIN_VS, ICON_MAX_VS, 0 - }; - - // Load main font - // If a custom font has been specified, load it, otherwise load the default ImGui font - if (fontFile.empty()) { - fonts->Clear(); - fonts->AddFontDefault(&cfg); - } else { - if (ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_bold", false)) - cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Bold; - if (ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_italic", false)) - cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Oblique; - if (!ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_antialias", false)) - cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Monochrome | ImGuiFreeTypeBuilderFlags_MonoHinting; - - auto font = fonts->AddFontFromFileTTF(wolv::util::toUTF8String(fontFile).c_str(), 0, &cfg, ranges.Data); - - cfg.FontBuilderFlags = 0; - - if (font == nullptr) { - log::warn("Failed to load custom font! Falling back to default font."); - - ImHexApi::System::impl::setFontSize(defaultFontSize); - cfg.SizePixels = defaultFontSize; - fonts->Clear(); - fonts->AddFontDefault(&cfg); - } - } - - // Merge all fonts into one big font atlas - cfg.MergeMode = true; - - // Add font awesome and codicons icons to font atlas - cfg.GlyphOffset = ImVec2(0, 3_scaled); - fonts->AddFontFromMemoryCompressedTTF(font_awesome_compressed_data, font_awesome_compressed_size, 0, &cfg, fontAwesomeRange.data()); - fonts->AddFontFromMemoryCompressedTTF(codicons_compressed_data, codicons_compressed_size, 0, &cfg, codiconsRange.data()); - - cfg.GlyphOffset = ImVec2(0, 0); - // Add unifont if unicode support is enabled - fonts->AddFontFromMemoryCompressedTTF(unifont_compressed_data, unifont_compressed_size, 0, &cfg, ranges.Data); - - // Try to build the font atlas - if (!fonts->Build()) { - // The main reason the font atlas failed to build is that the font is too big for the GPU to handle - // If unicode support is enabled, therefor try to load the font atlas without unicode support - // If that still didn't work, there's probably something else going on with the graphics drivers - // Especially Intel GPU drivers are known to have various bugs - - if (loadUnicode) { - log::error("Failed to build font atlas! Disabling Unicode support."); - IM_DELETE(fonts); - - // Disable unicode support in settings - ContentRegistry::Settings::write("hex.builtin.setting.font", "hex.builtin.setting.font.load_all_unicode_chars", false); - - // Try to load the font atlas again - return loadFontsImpl(false); - } else { - log::error("Failed to build font atlas! Check your Graphics driver!"); - return false; - } - } - - // Configure ImGui to use the font atlas - ImHexApi::System::impl::setFontAtlas(fonts); - - return true; - } - - bool loadFonts() { - // Check if unicode support is enabled in the settings and that the user doesn't use the No GPU version on Windows - // The Mesa3D software renderer on Windows identifies itself as "VMware, Inc." - bool shouldLoadUnicode = - ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.load_all_unicode_chars", false) && - ImHexApi::System::getGPUVendor() != "VMware, Inc."; - - return loadFontsImpl(shouldLoadUnicode); - } - bool deleteSharedData() { // This function is called when ImHex is closed. It deletes all shared data that was created by plugins // This is a bit of a hack but necessary because when ImHex gets closed, all plugins are unloaded in order for @@ -581,20 +294,6 @@ namespace hex::init { return true; } - bool configureUIScale() { - int interfaceScaleSetting = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.scaling", 0.0F).get() * 10; - - float interfaceScaling; - if (interfaceScaleSetting == 0) - interfaceScaling = ImHexApi::System::getNativeScale(); - else - interfaceScaling = interfaceScaleSetting / 10.0F; - - ImHexApi::System::impl::setGlobalScale(interfaceScaling); - - return true; - } - bool storeSettings() { try { ContentRegistry::Settings::impl::store(); @@ -617,14 +316,8 @@ namespace hex::init { return { { "Setting up environment", setupEnvironment, false }, { "Creating directories", createDirectories, false }, - #if defined(OS_LINUX) - { "Migrate config to .config", migrateConfig, false }, - #endif { "Loading settings", loadSettings, false }, - { "Configuring UI scale", configureUIScale, false }, { "Loading plugins", loadPlugins, true }, - { "Checking for updates", checkForUpdates, false }, - { "Loading fonts", loadFonts, true }, }; } diff --git a/plugins/builtin/CMakeLists.txt b/plugins/builtin/CMakeLists.txt index 1579d3899..af85a6b1b 100644 --- a/plugins/builtin/CMakeLists.txt +++ b/plugins/builtin/CMakeLists.txt @@ -43,6 +43,7 @@ add_imhex_plugin( source/content/achievements.cpp source/content/file_extraction.cpp source/content/report_generators.cpp + source/content/init_tasks.cpp source/content/dpn/basic_nodes.cpp source/content/dpn/control_nodes.cpp diff --git a/plugins/builtin/source/content/init_tasks.cpp b/plugins/builtin/source/content/init_tasks.cpp new file mode 100644 index 000000000..0db2d8887 --- /dev/null +++ b/plugins/builtin/source/content/init_tasks.cpp @@ -0,0 +1,299 @@ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +namespace hex::plugin::builtin { + + namespace { + + using namespace std::literals::string_literals; + + bool checkForUpdatesSync() { + int checkForUpdates = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.server_contact", 2); + + // Check if we should check for updates + if (checkForUpdates == 1){ + HttpRequest request("GET", GitHubApiURL + "/releases/latest"s); + + // Query the GitHub API for the latest release version + auto response = request.execute().get(); + if (response.getStatusCode() != 200) + return false; + + nlohmann::json releases; + try { + releases = nlohmann::json::parse(response.getData()); + } catch (const std::exception &) { + return false; + } + + // Check if the response is valid + if (!releases.contains("tag_name") || !releases["tag_name"].is_string()) + return false; + + // Convert the current version string to a format that can be compared to the latest release + auto versionString = ImHexApi::System::getImHexVersion(); + size_t versionLength = std::min(versionString.find_first_of('-'), versionString.length()); + auto currVersion = "v" + versionString.substr(0, versionLength); + + // Get the latest release version string + auto latestVersion = releases["tag_name"].get(); + + // Check if the latest release is different from the current version + if (latestVersion != currVersion) + ImHexApi::System::impl::addInitArgument("update-available", latestVersion.data()); + + // Check if there is a telemetry uuid + std::string uuid = ContentRegistry::Settings::read("hex.builtin.setting.general", "hex.builtin.setting.general.uuid", "").get(); + if (uuid.empty()) { + // Generate a new uuid + uuid = wolv::hash::generateUUID(); + // Save + ContentRegistry::Settings::write("hex.builtin.setting.general", "hex.builtin.setting.general.uuid", uuid); + } + + TaskManager::createBackgroundTask("Sending statistics...", [uuid, versionString](auto&) { + // To avoid potentially flooding our database with lots of dead users + // from people just visiting the website, don't send telemetry data from + // the web version + #if defined(OS_WEB) + return; + #endif + + // Make telemetry request + nlohmann::json telemetry = { + { "uuid", uuid }, + { "format_version", "1" }, + { "imhex_version", versionString }, + { "imhex_commit", fmt::format("{}@{}", ImHexApi::System::getCommitHash(true), ImHexApi::System::getCommitBranch()) }, + { "install_type", ImHexApi::System::isPortableVersion() ? "Portable" : "Installed" }, + { "os", ImHexApi::System::getOSName() }, + { "os_version", ImHexApi::System::getOSVersion() }, + { "arch", ImHexApi::System::getArchitecture() }, + { "gpu_vendor", ImHexApi::System::getGPUVendor() } + }; + + HttpRequest telemetryRequest("POST", ImHexApiURL + "/telemetry"s); + telemetryRequest.setTimeout(500); + + telemetryRequest.setBody(telemetry.dump()); + telemetryRequest.addHeader("Content-Type", "application/json"); + + // Execute request + telemetryRequest.execute(); + }); + } + return true; + } + + bool checkForUpdates() { + TaskManager::createBackgroundTask("Checking for updates", [](auto&) { checkForUpdatesSync(); }); + return true; + } + + bool configureUIScale() { + int interfaceScaleSetting = ContentRegistry::Settings::read("hex.builtin.setting.interface", "hex.builtin.setting.interface.scaling", 0.0F).get() * 10; + + float interfaceScaling; + if (interfaceScaleSetting == 0) + interfaceScaling = ImHexApi::System::getNativeScale(); + else + interfaceScaling = interfaceScaleSetting / 10.0F; + + ImHexApi::System::impl::setGlobalScale(interfaceScaling); + + return true; + } + + bool loadFontsImpl(bool loadUnicode) { + const float defaultFontSize = ImHexApi::System::DefaultFontSize * std::round(ImHexApi::System::getGlobalScale()); + + // Load custom font related settings + if (ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.custom_font_enable", false).get()) { + std::fs::path fontFile = ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_path", "").get(); + if (!fontFile.empty()) { + if (!wolv::io::fs::exists(fontFile) || !wolv::io::fs::isRegularFile(fontFile)) { + log::warn("Custom font file {} not found! Falling back to default font.", wolv::util::toUTF8String(fontFile)); + fontFile.clear(); + } + + log::info("Loading custom font from {}", wolv::util::toUTF8String(fontFile)); + } + + // If no custom font has been specified, search for a file called "font.ttf" in one of the resource folders + if (fontFile.empty()) { + for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Resources)) { + auto path = dir / "font.ttf"; + if (wolv::io::fs::exists(path)) { + log::info("Loading custom font from {}", wolv::util::toUTF8String(path)); + + fontFile = path; + break; + } + } + } + + ImHexApi::System::impl::setCustomFontPath(fontFile); + + // If a custom font has been loaded now, also load the font size + float fontSize = defaultFontSize; + if (!fontFile.empty()) { + fontSize = ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_size", 13).get() * ImHexApi::System::getGlobalScale(); + } + + ImHexApi::System::impl::setFontSize(fontSize); + } + + float fontSize = ImHexApi::System::getFontSize(); + + const auto &fontFile = ImHexApi::System::getCustomFontPath(); + + // Setup basic font configuration + auto fonts = IM_NEW(ImFontAtlas)(); + ImFontConfig cfg = {}; + cfg.OversampleH = cfg.OversampleV = 1, cfg.PixelSnapH = true; + cfg.SizePixels = fontSize; + + fonts->Flags |= ImFontAtlasFlags_NoPowerOfTwoHeight; + fonts->TexDesiredWidth = 4096; + + // Configure font glyph ranges that should be loaded from the default font and unifont + static ImVector ranges; + { + ImFontGlyphRangesBuilder glyphRangesBuilder; + + { + constexpr static std::array controlCodeRange = { 0x0001, 0x001F, 0 }; + constexpr static std::array extendedAsciiRange = { 0x007F, 0x00FF, 0 }; + + glyphRangesBuilder.AddRanges(controlCodeRange.data()); + glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesDefault()); + glyphRangesBuilder.AddRanges(extendedAsciiRange.data()); + } + + if (loadUnicode) { + constexpr static std::array fullRange = { 0x0100, 0xFFEF, 0 }; + + glyphRangesBuilder.AddRanges(fullRange.data()); + } else { + glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesJapanese()); + glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesChineseFull()); + glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesCyrillic()); + glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesKorean()); + glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesThai()); + glyphRangesBuilder.AddRanges(fonts->GetGlyphRangesVietnamese()); + } + + glyphRangesBuilder.BuildRanges(&ranges); + } + + // Glyph range for font awesome icons + constexpr static std::array fontAwesomeRange = { + ICON_MIN_FA, ICON_MAX_FA, 0 + }; + + // Glyph range for codicons icons + constexpr static std::array codiconsRange = { + ICON_MIN_VS, ICON_MAX_VS, 0 + }; + + // Load main font + // If a custom font has been specified, load it, otherwise load the default ImGui font + if (fontFile.empty()) { + fonts->Clear(); + fonts->AddFontDefault(&cfg); + } else { + if (ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_bold", false)) + cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Bold; + if (ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_italic", false)) + cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Oblique; + if (!ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_antialias", false)) + cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Monochrome | ImGuiFreeTypeBuilderFlags_MonoHinting; + + auto font = fonts->AddFontFromFileTTF(wolv::util::toUTF8String(fontFile).c_str(), 0, &cfg, ranges.Data); + + cfg.FontBuilderFlags = 0; + + if (font == nullptr) { + log::warn("Failed to load custom font! Falling back to default font."); + + ImHexApi::System::impl::setFontSize(defaultFontSize); + cfg.SizePixels = defaultFontSize; + fonts->Clear(); + fonts->AddFontDefault(&cfg); + } + } + + // Merge all fonts into one big font atlas + cfg.MergeMode = true; + + // Add font awesome and codicons icons to font atlas + cfg.GlyphOffset = ImVec2(0, 3_scaled); + fonts->AddFontFromMemoryCompressedTTF(font_awesome_compressed_data, font_awesome_compressed_size, 0, &cfg, fontAwesomeRange.data()); + fonts->AddFontFromMemoryCompressedTTF(codicons_compressed_data, codicons_compressed_size, 0, &cfg, codiconsRange.data()); + + cfg.GlyphOffset = ImVec2(0, 0); + // Add unifont if unicode support is enabled + fonts->AddFontFromMemoryCompressedTTF(unifont_compressed_data, unifont_compressed_size, 0, &cfg, ranges.Data); + + // Try to build the font atlas + if (!fonts->Build()) { + // The main reason the font atlas failed to build is that the font is too big for the GPU to handle + // If unicode support is enabled, therefor try to load the font atlas without unicode support + // If that still didn't work, there's probably something else going on with the graphics drivers + // Especially Intel GPU drivers are known to have various bugs + + if (loadUnicode) { + log::error("Failed to build font atlas! Disabling Unicode support."); + IM_DELETE(fonts); + + // Disable unicode support in settings + ContentRegistry::Settings::write("hex.builtin.setting.font", "hex.builtin.setting.font.load_all_unicode_chars", false); + + // Try to load the font atlas again + return loadFontsImpl(false); + } else { + log::error("Failed to build font atlas! Check your Graphics driver!"); + return false; + } + } + + // Configure ImGui to use the font atlas + ImHexApi::System::impl::setFontAtlas(fonts); + + return true; + } + + bool loadFonts() { + // Check if unicode support is enabled in the settings and that the user doesn't use the No GPU version on Windows + // The Mesa3D software renderer on Windows identifies itself as "VMware, Inc." + bool shouldLoadUnicode = + ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.load_all_unicode_chars", false) && + ImHexApi::System::getGPUVendor() != "VMware, Inc."; + + return loadFontsImpl(shouldLoadUnicode); + } + + } + + void addInitTasks() { + ImHexApi::System::addStartupTask("Loading fonts", true, loadFonts); + ImHexApi::System::addStartupTask("Checking for updates", true, checkForUpdates); + ImHexApi::System::addStartupTask("Configuring UI scale", true, configureUIScale); + } +} \ No newline at end of file diff --git a/plugins/builtin/source/plugin_builtin.cpp b/plugins/builtin/source/plugin_builtin.cpp index 644caaccf..08076e218 100644 --- a/plugins/builtin/source/plugin_builtin.cpp +++ b/plugins/builtin/source/plugin_builtin.cpp @@ -44,6 +44,7 @@ namespace hex::plugin::builtin { void addTitleBarButtons(); void addToolbarItems(); void addGlobalUIItems(); + void addInitTasks(); void handleBorderlessWindowMode(); @@ -74,6 +75,8 @@ IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") { for (auto &path : romfs::list("lang")) hex::ContentRegistry::Language::addLocalization(nlohmann::json::parse(romfs::get(path).string())); + addInitTasks(); + registerEventHandlers(); registerDataVisualizers(); registerDataInspectorEntries();