diff --git a/lib/libimhex/include/hex/api/imhex_api.hpp b/lib/libimhex/include/hex/api/imhex_api.hpp index 3ba0b9de6..204ab1ad4 100644 --- a/lib/libimhex/include/hex/api/imhex_api.hpp +++ b/lib/libimhex/include/hex/api/imhex_api.hpp @@ -401,8 +401,6 @@ namespace hex { void setGPUVendor(const std::string &vendor); - void setPortableVersion(bool enabled); - void addInitArgument(const std::string &key, const std::string &value = { }); void setLastFrameTime(double time); diff --git a/lib/libimhex/include/hex/helpers/logger.hpp b/lib/libimhex/include/hex/helpers/logger.hpp index 541cac872..17f4aae1e 100644 --- a/lib/libimhex/include/hex/helpers/logger.hpp +++ b/lib/libimhex/include/hex/helpers/logger.hpp @@ -19,7 +19,8 @@ namespace hex::log { [[maybe_unused]] void redirectToFile(); [[maybe_unused]] void enableColorPrinting(); - extern std::mutex g_loggerMutex; + [[nodiscard]] std::mutex& getLoggerMutex(); + [[nodiscard]] bool isLoggingSuspended(); struct LogEntry { std::string project; @@ -33,7 +34,10 @@ namespace hex::log { [[maybe_unused]] void printPrefix(FILE *dest, const fmt::text_style &ts, const std::string &level, const char *projectName); [[maybe_unused]] void print(const fmt::text_style &ts, const std::string &level, const std::string &fmt, auto && ... args) { - std::scoped_lock lock(g_loggerMutex); + if (isLoggingSuspended()) [[unlikely]] + return; + + std::scoped_lock lock(getLoggerMutex()); auto dest = getDestination(); printPrefix(dest, ts, level, IMHEX_PROJECT_NAME); @@ -47,6 +51,9 @@ namespace hex::log { } + void suspendLogging(); + void resumeLogging(); + [[maybe_unused]] void debug(const std::string &fmt, auto && ... args) { #if defined(DEBUG) hex::log::impl::print(fg(fmt::color::light_green) | fmt::emphasis::bold, "[DEBUG]", fmt, args...); @@ -73,7 +80,7 @@ namespace hex::log { [[maybe_unused]] void print(const std::string &fmt, auto && ... args) { - std::scoped_lock lock(impl::g_loggerMutex); + std::scoped_lock lock(impl::getLoggerMutex()); auto dest = impl::getDestination(); auto message = fmt::format(fmt::runtime(fmt), args...); @@ -82,7 +89,7 @@ namespace hex::log { } [[maybe_unused]] void println(const std::string &fmt, auto && ... args) { - std::scoped_lock lock(impl::g_loggerMutex); + std::scoped_lock lock(impl::getLoggerMutex()); auto dest = impl::getDestination(); auto message = fmt::format(fmt::runtime(fmt), args...); diff --git a/lib/libimhex/source/api/imhex_api.cpp b/lib/libimhex/source/api/imhex_api.cpp index b75aefa5f..5d1791c25 100644 --- a/lib/libimhex/source/api/imhex_api.cpp +++ b/lib/libimhex/source/api/imhex_api.cpp @@ -453,11 +453,6 @@ namespace hex { s_gpuVendor = vendor; } - static bool s_portableVersion = false; - void setPortableVersion(bool enabled) { - s_portableVersion = enabled; - } - static AutoReset> s_initArguments; void addInitArgument(const std::string &key, const std::string &value) { static std::mutex initArgumentsMutex; @@ -588,7 +583,19 @@ namespace hex { } bool isPortableVersion() { - return impl::s_portableVersion; + static std::optional portable; + if (portable.has_value()) + return portable.value(); + + if (const auto executablePath = wolv::io::fs::getExecutablePath(); executablePath.has_value()) { + const auto flagFile = executablePath->parent_path() / "PORTABLE"; + + portable = wolv::io::fs::exists(flagFile) && wolv::io::fs::isRegularFile(flagFile); + } else { + portable = false; + } + + return portable.value(); } std::string getOSName() { diff --git a/lib/libimhex/source/api/plugin_manager.cpp b/lib/libimhex/source/api/plugin_manager.cpp index 545420aef..e5260e40b 100644 --- a/lib/libimhex/source/api/plugin_manager.cpp +++ b/lib/libimhex/source/api/plugin_manager.cpp @@ -86,7 +86,7 @@ namespace hex { Plugin::~Plugin() { if (isLoaded()) { - log::debug("Trying to unload plugin '{}'", getPluginName()); + log::info("Trying to unload plugin '{}'", getPluginName()); } #if defined(OS_WINDOWS) diff --git a/lib/libimhex/source/helpers/logger.cpp b/lib/libimhex/source/helpers/logger.cpp index f69b3dd51..3c8b28b36 100644 --- a/lib/libimhex/source/helpers/logger.cpp +++ b/lib/libimhex/source/helpers/logger.cpp @@ -12,100 +12,125 @@ #include #endif -namespace hex::log::impl { +namespace hex::log { - static wolv::io::File s_loggerFile; - static bool s_colorOutputEnabled = false; - std::mutex g_loggerMutex; + namespace { + + wolv::io::File s_loggerFile; + bool s_colorOutputEnabled = false; + std::mutex s_loggerMutex; + bool s_loggingSuspended = false; - FILE *getDestination() { - if (s_loggerFile.isValid()) - return s_loggerFile.getHandle(); - else - return stdout; } - wolv::io::File& getFile() { - return s_loggerFile; + void suspendLogging() { + s_loggingSuspended = true; } - bool isRedirected() { - return s_loggerFile.isValid(); + void resumeLogging() { + s_loggingSuspended = false; } - void redirectToFile() { - if (s_loggerFile.isValid()) return; + namespace impl { - for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Logs, true)) { - wolv::io::fs::createDirectories(path); - s_loggerFile = wolv::io::File(path / hex::format("{0:%Y%m%d_%H%M%S}.log", fmt::localtime(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()))), wolv::io::File::Mode::Create); - s_loggerFile.disableBuffering(); - - if (s_loggerFile.isValid()) { - s_colorOutputEnabled = true; - break; - } + std::mutex& getLoggerMutex() { + return s_loggerMutex; } - } - void enableColorPrinting() { - s_colorOutputEnabled = true; + bool isLoggingSuspended() { + return s_loggingSuspended; + } - #if defined(OS_WINDOWS) - auto hConsole = ::GetStdHandle(STD_OUTPUT_HANDLE); - if (hConsole != INVALID_HANDLE_VALUE) { - DWORD mode = 0; - if (::GetConsoleMode(hConsole, &mode) == TRUE) { - mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT; - ::SetConsoleMode(hConsole, mode); + FILE *getDestination() { + if (s_loggerFile.isValid()) + return s_loggerFile.getHandle(); + else + return stdout; + } + + wolv::io::File& getFile() { + return s_loggerFile; + } + + bool isRedirected() { + return s_loggerFile.isValid(); + } + + void redirectToFile() { + if (s_loggerFile.isValid()) return; + + for (const auto &path : fs::getDefaultPaths(fs::ImHexPath::Logs, true)) { + wolv::io::fs::createDirectories(path); + s_loggerFile = wolv::io::File(path / hex::format("{0:%Y%m%d_%H%M%S}.log", fmt::localtime(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()))), wolv::io::File::Mode::Create); + s_loggerFile.disableBuffering(); + + if (s_loggerFile.isValid()) { + s_colorOutputEnabled = true; + break; } } - #endif - } + } + void enableColorPrinting() { + s_colorOutputEnabled = true; - std::vector& getLogEntries() { - static std::vector logEntries; - return logEntries; - } - - void addLogEntry(std::string_view project, std::string_view level, std::string_view message) { - getLogEntries().emplace_back(project.data(), level.data(), message.data()); - } - - - void printPrefix(FILE *dest, const fmt::text_style &ts, const std::string &level, const char *projectName) { - const auto now = fmt::localtime(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); - - fmt::print(dest, "[{0:%H:%M:%S}] ", now); - - if (s_colorOutputEnabled) - fmt::print(dest, ts, "{0} ", level); - else - fmt::print(dest, "{0} ", level); - - std::string projectThreadTag = projectName; - if (auto threadName = TaskManager::getCurrentThreadName(); !threadName.empty()) - projectThreadTag += fmt::format(" | {0}", threadName); - - constexpr static auto MaxTagLength = 25; - if (projectThreadTag.length() > MaxTagLength) - projectThreadTag.resize(MaxTagLength); - - fmt::print(dest, "[{0}] ", projectThreadTag); - - const auto projectNameLength = projectThreadTag.length(); - fmt::print(dest, "{0}", std::string(projectNameLength > MaxTagLength ? 0 : MaxTagLength - projectNameLength, ' ')); - } - - void assertionHandler(bool expr, const char* exprString, const char* file, int line) { - if (!expr) { - log::error("Assertion failed: {} at {}:{}", exprString, file, line); - - #if defined (DEBUG) - std::abort(); + #if defined(OS_WINDOWS) + auto hConsole = ::GetStdHandle(STD_OUTPUT_HANDLE); + if (hConsole != INVALID_HANDLE_VALUE) { + DWORD mode = 0; + if (::GetConsoleMode(hConsole, &mode) == TRUE) { + mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_PROCESSED_OUTPUT; + ::SetConsoleMode(hConsole, mode); + } + } #endif } + + + std::vector& getLogEntries() { + static std::vector logEntries; + return logEntries; + } + + void addLogEntry(std::string_view project, std::string_view level, std::string_view message) { + getLogEntries().emplace_back(project.data(), level.data(), message.data()); + } + + + void printPrefix(FILE *dest, const fmt::text_style &ts, const std::string &level, const char *projectName) { + const auto now = fmt::localtime(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); + + fmt::print(dest, "[{0:%H:%M:%S}] ", now); + + if (s_colorOutputEnabled) + fmt::print(dest, ts, "{0} ", level); + else + fmt::print(dest, "{0} ", level); + + std::string projectThreadTag = projectName; + if (auto threadName = TaskManager::getCurrentThreadName(); !threadName.empty()) + projectThreadTag += fmt::format(" | {0}", threadName); + + constexpr static auto MaxTagLength = 25; + if (projectThreadTag.length() > MaxTagLength) + projectThreadTag.resize(MaxTagLength); + + fmt::print(dest, "[{0}] ", projectThreadTag); + + const auto projectNameLength = projectThreadTag.length(); + fmt::print(dest, "{0}", std::string(projectNameLength > MaxTagLength ? 0 : MaxTagLength - projectNameLength, ' ')); + } + + void assertionHandler(bool expr, const char* exprString, const char* file, int line) { + if (!expr) { + log::error("Assertion failed: {} at {}:{}", exprString, file, line); + + #if defined (DEBUG) + std::abort(); + #endif + } + } + } } \ No newline at end of file diff --git a/main/gui/CMakeLists.txt b/main/gui/CMakeLists.txt index d46759e06..ab86c86aa 100644 --- a/main/gui/CMakeLists.txt +++ b/main/gui/CMakeLists.txt @@ -23,6 +23,7 @@ add_executable(main ${APPLICATION_TYPE} source/init/run/common.cpp source/init/run/native.cpp source/init/run/web.cpp + source/init/run/cli.cpp ${IMHEX_ICON} ) diff --git a/main/gui/source/init/run/cli.cpp b/main/gui/source/init/run/cli.cpp new file mode 100644 index 000000000..2d1871c79 --- /dev/null +++ b/main/gui/source/init/run/cli.cpp @@ -0,0 +1,76 @@ +#include "messaging.hpp" + +#include + +#include +#include +#include + +#include + + +#if defined(OS_WINDOWS) + #include + #include + #include +#endif + +namespace hex::init { + + /** + * @brief Handles commands passed to ImHex via the command line + * @param argc Argument count + * @param argv Argument values + */ + void runCommandLine(int argc, char **argv) { + // Suspend logging while processing command line arguments so + // we don't spam the console with log messages while printing + // CLI tool messages + log::suspendLogging(); + ON_SCOPE_EXIT { + log::resumeLogging(); + }; + + std::vector args; + + #if defined (OS_WINDOWS) + hex::unused(argv); + + // On Windows, argv contains UTF-16 encoded strings, so we need to convert them to UTF-8 + auto convertedCommandLine = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (convertedCommandLine == nullptr) { + log::error("Failed to convert command line arguments to UTF-8"); + std::exit(EXIT_FAILURE); + } + + // Skip the first argument (the executable path) and convert the rest to a vector of UTF-8 strings + for (int i = 1; i < argc; i += 1) { + std::wstring wcharArg = convertedCommandLine[i]; + std::string utf8Arg = std::wstring_convert>().to_bytes(wcharArg); + + args.push_back(utf8Arg); + } + + ::LocalFree(convertedCommandLine); + #else + // Skip the first argument (the executable path) and convert the rest to a vector of strings + args = { argv + 1, argv + argc }; + #endif + + + // Load all plugins but don't initialize them + for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Plugins)) { + PluginManager::load(dir); + } + + // Setup messaging system to allow sending commands to the main ImHex instance + hex::messaging::setupMessaging(); + + // Process the arguments + hex::subcommands::processArguments(args); + + // Unload plugins again + PluginManager::unload(); + } + +} \ No newline at end of file diff --git a/main/gui/source/main.cpp b/main/gui/source/main.cpp index 15f34f855..77bab02bd 100644 --- a/main/gui/source/main.cpp +++ b/main/gui/source/main.cpp @@ -3,96 +3,19 @@ #include #include "window.hpp" -#include "crash_handlers.hpp" -#include "messaging.hpp" - #include "init/splash_window.hpp" +#include "crash_handlers.hpp" + #include #include -#include -#include "hex/subcommands/subcommands.hpp" +namespace hex::init { -#include - -#if defined(OS_WINDOWS) - #include - #include - #include -#endif - -using namespace hex; - -namespace hex::init { int runImHex(); } - -namespace { - - /** - * @brief Handles commands passed to ImHex via the command line - * @param argc Argument count - * @param argv Argument values - */ - void handleCommandLineInterface(int argc, char **argv) { - std::vector args; - - #if defined (OS_WINDOWS) - hex::unused(argv); - - // On Windows, argv contains UTF-16 encoded strings, so we need to convert them to UTF-8 - auto convertedCommandLine = ::CommandLineToArgvW(::GetCommandLineW(), &argc); - if (convertedCommandLine == nullptr) { - log::error("Failed to convert command line arguments to UTF-8"); - std::exit(EXIT_FAILURE); - } - - for (int i = 1; i < argc; i += 1) { - std::wstring wcharArg = convertedCommandLine[i]; - std::string utf8Arg = std::wstring_convert>().to_bytes(wcharArg); - - args.push_back(utf8Arg); - } - - ::LocalFree(convertedCommandLine); - #else - // Skip the first argument (the executable path) and convert the rest to a vector of strings - args = { argv + 1, argv + argc }; - #endif - - - // Load all plugins but don't initialize them - for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Plugins)) { - PluginManager::load(dir); - } - - // Setup messaging system to allow sending commands to the main ImHex instance - hex::messaging::setupMessaging(); - - // Process the arguments - hex::subcommands::processArguments(args); - - // Unload plugins again - PluginManager::unload(); - } - - /** - * @brief Checks if the portable version of ImHex is installed - * @note The portable version is detected by the presence of a file named "PORTABLE" in the same directory as the executable - * @return True if ImHex is running in portable mode, false otherwise - */ - bool isPortableVersion() { - if (const auto executablePath = wolv::io::fs::getExecutablePath(); executablePath.has_value()) { - const auto flagFile = executablePath->parent_path() / "PORTABLE"; - - if (wolv::io::fs::exists(flagFile) && wolv::io::fs::isRegularFile(flagFile)) - return true; - } - - return false; - } + int runImHex(); + void runCommandLine(int argc, char **argv); } - /** * @brief Main entry point of ImHex * @param argc Argument count @@ -100,19 +23,27 @@ namespace { * @return Exit code */ int main(int argc, char **argv) { + using namespace hex; + + // Set the main thread's name to "Main" TaskManager::setCurrentThreadName("Main"); - Window::initNative(); + + // Setup crash handlers right away to catch crashes as early as possible crash::setupCrashHandlers(); + // Run platform-specific initialization code + Window::initNative(); + + // Handle command line arguments if any have been passed if (argc > 1) { - handleCommandLineInterface(argc, argv); + init::runCommandLine(argc, argv); } + // Log some system information to aid debugging when users share their logs log::info("Welcome to ImHex {}!", ImHexApi::System::getImHexVersion()); log::info("Compiled using commit {}@{}", ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash()); log::info("Running on {} {} ({})", ImHexApi::System::getOSName(), ImHexApi::System::getOSVersion(), ImHexApi::System::getArchitecture()); - ImHexApi::System::impl::setPortableVersion(isPortableVersion()); - + // Run ImHex return init::runImHex(); } diff --git a/plugins/builtin/source/content/command_line_interface.cpp b/plugins/builtin/source/content/command_line_interface.cpp index 9196b53d5..d573f1ce5 100644 --- a/plugins/builtin/source/content/command_line_interface.cpp +++ b/plugins/builtin/source/content/command_line_interface.cpp @@ -319,6 +319,7 @@ namespace hex::plugin::builtin { } log::println("{}", llvm::demangle(args[0])); + std::exit(EXIT_SUCCESS); }