1
0
mirror of synced 2025-02-02 12:27:25 +01:00

feat: Implemented macOS messaging support (#2088)

This commit is contained in:
Nik 2025-01-26 18:50:19 +01:00 committed by GitHub
parent 7340a30650
commit bb594a459f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 243 additions and 120 deletions

View File

@ -69,4 +69,11 @@ namespace hex {
*/ */
EVENT_DEF(EventProjectOpened); 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<u8>);
} }

View File

@ -24,6 +24,10 @@
void macosMarkContentEdited(GLFWwindow *window, bool edited = true); void macosMarkContentEdited(GLFWwindow *window, bool edited = true);
void macosGetKey(Keys key, int *output); void macosGetKey(Keys key, int *output);
bool macosIsMainInstance();
void macosSendMessageToMainInstance(const unsigned char *data, size_t size);
void macosInstallEventListener();
} }
#endif #endif

View File

@ -11,6 +11,7 @@
#include <imgui_internal.h> #include <imgui_internal.h>
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#include <hex/api/events/events_lifecycle.hpp>
#if defined(OS_WINDOWS) #if defined(OS_WINDOWS)
#include <windows.h> #include <windows.h>
@ -30,7 +31,6 @@
#endif #endif
namespace hex { namespace hex {
float operator""_scaled(long double value) { float operator""_scaled(long double value) {
return value * ImHexApi::System::getGlobalScale(); return value * ImHexApi::System::getGlobalScale();
} }
@ -163,25 +163,25 @@ namespace hex {
switch (unitIndex) { switch (unitIndex) {
case 0: case 0:
result += ((value == 1) ? " Byte" : " Bytes"); result += ((value == 1) ? " Byte" : " Bytes");
break; break;
case 1: case 1:
result += " kiB"; result += " kiB";
break; break;
case 2: case 2:
result += " MiB"; result += " MiB";
break; break;
case 3: case 3:
result += " GiB"; result += " GiB";
break; break;
case 4: case 4:
result += " TiB"; result += " TiB";
break; break;
case 5: case 5:
result += " PiB"; result += " PiB";
break; break;
case 6: case 6:
result += " EiB"; result += " EiB";
break; break;
default: default:
result = "A lot!"; result = "A lot!";
} }
@ -338,15 +338,15 @@ namespace hex {
void startProgram(const std::string &command) { void startProgram(const std::string &command) {
#if defined(OS_WINDOWS) #if defined(OS_WINDOWS)
std::ignore = system(hex::format("start {0}", command).c_str()); std::ignore = system(hex::format("start {0}", command).c_str());
#elif defined(OS_MACOS) #elif defined(OS_MACOS)
std::ignore = system(hex::format("open {0}", command).c_str()); std::ignore = system(hex::format("open {0}", command).c_str());
#elif defined(OS_LINUX) #elif defined(OS_LINUX)
executeCmd({"xdg-open", command}); executeCmd({"xdg-open", command});
#elif defined(OS_WEB) #elif defined(OS_WEB)
std::ignore = command; std::ignore = command;
#endif #endif
} }
int executeCommand(const std::string &command) { int executeCommand(const std::string &command) {
@ -357,19 +357,19 @@ namespace hex {
if (!url.contains("://")) if (!url.contains("://"))
url = "https://" + url; url = "https://" + url;
#if defined(OS_WINDOWS) #if defined(OS_WINDOWS)
ShellExecuteA(nullptr, "open", url.c_str(), nullptr, nullptr, SW_SHOWNORMAL); ShellExecuteA(nullptr, "open", url.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
#elif defined(OS_MACOS) #elif defined(OS_MACOS)
openWebpageMacos(url.c_str()); openWebpageMacos(url.c_str());
#elif defined(OS_LINUX) #elif defined(OS_LINUX)
executeCmd({"xdg-open", url}); executeCmd({"xdg-open", url});
#elif defined(OS_WEB) #elif defined(OS_WEB)
EM_ASM({ EM_ASM({
window.open(UTF8ToString($0), '_blank'); window.open(UTF8ToString($0), '_blank');
}, url.c_str()); }, url.c_str());
#else #else
#warning "Unknown OS, can't open webpages" #warning "Unknown OS, can't open webpages"
#endif #endif
} }
std::optional<u8> hexCharToValue(char c) { std::optional<u8> hexCharToValue(char c) {
@ -391,31 +391,31 @@ namespace hex {
switch (byte) { switch (byte) {
case '\\': case '\\':
result += "\\"; result += "\\";
break; break;
case '\a': case '\a':
result += "\\a"; result += "\\a";
break; break;
case '\b': case '\b':
result += "\\b"; result += "\\b";
break; break;
case '\f': case '\f':
result += "\\f"; result += "\\f";
break; break;
case '\n': case '\n':
result += "\\n"; result += "\\n";
break; break;
case '\r': case '\r':
result += "\\r"; result += "\\r";
break; break;
case '\t': case '\t':
result += "\\t"; result += "\\t";
break; break;
case '\v': case '\v':
result += "\\v"; result += "\\v";
break; break;
default: default:
result += hex::format("\\x{:02X}", byte); result += hex::format("\\x{:02X}", byte);
break; break;
} }
} }
} }
@ -442,46 +442,46 @@ namespace hex {
switch (escapeChar) { switch (escapeChar) {
case 'a': case 'a':
result.push_back('\a'); result.push_back('\a');
break; break;
case 'b': case 'b':
result.push_back('\b'); result.push_back('\b');
break; break;
case 'f': case 'f':
result.push_back('\f'); result.push_back('\f');
break; break;
case 'n': case 'n':
result.push_back('\n'); result.push_back('\n');
break; break;
case 'r': case 'r':
result.push_back('\r'); result.push_back('\r');
break; break;
case 't': case 't':
result.push_back('\t'); result.push_back('\t');
break; break;
case 'v': case 'v':
result.push_back('\v'); result.push_back('\v');
break; break;
case '\\': case '\\':
result.push_back('\\'); result.push_back('\\');
break; break;
case 'x': case 'x':
{ {
u8 byte = 0x00; u8 byte = 0x00;
if ((offset + 1) >= string.length()) return {}; if ((offset + 1) >= string.length()) return {};
for (u8 i = 0; i < 2; i++) { for (u8 i = 0; i < 2; i++) {
byte <<= 4; byte <<= 4;
if (auto hexValue = hexCharToValue(c()); hexValue.has_value()) if (auto hexValue = hexCharToValue(c()); hexValue.has_value())
byte |= hexValue.value(); byte |= hexValue.value();
else else
return {}; return {};
offset++; offset++;
}
result.push_back(byte);
} }
break;
result.push_back(byte);
}
break;
default: default:
return {}; return {};
} }
@ -650,27 +650,27 @@ namespace hex {
} }
bool isProcessElevated() { bool isProcessElevated() {
#if defined(OS_WINDOWS) #if defined(OS_WINDOWS)
bool elevated = false; bool elevated = false;
HANDLE token = INVALID_HANDLE_VALUE; HANDLE token = INVALID_HANDLE_VALUE;
if (::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) { if (::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &token)) {
TOKEN_ELEVATION elevation; TOKEN_ELEVATION elevation;
DWORD elevationSize = sizeof(TOKEN_ELEVATION); DWORD elevationSize = sizeof(TOKEN_ELEVATION);
if (::GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &elevationSize)) if (::GetTokenInformation(token, TokenElevation, &elevation, sizeof(elevation), &elevationSize))
elevated = elevation.TokenIsElevated; elevated = elevation.TokenIsElevated;
} }
if (token != INVALID_HANDLE_VALUE) if (token != INVALID_HANDLE_VALUE)
::CloseHandle(token); ::CloseHandle(token);
return elevated; return elevated;
#elif defined(OS_LINUX) || defined(OS_MACOS) #elif defined(OS_LINUX) || defined(OS_MACOS)
return getuid() == 0 || getuid() != geteuid(); return getuid() == 0 || getuid() != geteuid();
#else #else
return false; return false;
#endif #endif
} }
std::optional<std::string> getEnvironmentVariable(const std::string &env) { std::optional<std::string> getEnvironmentVariable(const std::string &env) {
@ -806,45 +806,45 @@ namespace hex {
} }
std::string formatSystemError(i32 error) { std::string formatSystemError(i32 error) {
#if defined(OS_WINDOWS) #if defined(OS_WINDOWS)
wchar_t *message = nullptr; wchar_t *message = nullptr;
auto wLength = FormatMessageW( auto wLength = FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr, error, nullptr, error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(wchar_t*)&message, 0, (wchar_t*)&message, 0,
nullptr nullptr
); );
ON_SCOPE_EXIT { LocalFree(message); }; ON_SCOPE_EXIT { LocalFree(message); };
auto length = ::WideCharToMultiByte(CP_UTF8, 0, message, wLength, nullptr, 0, nullptr, nullptr); auto length = ::WideCharToMultiByte(CP_UTF8, 0, message, wLength, nullptr, 0, nullptr, nullptr);
std::string result(length, '\x00'); std::string result(length, '\x00');
::WideCharToMultiByte(CP_UTF8, 0, message, wLength, result.data(), length, nullptr, nullptr); ::WideCharToMultiByte(CP_UTF8, 0, message, wLength, result.data(), length, nullptr, nullptr);
return result; return result;
#else #else
return std::system_category().message(error); return std::system_category().message(error);
#endif #endif
} }
void* getContainingModule(void* symbol) { void* getContainingModule(void* symbol) {
#if defined(OS_WINDOWS) #if defined(OS_WINDOWS)
MEMORY_BASIC_INFORMATION mbi; MEMORY_BASIC_INFORMATION mbi;
if (VirtualQuery(symbol, &mbi, sizeof(mbi))) if (VirtualQuery(symbol, &mbi, sizeof(mbi)))
return mbi.AllocationBase; return mbi.AllocationBase;
return nullptr;
#elif !defined(OS_WEB)
Dl_info info = {};
if (dladdr(symbol, &info) == 0)
return nullptr; return nullptr;
#elif !defined(OS_WEB)
Dl_info info = {};
if (dladdr(symbol, &info) == 0)
return nullptr;
return dlopen(info.dli_fname, RTLD_LAZY); return dlopen(info.dli_fname, RTLD_LAZY);
#else #else
std::ignore = symbol; std::ignore = symbol;
return nullptr; return nullptr;
#endif #endif
} }
std::optional<ImColor> blendColors(const std::optional<ImColor> &a, const std::optional<ImColor> &b) { std::optional<ImColor> blendColors(const std::optional<ImColor> &a, const std::optional<ImColor> &b) {
@ -865,4 +865,28 @@ namespace hex {
glfwIconifyWindow(windowHandle); glfwIconifyWindow(windowHandle);
} }
extern "C" void macosEventDataReceived(const u8 *data, size_t length) {
ssize_t nullIndex = -1;
auto messageData = reinterpret_cast<const char*>(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<u8> eventData(messageData + nullIndex + 1, messageData + length);
EventNativeMessageReceived::post(eventName, eventData);
}
} }

View File

@ -17,6 +17,7 @@
#import <Cocoa/Cocoa.h> #import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import <AppleScriptObjC/AppleScriptObjC.h>
#include <hex/helpers/keys.hpp> #include <hex/helpers/keys.hpp>
@ -147,6 +148,74 @@
[cocoaWindow setDocumentEdited:edited]; [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, &paramDesc);
if (err != noErr) {
NSLog(@"Failed to get parameter: %d", err);
return err;
}
// Convert the AEDesc to NSData
NSAppleEventDescriptor *descriptor = [[NSAppleEventDescriptor alloc] initWithAEDescNoCopy:&paramDesc];
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 @interface HexDocument : NSDocument
@end @end

View File

@ -26,6 +26,10 @@ namespace hex::messaging {
sendToOtherInstance(eventName, eventData); sendToOtherInstance(eventName, eventData);
} }
}); });
EventNativeMessageReceived::subscribe([](const std::string &eventName, const std::vector<u8> &eventData) {
messageReceived(eventName, eventData);
});
} }
void setupMessaging() { void setupMessaging() {

View File

@ -3,20 +3,30 @@
#include <stdexcept> #include <stdexcept>
#include <hex/helpers/logger.hpp> #include <hex/helpers/logger.hpp>
#include <hex/helpers/utils_macos.hpp>
#include "messaging.hpp" #include "messaging.hpp"
namespace hex::messaging { namespace hex::messaging {
void sendToOtherInstance(const std::string &eventName, const std::vector<u8> &args) { void sendToOtherInstance(const std::string &eventName, const std::vector<u8> &args) {
std::ignore = eventName; log::debug("Sending event {} to another instance (not us)", eventName);
std::ignore = args;
log::error("Unimplemented function 'sendToOtherInstance()' called"); // Create the message
std::vector<u8> 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() { bool setupNative() {
return true; macosInstallEventListener();
return macosIsMainInstance();
} }
} }

View File

@ -42,16 +42,20 @@ namespace hex::messaging {
log::debug("Sending event {} to another instance (not us)", eventName); log::debug("Sending event {} to another instance (not us)", eventName);
// Get the window we want to send it to // 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 // Create the message
std::vector<u8> fulleventData(eventName.begin(), eventName.end()); std::vector<u8> fullEventData(eventName.begin(), eventName.end());
fulleventData.push_back('\0'); fullEventData.push_back('\0');
fulleventData.insert(fulleventData.end(), args.begin(), args.end()); fullEventData.insert(fullEventData.end(), args.begin(), args.end());
u8 *data = &fulleventData[0]; u8 *data = &fullEventData[0];
DWORD dataSize = fulleventData.size(); DWORD dataSize = fullEventData.size();
COPYDATASTRUCT message = { COPYDATASTRUCT message = {
.dwData = 0, .dwData = 0,

View File

@ -12,6 +12,7 @@
#include <hex/helpers/default_paths.hpp> #include <hex/helpers/default_paths.hpp>
#include <hex/api/events/events_gui.hpp> #include <hex/api/events/events_gui.hpp>
#include <hex/api/events/events_lifecycle.hpp>
#include <hex/api/events/requests_gui.hpp> #include <hex/api/events/requests_gui.hpp>
#include <imgui.h> #include <imgui.h>
@ -93,7 +94,7 @@ namespace hex {
std::vector<u8> eventData(messageData + nullIndex + 1, messageData + messageSize); std::vector<u8> eventData(messageData + nullIndex + 1, messageData + messageSize);
hex::messaging::messageReceived(eventName, eventData); EventNativeMessageReceived::post(eventName, eventData);
break; break;
} }
case WM_SETTINGCHANGE: { case WM_SETTINGCHANGE: {