You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-04 12:00:25 +00:00
Made possible by EIREXE, xsellier and the SDL team. This commit includes statically linked SDL3 for Windows, Linux and macOS. The vendored copy of SDL3 was setup to only build the required subsystems for gamepad/joystick support, with some patches to be able to make it as minimal as possible and reduce the impact on binary size and code size. Co-authored-by: Álex Román Núñez <eirexe123@gmail.com> Co-authored-by: Xavier Sellier <xsellier@gmail.com> Co-authored-by: Rémi Verschelde <rverschelde@gmail.com>
1234 lines
38 KiB
Plaintext
1234 lines
38 KiB
Plaintext
/**************************************************************************/
|
|
/* os_macos.mm */
|
|
/**************************************************************************/
|
|
/* This file is part of: */
|
|
/* GODOT ENGINE */
|
|
/* https://godotengine.org */
|
|
/**************************************************************************/
|
|
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
|
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
|
/* */
|
|
/* Permission is hereby granted, free of charge, to any person obtaining */
|
|
/* a copy of this software and associated documentation files (the */
|
|
/* "Software"), to deal in the Software without restriction, including */
|
|
/* without limitation the rights to use, copy, modify, merge, publish, */
|
|
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
|
/* permit persons to whom the Software is furnished to do so, subject to */
|
|
/* the following conditions: */
|
|
/* */
|
|
/* The above copyright notice and this permission notice shall be */
|
|
/* included in all copies or substantial portions of the Software. */
|
|
/* */
|
|
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
|
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
|
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
|
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
|
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
|
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
|
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
|
/**************************************************************************/
|
|
|
|
#import "os_macos.h"
|
|
|
|
#import "dir_access_macos.h"
|
|
#ifdef DEBUG_ENABLED
|
|
#import "display_server_embedded.h"
|
|
#endif
|
|
#import "display_server_macos.h"
|
|
#import "godot_application.h"
|
|
#import "godot_application_delegate.h"
|
|
|
|
#include "core/crypto/crypto_core.h"
|
|
#include "core/version_generated.gen.h"
|
|
#include "drivers/apple/os_log_logger.h"
|
|
#include "main/main.h"
|
|
|
|
#ifdef SDL_ENABLED
|
|
#include "drivers/sdl/joypad_sdl.h"
|
|
#endif
|
|
|
|
#include <dlfcn.h>
|
|
#include <libproc.h>
|
|
#import <mach-o/dyld.h>
|
|
#include <os/log.h>
|
|
#include <sys/sysctl.h>
|
|
|
|
void OS_MacOS::add_frame_delay(bool p_can_draw, bool p_wake_for_events) {
|
|
if (p_wake_for_events) {
|
|
uint64_t delay = get_frame_delay(p_can_draw);
|
|
if (delay == 0) {
|
|
return;
|
|
}
|
|
if (wait_timer) {
|
|
CFRunLoopTimerInvalidate(wait_timer);
|
|
CFRelease(wait_timer);
|
|
}
|
|
wait_timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + (double(delay) / 1000000.0), 0, 0, 0,
|
|
^(CFRunLoopTimerRef timer) {
|
|
CFRunLoopTimerInvalidate(wait_timer);
|
|
CFRelease(wait_timer);
|
|
wait_timer = nil;
|
|
});
|
|
CFRunLoopAddTimer(CFRunLoopGetCurrent(), wait_timer, kCFRunLoopCommonModes);
|
|
return;
|
|
}
|
|
OS_Unix::add_frame_delay(p_can_draw, p_wake_for_events);
|
|
}
|
|
|
|
void OS_MacOS::initialize() {
|
|
crash_handler.initialize();
|
|
|
|
initialize_core();
|
|
}
|
|
|
|
String OS_MacOS::get_model_name() const {
|
|
char buffer[256];
|
|
size_t buffer_len = 256;
|
|
if (sysctlbyname("hw.model", &buffer, &buffer_len, nullptr, 0) == 0 && buffer_len != 0) {
|
|
return String::utf8(buffer, buffer_len);
|
|
}
|
|
return OS_Unix::get_model_name();
|
|
}
|
|
|
|
String OS_MacOS::get_processor_name() const {
|
|
char buffer[256];
|
|
size_t buffer_len = 256;
|
|
if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, nullptr, 0) == 0) {
|
|
return String::utf8(buffer, buffer_len);
|
|
}
|
|
ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string."));
|
|
}
|
|
|
|
bool OS_MacOS::is_sandboxed() const {
|
|
return has_environment("APP_SANDBOX_CONTAINER_ID");
|
|
}
|
|
|
|
bool OS_MacOS::request_permission(const String &p_name) {
|
|
if (@available(macOS 10.15, *)) {
|
|
if (p_name == "macos.permission.RECORD_SCREEN") {
|
|
if (CGPreflightScreenCaptureAccess()) {
|
|
return true;
|
|
} else {
|
|
CGRequestScreenCaptureAccess();
|
|
return false;
|
|
}
|
|
}
|
|
} else {
|
|
if (p_name == "macos.permission.RECORD_SCREEN") {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
Vector<String> OS_MacOS::get_granted_permissions() const {
|
|
Vector<String> ret;
|
|
|
|
if (@available(macOS 10.15, *)) {
|
|
if (CGPreflightScreenCaptureAccess()) {
|
|
ret.push_back("macos.permission.RECORD_SCREEN");
|
|
}
|
|
} else {
|
|
ret.push_back("macos.permission.RECORD_SCREEN");
|
|
}
|
|
|
|
if (is_sandboxed()) {
|
|
NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
|
|
for (id bookmark in bookmarks) {
|
|
NSError *error = nil;
|
|
BOOL isStale = NO;
|
|
NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
|
|
if (!error && !isStale) {
|
|
String url_string = String::utf8([[url path] UTF8String]);
|
|
ret.push_back(url_string);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void OS_MacOS::revoke_granted_permissions() {
|
|
if (is_sandboxed()) {
|
|
[[NSUserDefaults standardUserDefaults] setObject:nil forKey:@"sec_bookmarks"];
|
|
}
|
|
}
|
|
|
|
#if TOOLS_ENABLED
|
|
|
|
// Function to check if a debugger is attached to the current process
|
|
bool OS_MacOS::is_debugger_attached() {
|
|
int mib[4];
|
|
struct kinfo_proc info{};
|
|
size_t size = sizeof(info);
|
|
|
|
// Initialize the flags so that, if sysctl fails, info.kp_proc.p_flag will be 0.
|
|
info.kp_proc.p_flag = 0;
|
|
|
|
// Initialize mib, which tells sysctl the info we want, in this case we're looking for information
|
|
// about a specific process ID.
|
|
mib[0] = CTL_KERN;
|
|
mib[1] = KERN_PROC;
|
|
mib[2] = KERN_PROC_PID;
|
|
mib[3] = getpid();
|
|
|
|
if (sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0) {
|
|
perror("sysctl");
|
|
return false;
|
|
}
|
|
|
|
return (info.kp_proc.p_flag & P_TRACED) != 0;
|
|
}
|
|
|
|
void OS_MacOS::wait_for_debugger(uint32_t p_msec) {
|
|
if (p_msec == 0) {
|
|
return;
|
|
}
|
|
|
|
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
|
|
CFTimeInterval wait_time = p_msec / 1000.0;
|
|
|
|
NSTimer *timer = [NSTimer timerWithTimeInterval:0.100
|
|
repeats:YES
|
|
block:^(NSTimer *t) {
|
|
if (is_debugger_attached() || CFAbsoluteTimeGetCurrent() > start + wait_time) {
|
|
[NSApp stopModalWithCode:NSModalResponseContinue];
|
|
[t invalidate];
|
|
}
|
|
}];
|
|
|
|
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSModalPanelRunLoopMode];
|
|
|
|
pid_t pid = getpid();
|
|
alert(vformat("Attach debugger to pid: %d", pid));
|
|
|
|
print("continue...");
|
|
}
|
|
|
|
#endif
|
|
|
|
void OS_MacOS::initialize_core() {
|
|
OS_Unix::initialize_core();
|
|
|
|
DirAccess::make_default<DirAccessMacOS>(DirAccess::ACCESS_RESOURCES);
|
|
DirAccess::make_default<DirAccessMacOS>(DirAccess::ACCESS_USERDATA);
|
|
DirAccess::make_default<DirAccessMacOS>(DirAccess::ACCESS_FILESYSTEM);
|
|
}
|
|
|
|
void OS_MacOS::finalize() {
|
|
if (is_sandboxed()) {
|
|
NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
|
|
for (id bookmark in bookmarks) {
|
|
NSError *error = nil;
|
|
BOOL isStale = NO;
|
|
NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
|
|
if (!error && !isStale) {
|
|
[url stopAccessingSecurityScopedResource];
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef COREMIDI_ENABLED
|
|
midi_driver.close();
|
|
#endif
|
|
|
|
delete_main_loop();
|
|
|
|
#ifdef SDL_ENABLED
|
|
if (joypad_sdl) {
|
|
memdelete(joypad_sdl);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void OS_MacOS::initialize_joypads() {
|
|
#ifdef SDL_ENABLED
|
|
joypad_sdl = memnew(JoypadSDL());
|
|
if (joypad_sdl->initialize() != OK) {
|
|
ERR_PRINT("Couldn't initialize SDL joypad input driver.");
|
|
memdelete(joypad_sdl);
|
|
joypad_sdl = nullptr;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void OS_MacOS::set_main_loop(MainLoop *p_main_loop) {
|
|
main_loop = p_main_loop;
|
|
}
|
|
|
|
void OS_MacOS::delete_main_loop() {
|
|
if (!main_loop) {
|
|
return;
|
|
}
|
|
|
|
memdelete(main_loop);
|
|
main_loop = nullptr;
|
|
}
|
|
|
|
void OS_MacOS::set_cmdline_platform_args(const List<String> &p_args) {
|
|
launch_service_args = p_args;
|
|
}
|
|
|
|
List<String> OS_MacOS::get_cmdline_platform_args() const {
|
|
return launch_service_args;
|
|
}
|
|
|
|
void OS_MacOS::load_shell_environment() const {
|
|
static bool shell_env_loaded = false;
|
|
if (unlikely(!shell_env_loaded)) {
|
|
shell_env_loaded = true;
|
|
if (OS::get_singleton()->has_environment("TERM") || OS::get_singleton()->has_environment("__GODOT_SHELL_ENV_SET")) {
|
|
return; // Already started from terminal, or other the instance with the shell environment, do nothing.
|
|
}
|
|
String pipe;
|
|
List<String> args;
|
|
args.push_back("-c");
|
|
args.push_back(". /etc/zshrc;. /etc/zprofile;. ~/.zshenv;. ~/.zshrc;. ~/.zprofile;env");
|
|
Error err = OS::get_singleton()->execute("zsh", args, &pipe);
|
|
if (err == OK) {
|
|
Vector<String> env_vars = pipe.split("\n");
|
|
for (const String &E : env_vars) {
|
|
Vector<String> tags = E.split("=", 2);
|
|
if (tags.size() != 2 || tags[0] == "SHELL" || tags[0] == "USER" || tags[0] == "COMMAND_MODE" || tags[0] == "TMPDIR" || tags[0] == "TERM_SESSION_ID" || tags[0] == "PWD" || tags[0] == "OLDPWD" || tags[0] == "SHLVL" || tags[0] == "HOME" || tags[0] == "DISPLAY" || tags[0] == "LOGNAME" || tags[0] == "TERM" || tags[0] == "COLORTERM" || tags[0] == "_" || tags[0].begins_with("__CF") || tags[0].begins_with("XPC_") || tags[0].begins_with("__GODOT")) {
|
|
continue;
|
|
}
|
|
OS::get_singleton()->set_environment(tags[0], tags[1]);
|
|
}
|
|
}
|
|
OS::get_singleton()->set_environment("__GODOT_SHELL_ENV_SET", "1");
|
|
}
|
|
}
|
|
|
|
String OS_MacOS::get_name() const {
|
|
return "macOS";
|
|
}
|
|
|
|
String OS_MacOS::get_distribution_name() const {
|
|
return get_name();
|
|
}
|
|
|
|
String OS_MacOS::get_version() const {
|
|
NSOperatingSystemVersion ver = [NSProcessInfo processInfo].operatingSystemVersion;
|
|
return vformat("%d.%d.%d", (int64_t)ver.majorVersion, (int64_t)ver.minorVersion, (int64_t)ver.patchVersion);
|
|
}
|
|
|
|
String OS_MacOS::get_version_alias() const {
|
|
NSOperatingSystemVersion ver = [NSProcessInfo processInfo].operatingSystemVersion;
|
|
String macos_string;
|
|
if (ver.majorVersion == 15) {
|
|
macos_string += "Sequoia";
|
|
} else if (ver.majorVersion == 14) {
|
|
macos_string += "Sonoma";
|
|
} else if (ver.majorVersion == 13) {
|
|
macos_string += "Ventura";
|
|
} else if (ver.majorVersion == 12) {
|
|
macos_string += "Monterey";
|
|
} else if (ver.majorVersion == 11 || (ver.majorVersion == 10 && ver.minorVersion == 16)) {
|
|
// Big Sur was 10.16 during beta, but it became 11 for the stable version.
|
|
macos_string += "Big Sur";
|
|
} else if (ver.majorVersion == 10 && ver.minorVersion == 15) {
|
|
macos_string += "Catalina";
|
|
} else if (ver.majorVersion == 10 && ver.minorVersion == 14) {
|
|
macos_string += "Mojave";
|
|
} else if (ver.majorVersion == 10 && ver.minorVersion == 13) {
|
|
macos_string += "High Sierra";
|
|
} else {
|
|
macos_string += "Unknown";
|
|
}
|
|
// macOS versions older than 10.13 cannot run Godot.
|
|
return vformat("%s (%s)", macos_string, get_version());
|
|
}
|
|
|
|
void OS_MacOS::alert(const String &p_alert, const String &p_title) {
|
|
NSAlert *window = [[NSAlert alloc] init];
|
|
NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()];
|
|
NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()];
|
|
|
|
NSTextField *text_field = [NSTextField labelWithString:ns_alert];
|
|
[text_field setAlignment:NSTextAlignmentCenter];
|
|
[window addButtonWithTitle:@"OK"];
|
|
[window setMessageText:ns_title];
|
|
[window setAccessoryView:text_field];
|
|
[window setAlertStyle:NSAlertStyleWarning];
|
|
|
|
id key_window = [[NSApplication sharedApplication] keyWindow];
|
|
[window runModal];
|
|
if (key_window) {
|
|
[key_window makeKeyAndOrderFront:nil];
|
|
}
|
|
}
|
|
|
|
_FORCE_INLINE_ String OS_MacOS::get_framework_executable(const String &p_path) {
|
|
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
|
|
|
|
// Read framework bundle to get executable name.
|
|
NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
|
|
NSBundle *bundle = [NSBundle bundleWithURL:url];
|
|
if (bundle) {
|
|
String exe_path = String::utf8([[bundle executablePath] UTF8String]);
|
|
if (da->file_exists(exe_path)) {
|
|
return exe_path;
|
|
}
|
|
}
|
|
|
|
// Try default executable name (invalid framework).
|
|
if (da->dir_exists(p_path) && da->file_exists(p_path.path_join(p_path.get_file().get_basename()))) {
|
|
return p_path.path_join(p_path.get_file().get_basename());
|
|
}
|
|
|
|
// Not a framework, try loading as .dylib.
|
|
return p_path;
|
|
}
|
|
|
|
Error OS_MacOS::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
|
|
String path = get_framework_executable(p_path);
|
|
|
|
if (!FileAccess::exists(path)) {
|
|
// Load .dylib or framework from within the executable path.
|
|
path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file()));
|
|
}
|
|
|
|
if (!FileAccess::exists(path)) {
|
|
// Load .dylib or framework from a standard macOS location.
|
|
path = get_framework_executable(get_executable_path().get_base_dir().path_join("../Frameworks").path_join(p_path.get_file()));
|
|
}
|
|
|
|
if (!FileAccess::exists(path)) {
|
|
// Try using path as is. macOS system libraries with `/usr/lib/*` path do not exist as physical files and are loaded from shared cache.
|
|
path = p_path;
|
|
}
|
|
|
|
p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
|
|
ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
|
|
|
|
if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
|
|
*p_data->r_resolved_path = path;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
MainLoop *OS_MacOS::get_main_loop() const {
|
|
return main_loop;
|
|
}
|
|
|
|
String OS_MacOS::get_config_path() const {
|
|
if (has_environment("HOME")) {
|
|
return get_environment("HOME").path_join("Library/Application Support");
|
|
}
|
|
return ".";
|
|
}
|
|
|
|
String OS_MacOS::get_data_path() const {
|
|
return get_config_path();
|
|
}
|
|
|
|
String OS_MacOS::get_cache_path() const {
|
|
if (has_environment("HOME")) {
|
|
return get_environment("HOME").path_join("Library/Caches");
|
|
}
|
|
return get_config_path();
|
|
}
|
|
|
|
String OS_MacOS::get_temp_path() const {
|
|
static String ret;
|
|
if (ret.is_empty()) {
|
|
NSURL *url = [NSURL fileURLWithPath:NSTemporaryDirectory()
|
|
isDirectory:YES];
|
|
if (url) {
|
|
ret = String::utf8([url.path UTF8String]);
|
|
ret = ret.trim_prefix("file://");
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
String OS_MacOS::get_bundle_resource_dir() const {
|
|
String ret;
|
|
|
|
NSBundle *main = [NSBundle mainBundle];
|
|
if (main) {
|
|
NSString *resource_path = [main resourcePath];
|
|
ret.append_utf8([resource_path UTF8String]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
String OS_MacOS::get_bundle_icon_path() const {
|
|
String ret;
|
|
|
|
NSBundle *main = [NSBundle mainBundle];
|
|
if (main) {
|
|
NSString *icon_path = [[main infoDictionary] objectForKey:@"CFBundleIconFile"];
|
|
if (icon_path) {
|
|
ret.append_utf8([icon_path UTF8String]);
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
// Get properly capitalized engine name for system paths
|
|
String OS_MacOS::get_godot_dir_name() const {
|
|
return String(GODOT_VERSION_SHORT_NAME).capitalize();
|
|
}
|
|
|
|
String OS_MacOS::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
|
|
NSSearchPathDirectory id;
|
|
bool found = true;
|
|
|
|
switch (p_dir) {
|
|
case SYSTEM_DIR_DESKTOP: {
|
|
id = NSDesktopDirectory;
|
|
} break;
|
|
case SYSTEM_DIR_DOCUMENTS: {
|
|
id = NSDocumentDirectory;
|
|
} break;
|
|
case SYSTEM_DIR_DOWNLOADS: {
|
|
id = NSDownloadsDirectory;
|
|
} break;
|
|
case SYSTEM_DIR_MOVIES: {
|
|
id = NSMoviesDirectory;
|
|
} break;
|
|
case SYSTEM_DIR_MUSIC: {
|
|
id = NSMusicDirectory;
|
|
} break;
|
|
case SYSTEM_DIR_PICTURES: {
|
|
id = NSPicturesDirectory;
|
|
} break;
|
|
default: {
|
|
found = false;
|
|
}
|
|
}
|
|
|
|
String ret;
|
|
if (found) {
|
|
NSArray *paths = NSSearchPathForDirectoriesInDomains(id, NSUserDomainMask, YES);
|
|
if (paths && [paths count] >= 1) {
|
|
ret.append_utf8([[paths firstObject] UTF8String]);
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
Error OS_MacOS::shell_show_in_file_manager(String p_path, bool p_open_folder) {
|
|
bool open_folder = false;
|
|
if (DirAccess::dir_exists_absolute(p_path) && p_open_folder) {
|
|
open_folder = true;
|
|
}
|
|
|
|
if (!p_path.begins_with("file://")) {
|
|
p_path = String("file://") + p_path;
|
|
}
|
|
|
|
NSString *string = [NSString stringWithUTF8String:p_path.utf8().get_data()];
|
|
NSURL *uri = [[NSURL alloc] initWithString:[string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]];
|
|
|
|
if (open_folder) {
|
|
[[NSWorkspace sharedWorkspace] openURL:uri];
|
|
} else {
|
|
[[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[ uri ]];
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
Error OS_MacOS::shell_open(const String &p_uri) {
|
|
NSString *string = [NSString stringWithUTF8String:p_uri.utf8().get_data()];
|
|
NSURL *uri = [[NSURL alloc] initWithString:string];
|
|
if (!uri || !uri.scheme || [uri.scheme isEqual:@"file"]) {
|
|
// No scheme set, assume "file://" and escape special characters.
|
|
if (!p_uri.begins_with("file://")) {
|
|
string = [NSString stringWithUTF8String:("file://" + p_uri).utf8().get_data()];
|
|
}
|
|
uri = [[NSURL alloc] initWithString:[string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]];
|
|
}
|
|
[[NSWorkspace sharedWorkspace] openURL:uri];
|
|
return OK;
|
|
}
|
|
|
|
String OS_MacOS::get_locale() const {
|
|
NSString *locale_code = [[NSLocale preferredLanguages] objectAtIndex:0];
|
|
return String([locale_code UTF8String]).replace_char('-', '_');
|
|
}
|
|
|
|
Vector<String> OS_MacOS::get_system_fonts() const {
|
|
HashSet<String> font_names;
|
|
CFArrayRef fonts = CTFontManagerCopyAvailableFontFamilyNames();
|
|
if (fonts) {
|
|
for (CFIndex i = 0; i < CFArrayGetCount(fonts); i++) {
|
|
CFStringRef cf_name = (CFStringRef)CFArrayGetValueAtIndex(fonts, i);
|
|
if (cf_name && (CFStringGetLength(cf_name) > 0) && (CFStringCompare(cf_name, CFSTR("LastResort"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) && (CFStringGetCharacterAtIndex(cf_name, 0) != '.')) {
|
|
NSString *ns_name = (__bridge NSString *)cf_name;
|
|
font_names.insert(String::utf8([ns_name UTF8String]));
|
|
}
|
|
}
|
|
CFRelease(fonts);
|
|
}
|
|
|
|
Vector<String> ret;
|
|
for (const String &E : font_names) {
|
|
ret.push_back(E);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
String OS_MacOS::_get_default_fontname(const String &p_font_name) const {
|
|
String font_name = p_font_name;
|
|
if (font_name.to_lower() == "sans-serif") {
|
|
font_name = "Helvetica";
|
|
} else if (font_name.to_lower() == "serif") {
|
|
font_name = "Times";
|
|
} else if (font_name.to_lower() == "monospace") {
|
|
font_name = "Courier";
|
|
} else if (font_name.to_lower() == "fantasy") {
|
|
font_name = "Papyrus";
|
|
} else if (font_name.to_lower() == "cursive") {
|
|
font_name = "Apple Chancery";
|
|
};
|
|
return font_name;
|
|
}
|
|
|
|
CGFloat OS_MacOS::_weight_to_ct(int p_weight) const {
|
|
if (p_weight < 150) {
|
|
return -0.80;
|
|
} else if (p_weight < 250) {
|
|
return -0.60;
|
|
} else if (p_weight < 350) {
|
|
return -0.40;
|
|
} else if (p_weight < 450) {
|
|
return 0.0;
|
|
} else if (p_weight < 550) {
|
|
return 0.23;
|
|
} else if (p_weight < 650) {
|
|
return 0.30;
|
|
} else if (p_weight < 750) {
|
|
return 0.40;
|
|
} else if (p_weight < 850) {
|
|
return 0.56;
|
|
} else if (p_weight < 925) {
|
|
return 0.62;
|
|
} else {
|
|
return 1.00;
|
|
}
|
|
}
|
|
|
|
CGFloat OS_MacOS::_stretch_to_ct(int p_stretch) const {
|
|
if (p_stretch < 56) {
|
|
return -0.5;
|
|
} else if (p_stretch < 69) {
|
|
return -0.37;
|
|
} else if (p_stretch < 81) {
|
|
return -0.25;
|
|
} else if (p_stretch < 93) {
|
|
return -0.13;
|
|
} else if (p_stretch < 106) {
|
|
return 0.0;
|
|
} else if (p_stretch < 137) {
|
|
return 0.13;
|
|
} else if (p_stretch < 144) {
|
|
return 0.25;
|
|
} else if (p_stretch < 162) {
|
|
return 0.37;
|
|
} else {
|
|
return 0.5;
|
|
}
|
|
}
|
|
|
|
Vector<String> OS_MacOS::get_system_font_path_for_text(const String &p_font_name, const String &p_text, const String &p_locale, const String &p_script, int p_weight, int p_stretch, bool p_italic) const {
|
|
Vector<String> ret;
|
|
String font_name = _get_default_fontname(p_font_name);
|
|
|
|
CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8);
|
|
CTFontSymbolicTraits traits = 0;
|
|
if (p_weight >= 700) {
|
|
traits |= kCTFontBoldTrait;
|
|
}
|
|
if (p_italic) {
|
|
traits |= kCTFontItalicTrait;
|
|
}
|
|
if (p_stretch < 100) {
|
|
traits |= kCTFontCondensedTrait;
|
|
} else if (p_stretch > 100) {
|
|
traits |= kCTFontExpandedTrait;
|
|
}
|
|
|
|
CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits);
|
|
CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
|
|
CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits);
|
|
|
|
CGFloat weight = _weight_to_ct(p_weight);
|
|
CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight);
|
|
CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight);
|
|
|
|
CGFloat stretch = _stretch_to_ct(p_stretch);
|
|
CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch);
|
|
CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch);
|
|
|
|
CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
|
|
CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name);
|
|
CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict);
|
|
|
|
CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes);
|
|
if (font) {
|
|
CTFontRef family = CTFontCreateWithFontDescriptor(font, 0, nullptr);
|
|
if (family) {
|
|
CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, p_text.utf8().get_data(), kCFStringEncodingUTF8);
|
|
CFRange range = CFRangeMake(0, CFStringGetLength(string));
|
|
CTFontRef fallback_family = CTFontCreateForString(family, string, range);
|
|
if (fallback_family) {
|
|
CTFontDescriptorRef fallback_font = CTFontCopyFontDescriptor(fallback_family);
|
|
if (fallback_font) {
|
|
CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fallback_font, kCTFontURLAttribute);
|
|
if (url) {
|
|
NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]];
|
|
ret.push_back(String::utf8([font_path UTF8String]));
|
|
CFRelease(url);
|
|
}
|
|
CFRelease(fallback_font);
|
|
}
|
|
CFRelease(fallback_family);
|
|
}
|
|
CFRelease(string);
|
|
CFRelease(family);
|
|
}
|
|
CFRelease(font);
|
|
}
|
|
|
|
CFRelease(attributes);
|
|
CFRelease(traits_dict);
|
|
CFRelease(sym_traits);
|
|
CFRelease(font_stretch);
|
|
CFRelease(font_weight);
|
|
CFRelease(name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
String OS_MacOS::get_system_font_path(const String &p_font_name, int p_weight, int p_stretch, bool p_italic) const {
|
|
String ret;
|
|
String font_name = _get_default_fontname(p_font_name);
|
|
|
|
CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8);
|
|
|
|
CTFontSymbolicTraits traits = 0;
|
|
if (p_weight > 700) {
|
|
traits |= kCTFontBoldTrait;
|
|
}
|
|
if (p_italic) {
|
|
traits |= kCTFontItalicTrait;
|
|
}
|
|
if (p_stretch < 100) {
|
|
traits |= kCTFontCondensedTrait;
|
|
} else if (p_stretch > 100) {
|
|
traits |= kCTFontExpandedTrait;
|
|
}
|
|
|
|
CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits);
|
|
CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
|
|
CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits);
|
|
|
|
CGFloat weight = _weight_to_ct(p_weight);
|
|
CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight);
|
|
CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight);
|
|
|
|
CGFloat stretch = _stretch_to_ct(p_stretch);
|
|
CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch);
|
|
CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch);
|
|
|
|
CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
|
|
CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name);
|
|
CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict);
|
|
|
|
CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes);
|
|
if (font) {
|
|
CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(font, kCTFontURLAttribute);
|
|
if (url) {
|
|
NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]];
|
|
ret = String::utf8([font_path UTF8String]);
|
|
CFRelease(url);
|
|
}
|
|
CFRelease(font);
|
|
}
|
|
|
|
CFRelease(attributes);
|
|
CFRelease(traits_dict);
|
|
CFRelease(sym_traits);
|
|
CFRelease(font_stretch);
|
|
CFRelease(font_weight);
|
|
CFRelease(name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
String OS_MacOS::get_executable_path() const {
|
|
char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
|
|
int pid = getpid();
|
|
pid_t ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf));
|
|
if (ret <= 0) {
|
|
return OS::get_executable_path();
|
|
} else {
|
|
return String::utf8(pathbuf);
|
|
}
|
|
}
|
|
|
|
Error OS_MacOS::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
|
|
// Use NSWorkspace if path is an .app bundle.
|
|
NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
|
|
NSBundle *bundle = [NSBundle bundleWithURL:url];
|
|
if (bundle) {
|
|
NSMutableArray *arguments = [[NSMutableArray alloc] init];
|
|
for (const String &arg : p_arguments) {
|
|
[arguments addObject:[NSString stringWithUTF8String:arg.utf8().get_data()]];
|
|
}
|
|
#if defined(__x86_64__)
|
|
if (@available(macOS 10.15, *)) {
|
|
#endif
|
|
NSWorkspaceOpenConfiguration *configuration = [[NSWorkspaceOpenConfiguration alloc] init];
|
|
[configuration setArguments:arguments];
|
|
[configuration setCreatesNewApplicationInstance:YES];
|
|
__block dispatch_semaphore_t lock = dispatch_semaphore_create(0);
|
|
__block Error err = ERR_TIMEOUT;
|
|
__block pid_t pid = 0;
|
|
|
|
[[NSWorkspace sharedWorkspace] openApplicationAtURL:url
|
|
configuration:configuration
|
|
completionHandler:^(NSRunningApplication *app, NSError *error) {
|
|
if (error) {
|
|
err = ERR_CANT_FORK;
|
|
NSLog(@"Failed to execute: %@", error.localizedDescription);
|
|
} else {
|
|
pid = [app processIdentifier];
|
|
err = OK;
|
|
}
|
|
dispatch_semaphore_signal(lock);
|
|
}];
|
|
dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, 20000000000)); // 20 sec timeout, wait for app to launch.
|
|
|
|
if (err == OK) {
|
|
if (r_child_id) {
|
|
*r_child_id = (ProcessID)pid;
|
|
}
|
|
}
|
|
|
|
return err;
|
|
#if defined(__x86_64__)
|
|
} else {
|
|
Error err = ERR_TIMEOUT;
|
|
NSError *error = nullptr;
|
|
NSRunningApplication *app = [[NSWorkspace sharedWorkspace] launchApplicationAtURL:url options:NSWorkspaceLaunchNewInstance configuration:[NSDictionary dictionaryWithObject:arguments forKey:NSWorkspaceLaunchConfigurationArguments] error:&error];
|
|
if (error) {
|
|
err = ERR_CANT_FORK;
|
|
NSLog(@"Failed to execute: %@", error.localizedDescription);
|
|
} else {
|
|
if (r_child_id) {
|
|
*r_child_id = (ProcessID)[app processIdentifier];
|
|
}
|
|
err = OK;
|
|
}
|
|
return err;
|
|
}
|
|
#endif
|
|
} else {
|
|
return OS_Unix::create_process(p_path, p_arguments, r_child_id, p_open_console);
|
|
}
|
|
}
|
|
|
|
Error OS_MacOS::create_instance(const List<String> &p_arguments, ProcessID *r_child_id) {
|
|
// If executable is bundled, always execute editor instances as an app bundle to ensure app window is registered and activated correctly.
|
|
NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
|
|
if (nsappname != nil) {
|
|
String path = String::utf8([[[NSBundle mainBundle] bundlePath] UTF8String]);
|
|
#ifdef TOOLS_ENABLED
|
|
if (Engine::get_singleton() && !Engine::get_singleton()->is_project_manager_hint() && !Engine::get_singleton()->is_editor_hint()) {
|
|
// Project started from the editor, inject "path" argument to set instance working directory.
|
|
char cwd[PATH_MAX];
|
|
if (::getcwd(cwd, sizeof(cwd)) != nullptr) {
|
|
List<String> arguments = p_arguments;
|
|
arguments.push_back("--path");
|
|
arguments.push_back(String::utf8(cwd));
|
|
return create_process(path, arguments, r_child_id, false);
|
|
}
|
|
}
|
|
#endif
|
|
return create_process(path, p_arguments, r_child_id, false);
|
|
} else {
|
|
return create_process(get_executable_path(), p_arguments, r_child_id, false);
|
|
}
|
|
}
|
|
|
|
Error OS_MacOS::open_with_program(const String &p_program_path, const List<String> &p_paths) {
|
|
NSURL *app_url = [NSURL fileURLWithPath:@(p_program_path.utf8().get_data())];
|
|
if (!app_url) {
|
|
return ERR_INVALID_PARAMETER;
|
|
}
|
|
|
|
NSBundle *bundle = [NSBundle bundleWithURL:app_url];
|
|
if (!bundle) {
|
|
return OS_Unix::create_process(p_program_path, p_paths);
|
|
}
|
|
|
|
NSMutableArray *urls_to_open = [[NSMutableArray alloc] init];
|
|
for (const String &path : p_paths) {
|
|
NSURL *file_url = [NSURL fileURLWithPath:@(path.utf8().get_data())];
|
|
if (file_url) {
|
|
[urls_to_open addObject:file_url];
|
|
}
|
|
}
|
|
|
|
if ([urls_to_open count] == 0) {
|
|
return ERR_INVALID_PARAMETER;
|
|
}
|
|
|
|
#if defined(__x86_64__)
|
|
if (@available(macOS 10.15, *)) {
|
|
#endif
|
|
NSWorkspaceOpenConfiguration *configuration = [[NSWorkspaceOpenConfiguration alloc] init];
|
|
[configuration setCreatesNewApplicationInstance:NO];
|
|
__block dispatch_semaphore_t lock = dispatch_semaphore_create(0);
|
|
__block Error err = ERR_TIMEOUT;
|
|
|
|
[[NSWorkspace sharedWorkspace] openURLs:urls_to_open
|
|
withApplicationAtURL:app_url
|
|
configuration:configuration
|
|
completionHandler:^(NSRunningApplication *app, NSError *error) {
|
|
if (error) {
|
|
err = ERR_CANT_FORK;
|
|
NSLog(@"Failed to open paths: %@", error.localizedDescription);
|
|
} else {
|
|
err = OK;
|
|
}
|
|
dispatch_semaphore_signal(lock);
|
|
}];
|
|
dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, 20000000000)); // 20 sec timeout, wait for app to launch.
|
|
|
|
return err;
|
|
#if defined(__x86_64__)
|
|
} else {
|
|
NSError *error = nullptr;
|
|
[[NSWorkspace sharedWorkspace] openURLs:urls_to_open withApplicationAtURL:app_url options:NSWorkspaceLaunchDefault configuration:@{} error:&error];
|
|
if (error) {
|
|
return ERR_CANT_FORK;
|
|
}
|
|
return OK;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool OS_MacOS::is_process_running(const ProcessID &p_pid) const {
|
|
NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:(pid_t)p_pid];
|
|
if (!app) {
|
|
return OS_Unix::is_process_running(p_pid);
|
|
}
|
|
|
|
return ![app isTerminated];
|
|
}
|
|
|
|
String OS_MacOS::get_unique_id() const {
|
|
static String serial_number;
|
|
|
|
if (serial_number.is_empty()) {
|
|
io_service_t platform_expert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
|
|
CFStringRef serial_number_cf_string = nullptr;
|
|
if (platform_expert) {
|
|
serial_number_cf_string = (CFStringRef)IORegistryEntryCreateCFProperty(platform_expert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0);
|
|
IOObjectRelease(platform_expert);
|
|
}
|
|
|
|
NSString *serial_number_ns_string = nil;
|
|
if (serial_number_cf_string) {
|
|
serial_number_ns_string = [NSString stringWithString:(__bridge NSString *)serial_number_cf_string];
|
|
CFRelease(serial_number_cf_string);
|
|
}
|
|
|
|
if (serial_number_ns_string) {
|
|
serial_number.append_utf8([serial_number_ns_string UTF8String]);
|
|
}
|
|
}
|
|
|
|
return serial_number;
|
|
}
|
|
|
|
bool OS_MacOS::_check_internal_feature_support(const String &p_feature) {
|
|
if (p_feature == "system_fonts") {
|
|
return true;
|
|
}
|
|
if (p_feature == "pc") {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void OS_MacOS::disable_crash_handler() {
|
|
crash_handler.disable();
|
|
}
|
|
|
|
bool OS_MacOS::is_disable_crash_handler() const {
|
|
return crash_handler.is_disabled();
|
|
}
|
|
|
|
Error OS_MacOS::move_to_trash(const String &p_path) {
|
|
NSFileManager *fm = [NSFileManager defaultManager];
|
|
NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
|
|
NSError *err;
|
|
|
|
if (![fm trashItemAtURL:url resultingItemURL:nil error:&err]) {
|
|
ERR_PRINT("trashItemAtURL error: " + String::utf8(err.localizedDescription.UTF8String));
|
|
return FAILED;
|
|
}
|
|
|
|
return OK;
|
|
}
|
|
|
|
String OS_MacOS::get_system_ca_certificates() {
|
|
CFArrayRef result;
|
|
SecCertificateRef item;
|
|
CFDataRef der;
|
|
|
|
OSStatus ret = SecTrustCopyAnchorCertificates(&result);
|
|
ERR_FAIL_COND_V(ret != noErr, "");
|
|
|
|
CFIndex l = CFArrayGetCount(result);
|
|
String certs;
|
|
PackedByteArray pba;
|
|
for (CFIndex i = 0; i < l; i++) {
|
|
item = (SecCertificateRef)CFArrayGetValueAtIndex(result, i);
|
|
der = SecCertificateCopyData(item);
|
|
int derlen = CFDataGetLength(der);
|
|
if (pba.size() < derlen * 3) {
|
|
pba.resize(derlen * 3);
|
|
}
|
|
size_t b64len = 0;
|
|
Error err = CryptoCore::b64_encode(pba.ptrw(), pba.size(), &b64len, (unsigned char *)CFDataGetBytePtr(der), derlen);
|
|
CFRelease(der);
|
|
ERR_CONTINUE(err != OK);
|
|
// Certificate is bas64 encoded, aka ascii.
|
|
certs += "-----BEGIN CERTIFICATE-----\n" + String::ascii(Span((char *)pba.ptr(), b64len)) + "\n-----END CERTIFICATE-----\n";
|
|
}
|
|
CFRelease(result);
|
|
return certs;
|
|
}
|
|
|
|
OS::PreferredTextureFormat OS_MacOS::get_preferred_texture_format() const {
|
|
// macOS supports both formats on ARM. Prefer S3TC/BPTC
|
|
// for better compatibility with x86 platforms.
|
|
return PREFERRED_TEXTURE_FORMAT_S3TC_BPTC;
|
|
}
|
|
|
|
OS_MacOS::OS_MacOS(const char *p_execpath, int p_argc, char **p_argv) {
|
|
execpath = p_execpath;
|
|
argc = p_argc;
|
|
argv = p_argv;
|
|
|
|
if (is_sandboxed()) {
|
|
// Load security-scoped bookmarks, request access, remove stale or invalid bookmarks.
|
|
NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
|
|
NSMutableArray *new_bookmarks = [[NSMutableArray alloc] init];
|
|
for (id bookmark in bookmarks) {
|
|
NSError *error = nil;
|
|
BOOL isStale = NO;
|
|
NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
|
|
if (!error && !isStale) {
|
|
if ([url startAccessingSecurityScopedResource]) {
|
|
[new_bookmarks addObject:bookmark];
|
|
}
|
|
}
|
|
}
|
|
[[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
|
|
}
|
|
Vector<Logger *> loggers;
|
|
loggers.push_back(memnew(OsLogLogger(NSBundle.mainBundle.bundleIdentifier.UTF8String)));
|
|
loggers.push_back(memnew(UnixTerminalLogger));
|
|
_set_logger(memnew(CompositeLogger(loggers)));
|
|
|
|
#ifdef COREAUDIO_ENABLED
|
|
AudioDriverManager::add_driver(&audio_driver);
|
|
#endif
|
|
|
|
DisplayServerMacOS::register_macos_driver();
|
|
}
|
|
|
|
// MARK: - OS_MacOS_NSApp
|
|
|
|
void OS_MacOS_NSApp::run() {
|
|
[NSApp run];
|
|
}
|
|
|
|
static bool sig_received = false;
|
|
|
|
static void handle_interrupt(int sig) {
|
|
if (sig == SIGINT) {
|
|
sig_received = true;
|
|
}
|
|
}
|
|
|
|
void OS_MacOS_NSApp::start_main() {
|
|
Error err;
|
|
@autoreleasepool {
|
|
err = Main::setup(execpath, argc, argv);
|
|
}
|
|
|
|
if (err == OK) {
|
|
main_started = true;
|
|
|
|
int ret;
|
|
@autoreleasepool {
|
|
ret = Main::start();
|
|
}
|
|
if (ret == EXIT_SUCCESS) {
|
|
if (main_loop) {
|
|
@autoreleasepool {
|
|
main_loop->initialize();
|
|
}
|
|
DisplayServer *ds = DisplayServer::get_singleton();
|
|
DisplayServerMacOS *ds_mac = Object::cast_to<DisplayServerMacOS>(ds);
|
|
|
|
pre_wait_observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
|
|
@autoreleasepool {
|
|
@try {
|
|
if (ds_mac) {
|
|
ds_mac->_process_events(false);
|
|
} else if (ds) {
|
|
ds->process_events();
|
|
}
|
|
#ifdef SDL_ENABLED
|
|
if (joypad_sdl) {
|
|
joypad_sdl->process_events();
|
|
}
|
|
#endif
|
|
|
|
if (Main::iteration() || sig_received) {
|
|
terminate();
|
|
}
|
|
} @catch (NSException *exception) {
|
|
ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String));
|
|
}
|
|
}
|
|
if (wait_timer == nil) {
|
|
CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping.
|
|
}
|
|
});
|
|
CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
|
|
return;
|
|
}
|
|
} else {
|
|
set_exit_code(EXIT_FAILURE);
|
|
}
|
|
} else if (err == ERR_HELP) { // Returned by --help and --version, so success.
|
|
set_exit_code(EXIT_SUCCESS);
|
|
} else {
|
|
set_exit_code(EXIT_FAILURE);
|
|
}
|
|
|
|
terminate();
|
|
}
|
|
|
|
void OS_MacOS_NSApp::terminate() {
|
|
if (pre_wait_observer) {
|
|
CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
|
|
CFRelease(pre_wait_observer);
|
|
pre_wait_observer = nil;
|
|
}
|
|
|
|
should_terminate = true;
|
|
[NSApp terminate:nil];
|
|
}
|
|
|
|
void OS_MacOS_NSApp::cleanup() {
|
|
if (main_loop) {
|
|
main_loop->finalize();
|
|
}
|
|
if (main_started) {
|
|
@autoreleasepool {
|
|
Main::cleanup();
|
|
}
|
|
}
|
|
}
|
|
|
|
OS_MacOS_NSApp::OS_MacOS_NSApp(const char *p_execpath, int p_argc, char **p_argv) :
|
|
OS_MacOS(p_execpath, p_argc, p_argv) {
|
|
// Implicitly create shared NSApplication instance.
|
|
[GodotApplication sharedApplication];
|
|
|
|
// In case we are unbundled, make us a proper UI application.
|
|
[NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
|
|
|
|
// Menu bar setup must go between sharedApplication above and
|
|
// finishLaunching below, in order to properly emulate the behavior
|
|
// of NSApplicationMain.
|
|
|
|
NSMenu *main_menu = [[NSMenu alloc] initWithTitle:@""];
|
|
[NSApp setMainMenu:main_menu];
|
|
|
|
delegate = [[GodotApplicationDelegate alloc] initWithOS:this];
|
|
ERR_FAIL_NULL(delegate);
|
|
[NSApp setDelegate:delegate];
|
|
[NSApp registerUserInterfaceItemSearchHandler:delegate];
|
|
|
|
struct sigaction action;
|
|
memset(&action, 0, sizeof(action));
|
|
action.sa_handler = handle_interrupt;
|
|
sigaction(SIGINT, &action, nullptr);
|
|
}
|
|
|
|
// MARK: - OS_MacOS_Embedded
|
|
|
|
#ifdef DEBUG_ENABLED
|
|
|
|
void OS_MacOS_Embedded::run() {
|
|
CFRunLoopGetCurrent();
|
|
|
|
@autoreleasepool {
|
|
Error err = Main::setup(execpath, argc, argv);
|
|
if (err != OK) {
|
|
if (err == ERR_HELP) {
|
|
return set_exit_code(EXIT_SUCCESS);
|
|
}
|
|
return set_exit_code(EXIT_FAILURE);
|
|
}
|
|
}
|
|
|
|
int ret;
|
|
@autoreleasepool {
|
|
ret = Main::start();
|
|
}
|
|
|
|
DisplayServerEmbedded *ds = Object::cast_to<DisplayServerEmbedded>(DisplayServer::get_singleton());
|
|
if (!ds) {
|
|
ERR_FAIL_MSG("DisplayServerEmbedded is not initialized.");
|
|
}
|
|
|
|
if (ds && ret == EXIT_SUCCESS && main_loop) {
|
|
@autoreleasepool {
|
|
main_loop->initialize();
|
|
}
|
|
|
|
while (true) {
|
|
@autoreleasepool {
|
|
@try {
|
|
ds->process_events();
|
|
|
|
if (Main::iteration()) {
|
|
break;
|
|
}
|
|
|
|
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, 0);
|
|
} @catch (NSException *exception) {
|
|
ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String));
|
|
}
|
|
}
|
|
}
|
|
|
|
main_loop->finalize();
|
|
}
|
|
|
|
Main::cleanup();
|
|
}
|
|
|
|
OS_MacOS_Embedded::OS_MacOS_Embedded(const char *p_execpath, int p_argc, char **p_argv) :
|
|
OS_MacOS(p_execpath, p_argc, p_argv) {
|
|
DisplayServerEmbedded::register_embedded_driver();
|
|
}
|
|
|
|
#endif
|