// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: GPL-3.0 #include "cocoa_tools.h" #include "small_string.h" #include "error.h" #include "fmt/format.h" #include #include #include #if __has_feature(objc_arc) #error ARC should not be enabled. #endif NSString* CocoaTools::StringViewToNSString(const std::string_view& str) { if (str.empty()) return nil; return [[[NSString alloc] initWithBytes:str.data() length:static_cast(str.length()) encoding:NSUTF8StringEncoding] autorelease]; } std::string CocoaTools::NSErrorToString(NSError *error) { return fmt::format("{}: {}", static_cast(error.code), [error.description UTF8String]); } bool CocoaTools::MoveFile(const char *source, const char *destination, Error *error) { @autoreleasepool { NSError* nserror; const BOOL result = [[NSFileManager defaultManager] moveItemAtPath:[NSString stringWithUTF8String:source] toPath:[NSString stringWithUTF8String:destination] error:&nserror]; if (!result) { Error::SetString(error, NSErrorToString(nserror)); return false; } return true; } } // From https://github.com/PCSX2/pcsx2/blob/8d27c324187140df0c5a42f3a501b5d76b1215f5/common/CocoaTools.mm @interface PCSX2KVOHelper : NSObject - (void)addCallback:(void*)ctx run:(void(*)(void*))callback; - (void)removeCallback:(void*)ctx; @end @implementation PCSX2KVOHelper { std::vector> _callbacks; } - (void)addCallback:(void*)ctx run:(void(*)(void*))callback { _callbacks.push_back(std::make_pair(ctx, callback)); } - (void)removeCallback:(void*)ctx { auto new_end = std::remove_if(_callbacks.begin(), _callbacks.end(), [ctx](const auto& entry){ return ctx == entry.first; }); _callbacks.erase(new_end, _callbacks.end()); } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { for (const auto& callback : _callbacks) callback.second(callback.first); } @end static PCSX2KVOHelper* s_themeChangeHandler; void CocoaTools::AddThemeChangeHandler(void* ctx, void(handler)(void* ctx)) { assert([NSThread isMainThread]); if (!s_themeChangeHandler) { s_themeChangeHandler = [[PCSX2KVOHelper alloc] init]; NSApplication* app = [NSApplication sharedApplication]; [app addObserver:s_themeChangeHandler forKeyPath:@"effectiveAppearance" options:0 context:nil]; } [s_themeChangeHandler addCallback:ctx run:handler]; } void CocoaTools::RemoveThemeChangeHandler(void* ctx) { assert([NSThread isMainThread]); [s_themeChangeHandler removeCallback:ctx]; } std::optional CocoaTools::GetNonTranslocatedBundlePath() { // See https://objective-see.com/blog/blog_0x15.html NSURL* url = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; if (!url) return std::nullopt; if (void* handle = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY)) { auto IsTranslocatedURL = reinterpret_cast(dlsym(handle, "SecTranslocateIsTranslocatedURL")); auto CreateOriginalPathForURL = reinterpret_cast(dlsym(handle, "SecTranslocateCreateOriginalPathForURL")); bool is_translocated = false; if (IsTranslocatedURL) IsTranslocatedURL((__bridge CFURLRef)url, &is_translocated, nullptr); if (is_translocated) { if (CFURLRef actual = CreateOriginalPathForURL((__bridge CFURLRef)url, nullptr)) url = (__bridge_transfer NSURL*)actual; } dlclose(handle); } return std::string([url fileSystemRepresentation]); } bool CocoaTools::DelayedLaunch(std::string_view file, std::span args) { @autoreleasepool { const int pid = [[NSProcessInfo processInfo] processIdentifier]; // Hopefully we're not too large here... std::string task_args = fmt::format("while /bin/ps -p {} > /dev/null; do /bin/sleep 0.1; done; exec /usr/bin/open \"{}\"", pid, file); if (!args.empty()) { task_args += " --args"; for (const std::string_view& arg : args) { task_args += " \""; task_args += arg; task_args += "\""; } } NSTask* task = [NSTask new]; [task setExecutableURL:[NSURL fileURLWithPath:@"/bin/sh"]]; [task setArguments:@[@"-c", [NSString stringWithUTF8String:task_args.c_str()]]]; return [task launchAndReturnError:nil]; } }