feat: Implemented macOS messaging support (#2088)
This commit is contained in:
parent
7340a30650
commit
bb594a459f
@ -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>);
|
||||
|
||||
}
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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, ¶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
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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: {
|
||||
|
Loading…
x
Reference in New Issue
Block a user