/*
* Copyright (C) 2010-2013 Team XBMC
* http://xbmc.org
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with XBMC; see the file COPYING. If not, see
* .
*
*/
#define BOOL XBMC_BOOL
#include "system.h"
#include "Application.h"
#include "DllPaths.h"
#include "GUIUserMessages.h"
#include "utils/log.h"
#include "CompileInfo.h"
#undef BOOL
#if defined(TARGET_DARWIN)
#if defined(TARGET_DARWIN_IOS)
#import
#import
#import
#import
#else
#import
#import
#import
#import
#import
#endif
#import "AutoPool.h"
#import "DarwinUtils.h"
#ifndef NSAppKitVersionNumber10_5
#define NSAppKitVersionNumber10_5 949
#endif
#ifndef NSAppKitVersionNumber10_6
#define NSAppKitVersionNumber10_6 1038
#endif
enum iosPlatform
{
iDeviceUnknown = -1,
iPhone2G,
iPhone3G,
iPhone3GS,
iPodTouch1G,
iPodTouch2G,
iPodTouch3G,
iPad,
iPad3G,
iPad2WIFI,
iPad2CDMA,
iPad2,
iPadMini,
iPadMiniGSMCDMA,
iPadMiniWIFI,
AppleTV2,
iPhone4, //from here on list devices with retina support (e.x. mainscreen scale == 2.0)
iPhone4CDMA,
iPhone4S,
iPhone5,
iPhone5GSMCDMA,
iPhone5CGSM,
iPhone5CGlobal,
iPhone5SGSM,
iPhone5SGlobal,
iPodTouch4G,
iPodTouch5G,
iPad3WIFI,
iPad3GSMCDMA,
iPad3,
iPad4WIFI,
iPad4,
iPad4GSMCDMA,
iPadAirWifi,
iPadAirCellular,
iPadMini2Wifi,
iPadMini2Cellular,
iPhone6,
iPadAir2Wifi,
iPadAir2Cellular,
iPadMini3Wifi,
iPadMini3Cellular,
iPhone6Plus, //from here on list devices with retina support which have scale == 3.0
};
// platform strings are based on http://theiphonewiki.com/wiki/Models
const char* CDarwinUtils::getIosPlatformString(void)
{
static std::string iOSPlatformString;
if (iOSPlatformString.empty())
{
#if defined(TARGET_DARWIN_IOS)
// Gets a string with the device model
size_t size;
sysctlbyname("hw.machine", NULL, &size, NULL, 0);
char *machine = new char[size];
if (sysctlbyname("hw.machine", machine, &size, NULL, 0) == 0 && machine[0])
iOSPlatformString.assign(machine, size -1);
else
#endif
iOSPlatformString = "unknown0,0";
#if defined(TARGET_DARWIN_IOS)
delete [] machine;
#endif
}
return iOSPlatformString.c_str();
}
enum iosPlatform getIosPlatform()
{
static enum iosPlatform eDev = iDeviceUnknown;
#if defined(TARGET_DARWIN_IOS)
if (eDev == iDeviceUnknown)
{
std::string devStr(CDarwinUtils::getIosPlatformString());
if (devStr == "iPhone1,1") eDev = iPhone2G;
else if (devStr == "iPhone1,2") eDev = iPhone3G;
else if (devStr == "iPhone2,1") eDev = iPhone3GS;
else if (devStr == "iPhone3,1") eDev = iPhone4;
else if (devStr == "iPhone3,2") eDev = iPhone4;
else if (devStr == "iPhone3,3") eDev = iPhone4CDMA;
else if (devStr == "iPhone4,1") eDev = iPhone4S;
else if (devStr == "iPhone5,1") eDev = iPhone5;
else if (devStr == "iPhone5,2") eDev = iPhone5GSMCDMA;
else if (devStr == "iPhone5,3") eDev = iPhone5CGSM;
else if (devStr == "iPhone5,4") eDev = iPhone5CGlobal;
else if (devStr == "iPhone6,1") eDev = iPhone5SGSM;
else if (devStr == "iPhone6,2") eDev = iPhone5SGlobal;
else if (devStr == "iPhone7,1") eDev = iPhone6Plus;
else if (devStr == "iPhone7,2") eDev = iPhone6;
else if (devStr == "iPod1,1") eDev = iPodTouch1G;
else if (devStr == "iPod2,1") eDev = iPodTouch2G;
else if (devStr == "iPod3,1") eDev = iPodTouch3G;
else if (devStr == "iPod4,1") eDev = iPodTouch4G;
else if (devStr == "iPod5,1") eDev = iPodTouch5G;
else if (devStr == "iPad1,1") eDev = iPad;
else if (devStr == "iPad1,2") eDev = iPad;
else if (devStr == "iPad2,1") eDev = iPad2WIFI;
else if (devStr == "iPad2,2") eDev = iPad2;
else if (devStr == "iPad2,3") eDev = iPad2CDMA;
else if (devStr == "iPad2,4") eDev = iPad2;
else if (devStr == "iPad2,5") eDev = iPadMiniWIFI;
else if (devStr == "iPad2,6") eDev = iPadMini;
else if (devStr == "iPad2,7") eDev = iPadMiniGSMCDMA;
else if (devStr == "iPad3,1") eDev = iPad3WIFI;
else if (devStr == "iPad3,2") eDev = iPad3GSMCDMA;
else if (devStr == "iPad3,3") eDev = iPad3;
else if (devStr == "iPad3,4") eDev = iPad4WIFI;
else if (devStr == "iPad3,5") eDev = iPad4;
else if (devStr == "iPad3,6") eDev = iPad4GSMCDMA;
else if (devStr == "iPad4,1") eDev = iPadAirWifi;
else if (devStr == "iPad4,2") eDev = iPadAirCellular;
else if (devStr == "iPad4,4") eDev = iPadMini2Wifi;
else if (devStr == "iPad4,5") eDev = iPadMini2Cellular;
else if (devStr == "iPad4,7") eDev = iPadMini3Wifi;
else if (devStr == "iPad4,8") eDev = iPadMini3Cellular;
else if (devStr == "iPad4,9") eDev = iPadMini3Cellular;
else if (devStr == "iPad5,3") eDev = iPadAir2Wifi;
else if (devStr == "iPad5,4") eDev = iPadAir2Cellular;
else if (devStr == "AppleTV2,1") eDev = AppleTV2;
}
#endif
return eDev;
}
bool CDarwinUtils::IsAppleTV2(void)
{
static enum iosPlatform platform = iDeviceUnknown;
#if defined(TARGET_DARWIN_IOS)
if( platform == iDeviceUnknown )
{
platform = getIosPlatform();
}
#endif
return (platform == AppleTV2);
}
bool CDarwinUtils::IsMavericks(void)
{
static int isMavericks = -1;
#if defined(TARGET_DARWIN_OSX)
// there is no NSAppKitVersionNumber10_9 out there anywhere
// so we detect mavericks by one of these newly added app nap
// methods - and fix the ugly mouse rect problem which was hitting
// us when mavericks came out
if (isMavericks == -1)
{
CLog::Log(LOGDEBUG, "Detected Mavericks...");
isMavericks = [NSProcessInfo instancesRespondToSelector:@selector(beginActivityWithOptions:reason:)] == TRUE ? 1 : 0;
}
#endif
return isMavericks == 1;
}
bool CDarwinUtils::IsSnowLeopard(void)
{
static int isSnowLeopard = -1;
#if defined(TARGET_DARWIN_OSX)
if (isSnowLeopard == -1)
{
double appKitVersion = floor(NSAppKitVersionNumber);
isSnowLeopard = (appKitVersion <= NSAppKitVersionNumber10_6 && appKitVersion > NSAppKitVersionNumber10_5) ? 1 : 0;
}
#endif
return isSnowLeopard == 1;
}
bool CDarwinUtils::DeviceHasRetina(double &scale)
{
static enum iosPlatform platform = iDeviceUnknown;
#if defined(TARGET_DARWIN_IOS)
if( platform == iDeviceUnknown )
{
platform = getIosPlatform();
}
#endif
scale = 1.0; //no retina
// see http://www.paintcodeapp.com/news/iphone-6-screens-demystified
if (platform >= iPhone4 && platform < iPhone6Plus)
{
scale = 2.0; // 2x render retina
}
if (platform >= iPhone6Plus)
{
scale = 3.0; //3x render retina + downscale
}
return (platform >= iPhone4);
}
const char *CDarwinUtils::GetOSReleaseString(void)
{
static std::string osreleaseStr;
if (osreleaseStr.empty())
{
size_t size;
sysctlbyname("kern.osrelease", NULL, &size, NULL, 0);
char *osrelease = new char[size];
sysctlbyname("kern.osrelease", osrelease, &size, NULL, 0);
osreleaseStr = osrelease;
delete [] osrelease;
}
return osreleaseStr.c_str();
}
const char *CDarwinUtils::GetOSVersionString(void)
{
CCocoaAutoPool pool;
return [[[NSProcessInfo processInfo] operatingSystemVersionString] UTF8String];
}
float CDarwinUtils::GetIOSVersion(void)
{
CCocoaAutoPool pool;
float version;
#if defined(TARGET_DARWIN_IOS)
version = [[[UIDevice currentDevice] systemVersion] floatValue];
#else
version = 0.0f;
#endif
return(version);
}
const char *CDarwinUtils::GetIOSVersionString(void)
{
#if defined(TARGET_DARWIN_IOS)
static std::string iOSVersionString;
if (iOSVersionString.empty())
{
CCocoaAutoPool pool;
iOSVersionString.assign((const char*)[[[UIDevice currentDevice] systemVersion] UTF8String]);
}
return iOSVersionString.c_str();
#else
return "0.0";
#endif
}
const char *CDarwinUtils::GetOSXVersionString(void)
{
#if defined(TARGET_DARWIN_OSX)
static std::string OSXVersionString;
if (OSXVersionString.empty())
{
CCocoaAutoPool pool;
OSXVersionString.assign((const char*)[[[NSDictionary dictionaryWithContentsOfFile:
@"/System/Library/CoreServices/SystemVersion.plist"] objectForKey:@"ProductVersion"] UTF8String]);
}
return OSXVersionString.c_str();
#else
return "0.0";
#endif
}
int CDarwinUtils::GetFrameworkPath(bool forPython, char* path, uint32_t *pathsize)
{
CCocoaAutoPool pool;
// see if we can figure out who we are
NSString *pathname;
path[0] = 0;
*pathsize = 0;
// a) Kodi frappliance running under ATV2
Class Frapp = NSClassFromString(@"AppATV2Detector");
if (Frapp != NULL)
{
pathname = [[NSBundle bundleForClass:Frapp] pathForResource:@"Frameworks" ofType:@""];
strcpy(path, [pathname UTF8String]);
*pathsize = strlen(path);
//CLog::Log(LOGDEBUG, "DarwinFrameworkPath(a) -> %s", path);
return 0;
}
// b) Kodi application running under IOS
pathname = [[NSBundle mainBundle] executablePath];
std::string appName = std::string(CCompileInfo::GetAppName()) + ".app/" + std::string(CCompileInfo::GetAppName());
if (pathname && strstr([pathname UTF8String], appName.c_str()))
{
strcpy(path, [pathname UTF8String]);
// Move backwards to last "/"
for (int n=strlen(path)-1; path[n] != '/'; n--)
path[n] = '\0';
strcat(path, "Frameworks");
*pathsize = strlen(path);
//CLog::Log(LOGDEBUG, "DarwinFrameworkPath(c) -> %s", path);
return 0;
}
// d) Kodi application running under OSX
pathname = [[NSBundle mainBundle] executablePath];
if (pathname && strstr([pathname UTF8String], "Contents"))
{
strcpy(path, [pathname UTF8String]);
// ExectuablePath is .app/Contents/MacOS/
char *lastSlash = strrchr(path, '/');
if (lastSlash)
{
*lastSlash = '\0';//remove /
lastSlash = strrchr(path, '/');
if (lastSlash)
*lastSlash = '\0';//remove /MacOS
}
strcat(path, "/Libraries");//add /Libraries
//we should have .app/Contents/Libraries now
*pathsize = strlen(path);
//CLog::Log(LOGDEBUG, "DarwinFrameworkPath(d) -> %s", path);
return 0;
}
// e) Kodi OSX binary running under xcode or command-line
// but only if it's not for python. In this case, let python
// use it's internal compiled paths.
if (!forPython)
{
strcpy(path, PREFIX_USR_PATH);
strcat(path, "/lib");
*pathsize = strlen(path);
//CLog::Log(LOGDEBUG, "DarwinFrameworkPath(e) -> %s", path);
return 0;
}
return -1;
}
int CDarwinUtils::GetExecutablePath(char* path, uint32_t *pathsize)
{
CCocoaAutoPool pool;
// see if we can figure out who we are
NSString *pathname;
// a) Kodi frappliance running under ATV2
Class Frapp = NSClassFromString(@"AppATV2Detector");
if (Frapp != NULL)
{
NSString *appName = [NSString stringWithUTF8String:CCompileInfo::GetAppName()];
pathname = [[NSBundle bundleForClass:Frapp] pathForResource:appName ofType:@""];
strcpy(path, [pathname UTF8String]);
*pathsize = strlen(path);
//CLog::Log(LOGDEBUG, "DarwinExecutablePath(a) -> %s", path);
return 0;
}
// b) Kodi application running under IOS
// c) Kodi application running under OSX
pathname = [[NSBundle mainBundle] executablePath];
strcpy(path, [pathname UTF8String]);
*pathsize = strlen(path);
//CLog::Log(LOGDEBUG, "DarwinExecutablePath(b/c) -> %s", path);
return 0;
}
const char* CDarwinUtils::GetAppRootFolder(void)
{
static std::string rootFolder = "";
if ( rootFolder.length() == 0)
{
if (IsIosSandboxed())
{
// when we are sandbox make documents our root
// so that user can access everything he needs
// via itunes sharing
rootFolder = "Documents";
}
else
{
rootFolder = "Library/Preferences";
}
}
return rootFolder.c_str();
}
bool CDarwinUtils::IsIosSandboxed(void)
{
static int ret = -1;
if (ret == -1)
{
uint32_t path_size = 2*MAXPATHLEN;
char given_path[2*MAXPATHLEN];
int result = -1;
ret = 0;
memset(given_path, 0x0, path_size);
/* Get Application directory */
result = GetExecutablePath(given_path, &path_size);
if (result == 0)
{
// we re sandboxed if we are installed in /var/mobile/Applications
if (strlen("/var/mobile/Applications/") < path_size &&
strncmp(given_path, "/var/mobile/Applications/", strlen("/var/mobile/Applications/")) == 0)
{
ret = 1;
}
}
}
return ret == 1;
}
bool CDarwinUtils::HasVideoToolboxDecoder(void)
{
static int DecoderAvailable = -1;
if (DecoderAvailable == -1)
{
Class XBMCfrapp = NSClassFromString(@"AppATV2Detector");
if (XBMCfrapp != NULL)
{
// atv2 has seatbelt profile key removed so nothing to do here
DecoderAvailable = 1;
}
else
{
/* When XBMC is started from a sandbox directory we have to check the sysctl values */
if (IsIosSandboxed())
{
uint64_t proc_enforce = 0;
uint64_t vnode_enforce = 0;
size_t size = sizeof(vnode_enforce);
sysctlbyname("security.mac.proc_enforce", &proc_enforce, &size, NULL, 0);
sysctlbyname("security.mac.vnode_enforce", &vnode_enforce, &size, NULL, 0);
if (vnode_enforce && proc_enforce)
{
DecoderAvailable = 1;
CLog::Log(LOGINFO, "VideoToolBox decoder not available. Use : sysctl -w security.mac.proc_enforce=0; sysctl -w security.mac.vnode_enforce=0\n");
}
else
{
DecoderAvailable = 1;
CLog::Log(LOGINFO, "VideoToolBox decoder available\n");
}
}
else
{
DecoderAvailable = 1;
}
}
}
return (DecoderAvailable == 1);
}
int CDarwinUtils::BatteryLevel(void)
{
float batteryLevel = 0;
#if defined(TARGET_DARWIN_IOS)
if(!IsAppleTV2())
batteryLevel = [[UIDevice currentDevice] batteryLevel];
#else
CFTypeRef powerSourceInfo = IOPSCopyPowerSourcesInfo();
CFArrayRef powerSources = IOPSCopyPowerSourcesList(powerSourceInfo);
CFDictionaryRef powerSource = NULL;
const void *powerSourceVal;
for (int i = 0 ; i < CFArrayGetCount(powerSources) ; i++)
{
powerSource = IOPSGetPowerSourceDescription(powerSourceInfo, CFArrayGetValueAtIndex(powerSources, i));
if (!powerSource) break;
powerSourceVal = (CFStringRef)CFDictionaryGetValue(powerSource, CFSTR(kIOPSNameKey));
int curLevel = 0;
int maxLevel = 0;
powerSourceVal = CFDictionaryGetValue(powerSource, CFSTR(kIOPSCurrentCapacityKey));
CFNumberGetValue((CFNumberRef)powerSourceVal, kCFNumberSInt32Type, &curLevel);
powerSourceVal = CFDictionaryGetValue(powerSource, CFSTR(kIOPSMaxCapacityKey));
CFNumberGetValue((CFNumberRef)powerSourceVal, kCFNumberSInt32Type, &maxLevel);
batteryLevel = (double)curLevel/(double)maxLevel;
}
#endif
return batteryLevel * 100;
}
void CDarwinUtils::SetScheduling(int message)
{
int policy;
struct sched_param param;
pthread_t this_pthread_self = pthread_self();
int32_t result = pthread_getschedparam(this_pthread_self, &policy, ¶m );
policy = SCHED_OTHER;
thread_extended_policy_data_t theFixedPolicy={true};
if (message == GUI_MSG_PLAYBACK_STARTED && g_application.m_pPlayer->IsPlayingVideo())
{
policy = SCHED_RR;
theFixedPolicy.timeshare = false;
}
result = thread_policy_set(pthread_mach_thread_np(this_pthread_self),
THREAD_EXTENDED_POLICY,
(thread_policy_t)&theFixedPolicy,
THREAD_EXTENDED_POLICY_COUNT);
result = pthread_setschedparam(this_pthread_self, policy, ¶m );
}
bool CFStringRefToStringWithEncoding(CFStringRef source, std::string &destination, CFStringEncoding encoding)
{
const char *cstr = CFStringGetCStringPtr(source, encoding);
if (!cstr)
{
CFIndex strLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(source) + 1,
encoding);
char *allocStr = (char*)malloc(strLen);
if(!allocStr)
return false;
if(!CFStringGetCString(source, allocStr, strLen, encoding))
{
free((void*)allocStr);
return false;
}
destination = allocStr;
free((void*)allocStr);
return true;
}
destination = cstr;
return true;
}
void CDarwinUtils::PrintDebugString(std::string debugString)
{
NSLog(@"Debug Print: %s", debugString.c_str());
}
bool CDarwinUtils::CFStringRefToString(CFStringRef source, std::string &destination)
{
return CFStringRefToStringWithEncoding(source, destination, CFStringGetSystemEncoding());
}
bool CDarwinUtils::CFStringRefToUTF8String(CFStringRef source, std::string &destination)
{
return CFStringRefToStringWithEncoding(source, destination, kCFStringEncodingUTF8);
}
const std::string& CDarwinUtils::GetManufacturer(void)
{
static std::string manufName;
if (manufName.empty())
{
#ifdef TARGET_DARWIN_IOS
// to avoid dlloading of IOIKit, hardcode return value
// until other than Apple devices with iOS will be released
manufName = "Apple Inc.";
#elif defined(TARGET_DARWIN_OSX)
const CFMutableDictionaryRef matchExpDev = IOServiceMatching("IOPlatformExpertDevice");
if (matchExpDev)
{
const io_service_t servExpDev = IOServiceGetMatchingService(kIOMasterPortDefault, matchExpDev);
if (servExpDev)
{
CFTypeRef manufacturer = IORegistryEntryCreateCFProperty(servExpDev, CFSTR("manufacturer"), kCFAllocatorDefault, 0);
if (manufacturer)
{
if (CFGetTypeID(manufacturer) == CFStringGetTypeID())
manufName = (const char*)[[NSString stringWithString:(NSString *)manufacturer] UTF8String];
else if (CFGetTypeID(manufacturer) == CFDataGetTypeID())
{
manufName.assign((const char*)CFDataGetBytePtr((CFDataRef)manufacturer), CFDataGetLength((CFDataRef)manufacturer));
if (!manufName.empty() && manufName[manufName.length() - 1] == 0)
manufName.erase(manufName.length() - 1); // remove extra null at the end if any
}
CFRelease(manufacturer);
}
}
IOObjectRelease(servExpDev);
}
#endif // TARGET_DARWIN_OSX
}
return manufName;
}
#endif