1/* RetroArch - A frontend for libretro. 2 * Copyright (C) 2010-2014 - Hans-Kristian Arntzen 3 * Copyright (C) 2011-2017 - Daniel De Matteis 4 * Copyright (C) 2012-2014 - Jason Fetters 5 * Copyright (C) 2014-2015 - Jay McCarthy 6 * 7 * RetroArch is free software: you can redistribute it and/or modify it under the terms 8 * of the GNU General Public License as published by the Free Software Found- 9 * ation, either version 3 of the License, or (at your option) any later version. 10 * 11 * RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 12 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 13 * PURPOSE. See the GNU General Public License for more details. 14 * * You should have received a copy of the GNU General Public License along with RetroArch. 15 * If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18#include <stdint.h> 19#include <stddef.h> 20#include <string.h> 21#include <unistd.h> 22 23#include <sys/utsname.h> 24 25#include <mach/mach_host.h> 26 27#include <CoreFoundation/CoreFoundation.h> 28#include <CoreFoundation/CFArray.h> 29 30#ifdef HAVE_CONFIG_H 31#include "../../config.h" 32#endif 33 34#ifdef __OBJC__ 35#include <Foundation/NSPathUtilities.h> 36#include <objc/message.h> 37#endif 38 39#if defined(OSX) 40#include <Carbon/Carbon.h> 41#include <IOKit/ps/IOPowerSources.h> 42#include <IOKit/ps/IOPSKeys.h> 43 44#include <sys/sysctl.h> 45#elif defined(IOS) 46#include <UIKit/UIDevice.h> 47#include <sys/sysctl.h> 48#endif 49 50#include <boolean.h> 51#include <compat/apple_compat.h> 52#include <retro_assert.h> 53#include <retro_miscellaneous.h> 54#include <file/file_path.h> 55#include <streams/file_stream.h> 56#include <features/features_cpu.h> 57#include <string/stdstring.h> 58 59#ifdef HAVE_MENU 60#include "../../menu/menu_driver.h" 61#endif 62 63#include "../frontend_driver.h" 64#include "../../file_path_special.h" 65#include "../../configuration.h" 66#include "../../defaults.h" 67#include "../../retroarch.h" 68#include "../../verbosity.h" 69#include "../../msg_hash.h" 70#include "../../ui/ui_companion_driver.h" 71#include "../../paths.h" 72 73#if 1 74#define RELEASE_BUILD 75#endif 76 77typedef enum 78{ 79 CFApplicationDirectory = 1, /* Supported applications (Applications) */ 80 CFDemoApplicationDirectory = 2, /* Unsupported applications, demonstration versions (Demos) */ 81 CFDeveloperApplicationDirectory = 3, /* Developer applications (Developer/Applications). DEPRECATED - there is no one single Developer directory. */ 82 CFAdminApplicationDirectory = 4, /* System and network administration applications (Administration) */ 83 CFLibraryDirectory = 5, /* various documentation, support, and configuration files, resources (Library) */ 84 CFDeveloperDirectory = 6, /* developer resources (Developer) DEPRECATED - there is no one single Developer directory. */ 85 CFUserDirectory = 7, /* User home directories (Users) */ 86 CFDocumentationDirectory = 8, /* Documentation (Documentation) */ 87 CFDocumentDirectory = 9, /* Documents (Documents) */ 88 CFCoreServiceDirectory = 10, /* Location of CoreServices directory (System/Library/CoreServices) */ 89 CFAutosavedInformationDirectory = 11, /* Location of autosaved documents (Documents/Autosaved) */ 90 CFDesktopDirectory = 12, /* Location of user's desktop */ 91 CFCachesDirectory = 13, /* Location of discardable cache files (Library/Caches) */ 92 CFApplicationSupportDirectory = 14, /* Location of application support files (plug-ins, etc) (Library/Application Support) */ 93 CFDownloadsDirectory = 15, /* Location of the user's "Downloads" directory */ 94 CFInputMethodsDirectory = 16, /* Input methods (Library/Input Methods) */ 95 CFMoviesDirectory = 17, /* Location of user's Movies directory (~/Movies) */ 96 CFMusicDirectory = 18, /* Location of user's Music directory (~/Music) */ 97 CFPicturesDirectory = 19, /* Location of user's Pictures directory (~/Pictures) */ 98 CFPrinterDescriptionDirectory = 20, /* Location of system's PPDs directory (Library/Printers/PPDs) */ 99 CFSharedPublicDirectory = 21, /* Location of user's Public sharing directory (~/Public) */ 100 CFPreferencePanesDirectory = 22, /* Location of the PreferencePanes directory for use with System Preferences (Library/PreferencePanes) */ 101 CFApplicationScriptsDirectory = 23, /* Location of the user scripts folder for the calling application (~/Library/Application Scripts/code-signing-id) */ 102 CFItemReplacementDirectory = 99, /* For use with NSFileManager's URLForDirectory:inDomain:appropriateForURL:create:error: */ 103 CFAllApplicationsDirectory = 100, /* all directories where applications can occur */ 104 CFAllLibrariesDirectory = 101, /* all directories where resources can occur */ 105 CFTrashDirectory = 102 /* location of Trash directory */ 106} CFSearchPathDirectory; 107 108typedef enum 109{ 110 CFUserDomainMask = 1, /* user's home directory --- place to install user's personal items (~) */ 111 CFLocalDomainMask = 2, /* local to the current machine --- place to install items available to everyone on this machine (/Library) */ 112 CFNetworkDomainMask = 4, /* publically available location in the local area network --- place to install items available on the network (/Network) */ 113 CFSystemDomainMask = 8, /* provided by Apple, unmodifiable (/System) */ 114 CFAllDomainsMask = 0x0ffff /* All domains: all of the above and future items */ 115} CFDomainMask; 116 117#if (defined(OSX) && !(defined(__ppc__) || defined(__ppc64__))) 118static int speak_pid = 0; 119#endif 120 121static char darwin_cpu_model_name[64] = {0}; 122 123static NSSearchPathDirectory NSConvertFlagsCF(unsigned flags) 124{ 125 switch (flags) 126 { 127 case CFDocumentDirectory: 128#if TARGET_OS_TV 129 return NSCachesDirectory; 130#else 131 return NSDocumentDirectory; 132#endif 133 } 134 135 return 0; 136} 137 138static NSSearchPathDomainMask NSConvertDomainFlagsCF(unsigned flags) 139{ 140 switch (flags) 141 { 142 case CFUserDomainMask: 143 return NSUserDomainMask; 144 } 145 146 return 0; 147} 148 149static void CFSearchPathForDirectoriesInDomains(unsigned flags, 150 unsigned domain_mask, unsigned expand_tilde, 151 char *s, size_t len) 152{ 153 CFTypeRef array_val = (CFTypeRef)CFBridgingRetainCompat( 154 NSSearchPathForDirectoriesInDomains(NSConvertFlagsCF(flags), 155 NSConvertDomainFlagsCF(domain_mask), (BOOL)expand_tilde)); 156 CFArrayRef array = array_val ? CFRetain(array_val) : NULL; 157 CFTypeRef path_val = (CFTypeRef)CFArrayGetValueAtIndex(array, 0); 158 CFStringRef path = path_val ? CFRetain(path_val) : NULL; 159 if (!path || !array) 160 return; 161 162 CFStringGetCString(path, s, len, kCFStringEncodingUTF8); 163 CFRelease(path); 164 CFRelease(array); 165} 166 167static void CFTemporaryDirectory(char *s, size_t len) 168{ 169#if __has_feature(objc_arc) 170 CFStringRef path = (__bridge CFStringRef)NSTemporaryDirectory(); 171#else 172 CFStringRef path = (CFStringRef)NSTemporaryDirectory(); 173#endif 174 CFStringGetCString(path, s, len, kCFStringEncodingUTF8); 175} 176 177#if defined(IOS) 178void get_ios_version(int *major, int *minor); 179#endif 180 181#if defined(OSX) 182 183#define PMGMT_STRMATCH(a,b) (CFStringCompare(a, b, 0) == kCFCompareEqualTo) 184#define PMGMT_GETVAL(k,v) CFDictionaryGetValueIfPresent(dict, CFSTR(k), (const void **) v) 185 186/* Note that AC power sources also include a laptop battery it is charging. */ 187static void checkps(CFDictionaryRef dict, bool * have_ac, bool * have_battery, 188 bool * charging, int *seconds, int *percent) 189{ 190 CFStringRef strval; /* don't CFRelease() this. */ 191 CFBooleanRef bval; 192 CFNumberRef numval; 193 bool charge = false; 194 bool choose = false; 195 bool is_ac = false; 196 int secs = -1; 197 int maxpct = -1; 198 int pct = -1; 199 200 if ((PMGMT_GETVAL(kIOPSIsPresentKey, &bval)) && (bval == kCFBooleanFalse)) 201 return; 202 203 if (!PMGMT_GETVAL(kIOPSPowerSourceStateKey, &strval)) 204 return; 205 206 if (PMGMT_STRMATCH(strval, CFSTR(kIOPSACPowerValue))) 207 is_ac = *have_ac = true; 208 else if (!PMGMT_STRMATCH(strval, CFSTR(kIOPSBatteryPowerValue))) 209 return; /* not a battery? */ 210 211 if ((PMGMT_GETVAL(kIOPSIsChargingKey, &bval)) && (bval == kCFBooleanTrue)) 212 charge = true; 213 214 if (PMGMT_GETVAL(kIOPSMaxCapacityKey, &numval)) 215 { 216 SInt32 val = -1; 217 CFNumberGetValue(numval, kCFNumberSInt32Type, &val); 218 if (val > 0) 219 { 220 *have_battery = true; 221 maxpct = (int) val; 222 } 223 } 224 225 if (PMGMT_GETVAL(kIOPSMaxCapacityKey, &numval)) 226 { 227 SInt32 val = -1; 228 CFNumberGetValue(numval, kCFNumberSInt32Type, &val); 229 if (val > 0) 230 { 231 *have_battery = true; 232 maxpct = (int) val; 233 } 234 } 235 236 if (PMGMT_GETVAL(kIOPSTimeToEmptyKey, &numval)) 237 { 238 SInt32 val = -1; 239 CFNumberGetValue(numval, kCFNumberSInt32Type, &val); 240 241 /* Mac OS X reports 0 minutes until empty if you're plugged in. :( */ 242 if ((val == 0) && (is_ac)) 243 val = -1; /* !!! FIXME: calc from timeToFull and capacity? */ 244 245 secs = (int) val; 246 if (secs > 0) 247 secs *= 60; /* value is in minutes, so convert to seconds. */ 248 } 249 250 if (PMGMT_GETVAL(kIOPSCurrentCapacityKey, &numval)) 251 { 252 SInt32 val = -1; 253 CFNumberGetValue(numval, kCFNumberSInt32Type, &val); 254 pct = (int) val; 255 } 256 257 if ((pct > 0) && (maxpct > 0)) 258 pct = (int) ((((double) pct) / ((double) maxpct)) * 100.0); 259 260 if (pct > 100) 261 pct = 100; 262 263 /* 264 * We pick the battery that claims to have the most minutes left. 265 * (failing a report of minutes, we'll take the highest percent.) 266 */ 267 if ((secs < 0) && (*seconds < 0)) 268 { 269 if ((pct < 0) && (*percent < 0)) 270 choose = true; /* at least we know there's a battery. */ 271 if (pct > *percent) 272 choose = true; 273 } 274 else if (secs > *seconds) 275 choose = true; 276 277 if (choose) 278 { 279 *seconds = secs; 280 *percent = pct; 281 *charging = charge; 282 } 283} 284#endif 285 286static void frontend_darwin_get_name(char *s, size_t len) 287{ 288#if defined(IOS) 289 struct utsname buffer; 290 291 if (uname(&buffer) != 0) 292 return; 293 294 strlcpy(s, buffer.machine, len); 295#elif defined(OSX) 296 size_t length = 0; 297 sysctlbyname("hw.model", NULL, &length, NULL, 0); 298 if (length) 299 sysctlbyname("hw.model", s, &length, NULL, 0); 300#endif 301} 302 303static void frontend_darwin_get_os(char *s, size_t len, int *major, int *minor) 304{ 305#if defined(IOS) 306 get_ios_version(major, minor); 307 strcpy_literal(s, "iOS"); 308#elif defined(OSX) 309 310#if MAC_OS_X_VERSION_MIN_REQUIRED >= 101300 // MAC_OS_X_VERSION_10_13 311 NSOperatingSystemVersion version = NSProcessInfo.processInfo.operatingSystemVersion; 312 *major = (int)version.majorVersion; 313 *minor = (int)version.minorVersion; 314#else 315 /* MacOS 10.9 includes the [NSProcessInfo operatingSystemVersion] function, but it's not in the 10.9 SDK. So, call it via NSInvocation */ 316 /* Credit: OpenJDK (https://github.com/openjdk/jdk/commit/d4c7db50) */ 317 if ([[NSProcessInfo processInfo] respondsToSelector:@selector(operatingSystemVersion)]) 318 { 319 typedef struct 320 { 321 NSInteger majorVersion; 322 NSInteger minorVersion; 323 NSInteger patchVersion; 324 } NSMyOSVersion; 325 NSMyOSVersion version; 326 NSMethodSignature *sig = [[NSProcessInfo processInfo] methodSignatureForSelector:@selector(operatingSystemVersion)]; 327 NSInvocation *invoke = [NSInvocation invocationWithMethodSignature:sig]; 328 invoke.selector = @selector(operatingSystemVersion); 329 [invoke invokeWithTarget:[NSProcessInfo processInfo]]; 330 [invoke getReturnValue:&version]; 331 *major = (int)version.majorVersion; 332 *minor = (int)version.minorVersion; 333 } 334 else 335 { 336 Gestalt(gestaltSystemVersionMinor, (SInt32*)minor); 337 Gestalt(gestaltSystemVersionMajor, (SInt32*)major); 338 } 339#endif 340 strcpy_literal(s, "OSX"); 341#endif 342} 343 344static void frontend_darwin_get_env(int *argc, char *argv[], 345 void *args, void *params_data) 346{ 347 CFURLRef bundle_url; 348 CFStringRef bundle_path; 349 CFURLRef resource_url; 350 CFStringRef resource_path; 351#if TARGET_OS_IPHONE 352 char resolved_home_dir_buf[ 353 PATH_MAX_LENGTH] = {0}; 354 char resolved_bundle_dir_buf[ 355 PATH_MAX_LENGTH] = {0}; 356#endif 357 char temp_dir[PATH_MAX_LENGTH] = {0}; 358 char bundle_path_buf[PATH_MAX_LENGTH] = {0}; 359 char resource_path_buf[PATH_MAX_LENGTH] = {0}; 360 char full_resource_path_buf[PATH_MAX_LENGTH] = {0}; 361 char home_dir_buf[PATH_MAX_LENGTH] = {0}; 362 CFBundleRef bundle = CFBundleGetMainBundle(); 363 364 if (!bundle) 365 return; 366 367 bundle_url = CFBundleCopyBundleURL(bundle); 368 bundle_path = CFURLCopyPath(bundle_url); 369 370 resource_url = CFBundleCopyResourcesDirectoryURL(bundle); 371 resource_path = CFURLCopyPath(resource_url); 372 373 CFStringGetCString(bundle_path, 374 bundle_path_buf, sizeof(bundle_path_buf), kCFStringEncodingUTF8); 375 CFStringGetCString(resource_path, 376 resource_path_buf, sizeof(resource_path_buf), kCFStringEncodingUTF8); 377 fill_pathname_join(full_resource_path_buf, bundle_path_buf, resource_path_buf, sizeof(full_resource_path_buf)); 378 CFSearchPathForDirectoriesInDomains(CFDocumentDirectory, 379 CFUserDomainMask, 1, home_dir_buf, sizeof(home_dir_buf)); 380 381#if TARGET_OS_IPHONE 382 if (realpath(home_dir_buf, resolved_home_dir_buf)) 383 { 384 retro_assert(strlcpy(home_dir_buf, 385 resolved_home_dir_buf, 386 sizeof(home_dir_buf)) < sizeof(home_dir_buf)); 387 } 388 if (realpath(bundle_path_buf, resolved_bundle_dir_buf)) 389 { 390 retro_assert(strlcpy(bundle_path_buf, 391 resolved_bundle_dir_buf, 392 sizeof(bundle_path_buf)) < sizeof(bundle_path_buf)); 393 } 394#endif 395 396 strlcat(home_dir_buf, "/RetroArch", sizeof(home_dir_buf)); 397#ifdef HAVE_METAL 398 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SHADER], 399 home_dir_buf, "shaders_slang", 400 sizeof(g_defaults.dirs[DEFAULT_DIR_SHADER])); 401#else 402 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SHADER], 403 home_dir_buf, "shaders_glsl", 404 sizeof(g_defaults.dirs[DEFAULT_DIR_SHADER])); 405#endif 406#ifdef HAVE_UPDATE_CORES 407 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE], 408 home_dir_buf, "cores", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE])); 409#else 410 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE], 411 bundle_path_buf, "modules", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE])); 412#endif 413 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE_INFO], home_dir_buf, "info", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_INFO])); 414 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_OVERLAY], home_dir_buf, "overlays", sizeof(g_defaults.dirs[DEFAULT_DIR_OVERLAY])); 415#ifdef HAVE_VIDEO_LAYOUT 416 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_VIDEO_LAYOUT], home_dir_buf, "layouts", sizeof(g_defaults.dirs[DEFAULT_DIR_VIDEO_LAYOUT])); 417#endif 418 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_AUTOCONFIG], home_dir_buf, "autoconfig", sizeof(g_defaults.dirs[DEFAULT_DIR_AUTOCONFIG])); 419 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS], home_dir_buf, "downloads", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS])); 420 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_ASSETS], home_dir_buf, "assets", sizeof(g_defaults.dirs[DEFAULT_DIR_ASSETS])); 421 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SYSTEM], home_dir_buf, "system", sizeof(g_defaults.dirs[DEFAULT_DIR_SYSTEM])); 422 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG], home_dir_buf, "config", sizeof(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG])); 423 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_REMAP], g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG], "remaps", sizeof(g_defaults.dirs[DEFAULT_DIR_REMAP])); 424 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_DATABASE], home_dir_buf, "database/rdb", sizeof(g_defaults.dirs[DEFAULT_DIR_DATABASE])); 425 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CURSOR], home_dir_buf, "database/cursors", sizeof(g_defaults.dirs[DEFAULT_DIR_CURSOR])); 426 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CHEATS], home_dir_buf, "cht", sizeof(g_defaults.dirs[DEFAULT_DIR_CHEATS])); 427 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_THUMBNAILS], home_dir_buf, "thumbnails", sizeof(g_defaults.dirs[DEFAULT_DIR_THUMBNAILS])); 428 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SRAM], home_dir_buf, "saves", sizeof(g_defaults.dirs[DEFAULT_DIR_SRAM])); 429 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SAVESTATE], home_dir_buf, "states", sizeof(g_defaults.dirs[DEFAULT_DIR_SAVESTATE])); 430 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_RECORD_CONFIG], home_dir_buf, "records_config", sizeof(g_defaults.dirs[DEFAULT_DIR_RECORD_CONFIG])); 431 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_RECORD_OUTPUT], home_dir_buf, "records", sizeof(g_defaults.dirs[DEFAULT_DIR_RECORD_OUTPUT])); 432 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_LOGS], home_dir_buf, "logs", sizeof(g_defaults.dirs[DEFAULT_DIR_LOGS])); 433#if defined(IOS) 434 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_PLAYLIST], home_dir_buf, "playlists", sizeof(g_defaults.dirs[DEFAULT_DIR_PLAYLIST])); 435#endif 436#if defined(OSX) 437 char application_data[PATH_MAX_LENGTH]; 438 439 fill_pathname_application_data(application_data, sizeof(application_data)); 440 441#ifdef HAVE_CG 442 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SHADER], home_dir_buf, "shaders_cg", sizeof(g_defaults.dirs[DEFAULT_DIR_SHADER])); 443#endif 444 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_PLAYLIST], application_data, "playlists", sizeof(g_defaults.dirs[DEFAULT_DIR_PLAYLIST])); 445 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_THUMBNAILS], application_data, "thumbnails", sizeof(g_defaults.dirs[DEFAULT_DIR_THUMBNAILS])); 446 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG], application_data, "config", sizeof(g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG])); 447 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_REMAP], g_defaults.dirs[DEFAULT_DIR_MENU_CONFIG], "remaps", sizeof(g_defaults.dirs[DEFAULT_DIR_REMAP])); 448 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS], application_data, "downloads", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_ASSETS])); 449 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SCREENSHOT], application_data, "screenshots", sizeof(g_defaults.dirs[DEFAULT_DIR_SCREENSHOT])); 450#if defined(RELEASE_BUILD) 451 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_SHADER], application_data, "shaders", sizeof(g_defaults.dirs[DEFAULT_DIR_SHADER])); 452 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE], application_data, "cores", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE])); 453 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CORE_INFO], application_data, "info", sizeof(g_defaults.dirs[DEFAULT_DIR_CORE_INFO])); 454 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_OVERLAY], application_data, "overlays", sizeof(g_defaults.dirs[DEFAULT_DIR_OVERLAY])); 455#ifdef HAVE_VIDEO_LAYOUT 456 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_VIDEO_LAYOUT], application_data, "layouts", sizeof(g_defaults.dirs[DEFAULT_DIR_VIDEO_LAYOUT])); 457#endif 458 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_AUTOCONFIG], application_data, "autoconfig", sizeof(g_defaults.dirs[DEFAULT_DIR_AUTOCONFIG])); 459 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_ASSETS], application_data, "assets", sizeof(g_defaults.dirs[DEFAULT_DIR_ASSETS])); 460 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_DATABASE], application_data, "database/rdb", sizeof(g_defaults.dirs[DEFAULT_DIR_DATABASE])); 461 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CURSOR], application_data, "database/cursors", sizeof(g_defaults.dirs[DEFAULT_DIR_CURSOR])); 462 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_CHEATS], application_data, "cht", sizeof(g_defaults.dirs[DEFAULT_DIR_CHEATS])); 463 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_AUDIO_FILTER], application_data, "audio_filters", sizeof(g_defaults.dirs[DEFAULT_DIR_AUDIO_FILTER])); 464 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_VIDEO_FILTER], application_data, "video_filters", sizeof(g_defaults.dirs[DEFAULT_DIR_VIDEO_FILTER])); 465#else 466 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_AUDIO_FILTER], home_dir_buf, "audio_filters", sizeof(g_defaults.dirs[DEFAULT_DIR_AUDIO_FILTER])); 467 fill_pathname_join(g_defaults.dirs[DEFAULT_DIR_VIDEO_FILTER], home_dir_buf, "video_filters", sizeof(g_defaults.dirs[DEFAULT_DIR_VIDEO_FILTER])); 468#endif 469 470#endif 471 472 char assets_zip_path[PATH_MAX_LENGTH]; 473#if TARGET_OS_IOS 474 { 475 int major, minor; 476 get_ios_version(&major, &minor); 477 if (major > 8) 478 strcpy_literal(g_defaults.path_buildbot_server_url, "http://buildbot.libretro.com/nightly/apple/ios9/latest/"); 479 } 480#endif 481 482#if TARGET_OS_IOS 483 fill_pathname_join(assets_zip_path, bundle_path_buf, "assets.zip", sizeof(assets_zip_path)); 484#else 485 fill_pathname_join(assets_zip_path, full_resource_path_buf, "assets.zip", sizeof(assets_zip_path)); 486#endif 487 488 if (path_is_valid(assets_zip_path)) 489 { 490 settings_t *settings = config_get_ptr(); 491 492 RARCH_LOG("Assets ZIP found at [%s], setting up bundle assets extraction...\n", assets_zip_path); 493 RARCH_LOG("Extraction dir will be: %s\n", home_dir_buf); 494 configuration_set_string(settings, 495 settings->arrays.bundle_assets_src, 496 assets_zip_path); 497 configuration_set_string(settings, 498 settings->arrays.bundle_assets_dst, 499#if TARGET_OS_IOS || TARGET_OS_TV 500 home_dir_buf 501#else 502 application_data 503#endif 504 ); 505 /* TODO/FIXME: Just hardcode this for now */ 506 configuration_set_uint(settings, settings->uints.bundle_assets_extract_version_current, 1); 507 } 508 509 CFTemporaryDirectory(temp_dir, sizeof(temp_dir)); 510 strlcpy(g_defaults.dirs[DEFAULT_DIR_CACHE], 511 temp_dir, 512 sizeof(g_defaults.dirs[DEFAULT_DIR_CACHE])); 513 514 path_mkdir(bundle_path_buf); 515 516 if (access(bundle_path_buf, 0755) != 0) 517 RARCH_ERR("Failed to create or access base directory: %s\n", bundle_path_buf); 518 else 519 { 520 path_mkdir(g_defaults.dirs[DEFAULT_DIR_SYSTEM]); 521 522 if (access(g_defaults.dirs[DEFAULT_DIR_SYSTEM], 0755) != 0) 523 RARCH_ERR("Failed to create or access system directory: %s.\n", g_defaults.dirs[DEFAULT_DIR_SYSTEM]); 524 } 525 526 CFRelease(bundle_path); 527 CFRelease(bundle_url); 528 529#ifndef IS_SALAMANDER 530 dir_check_defaults("custom.ini"); 531#endif 532} 533 534static void frontend_darwin_content_loaded(void) 535{ 536 ui_companion_driver_notify_content_loaded(); 537} 538 539static int frontend_darwin_get_rating(void) 540{ 541 char model[PATH_MAX_LENGTH] = {0}; 542 543 frontend_darwin_get_name(model, sizeof(model)); 544 545 /* iPhone 4 */ 546#if 0 547 if (strstr(model, "iPhone3")) 548 return -1; 549#endif 550 551 /* iPad 1 */ 552#if 0 553 if (strstr(model, "iPad1,1")) 554 return -1; 555#endif 556 557 /* iPhone 4S */ 558 if (strstr(model, "iPhone4,1")) 559 return 8; 560 561 /* iPad 2/iPad Mini 1 */ 562 if (strstr(model, "iPad2")) 563 return 9; 564 565 /* iPhone 5/5C */ 566 if (strstr(model, "iPhone5")) 567 return 13; 568 569 /* iPhone 5S */ 570 if (strstr(model, "iPhone6,1") || strstr(model, "iPhone6,2")) 571 return 14; 572 573 /* iPad Mini 2/3 */ 574 if ( strstr(model, "iPad4,4") 575 || strstr(model, "iPad4,5") 576 || strstr(model, "iPad4,6") 577 || strstr(model, "iPad4,7") 578 || strstr(model, "iPad4,8") 579 || strstr(model, "iPad4,9") 580 ) 581 return 15; 582 583 /* iPad Air */ 584 if ( strstr(model, "iPad4,1") 585 || strstr(model, "iPad4,2") 586 || strstr(model, "iPad4,3") 587 ) 588 return 16; 589 590 /* iPhone 6, iPhone 6 Plus */ 591 if (strstr(model, "iPhone7")) 592 return 17; 593 594 /* iPad Air 2 */ 595 if (strstr(model, "iPad5,3") || strstr(model, "iPad5,4")) 596 return 18; 597 598 /* iPad Pro (12.9 Inch) */ 599 if (strstr(model, "iPad6,7") || strstr(model, "iPad6,8")) 600 return 19; 601 602 /* iPad Pro (9.7 Inch) */ 603 if (strstr(model, "iPad6,3") || strstr(model, "iPad6,4")) 604 return 19; 605 606 /* iPad 5th Generation */ 607 if (strstr(model, "iPad6,11") || strstr(model, "iPad6,12")) 608 return 19; 609 610 /* iPad Pro (12.9 Inch 2nd Generation) */ 611 if (strstr(model, "iPad7,1") || strstr(model, "iPad7,2")) 612 return 19; 613 614 /* iPad Pro (10.5 Inch) */ 615 if (strstr(model, "iPad7,3") || strstr(model, "iPad7,4")) 616 return 19; 617 618 /* iPad Pro 6th Generation) */ 619 if (strstr(model, "iPad7,5") || strstr(model, "iPad7,6")) 620 return 19; 621 622 /* iPad Pro (11 Inch) */ 623 if ( strstr(model, "iPad8,1") 624 || strstr(model, "iPad8,2") 625 || strstr(model, "iPad8,3") 626 || strstr(model, "iPad8,4") 627 ) 628 return 19; 629 630 /* iPad Pro (12.9 3rd Generation) */ 631 if ( strstr(model, "iPad8,5") 632 || strstr(model, "iPad8,6") 633 || strstr(model, "iPad8,7") 634 || strstr(model, "iPad8,8") 635 ) 636 return 19; 637 638 /* iPad Air 3rd Generation) */ 639 if ( strstr(model, "iPad11,3") 640 || strstr(model, "iPad11,4")) 641 return 19; 642 643 /* TODO/FIXME - 644 - more ratings for more systems 645 - determine rating more intelligently*/ 646 return -1; 647} 648 649static enum frontend_powerstate frontend_darwin_get_powerstate(int *seconds, int *percent) 650{ 651 enum frontend_powerstate ret = FRONTEND_POWERSTATE_NONE; 652#if defined(OSX) 653 CFIndex i, total; 654 CFArrayRef list; 655 bool have_ac, have_battery, charging; 656 CFTypeRef blob = IOPSCopyPowerSourcesInfo(); 657 658 *seconds = -1; 659 *percent = -1; 660 661 if (!blob) 662 goto end; 663 664 list = IOPSCopyPowerSourcesList(blob); 665 666 if (!list) 667 goto end; 668 669 /* don't CFRelease() the list items, or dictionaries! */ 670 have_ac = false; 671 have_battery = false; 672 charging = false; 673 total = CFArrayGetCount(list); 674 675 for (i = 0; i < total; i++) 676 { 677 CFTypeRef ps = (CFTypeRef)CFArrayGetValueAtIndex(list, i); 678 CFDictionaryRef dict = IOPSGetPowerSourceDescription(blob, ps); 679 if (dict) 680 checkps(dict, &have_ac, &have_battery, &charging, 681 seconds, percent); 682 } 683 684 if (!have_battery) 685 ret = FRONTEND_POWERSTATE_NO_SOURCE; 686 else if (charging) 687 ret = FRONTEND_POWERSTATE_CHARGING; 688 else if (have_ac) 689 ret = FRONTEND_POWERSTATE_CHARGED; 690 else 691 ret = FRONTEND_POWERSTATE_ON_POWER_SOURCE; 692 693 CFRelease(list); 694end: 695 if (blob) 696 CFRelease(blob); 697#elif TARGET_OS_IOS 698 float level; 699 UIDevice *uidev = [UIDevice currentDevice]; 700 701 if (!uidev) 702 return ret; 703 704 [uidev setBatteryMonitoringEnabled:true]; 705 706 switch (uidev.batteryState) 707 { 708 case UIDeviceBatteryStateCharging: 709 ret = FRONTEND_POWERSTATE_CHARGING; 710 break; 711 case UIDeviceBatteryStateFull: 712 ret = FRONTEND_POWERSTATE_CHARGED; 713 break; 714 case UIDeviceBatteryStateUnplugged: 715 ret = FRONTEND_POWERSTATE_ON_POWER_SOURCE; 716 break; 717 case UIDeviceBatteryStateUnknown: 718 break; 719 } 720 721 level = uidev.batteryLevel; 722 723 *percent = ((level < 0.0f) ? -1 : ((int)((level * 100) + 0.5f))); 724 725 [uidev setBatteryMonitoringEnabled:false]; 726#endif 727 return ret; 728} 729 730#ifndef OSX 731#ifndef CPU_ARCH_ABI64 732#define CPU_ARCH_ABI64 0x01000000 733#endif 734 735#ifndef CPU_TYPE_ARM64 736#define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64) 737#endif 738#endif 739 740static enum frontend_architecture frontend_darwin_get_arch(void) 741{ 742#ifdef OSX 743 struct utsname buffer; 744 745 if (uname(&buffer) != 0) 746 return FRONTEND_ARCH_NONE; 747 748 if (string_is_equal(buffer.machine, "x86_64")) 749 return FRONTEND_ARCH_X86_64; 750 if (string_is_equal(buffer.machine, "x86")) 751 return FRONTEND_ARCH_X86; 752 if (string_is_equal(buffer.machine, "Power Macintosh")) 753 return FRONTEND_ARCH_PPC; 754 if (string_is_equal(buffer.machine, "arm64")) 755 return FRONTEND_ARCH_ARMV8; 756#else 757 cpu_type_t type; 758 size_t size = sizeof(type); 759 760 sysctlbyname("hw.cputype", &type, &size, NULL, 0); 761 762 if (type == CPU_TYPE_X86_64) 763 return FRONTEND_ARCH_X86_64; 764 else if (type == CPU_TYPE_X86) 765 return FRONTEND_ARCH_X86; 766 else if (type == CPU_TYPE_ARM64) 767 return FRONTEND_ARCH_ARMV8; 768 else if (type == CPU_TYPE_ARM) 769 return FRONTEND_ARCH_ARMV7; 770#endif 771 return FRONTEND_ARCH_NONE; 772} 773 774static int frontend_darwin_parse_drive_list(void *data, bool load_content) 775{ 776 int ret = -1; 777#if TARGET_OS_IPHONE 778#ifdef HAVE_MENU 779 file_list_t *list = (file_list_t*)data; 780 char bundle_path_buf[PATH_MAX_LENGTH] = {0}; 781 char home_dir_buf[PATH_MAX_LENGTH] = {0}; 782 CFBundleRef bundle = CFBundleGetMainBundle(); 783 enum msg_hash_enums enum_idx = load_content 784 ? MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR 785 : MENU_ENUM_LABEL_FILE_BROWSER_DIRECTORY; 786 CFURLRef bundle_url = CFBundleCopyBundleURL(bundle); 787 CFStringRef bundle_path = CFURLCopyPath(bundle_url); 788 789 CFStringGetCString(bundle_path, bundle_path_buf, 790 sizeof(bundle_path_buf), kCFStringEncodingUTF8); 791 792 CFSearchPathForDirectoriesInDomains(CFDocumentDirectory, 793 CFUserDomainMask, 1, home_dir_buf, sizeof(home_dir_buf)); 794 795 menu_entries_append_enum(list, 796 home_dir_buf, 797 msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR), 798 enum_idx, 799 FILE_TYPE_DIRECTORY, 0, 0); 800 menu_entries_append_enum(list, "/", 801 msg_hash_to_str(MENU_ENUM_LABEL_FILE_DETECT_CORE_LIST_PUSH_DIR), 802 enum_idx, 803 FILE_TYPE_DIRECTORY, 0, 0); 804 805 ret = 0; 806 807 CFRelease(bundle_path); 808 CFRelease(bundle_url); 809#endif 810#endif 811 812 return ret; 813} 814 815static uint64_t frontend_darwin_get_total_mem(void) 816{ 817#if defined(OSX) 818 uint64_t size; 819 int mib[2] = { CTL_HW, HW_MEMSIZE }; 820 u_int namelen = sizeof(mib) / sizeof(mib[0]); 821 size_t len = sizeof(size); 822 823 if (sysctl(mib, namelen, &size, &len, NULL, 0) < 0) 824 return 0; 825 return size; 826#else 827 return 0; 828#endif 829} 830 831static uint64_t frontend_darwin_get_free_mem(void) 832{ 833#if (defined(OSX) && !(defined(__ppc__) || defined(__ppc64__))) 834 vm_size_t page_size; 835 vm_statistics64_data_t vm_stats; 836 mach_port_t mach_port = mach_host_self(); 837 mach_msg_type_number_t count = sizeof(vm_stats) / sizeof(natural_t); 838 839 if (KERN_SUCCESS == host_page_size(mach_port, &page_size) && 840 KERN_SUCCESS == host_statistics64(mach_port, HOST_VM_INFO, 841 (host_info64_t)&vm_stats, &count)) 842 { 843 844 long long used_memory = ((int64_t)vm_stats.active_count + 845 (int64_t)vm_stats.inactive_count + 846 (int64_t)vm_stats.wire_count) * (int64_t)page_size; 847 return used_memory; 848 } 849#endif 850 return 0; 851} 852 853static const char* frontend_darwin_get_cpu_model_name(void) 854{ 855 cpu_features_get_model_name(darwin_cpu_model_name, sizeof(darwin_cpu_model_name)); 856 return darwin_cpu_model_name; 857} 858 859#if (defined(OSX) && !(defined(__ppc__) || defined(__ppc64__))) 860static char* accessibility_mac_language_code(const char* language) 861{ 862 if (string_is_equal(language,"en")) 863 return "Alex"; 864 else if (string_is_equal(language,"it")) 865 return "Alice"; 866 else if (string_is_equal(language,"sv")) 867 return "Alva"; 868 else if (string_is_equal(language,"fr")) 869 return "Amelie"; 870 else if (string_is_equal(language,"de")) 871 return "Anna"; 872 else if (string_is_equal(language,"he")) 873 return "Carmit"; 874 else if (string_is_equal(language,"id")) 875 return "Damayanti"; 876 else if (string_is_equal(language,"es")) 877 return "Diego"; 878 else if (string_is_equal(language,"nl")) 879 return "Ellen"; 880 else if (string_is_equal(language,"ro")) 881 return "Ioana"; 882 else if (string_is_equal(language,"pt_pt")) 883 return "Joana"; 884 else if (string_is_equal(language,"pt_bt") || string_is_equal(language,"pt")) 885 return "Luciana"; 886 else if (string_is_equal(language,"th")) 887 return "Kanya"; 888 else if (string_is_equal(language,"ja")) 889 return "Kyoko"; 890 else if (string_is_equal(language,"sk")) 891 return "Laura"; 892 else if (string_is_equal(language,"hi")) 893 return "Lekha"; 894 else if (string_is_equal(language,"ar")) 895 return "Maged"; 896 else if (string_is_equal(language,"hu")) 897 return "Mariska"; 898 else if (string_is_equal(language,"zh_tw") || string_is_equal(language,"zh")) 899 return "Mei-Jia"; 900 else if (string_is_equal(language,"el")) 901 return "Melina"; 902 else if (string_is_equal(language,"ru")) 903 return "Milena"; 904 else if (string_is_equal(language,"nb")) 905 return "Nora"; 906 else if (string_is_equal(language,"da")) 907 return "Sara"; 908 else if (string_is_equal(language,"fi")) 909 return "Satu"; 910 else if (string_is_equal(language,"zh_hk")) 911 return "Sin-ji"; 912 else if (string_is_equal(language,"zh_cn")) 913 return "Ting-Ting"; 914 else if (string_is_equal(language,"tr")) 915 return "Yelda"; 916 else if (string_is_equal(language,"ko")) 917 return "Yuna"; 918 else if (string_is_equal(language,"pl")) 919 return "Zosia"; 920 else if (string_is_equal(language,"cs")) 921 return "Zuzana"; 922 else 923 return ""; 924} 925 926static bool is_narrator_running_macos(void) 927{ 928 return (kill(speak_pid, 0) == 0); 929} 930 931static bool accessibility_speak_macos(int speed, 932 const char* speak_text, int priority) 933{ 934 int pid; 935 const char *voice = get_user_language_iso639_1(false); 936 char* language_speaker = accessibility_mac_language_code(voice); 937 char* speeds[10] = {"80", "100", "125", "150", "170", "210", "260", "310", "380", "450"}; 938 939 if (speed < 1) 940 speed = 1; 941 else if (speed > 10) 942 speed = 10; 943 944 if (priority < 10 && speak_pid > 0) 945 { 946 /* check if old pid is running */ 947 if (is_narrator_running_macos()) 948 return true; 949 } 950 951 if (speak_pid > 0) 952 { 953 /* Kill the running say */ 954 kill(speak_pid, SIGTERM); 955 speak_pid = 0; 956 } 957 958 pid = fork(); 959 if (pid < 0) 960 { 961 /* error */ 962 RARCH_LOG("ERROR: could not fork for say command.\n"); 963 } 964 else if (pid > 0) 965 { 966 /* parent process */ 967 speak_pid = pid; 968 969 /* Tell the system that we'll ignore the exit status of the child 970 * process. This prevents zombie processes. */ 971 signal(SIGCHLD,SIG_IGN); 972 } 973 else 974 { 975 /* child process: replace process with the say command */ 976 if (strlen(language_speaker)> 0) 977 { 978 char* cmd[] = {"say", "-v", NULL, 979 NULL, "-r", NULL, NULL}; 980 cmd[2] = language_speaker; 981 cmd[3] = (char *) speak_text; 982 cmd[5] = speeds[speed-1]; 983 execvp("say", cmd); 984 } 985 else 986 { 987 char* cmd[] = {"say", NULL, "-r", NULL, NULL}; 988 cmd[1] = (char*) speak_text; 989 cmd[3] = speeds[speed-1]; 990 execvp("say",cmd); 991 } 992 } 993 return true; 994} 995#endif 996 997frontend_ctx_driver_t frontend_ctx_darwin = { 998 frontend_darwin_get_env, /* get_env */ 999 NULL, /* init */ 1000 NULL, /* deinit */ 1001 NULL, /* exitspawn */ 1002 NULL, /* process_args */ 1003 NULL, /* exec */ 1004 NULL, /* set_fork */ 1005 NULL, /* shutdown */ 1006 frontend_darwin_get_name, /* get_name */ 1007 frontend_darwin_get_os, /* get_os */ 1008 frontend_darwin_get_rating, /* get_rating */ 1009 frontend_darwin_content_loaded, /* content_loaded */ 1010 frontend_darwin_get_arch, /* get_architecture */ 1011 frontend_darwin_get_powerstate, /* get_powerstate */ 1012 frontend_darwin_parse_drive_list,/* parse_drive_list */ 1013 frontend_darwin_get_total_mem, /* get_total_mem */ 1014 frontend_darwin_get_free_mem, /* get_free_mem */ 1015 NULL, /* install_signal_handler */ 1016 NULL, /* get_sighandler_state */ 1017 NULL, /* set_sighandler_state */ 1018 NULL, /* destroy_signal_handler_state */ 1019 NULL, /* attach_console */ 1020 NULL, /* detach_console */ 1021 NULL, /* get_lakka_version */ 1022 NULL, /* set_screen_brightness */ 1023 NULL, /* watch_path_for_changes */ 1024 NULL, /* check_for_path_changes */ 1025 NULL, /* set_sustained_performance_mode */ 1026#if (defined(OSX) && !(defined(__ppc__) || defined(__ppc64__))) 1027 frontend_darwin_get_cpu_model_name, /* get_cpu_model_name */ 1028#else 1029 NULL, /* get_cpu_model_name */ 1030#endif 1031 NULL, /* get_user_language */ 1032#if (defined(OSX) && !(defined(__ppc__) || defined(__ppc64__))) 1033 is_narrator_running_macos, /* is_narrator_running */ 1034 accessibility_speak_macos, /* accessibility_speak */ 1035#else 1036 NULL, /* is_narrator_running */ 1037 NULL, /* accessibility_speak */ 1038#endif 1039 "darwin", /* ident */ 1040 NULL /* get_video_driver */ 1041}; 1042