mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-04-02 13:37:42 -05:00
feat: Added command line interface support (#1172)
System design has been discussed on discord Should fix #948 --------- Co-authored-by: WerWolv <werwolv98@gmail.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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<u8>&);
|
||||
}
|
||||
@@ -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<std::u8string> 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<void(const std::vector<u8> &)>;
|
||||
|
||||
std::map<std::string, MessagingHandler> &getHandlers();
|
||||
|
||||
void runHandler(const std::string &eventName, const std::vector<u8> &args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Register the handler for this specific event name
|
||||
*/
|
||||
void registerHandler(const std::string &eventName, const impl::MessagingHandler &handler);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
#include <hex/helpers/fs.hpp>
|
||||
|
||||
#include <span>
|
||||
#include <string>
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
@@ -17,6 +18,17 @@ struct ImGuiContext;
|
||||
|
||||
namespace hex {
|
||||
|
||||
struct SubCommand {
|
||||
std::string commandKey;
|
||||
std::string commandDesc;
|
||||
std::function<void(const std::vector<std::string>&)> 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<SubCommand> 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<typename T>
|
||||
[[nodiscard]] auto getPluginFunction(const std::string &symbol) {
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
|
||||
#include <hex.hpp>
|
||||
#include <hex/api/plugin_manager.hpp>
|
||||
|
||||
#include <wolv/utils/string.hpp>
|
||||
|
||||
/**
|
||||
* 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<hex::SubCommand> g_subCommands; \
|
||||
extern "C" [[gnu::visibility("default")]] hex::SubCommandList getSubCommands() { \
|
||||
return hex::SubCommandList { \
|
||||
g_subCommands.data(), \
|
||||
g_subCommands.size() \
|
||||
}; \
|
||||
} \
|
||||
std::vector<hex::SubCommand> g_subCommands
|
||||
|
||||
30
lib/libimhex/include/hex/subcommands/subcommands.hpp
Normal file
30
lib/libimhex/include/hex/subcommands/subcommands.hpp
Normal file
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#include<vector>
|
||||
#include<string>
|
||||
#include<functional>
|
||||
|
||||
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<std::string> &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<std::string> &args);
|
||||
|
||||
using ForwardCommandHandler = std::function<void(const std::vector<std::string> &)>;
|
||||
|
||||
/**
|
||||
* @brief Register the handler for this specific command name
|
||||
*/
|
||||
void registerSubCommand(const std::string &cmdName, const ForwardCommandHandler &handler);
|
||||
}
|
||||
@@ -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<RequestCloseImHex>(noQuestions);
|
||||
}
|
||||
@@ -418,25 +423,6 @@ namespace hex {
|
||||
EventManager::post<EventSetTaskBarIconState>(u32(state), u32(type), progress);
|
||||
}
|
||||
|
||||
const ProgramArguments &getProgramArguments() {
|
||||
return impl::s_programArguments;
|
||||
}
|
||||
|
||||
std::optional<std::u8string> 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<std::codecvt_utf8_utf16<wchar_t>, wchar_t>().to_bytes(wideArg);
|
||||
|
||||
return std::u8string(byteArg.begin(), byteArg.end());
|
||||
#else
|
||||
return std::u8string(reinterpret_cast<const char8_t *>(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<std::string, MessagingHandler> &getHandlers() {
|
||||
static std::map<std::string, MessagingHandler> handlers;
|
||||
|
||||
return handlers;
|
||||
}
|
||||
|
||||
void runHandler(const std::string &eventName, const std::vector<u8> &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 });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ namespace hex {
|
||||
this->m_getCompatibleVersionFunction = getPluginFunction<GetCompatibleVersionFunc>("getCompatibleVersion");
|
||||
this->m_setImGuiContextFunction = getPluginFunction<SetImGuiContextFunc>("setImGuiContext");
|
||||
this->m_isBuiltinPluginFunction = getPluginFunction<IsBuiltinPluginFunc>("isBuiltinPlugin");
|
||||
this->m_getSubCommandsFunction = getPluginFunction<GetSubCommandsFunc>("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<SubCommand> 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)
|
||||
|
||||
120
lib/libimhex/source/subcommands/subcommands.cpp
Normal file
120
lib/libimhex/source/subcommands/subcommands.cpp
Normal file
@@ -0,0 +1,120 @@
|
||||
#include<iostream>
|
||||
#include<numeric>
|
||||
#include<string_view>
|
||||
#include<ranges>
|
||||
#include<stdlib.h>
|
||||
|
||||
#include "hex/subcommands/subcommands.hpp"
|
||||
|
||||
#include <hex/api/event.hpp>
|
||||
#include <hex/api/plugin_manager.hpp>
|
||||
#include <hex/api/imhex_api.hpp>
|
||||
#include <hex/helpers/logger.hpp>
|
||||
|
||||
namespace hex::subcommands {
|
||||
|
||||
std::optional<SubCommand> 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<std::string> &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<std::pair<SubCommand, std::vector<std::string>>> subCommands;
|
||||
|
||||
auto argsIter = args.begin();
|
||||
|
||||
// get subcommand associated with the first argument
|
||||
std::optional<SubCommand> 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<std::string> 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<std::string> &args) {
|
||||
log::debug("Forwarding subcommand {} (maybe to us)", cmdName);
|
||||
std::string dataStr = std::accumulate(args.begin(), args.end(), std::string("\0"));
|
||||
|
||||
std::vector<u8> data(dataStr.begin(), dataStr.end());
|
||||
|
||||
EventManager::post<SendMessageToMainInstance>(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<u8> &eventData){
|
||||
std::string str((const char*) eventData.data(), eventData.size());
|
||||
|
||||
std::vector<std::string> 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);
|
||||
}});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user