1
0
mirror of synced 2025-02-02 04:17:56 +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);
/**
* @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 macosGetKey(Keys key, int *output);
bool macosIsMainInstance();
void macosSendMessageToMainInstance(const unsigned char *data, size_t size);
void macosInstallEventListener();
}
#endif

View File

@ -11,6 +11,7 @@
#include <imgui_internal.h>
#include <GLFW/glfw3.h>
#include <hex/api/events/events_lifecycle.hpp>
#if defined(OS_WINDOWS)
#include <windows.h>
@ -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<u8> 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<std::string> 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<ImColor> blendColors(const std::optional<ImColor> &a, const std::optional<ImColor> &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<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 <Foundation/Foundation.h>
#import <AppleScriptObjC/AppleScriptObjC.h>
#include <hex/helpers/keys.hpp>
@ -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, &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
@end

View File

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

View File

@ -3,20 +3,30 @@
#include <stdexcept>
#include <hex/helpers/logger.hpp>
#include <hex/helpers/utils_macos.hpp>
#include "messaging.hpp"
namespace hex::messaging {
void sendToOtherInstance(const std::string &eventName, const std::vector<u8> &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<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() {
return true;
macosInstallEventListener();
return macosIsMainInstance();
}
}

View File

@ -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<u8> fulleventData(eventName.begin(), eventName.end());
fulleventData.push_back('\0');
std::vector<u8> 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,

View File

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