From a115b40ef74480d53f3eba4e6cf70915eec28da2 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sat, 23 Sep 2023 22:20:50 +1000 Subject: [PATCH] Common: Split some routines into CocoaTools --- src/common/CMakeLists.txt | 11 ++ src/{util => common}/cocoa_tools.h | 19 +++- src/common/cocoa_tools.mm | 159 +++++++++++++++++++++++++++++ src/duckstation-qt/mainwindow.cpp | 2 +- src/util/CMakeLists.txt | 2 +- src/util/metal_layer.h | 12 +++ src/util/platform_misc_mac.mm | 70 +------------ src/util/vulkan_swap_chain.cpp | 2 +- 8 files changed, 200 insertions(+), 77 deletions(-) rename src/{util => common}/cocoa_tools.h (52%) create mode 100644 src/common/cocoa_tools.mm create mode 100644 src/util/metal_layer.h diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 3eb3cd005..dacabb56a 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -86,6 +86,17 @@ if(MSVC) endif() endif() +if(APPLE) + set(MAC_SOURCES + cocoa_tools.h + cocoa_tools.mm + ) + target_sources(common PRIVATE ${MAC_SOURCES}) + set_source_files_properties(${MAC_SOURCES} PROPERTIES SKIP_PRECOMPILE_HEADERS TRUE) + find_library(COCOA_LIBRARY Cocoa REQUIRED) + target_link_libraries(common PRIVATE ${COCOA_LIBRARY}) +endif() + if(NOT WIN32 AND NOT ANDROID) target_sources(common PRIVATE http_downloader_curl.cpp diff --git a/src/util/cocoa_tools.h b/src/common/cocoa_tools.h similarity index 52% rename from src/util/cocoa_tools.h rename to src/common/cocoa_tools.h index 49766747c..a3554b0a8 100644 --- a/src/util/cocoa_tools.h +++ b/src/common/cocoa_tools.h @@ -1,9 +1,12 @@ // SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) +#include +#include #include +#include -struct WindowInfo; +class Error; #ifdef __OBJC__ #import @@ -11,6 +14,9 @@ struct WindowInfo; namespace CocoaTools { NSString* StringViewToNSString(const std::string_view& str); + +/// Converts NSError to a human-readable string. +std::string NSErrorToString(NSError* error); } #endif @@ -22,9 +28,12 @@ void AddThemeChangeHandler(void* ctx, void(handler)(void* ctx)); /// Remove a handler previously added using AddThemeChangeHandler with the given context void RemoveThemeChangeHandler(void* ctx); -/// Creates metal layer on specified window surface. -bool CreateMetalLayer(WindowInfo* wi); +/// Moves a file from one location to another, using NSFileManager. +bool MoveFile(const char* source, const char* destination, Error* error); -/// Destroys metal layer on specified window surface. -void DestroyMetalLayer(WindowInfo* wi); +/// Get the bundle path to the actual application without any translocation fun +std::optional GetNonTranslocatedBundlePath(); + +/// Launch the given application once this one quits +bool DelayedLaunch(std::string_view file, std::span args = {}); } // namespace CocoaTools diff --git a/src/common/cocoa_tools.mm b/src/common/cocoa_tools.mm new file mode 100644 index 000000000..b60687952 --- /dev/null +++ b/src/common/cocoa_tools.mm @@ -0,0 +1,159 @@ +// 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]; + } +} diff --git a/src/duckstation-qt/mainwindow.cpp b/src/duckstation-qt/mainwindow.cpp index 4774bd112..98eebe17d 100644 --- a/src/duckstation-qt/mainwindow.cpp +++ b/src/duckstation-qt/mainwindow.cpp @@ -53,7 +53,7 @@ #endif #ifdef __APPLE__ -#include "util/cocoa_tools.h" +#include "common/cocoa_tools.h" #endif Log_SetChannel(MainWindow); diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 378a0bf54..b2c4e9d61 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -254,9 +254,9 @@ elseif(APPLE) include(AddMetalSources) set(MAC_SOURCES - cocoa_tools.h metal_device.h metal_device.mm + metal_layer.h metal_stream_buffer.h metal_stream_buffer.mm platform_misc_mac.mm diff --git a/src/util/metal_layer.h b/src/util/metal_layer.h new file mode 100644 index 000000000..b6c745094 --- /dev/null +++ b/src/util/metal_layer.h @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +struct WindowInfo; + +namespace CocoaTools { +/// Creates metal layer on specified window surface. +bool CreateMetalLayer(WindowInfo* wi); + +/// Destroys metal layer on specified window surface. +void DestroyMetalLayer(WindowInfo* wi); +} // namespace CocoaTools diff --git a/src/util/platform_misc_mac.mm b/src/util/platform_misc_mac.mm index e83fc5992..582b957c4 100644 --- a/src/util/platform_misc_mac.mm +++ b/src/util/platform_misc_mac.mm @@ -3,7 +3,7 @@ #include "platform_misc.h" #include "window_info.h" -#include "cocoa_tools.h" +#include "metal_layer.h" #include "common/log.h" #include "common/small_string.h" @@ -80,74 +80,6 @@ bool PlatformMisc::PlaySoundAsync(const char* path) return result; } -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]; -} - -// From https://github.com/PCSX2/pcsx2/blob/1b673d9dd0829a48f5f0b6604c1de2108e981399/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]; -} - bool CocoaTools::CreateMetalLayer(WindowInfo *wi) { // Punt off to main thread if we're not calling from it already. diff --git a/src/util/vulkan_swap_chain.cpp b/src/util/vulkan_swap_chain.cpp index efcb96807..2a825c10e 100644 --- a/src/util/vulkan_swap_chain.cpp +++ b/src/util/vulkan_swap_chain.cpp @@ -17,7 +17,7 @@ #endif #if defined(VK_USE_PLATFORM_METAL_EXT) -#include "util/cocoa_tools.h" +#include "util/metal_layer.h" #endif Log_SetChannel(VulkanDevice);