cocoa_tools.mm (5178B)
1 // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin <stenzek@gmail.com> 2 // SPDX-License-Identifier: GPL-3.0 3 4 #include "cocoa_tools.h" 5 #include "small_string.h" 6 #include "error.h" 7 8 #include "fmt/format.h" 9 10 #include <cinttypes> 11 #include <vector> 12 #include <dlfcn.h> 13 14 #if __has_feature(objc_arc) 15 #error ARC should not be enabled. 16 #endif 17 18 NSString* CocoaTools::StringViewToNSString(std::string_view str) 19 { 20 if (str.empty()) 21 return nil; 22 23 return [[[NSString alloc] initWithBytes:str.data() 24 length:static_cast<NSUInteger>(str.length()) 25 encoding:NSUTF8StringEncoding] autorelease]; 26 } 27 28 std::string CocoaTools::NSErrorToString(NSError *error) 29 { 30 return fmt::format("{}: {}", static_cast<u32>(error.code), [error.description UTF8String]); 31 } 32 33 34 bool CocoaTools::MoveFile(const char *source, const char *destination, Error *error) 35 { 36 @autoreleasepool { 37 NSError* nserror; 38 const BOOL result = [[NSFileManager defaultManager] moveItemAtPath:[NSString stringWithUTF8String:source] 39 toPath:[NSString stringWithUTF8String:destination] 40 error:&nserror]; 41 if (!result) 42 { 43 Error::SetString(error, NSErrorToString(nserror)); 44 return false; 45 } 46 47 return true; 48 } 49 } 50 51 52 // From https://github.com/PCSX2/pcsx2/blob/8d27c324187140df0c5a42f3a501b5d76b1215f5/common/CocoaTools.mm 53 54 @interface PCSX2KVOHelper : NSObject 55 56 - (void)addCallback:(void*)ctx run:(void(*)(void*))callback; 57 - (void)removeCallback:(void*)ctx; 58 59 @end 60 61 @implementation PCSX2KVOHelper 62 { 63 std::vector<std::pair<void*, void(*)(void*)>> _callbacks; 64 } 65 66 - (void)addCallback:(void*)ctx run:(void(*)(void*))callback 67 { 68 _callbacks.push_back(std::make_pair(ctx, callback)); 69 } 70 71 - (void)removeCallback:(void*)ctx 72 { 73 auto new_end = std::remove_if(_callbacks.begin(), _callbacks.end(), [ctx](const auto& entry){ 74 return ctx == entry.first; 75 }); 76 _callbacks.erase(new_end, _callbacks.end()); 77 } 78 79 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context 80 { 81 for (const auto& callback : _callbacks) 82 callback.second(callback.first); 83 } 84 85 @end 86 87 static PCSX2KVOHelper* s_themeChangeHandler; 88 89 void CocoaTools::AddThemeChangeHandler(void* ctx, void(handler)(void* ctx)) 90 { 91 assert([NSThread isMainThread]); 92 if (!s_themeChangeHandler) 93 { 94 s_themeChangeHandler = [[PCSX2KVOHelper alloc] init]; 95 NSApplication* app = [NSApplication sharedApplication]; 96 [app addObserver:s_themeChangeHandler 97 forKeyPath:@"effectiveAppearance" 98 options:0 99 context:nil]; 100 } 101 [s_themeChangeHandler addCallback:ctx run:handler]; 102 } 103 104 void CocoaTools::RemoveThemeChangeHandler(void* ctx) 105 { 106 assert([NSThread isMainThread]); 107 [s_themeChangeHandler removeCallback:ctx]; 108 } 109 110 std::optional<std::string> CocoaTools::GetBundlePath() 111 { 112 std::optional<std::string> ret; 113 @autoreleasepool { 114 NSURL* url = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; 115 if (url) 116 ret = std::string([url fileSystemRepresentation]); 117 } 118 return ret; 119 } 120 121 std::optional<std::string> CocoaTools::GetNonTranslocatedBundlePath() 122 { 123 // See https://objective-see.com/blog/blog_0x15.html 124 125 NSURL* url = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]]; 126 if (!url) 127 return std::nullopt; 128 129 if (void* handle = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY)) 130 { 131 auto IsTranslocatedURL = reinterpret_cast<Boolean(*)(CFURLRef path, bool* isTranslocated, CFErrorRef*__nullable error)>(dlsym(handle, "SecTranslocateIsTranslocatedURL")); 132 auto CreateOriginalPathForURL = reinterpret_cast<CFURLRef __nullable(*)(CFURLRef translocatedPath, CFErrorRef*__nullable error)>(dlsym(handle, "SecTranslocateCreateOriginalPathForURL")); 133 bool is_translocated = false; 134 if (IsTranslocatedURL) 135 IsTranslocatedURL((__bridge CFURLRef)url, &is_translocated, nullptr); 136 if (is_translocated) 137 { 138 if (CFURLRef actual = CreateOriginalPathForURL((__bridge CFURLRef)url, nullptr)) 139 url = (__bridge_transfer NSURL*)actual; 140 } 141 dlclose(handle); 142 } 143 144 return std::string([url fileSystemRepresentation]); 145 } 146 147 bool CocoaTools::DelayedLaunch(std::string_view file, std::span<const std::string_view> args) 148 { 149 @autoreleasepool { 150 const int pid = [[NSProcessInfo processInfo] processIdentifier]; 151 152 // Hopefully we're not too large here... 153 std::string task_args = fmt::format("while /bin/ps -p {} > /dev/null; do /bin/sleep 0.1; done; exec /usr/bin/open \"{}\"", pid, file); 154 if (!args.empty()) 155 { 156 task_args += " --args"; 157 for (const std::string_view& arg : args) 158 { 159 task_args += " \""; 160 task_args += arg; 161 task_args += "\""; 162 } 163 } 164 165 NSTask* task = [NSTask new]; 166 [task setExecutableURL:[NSURL fileURLWithPath:@"/bin/sh"]]; 167 [task setArguments:@[@"-c", [NSString stringWithUTF8String:task_args.c_str()]]]; 168 return [task launchAndReturnError:nil]; 169 } 170 }