diff --git a/lib/libimhex/include/hex/api/events/events_lifecycle.hpp b/lib/libimhex/include/hex/api/events/events_lifecycle.hpp index fd65c026f..4a4b65417 100644 --- a/lib/libimhex/include/hex/api/events/events_lifecycle.hpp +++ b/lib/libimhex/include/hex/api/events/events_lifecycle.hpp @@ -69,4 +69,11 @@ namespace hex { */ EVENT_DEF(EventProjectOpened); + /** + * @brief Called when a native message was received from another ImHex instance + * @param eventType Type name of the event + * @param args Decoded arguments sent from other instance + */ + EVENT_DEF(EventNativeMessageReceived, std::string, std::vector); + } \ No newline at end of file diff --git a/lib/libimhex/include/hex/helpers/utils_macos.hpp b/lib/libimhex/include/hex/helpers/utils_macos.hpp index 12826a6cf..8fd672cec 100644 --- a/lib/libimhex/include/hex/helpers/utils_macos.hpp +++ b/lib/libimhex/include/hex/helpers/utils_macos.hpp @@ -24,6 +24,10 @@ void macosMarkContentEdited(GLFWwindow *window, bool edited = true); void macosGetKey(Keys key, int *output); + + bool macosIsMainInstance(); + void macosSendMessageToMainInstance(const unsigned char *data, size_t size); + void macosInstallEventListener(); } #endif diff --git a/lib/libimhex/source/helpers/utils.cpp b/lib/libimhex/source/helpers/utils.cpp index 1ab1084f6..af7ab785b 100644 --- a/lib/libimhex/source/helpers/utils.cpp +++ b/lib/libimhex/source/helpers/utils.cpp @@ -11,6 +11,7 @@ #include #include +#include #if defined(OS_WINDOWS) #include @@ -30,7 +31,6 @@ #endif namespace hex { - float operator""_scaled(long double value) { return value * ImHexApi::System::getGlobalScale(); } @@ -163,25 +163,25 @@ namespace hex { switch (unitIndex) { case 0: result += ((value == 1) ? " Byte" : " Bytes"); - break; + break; case 1: result += " kiB"; - break; + break; case 2: result += " MiB"; - break; + break; case 3: result += " GiB"; - break; + break; case 4: result += " TiB"; - break; + break; case 5: result += " PiB"; - break; + break; case 6: result += " EiB"; - break; + break; default: result = "A lot!"; } @@ -338,15 +338,15 @@ namespace hex { void startProgram(const std::string &command) { - #if defined(OS_WINDOWS) - std::ignore = system(hex::format("start {0}", command).c_str()); - #elif defined(OS_MACOS) - std::ignore = system(hex::format("open {0}", command).c_str()); - #elif defined(OS_LINUX) - executeCmd({"xdg-open", command}); - #elif defined(OS_WEB) - std::ignore = command; - #endif +#if defined(OS_WINDOWS) + std::ignore = system(hex::format("start {0}", command).c_str()); +#elif defined(OS_MACOS) + std::ignore = system(hex::format("open {0}", command).c_str()); +#elif defined(OS_LINUX) + executeCmd({"xdg-open", command}); +#elif defined(OS_WEB) + std::ignore = command; +#endif } int executeCommand(const std::string &command) { @@ -357,19 +357,19 @@ namespace hex { if (!url.contains("://")) url = "https://" + url; - #if defined(OS_WINDOWS) - ShellExecuteA(nullptr, "open", url.c_str(), nullptr, nullptr, SW_SHOWNORMAL); - #elif defined(OS_MACOS) - openWebpageMacos(url.c_str()); - #elif defined(OS_LINUX) - executeCmd({"xdg-open", url}); - #elif defined(OS_WEB) - EM_ASM({ - window.open(UTF8ToString($0), '_blank'); - }, url.c_str()); - #else - #warning "Unknown OS, can't open webpages" - #endif +#if defined(OS_WINDOWS) + ShellExecuteA(nullptr, "open", url.c_str(), nullptr, nullptr, SW_SHOWNORMAL); +#elif defined(OS_MACOS) + openWebpageMacos(url.c_str()); +#elif defined(OS_LINUX) + executeCmd({"xdg-open", url}); +#elif defined(OS_WEB) + EM_ASM({ + window.open(UTF8ToString($0), '_blank'); + }, url.c_str()); +#else +#warning "Unknown OS, can't open webpages" +#endif } std::optional hexCharToValue(char c) { @@ -391,31 +391,31 @@ namespace hex { switch (byte) { case '\\': result += "\\"; - break; + break; case '\a': result += "\\a"; - break; + break; case '\b': result += "\\b"; - break; + break; case '\f': result += "\\f"; - break; + break; case '\n': result += "\\n"; - break; + break; case '\r': result += "\\r"; - break; + break; case '\t': result += "\\t"; - break; + break; case '\v': result += "\\v"; - break; + break; default: result += hex::format("\\x{:02X}", byte); - break; + break; } } } @@ -442,46 +442,46 @@ namespace hex { switch (escapeChar) { case 'a': result.push_back('\a'); - break; + break; case 'b': result.push_back('\b'); - break; + break; case 'f': result.push_back('\f'); - break; + break; case 'n': result.push_back('\n'); - break; + break; case 'r': result.push_back('\r'); - break; + break; case 't': result.push_back('\t'); - break; + break; case 'v': result.push_back('\v'); - break; + break; case '\\': result.push_back('\\'); - break; + break; case 'x': - { - u8 byte = 0x00; - if ((offset + 1) >= string.length()) return {}; + { + u8 byte = 0x00; + if ((offset + 1) >= string.length()) return {}; - for (u8 i = 0; i < 2; i++) { - byte <<= 4; - if (auto hexValue = hexCharToValue(c()); hexValue.has_value()) - byte |= hexValue.value(); - else - return {}; + for (u8 i = 0; i < 2; i++) { + byte <<= 4; + if (auto hexValue = hexCharToValue(c()); hexValue.has_value()) + byte |= hexValue.value(); + else + return {}; - offset++; - } - - result.push_back(byte); + offset++; } - break; + + result.push_back(byte); + } + break; default: return {}; } @@ -650,27 +650,27 @@ namespace hex { } bool isProcessElevated() { - #if defined(OS_WINDOWS) - bool elevated = false; - HANDLE token = INVALID_HANDLE_VALUE; +#if defined(OS_WINDOWS) + bool elevated = false; + HANDLE token = INVALID_HANDLE_VALUE; - if (::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) { - TOKEN_ELEVATION elevation; - DWORD elevationSize = sizeof(TOKEN_ELEVATION); + if (::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) { + TOKEN_ELEVATION elevation; + DWORD elevationSize = sizeof(TOKEN_ELEVATION); - if (::GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &elevationSize)) - elevated = elevation.TokenIsElevated; - } + if (::GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &elevationSize)) + elevated = elevation.TokenIsElevated; + } - if (token != INVALID_HANDLE_VALUE) - ::CloseHandle(token); + if (token != INVALID_HANDLE_VALUE) + ::CloseHandle(token); - return elevated; - #elif defined(OS_LINUX) || defined(OS_MACOS) - return getuid() == 0 || getuid() != geteuid(); - #else - return false; - #endif + return elevated; +#elif defined(OS_LINUX) || defined(OS_MACOS) + return getuid() == 0 || getuid() != geteuid(); +#else + return false; +#endif } std::optional getEnvironmentVariable(const std::string &env) { @@ -806,45 +806,45 @@ namespace hex { } std::string formatSystemError(i32 error) { - #if defined(OS_WINDOWS) - wchar_t *message = nullptr; - auto wLength = FormatMessageW( - FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, - nullptr, error, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (wchar_t*)&message, 0, - nullptr - ); - ON_SCOPE_EXIT { LocalFree(message); }; +#if defined(OS_WINDOWS) + wchar_t *message = nullptr; + auto wLength = FormatMessageW( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, error, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (wchar_t*)&message, 0, + nullptr + ); + ON_SCOPE_EXIT { LocalFree(message); }; - auto length = ::WideCharToMultiByte(CP_UTF8, 0, message, wLength, nullptr, 0, nullptr, nullptr); - std::string result(length, '\x00'); - ::WideCharToMultiByte(CP_UTF8, 0, message, wLength, result.data(), length, nullptr, nullptr); + auto length = ::WideCharToMultiByte(CP_UTF8, 0, message, wLength, nullptr, 0, nullptr, nullptr); + std::string result(length, '\x00'); + ::WideCharToMultiByte(CP_UTF8, 0, message, wLength, result.data(), length, nullptr, nullptr); - return result; - #else - return std::system_category().message(error); - #endif + return result; +#else + return std::system_category().message(error); +#endif } void* getContainingModule(void* symbol) { - #if defined(OS_WINDOWS) - MEMORY_BASIC_INFORMATION mbi; - if (VirtualQuery(symbol, &mbi, sizeof(mbi))) - return mbi.AllocationBase; +#if defined(OS_WINDOWS) + MEMORY_BASIC_INFORMATION mbi; + if (VirtualQuery(symbol, &mbi, sizeof(mbi))) + return mbi.AllocationBase; + return nullptr; +#elif !defined(OS_WEB) + Dl_info info = {}; + if (dladdr(symbol, &info) == 0) return nullptr; - #elif !defined(OS_WEB) - Dl_info info = {}; - if (dladdr(symbol, &info) == 0) - return nullptr; - return dlopen(info.dli_fname, RTLD_LAZY); - #else - std::ignore = symbol; - return nullptr; - #endif + return dlopen(info.dli_fname, RTLD_LAZY); +#else + std::ignore = symbol; + return nullptr; +#endif } std::optional blendColors(const std::optional &a, const std::optional &b) { @@ -865,4 +865,28 @@ namespace hex { glfwIconifyWindow(windowHandle); } + extern "C" void macosEventDataReceived(const u8 *data, size_t length) { + ssize_t nullIndex = -1; + + auto messageData = reinterpret_cast(data); + + for (size_t i = 0; i < length; i++) { + if (messageData[i] == '\0') { + nullIndex = i; + break; + } + } + + if (nullIndex == -1) { + log::warn("Received invalid forwarded event"); + return; + } + + std::string eventName(messageData, nullIndex); + + std::vector eventData(messageData + nullIndex + 1, messageData + length); + + EventNativeMessageReceived::post(eventName, eventData); + } + } \ No newline at end of file diff --git a/lib/libimhex/source/helpers/utils_macos.m b/lib/libimhex/source/helpers/utils_macos.m index 37702bb44..df26e5cc4 100644 --- a/lib/libimhex/source/helpers/utils_macos.m +++ b/lib/libimhex/source/helpers/utils_macos.m @@ -17,6 +17,7 @@ #import #import + #import #include @@ -147,6 +148,74 @@ [cocoaWindow setDocumentEdited:edited]; } + static NSArray* getRunningInstances(NSString *bundleIdentifier) { + return [NSRunningApplication runningApplicationsWithBundleIdentifier: bundleIdentifier]; + } + + bool macosIsMainInstance(void) { + NSArray *applications = getRunningInstances(@"net.WerWolv.ImHex"); + return applications.count == 0; + } + + extern void macosEventDataReceived(const unsigned char *data, size_t length); + static OSErr handleAppleEvent(const AppleEvent *event, AppleEvent *reply, void *refcon) { + (void)reply; + (void)refcon; + + // Extract the raw binary data from the event's parameter + AEDesc paramDesc; + OSErr err = AEGetParamDesc(event, keyDirectObject, typeWildCard, ¶mDesc); + if (err != noErr) { + NSLog(@"Failed to get parameter: %d", err); + return err; + } + + // Convert the AEDesc to NSData + NSAppleEventDescriptor *descriptor = [[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:¶mDesc]; + NSData *binaryData = descriptor.data; + + // Process the binary data + if (binaryData) { + macosEventDataReceived(binaryData.bytes, binaryData.length); + } + + return noErr; + } + + void macosInstallEventListener(void) { + AEInstallEventHandler('misc', 'imhx', NewAEEventHandlerUPP(handleAppleEvent), 0, false); + } + + void macosSendMessageToMainInstance(const unsigned char *data, size_t size) { + NSString *bundleIdentifier = @"net.WerWolv.ImHex"; + + NSData *binaryData = [NSData dataWithBytes:data length:size]; + // Find the target application by its bundle identifier + NSAppleEventDescriptor *targetApp = [NSAppleEventDescriptor descriptorWithBundleIdentifier:bundleIdentifier]; + if (!targetApp) { + NSLog(@"Application with bundle identifier %@ not found.", bundleIdentifier); + return; + } + + // Create the Apple event + NSAppleEventDescriptor *event = [[NSAppleEventDescriptor alloc] initWithEventClass:'misc' + eventID:'imhx' + targetDescriptor:targetApp + returnID:kAutoGenerateReturnID + transactionID:kAnyTransactionID]; + + // Add a parameter with raw binary data + NSAppleEventDescriptor *binaryDescriptor = [NSAppleEventDescriptor descriptorWithDescriptorType:typeData + data:binaryData]; + [event setParamDescriptor:binaryDescriptor forKeyword:keyDirectObject]; + + // Send the event + OSStatus status = AESendMessage([event aeDesc], NULL, kAENoReply, kAEDefaultTimeout); + if (status != noErr) { + NSLog(@"Failed to send Apple event: %d", status); + } + } + @interface HexDocument : NSDocument @end diff --git a/main/gui/source/messaging/common.cpp b/main/gui/source/messaging/common.cpp index 1d51c16ee..15995a95e 100644 --- a/main/gui/source/messaging/common.cpp +++ b/main/gui/source/messaging/common.cpp @@ -26,6 +26,10 @@ namespace hex::messaging { sendToOtherInstance(eventName, eventData); } }); + + EventNativeMessageReceived::subscribe([](const std::string &eventName, const std::vector &eventData) { + messageReceived(eventName, eventData); + }); } void setupMessaging() { diff --git a/main/gui/source/messaging/macos.cpp b/main/gui/source/messaging/macos.cpp index c484bc79e..67edfef1c 100644 --- a/main/gui/source/messaging/macos.cpp +++ b/main/gui/source/messaging/macos.cpp @@ -3,20 +3,30 @@ #include #include +#include #include "messaging.hpp" namespace hex::messaging { void sendToOtherInstance(const std::string &eventName, const std::vector &args) { - std::ignore = eventName; - std::ignore = args; - log::error("Unimplemented function 'sendToOtherInstance()' called"); + log::debug("Sending event {} to another instance (not us)", eventName); + + // Create the message + std::vector fullEventData(eventName.begin(), eventName.end()); + fullEventData.push_back('\0'); + + fullEventData.insert(fullEventData.end(), args.begin(), args.end()); + + u8 *data = &fullEventData[0]; + auto dataSize = fullEventData.size(); + + macosSendMessageToMainInstance(data, dataSize); } - // Not implemented, so lets say we are the main instance every time so events are forwarded to ourselves bool setupNative() { - return true; + macosInstallEventListener(); + return macosIsMainInstance(); } } diff --git a/main/gui/source/messaging/win.cpp b/main/gui/source/messaging/win.cpp index 1e0d7ea71..43f3900e5 100644 --- a/main/gui/source/messaging/win.cpp +++ b/main/gui/source/messaging/win.cpp @@ -42,16 +42,20 @@ namespace hex::messaging { log::debug("Sending event {} to another instance (not us)", eventName); // Get the window we want to send it to - HWND imHexWindow = *getImHexWindow(); + auto potentialWindow = getImHexWindow(); + if (!potentialWindow.has_value()) + return; + + HWND imHexWindow = potentialWindow.value(); // Create the message - std::vector fulleventData(eventName.begin(), eventName.end()); - fulleventData.push_back('\0'); + std::vector fullEventData(eventName.begin(), eventName.end()); + fullEventData.push_back('\0'); - fulleventData.insert(fulleventData.end(), args.begin(), args.end()); + fullEventData.insert(fullEventData.end(), args.begin(), args.end()); - u8 *data = &fulleventData[0]; - DWORD dataSize = fulleventData.size(); + u8 *data = &fullEventData[0]; + DWORD dataSize = fullEventData.size(); COPYDATASTRUCT message = { .dwData = 0, diff --git a/main/gui/source/window/win_window.cpp b/main/gui/source/window/win_window.cpp index 2e767ee93..4745cbca8 100644 --- a/main/gui/source/window/win_window.cpp +++ b/main/gui/source/window/win_window.cpp @@ -12,6 +12,7 @@ #include #include + #include #include #include @@ -93,7 +94,7 @@ namespace hex { std::vector eventData(messageData + nullIndex + 1, messageData + messageSize); - hex::messaging::messageReceived(eventName, eventData); + EventNativeMessageReceived::post(eventName, eventData); break; } case WM_SETTINGCHANGE: {