From 1ed658bcdc33207fe1da2c09d60c7769f9bf5f28 Mon Sep 17 00:00:00 2001 From: iTrooz Date: Thu, 13 Jul 2023 14:08:23 +0200 Subject: [PATCH] feat: Added command line interface support (#1172) System design has been discussed on discord Should fix #948 --------- Co-authored-by: WerWolv --- cmake/build_helpers.cmake | 1 - lib/libimhex/CMakeLists.txt | 2 + lib/libimhex/include/hex/api/event.hpp | 6 + lib/libimhex/include/hex/api/imhex_api.hpp | 39 +++--- .../include/hex/api/plugin_manager.hpp | 16 +++ lib/libimhex/include/hex/plugin.hpp | 25 ++++ .../include/hex/subcommands/subcommands.hpp | 30 +++++ lib/libimhex/source/api/imhex_api.cpp | 70 ++++++---- lib/libimhex/source/api/plugin_manager.cpp | 11 ++ .../source/subcommands/subcommands.cpp | 120 +++++++++++++++++ main/CMakeLists.txt | 5 + main/include/messaging.hpp | 35 +++++ main/source/init/tasks.cpp | 6 +- main/source/main.cpp | 39 ++++-- main/source/messaging/common.cpp | 33 +++++ main/source/messaging/linux.cpp | 24 ++++ main/source/messaging/macos.cpp | 24 ++++ main/source/messaging/win.cpp | 84 ++++++++++++ main/source/window/win_window.cpp | 123 ++++++------------ plugins/builtin/romfs/logo.ans | 21 +++ plugins/builtin/source/plugin_builtin.cpp | 65 +++++++++ 21 files changed, 636 insertions(+), 143 deletions(-) create mode 100644 lib/libimhex/include/hex/subcommands/subcommands.hpp create mode 100644 lib/libimhex/source/subcommands/subcommands.cpp create mode 100644 main/include/messaging.hpp create mode 100644 main/source/messaging/common.cpp create mode 100644 main/source/messaging/linux.cpp create mode 100644 main/source/messaging/macos.cpp create mode 100644 main/source/messaging/win.cpp create mode 100644 plugins/builtin/romfs/logo.ans diff --git a/cmake/build_helpers.cmake b/cmake/build_helpers.cmake index 2a920ed17..4a9db5293 100644 --- a/cmake/build_helpers.cmake +++ b/cmake/build_helpers.cmake @@ -100,7 +100,6 @@ macro(configurePackingResources) if (WIN32) set(APPLICATION_TYPE) set(IMHEX_ICON "${IMHEX_BASE_FOLDER}/resources/resource.rc") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wl,-subsystem,windows") if (CREATE_PACKAGE) set(CPACK_GENERATOR "WIX") diff --git a/lib/libimhex/CMakeLists.txt b/lib/libimhex/CMakeLists.txt index 5586fb18b..4e1a307d9 100644 --- a/lib/libimhex/CMakeLists.txt +++ b/lib/libimhex/CMakeLists.txt @@ -38,6 +38,8 @@ set(LIBIMHEX_SOURCES source/ui/imgui_imhex_extensions.cpp source/ui/view.cpp source/ui/popup.cpp + + source/subcommands/subcommands.cpp ) if (APPLE) diff --git a/lib/libimhex/include/hex/api/event.hpp b/lib/libimhex/include/hex/api/event.hpp index 4a0e02ba3..0ce8d6d5d 100644 --- a/lib/libimhex/include/hex/api/event.hpp +++ b/lib/libimhex/include/hex/api/event.hpp @@ -232,4 +232,10 @@ namespace hex { EVENT_DEF(RequestOpenInfoPopup, const std::string); EVENT_DEF(RequestOpenErrorPopup, const std::string); EVENT_DEF(RequestOpenFatalPopup, const std::string); + + + /** + * @brief Send an event to the main Imhex instance + */ + EVENT_DEF(SendMessageToMainInstance, const std::string, const std::vector&); } \ No newline at end of file diff --git a/lib/libimhex/include/hex/api/imhex_api.hpp b/lib/libimhex/include/hex/api/imhex_api.hpp index db12afc97..ae8e25146 100644 --- a/lib/libimhex/include/hex/api/imhex_api.hpp +++ b/lib/libimhex/include/hex/api/imhex_api.hpp @@ -322,7 +322,10 @@ namespace hex { /* Functions to interact with various ImHex system settings */ namespace System { + bool isMainInstance(); + namespace impl { + void setMainInstanceStatus(bool status); void setMainWindowPosition(i32 x, i32 y); void setMainWindowSize(u32 width, u32 height); @@ -331,8 +334,6 @@ namespace hex { void setGlobalScale(float scale); void setNativeScale(float scale); - void setProgramArguments(int argc, char **argv, char **envp); - void setBorderlessWindowMode(bool enabled); void setCustomFontPath(const std::fs::path &path); @@ -383,20 +384,6 @@ namespace hex { void setTaskBarProgress(TaskProgressState state, TaskProgressType type, u32 progress); - /** - * @brief Gets the current program arguments - * @return The current program arguments - */ - const ProgramArguments &getProgramArguments(); - - /** - * @brief Gets a program argument - * @param index The index of the argument to get - * @return The argument at the given index - */ - std::optional getProgramArgument(int index); - - /** * @brief Gets the current target FPS * @return The current target FPS @@ -545,6 +532,26 @@ namespace hex { std::string getCommitBranch(); } + /** + * @brief Cross-instance messaging system + * This allows you to send messages to the "main" instance of ImHex running, from any other instance + */ + namespace Messaging { + + namespace impl { + using MessagingHandler = std::function &)>; + + std::map &getHandlers(); + + void runHandler(const std::string &eventName, const std::vector &args); + } + + /** + * @brief Register the handler for this specific event name + */ + void registerHandler(const std::string &eventName, const impl::MessagingHandler &handler); + } + } } diff --git a/lib/libimhex/include/hex/api/plugin_manager.hpp b/lib/libimhex/include/hex/api/plugin_manager.hpp index 4ed2dc45c..087d83202 100644 --- a/lib/libimhex/include/hex/api/plugin_manager.hpp +++ b/lib/libimhex/include/hex/api/plugin_manager.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #if defined(OS_WINDOWS) @@ -17,6 +18,17 @@ struct ImGuiContext; namespace hex { + struct SubCommand { + std::string commandKey; + std::string commandDesc; + std::function&)> callback; + }; + + struct SubCommandList { + hex::SubCommand *subCommands; + size_t size; + }; + class Plugin { public: explicit Plugin(const std::fs::path &path); @@ -36,6 +48,8 @@ namespace hex { [[nodiscard]] bool isLoaded() const; + [[nodiscard]] std::span getSubCommands() const; + private: using InitializePluginFunc = void (*)(); using GetPluginNameFunc = const char *(*)(); @@ -44,6 +58,7 @@ namespace hex { using GetCompatibleVersionFunc = const char *(*)(); using SetImGuiContextFunc = void (*)(ImGuiContext *); using IsBuiltinPluginFunc = bool (*)(); + using GetSubCommandsFunc = SubCommandList* (*)(); #if defined(OS_WINDOWS) HMODULE m_handle = nullptr; @@ -61,6 +76,7 @@ namespace hex { GetCompatibleVersionFunc m_getCompatibleVersionFunction = nullptr; SetImGuiContextFunc m_setImGuiContextFunction = nullptr; IsBuiltinPluginFunc m_isBuiltinPluginFunction = nullptr; + GetSubCommandsFunc m_getSubCommandsFunction = nullptr; template [[nodiscard]] auto getPluginFunction(const std::string &symbol) { diff --git a/lib/libimhex/include/hex/plugin.hpp b/lib/libimhex/include/hex/plugin.hpp index 722927a24..b60410ec1 100644 --- a/lib/libimhex/include/hex/plugin.hpp +++ b/lib/libimhex/include/hex/plugin.hpp @@ -1,9 +1,14 @@ #pragma once +#include + #include #include #include +#include + +#include /** * This macro is used to define all the required entry points for a plugin. @@ -21,3 +26,23 @@ GImGui = ctx; \ } \ extern "C" [[gnu::visibility("default")]] void initializePlugin() + +/** + * This macro is used to define subcommands defined by the plugin + * A subcommand consists of a key, a description, and a callback + * The key is what the first argument to ImHex should be, prefixed by `--` + * For example, if the key if `help`, ImHex should be started with `--help` as its first argument to trigger the subcommand + * when the subcommand is triggerred, it's callback will be executed. The callback is executed BEFORE most of ImHex initialization + * so to do anything meaningful, you should subscribe to an event (like EventImHexStartupFinished) and run your code there. + */ +#define IMHEX_PLUGIN_SUBCOMMANDS() IMHEX_PLUGIN_SUBCOMMANDS_IMPL() + +#define IMHEX_PLUGIN_SUBCOMMANDS_IMPL() \ + extern std::vector g_subCommands; \ + extern "C" [[gnu::visibility("default")]] hex::SubCommandList getSubCommands() { \ + return hex::SubCommandList { \ + g_subCommands.data(), \ + g_subCommands.size() \ + }; \ + } \ + std::vector g_subCommands diff --git a/lib/libimhex/include/hex/subcommands/subcommands.hpp b/lib/libimhex/include/hex/subcommands/subcommands.hpp new file mode 100644 index 000000000..2f01aed45 --- /dev/null +++ b/lib/libimhex/include/hex/subcommands/subcommands.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +namespace hex::subcommands { + /** + * @brief Internal method - takes all the arguments ImHex received from the command line, + * and determine which subcommands to run, with which arguments. + * In some cases, the subcommand or this function directly might exit the program + * (e.g. --help, or when forwarding providers to open to another instance) + * and so this function might not return + */ + void processArguments(const std::vector &args); + + + /** + * @brief Forward the given command to the main instance (might be this instance) + * The callback will be executed after EventImHexStartupFinished + */ + void forwardSubCommand(const std::string &cmdName, const std::vector &args); + + using ForwardCommandHandler = std::function &)>; + + /** + * @brief Register the handler for this specific command name + */ + void registerSubCommand(const std::string &cmdName, const ForwardCommandHandler &handler); +} diff --git a/lib/libimhex/source/api/imhex_api.cpp b/lib/libimhex/source/api/imhex_api.cpp index 942c1e27d..9b52e914b 100644 --- a/lib/libimhex/source/api/imhex_api.cpp +++ b/lib/libimhex/source/api/imhex_api.cpp @@ -335,8 +335,16 @@ namespace hex { namespace ImHexApi::System { + namespace impl { + // default to true means we forward to ourselves by default + static bool s_isMainInstance = true; + + void setMainInstanceStatus(bool status) { + s_isMainInstance = status; + } + static ImVec2 s_mainWindowPos; static ImVec2 s_mainWindowSize; void setMainWindowPosition(i32 x, i32 y) { @@ -364,13 +372,6 @@ namespace hex { } - static ProgramArguments s_programArguments; - void setProgramArguments(int argc, char **argv, char **envp) { - s_programArguments.argc = argc; - s_programArguments.argv = argv; - s_programArguments.envp = envp; - } - static bool s_borderlessWindowMode; void setBorderlessWindowMode(bool enabled) { s_borderlessWindowMode = enabled; @@ -405,6 +406,10 @@ namespace hex { } + bool isMainInstance() { + return impl::s_isMainInstance; + } + void closeImHex(bool noQuestions) { EventManager::post(noQuestions); } @@ -418,25 +423,6 @@ namespace hex { EventManager::post(u32(state), u32(type), progress); } - const ProgramArguments &getProgramArguments() { - return impl::s_programArguments; - } - - std::optional getProgramArgument(int index) { - if (index >= impl::s_programArguments.argc) { - return std::nullopt; - } - - #if defined(OS_WINDOWS) - std::wstring wideArg = ::CommandLineToArgvW(::GetCommandLineW(), &impl::s_programArguments.argc)[index]; - std::string byteArg = std::wstring_convert, wchar_t>().to_bytes(wideArg); - - return std::u8string(byteArg.begin(), byteArg.end()); - #else - return std::u8string(reinterpret_cast(impl::s_programArguments.argv[index])); - #endif - } - static float s_targetFPS = 14.0F; @@ -621,4 +607,36 @@ namespace hex { } + namespace ImHexApi::Messaging { + + namespace impl { + + std::map &getHandlers() { + static std::map handlers; + + return handlers; + } + + void runHandler(const std::string &eventName, const std::vector &args) { + const auto& handlers = impl::getHandlers(); + auto matchHandler = handlers.find(eventName); + + if (matchHandler == handlers.end()) { + log::error("Forward event handler {} not found", eventName); + } else { + matchHandler->second(args); + } + + } + + } + + void registerHandler(const std::string &eventName, const impl::MessagingHandler &handler) { + log::debug("Registered new forward event handler: {}", eventName); + + impl::getHandlers().insert({ eventName, handler }); + } + + } + } diff --git a/lib/libimhex/source/api/plugin_manager.cpp b/lib/libimhex/source/api/plugin_manager.cpp index e55ae6ee1..7266fd54c 100644 --- a/lib/libimhex/source/api/plugin_manager.cpp +++ b/lib/libimhex/source/api/plugin_manager.cpp @@ -35,6 +35,7 @@ namespace hex { this->m_getCompatibleVersionFunction = getPluginFunction("getCompatibleVersion"); this->m_setImGuiContextFunction = getPluginFunction("setImGuiContext"); this->m_isBuiltinPluginFunction = getPluginFunction("isBuiltinPlugin"); + this->m_getSubCommandsFunction = getPluginFunction("getSubCommands"); } Plugin::Plugin(Plugin &&other) noexcept { @@ -48,6 +49,7 @@ namespace hex { this->m_getCompatibleVersionFunction = other.m_getCompatibleVersionFunction; this->m_setImGuiContextFunction = other.m_setImGuiContextFunction; this->m_isBuiltinPluginFunction = other.m_isBuiltinPluginFunction; + this->m_getSubCommandsFunction = other.m_getSubCommandsFunction; other.m_handle = nullptr; other.m_initializePluginFunction = nullptr; @@ -57,6 +59,7 @@ namespace hex { other.m_getCompatibleVersionFunction = nullptr; other.m_setImGuiContextFunction = nullptr; other.m_isBuiltinPluginFunction = nullptr; + other.m_getSubCommandsFunction = nullptr; } Plugin::~Plugin() { @@ -141,6 +144,14 @@ namespace hex { return this->m_initialized; } + std::span Plugin::getSubCommands() const { + if (this->m_getSubCommandsFunction != nullptr) { + auto result = this->m_getSubCommandsFunction(); + return { result->subCommands, result->size }; + } else + return { }; + } + void *Plugin::getPluginFunction(const std::string &symbol) { #if defined(OS_WINDOWS) diff --git a/lib/libimhex/source/subcommands/subcommands.cpp b/lib/libimhex/source/subcommands/subcommands.cpp new file mode 100644 index 000000000..1e496ef9f --- /dev/null +++ b/lib/libimhex/source/subcommands/subcommands.cpp @@ -0,0 +1,120 @@ +#include +#include +#include +#include +#include + +#include "hex/subcommands/subcommands.hpp" + +#include +#include +#include +#include + +namespace hex::subcommands { + + std::optional findSubCommand(const std::string &arg) { + for (auto &plugin : PluginManager::getPlugins()) { + for (auto &subCommand : plugin.getSubCommands()) { + if (hex::format("--{}", subCommand.commandKey) == arg) { + return subCommand; + } + } + } + return std::nullopt; + } + + void processArguments(const std::vector &args) { + // If no arguments, do not even try to process arguments + // (important because this function will exit ImHex if an instance is already opened, + // and we don't want that if no arguments were provided) + if (args.empty()) return; + + std::vector>> subCommands; + + auto argsIter = args.begin(); + + // get subcommand associated with the first argument + std::optional currentSubCommand = findSubCommand(*argsIter); + + if (currentSubCommand) { + argsIter++; + // if it is a valid subcommand, remove it from the argument list + } else { + // if no (valid) subcommand was provided, the default one is --open + currentSubCommand = findSubCommand("--open"); + } + + // arguments of the current subcommand + std::vector currentSubCommandArgs; + + // compute all subcommands to run + while (argsIter != args.end()) { + const std::string &arg = *argsIter; + + if (arg == "--othercmd") { + // save command to run + if (currentSubCommand) { + subCommands.push_back({*currentSubCommand, currentSubCommandArgs}); + } + + currentSubCommand = std::nullopt; + currentSubCommandArgs = { }; + + } else if (currentSubCommand) { + // add current argument to the current command + currentSubCommandArgs.push_back(arg); + } else { + // get next subcommand from current argument + currentSubCommand = findSubCommand(arg); + if (!currentSubCommand) { + log::error("No subcommand named '{}' found", arg); + exit(EXIT_FAILURE); + } + } + + argsIter++; + } + + // save last command to run + if (currentSubCommand) { + subCommands.push_back({*currentSubCommand, currentSubCommandArgs}); + } + + // run the subcommands + for (auto& subCommandPair : subCommands) { + subCommandPair.first.callback(subCommandPair.second); + } + + // exit the process if its not the main instance (the commands have been forwarded to another instance) + if (!ImHexApi::System::isMainInstance()) { + exit(0); + } + } + + void forwardSubCommand(const std::string &cmdName, const std::vector &args) { + log::debug("Forwarding subcommand {} (maybe to us)", cmdName); + std::string dataStr = std::accumulate(args.begin(), args.end(), std::string("\0")); + + std::vector data(dataStr.begin(), dataStr.end()); + + EventManager::post(hex::format("command/{}", cmdName), data); + } + + void registerSubCommand(const std::string &cmdName, const ForwardCommandHandler &handler) { + log::debug("Registered new forward command handler: {}", cmdName); + + ImHexApi::Messaging::impl::getHandlers().insert({ hex::format("command/{}", cmdName), [handler](const std::vector &eventData){ + std::string str((const char*) eventData.data(), eventData.size()); + + std::vector args; + + for (const auto &arg_view : std::views::split(str, '\0')) { + std::string arg(arg_view.data(), arg_view.size()); + args.push_back(arg); + } + + handler(args); + }}); + } +} diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 8f0f0627a..65ed226e0 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -9,6 +9,11 @@ add_executable(main ${APPLICATION_TYPE} source/window/macos_window.cpp source/window/linux_window.cpp + source/messaging/common.cpp + source/messaging/linux.cpp + source/messaging/macos.cpp + source/messaging/win.cpp + source/init/splash_window.cpp source/init/tasks.cpp diff --git a/main/include/messaging.hpp b/main/include/messaging.hpp new file mode 100644 index 000000000..779fb31d5 --- /dev/null +++ b/main/include/messaging.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#include + +/** + * Cross-instance (cross-process) messaging system + * As of now, this system may not be stable for use beyond its current use: + * forwarding providers opened in new instances + */ +namespace hex::messaging { + + /** + * @brief Setup everything to be able to send/receive messages + */ + void setupMessaging(); + + /** + * @brief Internal method - setup platform-specific things to be able to send messages + * @return if this instance has been determined to be the main instance + */ + bool setupNative(); + + /** + * @brief Internal method - send a message to another Imhex instance in a platform-specific way + */ + void sendToOtherInstance(const std::string &eventName, const std::vector &args); + + /** + * @brief Internal method - called by platform-specific code when a event has been received + */ + void messageReceived(const std::string &eventName, const std::vector &args); +} diff --git a/main/source/init/tasks.cpp b/main/source/init/tasks.cpp index ca8641e0a..e6801bdb5 100644 --- a/main/source/init/tasks.cpp +++ b/main/source/init/tasks.cpp @@ -357,6 +357,7 @@ namespace hex::init { ImHexApi::HexEditor::impl::getTooltipFunctions().clear(); ImHexApi::System::getAdditionalFolderPaths().clear(); ImHexApi::System::getCustomFontPath().clear(); + ImHexApi::Messaging::impl::getHandlers().clear(); ContentRegistry::Settings::impl::getEntries().clear(); ContentRegistry::Settings::impl::getSettingsData().clear(); @@ -418,11 +419,6 @@ namespace hex::init { } bool loadPlugins() { - // Load plugins - for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Plugins)) { - PluginManager::load(dir); - } - // Get loaded plugins auto &plugins = PluginManager::getPlugins(); diff --git a/main/source/main.cpp b/main/source/main.cpp index 62100d507..352ceaf23 100644 --- a/main/source/main.cpp +++ b/main/source/main.cpp @@ -4,20 +4,41 @@ #include "window.hpp" #include "crash_handlers.hpp" +#include "messaging.hpp" #include "init/splash_window.hpp" #include "init/tasks.hpp" #include #include +#include +#include +#include +#include "hex/subcommands/subcommands.hpp" #include #include +using namespace hex; + +void loadPlugins() { + for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Plugins)) { + PluginManager::load(dir); + } +} + int main(int argc, char **argv, char **envp) { - using namespace hex; + Window::initNative(); + hex::crash::setupCrashHandlers(); - ImHexApi::System::impl::setProgramArguments(argc, argv, envp); + hex::unused(envp); + + std::vector args(argv + 1, argv + argc); + + loadPlugins(); + + hex::messaging::setupMessaging(); + hex::subcommands::processArguments(args); // Check if ImHex is installed in portable mode { @@ -30,14 +51,14 @@ int main(int argc, char **argv, char **envp) { } bool shouldRestart = false; + // Register an event to handle restarting of ImHex + EventManager::subscribe([&]{ shouldRestart = true; }); + do { - // Register an event to handle restarting of ImHex - EventManager::subscribe([&]{ shouldRestart = true; }); shouldRestart = false; // Initialization { - Window::initNative(); log::info("Welcome to ImHex {}!", ImHexApi::System::getImHexVersion()); log::info("Compiled using commit {}@{}", ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash()); @@ -69,14 +90,6 @@ int main(int argc, char **argv, char **envp) { // Main window { Window window; - if (argc == 1) - ; // No arguments provided - else if (argc >= 2) { - for (auto i = 1; i < argc; i++) { - if (auto argument = ImHexApi::System::getProgramArgument(i); argument.has_value()) - EventManager::post(argument.value()); - } - } // Open file that has been requested to be opened through other, OS-specific means if (auto path = hex::getInitialFilePath(); path.has_value()) { diff --git a/main/source/messaging/common.cpp b/main/source/messaging/common.cpp new file mode 100644 index 000000000..61cef71c0 --- /dev/null +++ b/main/source/messaging/common.cpp @@ -0,0 +1,33 @@ +#include + +#include +#include +#include + +#include "messaging.hpp" + +namespace hex::messaging { + + void messageReceived(const std::string &eventName, const std::vector &eventData) { + log::debug("Received event '{}' with size {}", eventName, eventData.size()); + ImHexApi::Messaging::impl::runHandler(eventName, eventData); + } + + void setupEvents() { + EventManager::subscribe([](const std::string eventName, const std::vector &eventData) { + log::debug("Forwarding message {} (maybe to us)", eventName); + if (ImHexApi::System::isMainInstance()) { + EventManager::subscribe([eventName, eventData](){ + ImHexApi::Messaging::impl::runHandler(eventName, eventData); + }); + } else { + sendToOtherInstance(eventName, eventData); + } + }); + } + + void setupMessaging() { + ImHexApi::System::impl::setMainInstanceStatus(setupNative()); + setupEvents(); + } +} diff --git a/main/source/messaging/linux.cpp b/main/source/messaging/linux.cpp new file mode 100644 index 000000000..74cac4fef --- /dev/null +++ b/main/source/messaging/linux.cpp @@ -0,0 +1,24 @@ +#if defined(OS_LINUX) + +#include + +#include +#include + +#include "messaging.hpp" + +namespace hex::messaging { + + void sendToOtherInstance(const std::string &eventName, const std::vector &args) { + hex::unused(eventName); + hex::unused(args); + log::error("Not implemented function sendToOtherInstance() called"); + } + + // Not implemented, so lets say we are the main instance every time so events are forwarded to ourselves + bool setupNative() { + return true; + } +} + +#endif diff --git a/main/source/messaging/macos.cpp b/main/source/messaging/macos.cpp new file mode 100644 index 000000000..9a0a21d52 --- /dev/null +++ b/main/source/messaging/macos.cpp @@ -0,0 +1,24 @@ +#if defined(OS_MACOS) + +#include + +#include +#include + +#include "messaging.hpp" + +namespace hex::messaging { + + void sendToOtherInstance(const std::string &eventName, const std::vector &args) { + hex::unused(eventName); + hex::unused(args); + log::error("Not implemented function sendToOtherInstance() called"); + } + + // Not implemented, so lets say we are the main instance every time so events are forwarded to ourselves + bool setupNative() { + return true; + } +} + +#endif diff --git a/main/source/messaging/win.cpp b/main/source/messaging/win.cpp new file mode 100644 index 000000000..aea59cb31 --- /dev/null +++ b/main/source/messaging/win.cpp @@ -0,0 +1,84 @@ +#if defined(OS_WINDOWS) + +#include "messaging.hpp" + +#include +#include + +#include + +namespace hex::messaging { + + std::optional getImHexWindow() { + + HWND imhexWindow = 0; + + ::EnumWindows([](HWND hWnd, LPARAM ret) -> BOOL { + // Get the window name + auto length = ::GetWindowTextLength(hWnd); + std::string windowName(length + 1, '\x00'); + ::GetWindowText(hWnd, windowName.data(), windowName.size()); + + // Check if the window is visible and if it's an ImHex window + if (::IsWindowVisible(hWnd) == TRUE && length != 0) { + if (windowName.starts_with("ImHex")) { + // it's our window, return it and stop iteration + *reinterpret_cast(ret) = hWnd; + return FALSE; + } + } + + // continue iteration + return TRUE; + + }, reinterpret_cast(&imhexWindow)); + + if (imhexWindow == 0) return { }; + else return imhexWindow; + } + + void sendToOtherInstance(const std::string &eventName, const std::vector &eventData) { + log::debug("Sending event {} to another instance (not us)", eventName); + + // Get the window we want to send it to + HWND imHexWindow = *getImHexWindow(); + + // Create the message + // TODO actually send all arguments and not just the eventName + + std::vector fulleventData(eventName.begin(), eventName.end()); + fulleventData.push_back('\0'); + + fulleventData.insert(fulleventData.end(), eventData.begin(), eventData.end()); + + u8 *data = &fulleventData[0]; + DWORD dataSize = static_cast(fulleventData.size()); + + COPYDATASTRUCT message = { + .dwData = 0, + .cbData = dataSize, + .lpData = data + }; + + // Send the message + SendMessage(imHexWindow, WM_COPYDATA, reinterpret_cast(imHexWindow), reinterpret_cast(&message)); + + } + + bool setupNative() { + + constexpr static auto UniqueMutexId = "ImHex/a477ea68-e334-4d07-a439-4f159c683763"; + + // check if an ImHex instance is already running by opening a global mutex + HANDLE globalMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, UniqueMutexId); + if (globalMutex == nullptr) { + // If no ImHex instance is running, create a new global mutex + globalMutex = CreateMutex(nullptr, FALSE, UniqueMutexId); + return true; + } else { + return false; + } + } +} + +#endif diff --git a/main/source/window/win_window.cpp b/main/source/window/win_window.cpp index a4430219d..8370469a6 100644 --- a/main/source/window/win_window.cpp +++ b/main/source/window/win_window.cpp @@ -1,5 +1,7 @@ #include "window.hpp" +#include "messaging.hpp" + #include #if defined(OS_WINDOWS) @@ -53,12 +55,28 @@ namespace hex { auto message = reinterpret_cast(lParam); if (message == nullptr) break; - auto data = reinterpret_cast(message->lpData); - if (data == nullptr) break; + ssize_t nullIndex = -1; - std::fs::path path = data; - log::info("Opening file in existing instance: {}", wolv::util::toUTF8String(path)); - EventManager::post(path); + char* messageData = reinterpret_cast(message->lpData); + size_t messageSize = message->cbData; + + for (size_t i = 0; i < messageSize; i++) { + if (messageData[i] == '\0') { + nullIndex = i; + break; + } + } + + if (nullIndex == -1) { + log::warn("Received invalid forwarded event"); + break; + } + + std::string eventName(messageData, nullIndex); + + std::vector eventData(messageData + nullIndex + 1, messageData + messageSize); + + hex::messaging::messageReceived(eventName, eventData); break; } case WM_SETTINGCHANGE: { @@ -214,6 +232,24 @@ namespace hex { void Window::initNative() { + HWND consoleWindow = ::GetConsoleWindow(); + DWORD processId = 0; + ::GetWindowThreadProcessId(consoleWindow, &processId); + if (GetCurrentProcessId() == processId) { + ShowWindow(consoleWindow, SW_HIDE); + FreeConsole(); + log::impl::redirectToFile(); + } else { + 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); + } + } + } + ImHexApi::System::impl::setBorderlessWindowMode(true); // Add plugin library folders to dll search path @@ -226,83 +262,6 @@ namespace hex { // We redirect stderr to NUL to prevent this freopen("NUL:", "w", stderr); setvbuf(stderr, nullptr, _IONBF, 0); - - // Attach to parent console if one exists - bool result = AttachConsole(ATTACH_PARENT_PROCESS) == TRUE; - - #if defined(DEBUG) - if (::GetLastError() == ERROR_INVALID_HANDLE) { - result = AllocConsole() == TRUE; - } - #endif - - if (result) { - // Redirect stdin and stdout to that new console - freopen("CONIN$", "r", stdin); - freopen("CONOUT$", "w", stdout); - setvbuf(stdin, nullptr, _IONBF, 0); - setvbuf(stdout, nullptr, _IONBF, 0); - - fmt::print("\n"); - - // Enable color format specifiers in console - { - 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); - } - } - } - } else { - log::impl::redirectToFile(); - } - - // Open new files in already existing ImHex instance - constexpr static auto UniqueMutexId = "ImHex/a477ea68-e334-4d07-a439-4f159c683763"; - - HANDLE globalMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, UniqueMutexId); - if (globalMutex == nullptr) { - // If no ImHex instance is running, create a new global mutex - globalMutex = CreateMutex(nullptr, FALSE, UniqueMutexId); - } else { - // If an ImHex instance is already running, send the file path to it and exit - - if (ImHexApi::System::getProgramArguments().argc > 1) { - // Find the ImHex Window and send the file path as a message to it - ::EnumWindows([](HWND hWnd, LPARAM) -> BOOL { - auto &programArgs = ImHexApi::System::getProgramArguments(); - - // Get the window name - auto length = ::GetWindowTextLength(hWnd); - std::string windowName(length + 1, '\x00'); - ::GetWindowText(hWnd, windowName.data(), windowName.size()); - - // Check if the window is visible and if it's an ImHex window - if (::IsWindowVisible(hWnd) == TRUE && length != 0) { - if (windowName.starts_with("ImHex")) { - // Create the message - COPYDATASTRUCT message = { - .dwData = 0, - .cbData = static_cast(std::strlen(programArgs.argv[1])) + 1, - .lpData = programArgs.argv[1] - }; - - // Send the message - SendMessage(hWnd, WM_COPYDATA, reinterpret_cast(hWnd), reinterpret_cast(&message)); - - return FALSE; - } - } - - return TRUE; - }, 0); - - std::exit(0); - } - } } void Window::setupNativeWindow() { diff --git a/plugins/builtin/romfs/logo.ans b/plugins/builtin/romfs/logo.ans new file mode 100644 index 000000000..1d2868217 --- /dev/null +++ b/plugins/builtin/romfs/logo.ans @@ -0,0 +1,21 @@ +ImHex Hex Editor + A Hex Editor for Reverse Engineers, Programmers and people + who value their retinas when working at 3 AM. +                                                +   ((((((((((((((((((((/   (((((((((((((((/   +   ((((((((((((((((((((/   (((((((((((((((/   +   *,,,,,        ,,,,,,*             ,,,,,*   Version: {0} +   ((((//        (((((//             (((((/   Commit: {1}@{2} +   (/////        (//////             ((((//   Build time: {3}, {4} +   /////*        //////*             //////   Type: {5} +   /////*        //////*             /////*   +   /*****        /////**             /////*   +   ******        *******             /*****   +   *****,        ******,             ******   +   **,,,,        *****,,             *****,   +   *,,,,,        *,,,,,,             ****,,   +   ,,,,,,        ,,,,,,,             *,,,,,   +   ////////////////////*             ,,,,,,,   +   *********************             ,.....    +                                                + \ No newline at end of file diff --git a/plugins/builtin/source/plugin_builtin.cpp b/plugins/builtin/source/plugin_builtin.cpp index f36207d96..a4f96a755 100644 --- a/plugins/builtin/source/plugin_builtin.cpp +++ b/plugins/builtin/source/plugin_builtin.cpp @@ -1,11 +1,15 @@ #include +#include +#include #include #include #include #include +using namespace hex; + namespace hex::plugin::builtin { void registerEventHandlers(); @@ -41,8 +45,69 @@ namespace hex::plugin::builtin { void handleBorderlessWindowMode(); + void handleSubCommands(); + } +IMHEX_PLUGIN_SUBCOMMANDS() { + { "version", "Print ImHex version and exit", [](const std::vector&) { + hex::print(romfs::get("logo.ans").string(), + ImHexApi::System::getImHexVersion(), + ImHexApi::System::getCommitBranch(), ImHexApi::System::getCommitHash(), + __DATE__, __TIME__, + ImHexApi::System::isPortableVersion() ? "Portable" : "Installed"); + + exit(EXIT_SUCCESS); + }}, + { "help", "Print help about this command and exit", [](const std::vector&) { + hex::print( + "ImHex - A Hex Editor for Reverse Engineers, Programmers and people who value their retinas when working at 3 AM.\n" + "\n" + "usage: imhex [subcommand] [options]\n" + "Available subcommands:\n" + ); + + size_t longestCommand = 0; + for (const auto &plugin : PluginManager::getPlugins()) { + for (const auto &subCommand : plugin.getSubCommands()) { + longestCommand = std::max(longestCommand, subCommand.commandKey.size()); + } + } + + for (const auto &plugin : PluginManager::getPlugins()) { + for (const auto &subCommand : plugin.getSubCommands()) { + hex::print(" --{}{: <{}} {}\n", subCommand.commandKey, "", longestCommand - subCommand.commandKey.size(), subCommand.commandDesc); + } + } + + exit(EXIT_SUCCESS); + }}, + { "open", "Open files passed as argument. This is the default subcommand is none is entered", + [](const std::vector &args) { + + hex::subcommands::registerSubCommand("open", [](const std::vector &args){ + for (auto &arg : args) { + EventManager::post(arg); + } + }); + + std::vector fullPaths; + bool doubleDashFound = false; + for (auto &arg : args) { + + // Skip the first argument named `--` + if (arg == "--" && !doubleDashFound) { + doubleDashFound = true; + } else { + auto path = std::filesystem::weakly_canonical(arg); + fullPaths.push_back(wolv::util::toUTF8String(path)); + } + } + + hex::subcommands::forwardSubCommand("open", fullPaths); + }} +}; + IMHEX_PLUGIN_SETUP("Built-in", "WerWolv", "Default ImHex functionality") { using namespace hex::plugin::builtin;