#pragma once #include #include #include #include #include #include #include #include #include #define EVENT_DEF_IMPL(event_name, event_name_string, should_log, ...) \ struct event_name final : public hex::impl::Event<__VA_ARGS__> { \ constexpr static auto Id = [] { return hex::impl::EventId(event_name_string); }(); \ constexpr static auto ShouldLog = (should_log); \ explicit event_name(Callback func) noexcept : Event(std::move(func)) { } \ \ static EventManager::EventList::iterator subscribe(Event::Callback function) { return EventManager::subscribe(function); } \ static void subscribe(void *token, Event::Callback function) { EventManager::subscribe(token, function); } \ static void unsubscribe(const EventManager::EventList::iterator &token) noexcept { EventManager::unsubscribe(token); } \ static void unsubscribe(void *token) noexcept { EventManager::unsubscribe(token); } \ static void post(auto &&...args) { EventManager::post(std::forward(args)...); } \ }; #define EVENT_DEF(event_name, ...) EVENT_DEF_IMPL(event_name, #event_name, true, __VA_ARGS__) #define EVENT_DEF_NO_LOG(event_name, ...) EVENT_DEF_IMPL(event_name, #event_name, false, __VA_ARGS__) /* Forward declarations */ struct GLFWwindow; namespace hex { class Achievement; class View; } namespace pl::ptrn { class Pattern; } namespace hex { namespace impl { class EventId { public: explicit constexpr EventId(const char *eventName) { m_hash = 0x811C'9DC5; for (const char c : std::string_view(eventName)) { m_hash = (m_hash >> 5) | (m_hash << 27); m_hash ^= c; } } constexpr bool operator==(const EventId &other) const { return m_hash == other.m_hash; } private: u32 m_hash; }; struct EventBase { EventBase() noexcept = default; virtual ~EventBase() = default; }; template struct Event : EventBase { using Callback = std::function; explicit Event(Callback func) noexcept : m_func(std::move(func)) { } void operator()(std::string_view eventName, Params... params) const { try { m_func(params...); } catch (const std::exception &e) { log::error("An exception occurred while handling event {}: {}", eventName, e.what()); throw; } } private: Callback m_func; }; template concept EventType = std::derived_from; } /** * @brief The EventManager allows subscribing to and posting events to different parts of the program. * To create a new event, use the EVENT_DEF macro. This will create a new event type with the given name and parameters */ class EventManager { public: using EventList = std::list>>; /** * @brief Subscribes to an event * @tparam E Event * @param function Function to call when the event is posted * @return Token to unsubscribe from the event */ template static EventList::iterator subscribe(typename E::Callback function) { std::scoped_lock lock(getEventMutex()); auto &events = getEvents(); return events.insert(events.end(), std::make_pair(E::Id, std::make_unique(function))); } /** * @brief Subscribes to an event * @tparam E Event * @param token Unique token to register the event to. Later required to unsubscribe again * @param function Function to call when the event is posted */ template static void subscribe(void *token, typename E::Callback function) { std::scoped_lock lock(getEventMutex()); if (getTokenStore().contains(token)) { auto&& [begin, end] = getTokenStore().equal_range(token); const auto eventRegistered = std::any_of(begin, end, [&](auto &item) { return item.second->first == E::Id; }); if (eventRegistered) { log::fatal("The token '{}' has already registered the same event ('{}')", token, wolv::type::getTypeName()); return; } } getTokenStore().insert({ token, subscribe(function) }); } /** * @brief Unsubscribes from an event * @param token Token returned by subscribe */ static void unsubscribe(const EventList::iterator &token) noexcept { std::scoped_lock lock(getEventMutex()); getEvents().erase(token); } /** * @brief Unsubscribes from an event * @tparam E Event * @param token Token passed to subscribe */ template static void unsubscribe(void *token) noexcept { std::scoped_lock lock(getEventMutex()); auto &tokenStore = getTokenStore(); auto iter = std::find_if(tokenStore.begin(), tokenStore.end(), [&](auto &item) { return item.first == token && item.second->first == E::Id; }); if (iter != tokenStore.end()) { getEvents().remove(*iter->second); tokenStore.erase(iter); } } /** * @brief Posts an event to all subscribers of it * @tparam E Event * @param args Arguments to pass to the event */ template static void post(auto && ...args) { std::scoped_lock lock(getEventMutex()); for (const auto &[id, event] : getEvents()) { if (id == E::Id) { (*static_cast(event.get()))(wolv::type::getTypeName(), std::forward(args)...); } } #if defined (DEBUG) if (E::ShouldLog) log::debug("Event posted: '{}'", wolv::type::getTypeName()); #endif } /** * @brief Unsubscribe all subscribers from all events */ static void clear() noexcept { std::scoped_lock lock(getEventMutex()); getEvents().clear(); getTokenStore().clear(); } private: static std::multimap& getTokenStore(); static EventList& getEvents(); static std::recursive_mutex& getEventMutex(); }; /* Default Events */ /** * @brief Called when Imhex finished startup, and will enter the main window rendering loop */ EVENT_DEF(EventImHexStartupFinished); EVENT_DEF(EventFileLoaded, std::fs::path); EVENT_DEF(EventDataChanged, prv::Provider *); EVENT_DEF(EventHighlightingChanged); EVENT_DEF(EventWindowClosing, GLFWwindow *); EVENT_DEF(EventRegionSelected, ImHexApi::HexEditor::ProviderRegion); EVENT_DEF(EventAbnormalTermination, int); EVENT_DEF(EventThemeChanged); EVENT_DEF(EventOSThemeChanged); EVENT_DEF(EventWindowFocused, bool); /** * @brief Called when the provider is created. * This event is responsible for (optionally) initializing the provider and calling EventProviderOpened * (although the event can also be called manually without problem) */ EVENT_DEF(EventProviderCreated, prv::Provider *); EVENT_DEF(EventProviderChanged, prv::Provider *, prv::Provider *); /** * @brief Called as a continuation of EventProviderCreated * this event is normally called immediately after EventProviderCreated successfully initialized the provider. * If no initialization (Provider::skipLoadInterface() has been set), this event should be called manually * If skipLoadInterface failed, this event is not called * * @note this is not related to Provider::open() */ EVENT_DEF(EventProviderOpened, prv::Provider *); EVENT_DEF(EventProviderClosing, prv::Provider *, bool *); EVENT_DEF(EventProviderClosed, prv::Provider *); EVENT_DEF(EventProviderDeleted, prv::Provider *); EVENT_DEF(EventProviderSaved, prv::Provider *); EVENT_DEF(EventWindowInitialized); EVENT_DEF(EventWindowDeinitializing, GLFWwindow *); EVENT_DEF(EventBookmarkCreated, ImHexApi::Bookmarks::Entry&); EVENT_DEF(EventPatchCreated, u64, u8, u8); EVENT_DEF(EventPatternEvaluating); EVENT_DEF(EventPatternExecuted, const std::string&); EVENT_DEF(EventPatternEditorChanged, const std::string&); EVENT_DEF(EventStoreContentDownloaded, const std::fs::path&); EVENT_DEF(EventStoreContentRemoved, const std::fs::path&); EVENT_DEF(EventImHexClosing); EVENT_DEF(EventAchievementUnlocked, const Achievement&); EVENT_DEF(EventSearchBoxClicked, u32); EVENT_DEF(EventViewOpened, View*); EVENT_DEF(EventFirstLaunch); EVENT_DEF(EventFileDragged, bool); EVENT_DEF(EventFileDropped, std::fs::path); EVENT_DEF(EventProviderDataModified, prv::Provider *, u64, u64, const u8*); EVENT_DEF(EventProviderDataInserted, prv::Provider *, u64, u64); EVENT_DEF(EventProviderDataRemoved, prv::Provider *, u64, u64); /** * @brief Called when a project has been loaded */ EVENT_DEF(EventProjectOpened); EVENT_DEF_NO_LOG(EventFrameBegin); 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(RequestHexEditorSelectionChange, Region); EVENT_DEF(RequestPatternEditorSelectionChange, u32, u32); EVENT_DEF(RequestJumpToPattern, const pl::ptrn::Pattern*); EVENT_DEF(RequestAddBookmark, Region, std::string, std::string, color_t, u64*); EVENT_DEF(RequestRemoveBookmark, u64); EVENT_DEF(RequestSetPatternLanguageCode, std::string); EVENT_DEF(RequestRunPatternCode); EVENT_DEF(RequestLoadPatternLanguageFile, std::fs::path); EVENT_DEF(RequestSavePatternLanguageFile, std::fs::path); EVENT_DEF(RequestUpdateWindowTitle); EVENT_DEF(RequestCloseImHex, bool); EVENT_DEF(RequestRestartImHex); EVENT_DEF(RequestOpenFile, std::fs::path); EVENT_DEF(RequestChangeTheme, std::string); EVENT_DEF(RequestOpenPopup, std::string); EVENT_DEF(RequestAddVirtualFile, std::fs::path, std::vector, Region); /** * @brief Creates a provider from it's unlocalized name, and add it to the provider list */ EVENT_DEF(RequestCreateProvider, std::string, bool, bool, hex::prv::Provider **); EVENT_DEF(RequestInitThemeHandlers); /** * @brief Send an event to the main Imhex instance */ EVENT_DEF(SendMessageToMainInstance, const std::string, const std::vector&); /** * Move the data from all PerProvider instances from one provider to another. * The 'from' provider should not have any per provider data after this, and should be immediately deleted */ EVENT_DEF(MovePerProviderData, prv::Provider *, prv::Provider *); /** * Called when ImHex managed to catch an error in a general try/catch to prevent/recover from a crash */ EVENT_DEF(EventCrashRecovered, const std::exception &); }