From 691b56b4acbfe96f57aadc53481e133e4812cf12 Mon Sep 17 00:00:00 2001 From: WerWolv Date: Thu, 25 Dec 2025 15:34:56 +0100 Subject: [PATCH] impr: Show OS toast popup when a task finishes while ImHex is not focused --- lib/libimhex/include/hex/api/task_manager.hpp | 2 ++ lib/libimhex/source/api/task_manager.cpp | 16 ++++++++++++++++ lib/libimhex/source/helpers/utils_macos.m | 15 +++++++++++++++ plugins/builtin/romfs/lang/en_US.json | 1 + plugins/builtin/source/content/events.cpp | 12 ++++++++++++ 5 files changed, 46 insertions(+) diff --git a/lib/libimhex/include/hex/api/task_manager.hpp b/lib/libimhex/include/hex/api/task_manager.hpp index 763c02829..881721e3e 100644 --- a/lib/libimhex/include/hex/api/task_manager.hpp +++ b/lib/libimhex/include/hex/api/task_manager.hpp @@ -252,6 +252,8 @@ EXPORT_MODULE namespace hex { static const std::list>& getRunningTasks(); static void runDeferredCalls(); + static void addTaskCompletionCallback(const std::function& function); + private: static TaskHolder createTask(const UnlocalizedString &unlocalizedName, u64 maxValue, bool background, bool blocking, std::function function); }; diff --git a/lib/libimhex/source/api/task_manager.cpp b/lib/libimhex/source/api/task_manager.cpp index 30e1daf60..a51a6a541 100644 --- a/lib/libimhex/source/api/task_manager.cpp +++ b/lib/libimhex/source/api/task_manager.cpp @@ -55,6 +55,7 @@ namespace hex { std::list> s_deferredCalls; std::unordered_map> s_onceDeferredCalls; std::list> s_tasksFinishedCallbacks; + std::list> s_taskCompletionCallbacks; std::mutex s_queueMutex; std::condition_variable s_jobCondVar; @@ -322,6 +323,13 @@ namespace hex { task->m_function(*task); log::debug("Task '{}' finished", task->m_unlocalizedName.get()); + + { + std::scoped_lock lock(s_tasksFinishedMutex); + + for (const auto &callback : s_taskCompletionCallbacks) + callback(*task); + } } catch (const Task::TaskInterruptor &) { // Handle the task being interrupted by user request task->interruption(); @@ -580,5 +588,13 @@ namespace hex { return s_mainThreadId == std::this_thread::get_id(); } + void TaskManager::addTaskCompletionCallback(const std::function &function) { + std::scoped_lock lock(s_tasksFinishedMutex); + for (const auto &task : s_tasks) { + task->interrupt(); + } + + s_taskCompletionCallbacks.push_back(function); + } } diff --git a/lib/libimhex/source/helpers/utils_macos.m b/lib/libimhex/source/helpers/utils_macos.m index c5e284969..2a941309b 100644 --- a/lib/libimhex/source/helpers/utils_macos.m +++ b/lib/libimhex/source/helpers/utils_macos.m @@ -416,6 +416,21 @@ return [bundlePath.pathExtension.lowercaseString isEqualToString:@"app"]; } + @interface NotificationDelegate : NSObject + @end + + @implementation NotificationDelegate + + - (void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler { + completionHandler(UNNotificationPresentationOptionBanner | + UNNotificationPresentationOptionSound | + UNNotificationPresentationOptionList); + } + + @end + void toastMessageMacos(const char *title, const char *message) { @autoreleasepool { // Only show notification if we're inside a bundle diff --git a/plugins/builtin/romfs/lang/en_US.json b/plugins/builtin/romfs/lang/en_US.json index 75102a871..cacded47e 100644 --- a/plugins/builtin/romfs/lang/en_US.json +++ b/plugins/builtin/romfs/lang/en_US.json @@ -75,6 +75,7 @@ "hex.builtin.command.web.desc": "Website lookup", "hex.builtin.command.web.result": "Navigate to '{0}'", "hex.builtin.drag_drop.text": "Drop files here to open them...", + "hex.builtin.os_toast_message.task_finished": "Task '{0}' finished", "hex.builtin.inspector.ascii": "char", "hex.builtin.inspector.binary": "Binary", "hex.builtin.inspector.bfloat16": "bfloat16", diff --git a/plugins/builtin/source/content/events.cpp b/plugins/builtin/source/content/events.cpp index 7a88baad1..a5b810ab3 100644 --- a/plugins/builtin/source/content/events.cpp +++ b/plugins/builtin/source/content/events.cpp @@ -206,6 +206,13 @@ namespace hex::plugin::builtin { ui::ToastError::open(fmt::format("hex.builtin.popup.error.project.load"_lang, wolv::util::toUTF8String(path))); } }); + } else if (name == "Open Folder") { + fs::openFileBrowser(fs::DialogMode::Folder, { }, + [](const auto &path) { + if (!ProjectFile::load(path)) { + ui::ToastError::open(fmt::format("hex.builtin.popup.error.project.load"_lang, wolv::util::toUTF8String(path))); + } + }); } }); @@ -420,6 +427,11 @@ namespace hex::plugin::builtin { #endif }); + TaskManager::addTaskCompletionCallback([](Task &task) { + if (!glfwGetWindowAttrib(ImHexApi::System::getMainWindowHandle(), GLFW_FOCUSED) && !task.isBackgroundTask()) + hex::showToastMessage("ImHex", fmt::format("hex.builtin.os_toast_message.task_finished"_lang, Lang(task.getUnlocalizedName()))); + }); + } }