os_macos.mm 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233
  1. /**************************************************************************/
  2. /* os_macos.mm */
  3. /**************************************************************************/
  4. /* This file is part of: */
  5. /* GODOT ENGINE */
  6. /* https://godotengine.org */
  7. /**************************************************************************/
  8. /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
  9. /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
  10. /* */
  11. /* Permission is hereby granted, free of charge, to any person obtaining */
  12. /* a copy of this software and associated documentation files (the */
  13. /* "Software"), to deal in the Software without restriction, including */
  14. /* without limitation the rights to use, copy, modify, merge, publish, */
  15. /* distribute, sublicense, and/or sell copies of the Software, and to */
  16. /* permit persons to whom the Software is furnished to do so, subject to */
  17. /* the following conditions: */
  18. /* */
  19. /* The above copyright notice and this permission notice shall be */
  20. /* included in all copies or substantial portions of the Software. */
  21. /* */
  22. /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
  23. /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
  24. /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
  25. /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
  26. /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
  27. /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
  28. /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
  29. /**************************************************************************/
  30. #import "os_macos.h"
  31. #import "dir_access_macos.h"
  32. #ifdef DEBUG_ENABLED
  33. #import "display_server_embedded.h"
  34. #endif
  35. #import "display_server_macos.h"
  36. #import "godot_application.h"
  37. #import "godot_application_delegate.h"
  38. #include "core/crypto/crypto_core.h"
  39. #include "core/version_generated.gen.h"
  40. #include "drivers/apple/os_log_logger.h"
  41. #include "main/main.h"
  42. #ifdef SDL_ENABLED
  43. #include "drivers/sdl/joypad_sdl.h"
  44. #endif
  45. #include <dlfcn.h>
  46. #include <libproc.h>
  47. #import <mach-o/dyld.h>
  48. #include <os/log.h>
  49. #include <sys/sysctl.h>
  50. void OS_MacOS::add_frame_delay(bool p_can_draw, bool p_wake_for_events) {
  51. if (p_wake_for_events) {
  52. uint64_t delay = get_frame_delay(p_can_draw);
  53. if (delay == 0) {
  54. return;
  55. }
  56. if (wait_timer) {
  57. CFRunLoopTimerInvalidate(wait_timer);
  58. CFRelease(wait_timer);
  59. }
  60. wait_timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, CFAbsoluteTimeGetCurrent() + (double(delay) / 1000000.0), 0, 0, 0,
  61. ^(CFRunLoopTimerRef timer) {
  62. CFRunLoopTimerInvalidate(wait_timer);
  63. CFRelease(wait_timer);
  64. wait_timer = nil;
  65. });
  66. CFRunLoopAddTimer(CFRunLoopGetCurrent(), wait_timer, kCFRunLoopCommonModes);
  67. return;
  68. }
  69. OS_Unix::add_frame_delay(p_can_draw, p_wake_for_events);
  70. }
  71. void OS_MacOS::initialize() {
  72. crash_handler.initialize();
  73. initialize_core();
  74. }
  75. String OS_MacOS::get_model_name() const {
  76. char buffer[256];
  77. size_t buffer_len = 256;
  78. if (sysctlbyname("hw.model", &buffer, &buffer_len, nullptr, 0) == 0 && buffer_len != 0) {
  79. return String::utf8(buffer, buffer_len);
  80. }
  81. return OS_Unix::get_model_name();
  82. }
  83. String OS_MacOS::get_processor_name() const {
  84. char buffer[256];
  85. size_t buffer_len = 256;
  86. if (sysctlbyname("machdep.cpu.brand_string", &buffer, &buffer_len, nullptr, 0) == 0) {
  87. return String::utf8(buffer, buffer_len);
  88. }
  89. ERR_FAIL_V_MSG("", String("Couldn't get the CPU model name. Returning an empty string."));
  90. }
  91. bool OS_MacOS::is_sandboxed() const {
  92. return has_environment("APP_SANDBOX_CONTAINER_ID");
  93. }
  94. bool OS_MacOS::request_permission(const String &p_name) {
  95. if (@available(macOS 10.15, *)) {
  96. if (p_name == "macos.permission.RECORD_SCREEN") {
  97. if (CGPreflightScreenCaptureAccess()) {
  98. return true;
  99. } else {
  100. CGRequestScreenCaptureAccess();
  101. return false;
  102. }
  103. }
  104. } else {
  105. if (p_name == "macos.permission.RECORD_SCREEN") {
  106. return true;
  107. }
  108. }
  109. return false;
  110. }
  111. Vector<String> OS_MacOS::get_granted_permissions() const {
  112. Vector<String> ret;
  113. if (@available(macOS 10.15, *)) {
  114. if (CGPreflightScreenCaptureAccess()) {
  115. ret.push_back("macos.permission.RECORD_SCREEN");
  116. }
  117. } else {
  118. ret.push_back("macos.permission.RECORD_SCREEN");
  119. }
  120. if (is_sandboxed()) {
  121. NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
  122. for (id bookmark in bookmarks) {
  123. NSError *error = nil;
  124. BOOL isStale = NO;
  125. NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
  126. if (!error && !isStale) {
  127. String url_string = String::utf8([[url path] UTF8String]);
  128. ret.push_back(url_string);
  129. }
  130. }
  131. }
  132. return ret;
  133. }
  134. void OS_MacOS::revoke_granted_permissions() {
  135. if (is_sandboxed()) {
  136. [[NSUserDefaults standardUserDefaults] setObject:nil forKey:@"sec_bookmarks"];
  137. }
  138. }
  139. #if TOOLS_ENABLED
  140. // Function to check if a debugger is attached to the current process
  141. bool OS_MacOS::is_debugger_attached() {
  142. int mib[4];
  143. struct kinfo_proc info{};
  144. size_t size = sizeof(info);
  145. // Initialize the flags so that, if sysctl fails, info.kp_proc.p_flag will be 0.
  146. info.kp_proc.p_flag = 0;
  147. // Initialize mib, which tells sysctl the info we want, in this case we're looking for information
  148. // about a specific process ID.
  149. mib[0] = CTL_KERN;
  150. mib[1] = KERN_PROC;
  151. mib[2] = KERN_PROC_PID;
  152. mib[3] = getpid();
  153. if (sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, nullptr, 0) != 0) {
  154. perror("sysctl");
  155. return false;
  156. }
  157. return (info.kp_proc.p_flag & P_TRACED) != 0;
  158. }
  159. void OS_MacOS::wait_for_debugger(uint32_t p_msec) {
  160. if (p_msec == 0) {
  161. return;
  162. }
  163. CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
  164. CFTimeInterval wait_time = p_msec / 1000.0;
  165. NSTimer *timer = [NSTimer timerWithTimeInterval:0.100
  166. repeats:YES
  167. block:^(NSTimer *t) {
  168. if (is_debugger_attached() || CFAbsoluteTimeGetCurrent() > start + wait_time) {
  169. [NSApp stopModalWithCode:NSModalResponseContinue];
  170. [t invalidate];
  171. }
  172. }];
  173. [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSModalPanelRunLoopMode];
  174. pid_t pid = getpid();
  175. alert(vformat("Attach debugger to pid: %d", pid));
  176. print("continue...");
  177. }
  178. #endif
  179. void OS_MacOS::initialize_core() {
  180. OS_Unix::initialize_core();
  181. DirAccess::make_default<DirAccessMacOS>(DirAccess::ACCESS_RESOURCES);
  182. DirAccess::make_default<DirAccessMacOS>(DirAccess::ACCESS_USERDATA);
  183. DirAccess::make_default<DirAccessMacOS>(DirAccess::ACCESS_FILESYSTEM);
  184. }
  185. void OS_MacOS::finalize() {
  186. if (is_sandboxed()) {
  187. NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
  188. for (id bookmark in bookmarks) {
  189. NSError *error = nil;
  190. BOOL isStale = NO;
  191. NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
  192. if (!error && !isStale) {
  193. [url stopAccessingSecurityScopedResource];
  194. }
  195. }
  196. }
  197. #ifdef COREMIDI_ENABLED
  198. midi_driver.close();
  199. #endif
  200. delete_main_loop();
  201. #ifdef SDL_ENABLED
  202. if (joypad_sdl) {
  203. memdelete(joypad_sdl);
  204. }
  205. #endif
  206. }
  207. void OS_MacOS::initialize_joypads() {
  208. #ifdef SDL_ENABLED
  209. joypad_sdl = memnew(JoypadSDL());
  210. if (joypad_sdl->initialize() != OK) {
  211. ERR_PRINT("Couldn't initialize SDL joypad input driver.");
  212. memdelete(joypad_sdl);
  213. joypad_sdl = nullptr;
  214. }
  215. #endif
  216. }
  217. void OS_MacOS::set_main_loop(MainLoop *p_main_loop) {
  218. main_loop = p_main_loop;
  219. }
  220. void OS_MacOS::delete_main_loop() {
  221. if (!main_loop) {
  222. return;
  223. }
  224. memdelete(main_loop);
  225. main_loop = nullptr;
  226. }
  227. void OS_MacOS::set_cmdline_platform_args(const List<String> &p_args) {
  228. launch_service_args = p_args;
  229. }
  230. List<String> OS_MacOS::get_cmdline_platform_args() const {
  231. return launch_service_args;
  232. }
  233. void OS_MacOS::load_shell_environment() const {
  234. static bool shell_env_loaded = false;
  235. if (unlikely(!shell_env_loaded)) {
  236. shell_env_loaded = true;
  237. if (OS::get_singleton()->has_environment("TERM") || OS::get_singleton()->has_environment("__GODOT_SHELL_ENV_SET")) {
  238. return; // Already started from terminal, or other the instance with the shell environment, do nothing.
  239. }
  240. String pipe;
  241. List<String> args;
  242. args.push_back("-c");
  243. args.push_back(". /etc/zshrc;. /etc/zprofile;. ~/.zshenv;. ~/.zshrc;. ~/.zprofile;env");
  244. Error err = OS::get_singleton()->execute("zsh", args, &pipe);
  245. if (err == OK) {
  246. Vector<String> env_vars = pipe.split("\n");
  247. for (const String &E : env_vars) {
  248. Vector<String> tags = E.split("=", 2);
  249. 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")) {
  250. continue;
  251. }
  252. OS::get_singleton()->set_environment(tags[0], tags[1]);
  253. }
  254. }
  255. OS::get_singleton()->set_environment("__GODOT_SHELL_ENV_SET", "1");
  256. }
  257. }
  258. String OS_MacOS::get_name() const {
  259. return "macOS";
  260. }
  261. String OS_MacOS::get_distribution_name() const {
  262. return get_name();
  263. }
  264. String OS_MacOS::get_version() const {
  265. NSOperatingSystemVersion ver = [NSProcessInfo processInfo].operatingSystemVersion;
  266. return vformat("%d.%d.%d", (int64_t)ver.majorVersion, (int64_t)ver.minorVersion, (int64_t)ver.patchVersion);
  267. }
  268. String OS_MacOS::get_version_alias() const {
  269. NSOperatingSystemVersion ver = [NSProcessInfo processInfo].operatingSystemVersion;
  270. String macos_string;
  271. if (ver.majorVersion == 15) {
  272. macos_string += "Sequoia";
  273. } else if (ver.majorVersion == 14) {
  274. macos_string += "Sonoma";
  275. } else if (ver.majorVersion == 13) {
  276. macos_string += "Ventura";
  277. } else if (ver.majorVersion == 12) {
  278. macos_string += "Monterey";
  279. } else if (ver.majorVersion == 11 || (ver.majorVersion == 10 && ver.minorVersion == 16)) {
  280. // Big Sur was 10.16 during beta, but it became 11 for the stable version.
  281. macos_string += "Big Sur";
  282. } else if (ver.majorVersion == 10 && ver.minorVersion == 15) {
  283. macos_string += "Catalina";
  284. } else if (ver.majorVersion == 10 && ver.minorVersion == 14) {
  285. macos_string += "Mojave";
  286. } else if (ver.majorVersion == 10 && ver.minorVersion == 13) {
  287. macos_string += "High Sierra";
  288. } else {
  289. macos_string += "Unknown";
  290. }
  291. // macOS versions older than 10.13 cannot run Godot.
  292. return vformat("%s (%s)", macos_string, get_version());
  293. }
  294. void OS_MacOS::alert(const String &p_alert, const String &p_title) {
  295. NSAlert *window = [[NSAlert alloc] init];
  296. NSString *ns_title = [NSString stringWithUTF8String:p_title.utf8().get_data()];
  297. NSString *ns_alert = [NSString stringWithUTF8String:p_alert.utf8().get_data()];
  298. NSTextField *text_field = [NSTextField labelWithString:ns_alert];
  299. [text_field setAlignment:NSTextAlignmentCenter];
  300. [window addButtonWithTitle:@"OK"];
  301. [window setMessageText:ns_title];
  302. [window setAccessoryView:text_field];
  303. [window setAlertStyle:NSAlertStyleWarning];
  304. id key_window = [[NSApplication sharedApplication] keyWindow];
  305. [window runModal];
  306. if (key_window) {
  307. [key_window makeKeyAndOrderFront:nil];
  308. }
  309. }
  310. _FORCE_INLINE_ String OS_MacOS::get_framework_executable(const String &p_path) {
  311. Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
  312. // Read framework bundle to get executable name.
  313. NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
  314. NSBundle *bundle = [NSBundle bundleWithURL:url];
  315. if (bundle) {
  316. String exe_path = String::utf8([[bundle executablePath] UTF8String]);
  317. if (da->file_exists(exe_path)) {
  318. return exe_path;
  319. }
  320. }
  321. // Try default executable name (invalid framework).
  322. if (da->dir_exists(p_path) && da->file_exists(p_path.path_join(p_path.get_file().get_basename()))) {
  323. return p_path.path_join(p_path.get_file().get_basename());
  324. }
  325. // Not a framework, try loading as .dylib.
  326. return p_path;
  327. }
  328. Error OS_MacOS::open_dynamic_library(const String &p_path, void *&p_library_handle, GDExtensionData *p_data) {
  329. String path = get_framework_executable(p_path);
  330. if (!FileAccess::exists(path)) {
  331. // Load .dylib or framework from within the executable path.
  332. path = get_framework_executable(get_executable_path().get_base_dir().path_join(p_path.get_file()));
  333. }
  334. if (!FileAccess::exists(path)) {
  335. // Load .dylib or framework from a standard macOS location.
  336. path = get_framework_executable(get_executable_path().get_base_dir().path_join("../Frameworks").path_join(p_path.get_file()));
  337. }
  338. if (!FileAccess::exists(path)) {
  339. // Try using path as is. macOS system libraries with `/usr/lib/*` path do not exist as physical files and are loaded from shared cache.
  340. path = p_path;
  341. }
  342. p_library_handle = dlopen(path.utf8().get_data(), RTLD_NOW);
  343. ERR_FAIL_NULL_V_MSG(p_library_handle, ERR_CANT_OPEN, vformat("Can't open dynamic library: %s. Error: %s.", p_path, dlerror()));
  344. if (p_data != nullptr && p_data->r_resolved_path != nullptr) {
  345. *p_data->r_resolved_path = path;
  346. }
  347. return OK;
  348. }
  349. MainLoop *OS_MacOS::get_main_loop() const {
  350. return main_loop;
  351. }
  352. String OS_MacOS::get_config_path() const {
  353. if (has_environment("HOME")) {
  354. return get_environment("HOME").path_join("Library/Application Support");
  355. }
  356. return ".";
  357. }
  358. String OS_MacOS::get_data_path() const {
  359. return get_config_path();
  360. }
  361. String OS_MacOS::get_cache_path() const {
  362. if (has_environment("HOME")) {
  363. return get_environment("HOME").path_join("Library/Caches");
  364. }
  365. return get_config_path();
  366. }
  367. String OS_MacOS::get_temp_path() const {
  368. static String ret;
  369. if (ret.is_empty()) {
  370. NSURL *url = [NSURL fileURLWithPath:NSTemporaryDirectory()
  371. isDirectory:YES];
  372. if (url) {
  373. ret = String::utf8([url.path UTF8String]);
  374. ret = ret.trim_prefix("file://");
  375. }
  376. }
  377. return ret;
  378. }
  379. String OS_MacOS::get_bundle_resource_dir() const {
  380. String ret;
  381. NSBundle *main = [NSBundle mainBundle];
  382. if (main) {
  383. NSString *resource_path = [main resourcePath];
  384. ret.append_utf8([resource_path UTF8String]);
  385. }
  386. return ret;
  387. }
  388. String OS_MacOS::get_bundle_icon_path() const {
  389. String ret;
  390. NSBundle *main = [NSBundle mainBundle];
  391. if (main) {
  392. NSString *icon_path = [[main infoDictionary] objectForKey:@"CFBundleIconFile"];
  393. if (icon_path) {
  394. ret.append_utf8([icon_path UTF8String]);
  395. }
  396. }
  397. return ret;
  398. }
  399. // Get properly capitalized engine name for system paths
  400. String OS_MacOS::get_godot_dir_name() const {
  401. return String(GODOT_VERSION_SHORT_NAME).capitalize();
  402. }
  403. String OS_MacOS::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
  404. NSSearchPathDirectory id;
  405. bool found = true;
  406. switch (p_dir) {
  407. case SYSTEM_DIR_DESKTOP: {
  408. id = NSDesktopDirectory;
  409. } break;
  410. case SYSTEM_DIR_DOCUMENTS: {
  411. id = NSDocumentDirectory;
  412. } break;
  413. case SYSTEM_DIR_DOWNLOADS: {
  414. id = NSDownloadsDirectory;
  415. } break;
  416. case SYSTEM_DIR_MOVIES: {
  417. id = NSMoviesDirectory;
  418. } break;
  419. case SYSTEM_DIR_MUSIC: {
  420. id = NSMusicDirectory;
  421. } break;
  422. case SYSTEM_DIR_PICTURES: {
  423. id = NSPicturesDirectory;
  424. } break;
  425. default: {
  426. found = false;
  427. }
  428. }
  429. String ret;
  430. if (found) {
  431. NSArray *paths = NSSearchPathForDirectoriesInDomains(id, NSUserDomainMask, YES);
  432. if (paths && [paths count] >= 1) {
  433. ret.append_utf8([[paths firstObject] UTF8String]);
  434. }
  435. }
  436. return ret;
  437. }
  438. Error OS_MacOS::shell_show_in_file_manager(String p_path, bool p_open_folder) {
  439. bool open_folder = false;
  440. if (DirAccess::dir_exists_absolute(p_path) && p_open_folder) {
  441. open_folder = true;
  442. }
  443. if (!p_path.begins_with("file://")) {
  444. p_path = String("file://") + p_path;
  445. }
  446. NSString *string = [NSString stringWithUTF8String:p_path.utf8().get_data()];
  447. NSURL *uri = [[NSURL alloc] initWithString:[string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]];
  448. if (open_folder) {
  449. [[NSWorkspace sharedWorkspace] openURL:uri];
  450. } else {
  451. [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[ uri ]];
  452. }
  453. return OK;
  454. }
  455. Error OS_MacOS::shell_open(const String &p_uri) {
  456. NSString *string = [NSString stringWithUTF8String:p_uri.utf8().get_data()];
  457. NSURL *uri = [[NSURL alloc] initWithString:string];
  458. if (!uri || !uri.scheme || [uri.scheme isEqual:@"file"]) {
  459. // No scheme set, assume "file://" and escape special characters.
  460. if (!p_uri.begins_with("file://")) {
  461. string = [NSString stringWithUTF8String:("file://" + p_uri).utf8().get_data()];
  462. }
  463. uri = [[NSURL alloc] initWithString:[string stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]]];
  464. }
  465. [[NSWorkspace sharedWorkspace] openURL:uri];
  466. return OK;
  467. }
  468. String OS_MacOS::get_locale() const {
  469. NSString *locale_code = [[NSLocale preferredLanguages] objectAtIndex:0];
  470. return String([locale_code UTF8String]).replace_char('-', '_');
  471. }
  472. Vector<String> OS_MacOS::get_system_fonts() const {
  473. HashSet<String> font_names;
  474. CFArrayRef fonts = CTFontManagerCopyAvailableFontFamilyNames();
  475. if (fonts) {
  476. for (CFIndex i = 0; i < CFArrayGetCount(fonts); i++) {
  477. CFStringRef cf_name = (CFStringRef)CFArrayGetValueAtIndex(fonts, i);
  478. if (cf_name && (CFStringGetLength(cf_name) > 0) && (CFStringCompare(cf_name, CFSTR("LastResort"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) && (CFStringGetCharacterAtIndex(cf_name, 0) != '.')) {
  479. NSString *ns_name = (__bridge NSString *)cf_name;
  480. font_names.insert(String::utf8([ns_name UTF8String]));
  481. }
  482. }
  483. CFRelease(fonts);
  484. }
  485. Vector<String> ret;
  486. for (const String &E : font_names) {
  487. ret.push_back(E);
  488. }
  489. return ret;
  490. }
  491. String OS_MacOS::_get_default_fontname(const String &p_font_name) const {
  492. String font_name = p_font_name;
  493. if (font_name.to_lower() == "sans-serif") {
  494. font_name = "Helvetica";
  495. } else if (font_name.to_lower() == "serif") {
  496. font_name = "Times";
  497. } else if (font_name.to_lower() == "monospace") {
  498. font_name = "Courier";
  499. } else if (font_name.to_lower() == "fantasy") {
  500. font_name = "Papyrus";
  501. } else if (font_name.to_lower() == "cursive") {
  502. font_name = "Apple Chancery";
  503. };
  504. return font_name;
  505. }
  506. CGFloat OS_MacOS::_weight_to_ct(int p_weight) const {
  507. if (p_weight < 150) {
  508. return -0.80;
  509. } else if (p_weight < 250) {
  510. return -0.60;
  511. } else if (p_weight < 350) {
  512. return -0.40;
  513. } else if (p_weight < 450) {
  514. return 0.0;
  515. } else if (p_weight < 550) {
  516. return 0.23;
  517. } else if (p_weight < 650) {
  518. return 0.30;
  519. } else if (p_weight < 750) {
  520. return 0.40;
  521. } else if (p_weight < 850) {
  522. return 0.56;
  523. } else if (p_weight < 925) {
  524. return 0.62;
  525. } else {
  526. return 1.00;
  527. }
  528. }
  529. CGFloat OS_MacOS::_stretch_to_ct(int p_stretch) const {
  530. if (p_stretch < 56) {
  531. return -0.5;
  532. } else if (p_stretch < 69) {
  533. return -0.37;
  534. } else if (p_stretch < 81) {
  535. return -0.25;
  536. } else if (p_stretch < 93) {
  537. return -0.13;
  538. } else if (p_stretch < 106) {
  539. return 0.0;
  540. } else if (p_stretch < 137) {
  541. return 0.13;
  542. } else if (p_stretch < 144) {
  543. return 0.25;
  544. } else if (p_stretch < 162) {
  545. return 0.37;
  546. } else {
  547. return 0.5;
  548. }
  549. }
  550. 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 {
  551. Vector<String> ret;
  552. String font_name = _get_default_fontname(p_font_name);
  553. CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8);
  554. CTFontSymbolicTraits traits = 0;
  555. if (p_weight >= 700) {
  556. traits |= kCTFontBoldTrait;
  557. }
  558. if (p_italic) {
  559. traits |= kCTFontItalicTrait;
  560. }
  561. if (p_stretch < 100) {
  562. traits |= kCTFontCondensedTrait;
  563. } else if (p_stretch > 100) {
  564. traits |= kCTFontExpandedTrait;
  565. }
  566. CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits);
  567. CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
  568. CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits);
  569. CGFloat weight = _weight_to_ct(p_weight);
  570. CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight);
  571. CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight);
  572. CGFloat stretch = _stretch_to_ct(p_stretch);
  573. CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch);
  574. CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch);
  575. CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
  576. CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name);
  577. CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict);
  578. CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes);
  579. if (font) {
  580. CTFontRef family = CTFontCreateWithFontDescriptor(font, 0, nullptr);
  581. if (family) {
  582. CFStringRef string = CFStringCreateWithCString(kCFAllocatorDefault, p_text.utf8().get_data(), kCFStringEncodingUTF8);
  583. CFRange range = CFRangeMake(0, CFStringGetLength(string));
  584. CTFontRef fallback_family = CTFontCreateForString(family, string, range);
  585. if (fallback_family) {
  586. CTFontDescriptorRef fallback_font = CTFontCopyFontDescriptor(fallback_family);
  587. if (fallback_font) {
  588. CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(fallback_font, kCTFontURLAttribute);
  589. if (url) {
  590. NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]];
  591. ret.push_back(String::utf8([font_path UTF8String]));
  592. CFRelease(url);
  593. }
  594. CFRelease(fallback_font);
  595. }
  596. CFRelease(fallback_family);
  597. }
  598. CFRelease(string);
  599. CFRelease(family);
  600. }
  601. CFRelease(font);
  602. }
  603. CFRelease(attributes);
  604. CFRelease(traits_dict);
  605. CFRelease(sym_traits);
  606. CFRelease(font_stretch);
  607. CFRelease(font_weight);
  608. CFRelease(name);
  609. return ret;
  610. }
  611. String OS_MacOS::get_system_font_path(const String &p_font_name, int p_weight, int p_stretch, bool p_italic) const {
  612. String ret;
  613. String font_name = _get_default_fontname(p_font_name);
  614. CFStringRef name = CFStringCreateWithCString(kCFAllocatorDefault, font_name.utf8().get_data(), kCFStringEncodingUTF8);
  615. CTFontSymbolicTraits traits = 0;
  616. if (p_weight > 700) {
  617. traits |= kCTFontBoldTrait;
  618. }
  619. if (p_italic) {
  620. traits |= kCTFontItalicTrait;
  621. }
  622. if (p_stretch < 100) {
  623. traits |= kCTFontCondensedTrait;
  624. } else if (p_stretch > 100) {
  625. traits |= kCTFontExpandedTrait;
  626. }
  627. CFNumberRef sym_traits = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &traits);
  628. CFMutableDictionaryRef traits_dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
  629. CFDictionaryAddValue(traits_dict, kCTFontSymbolicTrait, sym_traits);
  630. CGFloat weight = _weight_to_ct(p_weight);
  631. CFNumberRef font_weight = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &weight);
  632. CFDictionaryAddValue(traits_dict, kCTFontWeightTrait, font_weight);
  633. CGFloat stretch = _stretch_to_ct(p_stretch);
  634. CFNumberRef font_stretch = CFNumberCreate(kCFAllocatorDefault, kCFNumberCGFloatType, &stretch);
  635. CFDictionaryAddValue(traits_dict, kCTFontWidthTrait, font_stretch);
  636. CFMutableDictionaryRef attributes = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, nullptr, nullptr);
  637. CFDictionaryAddValue(attributes, kCTFontFamilyNameAttribute, name);
  638. CFDictionaryAddValue(attributes, kCTFontTraitsAttribute, traits_dict);
  639. CTFontDescriptorRef font = CTFontDescriptorCreateWithAttributes(attributes);
  640. if (font) {
  641. CFURLRef url = (CFURLRef)CTFontDescriptorCopyAttribute(font, kCTFontURLAttribute);
  642. if (url) {
  643. NSString *font_path = [NSString stringWithString:[(__bridge NSURL *)url path]];
  644. ret = String::utf8([font_path UTF8String]);
  645. CFRelease(url);
  646. }
  647. CFRelease(font);
  648. }
  649. CFRelease(attributes);
  650. CFRelease(traits_dict);
  651. CFRelease(sym_traits);
  652. CFRelease(font_stretch);
  653. CFRelease(font_weight);
  654. CFRelease(name);
  655. return ret;
  656. }
  657. String OS_MacOS::get_executable_path() const {
  658. char pathbuf[PROC_PIDPATHINFO_MAXSIZE];
  659. int pid = getpid();
  660. pid_t ret = proc_pidpath(pid, pathbuf, sizeof(pathbuf));
  661. if (ret <= 0) {
  662. return OS::get_executable_path();
  663. } else {
  664. return String::utf8(pathbuf);
  665. }
  666. }
  667. Error OS_MacOS::create_process(const String &p_path, const List<String> &p_arguments, ProcessID *r_child_id, bool p_open_console) {
  668. // Use NSWorkspace if path is an .app bundle.
  669. NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
  670. NSBundle *bundle = [NSBundle bundleWithURL:url];
  671. if (bundle) {
  672. NSMutableArray *arguments = [[NSMutableArray alloc] init];
  673. for (const String &arg : p_arguments) {
  674. [arguments addObject:[NSString stringWithUTF8String:arg.utf8().get_data()]];
  675. }
  676. #if defined(__x86_64__)
  677. if (@available(macOS 10.15, *)) {
  678. #endif
  679. NSWorkspaceOpenConfiguration *configuration = [[NSWorkspaceOpenConfiguration alloc] init];
  680. [configuration setArguments:arguments];
  681. [configuration setCreatesNewApplicationInstance:YES];
  682. __block dispatch_semaphore_t lock = dispatch_semaphore_create(0);
  683. __block Error err = ERR_TIMEOUT;
  684. __block pid_t pid = 0;
  685. [[NSWorkspace sharedWorkspace] openApplicationAtURL:url
  686. configuration:configuration
  687. completionHandler:^(NSRunningApplication *app, NSError *error) {
  688. if (error) {
  689. err = ERR_CANT_FORK;
  690. NSLog(@"Failed to execute: %@", error.localizedDescription);
  691. } else {
  692. pid = [app processIdentifier];
  693. err = OK;
  694. }
  695. dispatch_semaphore_signal(lock);
  696. }];
  697. dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, 20000000000)); // 20 sec timeout, wait for app to launch.
  698. if (err == OK) {
  699. if (r_child_id) {
  700. *r_child_id = (ProcessID)pid;
  701. }
  702. }
  703. return err;
  704. #if defined(__x86_64__)
  705. } else {
  706. Error err = ERR_TIMEOUT;
  707. NSError *error = nullptr;
  708. NSRunningApplication *app = [[NSWorkspace sharedWorkspace] launchApplicationAtURL:url options:NSWorkspaceLaunchNewInstance configuration:[NSDictionary dictionaryWithObject:arguments forKey:NSWorkspaceLaunchConfigurationArguments] error:&error];
  709. if (error) {
  710. err = ERR_CANT_FORK;
  711. NSLog(@"Failed to execute: %@", error.localizedDescription);
  712. } else {
  713. if (r_child_id) {
  714. *r_child_id = (ProcessID)[app processIdentifier];
  715. }
  716. err = OK;
  717. }
  718. return err;
  719. }
  720. #endif
  721. } else {
  722. return OS_Unix::create_process(p_path, p_arguments, r_child_id, p_open_console);
  723. }
  724. }
  725. Error OS_MacOS::create_instance(const List<String> &p_arguments, ProcessID *r_child_id) {
  726. // If executable is bundled, always execute editor instances as an app bundle to ensure app window is registered and activated correctly.
  727. NSString *nsappname = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"];
  728. if (nsappname != nil) {
  729. String path = String::utf8([[[NSBundle mainBundle] bundlePath] UTF8String]);
  730. #ifdef TOOLS_ENABLED
  731. if (Engine::get_singleton() && !Engine::get_singleton()->is_project_manager_hint() && !Engine::get_singleton()->is_editor_hint()) {
  732. // Project started from the editor, inject "path" argument to set instance working directory.
  733. char cwd[PATH_MAX];
  734. if (::getcwd(cwd, sizeof(cwd)) != nullptr) {
  735. List<String> arguments = p_arguments;
  736. arguments.push_back("--path");
  737. arguments.push_back(String::utf8(cwd));
  738. return create_process(path, arguments, r_child_id, false);
  739. }
  740. }
  741. #endif
  742. return create_process(path, p_arguments, r_child_id, false);
  743. } else {
  744. return create_process(get_executable_path(), p_arguments, r_child_id, false);
  745. }
  746. }
  747. Error OS_MacOS::open_with_program(const String &p_program_path, const List<String> &p_paths) {
  748. NSURL *app_url = [NSURL fileURLWithPath:@(p_program_path.utf8().get_data())];
  749. if (!app_url) {
  750. return ERR_INVALID_PARAMETER;
  751. }
  752. NSBundle *bundle = [NSBundle bundleWithURL:app_url];
  753. if (!bundle) {
  754. return OS_Unix::create_process(p_program_path, p_paths);
  755. }
  756. NSMutableArray *urls_to_open = [[NSMutableArray alloc] init];
  757. for (const String &path : p_paths) {
  758. NSURL *file_url = [NSURL fileURLWithPath:@(path.utf8().get_data())];
  759. if (file_url) {
  760. [urls_to_open addObject:file_url];
  761. }
  762. }
  763. if ([urls_to_open count] == 0) {
  764. return ERR_INVALID_PARAMETER;
  765. }
  766. #if defined(__x86_64__)
  767. if (@available(macOS 10.15, *)) {
  768. #endif
  769. NSWorkspaceOpenConfiguration *configuration = [[NSWorkspaceOpenConfiguration alloc] init];
  770. [configuration setCreatesNewApplicationInstance:NO];
  771. __block dispatch_semaphore_t lock = dispatch_semaphore_create(0);
  772. __block Error err = ERR_TIMEOUT;
  773. [[NSWorkspace sharedWorkspace] openURLs:urls_to_open
  774. withApplicationAtURL:app_url
  775. configuration:configuration
  776. completionHandler:^(NSRunningApplication *app, NSError *error) {
  777. if (error) {
  778. err = ERR_CANT_FORK;
  779. NSLog(@"Failed to open paths: %@", error.localizedDescription);
  780. } else {
  781. err = OK;
  782. }
  783. dispatch_semaphore_signal(lock);
  784. }];
  785. dispatch_semaphore_wait(lock, dispatch_time(DISPATCH_TIME_NOW, 20000000000)); // 20 sec timeout, wait for app to launch.
  786. return err;
  787. #if defined(__x86_64__)
  788. } else {
  789. NSError *error = nullptr;
  790. [[NSWorkspace sharedWorkspace] openURLs:urls_to_open withApplicationAtURL:app_url options:NSWorkspaceLaunchDefault configuration:@{} error:&error];
  791. if (error) {
  792. return ERR_CANT_FORK;
  793. }
  794. return OK;
  795. }
  796. #endif
  797. }
  798. bool OS_MacOS::is_process_running(const ProcessID &p_pid) const {
  799. NSRunningApplication *app = [NSRunningApplication runningApplicationWithProcessIdentifier:(pid_t)p_pid];
  800. if (!app) {
  801. return OS_Unix::is_process_running(p_pid);
  802. }
  803. return ![app isTerminated];
  804. }
  805. String OS_MacOS::get_unique_id() const {
  806. static String serial_number;
  807. if (serial_number.is_empty()) {
  808. io_service_t platform_expert = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
  809. CFStringRef serial_number_cf_string = nullptr;
  810. if (platform_expert) {
  811. serial_number_cf_string = (CFStringRef)IORegistryEntryCreateCFProperty(platform_expert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0);
  812. IOObjectRelease(platform_expert);
  813. }
  814. NSString *serial_number_ns_string = nil;
  815. if (serial_number_cf_string) {
  816. serial_number_ns_string = [NSString stringWithString:(__bridge NSString *)serial_number_cf_string];
  817. CFRelease(serial_number_cf_string);
  818. }
  819. if (serial_number_ns_string) {
  820. serial_number.append_utf8([serial_number_ns_string UTF8String]);
  821. }
  822. }
  823. return serial_number;
  824. }
  825. bool OS_MacOS::_check_internal_feature_support(const String &p_feature) {
  826. if (p_feature == "system_fonts") {
  827. return true;
  828. }
  829. if (p_feature == "pc") {
  830. return true;
  831. }
  832. return false;
  833. }
  834. void OS_MacOS::disable_crash_handler() {
  835. crash_handler.disable();
  836. }
  837. bool OS_MacOS::is_disable_crash_handler() const {
  838. return crash_handler.is_disabled();
  839. }
  840. Error OS_MacOS::move_to_trash(const String &p_path) {
  841. NSFileManager *fm = [NSFileManager defaultManager];
  842. NSURL *url = [NSURL fileURLWithPath:@(p_path.utf8().get_data())];
  843. NSError *err;
  844. if (![fm trashItemAtURL:url resultingItemURL:nil error:&err]) {
  845. ERR_PRINT("trashItemAtURL error: " + String::utf8(err.localizedDescription.UTF8String));
  846. return FAILED;
  847. }
  848. return OK;
  849. }
  850. String OS_MacOS::get_system_ca_certificates() {
  851. CFArrayRef result;
  852. SecCertificateRef item;
  853. CFDataRef der;
  854. OSStatus ret = SecTrustCopyAnchorCertificates(&result);
  855. ERR_FAIL_COND_V(ret != noErr, "");
  856. CFIndex l = CFArrayGetCount(result);
  857. String certs;
  858. PackedByteArray pba;
  859. for (CFIndex i = 0; i < l; i++) {
  860. item = (SecCertificateRef)CFArrayGetValueAtIndex(result, i);
  861. der = SecCertificateCopyData(item);
  862. int derlen = CFDataGetLength(der);
  863. if (pba.size() < derlen * 3) {
  864. pba.resize(derlen * 3);
  865. }
  866. size_t b64len = 0;
  867. Error err = CryptoCore::b64_encode(pba.ptrw(), pba.size(), &b64len, (unsigned char *)CFDataGetBytePtr(der), derlen);
  868. CFRelease(der);
  869. ERR_CONTINUE(err != OK);
  870. // Certificate is bas64 encoded, aka ascii.
  871. certs += "-----BEGIN CERTIFICATE-----\n" + String::ascii(Span((char *)pba.ptr(), b64len)) + "\n-----END CERTIFICATE-----\n";
  872. }
  873. CFRelease(result);
  874. return certs;
  875. }
  876. OS::PreferredTextureFormat OS_MacOS::get_preferred_texture_format() const {
  877. // macOS supports both formats on ARM. Prefer S3TC/BPTC
  878. // for better compatibility with x86 platforms.
  879. return PREFERRED_TEXTURE_FORMAT_S3TC_BPTC;
  880. }
  881. OS_MacOS::OS_MacOS(const char *p_execpath, int p_argc, char **p_argv) {
  882. execpath = p_execpath;
  883. argc = p_argc;
  884. argv = p_argv;
  885. if (is_sandboxed()) {
  886. // Load security-scoped bookmarks, request access, remove stale or invalid bookmarks.
  887. NSArray *bookmarks = [[NSUserDefaults standardUserDefaults] arrayForKey:@"sec_bookmarks"];
  888. NSMutableArray *new_bookmarks = [[NSMutableArray alloc] init];
  889. for (id bookmark in bookmarks) {
  890. NSError *error = nil;
  891. BOOL isStale = NO;
  892. NSURL *url = [NSURL URLByResolvingBookmarkData:bookmark options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&isStale error:&error];
  893. if (!error && !isStale) {
  894. if ([url startAccessingSecurityScopedResource]) {
  895. [new_bookmarks addObject:bookmark];
  896. }
  897. }
  898. }
  899. [[NSUserDefaults standardUserDefaults] setObject:new_bookmarks forKey:@"sec_bookmarks"];
  900. }
  901. Vector<Logger *> loggers;
  902. loggers.push_back(memnew(OsLogLogger(NSBundle.mainBundle.bundleIdentifier.UTF8String)));
  903. loggers.push_back(memnew(UnixTerminalLogger));
  904. _set_logger(memnew(CompositeLogger(loggers)));
  905. #ifdef COREAUDIO_ENABLED
  906. AudioDriverManager::add_driver(&audio_driver);
  907. #endif
  908. DisplayServerMacOS::register_macos_driver();
  909. }
  910. // MARK: - OS_MacOS_NSApp
  911. void OS_MacOS_NSApp::run() {
  912. [NSApp run];
  913. }
  914. static bool sig_received = false;
  915. static void handle_interrupt(int sig) {
  916. if (sig == SIGINT) {
  917. sig_received = true;
  918. }
  919. }
  920. void OS_MacOS_NSApp::start_main() {
  921. Error err;
  922. @autoreleasepool {
  923. err = Main::setup(execpath, argc, argv);
  924. }
  925. if (err == OK) {
  926. main_started = true;
  927. int ret;
  928. @autoreleasepool {
  929. ret = Main::start();
  930. }
  931. if (ret == EXIT_SUCCESS) {
  932. if (main_loop) {
  933. @autoreleasepool {
  934. main_loop->initialize();
  935. }
  936. DisplayServer *ds = DisplayServer::get_singleton();
  937. DisplayServerMacOS *ds_mac = Object::cast_to<DisplayServerMacOS>(ds);
  938. pre_wait_observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
  939. @autoreleasepool {
  940. @try {
  941. if (ds_mac) {
  942. ds_mac->_process_events(false);
  943. } else if (ds) {
  944. ds->process_events();
  945. }
  946. #ifdef SDL_ENABLED
  947. if (joypad_sdl) {
  948. joypad_sdl->process_events();
  949. }
  950. #endif
  951. if (Main::iteration() || sig_received) {
  952. terminate();
  953. }
  954. } @catch (NSException *exception) {
  955. ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String));
  956. }
  957. }
  958. if (wait_timer == nil) {
  959. CFRunLoopWakeUp(CFRunLoopGetCurrent()); // Prevent main loop from sleeping.
  960. }
  961. });
  962. CFRunLoopAddObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
  963. return;
  964. }
  965. } else {
  966. set_exit_code(EXIT_FAILURE);
  967. }
  968. } else if (err == ERR_HELP) { // Returned by --help and --version, so success.
  969. set_exit_code(EXIT_SUCCESS);
  970. } else {
  971. set_exit_code(EXIT_FAILURE);
  972. }
  973. terminate();
  974. }
  975. void OS_MacOS_NSApp::terminate() {
  976. if (pre_wait_observer) {
  977. CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), pre_wait_observer, kCFRunLoopCommonModes);
  978. CFRelease(pre_wait_observer);
  979. pre_wait_observer = nil;
  980. }
  981. should_terminate = true;
  982. [NSApp terminate:nil];
  983. }
  984. void OS_MacOS_NSApp::cleanup() {
  985. if (main_loop) {
  986. main_loop->finalize();
  987. }
  988. if (main_started) {
  989. @autoreleasepool {
  990. Main::cleanup();
  991. }
  992. }
  993. }
  994. OS_MacOS_NSApp::OS_MacOS_NSApp(const char *p_execpath, int p_argc, char **p_argv) :
  995. OS_MacOS(p_execpath, p_argc, p_argv) {
  996. // Implicitly create shared NSApplication instance.
  997. [GodotApplication sharedApplication];
  998. // In case we are unbundled, make us a proper UI application.
  999. [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory];
  1000. // Menu bar setup must go between sharedApplication above and
  1001. // finishLaunching below, in order to properly emulate the behavior
  1002. // of NSApplicationMain.
  1003. NSMenu *main_menu = [[NSMenu alloc] initWithTitle:@""];
  1004. [NSApp setMainMenu:main_menu];
  1005. delegate = [[GodotApplicationDelegate alloc] initWithOS:this];
  1006. ERR_FAIL_NULL(delegate);
  1007. [NSApp setDelegate:delegate];
  1008. [NSApp registerUserInterfaceItemSearchHandler:delegate];
  1009. struct sigaction action;
  1010. memset(&action, 0, sizeof(action));
  1011. action.sa_handler = handle_interrupt;
  1012. sigaction(SIGINT, &action, nullptr);
  1013. }
  1014. // MARK: - OS_MacOS_Embedded
  1015. #ifdef DEBUG_ENABLED
  1016. void OS_MacOS_Embedded::run() {
  1017. CFRunLoopGetCurrent();
  1018. @autoreleasepool {
  1019. Error err = Main::setup(execpath, argc, argv);
  1020. if (err != OK) {
  1021. if (err == ERR_HELP) {
  1022. return set_exit_code(EXIT_SUCCESS);
  1023. }
  1024. return set_exit_code(EXIT_FAILURE);
  1025. }
  1026. }
  1027. int ret;
  1028. @autoreleasepool {
  1029. ret = Main::start();
  1030. }
  1031. DisplayServerEmbedded *ds = Object::cast_to<DisplayServerEmbedded>(DisplayServer::get_singleton());
  1032. if (!ds) {
  1033. ERR_FAIL_MSG("DisplayServerEmbedded is not initialized.");
  1034. }
  1035. if (ds && ret == EXIT_SUCCESS && main_loop) {
  1036. @autoreleasepool {
  1037. main_loop->initialize();
  1038. }
  1039. while (true) {
  1040. @autoreleasepool {
  1041. @try {
  1042. ds->process_events();
  1043. if (Main::iteration()) {
  1044. break;
  1045. }
  1046. CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, 0);
  1047. } @catch (NSException *exception) {
  1048. ERR_PRINT("NSException: " + String::utf8([exception reason].UTF8String));
  1049. }
  1050. }
  1051. }
  1052. main_loop->finalize();
  1053. }
  1054. Main::cleanup();
  1055. }
  1056. OS_MacOS_Embedded::OS_MacOS_Embedded(const char *p_execpath, int p_argc, char **p_argv) :
  1057. OS_MacOS(p_execpath, p_argc, p_argv) {
  1058. DisplayServerEmbedded::register_embedded_driver();
  1059. }
  1060. #endif