diff --git a/build/macosx/Juce.xcodeproj/project.pbxproj b/build/macosx/Juce.xcodeproj/project.pbxproj index 4083c83317..3dd0f56e42 100644 --- a/build/macosx/Juce.xcodeproj/project.pbxproj +++ b/build/macosx/Juce.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 8484E9D5103C9595008B7C6C /* juce_mac_WebBrowserComponent.mm in Sources */ = {isa = PBXBuildFile; fileRef = 8484E9BD103C9595008B7C6C /* juce_mac_WebBrowserComponent.mm */; }; 8484E9D8103C95A6008B7C6C /* juce_posix_SharedCode.h in Headers */ = {isa = PBXBuildFile; fileRef = 8484E9D6103C95A6008B7C6C /* juce_posix_SharedCode.h */; }; 8484E9D9103C95A6008B7C6C /* juce_posix_NamedPipe.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8484E9D7103C95A6008B7C6C /* juce_posix_NamedPipe.cpp */; }; + 84A63C02107DF286000326FD /* juce_mac_ObjCSuffix.h in Headers */ = {isa = PBXBuildFile; fileRef = 84A63C01107DF286000326FD /* juce_mac_ObjCSuffix.h */; }; 84F1E6E710403605006A1807 /* juce_Application.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84F1E6DC10403605006A1807 /* juce_Application.cpp */; }; 84F1E6E810403605006A1807 /* juce_Application.h in Headers */ = {isa = PBXBuildFile; fileRef = 84F1E6DD10403605006A1807 /* juce_Application.h */; }; 84F1E6E910403605006A1807 /* juce_ApplicationCommandID.h in Headers */ = {isa = PBXBuildFile; fileRef = 84F1E6DE10403605006A1807 /* juce_ApplicationCommandID.h */; }; @@ -631,6 +632,7 @@ 8484E9BD103C9595008B7C6C /* juce_mac_WebBrowserComponent.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = juce_mac_WebBrowserComponent.mm; path = ../../src/native/mac/juce_mac_WebBrowserComponent.mm; sourceTree = SOURCE_ROOT; }; 8484E9D6103C95A6008B7C6C /* juce_posix_SharedCode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = juce_posix_SharedCode.h; path = ../../src/native/common/juce_posix_SharedCode.h; sourceTree = SOURCE_ROOT; }; 8484E9D7103C95A6008B7C6C /* juce_posix_NamedPipe.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = juce_posix_NamedPipe.cpp; path = ../../src/native/common/juce_posix_NamedPipe.cpp; sourceTree = SOURCE_ROOT; }; + 84A63C01107DF286000326FD /* juce_mac_ObjCSuffix.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = juce_mac_ObjCSuffix.h; sourceTree = ""; }; 84F1E6DC10403605006A1807 /* juce_Application.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = juce_Application.cpp; path = ../../src/application/juce_Application.cpp; sourceTree = SOURCE_ROOT; }; 84F1E6DD10403605006A1807 /* juce_Application.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = juce_Application.h; path = ../../src/application/juce_Application.h; sourceTree = SOURCE_ROOT; }; 84F1E6DE10403605006A1807 /* juce_ApplicationCommandID.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = juce_ApplicationCommandID.h; path = ../../src/application/juce_ApplicationCommandID.h; sourceTree = SOURCE_ROOT; }; @@ -1251,7 +1253,6 @@ 84A4881C08A22E2400752A2B /* native mac code */ = { isa = PBXGroup; children = ( - 8484E9A4103C958A008B7C6C /* juce_mac_NativeCode.mm */, 8484E9A6103C9595008B7C6C /* juce_mac_AppleRemote.mm */, 8484E9A7103C9595008B7C6C /* juce_mac_AudioCDBurner.mm */, 8484E9A8103C9595008B7C6C /* juce_mac_CameraDevice.mm */, @@ -1266,10 +1267,12 @@ 8484E9B1103C9595008B7C6C /* juce_mac_MessageManager.mm */, 8484E9B2103C9595008B7C6C /* juce_mac_MiscUtilities.mm */, 8484E9B3103C9595008B7C6C /* juce_mac_MouseCursor.mm */, + 8484E9A4103C958A008B7C6C /* juce_mac_NativeCode.mm */, 8484E9B4103C9595008B7C6C /* juce_mac_NativeIncludes.h */, 8484E9B5103C9595008B7C6C /* juce_mac_Network.mm */, 8484E9B6103C9595008B7C6C /* juce_mac_NSViewComponent.mm */, 8484E9B7103C9595008B7C6C /* juce_mac_NSViewComponentPeer.mm */, + 84A63C01107DF286000326FD /* juce_mac_ObjCSuffix.h */, 8484E9B8103C9595008B7C6C /* juce_mac_OpenGLComponent.mm */, 8484E9B9103C9595008B7C6C /* juce_mac_QuickTimeMovieComponent.mm */, 8484E9BA103C9595008B7C6C /* juce_mac_Strings.mm */, @@ -2560,6 +2563,7 @@ 84F1ECE41040370A006A1807 /* juce_ImageConvolutionKernel.h in Headers */, 84F1ECE61040370A006A1807 /* juce_ImageFileFormat.h in Headers */, 84F1ECE81040370A006A1807 /* juce_GIFLoader.h in Headers */, + 84A63C02107DF286000326FD /* juce_mac_ObjCSuffix.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/juce_Config.h b/juce_Config.h index 689579a6cb..bc9a47cade 100644 --- a/juce_Config.h +++ b/juce_Config.h @@ -101,7 +101,7 @@ On Windows, if you enable this, you'll need to have the QuickTime SDK installed, and its header files will need to be on your include path. */ -#if ! (defined (JUCE_QUICKTIME) || defined (LINUX) || (defined (_WIN32) && ! defined (_MSC_VER))) +#if ! (defined (JUCE_QUICKTIME) || defined (LINUX) || defined (TARGET_OS_IPHONE) || defined (TARGET_IPHONE_SIMULATOR) || (defined (_WIN32) && ! defined (_MSC_VER))) #define JUCE_QUICKTIME 1 #endif diff --git a/juce_amalgamated.cpp b/juce_amalgamated.cpp index e3d571081c..759c7c5260 100644 --- a/juce_amalgamated.cpp +++ b/juce_amalgamated.cpp @@ -111,7 +111,7 @@ On Windows, if you enable this, you'll need to have the QuickTime SDK installed, and its header files will need to be on your include path. */ -#if ! (defined (JUCE_QUICKTIME) || defined (LINUX) || (defined (_WIN32) && ! defined (_MSC_VER))) +#if ! (defined (JUCE_QUICKTIME) || defined (LINUX) || defined (TARGET_OS_IPHONE) || defined (TARGET_IPHONE_SIMULATOR) || (defined (_WIN32) && ! defined (_MSC_VER))) #define JUCE_QUICKTIME 1 #endif @@ -542,6 +542,41 @@ public: #endif // __JUCE_LINUX_NATIVEINCLUDES_JUCEHEADER__ /********* End of inlined file: juce_linux_NativeIncludes.h *********/ +#elif TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + +/********* Start of inlined file: juce_iphone_NativeIncludes.h *********/ +#ifndef __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ +#define __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ + +/* + This file wraps together all the mac-specific code, so that + we can include all the native headers just once, and compile all our + platform-specific stuff in one big lump, keeping it out of the way of + the rest of the codebase. +*/ + +#import +#import +#import +#import +#import +#import +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include + +#endif // __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ +/********* End of inlined file: juce_iphone_NativeIncludes.h *********/ + #else /********* Start of inlined file: juce_mac_NativeIncludes.h *********/ @@ -566,9 +601,6 @@ public: #import #import #import -#import -#import -#import #import #include @@ -579,6 +611,9 @@ public: #include #include #include +#include +#include +#include #if MACOS_10_4_OR_EARLIER #include @@ -1394,10 +1429,9 @@ void JUCE_PUBLIC_FUNCTION initialiseJuce_NonGUI() juceInitialisedNonGUI = true; DBG (SystemStats::getJUCEVersion()); - Random::getSystemRandom().setSeedRandomly(); // (calling this more than once improves its randomness) juce_initialiseStrings(); SystemStats::initialiseStats(); - Random::getSystemRandom().setSeedRandomly(); // (calling this more than once improves its randomness) + Random::getSystemRandom().setSeedRandomly(); // (mustn't call this before initialiseStats() because it relies on the time being set up) } } @@ -7209,7 +7243,7 @@ END_JUCE_NAMESPACE #include #include #else - #if MACOSX_DEPLOYMENT_TARGET <= MAC_OS_X_VERSION_10_4 + #if (MACOSX_DEPLOYMENT_TARGET <= MAC_OS_X_VERSION_10_4) && ! (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) #include #endif #endif @@ -7222,6 +7256,12 @@ END_JUCE_NAMESPACE BEGIN_JUCE_NAMESPACE +#if defined (JUCE_LINUX) || defined (JUCE_MAC) || defined (JUCE_IPHONE) + typedef socklen_t juce_socklen_t; +#else + typedef int juce_socklen_t; +#endif + #if JUCE_WIN32 typedef int (__stdcall juce_CloseWin32SocketLibCall) (void); @@ -7353,12 +7393,7 @@ static int waitForReadiness (const int handle, const bool forReading, { int opt; - -#if defined (JUCE_LINUX) || defined (JUCE_MAC) - socklen_t len = sizeof (opt); -#else - int len = sizeof (opt); -#endif + juce_socklen_t len = sizeof (opt); if (getsockopt (handle, SOL_SOCKET, SO_ERROR, (char*) &opt, &len) < 0 || opt != 0) @@ -7627,12 +7662,7 @@ StreamingSocket* StreamingSocket::waitForNextConnection() const if (connected && isListener) { struct sockaddr address; - -#if defined (JUCE_LINUX) || defined (JUCE_MAC) - socklen_t len = sizeof (sockaddr); -#else - int len = sizeof (sockaddr); -#endif + juce_socklen_t len = sizeof (sockaddr); const int newSocket = (int) accept (handle, &address, &len); if (newSocket >= 0 && connected) @@ -7734,12 +7764,7 @@ bool DatagramSocket::connect (const String& remoteHostName, DatagramSocket* DatagramSocket::waitForNextConnection() const { struct sockaddr address; - -#if defined (JUCE_LINUX) || defined (JUCE_MAC) - socklen_t len = sizeof (sockaddr); -#else - int len = sizeof (sockaddr); -#endif + juce_socklen_t len = sizeof (sockaddr); while (waitUntilReady (true, -1) == 1) { @@ -17265,7 +17290,7 @@ const File PropertiesFile::getDefaultAppSettingsFile (const String& applicationN // mustn't have illegal characters in this name.. jassert (applicationName == File::createLegalFileName (applicationName)); -#if JUCE_MAC +#if JUCE_MAC || JUCE_IPHONE File dir (commonToAllUsers ? "/Library/Preferences" : "~/Library/Preferences"); @@ -55043,7 +55068,7 @@ public: void paintItem (Graphics& g, int width, int height) { - if (file != File::nonexistent && ! isDirectory) + if (file != File::nonexistent) { updateIcon (true); @@ -233612,12 +233637,9 @@ static int getMACAddressesViaNetBios (int64* addresses, int maxNum, const bool l { if (astat.adapt.adapter_type == 0xfe) { - int64 mac = 0; - for (unsigned int i = 0; i < 6; ++i) - mac = (mac << 8) | astat.adapt.adapter_address[i]; - - if (littleEndian) - mac = (int64) swapByteOrder ((uint64) mac); + uint64 mac = 0; + for (int i = 6; --i >= 0;) + mac = (mac << 8) | astat.adapt.adapter_address [littleEndian ? i : (5 - i)]; if (numFound < maxNum && mac != 0) addresses [numFound++] = mac; @@ -249321,7 +249343,7 @@ int SystemStats::getMACAddresses (int64* addresses, int maxNum, const bool littl { int64 a = 0; for (int j = 6; --j >= 0;) - a = (a << 8) | (uint8) ifr.ifr_hwaddr.sa_data[j]; + a = (a << 8) | (uint8) ifr.ifr_hwaddr.sa_data [littleEndian ? j : (5 - j)]; *addresses++ = a; ++numResults; @@ -256010,6 +256032,11 @@ BEGIN_JUCE_NAMESPACE #undef Point +#define JUCE_INCLUDED_FILE 1 + +// Now include the actual code files.. + +/********* Start of inlined file: juce_mac_ObjCSuffix.h *********/ /** This suffix is used for naming all Obj-C classes that are used inside juce. Because of the flat naming structure used by Obj-C, you can get horrible situations where @@ -256030,10 +256057,7 @@ BEGIN_JUCE_NAMESPACE #define appendMacro1(a, b, c, d) a ## _ ## b ## _ ## c ## _ ## d #define appendMacro2(a, b, c, d) appendMacro1(a, b, c, d) #define MakeObjCClassName(rootName) appendMacro2 (rootName, JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_ObjCExtraSuffix) - -#define JUCE_INCLUDED_FILE 1 - -// Now include the actual code files.. +/********* End of inlined file: juce_mac_ObjCSuffix.h *********/ /********* Start of inlined file: juce_mac_Strings.mm *********/ // (This file gets included by juce_mac_NativeCode.mm, rather than being @@ -256113,6 +256137,10 @@ CFStringRef PlatformUtilities::juceStringToCFString (const String& s) const String PlatformUtilities::convertToPrecomposedUnicode (const String& s) { +#if JUCE_IPHONE + const ScopedAutoReleasePool pool; + return nsStringToJuce ([juceStringToNS (s) precomposedStringWithCanonicalMapping]); +#else UnicodeMapping map; map.unicodeEncoding = CreateTextEncoding (kTextEncodingUnicodeDefault, @@ -256166,22 +256194,32 @@ const String PlatformUtilities::convertToPrecomposedUnicode (const String& s) } return result; +#endif } #if ! JUCE_ONLY_BUILD_CORE_LIBRARY void SystemClipboard::copyTextToClipboard (const String& text) throw() { +#if JUCE_IPHONE + [[UIPasteboard generalPasteboard] setValue: juceStringToNS (text) + forPasteboardType: (NSString*) kUTTypePlainText]; +#else [[NSPasteboard generalPasteboard] declareTypes: [NSArray arrayWithObject: NSStringPboardType] owner: nil]; [[NSPasteboard generalPasteboard] setString: juceStringToNS (text) forType: NSStringPboardType]; +#endif } const String SystemClipboard::getTextFromClipboard() throw() { +#if JUCE_IPHONE + NSString* text = [[UIPasteboard generalPasteboard] valueForPasteboardType: (NSString*) kUTTypePlainText]; +#else NSString* text = [[NSPasteboard generalPasteboard] stringForType: NSStringPboardType]; +#endif return text == 0 ? String::empty : nsStringToJuce (text); @@ -256197,7 +256235,8 @@ const String SystemClipboard::getTextFromClipboard() throw() // compiled on its own). #ifdef JUCE_INCLUDED_FILE -static int64 highResTimerFrequency; +static int64 highResTimerFrequency = 0; +static double highResTimerToMillisecRatio = 0; #if JUCE_INTEL @@ -256252,11 +256291,13 @@ void SystemStats::initialiseStats() throw() { initialised = true; +#if JUCE_MAC // extremely annoying: adding this line stops the apple menu items from working. Of // course, not adding it means that carbon windows (e.g. in plugins) won't get // any events. //NSApplicationLoad(); [NSApplication sharedApplication]; +#endif #if JUCE_INTEL { @@ -256270,7 +256311,10 @@ void SystemStats::initialiseStats() throw() } #endif - highResTimerFrequency = (int64) AudioGetHostClockFrequency(); + mach_timebase_info_data_t timebase; + (void) mach_timebase_info (&timebase); + highResTimerFrequency = (int64) (1.0e9 * timebase.denom / timebase.numer); + highResTimerToMillisecRatio = timebase.numer / (1.0e6 * timebase.denom); String s (SystemStats::getJUCEVersion()); @@ -256380,27 +256424,19 @@ int SystemStats::getNumCpus() throw() #endif } -static int64 juce_getMicroseconds() throw() -{ - UnsignedWide t; - Microseconds (&t); - return (((int64) t.hi) << 32) | t.lo; -} - uint32 juce_millisecondsSinceStartup() throw() { - return (uint32) (juce_getMicroseconds() / 1000); + return (uint32) (mach_absolute_time() * highResTimerToMillisecRatio); } double Time::getMillisecondCounterHiRes() throw() { - // xxx might be more accurate to use a scaled AudioGetCurrentHostTime? - return juce_getMicroseconds() * 0.001; + return mach_absolute_time() * highResTimerToMillisecRatio; } int64 Time::getHighResolutionTicks() throw() { - return (int64) AudioGetCurrentHostTime(); + return (int64) mach_absolute_time(); } int64 Time::getHighResolutionTicksPerSecond() throw() @@ -256410,7 +256446,7 @@ int64 Time::getHighResolutionTicksPerSecond() throw() int64 SystemStats::getClockCycleCounter() throw() { - return (int64) AudioGetCurrentHostTime(); + return (int64) mach_absolute_time(); } bool Time::setSystemTimeToThisTime() const throw() @@ -256436,84 +256472,42 @@ void PlatformUtilities::fpuReset() // compiled on its own). #if JUCE_INCLUDED_FILE -static bool getEthernetIterator (io_iterator_t* matchingServices) throw() -{ - mach_port_t masterPort; - - if (IOMasterPort (MACH_PORT_NULL, &masterPort) == KERN_SUCCESS) - { - CFMutableDictionaryRef dict = IOServiceMatching (kIOEthernetInterfaceClass); - - if (dict != 0) - { - CFMutableDictionaryRef propDict = CFDictionaryCreateMutable (kCFAllocatorDefault, - 0, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - - if (propDict != 0) - { - CFDictionarySetValue (propDict, CFSTR (kIOPrimaryInterface), kCFBooleanTrue); - - CFDictionarySetValue (dict, CFSTR (kIOPropertyMatchKey), propDict); - CFRelease (propDict); - } - } - - return IOServiceGetMatchingServices (masterPort, dict, matchingServices) == KERN_SUCCESS; - } - - return false; -} - int SystemStats::getMACAddresses (int64* addresses, int maxNum, const bool littleEndian) throw() { + #ifndef IFT_ETHER + #define IFT_ETHER 6 + #endif + + ifaddrs* addrs = 0; int numResults = 0; - io_iterator_t it; - if (getEthernetIterator (&it)) + if (getifaddrs (&addrs) == 0) { - io_object_t i; + const ifaddrs* cursor = addrs; - while ((i = IOIteratorNext (it)) != 0) + while (cursor != 0 && numResults < maxNum) { - io_object_t controller; - - if (IORegistryEntryGetParentEntry (i, kIOServicePlane, &controller) == KERN_SUCCESS) + if (cursor->ifa_addr->sa_family == AF_LINK) { - CFTypeRef data = IORegistryEntryCreateCFProperty (controller, - CFSTR (kIOMACAddress), - kCFAllocatorDefault, - 0); - if (data != 0) + const sockaddr_dl* const sadd = (const sockaddr_dl*) cursor->ifa_addr; + + if (sadd->sdl_type == IFT_ETHER) { - UInt8 addr [kIOEthernetAddressSize]; - zeromem (addr, sizeof (addr)); + const uint8* const addr = ((const uint8*) sadd->sdl_data) + sadd->sdl_nlen; - CFDataGetBytes ((CFDataRef) data, CFRangeMake (0, sizeof (addr)), addr); - CFRelease (data); - - int64 a = 0; + uint64 a = 0; for (int i = 6; --i >= 0;) - a = (a << 8) | addr[i]; + a = (a << 8) | addr [littleEndian ? i : (5 - i)]; - if (! littleEndian) - a = (int64) swapByteOrder ((uint64) a); - - if (numResults < maxNum) - { - *addresses++ = a; - ++numResults; - } + *addresses++ = (int64) a; + ++numResults; } - - IOObjectRelease (controller); } - IOObjectRelease (i); + cursor = cursor->ifa_next; } - IOObjectRelease (it); + freeifaddrs (addrs); } return numResults; @@ -256524,6 +256518,11 @@ bool PlatformUtilities::launchEmailWithAttachments (const String& targetEmailAdd const String& bodyText, const StringArray& filesToAttach) { +#if JUCE_IPHONE + //xxx probably need to use MFMailComposeViewController + jassertfalse + return false; +#else const ScopedAutoReleasePool pool; String script; @@ -256559,6 +256558,7 @@ bool PlatformUtilities::launchEmailWithAttachments (const String& targetEmailAdd [s release]; return ok; +#endif } END_JUCE_NAMESPACE @@ -257124,7 +257124,11 @@ void Thread::setCurrentThreadAffinityMask (const uint32 affinityMask) throw() bool Process::isForegroundProcess() throw() { +#if JUCE_MAC return [NSApp isActive]; +#else + return true; // xxx change this if more than one app is ever possible on the iPhone! +#endif } void Process::raisePrivilege() @@ -257634,28 +257638,6 @@ void InterProcessLock::exit() throw() live in juce_posix_SharedCode.h! */ -const unsigned int macTimeToUnixTimeDiff = 0x7c25be90; - -static uint64 utcDateTimeToUnixTime (const UTCDateTime& d) throw() -{ - if (d.highSeconds == 0 && d.lowSeconds == 0 && d.fraction == 0) - return 0; - - return (((((uint64) d.highSeconds) << 32) | (uint64) d.lowSeconds) * 1000) - + ((d.fraction * 1000) >> 16) - - 2082844800000ll; -} - -static void unixTimeToUtcDateTime (uint64 t, UTCDateTime& d) throw() -{ - if (t != 0) - t += 2082844800000ll; - - d.highSeconds = (t / 1000) >> 32; - d.lowSeconds = (t / 1000) & (uint64) 0xffffffff; - d.fraction = ((t % 1000) << 16) / 1000; -} - void juce_getFileTimes (const String& fileName, int64& modificationTime, int64& accessTime, @@ -257665,24 +257647,13 @@ void juce_getFileTimes (const String& fileName, accessTime = 0; creationTime = 0; - FSRef fileRef; - if (PlatformUtilities::makeFSRefFromPath (&fileRef, fileName)) + struct stat info; + const int res = stat (fileName.toUTF8(), &info); + if (res == 0) { - FSRefParam info; - zerostruct (info); - - info.ref = &fileRef; - info.whichInfo = kFSCatInfoAllDates; - - FSCatalogInfo catInfo; - info.catInfo = &catInfo; - - if (PBGetCatalogInfoSync (&info) == noErr) - { - creationTime = utcDateTimeToUnixTime (catInfo.createDate); - accessTime = utcDateTimeToUnixTime (catInfo.accessDate); - modificationTime = utcDateTimeToUnixTime (catInfo.contentModDate); - } + modificationTime = (int64) info.st_mtime * 1000; + accessTime = (int64) info.st_atime * 1000; + creationTime = (int64) info.st_ctime * 1000; } } @@ -257691,59 +257662,29 @@ bool juce_setFileTimes (const String& fileName, int64 accessTime, int64 creationTime) throw() { - FSRef fileRef; - if (PlatformUtilities::makeFSRefFromPath (&fileRef, fileName)) - { - FSRefParam info; - zerostruct (info); + struct utimbuf times; + times.actime = (time_t) (accessTime / 1000); + times.modtime = (time_t) (modificationTime / 1000); - info.ref = &fileRef; - info.whichInfo = kFSCatInfoAllDates; - - FSCatalogInfo catInfo; - info.catInfo = &catInfo; - - if (PBGetCatalogInfoSync (&info) == noErr) - { - if (creationTime != 0) - unixTimeToUtcDateTime (creationTime, catInfo.createDate); - - if (modificationTime != 0) - unixTimeToUtcDateTime (modificationTime, catInfo.contentModDate); - - if (accessTime != 0) - unixTimeToUtcDateTime (accessTime, catInfo.accessDate); - - return PBSetCatalogInfoSync (&info) == noErr; - } - } - - return false; + return utime (fileName.toUTF8(), ×) == 0; } bool juce_setFileReadOnly (const String& fileName, bool isReadOnly) throw() { - const char* const fileNameUTF8 = fileName.toUTF8(); - struct stat info; - const int res = stat (fileNameUTF8, &info); + const int res = stat (fileName.toUTF8(), &info); + if (res != 0) + return false; - bool ok = false; + info.st_mode &= 0777; // Just permissions - if (res == 0) - { - info.st_mode &= 0777; // Just permissions + if (isReadOnly) + info.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); + else + // Give everybody write permission? + info.st_mode |= S_IWUSR | S_IWGRP | S_IWOTH; - if (isReadOnly) - info.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); - else - // Give everybody write permission? - info.st_mode |= S_IWUSR | S_IWGRP | S_IWOTH; - - ok = chmod (fileNameUTF8, info.st_mode) == 0; - } - - return ok; + return chmod (fileName.toUTF8(), info.st_mode) == 0; } bool juce_copyFile (const String& src, const String& dst) throw() @@ -257802,22 +257743,29 @@ bool File::isOnHardDisk() const throw() bool File::isOnRemovableDrive() const throw() { - const ScopedAutoReleasePool pool; - BOOL removable = false; +#if JUCE_IPHONE + return false; // xxx is this possible? +#else + const ScopedAutoReleasePool pool; + BOOL removable = false; - [[NSWorkspace sharedWorkspace] - getFileSystemInfoForPath: juceStringToNS (getFullPathName()) - isRemovable: &removable - isWritable: nil - isUnmountable: nil - description: nil - type: nil]; + [[NSWorkspace sharedWorkspace] + getFileSystemInfoForPath: juceStringToNS (getFullPathName()) + isRemovable: &removable + isWritable: nil + isUnmountable: nil + description: nil + type: nil]; return removable; +#endif } static bool juce_isHiddenFile (const String& path) throw() { +#if JUCE_IPHONE + return File (path).getFileName().startsWithChar (T('.')); +#else FSRef ref; if (! PlatformUtilities::makeFSRefFromPath (&ref, path)) return false; @@ -257829,6 +257777,7 @@ static bool juce_isHiddenFile (const String& path) throw() return (((FolderInfo*) &info.finderInfo)->finderFlags & kIsInvisible) != 0; return (((FileInfo*) &info.finderInfo)->finderFlags & kIsInvisible) != 0; +#endif } bool File::isHidden() const throw() @@ -257946,15 +257895,10 @@ const String File::getVersion() const throw() const File File::getLinkedTarget() const throw() { - FSRef ref; - Boolean targetIsAFolder, wasAliased; + NSString* dest = [[NSFileManager defaultManager] destinationOfSymbolicLinkAtPath: juceStringToNS (getFullPathName()) error: nil]; - if (PlatformUtilities::makeFSRefFromPath (&ref, getFullPathName()) - && (FSResolveAliasFileWithMountFlags (&ref, true, &targetIsAFolder, &wasAliased, 0) == noErr) - && wasAliased) - { - return File (PlatformUtilities::makePathFromFSRef (&ref)); - } + if (dest != nil) + return File (nsStringToJuce (dest)); return *this; } @@ -257964,6 +257908,9 @@ bool File::moveToTrash() const throw() if (! exists()) return true; +#if JUCE_IPHONE + return deleteFile(); //xxx is there a trashcan on the iPhone? +#else const ScopedAutoReleasePool pool; NSString* p = juceStringToNS (getFullPathName()); @@ -257974,99 +257921,35 @@ bool File::moveToTrash() const throw() destination: @"" files: [NSArray arrayWithObject: [p lastPathComponent]] tag: nil ]; +#endif } struct FindFileStruct { - String parentDir, wildCard; - DIR* dir; - - bool getNextMatch (String& result, bool* const isDir, bool* const isHidden, int64* const fileSize, - Time* const modTime, Time* const creationTime, bool* const isReadOnly) throw() - { - const char* const wildCardUTF8 = wildCard.toUTF8(); - - for (;;) - { - struct dirent* const de = readdir (dir); - - if (de == 0) - break; - - if (fnmatch (wildCardUTF8, de->d_name, 0) == 0) - { - result = String::fromUTF8 ((const uint8*) de->d_name); - - const String path (parentDir + result); - - if (isDir != 0 || fileSize != 0) - { - struct stat info; - const bool statOk = juce_stat (path, info); - - if (isDir != 0) - *isDir = path.isEmpty() || (statOk && ((info.st_mode & S_IFDIR) != 0)); - - if (isHidden != 0) - *isHidden = (de->d_name[0] == '.') - || juce_isHiddenFile (path); - - if (fileSize != 0) - *fileSize = statOk ? info.st_size : 0; - } - - if (modTime != 0 || creationTime != 0) - { - int64 m, a, c; - juce_getFileTimes (path, m, a, c); - - if (modTime != 0) - *modTime = m; - - if (creationTime != 0) - *creationTime = c; - } - - if (isReadOnly != 0) - *isReadOnly = ! juce_canWriteToFile (path); - - return true; - } - } - - return false; - } + NSDirectoryEnumerator* enumerator; + String parentDir; }; -// returns 0 on failure void* juce_findFileStart (const String& directory, const String& wildCard, String& firstResultFile, bool* isDir, bool* isHidden, int64* fileSize, Time* modTime, Time* creationTime, bool* isReadOnly) throw() { - DIR* const d = opendir (directory.toUTF8()); + NSDirectoryEnumerator* e = [[NSFileManager defaultManager] enumeratorAtPath: juceStringToNS (directory)]; - if (d != 0) + if (e != 0) { - FindFileStruct* const ff = new FindFileStruct(); + FindFileStruct* ff = new FindFileStruct(); + ff->enumerator = [e retain]; ff->parentDir = directory; - if (!ff->parentDir.endsWithChar (File::separator)) + if (! ff->parentDir.endsWithChar (File::separator)) ff->parentDir += File::separator; - ff->wildCard = wildCard; - ff->dir = d; - - if (ff->getNextMatch (firstResultFile, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly)) - { + if (juce_findFileNext (ff, firstResultFile, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly)) return ff; - } - else - { - firstResultFile = String::empty; - isDir = false; - closedir (d); - delete ff; - } + + [e release]; + delete ff; } return 0; @@ -258075,23 +257958,55 @@ void* juce_findFileStart (const String& directory, const String& wildCard, Strin bool juce_findFileNext (void* handle, String& resultFile, bool* isDir, bool* isHidden, int64* fileSize, Time* modTime, Time* creationTime, bool* isReadOnly) throw() { - FindFileStruct* const ff = (FindFileStruct*) handle; + FindFileStruct* ff = (FindFileStruct*) handle; + NSString* file; - if (ff != 0) - return ff->getNextMatch (resultFile, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly); + if (ff == 0 || (file = [ff->enumerator nextObject]) == 0) + return false; - return false; + [ff->enumerator skipDescendents]; + resultFile = nsStringToJuce (file); + + const String path (ff->parentDir + resultFile); + + if (isDir != 0 || fileSize != 0) + { + struct stat info; + const bool statOk = juce_stat (path, info); + + if (isDir != 0) + *isDir = statOk && ((info.st_mode & S_IFDIR) != 0); + + if (isHidden != 0) + *isHidden = juce_isHiddenFile (path); + + if (fileSize != 0) + *fileSize = statOk ? info.st_size : 0; + } + + if (modTime != 0 || creationTime != 0) + { + int64 m, a, c; + juce_getFileTimes (path, m, a, c); + + if (modTime != 0) + *modTime = m; + + if (creationTime != 0) + *creationTime = c; + } + + if (isReadOnly != 0) + *isReadOnly = ! juce_canWriteToFile (path); + + return true; } void juce_findFileClose (void* handle) throw() { - FindFileStruct* const ff = (FindFileStruct*)handle; - - if (ff != 0) - { - closedir (ff->dir); - delete ff; - } + FindFileStruct* ff = (FindFileStruct*) handle; + [ff->enumerator release]; + delete ff; } bool juce_launchExecutable (const String& pathAndArguments) throw() @@ -258118,6 +258033,9 @@ bool juce_launchExecutable (const String& pathAndArguments) throw() bool juce_launchFile (const String& fileName, const String& parameters) throw() { +#if JUCE_IPHONE + return false; // is this possible? +#else const ScopedAutoReleasePool pool; if (parameters.isEmpty()) @@ -258153,8 +258071,10 @@ bool juce_launchFile (const String& fileName, } return ok; +#endif } +#if ! JUCE_IPHONE bool PlatformUtilities::makeFSRefFromPath (FSRef* destFSRef, const String& path) { return FSPathMakeRef ((const UInt8*) path.toUTF8(), destFSRef, 0) == noErr; @@ -258172,17 +258092,23 @@ const String PlatformUtilities::makePathFromFSRef (FSRef* file) return PlatformUtilities::convertToPrecomposedUnicode (result); } +#endif OSType PlatformUtilities::getTypeOfFile (const String& filename) { const ScopedAutoReleasePool pool; - return NSHFSTypeCodeFromFileType (NSHFSTypeOfFile (juceStringToNS (filename))); + NSDictionary* fileDict = [[NSFileManager defaultManager] fileAttributesAtPath: juceStringToNS (filename) traverseLink: NO]; + return (OSType) [fileDict objectForKey: NSFileHFSTypeCode]; } bool PlatformUtilities::isBundle (const String& filename) { +#if JUCE_IPHONE + return false; // xxx can't find a sensible way to do this without trying to open the bundle.. +#else const ScopedAutoReleasePool pool; return [[NSWorkspace sharedWorkspace] isFilePackageAtPath: juceStringToNS (filename)]; +#endif } #endif @@ -265403,3 +265329,7648 @@ END_JUCE_NAMESPACE /********* End of inlined file: juce_mac_NativeCode.mm *********/ #endif + +#if JUCE_IPHONE + +/********* Start of inlined file: juce_iphone_NativeCode.mm *********/ +/* + This file wraps together all the mac-specific code, so that + we can include all the native headers just once, and compile all our + platform-specific stuff in one big lump, keeping it out of the way of + the rest of the codebase. +*/ + +BEGIN_JUCE_NAMESPACE + +#undef Point + +#define JUCE_INCLUDED_FILE 1 + +// Now include the actual code files.. + +/********* Start of inlined file: juce_mac_ObjCSuffix.h *********/ +/** This suffix is used for naming all Obj-C classes that are used inside juce. + + Because of the flat naming structure used by Obj-C, you can get horrible situations where + two DLLs are loaded into a host, each of which uses classes with the same names, and these get + cross-linked so that when you make a call to a class that you thought was private, it ends up + actually calling into a similarly named class in the other module's address space. + + By changing this macro to a unique value, you ensure that all the obj-C classes in your app + have unique names, and should avoid this problem. + + If you're using the amalgamated version, you can just set this macro to something unique before + you include juce_amalgamated.cpp. +*/ +#ifndef JUCE_ObjCExtraSuffix + #define JUCE_ObjCExtraSuffix 3 +#endif + +#define appendMacro1(a, b, c, d) a ## _ ## b ## _ ## c ## _ ## d +#define appendMacro2(a, b, c, d) appendMacro1(a, b, c, d) +#define MakeObjCClassName(rootName) appendMacro2 (rootName, JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_ObjCExtraSuffix) +/********* End of inlined file: juce_mac_ObjCSuffix.h *********/ + +/********* Start of inlined file: juce_mac_Strings.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +static const String nsStringToJuce (NSString* s) +{ + return String::fromUTF8 ((uint8*) [s UTF8String]); +} + +static NSString* juceStringToNS (const String& s) +{ + return [NSString stringWithUTF8String: (const char*) s.toUTF8()]; +} + +static const String convertUTF16ToString (const UniChar* utf16) +{ + String s; + + while (*utf16 != 0) + s += (juce_wchar) *utf16++; + + return s; +} + +const String PlatformUtilities::cfStringToJuceString (CFStringRef cfString) +{ + String result; + + if (cfString != 0) + { +#if JUCE_STRINGS_ARE_UNICODE + CFRange range = { 0, CFStringGetLength (cfString) }; + UniChar* const u = (UniChar*) juce_malloc (sizeof (UniChar) * (range.length + 1)); + + CFStringGetCharacters (cfString, range, u); + u[range.length] = 0; + + result = convertUTF16ToString (u); + + juce_free (u); +#else + const int len = CFStringGetLength (cfString); + char* buffer = (char*) juce_malloc (len + 1); + CFStringGetCString (cfString, buffer, len + 1, CFStringGetSystemEncoding()); + result = buffer; + juce_free (buffer); +#endif + } + + return result; +} + +CFStringRef PlatformUtilities::juceStringToCFString (const String& s) +{ +#if JUCE_STRINGS_ARE_UNICODE + const int len = s.length(); + const juce_wchar* t = (const juce_wchar*) s; + + UniChar* temp = (UniChar*) juce_malloc (sizeof (UniChar) * len + 4); + + for (int i = 0; i <= len; ++i) + temp[i] = t[i]; + + CFStringRef result = CFStringCreateWithCharacters (kCFAllocatorDefault, temp, len); + juce_free (temp); + + return result; + +#else + return CFStringCreateWithCString (kCFAllocatorDefault, + (const char*) s, + CFStringGetSystemEncoding()); +#endif +} + +const String PlatformUtilities::convertToPrecomposedUnicode (const String& s) +{ +#if JUCE_IPHONE + const ScopedAutoReleasePool pool; + return nsStringToJuce ([juceStringToNS (s) precomposedStringWithCanonicalMapping]); +#else + UnicodeMapping map; + + map.unicodeEncoding = CreateTextEncoding (kTextEncodingUnicodeDefault, + kUnicodeNoSubset, + kTextEncodingDefaultFormat); + + map.otherEncoding = CreateTextEncoding (kTextEncodingUnicodeDefault, + kUnicodeCanonicalCompVariant, + kTextEncodingDefaultFormat); + + map.mappingVersion = kUnicodeUseLatestMapping; + + UnicodeToTextInfo conversionInfo = 0; + String result; + + if (CreateUnicodeToTextInfo (&map, &conversionInfo) == noErr) + { + const int len = s.length(); + + UniChar* const tempIn = (UniChar*) juce_calloc (sizeof (UniChar) * len + 4); + UniChar* const tempOut = (UniChar*) juce_calloc (sizeof (UniChar) * len + 4); + + for (int i = 0; i <= len; ++i) + tempIn[i] = s[i]; + + ByteCount bytesRead = 0; + ByteCount outputBufferSize = 0; + + if (ConvertFromUnicodeToText (conversionInfo, + len * sizeof (UniChar), tempIn, + kUnicodeDefaultDirectionMask, + 0, 0, 0, 0, + len * sizeof (UniChar), &bytesRead, + &outputBufferSize, tempOut) == noErr) + { + result.preallocateStorage (bytesRead / sizeof (UniChar) + 2); + + tchar* t = const_cast ((const tchar*) result); + + unsigned int i; + for (i = 0; i < bytesRead / sizeof (UniChar); ++i) + t[i] = (tchar) tempOut[i]; + + t[i] = 0; + } + + juce_free (tempIn); + juce_free (tempOut); + + DisposeUnicodeToTextInfo (&conversionInfo); + } + + return result; +#endif +} + +#if ! JUCE_ONLY_BUILD_CORE_LIBRARY + +void SystemClipboard::copyTextToClipboard (const String& text) throw() +{ +#if JUCE_IPHONE + [[UIPasteboard generalPasteboard] setValue: juceStringToNS (text) + forPasteboardType: (NSString*) kUTTypePlainText]; +#else + [[NSPasteboard generalPasteboard] declareTypes: [NSArray arrayWithObject: NSStringPboardType] + owner: nil]; + + [[NSPasteboard generalPasteboard] setString: juceStringToNS (text) + forType: NSStringPboardType]; +#endif +} + +const String SystemClipboard::getTextFromClipboard() throw() +{ +#if JUCE_IPHONE + NSString* text = [[UIPasteboard generalPasteboard] valueForPasteboardType: (NSString*) kUTTypePlainText]; +#else + NSString* text = [[NSPasteboard generalPasteboard] stringForType: NSStringPboardType]; +#endif + + return text == 0 ? String::empty + : nsStringToJuce (text); +} + +#endif + +#endif +/********* End of inlined file: juce_mac_Strings.mm *********/ + +/********* Start of inlined file: juce_mac_SystemStats.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +static int64 highResTimerFrequency = 0; +static double highResTimerToMillisecRatio = 0; + +#if JUCE_INTEL + +static void juce_getCpuVendor (char* const v) throw() +{ + int vendor[4]; + zerostruct (vendor); + int dummy = 0; + + asm ("mov %%ebx, %%esi \n\t" + "cpuid \n\t" + "xchg %%esi, %%ebx" + : "=a" (dummy), "=S" (vendor[0]), "=c" (vendor[2]), "=d" (vendor[1]) : "a" (0)); + + memcpy (v, vendor, 16); +} + +static unsigned int getCPUIDWord (unsigned int& familyModel, unsigned int& extFeatures) throw() +{ + unsigned int cpu = 0; + unsigned int ext = 0; + unsigned int family = 0; + unsigned int dummy = 0; + + asm ("mov %%ebx, %%esi \n\t" + "cpuid \n\t" + "xchg %%esi, %%ebx" + : "=a" (family), "=S" (ext), "=c" (dummy), "=d" (cpu) : "a" (1)); + + familyModel = family; + extFeatures = ext; + return cpu; +} + +struct CPUFlags +{ + bool hasMMX : 1; + bool hasSSE : 1; + bool hasSSE2 : 1; + bool has3DNow : 1; +}; + +static CPUFlags cpuFlags; + +#endif + +void SystemStats::initialiseStats() throw() +{ + static bool initialised = false; + + if (! initialised) + { + initialised = true; + +#if JUCE_MAC + // extremely annoying: adding this line stops the apple menu items from working. Of + // course, not adding it means that carbon windows (e.g. in plugins) won't get + // any events. + //NSApplicationLoad(); + [NSApplication sharedApplication]; +#endif + +#if JUCE_INTEL + { + unsigned int familyModel, extFeatures; + const unsigned int features = getCPUIDWord (familyModel, extFeatures); + + cpuFlags.hasMMX = ((features & (1 << 23)) != 0); + cpuFlags.hasSSE = ((features & (1 << 25)) != 0); + cpuFlags.hasSSE2 = ((features & (1 << 26)) != 0); + cpuFlags.has3DNow = ((extFeatures & (1 << 31)) != 0); + } +#endif + + mach_timebase_info_data_t timebase; + (void) mach_timebase_info (&timebase); + highResTimerFrequency = (int64) (1.0e9 * timebase.denom / timebase.numer); + highResTimerToMillisecRatio = timebase.numer / (1.0e6 * timebase.denom); + + String s (SystemStats::getJUCEVersion()); + + rlimit lim; + getrlimit (RLIMIT_NOFILE, &lim); + lim.rlim_cur = lim.rlim_max = RLIM_INFINITY; + setrlimit (RLIMIT_NOFILE, &lim); + } +} + +SystemStats::OperatingSystemType SystemStats::getOperatingSystemType() throw() +{ + return MacOSX; +} + +const String SystemStats::getOperatingSystemName() throw() +{ + return T("Mac OS X"); +} + +bool SystemStats::isOperatingSystem64Bit() throw() +{ +#if JUCE_64BIT + return true; +#else + //xxx not sure how to find this out?.. + return false; +#endif +} + +int SystemStats::getMemorySizeInMegabytes() throw() +{ + uint64 mem = 0; + size_t memSize = sizeof (mem); + int mib[] = { CTL_HW, HW_MEMSIZE }; + sysctl (mib, 2, &mem, &memSize, 0, 0); + return mem / (1024 * 1024); +} + +bool SystemStats::hasMMX() throw() +{ +#if JUCE_INTEL + return cpuFlags.hasMMX; +#else + return false; +#endif +} + +bool SystemStats::hasSSE() throw() +{ +#if JUCE_INTEL + return cpuFlags.hasSSE; +#else + return false; +#endif +} + +bool SystemStats::hasSSE2() throw() +{ +#if JUCE_INTEL + return cpuFlags.hasSSE2; +#else + return false; +#endif +} + +bool SystemStats::has3DNow() throw() +{ +#if JUCE_INTEL + return cpuFlags.has3DNow; +#else + return false; +#endif +} + +const String SystemStats::getCpuVendor() throw() +{ +#if JUCE_INTEL + char v [16]; + juce_getCpuVendor (v); + return String (v, 16); +#else + return String::empty; +#endif +} + +int SystemStats::getCpuSpeedInMegaherz() throw() +{ + uint64 speedHz = 0; + size_t speedSize = sizeof (speedHz); + int mib[] = { CTL_HW, HW_CPU_FREQ }; + sysctl (mib, 2, &speedHz, &speedSize, 0, 0); + +#if JUCE_BIG_ENDIAN + if (speedSize == 4) + speedHz >>= 32; +#endif + return speedHz / 1000000; +} + +int SystemStats::getNumCpus() throw() +{ +#if MACOS_10_4_OR_EARLIER + return MPProcessors(); +#else + return [[NSProcessInfo processInfo] activeProcessorCount]; +#endif +} + +uint32 juce_millisecondsSinceStartup() throw() +{ + return (uint32) (mach_absolute_time() * highResTimerToMillisecRatio); +} + +double Time::getMillisecondCounterHiRes() throw() +{ + return mach_absolute_time() * highResTimerToMillisecRatio; +} + +int64 Time::getHighResolutionTicks() throw() +{ + return (int64) mach_absolute_time(); +} + +int64 Time::getHighResolutionTicksPerSecond() throw() +{ + return highResTimerFrequency; +} + +int64 SystemStats::getClockCycleCounter() throw() +{ + return (int64) mach_absolute_time(); +} + +bool Time::setSystemTimeToThisTime() const throw() +{ + jassertfalse + return false; +} + +int SystemStats::getPageSize() throw() +{ + return NSPageSize(); +} + +void PlatformUtilities::fpuReset() +{ +} + +#endif +/********* End of inlined file: juce_mac_SystemStats.mm *********/ + +/********* Start of inlined file: juce_mac_Network.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#if JUCE_INCLUDED_FILE + +int SystemStats::getMACAddresses (int64* addresses, int maxNum, const bool littleEndian) throw() +{ + #ifndef IFT_ETHER + #define IFT_ETHER 6 + #endif + + ifaddrs* addrs = 0; + int numResults = 0; + + if (getifaddrs (&addrs) == 0) + { + const ifaddrs* cursor = addrs; + + while (cursor != 0 && numResults < maxNum) + { + if (cursor->ifa_addr->sa_family == AF_LINK) + { + const sockaddr_dl* const sadd = (const sockaddr_dl*) cursor->ifa_addr; + + if (sadd->sdl_type == IFT_ETHER) + { + const uint8* const addr = ((const uint8*) sadd->sdl_data) + sadd->sdl_nlen; + + uint64 a = 0; + for (int i = 6; --i >= 0;) + a = (a << 8) | addr [littleEndian ? i : (5 - i)]; + + *addresses++ = (int64) a; + ++numResults; + } + } + + cursor = cursor->ifa_next; + } + + freeifaddrs (addrs); + } + + return numResults; +} + +bool PlatformUtilities::launchEmailWithAttachments (const String& targetEmailAddress, + const String& emailSubject, + const String& bodyText, + const StringArray& filesToAttach) +{ +#if JUCE_IPHONE + //xxx probably need to use MFMailComposeViewController + jassertfalse + return false; +#else + const ScopedAutoReleasePool pool; + + String script; + script << "tell application \"Mail\"\r\n" + "set newMessage to make new outgoing message with properties {subject:\"" + << emailSubject.replace (T("\""), T("\\\"")) + << "\", content:\"" + << bodyText.replace (T("\""), T("\\\"")) + << "\" & return & return}\r\n" + "tell newMessage\r\n" + "set visible to true\r\n" + "set sender to \"sdfsdfsdfewf\"\r\n" + "make new to recipient at end of to recipients with properties {address:\"" + << targetEmailAddress + << "\"}\r\n"; + + for (int i = 0; i < filesToAttach.size(); ++i) + { + script << "tell content\r\n" + "make new attachment with properties {file name:\"" + << filesToAttach[i].replace (T("\""), T("\\\"")) + << "\"} at after the last paragraph\r\n" + "end tell\r\n"; + } + + script << "end tell\r\n" + "end tell\r\n"; + + NSAppleScript* s = [[NSAppleScript alloc] + initWithSource: juceStringToNS (script)]; + NSDictionary* error = 0; + const bool ok = [s executeAndReturnError: &error] != nil; + [s release]; + + return ok; +#endif +} + +END_JUCE_NAMESPACE + +using namespace JUCE_NAMESPACE; + +#define JuceURLConnection MakeObjCClassName(JuceURLConnection) + +@interface JuceURLConnection : NSObject +{ +@public + NSURLRequest* request; + NSURLConnection* connection; + NSMutableData* data; + Thread* runLoopThread; + bool initialised, hasFailed, hasFinished; + int position; + int64 contentLength; + NSLock* dataLock; +} + +- (JuceURLConnection*) initWithRequest: (NSURLRequest*) req withCallback: (URL::OpenStreamProgressCallback*) callback withContext: (void*) context; +- (void) dealloc; +- (void) connection: (NSURLConnection*) connection didReceiveResponse: (NSURLResponse*) response; +- (void) connection: (NSURLConnection*) connection didFailWithError: (NSError*) error; +- (void) connection: (NSURLConnection*) connection didReceiveData: (NSData*) data; +- (void) connectionDidFinishLoading: (NSURLConnection*) connection; + +- (BOOL) isOpen; +- (int) read: (char*) dest numBytes: (int) num; +- (int) readPosition; +- (void) stop; +- (void) createConnection; + +@end + +class JuceURLConnectionMessageThread : public Thread +{ + JuceURLConnection* owner; + +public: + JuceURLConnectionMessageThread (JuceURLConnection* owner_) + : Thread (T("http connection")), + owner (owner_) + { + } + + ~JuceURLConnectionMessageThread() + { + stopThread (10000); + } + + void run() + { + [owner createConnection]; + + while (! threadShouldExit()) + { + const ScopedAutoReleasePool pool; + [[NSRunLoop currentRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; + } + } +}; + +@implementation JuceURLConnection + +- (JuceURLConnection*) initWithRequest: (NSURLRequest*) req + withCallback: (URL::OpenStreamProgressCallback*) callback + withContext: (void*) context; +{ + [super init]; + request = req; + [request retain]; + data = [[NSMutableData data] retain]; + dataLock = [[NSLock alloc] init]; + connection = 0; + initialised = false; + hasFailed = false; + hasFinished = false; + contentLength = -1; + + runLoopThread = new JuceURLConnectionMessageThread (self); + runLoopThread->startThread(); + + while (runLoopThread->isThreadRunning() && ! initialised) + { + if (callback != 0) + callback (context, -1, [[request HTTPBody] length]); + + Thread::sleep (1); + } + + return self; +} + +- (void) dealloc +{ + [self stop]; + + delete runLoopThread; + [connection release]; + [data release]; + [dataLock release]; + [request release]; + [super dealloc]; +} + +- (void) createConnection +{ + connection = [[NSURLConnection alloc] initWithRequest: request + delegate: [self retain]]; + + if (connection == nil) + runLoopThread->signalThreadShouldExit(); +} + +- (void) connection: (NSURLConnection*) connection didReceiveResponse: (NSURLResponse*) response +{ + [dataLock lock]; + [data setLength: 0]; + [dataLock unlock]; + initialised = true; + contentLength = [response expectedContentLength]; +} + +- (void) connection: (NSURLConnection*) connection didFailWithError: (NSError*) error +{ + DBG (nsStringToJuce ([error description])); + hasFailed = true; + initialised = true; + runLoopThread->signalThreadShouldExit(); +} + +- (void) connection: (NSURLConnection*) connection didReceiveData: (NSData*) newData +{ + [dataLock lock]; + [data appendData: newData]; + [dataLock unlock]; + initialised = true; +} + +- (void) connectionDidFinishLoading: (NSURLConnection*) connection +{ + hasFinished = true; + initialised = true; + runLoopThread->signalThreadShouldExit(); +} + +- (BOOL) isOpen +{ + return connection != 0 && ! hasFailed; +} + +- (int) readPosition +{ + return position; +} + +- (int) read: (char*) dest numBytes: (int) numNeeded +{ + int numDone = 0; + + while (numNeeded > 0) + { + int available = jmin (numNeeded, [data length]); + + if (available > 0) + { + [dataLock lock]; + [data getBytes: dest length: available]; + [data replaceBytesInRange: NSMakeRange (0, available) withBytes: nil length: 0]; + [dataLock unlock]; + + numDone += available; + numNeeded -= available; + dest += available; + } + else + { + if (hasFailed || hasFinished) + break; + + Thread::sleep (1); + } + } + + position += numDone; + return numDone; +} + +- (void) stop +{ + [connection cancel]; + runLoopThread->stopThread (10000); +} + +@end +BEGIN_JUCE_NAMESPACE + +bool juce_isOnLine() +{ + return true; +} + +void* juce_openInternetFile (const String& url, + const String& headers, + const MemoryBlock& postData, + const bool isPost, + URL::OpenStreamProgressCallback* callback, + void* callbackContext, + int timeOutMs) +{ + const ScopedAutoReleasePool pool; + + NSMutableURLRequest* req = [NSMutableURLRequest + requestWithURL: [NSURL URLWithString: juceStringToNS (url)] + cachePolicy: NSURLRequestUseProtocolCachePolicy + timeoutInterval: timeOutMs <= 0 ? 60.0 : (timeOutMs / 1000.0)]; + + if (req == nil) + return 0; + + [req setHTTPMethod: isPost ? @"POST" : @"GET"]; + //[req setCachePolicy: NSURLRequestReloadIgnoringLocalAndRemoteCacheData]; + + StringArray headerLines; + headerLines.addLines (headers); + headerLines.removeEmptyStrings (true); + + for (int i = 0; i < headerLines.size(); ++i) + { + const String key (headerLines[i].upToFirstOccurrenceOf (T(":"), false, false).trim()); + const String value (headerLines[i].fromFirstOccurrenceOf (T(":"), false, false).trim()); + + if (key.isNotEmpty() && value.isNotEmpty()) + [req addValue: juceStringToNS (value) forHTTPHeaderField: juceStringToNS (key)]; + } + + if (isPost && postData.getSize() > 0) + { + [req setHTTPBody: [NSData dataWithBytes: postData.getData() + length: postData.getSize()]]; + } + + JuceURLConnection* const s = [[JuceURLConnection alloc] initWithRequest: req + withCallback: callback + withContext: callbackContext]; + + if ([s isOpen]) + return s; + + [s release]; + return 0; +} + +void juce_closeInternetFile (void* handle) +{ + JuceURLConnection* const s = (JuceURLConnection*) handle; + + if (s != 0) + { + const ScopedAutoReleasePool pool; + [s stop]; + [s release]; + } +} + +int juce_readFromInternetFile (void* handle, void* buffer, int bytesToRead) +{ + JuceURLConnection* const s = (JuceURLConnection*) handle; + + if (s != 0) + { + const ScopedAutoReleasePool pool; + return [s read: (char*) buffer numBytes: bytesToRead]; + } + + return 0; +} + +int64 juce_getInternetFileContentLength (void* handle) +{ + JuceURLConnection* const s = (JuceURLConnection*) handle; + + if (s != 0) + return s->contentLength; + + return -1; +} + +int juce_seekInInternetFile (void* handle, int newPosition) +{ + JuceURLConnection* const s = (JuceURLConnection*) handle; + + if (s != 0) + return [s readPosition]; + + return 0; +} + +#endif +/********* End of inlined file: juce_mac_Network.mm *********/ + +/********* Start of inlined file: juce_posix_NamedPipe.cpp *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#if JUCE_INCLUDED_FILE + +struct NamedPipeInternal +{ + String pipeInName, pipeOutName; + int pipeIn, pipeOut; + + bool volatile createdPipe, blocked, stopReadOperation; + + static void signalHandler (int) {} +}; + +void NamedPipe::cancelPendingReads() +{ + while (internal != 0 && ((NamedPipeInternal*) internal)->blocked) + { + NamedPipeInternal* const intern = (NamedPipeInternal*) internal; + + intern->stopReadOperation = true; + + char buffer [1] = { 0 }; + int bytesWritten = ::write (intern->pipeIn, buffer, 1); + (void) bytesWritten; + + int timeout = 2000; + while (intern->blocked && --timeout >= 0) + Thread::sleep (2); + + intern->stopReadOperation = false; + } +} + +void NamedPipe::close() +{ + NamedPipeInternal* const intern = (NamedPipeInternal*) internal; + + if (intern != 0) + { + internal = 0; + + if (intern->pipeIn != -1) + ::close (intern->pipeIn); + + if (intern->pipeOut != -1) + ::close (intern->pipeOut); + + if (intern->createdPipe) + { + unlink (intern->pipeInName); + unlink (intern->pipeOutName); + } + + delete intern; + } +} + +bool NamedPipe::openInternal (const String& pipeName, const bool createPipe) +{ + close(); + + NamedPipeInternal* const intern = new NamedPipeInternal(); + internal = intern; + intern->createdPipe = createPipe; + intern->blocked = false; + intern->stopReadOperation = false; + + signal (SIGPIPE, NamedPipeInternal::signalHandler); + siginterrupt (SIGPIPE, 1); + + const String pipePath (T("/tmp/") + File::createLegalFileName (pipeName)); + + intern->pipeInName = pipePath + T("_in"); + intern->pipeOutName = pipePath + T("_out"); + intern->pipeIn = -1; + intern->pipeOut = -1; + + if (createPipe) + { + if ((mkfifo (intern->pipeInName, 0666) && errno != EEXIST) + || (mkfifo (intern->pipeOutName, 0666) && errno != EEXIST)) + { + delete intern; + internal = 0; + + return false; + } + } + + return true; +} + +int NamedPipe::read (void* destBuffer, int maxBytesToRead, int /*timeOutMilliseconds*/) +{ + int bytesRead = -1; + NamedPipeInternal* const intern = (NamedPipeInternal*) internal; + + if (intern != 0) + { + intern->blocked = true; + + if (intern->pipeIn == -1) + { + if (intern->createdPipe) + intern->pipeIn = ::open (intern->pipeInName, O_RDWR); + else + intern->pipeIn = ::open (intern->pipeOutName, O_RDWR); + + if (intern->pipeIn == -1) + { + intern->blocked = false; + return -1; + } + } + + bytesRead = 0; + + char* p = (char*) destBuffer; + + while (bytesRead < maxBytesToRead) + { + const int bytesThisTime = maxBytesToRead - bytesRead; + const int numRead = ::read (intern->pipeIn, p, bytesThisTime); + + if (numRead <= 0 || intern->stopReadOperation) + { + bytesRead = -1; + break; + } + + bytesRead += numRead; + p += bytesRead; + } + + intern->blocked = false; + } + + return bytesRead; +} + +int NamedPipe::write (const void* sourceBuffer, int numBytesToWrite, int timeOutMilliseconds) +{ + int bytesWritten = -1; + NamedPipeInternal* const intern = (NamedPipeInternal*) internal; + + if (intern != 0) + { + if (intern->pipeOut == -1) + { + if (intern->createdPipe) + intern->pipeOut = ::open (intern->pipeOutName, O_WRONLY); + else + intern->pipeOut = ::open (intern->pipeInName, O_WRONLY); + + if (intern->pipeOut == -1) + { + return -1; + } + } + + const char* p = (const char*) sourceBuffer; + bytesWritten = 0; + + const uint32 timeOutTime = Time::getMillisecondCounter() + timeOutMilliseconds; + + while (bytesWritten < numBytesToWrite + && (timeOutMilliseconds < 0 || Time::getMillisecondCounter() < timeOutTime)) + { + const int bytesThisTime = numBytesToWrite - bytesWritten; + const int numWritten = ::write (intern->pipeOut, p, bytesThisTime); + + if (numWritten <= 0) + { + bytesWritten = -1; + break; + } + + bytesWritten += numWritten; + p += bytesWritten; + } + } + + return bytesWritten; +} + +#endif +/********* End of inlined file: juce_posix_NamedPipe.cpp *********/ + +/********* Start of inlined file: juce_mac_Threads.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +/* + Note that a lot of methods that you'd expect to find in this file actually + live in juce_posix_SharedCode.h! +*/ + +void JUCE_API juce_threadEntryPoint (void*); + +void* threadEntryProc (void* userData) throw() +{ + const ScopedAutoReleasePool pool; + juce_threadEntryPoint (userData); + return 0; +} + +void* juce_createThread (void* userData) throw() +{ + pthread_t handle = 0; + + if (pthread_create (&handle, 0, threadEntryProc, userData) == 0) + { + pthread_detach (handle); + return (void*) handle; + } + + return 0; +} + +void juce_killThread (void* handle) throw() +{ + if (handle != 0) + pthread_cancel ((pthread_t) handle); +} + +void juce_setCurrentThreadName (const String& /*name*/) throw() +{ +} + +Thread::ThreadID Thread::getCurrentThreadId() throw() +{ + return (ThreadID) pthread_self(); +} + +bool juce_setThreadPriority (void* handle, int priority) throw() +{ + if (handle == 0) + handle = (void*) pthread_self(); + + struct sched_param param; + int policy; + pthread_getschedparam ((pthread_t) handle, &policy, ¶m); + param.sched_priority = jlimit (1, 127, 1 + (priority * 126) / 11); + return pthread_setschedparam ((pthread_t) handle, policy, ¶m) == 0; +} + +void Thread::yield() throw() +{ + sched_yield(); +} + +void Thread::setCurrentThreadAffinityMask (const uint32 affinityMask) throw() +{ + // xxx + jassertfalse +} + +bool Process::isForegroundProcess() throw() +{ +#if JUCE_MAC + return [NSApp isActive]; +#else + return true; // xxx change this if more than one app is ever possible on the iPhone! +#endif +} + +void Process::raisePrivilege() +{ + jassertfalse +} + +void Process::lowerPrivilege() +{ + jassertfalse +} + +void Process::terminate() +{ + exit (0); +} + +void Process::setPriority (ProcessPriority p) +{ + // xxx +} + +#endif +/********* End of inlined file: juce_mac_Threads.mm *********/ + +/********* Start of inlined file: juce_posix_SharedCode.h *********/ +/* + This file contains posix routines that are common to both the Linux and Mac builds. + + It gets included directly in the cpp files for these platforms. +*/ + +CriticalSection::CriticalSection() throw() +{ + pthread_mutexattr_t atts; + pthread_mutexattr_init (&atts); + pthread_mutexattr_settype (&atts, PTHREAD_MUTEX_RECURSIVE); + pthread_mutex_init (&internal, &atts); +} + +CriticalSection::~CriticalSection() throw() +{ + pthread_mutex_destroy (&internal); +} + +void CriticalSection::enter() const throw() +{ + pthread_mutex_lock (&internal); +} + +bool CriticalSection::tryEnter() const throw() +{ + return pthread_mutex_trylock (&internal) == 0; +} + +void CriticalSection::exit() const throw() +{ + pthread_mutex_unlock (&internal); +} + +struct EventStruct +{ + pthread_cond_t condition; + pthread_mutex_t mutex; + bool triggered; +}; + +WaitableEvent::WaitableEvent() throw() +{ + EventStruct* const es = new EventStruct(); + es->triggered = false; + + pthread_cond_init (&es->condition, 0); + pthread_mutex_init (&es->mutex, 0); + + internal = es; +} + +WaitableEvent::~WaitableEvent() throw() +{ + EventStruct* const es = (EventStruct*) internal; + + pthread_cond_destroy (&es->condition); + pthread_mutex_destroy (&es->mutex); + + delete es; +} + +bool WaitableEvent::wait (const int timeOutMillisecs) const throw() +{ + EventStruct* const es = (EventStruct*) internal; + + bool ok = true; + pthread_mutex_lock (&es->mutex); + + if (timeOutMillisecs < 0) + { + while (! es->triggered) + pthread_cond_wait (&es->condition, &es->mutex); + } + else + { + while (! es->triggered) + { + struct timeval t; + gettimeofday (&t, 0); + + struct timespec time; + time.tv_sec = t.tv_sec + (timeOutMillisecs / 1000); + time.tv_nsec = (t.tv_usec + ((timeOutMillisecs % 1000) * 1000)) * 1000; + + if (time.tv_nsec >= 1000000000) + { + time.tv_nsec -= 1000000000; + time.tv_sec++; + } + + if (pthread_cond_timedwait (&es->condition, &es->mutex, &time) == ETIMEDOUT) + { + ok = false; + break; + } + } + } + + es->triggered = false; + + pthread_mutex_unlock (&es->mutex); + return ok; +} + +void WaitableEvent::signal() const throw() +{ + EventStruct* const es = (EventStruct*) internal; + + pthread_mutex_lock (&es->mutex); + es->triggered = true; + pthread_cond_broadcast (&es->condition); + pthread_mutex_unlock (&es->mutex); +} + +void WaitableEvent::reset() const throw() +{ + EventStruct* const es = (EventStruct*) internal; + + pthread_mutex_lock (&es->mutex); + es->triggered = false; + pthread_mutex_unlock (&es->mutex); +} + +void JUCE_CALLTYPE Thread::sleep (int millisecs) throw() +{ + struct timespec time; + time.tv_sec = millisecs / 1000; + time.tv_nsec = (millisecs % 1000) * 1000000; + nanosleep (&time, 0); +} + +const tchar File::separator = T('/'); +const tchar* File::separatorString = T("/"); + +bool juce_copyFile (const String& s, const String& d) throw(); + +static bool juce_stat (const String& fileName, struct stat& info) throw() +{ + return fileName.isNotEmpty() + && (stat (fileName.toUTF8(), &info) == 0); +} + +bool juce_isDirectory (const String& fileName) throw() +{ + struct stat info; + + return fileName.isEmpty() + || (juce_stat (fileName, info) + && ((info.st_mode & S_IFDIR) != 0)); +} + +bool juce_fileExists (const String& fileName, const bool dontCountDirectories) throw() +{ + if (fileName.isEmpty()) + return false; + + const char* const fileNameUTF8 = fileName.toUTF8(); + bool exists = access (fileNameUTF8, F_OK) == 0; + + if (exists && dontCountDirectories) + { + struct stat info; + const int res = stat (fileNameUTF8, &info); + + if (res == 0 && (info.st_mode & S_IFDIR) != 0) + exists = false; + } + + return exists; +} + +int64 juce_getFileSize (const String& fileName) throw() +{ + struct stat info; + return juce_stat (fileName, info) ? info.st_size : 0; +} + +bool juce_canWriteToFile (const String& fileName) throw() +{ + return access (fileName.toUTF8(), W_OK) == 0; +} + +bool juce_deleteFile (const String& fileName) throw() +{ + if (juce_isDirectory (fileName)) + return rmdir ((const char*) fileName.toUTF8()) == 0; + else + return remove ((const char*) fileName.toUTF8()) == 0; +} + +bool juce_moveFile (const String& source, const String& dest) throw() +{ + if (rename (source.toUTF8(), dest.toUTF8()) == 0) + return true; + + if (juce_canWriteToFile (source) + && juce_copyFile (source, dest)) + { + if (juce_deleteFile (source)) + return true; + + juce_deleteFile (dest); + } + + return false; +} + +void juce_createDirectory (const String& fileName) throw() +{ + mkdir (fileName.toUTF8(), 0777); +} + +void* juce_fileOpen (const String& fileName, bool forWriting) throw() +{ + int flags = O_RDONLY; + + if (forWriting) + { + if (juce_fileExists (fileName, false)) + { + const int f = open ((const char*) fileName.toUTF8(), O_RDWR, 00644); + + if (f != -1) + lseek (f, 0, SEEK_END); + + return (void*) f; + } + else + { + flags = O_RDWR + O_CREAT; + } + } + + return (void*) open ((const char*) fileName.toUTF8(), flags, 00644); +} + +void juce_fileClose (void* handle) throw() +{ + if (handle != 0) + close ((int) (pointer_sized_int) handle); +} + +int juce_fileRead (void* handle, void* buffer, int size) throw() +{ + if (handle != 0) + return read ((int) (pointer_sized_int) handle, buffer, size); + + return 0; +} + +int juce_fileWrite (void* handle, const void* buffer, int size) throw() +{ + if (handle != 0) + return write ((int) (pointer_sized_int) handle, buffer, size); + + return 0; +} + +int64 juce_fileSetPosition (void* handle, int64 pos) throw() +{ + if (handle != 0 && lseek ((int) (pointer_sized_int) handle, pos, SEEK_SET) == pos) + return pos; + + return -1; +} + +int64 juce_fileGetPosition (void* handle) throw() +{ + if (handle != 0) + return lseek ((int) (pointer_sized_int) handle, 0, SEEK_CUR); + else + return -1; +} + +void juce_fileFlush (void* handle) throw() +{ + if (handle != 0) + fsync ((int) (pointer_sized_int) handle); +} + +const File juce_getExecutableFile() +{ + Dl_info exeInfo; + dladdr ((const void*) juce_getExecutableFile, &exeInfo); + return File (exeInfo.dli_fname); +} + +// if this file doesn't exist, find a parent of it that does.. +static bool doStatFS (const File* file, struct statfs& result) throw() +{ + File f (*file); + + for (int i = 5; --i >= 0;) + { + if (f.exists()) + break; + + f = f.getParentDirectory(); + } + + return statfs (f.getFullPathName().toUTF8(), &result) == 0; +} + +int64 File::getBytesFreeOnVolume() const throw() +{ + struct statfs buf; + if (doStatFS (this, buf)) + return (int64) buf.f_bsize * (int64) buf.f_bavail; // Note: this returns space available to non-super user + + return 0; +} + +int64 File::getVolumeTotalSize() const throw() +{ + struct statfs buf; + if (doStatFS (this, buf)) + return (int64) buf.f_bsize * (int64) buf.f_blocks; + + return 0; +} + +const String juce_getVolumeLabel (const String& filenameOnVolume, + int& volumeSerialNumber) throw() +{ + volumeSerialNumber = 0; + +#if JUCE_MAC + struct VolAttrBuf + { + u_int32_t length; + attrreference_t mountPointRef; + char mountPointSpace [MAXPATHLEN]; + } attrBuf; + + struct attrlist attrList; + zerostruct (attrList); + attrList.bitmapcount = ATTR_BIT_MAP_COUNT; + attrList.volattr = ATTR_VOL_INFO | ATTR_VOL_NAME; + + File f (filenameOnVolume); + + for (;;) + { + if (getattrlist ((const char*) f.getFullPathName().toUTF8(), + &attrList, &attrBuf, sizeof(attrBuf), 0) == 0) + { + return String::fromUTF8 (((const uint8*) &attrBuf.mountPointRef) + attrBuf.mountPointRef.attr_dataoffset, + (int) attrBuf.mountPointRef.attr_length); + } + + const File parent (f.getParentDirectory()); + + if (f == parent) + break; + + f = parent; + } +#endif + + return String::empty; +} + +void juce_runSystemCommand (const String& command) +{ + int result = system ((const char*) command.toUTF8()); + (void) result; +} + +const String juce_getOutputFromCommand (const String& command) +{ + // slight bodge here, as we just pipe the output into a temp file and read it... + const File tempFile (File::getSpecialLocation (File::tempDirectory) + .getNonexistentChildFile (String::toHexString (Random::getSystemRandom().nextInt()), ".tmp", false)); + + juce_runSystemCommand (command + " > " + tempFile.getFullPathName()); + + String result (tempFile.loadFileAsString()); + tempFile.deleteFile(); + return result; +} + +#if JUCE_64BIT + #define filedesc ((long long) internal) +#else + #define filedesc ((int) internal) +#endif + +InterProcessLock::InterProcessLock (const String& name_) throw() + : internal (0), + name (name_), + reentrancyLevel (0) +{ +#if JUCE_MAC + // (don't use getSpecialLocation() to avoid the temp folder being different for each app) + const File temp (File (T("~/Library/Caches/Juce")).getChildFile (name)); +#else + const File temp (File::getSpecialLocation (File::tempDirectory).getChildFile (name)); +#endif + + temp.create(); + + internal = (void*) open (temp.getFullPathName().toUTF8(), O_RDWR); +} + +InterProcessLock::~InterProcessLock() throw() +{ + while (reentrancyLevel > 0) + this->exit(); + + close (filedesc); +} + +bool InterProcessLock::enter (const int timeOutMillisecs) throw() +{ + if (internal == 0) + return false; + + if (reentrancyLevel != 0) + return true; + + const int64 endTime = Time::currentTimeMillis() + timeOutMillisecs; + + struct flock fl; + zerostruct (fl); + fl.l_whence = SEEK_SET; + fl.l_type = F_WRLCK; + + for (;;) + { + const int result = fcntl (filedesc, F_SETLK, &fl); + + if (result >= 0) + { + ++reentrancyLevel; + return true; + } + + if (errno != EINTR) + { + if (timeOutMillisecs == 0 + || (timeOutMillisecs > 0 && Time::currentTimeMillis() >= endTime)) + break; + + Thread::sleep (10); + } + } + + return false; +} + +void InterProcessLock::exit() throw() +{ + if (reentrancyLevel > 0 && internal != 0) + { + --reentrancyLevel; + + struct flock fl; + zerostruct (fl); + fl.l_whence = SEEK_SET; + fl.l_type = F_UNLCK; + + for (;;) + { + const int result = fcntl (filedesc, F_SETLKW, &fl); + + if (result >= 0 || errno != EINTR) + break; + } + } +} +/********* End of inlined file: juce_posix_SharedCode.h *********/ + +/********* Start of inlined file: juce_mac_Files.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +/* + Note that a lot of methods that you'd expect to find in this file actually + live in juce_posix_SharedCode.h! +*/ + +void juce_getFileTimes (const String& fileName, + int64& modificationTime, + int64& accessTime, + int64& creationTime) throw() +{ + modificationTime = 0; + accessTime = 0; + creationTime = 0; + + struct stat info; + const int res = stat (fileName.toUTF8(), &info); + if (res == 0) + { + modificationTime = (int64) info.st_mtime * 1000; + accessTime = (int64) info.st_atime * 1000; + creationTime = (int64) info.st_ctime * 1000; + } +} + +bool juce_setFileTimes (const String& fileName, + int64 modificationTime, + int64 accessTime, + int64 creationTime) throw() +{ + struct utimbuf times; + times.actime = (time_t) (accessTime / 1000); + times.modtime = (time_t) (modificationTime / 1000); + + return utime (fileName.toUTF8(), ×) == 0; +} + +bool juce_setFileReadOnly (const String& fileName, bool isReadOnly) throw() +{ + struct stat info; + const int res = stat (fileName.toUTF8(), &info); + if (res != 0) + return false; + + info.st_mode &= 0777; // Just permissions + + if (isReadOnly) + info.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); + else + // Give everybody write permission? + info.st_mode |= S_IWUSR | S_IWGRP | S_IWOTH; + + return chmod (fileName.toUTF8(), info.st_mode) == 0; +} + +bool juce_copyFile (const String& src, const String& dst) throw() +{ + const ScopedAutoReleasePool pool; + NSFileManager* fm = [NSFileManager defaultManager]; + + return [fm fileExistsAtPath: juceStringToNS (src)] +#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + && [fm copyItemAtPath: juceStringToNS (src) + toPath: juceStringToNS (dst) + error: nil]; +#else + && [fm copyPath: juceStringToNS (src) + toPath: juceStringToNS (dst) + handler: nil]; +#endif +} + +const StringArray juce_getFileSystemRoots() throw() +{ + StringArray s; + s.add (T("/")); + return s; +} + +static bool isFileOnDriveType (const File* const f, const char** types) throw() +{ + struct statfs buf; + + if (doStatFS (f, buf)) + { + const String type (buf.f_fstypename); + + while (*types != 0) + if (type.equalsIgnoreCase (*types++)) + return true; + } + + return false; +} + +bool File::isOnCDRomDrive() const throw() +{ + static const char* const cdTypes[] = { "cd9660", "cdfs", "cddafs", "udf", 0 }; + + return isFileOnDriveType (this, (const char**) cdTypes); +} + +bool File::isOnHardDisk() const throw() +{ + static const char* const nonHDTypes[] = { "nfs", "smbfs", "ramfs", 0 }; + + return ! (isOnCDRomDrive() || isFileOnDriveType (this, (const char**) nonHDTypes)); +} + +bool File::isOnRemovableDrive() const throw() +{ +#if JUCE_IPHONE + return false; // xxx is this possible? +#else + const ScopedAutoReleasePool pool; + BOOL removable = false; + + [[NSWorkspace sharedWorkspace] + getFileSystemInfoForPath: juceStringToNS (getFullPathName()) + isRemovable: &removable + isWritable: nil + isUnmountable: nil + description: nil + type: nil]; + + return removable; +#endif +} + +static bool juce_isHiddenFile (const String& path) throw() +{ +#if JUCE_IPHONE + return File (path).getFileName().startsWithChar (T('.')); +#else + FSRef ref; + if (! PlatformUtilities::makeFSRefFromPath (&ref, path)) + return false; + + FSCatalogInfo info; + FSGetCatalogInfo (&ref, kFSCatInfoNodeFlags | kFSCatInfoFinderInfo, &info, 0, 0, 0); + + if ((info.nodeFlags & kFSNodeIsDirectoryBit) != 0) + return (((FolderInfo*) &info.finderInfo)->finderFlags & kIsInvisible) != 0; + + return (((FileInfo*) &info.finderInfo)->finderFlags & kIsInvisible) != 0; +#endif +} + +bool File::isHidden() const throw() +{ + return juce_isHiddenFile (getFullPathName()); +} + +const File File::getSpecialLocation (const SpecialLocationType type) +{ + const ScopedAutoReleasePool pool; + + String resultPath; + + switch (type) + { + case userHomeDirectory: + resultPath = nsStringToJuce (NSHomeDirectory()); + break; + + case userDocumentsDirectory: + resultPath = "~/Documents"; + break; + + case userDesktopDirectory: + resultPath = "~/Desktop"; + break; + + case userApplicationDataDirectory: + resultPath = "~/Library"; + break; + + case commonApplicationDataDirectory: + resultPath = "/Library"; + break; + + case globalApplicationsDirectory: + resultPath = "/Applications"; + break; + + case userMusicDirectory: + resultPath = "~/Music"; + break; + + case userMoviesDirectory: + resultPath = "~/Movies"; + break; + + case tempDirectory: + { + File tmp (T("~/Library/Caches/") + juce_getExecutableFile().getFileNameWithoutExtension()); + + tmp.createDirectory(); + return tmp.getFullPathName(); + } + + case currentExecutableFile: + return juce_getExecutableFile(); + + case currentApplicationFile: + { + const File exe (juce_getExecutableFile()); + const File parent (exe.getParentDirectory()); + + return parent.getFullPathName().endsWithIgnoreCase (T("Contents/MacOS")) + ? parent.getParentDirectory().getParentDirectory() + : exe; + } + + default: + jassertfalse // unknown type? + break; + } + + if (resultPath != 0) + return File (PlatformUtilities::convertToPrecomposedUnicode (resultPath)); + + return File::nonexistent; +} + +const File File::getCurrentWorkingDirectory() throw() +{ + char buf [2048]; + getcwd (buf, sizeof(buf)); + + return File (PlatformUtilities::convertToPrecomposedUnicode (buf)); +} + +bool File::setAsCurrentWorkingDirectory() const throw() +{ + return chdir (getFullPathName().toUTF8()) == 0; +} + +const String File::getVersion() const throw() +{ + const ScopedAutoReleasePool pool; + String result; + + NSBundle* bundle = [NSBundle bundleWithPath: juceStringToNS (getFullPathName())]; + + if (bundle != 0) + { + NSDictionary* info = [bundle infoDictionary]; + + if (info != 0) + { + NSString* name = [info valueForKey: @"CFBundleShortVersionString"]; + + if (name != nil) + result = nsStringToJuce (name); + } + } + + return result; +} + +const File File::getLinkedTarget() const throw() +{ + NSString* dest = [[NSFileManager defaultManager] destinationOfSymbolicLinkAtPath: juceStringToNS (getFullPathName()) error: nil]; + + if (dest != nil) + return File (nsStringToJuce (dest)); + + return *this; +} + +bool File::moveToTrash() const throw() +{ + if (! exists()) + return true; + +#if JUCE_IPHONE + return deleteFile(); //xxx is there a trashcan on the iPhone? +#else + const ScopedAutoReleasePool pool; + + NSString* p = juceStringToNS (getFullPathName()); + + return [[NSWorkspace sharedWorkspace] + performFileOperation: NSWorkspaceRecycleOperation + source: [p stringByDeletingLastPathComponent] + destination: @"" + files: [NSArray arrayWithObject: [p lastPathComponent]] + tag: nil ]; +#endif +} + +struct FindFileStruct +{ + NSDirectoryEnumerator* enumerator; + String parentDir; +}; + +void* juce_findFileStart (const String& directory, const String& wildCard, String& firstResultFile, + bool* isDir, bool* isHidden, int64* fileSize, Time* modTime, + Time* creationTime, bool* isReadOnly) throw() +{ + NSDirectoryEnumerator* e = [[NSFileManager defaultManager] enumeratorAtPath: juceStringToNS (directory)]; + + if (e != 0) + { + FindFileStruct* ff = new FindFileStruct(); + ff->enumerator = [e retain]; + ff->parentDir = directory; + + if (! ff->parentDir.endsWithChar (File::separator)) + ff->parentDir += File::separator; + + if (juce_findFileNext (ff, firstResultFile, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly)) + return ff; + + [e release]; + delete ff; + } + + return 0; +} + +bool juce_findFileNext (void* handle, String& resultFile, + bool* isDir, bool* isHidden, int64* fileSize, Time* modTime, Time* creationTime, bool* isReadOnly) throw() +{ + FindFileStruct* ff = (FindFileStruct*) handle; + NSString* file; + + if (ff == 0 || (file = [ff->enumerator nextObject]) == 0) + return false; + + [ff->enumerator skipDescendents]; + resultFile = nsStringToJuce (file); + + const String path (ff->parentDir + resultFile); + + if (isDir != 0 || fileSize != 0) + { + struct stat info; + const bool statOk = juce_stat (path, info); + + if (isDir != 0) + *isDir = statOk && ((info.st_mode & S_IFDIR) != 0); + + if (isHidden != 0) + *isHidden = juce_isHiddenFile (path); + + if (fileSize != 0) + *fileSize = statOk ? info.st_size : 0; + } + + if (modTime != 0 || creationTime != 0) + { + int64 m, a, c; + juce_getFileTimes (path, m, a, c); + + if (modTime != 0) + *modTime = m; + + if (creationTime != 0) + *creationTime = c; + } + + if (isReadOnly != 0) + *isReadOnly = ! juce_canWriteToFile (path); + + return true; +} + +void juce_findFileClose (void* handle) throw() +{ + FindFileStruct* ff = (FindFileStruct*) handle; + [ff->enumerator release]; + delete ff; +} + +bool juce_launchExecutable (const String& pathAndArguments) throw() +{ + const char* const argv[4] = { "/bin/sh", "-c", (const char*) pathAndArguments, 0 }; + + const int cpid = fork(); + + if (cpid == 0) + { + // Child process + if (execve (argv[0], (char**) argv, 0) < 0) + exit (0); + } + else + { + if (cpid < 0) + return false; + } + + return true; +} + +bool juce_launchFile (const String& fileName, + const String& parameters) throw() +{ +#if JUCE_IPHONE + return false; // is this possible? +#else + const ScopedAutoReleasePool pool; + + if (parameters.isEmpty()) + { + return [[NSWorkspace sharedWorkspace] openFile: juceStringToNS (fileName)] + || [[NSWorkspace sharedWorkspace] openURL: [NSURL URLWithString: juceStringToNS (fileName)]]; + } + + bool ok = false; + + FSRef ref; + if (PlatformUtilities::makeFSRefFromPath (&ref, fileName)) + { + if (PlatformUtilities::isBundle (fileName)) + { + NSMutableArray* urls = [NSMutableArray array]; + + StringArray docs; + docs.addTokens (parameters, true); + for (int i = 0; i < docs.size(); ++i) + [urls addObject: juceStringToNS (docs[i])]; + + ok = [[NSWorkspace sharedWorkspace] openURLs: urls + withAppBundleIdentifier: [[NSBundle bundleWithPath: juceStringToNS (fileName)] bundleIdentifier] + options: nil + additionalEventParamDescriptor: nil + launchIdentifiers: nil]; + } + else + { + ok = juce_launchExecutable (T("\"") + fileName + T("\" ") + parameters); + } + } + + return ok; +#endif +} + +#if ! JUCE_IPHONE +bool PlatformUtilities::makeFSRefFromPath (FSRef* destFSRef, const String& path) +{ + return FSPathMakeRef ((const UInt8*) path.toUTF8(), destFSRef, 0) == noErr; +} + +const String PlatformUtilities::makePathFromFSRef (FSRef* file) +{ + uint8 path [2048]; + zeromem (path, sizeof (path)); + + String result; + + if (FSRefMakePath (file, (UInt8*) path, sizeof (path) - 1) == noErr) + result = String::fromUTF8 (path); + + return PlatformUtilities::convertToPrecomposedUnicode (result); +} +#endif + +OSType PlatformUtilities::getTypeOfFile (const String& filename) +{ + const ScopedAutoReleasePool pool; + NSDictionary* fileDict = [[NSFileManager defaultManager] fileAttributesAtPath: juceStringToNS (filename) traverseLink: NO]; + return (OSType) [fileDict objectForKey: NSFileHFSTypeCode]; +} + +bool PlatformUtilities::isBundle (const String& filename) +{ +#if JUCE_IPHONE + return false; // xxx can't find a sensible way to do this without trying to open the bundle.. +#else + const ScopedAutoReleasePool pool; + return [[NSWorkspace sharedWorkspace] isFilePackageAtPath: juceStringToNS (filename)]; +#endif +} + +#endif +/********* End of inlined file: juce_mac_Files.mm *********/ + +/********* Start of inlined file: juce_iphone_MiscUtilities.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +ScopedAutoReleasePool::ScopedAutoReleasePool() +{ + pool = [[NSAutoreleasePool alloc] init]; +} + +ScopedAutoReleasePool::~ScopedAutoReleasePool() +{ + [((NSAutoreleasePool*) pool) release]; +} + +void PlatformUtilities::beep() +{ + //xxx + //AudioServicesPlaySystemSound (); +} + +void PlatformUtilities::addItemToDock (const File& file) +{ +} + +#if ! JUCE_ONLY_BUILD_CORE_LIBRARY + +bool AlertWindow::showNativeDialogBox (const String& title, + const String& bodyText, + bool isOkCancel) +{ + const ScopedAutoReleasePool pool; + + UIAlertView *alert = [[[UIAlertView alloc] initWithTitle: juceStringToNS (title) + message: juceStringToNS (title) + delegate: nil + cancelButtonTitle: @"OK" + otherButtonTitles: (isOkCancel ? @"Cancel" : nil), nil] autorelease]; + alert.cancelButtonIndex = alert.firstOtherButtonIndex; + [alert show]; + + // xxx need to use a delegate to find which button was clicked + return false; +} + +bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, const bool canMoveFiles) +{ + jassertfalse // not implemented! + return false; +} + +bool DragAndDropContainer::performExternalDragDropOfText (const String& text) +{ + jassertfalse // not implemented! + return false; +} + +bool Desktop::canUseSemiTransparentWindows() throw() +{ + return true; +} + +void Desktop::getMousePosition (int& x, int& y) throw() +{ + x = 0; + y = 0; +} + +void Desktop::setMousePosition (int x, int y) throw() +{ +} + +void Desktop::setScreenSaverEnabled (const bool isEnabled) throw() +{ + [[UIApplication sharedApplication] setIdleTimerDisabled: ! isEnabled]; +} + +bool Desktop::isScreenSaverEnabled() throw() +{ + return ! [[UIApplication sharedApplication] isIdleTimerDisabled]; +} + +void juce_updateMultiMonitorInfo (Array & monitorCoords, const bool clipToWorkArea) throw() +{ + const ScopedAutoReleasePool pool; + monitorCoords.clear(); + + CGRect r = clipToWorkArea ? [[UIScreen mainScreen] applicationFrame] + : [[UIScreen mainScreen] bounds]; + + monitorCoords.add (Rectangle ((int) r.origin.x, + (int) r.origin.y, + (int) r.size.width, + (int) r.size.height)); +} + +#endif + +#endif +/********* End of inlined file: juce_iphone_MiscUtilities.mm *********/ + +/********* Start of inlined file: juce_mac_Debugging.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +void Logger::outputDebugString (const String& text) throw() +{ + fputs (text.toUTF8(), stderr); + fputs ("\n", stderr); +} + +void Logger::outputDebugPrintf (const tchar* format, ...) throw() +{ + String text; + va_list args; + va_start (args, format); + text.vprintf (format, args); + outputDebugString (text); +} + +bool JUCE_CALLTYPE juce_isRunningUnderDebugger() throw() +{ + static char testResult = 0; + + if (testResult == 0) + { + struct kinfo_proc info; + int m[] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, getpid() }; + size_t sz = sizeof (info); + sysctl (m, 4, &info, &sz, 0, 0); + testResult = ((info.kp_proc.p_flag & P_TRACED) != 0) ? 1 : -1; + } + + return testResult > 0; +} + +bool JUCE_CALLTYPE Process::isRunningUnderDebugger() throw() +{ + return juce_isRunningUnderDebugger(); +} + +#endif +/********* End of inlined file: juce_mac_Debugging.mm *********/ + +#if ! JUCE_ONLY_BUILD_CORE_LIBRARY + /*#include "mac/juce_mac_NSViewComponentPeer.mm" + +/********* Start of inlined file: juce_mac_MouseCursor.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +static NSImage* juceImageToNSImage (const Image& image) +{ + const ScopedAutoReleasePool pool; + int lineStride, pixelStride; + const uint8* pixels = image.lockPixelDataReadOnly (0, 0, image.getWidth(), image.getHeight(), + lineStride, pixelStride); + + NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes: NULL + pixelsWide: image.getWidth() + pixelsHigh: image.getHeight() + bitsPerSample: 8 + samplesPerPixel: image.hasAlphaChannel() ? 4 : 3 + hasAlpha: image.hasAlphaChannel() + isPlanar: NO + colorSpaceName: NSCalibratedRGBColorSpace + bitmapFormat: (NSBitmapFormat) 0 + bytesPerRow: lineStride + bitsPerPixel: pixelStride * 8]; + + unsigned char* newData = [rep bitmapData]; + memcpy (newData, pixels, lineStride * image.getHeight()); + image.releasePixelDataReadOnly (pixels); + + NSImage* im = [[NSImage alloc] init]; + [im addRepresentation: rep]; + [rep release]; + + return im; +} + +void* juce_createMouseCursorFromImage (const Image& image, int hotspotX, int hotspotY) throw() +{ + NSImage* im = juceImageToNSImage (image); + NSCursor* c = [[NSCursor alloc] initWithImage: im + hotSpot: NSMakePoint (hotspotX, hotspotY)]; + [im release]; + + return (void*) c; +} + +static void* juce_cursorFromData (const unsigned char* data, const int size, float hx, float hy) throw() +{ + Image* const im = ImageFileFormat::loadFrom ((const char*) data, size); + jassert (im != 0); + + if (im == 0) + return 0; + + void* const curs = juce_createMouseCursorFromImage (*im, + (int) (hx * im->getWidth()), + (int) (hy * im->getHeight())); + delete im; + return curs; +} + +static void* juce_cursorFromWebKitFile (const char* filename, float hx, float hy) +{ + File f ("/System/Library/Frameworks/WebKit.framework/Frameworks/WebCore.framework/Resources"); + + MemoryBlock mb; + if (f.getChildFile (filename).loadFileAsData (mb)) + return juce_cursorFromData ((const unsigned char*) mb.getData(), mb.getSize(), hx, hy); + + return 0; +} + +void* juce_createStandardMouseCursor (MouseCursor::StandardCursorType type) throw() +{ + const ScopedAutoReleasePool pool; + NSCursor* c = 0; + + switch (type) + { + case MouseCursor::NormalCursor: + c = [NSCursor arrowCursor]; + break; + + case MouseCursor::NoCursor: + { + Image blank (Image::ARGB, 8, 8, true); + return juce_createMouseCursorFromImage (blank, 0, 0); + } + + case MouseCursor::DraggingHandCursor: + c = [NSCursor openHandCursor]; + break; + + case MouseCursor::CopyingCursor: + return juce_cursorFromWebKitFile ("copyCursor.png", 0, 0); + + case MouseCursor::WaitCursor: + c = [NSCursor arrowCursor]; // avoid this on the mac, let the OS provide the beachball + break; + //return juce_cursorFromWebKitFile ("waitCursor.png", 0.5f, 0.5f); + + case MouseCursor::IBeamCursor: + c = [NSCursor IBeamCursor]; + break; + + case MouseCursor::PointingHandCursor: + c = [NSCursor pointingHandCursor]; + break; + + case MouseCursor::LeftRightResizeCursor: + c = [NSCursor resizeLeftRightCursor]; + break; + + case MouseCursor::LeftEdgeResizeCursor: + c = [NSCursor resizeLeftCursor]; + break; + + case MouseCursor::RightEdgeResizeCursor: + c = [NSCursor resizeRightCursor]; + break; + + case MouseCursor::UpDownResizeCursor: + case MouseCursor::TopEdgeResizeCursor: + case MouseCursor::BottomEdgeResizeCursor: + return juce_cursorFromWebKitFile ("northSouthResizeCursor.png", 0.5f, 0.5f); + + case MouseCursor::TopLeftCornerResizeCursor: + case MouseCursor::BottomRightCornerResizeCursor: + return juce_cursorFromWebKitFile ("northWestSouthEastResizeCursor.png", 0.5f, 0.5f); + + case MouseCursor::TopRightCornerResizeCursor: + case MouseCursor::BottomLeftCornerResizeCursor: + return juce_cursorFromWebKitFile ("northEastSouthWestResizeCursor.png", 0.5f, 0.5f); + + case MouseCursor::UpDownLeftRightResizeCursor: + return juce_cursorFromWebKitFile ("moveCursor.png", 0.5f, 0.5f); + + case MouseCursor::CrosshairCursor: + c = [NSCursor crosshairCursor]; + break; + } + + [c retain]; + return (void*) c; +} + +void juce_deleteMouseCursor (void* const cursorHandle, const bool isStandard) throw() +{ + NSCursor* c = (NSCursor*) cursorHandle; + [c release]; +} + +void MouseCursor::showInAllWindows() const throw() +{ + showInWindow (0); +} + +void MouseCursor::showInWindow (ComponentPeer*) const throw() +{ + NSCursor* const c = (NSCursor*) getHandle(); + [c set]; +} + +#endif +/********* End of inlined file: juce_mac_MouseCursor.mm *********/ + +/********* Start of inlined file: juce_mac_NSViewComponent.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +class NSViewComponentInternal : public ComponentMovementWatcher +{ + Component* const owner; + NSViewComponentPeer* currentPeer; + bool wasShowing; + +public: + NSView* const view; + + NSViewComponentInternal (NSView* const view_, Component* const owner_) + : ComponentMovementWatcher (owner_), + owner (owner_), + currentPeer (0), + wasShowing (false), + view (view_) + { + [view_ retain]; + + if (owner_->isShowing()) + componentPeerChanged(); + } + + ~NSViewComponentInternal() + { + [view removeFromSuperview]; + [view release]; + } + + void componentMovedOrResized (Component& comp, bool wasMoved, bool wasResized) + { + ComponentMovementWatcher::componentMovedOrResized (comp, wasMoved, wasResized); + + // The ComponentMovementWatcher version of this method avoids calling + // us when the top-level comp is resized, but for an NSView we need to know this + // because with inverted co-ords, we need to update the position even if the + // top-left pos hasn't changed + if (comp.isOnDesktop() && wasResized) + componentMovedOrResized (wasMoved, wasResized); + } + + void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) + { + Component* const topComp = owner->getTopLevelComponent(); + + if (topComp->getPeer() != 0) + { + int x = 0, y = 0; + owner->relativePositionToOtherComponent (topComp, x, y); + + NSRect r; + r.origin.x = (float) x; + r.origin.y = (float) y; + r.size.width = (float) owner->getWidth(); + r.size.height = (float) owner->getHeight(); + r.origin.y = [[view superview] frame].size.height - (r.origin.y + r.size.height); + + [view setFrame: r]; + } + } + + void componentPeerChanged() + { + NSViewComponentPeer* const peer = dynamic_cast (owner->getPeer()); + + if (currentPeer != peer) + { + [view removeFromSuperview]; + currentPeer = peer; + + if (peer != 0) + { + [peer->view addSubview: view]; + componentMovedOrResized (false, false); + } + } + + [view setHidden: ! owner->isShowing()]; + } + + void componentVisibilityChanged (Component&) + { + componentPeerChanged(); + } + + juce_UseDebuggingNewOperator + +private: + NSViewComponentInternal (const NSViewComponentInternal&); + const NSViewComponentInternal& operator= (const NSViewComponentInternal&); +}; + +NSViewComponent::NSViewComponent() + : info (0) +{ +} + +NSViewComponent::~NSViewComponent() +{ + delete info; +} + +void NSViewComponent::setView (void* view) +{ + if (view != getView()) + { + deleteAndZero (info); + + if (view != 0) + info = new NSViewComponentInternal ((NSView*) view, this); + } +} + +void* NSViewComponent::getView() const +{ + return info == 0 ? 0 : info->view; +} + +void NSViewComponent::paint (Graphics& g) +{ +} + +#endif +/********* End of inlined file: juce_mac_NSViewComponent.mm *********/ + +/********* Start of inlined file: juce_mac_AppleRemote.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +AppleRemoteDevice::AppleRemoteDevice() + : device (0), + queue (0), + remoteId (0) +{ +} + +AppleRemoteDevice::~AppleRemoteDevice() +{ + stop(); +} + +static io_object_t getAppleRemoteDevice() throw() +{ + CFMutableDictionaryRef dict = IOServiceMatching ("AppleIRController"); + + io_iterator_t iter = 0; + io_object_t iod = 0; + + if (IOServiceGetMatchingServices (kIOMasterPortDefault, dict, &iter) == kIOReturnSuccess + && iter != 0) + { + iod = IOIteratorNext (iter); + } + + IOObjectRelease (iter); + return iod; +} + +static bool createAppleRemoteInterface (io_object_t iod, void** device) throw() +{ + jassert (*device == 0); + io_name_t classname; + + if (IOObjectGetClass (iod, classname) == kIOReturnSuccess) + { + IOCFPlugInInterface** cfPlugInInterface = 0; + SInt32 score = 0; + + if (IOCreatePlugInInterfaceForService (iod, + kIOHIDDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, + &cfPlugInInterface, + &score) == kIOReturnSuccess) + { + HRESULT hr = (*cfPlugInInterface)->QueryInterface (cfPlugInInterface, + CFUUIDGetUUIDBytes (kIOHIDDeviceInterfaceID), + device); + + (void) hr; + + (*cfPlugInInterface)->Release (cfPlugInInterface); + } + } + + return *device != 0; +} + +bool AppleRemoteDevice::start (const bool inExclusiveMode) throw() +{ + if (queue != 0) + return true; + + stop(); + + bool result = false; + io_object_t iod = getAppleRemoteDevice(); + + if (iod != 0) + { + if (createAppleRemoteInterface (iod, &device) && open (inExclusiveMode)) + result = true; + else + stop(); + + IOObjectRelease (iod); + } + + return result; +} + +void AppleRemoteDevice::stop() throw() +{ + if (queue != 0) + { + (*(IOHIDQueueInterface**) queue)->stop ((IOHIDQueueInterface**) queue); + (*(IOHIDQueueInterface**) queue)->dispose ((IOHIDQueueInterface**) queue); + (*(IOHIDQueueInterface**) queue)->Release ((IOHIDQueueInterface**) queue); + queue = 0; + } + + if (device != 0) + { + (*(IOHIDDeviceInterface**) device)->close ((IOHIDDeviceInterface**) device); + (*(IOHIDDeviceInterface**) device)->Release ((IOHIDDeviceInterface**) device); + device = 0; + } +} + +bool AppleRemoteDevice::isActive() const throw() +{ + return queue != 0; +} + +static void appleRemoteQueueCallback (void* const target, const IOReturn result, void*, void*) +{ + if (result == kIOReturnSuccess) + ((AppleRemoteDevice*) target)->handleCallbackInternal(); +} + +bool AppleRemoteDevice::open (const bool openInExclusiveMode) throw() +{ + Array cookies; + + CFArrayRef elements; + IOHIDDeviceInterface122** const device122 = (IOHIDDeviceInterface122**) device; + + if ((*device122)->copyMatchingElements (device122, 0, &elements) != kIOReturnSuccess) + return false; + + for (int i = 0; i < CFArrayGetCount (elements); ++i) + { + CFDictionaryRef element = (CFDictionaryRef) CFArrayGetValueAtIndex (elements, i); + + // get the cookie + CFTypeRef object = CFDictionaryGetValue (element, CFSTR (kIOHIDElementCookieKey)); + + if (object == 0 || CFGetTypeID (object) != CFNumberGetTypeID()) + continue; + + long number; + if (! CFNumberGetValue ((CFNumberRef) object, kCFNumberLongType, &number)) + continue; + + cookies.add ((int) number); + } + + CFRelease (elements); + + if ((*(IOHIDDeviceInterface**) device) + ->open ((IOHIDDeviceInterface**) device, + openInExclusiveMode ? kIOHIDOptionsTypeSeizeDevice + : kIOHIDOptionsTypeNone) == KERN_SUCCESS) + { + queue = (*(IOHIDDeviceInterface**) device)->allocQueue ((IOHIDDeviceInterface**) device); + + if (queue != 0) + { + (*(IOHIDQueueInterface**) queue)->create ((IOHIDQueueInterface**) queue, 0, 12); + + for (int i = 0; i < cookies.size(); ++i) + { + IOHIDElementCookie cookie = (IOHIDElementCookie) cookies.getUnchecked(i); + (*(IOHIDQueueInterface**) queue)->addElement ((IOHIDQueueInterface**) queue, cookie, 0); + } + + CFRunLoopSourceRef eventSource; + + if ((*(IOHIDQueueInterface**) queue) + ->createAsyncEventSource ((IOHIDQueueInterface**) queue, &eventSource) == KERN_SUCCESS) + { + if ((*(IOHIDQueueInterface**) queue)->setEventCallout ((IOHIDQueueInterface**) queue, + appleRemoteQueueCallback, this, 0) == KERN_SUCCESS) + { + CFRunLoopAddSource (CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); + + (*(IOHIDQueueInterface**) queue)->start ((IOHIDQueueInterface**) queue); + + return true; + } + } + } + } + + return false; +} + +void AppleRemoteDevice::handleCallbackInternal() +{ + int totalValues = 0; + AbsoluteTime nullTime = { 0, 0 }; + char cookies [12]; + int numCookies = 0; + + while (numCookies < numElementsInArray (cookies)) + { + IOHIDEventStruct e; + + if ((*(IOHIDQueueInterface**) queue)->getNextEvent ((IOHIDQueueInterface**) queue, &e, nullTime, 0) != kIOReturnSuccess) + break; + + if ((int) e.elementCookie == 19) + { + remoteId = e.value; + buttonPressed (switched, false); + } + else + { + totalValues += e.value; + cookies [numCookies++] = (char) (pointer_sized_int) e.elementCookie; + } + } + + cookies [numCookies++] = 0; + //DBG (String::toHexString ((uint8*) cookies, numCookies, 1) + " " + String (totalValues)); + + static const char buttonPatterns[] = + { + 0x1f, 0x14, 0x12, 0x1f, 0x14, 0x12, 0, + 0x1f, 0x15, 0x12, 0x1f, 0x15, 0x12, 0, + 0x1f, 0x1d, 0x1c, 0x12, 0, + 0x1f, 0x1e, 0x1c, 0x12, 0, + 0x1f, 0x16, 0x12, 0x1f, 0x16, 0x12, 0, + 0x1f, 0x17, 0x12, 0x1f, 0x17, 0x12, 0, + 0x1f, 0x12, 0x04, 0x02, 0, + 0x1f, 0x12, 0x03, 0x02, 0, + 0x1f, 0x12, 0x1f, 0x12, 0, + 0x23, 0x1f, 0x12, 0x23, 0x1f, 0x12, 0, + 19, 0 + }; + + int buttonNum = (int) menuButton; + int i = 0; + + while (i < numElementsInArray (buttonPatterns)) + { + if (strcmp (cookies, buttonPatterns + i) == 0) + { + buttonPressed ((ButtonType) buttonNum, totalValues > 0); + break; + } + + i += strlen (buttonPatterns + i) + 1; + ++buttonNum; + } +} + +#endif +/********* End of inlined file: juce_mac_AppleRemote.mm *********/ + +/********* Start of inlined file: juce_mac_OpenGLComponent.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#if JUCE_INCLUDED_FILE && JUCE_OPENGL + +END_JUCE_NAMESPACE + +#define ThreadSafeNSOpenGLView MakeObjCClassName(ThreadSafeNSOpenGLView) + +@interface ThreadSafeNSOpenGLView : NSOpenGLView +{ + CriticalSection* contextLock; + bool needsUpdate; +} + +- (id) initWithFrame: (NSRect) frameRect pixelFormat: (NSOpenGLPixelFormat*) format; +- (bool) makeActive; +- (void) makeInactive; +- (void) reshape; +@end + +@implementation ThreadSafeNSOpenGLView + +- (id) initWithFrame: (NSRect) frameRect + pixelFormat: (NSOpenGLPixelFormat*) format +{ + contextLock = new CriticalSection(); + self = [super initWithFrame: frameRect pixelFormat: format]; + + if (self != nil) + [[NSNotificationCenter defaultCenter] addObserver: self + selector: @selector (_surfaceNeedsUpdate:) + name: NSViewGlobalFrameDidChangeNotification + object: self]; + return self; +} + +- (void) dealloc +{ + [[NSNotificationCenter defaultCenter] removeObserver: self]; + delete contextLock; + [super dealloc]; +} + +- (bool) makeActive +{ + const ScopedLock sl (*contextLock); + + if ([self openGLContext] == 0) + return false; + + [[self openGLContext] makeCurrentContext]; + + if (needsUpdate) + { + [super update]; + needsUpdate = false; + } + + return true; +} + +- (void) makeInactive +{ + const ScopedLock sl (*contextLock); + [NSOpenGLContext clearCurrentContext]; +} + +- (void) _surfaceNeedsUpdate: (NSNotification*) notification +{ + const ScopedLock sl (*contextLock); + needsUpdate = true; +} + +- (void) update +{ + const ScopedLock sl (*contextLock); + needsUpdate = true; +} + +- (void) reshape +{ + const ScopedLock sl (*contextLock); + needsUpdate = true; +} + +@end +BEGIN_JUCE_NAMESPACE + +class WindowedGLContext : public OpenGLContext +{ +public: + WindowedGLContext (Component* const component, + const OpenGLPixelFormat& pixelFormat_, + NSOpenGLContext* sharedContext) + : renderContext (0), + pixelFormat (pixelFormat_) + { + jassert (component != 0); + + NSOpenGLPixelFormatAttribute attribs [64]; + int n = 0; + attribs[n++] = NSOpenGLPFADoubleBuffer; + attribs[n++] = NSOpenGLPFAAccelerated; + attribs[n++] = NSOpenGLPFAMPSafe; // NSOpenGLPFAAccelerated, NSOpenGLPFAMultiScreen, NSOpenGLPFASingleRenderer + attribs[n++] = NSOpenGLPFAColorSize; + attribs[n++] = (NSOpenGLPixelFormatAttribute) jmax (pixelFormat.redBits, + pixelFormat.greenBits, + pixelFormat.blueBits); + attribs[n++] = NSOpenGLPFAAlphaSize; + attribs[n++] = (NSOpenGLPixelFormatAttribute) pixelFormat.alphaBits; + attribs[n++] = NSOpenGLPFADepthSize; + attribs[n++] = (NSOpenGLPixelFormatAttribute) pixelFormat.depthBufferBits; + attribs[n++] = NSOpenGLPFAStencilSize; + attribs[n++] = (NSOpenGLPixelFormatAttribute) pixelFormat.stencilBufferBits; + attribs[n++] = NSOpenGLPFAAccumSize; + attribs[n++] = (NSOpenGLPixelFormatAttribute) jmax (pixelFormat.accumulationBufferRedBits, + pixelFormat.accumulationBufferGreenBits, + pixelFormat.accumulationBufferBlueBits, + pixelFormat.accumulationBufferAlphaBits); + + // xxx not sure how to do fullSceneAntiAliasingNumSamples.. + attribs[n++] = NSOpenGLPFASampleBuffers; + attribs[n++] = (NSOpenGLPixelFormatAttribute) 1; + attribs[n++] = NSOpenGLPFAClosestPolicy; + attribs[n++] = NSOpenGLPFANoRecovery; + attribs[n++] = (NSOpenGLPixelFormatAttribute) 0; + + NSOpenGLPixelFormat* format + = [[NSOpenGLPixelFormat alloc] initWithAttributes: attribs]; + + view = [[ThreadSafeNSOpenGLView alloc] initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f) + pixelFormat: format]; + + renderContext = [[[NSOpenGLContext alloc] initWithFormat: format + shareContext: sharedContext] autorelease]; + + const GLint swapInterval = 1; + [renderContext setValues: &swapInterval forParameter: NSOpenGLCPSwapInterval]; + + [view setOpenGLContext: renderContext]; + [renderContext setView: view]; + + [format release]; + + viewHolder = new NSViewComponentInternal (view, component); + } + + ~WindowedGLContext() + { + makeInactive(); + [renderContext setView: nil]; + delete viewHolder; + } + + bool makeActive() const throw() + { + jassert (renderContext != 0); + [view makeActive]; + return isActive(); + } + + bool makeInactive() const throw() + { + [view makeInactive]; + return true; + } + + bool isActive() const throw() + { + return [NSOpenGLContext currentContext] == renderContext; + } + + const OpenGLPixelFormat getPixelFormat() const { return pixelFormat; } + void* getRawContext() const throw() { return renderContext; } + + void updateWindowPosition (int x, int y, int w, int h, int outerWindowHeight) + { + } + + void swapBuffers() + { + [renderContext flushBuffer]; + } + + bool setSwapInterval (const int numFramesPerSwap) + { + [renderContext setValues: (const GLint*) &numFramesPerSwap + forParameter: NSOpenGLCPSwapInterval]; + return true; + } + + int getSwapInterval() const + { + GLint numFrames = 0; + [renderContext getValues: &numFrames + forParameter: NSOpenGLCPSwapInterval]; + return numFrames; + } + + void repaint() + { + // we need to invalidate the juce view that holds this gl view, to make it + // cause a repaint callback + NSView* v = (NSView*) viewHolder->view; + NSRect r = [v frame]; + + // bit of a bodge here.. if we only invalidate the area of the gl component, + // it's completely covered by the NSOpenGLView, so the OS throws away the + // repaint message, thus never causing our paint() callback, and never repainting + // the comp. So invalidating just a little bit around the edge helps.. + [[v superview] setNeedsDisplayInRect: NSInsetRect (r, -2.0f, -2.0f)]; + } + + void* getNativeWindowHandle() const { return viewHolder->view; } + + juce_UseDebuggingNewOperator + + NSOpenGLContext* renderContext; + ThreadSafeNSOpenGLView* view; + +private: + OpenGLPixelFormat pixelFormat; + NSViewComponentInternal* viewHolder; + + WindowedGLContext (const WindowedGLContext&); + const WindowedGLContext& operator= (const WindowedGLContext&); +}; + +OpenGLContext* OpenGLContext::createContextForWindow (Component* const component, + const OpenGLPixelFormat& pixelFormat, + const OpenGLContext* const contextToShareWith) +{ + WindowedGLContext* c = new WindowedGLContext (component, pixelFormat, + contextToShareWith != 0 ? (NSOpenGLContext*) contextToShareWith->getRawContext() : 0); + + if (c->renderContext == 0) + deleteAndZero (c); + + return c; +} + +void* OpenGLComponent::getNativeWindowHandle() const +{ + return context != 0 ? ((WindowedGLContext*) context)->getNativeWindowHandle() + : 0; +} + +void juce_glViewport (const int w, const int h) +{ + glViewport (0, 0, w, h); +} + +void OpenGLPixelFormat::getAvailablePixelFormats (Component* /*component*/, + OwnedArray & results) +{ +/* GLint attribs [64]; + int n = 0; + attribs[n++] = AGL_RGBA; + attribs[n++] = AGL_DOUBLEBUFFER; + attribs[n++] = AGL_ACCELERATED; + attribs[n++] = AGL_NO_RECOVERY; + attribs[n++] = AGL_NONE; + + AGLPixelFormat p = aglChoosePixelFormat (0, 0, attribs); + + while (p != 0) + { + OpenGLPixelFormat* const pf = new OpenGLPixelFormat(); + pf->redBits = getAGLAttribute (p, AGL_RED_SIZE); + pf->greenBits = getAGLAttribute (p, AGL_GREEN_SIZE); + pf->blueBits = getAGLAttribute (p, AGL_BLUE_SIZE); + pf->alphaBits = getAGLAttribute (p, AGL_ALPHA_SIZE); + pf->depthBufferBits = getAGLAttribute (p, AGL_DEPTH_SIZE); + pf->stencilBufferBits = getAGLAttribute (p, AGL_STENCIL_SIZE); + pf->accumulationBufferRedBits = getAGLAttribute (p, AGL_ACCUM_RED_SIZE); + pf->accumulationBufferGreenBits = getAGLAttribute (p, AGL_ACCUM_GREEN_SIZE); + pf->accumulationBufferBlueBits = getAGLAttribute (p, AGL_ACCUM_BLUE_SIZE); + pf->accumulationBufferAlphaBits = getAGLAttribute (p, AGL_ACCUM_ALPHA_SIZE); + + results.add (pf); + + p = aglNextPixelFormat (p); + }*/ + + //jassertfalse //xxx can't see how you do this in cocoa! +} + +#endif +/********* End of inlined file: juce_mac_OpenGLComponent.mm *********/ + +/********* Start of inlined file: juce_mac_MainMenu.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +class JuceMainMenuHandler; + +END_JUCE_NAMESPACE +using namespace JUCE_NAMESPACE; + +#define JuceMenuCallback MakeObjCClassName(JuceMenuCallback) + +#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 +@interface JuceMenuCallback : NSObject +#else +@interface JuceMenuCallback : NSObject +#endif +{ + JuceMainMenuHandler* owner; +} + +- (JuceMenuCallback*) initWithOwner: (JuceMainMenuHandler*) owner_; +- (void) dealloc; +- (void) menuItemInvoked: (id) menu; +- (void) menuNeedsUpdate: (NSMenu*) menu; +@end +BEGIN_JUCE_NAMESPACE + +class JuceMainMenuHandler : private MenuBarModelListener, + private DeletedAtShutdown +{ +public: + static JuceMainMenuHandler* instance; + + JuceMainMenuHandler() throw() + : currentModel (0), + lastUpdateTime (0) + { + callback = [[JuceMenuCallback alloc] initWithOwner: this]; + } + + ~JuceMainMenuHandler() throw() + { + setMenu (0); + + jassert (instance == this); + instance = 0; + + [callback release]; + } + + void setMenu (MenuBarModel* const newMenuBarModel) throw() + { + if (currentModel != newMenuBarModel) + { + if (currentModel != 0) + currentModel->removeListener (this); + + currentModel = newMenuBarModel; + + if (currentModel != 0) + currentModel->addListener (this); + + menuBarItemsChanged (0); + } + } + + void addSubMenu (NSMenu* parent, const PopupMenu& child, + const String& name, const int menuId, const int tag) + { + NSMenuItem* item = [parent addItemWithTitle: juceStringToNS (name) + action: nil + keyEquivalent: @""]; + [item setTag: tag]; + + NSMenu* sub = createMenu (child, name, menuId, tag); + + [parent setSubmenu: sub forItem: item]; + [sub setAutoenablesItems: false]; + [sub release]; + } + + void updateSubMenu (NSMenuItem* parentItem, const PopupMenu& menuToCopy, + const String& name, const int menuId, const int tag) + { + [parentItem setTag: tag]; + NSMenu* menu = [parentItem submenu]; + + [menu setTitle: juceStringToNS (name)]; + + while ([menu numberOfItems] > 0) + [menu removeItemAtIndex: 0]; + + PopupMenu::MenuItemIterator iter (menuToCopy); + + while (iter.next()) + addMenuItem (iter, menu, menuId, tag); + + [menu setAutoenablesItems: false]; + [menu update]; + } + + void menuBarItemsChanged (MenuBarModel*) + { + lastUpdateTime = Time::getMillisecondCounter(); + + StringArray menuNames; + if (currentModel != 0) + menuNames = currentModel->getMenuBarNames(); + + NSMenu* menuBar = [NSApp mainMenu]; + while ([menuBar numberOfItems] > 1 + menuNames.size()) + [menuBar removeItemAtIndex: [menuBar numberOfItems] - 1]; + + int menuId = 1; + + for (int i = 0; i < menuNames.size(); ++i) + { + const PopupMenu menu (currentModel->getMenuForIndex (i, menuNames [i])); + + if (i >= [menuBar numberOfItems] - 1) + addSubMenu (menuBar, menu, menuNames[i], menuId, i); + else + updateSubMenu ([menuBar itemAtIndex: 1 + i], menu, menuNames[i], menuId, i); + } + } + + static void flashMenuBar (NSMenu* menu) + { + const unichar f35Key = NSF35FunctionKey; + NSString* f35String = [NSString stringWithCharacters: &f35Key length: 1]; + + NSMenuItem* item = [[NSMenuItem alloc] initWithTitle: @"x" + action: nil + keyEquivalent: f35String]; + [item setTarget: nil]; + [menu insertItem: item atIndex: [menu numberOfItems]]; + [item release]; + + NSEvent* f35Event = [NSEvent keyEventWithType: NSKeyDown + location: NSZeroPoint + modifierFlags: NSCommandKeyMask + timestamp: 0 + windowNumber: 0 + context: [NSGraphicsContext currentContext] + characters: f35String + charactersIgnoringModifiers: f35String + isARepeat: NO + keyCode: 0]; + + [menu performKeyEquivalent: f35Event]; + [menu removeItem: item]; + } + + static NSMenuItem* findMenuItem (NSMenu* const menu, const ApplicationCommandTarget::InvocationInfo& info) + { + for (int i = [menu numberOfItems]; --i >= 0;) + { + NSMenuItem* m = [menu itemAtIndex: i]; + if ([m tag] == info.commandID) + return m; + + if ([m submenu] != 0) + { + NSMenuItem* found = findMenuItem ([m submenu], info); + if (found != 0) + return found; + } + } + + return 0; + } + + void menuCommandInvoked (MenuBarModel*, const ApplicationCommandTarget::InvocationInfo& info) + { + NSMenuItem* item = findMenuItem ([NSApp mainMenu], info); + + if (item != 0) + flashMenuBar ([item menu]); + } + + void updateMenus() + { + if (Time::getMillisecondCounter() > lastUpdateTime + 500) + menuBarItemsChanged (0); + } + + void invoke (const int commandId, ApplicationCommandManager* const commandManager, const int topLevelIndex) const + { + if (currentModel != 0) + { + if (commandManager != 0) + { + ApplicationCommandTarget::InvocationInfo info (commandId); + info.invocationMethod = ApplicationCommandTarget::InvocationInfo::fromMenu; + + commandManager->invoke (info, true); + } + + currentModel->menuItemSelected (commandId, topLevelIndex); + } + } + + MenuBarModel* currentModel; + uint32 lastUpdateTime; + + void addMenuItem (PopupMenu::MenuItemIterator& iter, NSMenu* menuToAddTo, + const int topLevelMenuId, const int topLevelIndex) + { + NSString* text = juceStringToNS (iter.itemName.upToFirstOccurrenceOf (T(""), false, true)); + + if (text == 0) + text = @""; + + if (iter.isSeparator) + { + [menuToAddTo addItem: [NSMenuItem separatorItem]]; + } + else if (iter.isSectionHeader) + { + NSMenuItem* item = [menuToAddTo addItemWithTitle: text + action: nil + keyEquivalent: @""]; + + [item setEnabled: false]; + } + else if (iter.subMenu != 0) + { + NSMenuItem* item = [menuToAddTo addItemWithTitle: text + action: nil + keyEquivalent: @""]; + + [item setTag: iter.itemId]; + [item setEnabled: iter.isEnabled]; + + NSMenu* sub = createMenu (*iter.subMenu, iter.itemName, topLevelMenuId, topLevelIndex); + [sub setDelegate: nil]; + [menuToAddTo setSubmenu: sub forItem: item]; + } + else + { + NSMenuItem* item = [menuToAddTo addItemWithTitle: text + action: @selector (menuItemInvoked:) + keyEquivalent: @""]; + + [item setTag: iter.itemId]; + [item setEnabled: iter.isEnabled]; + [item setState: iter.isTicked ? NSOnState : NSOffState]; + [item setTarget: (id) callback]; + + NSMutableArray* info = [NSMutableArray arrayWithObject: [NSNumber numberWithUnsignedLongLong: (pointer_sized_int) (void*) iter.commandManager]]; + [info addObject: [NSNumber numberWithInt: topLevelIndex]]; + [item setRepresentedObject: info]; + + if (iter.commandManager != 0) + { + const Array keyPresses (iter.commandManager->getKeyMappings() + ->getKeyPressesAssignedToCommand (iter.itemId)); + + if (keyPresses.size() > 0) + { + const KeyPress& kp = keyPresses.getReference(0); + + juce_wchar key = kp.getTextCharacter(); + + if (kp.getKeyCode() == KeyPress::backspaceKey) + key = NSBackspaceCharacter; + else if (kp.getKeyCode() == KeyPress::deleteKey) + key = NSDeleteCharacter; + else if (key == 0) + key = (juce_wchar) kp.getKeyCode(); + + unsigned int mods = 0; + if (kp.getModifiers().isShiftDown()) + mods |= NSShiftKeyMask; + if (kp.getModifiers().isCtrlDown()) + mods |= NSControlKeyMask; + if (kp.getModifiers().isAltDown()) + mods |= NSAlternateKeyMask; + if (kp.getModifiers().isCommandDown()) + mods |= NSCommandKeyMask; + + [item setKeyEquivalent: juceStringToNS (String::charToString (key))]; + [item setKeyEquivalentModifierMask: mods]; + } + } + } + } + + JuceMenuCallback* callback; +private: + + NSMenu* createMenu (const PopupMenu menu, + const String& menuName, + const int topLevelMenuId, + const int topLevelIndex) + { + NSMenu* m = [[NSMenu alloc] initWithTitle: juceStringToNS (menuName)]; + + [m setAutoenablesItems: false]; + [m setDelegate: callback]; + + PopupMenu::MenuItemIterator iter (menu); + + while (iter.next()) + addMenuItem (iter, m, topLevelMenuId, topLevelIndex); + + [m update]; + return m; + } +}; + +JuceMainMenuHandler* JuceMainMenuHandler::instance = 0; + +END_JUCE_NAMESPACE +@implementation JuceMenuCallback + +- (JuceMenuCallback*) initWithOwner: (JuceMainMenuHandler*) owner_ +{ + [super init]; + owner = owner_; + return self; +} + +- (void) dealloc +{ + [super dealloc]; +} + +- (void) menuItemInvoked: (id) menu +{ + NSMenuItem* item = (NSMenuItem*) menu; + + if ([[item representedObject] isKindOfClass: [NSArray class]]) + { + NSArray* info = (NSArray*) [item representedObject]; + + owner->invoke ([item tag], + (ApplicationCommandManager*) (pointer_sized_int) + [((NSNumber*) [info objectAtIndex: 0]) unsignedLongLongValue], + (int) [((NSNumber*) [info objectAtIndex: 1]) intValue]); + } +} + +- (void) menuNeedsUpdate: (NSMenu*) menu; +{ + if (JuceMainMenuHandler::instance != 0) + JuceMainMenuHandler::instance->updateMenus(); +} + +@end +BEGIN_JUCE_NAMESPACE + +static NSMenu* createStandardAppMenu (NSMenu* menu, const String& appName, + const PopupMenu* extraItems) +{ + if (extraItems != 0 && JuceMainMenuHandler::instance != 0 && extraItems->getNumItems() > 0) + { + PopupMenu::MenuItemIterator iter (*extraItems); + + while (iter.next()) + JuceMainMenuHandler::instance->addMenuItem (iter, menu, 0, -1); + + [menu addItem: [NSMenuItem separatorItem]]; + } + + NSMenuItem* item; + + // Services... + item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (@"Services", nil) + action: nil keyEquivalent: @""]; + [menu addItem: item]; + [item release]; + NSMenu* servicesMenu = [[NSMenu alloc] initWithTitle: @"Services"]; + [menu setSubmenu: servicesMenu forItem: item]; + [NSApp setServicesMenu: servicesMenu]; + [servicesMenu release]; + [menu addItem: [NSMenuItem separatorItem]]; + + // Hide + Show stuff... + item = [[NSMenuItem alloc] initWithTitle: juceStringToNS ("Hide " + appName) + action: @selector (hide:) keyEquivalent: @"h"]; + [item setTarget: NSApp]; + [menu addItem: item]; + [item release]; + + item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (@"Hide Others", nil) + action: @selector (hideOtherApplications:) keyEquivalent: @"h"]; + [item setKeyEquivalentModifierMask: NSCommandKeyMask | NSAlternateKeyMask]; + [item setTarget: NSApp]; + [menu addItem: item]; + [item release]; + + item = [[NSMenuItem alloc] initWithTitle: NSLocalizedString (@"Show All", nil) + action: @selector (unhideAllApplications:) keyEquivalent: @""]; + [item setTarget: NSApp]; + [menu addItem: item]; + [item release]; + + [menu addItem: [NSMenuItem separatorItem]]; + + // Quit item.... + item = [[NSMenuItem alloc] initWithTitle: juceStringToNS ("Quit " + appName) + action: @selector (terminate:) keyEquivalent: @"q"]; + + [item setTarget: NSApp]; + [menu addItem: item]; + [item release]; + + return menu; +} + +// Since our app has no NIB, this initialises a standard app menu... +static void rebuildMainMenu (const PopupMenu* extraItems) +{ + // this can't be used in a plugin! + jassert (JUCEApplication::getInstance() != 0); + + if (JUCEApplication::getInstance() != 0) + { + const ScopedAutoReleasePool pool; + + NSMenu* mainMenu = [[NSMenu alloc] initWithTitle: @"MainMenu"]; + NSMenuItem* item = [mainMenu addItemWithTitle: @"Apple" action: nil keyEquivalent: @""]; + + NSMenu* appMenu = [[NSMenu alloc] initWithTitle: @"Apple"]; + + [NSApp performSelector: @selector (setAppleMenu:) withObject: appMenu]; + [mainMenu setSubmenu: appMenu forItem: item]; + + [NSApp setMainMenu: mainMenu]; + createStandardAppMenu (appMenu, JUCEApplication::getInstance()->getApplicationName(), extraItems); + + [appMenu release]; + [mainMenu release]; + } +} + +void MenuBarModel::setMacMainMenu (MenuBarModel* newMenuBarModel, + const PopupMenu* extraAppleMenuItems) throw() +{ + if (getMacMainMenu() != newMenuBarModel) + { + const ScopedAutoReleasePool pool; + + if (newMenuBarModel == 0) + { + delete JuceMainMenuHandler::instance; + jassert (JuceMainMenuHandler::instance == 0); // should be zeroed in the destructor + jassert (extraAppleMenuItems == 0); // you can't specify some extra items without also supplying a model + + extraAppleMenuItems = 0; + } + else + { + if (JuceMainMenuHandler::instance == 0) + JuceMainMenuHandler::instance = new JuceMainMenuHandler(); + + JuceMainMenuHandler::instance->setMenu (newMenuBarModel); + } + } + + rebuildMainMenu (extraAppleMenuItems); + + if (newMenuBarModel != 0) + newMenuBarModel->menuItemsChanged(); +} + +MenuBarModel* MenuBarModel::getMacMainMenu() throw() +{ + return JuceMainMenuHandler::instance != 0 + ? JuceMainMenuHandler::instance->currentModel : 0; +} + +void initialiseMainMenu() +{ + if (JUCEApplication::getInstance() != 0) // only needed in an app + rebuildMainMenu (0); +} + +#endif +/********* End of inlined file: juce_mac_MainMenu.mm *********/ + +/********* Start of inlined file: juce_mac_FileChooser.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +END_JUCE_NAMESPACE +using namespace JUCE_NAMESPACE; + +#define JuceFileChooserDelegate MakeObjCClassName(JuceFileChooserDelegate) + +#if defined (MAC_OS_X_VERSION_10_6) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 +@interface JuceFileChooserDelegate : NSObject +#else +@interface JuceFileChooserDelegate : NSObject +#endif +{ + StringArray* filters; +} + +- (JuceFileChooserDelegate*) initWithFilters: (StringArray*) filters_; +- (void) dealloc; +- (BOOL) panel: (id) sender shouldShowFilename: (NSString*) filename; + +@end + +@implementation JuceFileChooserDelegate +- (JuceFileChooserDelegate*) initWithFilters: (StringArray*) filters_ +{ + [super init]; + filters = filters_; + return self; +} + +- (void) dealloc +{ + delete filters; + [super dealloc]; +} + +- (BOOL) panel: (id) sender shouldShowFilename: (NSString*) filename +{ + const String fname (nsStringToJuce (filename)); + + for (int i = filters->size(); --i >= 0;) + if (fname.matchesWildcard ((*filters)[i], true)) + return true; + + return File (fname).isDirectory(); +} +@end + +BEGIN_JUCE_NAMESPACE + +void FileChooser::showPlatformDialog (OwnedArray& results, + const String& title, + const File& currentFileOrDirectory, + const String& filter, + bool selectsDirectory, + bool isSaveDialogue, + bool warnAboutOverwritingExistingFiles, + bool selectMultipleFiles, + FilePreviewComponent* extraInfoComponent) +{ + const ScopedAutoReleasePool pool; + + StringArray* filters = new StringArray(); + filters->addTokens (filter.replaceCharacters (T(",:"), T(";;")), T(";"), 0); + filters->trim(); + filters->removeEmptyStrings(); + + JuceFileChooserDelegate* delegate = [[JuceFileChooserDelegate alloc] initWithFilters: filters]; + [delegate autorelease]; + + NSSavePanel* panel = isSaveDialogue ? [NSSavePanel savePanel] + : [NSOpenPanel openPanel]; + + [panel setTitle: juceStringToNS (title)]; + + if (! isSaveDialogue) + { + NSOpenPanel* openPanel = (NSOpenPanel*) panel; + [openPanel setCanChooseDirectories: selectsDirectory]; + [openPanel setCanChooseFiles: ! selectsDirectory]; + [openPanel setAllowsMultipleSelection: selectMultipleFiles]; + } + + [panel setDelegate: delegate]; + + String directory, filename; + + if (currentFileOrDirectory.isDirectory()) + { + directory = currentFileOrDirectory.getFullPathName(); + } + else + { + directory = currentFileOrDirectory.getParentDirectory().getFullPathName(); + filename = currentFileOrDirectory.getFileName(); + } + + if ([panel runModalForDirectory: juceStringToNS (directory) + file: juceStringToNS (filename)] + == NSOKButton) + { + if (isSaveDialogue) + { + results.add (new File (nsStringToJuce ([panel filename]))); + } + else + { + NSOpenPanel* openPanel = (NSOpenPanel*) panel; + NSArray* urls = [openPanel filenames]; + for (unsigned int i = 0; i < [urls count]; ++i) + { + NSString* f = [urls objectAtIndex: i]; + results.add (new File (nsStringToJuce (f))); + } + } + } + + [panel setDelegate: nil]; +} + +#endif +/********* End of inlined file: juce_mac_FileChooser.mm *********/ + +/********* Start of inlined file: juce_mac_QuickTimeMovieComponent.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#if JUCE_INCLUDED_FILE && JUCE_QUICKTIME + +#define theMovie ((QTMovie*) movie) + +QuickTimeMovieComponent::QuickTimeMovieComponent() + : movie (0) +{ + setOpaque (true); + setVisible (true); + + QTMovieView* view = [[QTMovieView alloc] initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)]; + setView (view); +} + +QuickTimeMovieComponent::~QuickTimeMovieComponent() +{ + closeMovie(); + setView (0); +} + +bool QuickTimeMovieComponent::isQuickTimeAvailable() throw() +{ + return true; +} + +static QTMovie* openMovieFromStream (InputStream* movieStream, File& movieFile) +{ + // unfortunately, QTMovie objects can only be created on the main thread.. + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + + QTMovie* movie = 0; + + FileInputStream* const fin = dynamic_cast (movieStream); + + if (fin != 0) + { + movieFile = fin->getFile(); + movie = [QTMovie movieWithFile: juceStringToNS (movieFile.getFullPathName()) + error: nil]; + } + else + { + MemoryBlock temp; + movieStream->readIntoMemoryBlock (temp); + + const char* const suffixesToTry[] = { ".mov", ".mp3", ".avi", ".m4a" }; + + for (int i = 0; i < numElementsInArray (suffixesToTry); ++i) + { + movie = [QTMovie movieWithDataReference: [QTDataReference dataReferenceWithReferenceToData: [NSData dataWithBytes: temp.getData() + length: temp.getSize()] + name: [NSString stringWithUTF8String: suffixesToTry[i]] + MIMEType: @""] + error: nil]; + + if (movie != 0) + break; + } + } + + return movie; +} + +bool QuickTimeMovieComponent::loadMovie (InputStream* movieStream, + const bool controllerVisible) +{ + closeMovie(); + + if (getPeer() == 0) + { + // To open a movie, this component must be visible inside a functioning window, so that + // the QT control can be assigned to the window. + jassertfalse + return false; + } + + movie = openMovieFromStream (movieStream, movieFile); + + [theMovie retain]; + QTMovieView* view = (QTMovieView*) getView(); + [view setMovie: theMovie]; + [view setControllerVisible: controllerVisible]; + setLooping (looping); + + return movie != nil; +} + +void QuickTimeMovieComponent::closeMovie() +{ + stop(); + QTMovieView* view = (QTMovieView*) getView(); + [view setMovie: nil]; + [theMovie release]; + movie = 0; + movieFile = File::nonexistent; +} + +bool QuickTimeMovieComponent::isMovieOpen() const +{ + return movie != nil; +} + +const File QuickTimeMovieComponent::getCurrentMovieFile() const +{ + return movieFile; +} + +void QuickTimeMovieComponent::play() +{ + [theMovie play]; +} + +void QuickTimeMovieComponent::stop() +{ + [theMovie stop]; +} + +bool QuickTimeMovieComponent::isPlaying() const +{ + return movie != 0 && [theMovie rate] != 0; +} + +void QuickTimeMovieComponent::setPosition (const double seconds) +{ + if (movie != 0) + { + QTTime t; + t.timeValue = (uint64) (100000.0 * seconds); + t.timeScale = 100000; + t.flags = 0; + + [theMovie setCurrentTime: t]; + } +} + +double QuickTimeMovieComponent::getPosition() const +{ + if (movie == 0) + return 0.0; + + QTTime t = [theMovie currentTime]; + return t.timeValue / (double) t.timeScale; +} + +void QuickTimeMovieComponent::setSpeed (const float newSpeed) +{ + [theMovie setRate: newSpeed]; +} + +double QuickTimeMovieComponent::getMovieDuration() const +{ + if (movie == 0) + return 0.0; + + QTTime t = [theMovie duration]; + return t.timeValue / (double) t.timeScale; +} + +void QuickTimeMovieComponent::setLooping (const bool shouldLoop) +{ + looping = shouldLoop; + + [theMovie setAttribute: [NSNumber numberWithBool: shouldLoop] + forKey: QTMovieLoopsAttribute]; +} + +bool QuickTimeMovieComponent::isLooping() const +{ + return looping; +} + +void QuickTimeMovieComponent::setMovieVolume (const float newVolume) +{ + [theMovie setVolume: newVolume]; +} + +float QuickTimeMovieComponent::getMovieVolume() const +{ + return movie != 0 ? [theMovie volume] : 0.0f; +} + +void QuickTimeMovieComponent::getMovieNormalSize (int& width, int& height) const +{ + width = 0; + height = 0; + + if (movie != 0) + { + NSSize s = [[theMovie attributeForKey: QTMovieNaturalSizeAttribute] sizeValue]; + width = s.width; + height = s.height; + } +} + +void QuickTimeMovieComponent::paint (Graphics& g) +{ + if (movie == 0) + g.fillAll (Colours::black); +} + +bool QuickTimeMovieComponent::isControllerVisible() const +{ + return controllerVisible; +} + +bool QuickTimeMovieComponent::loadMovie (const File& movieFile_, + const bool isControllerVisible) +{ + const bool ok = loadMovie ((InputStream*) movieFile_.createInputStream(), isControllerVisible); + movieFile = movieFile_; + return ok; +} + +void QuickTimeMovieComponent::goToStart() +{ + setPosition (0.0); +} + +void QuickTimeMovieComponent::setBoundsWithCorrectAspectRatio (const Rectangle& spaceToFitWithin, + const RectanglePlacement& placement) +{ + int normalWidth, normalHeight; + getMovieNormalSize (normalWidth, normalHeight); + + if (normalWidth > 0 && normalHeight > 0 && ! spaceToFitWithin.isEmpty()) + { + double x = 0.0, y = 0.0, w = normalWidth, h = normalHeight; + + placement.applyTo (x, y, w, h, + spaceToFitWithin.getX(), spaceToFitWithin.getY(), + spaceToFitWithin.getWidth(), spaceToFitWithin.getHeight()); + + if (w > 0 && h > 0) + { + setBounds (roundDoubleToInt (x), roundDoubleToInt (y), + roundDoubleToInt (w), roundDoubleToInt (h)); + } + } + else + { + setBounds (spaceToFitWithin); + } +} + +#if ! (JUCE_MAC && JUCE_64BIT) + +bool juce_OpenQuickTimeMovieFromStream (InputStream* movieStream, Movie& result, Handle& dataHandle) +{ + if (movieStream == 0) + return false; + + File file; + QTMovie* movie = openMovieFromStream (movieStream, file); + + if (movie != nil) + result = [movie quickTimeMovie]; + + return movie != nil; +} + +#endif + +#endif +/********* End of inlined file: juce_mac_QuickTimeMovieComponent.mm *********/ + +/********* Start of inlined file: juce_mac_AudioCDBurner.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#if JUCE_INCLUDED_FILE && JUCE_USE_CDBURNER + +END_JUCE_NAMESPACE + +#define OpenDiskDevice MakeObjCClassName(OpenDiskDevice) + +@interface OpenDiskDevice : NSObject +{ + DRDevice* device; + + NSMutableArray* tracks; +} + +- (OpenDiskDevice*) initWithDevice: (DRDevice*) device; +- (void) dealloc; +- (bool) isDiskPresent; +- (int) getNumAvailableAudioBlocks; +- (void) addSourceTrack: (JUCE_NAMESPACE::AudioSource*) source numSamples: (int) numSamples_; +- (void) burn: (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener*) listener errorString: (JUCE_NAMESPACE::String*) error + ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting; +@end + +#define AudioTrackProducer MakeObjCClassName(AudioTrackProducer) + +@interface AudioTrackProducer : NSObject +{ + JUCE_NAMESPACE::AudioSource* source; + int readPosition, lengthInFrames; +} + +- (AudioTrackProducer*) init: (int) lengthInFrames; +- (AudioTrackProducer*) initWithAudioSource: (JUCE_NAMESPACE::AudioSource*) source numSamples: (int) lengthInSamples; +- (void) dealloc; +- (void) setupTrackProperties: (DRTrack*) track; + +- (void) cleanupTrackAfterBurn: (DRTrack*) track; +- (BOOL) cleanupTrackAfterVerification:(DRTrack*)track; +- (uint64_t) estimateLengthOfTrack:(DRTrack*)track; +- (BOOL) prepareTrack:(DRTrack*)track forBurn:(DRBurn*)burn + toMedia:(NSDictionary*)mediaInfo; +- (BOOL) prepareTrackForVerification:(DRTrack*)track; +- (uint32_t) produceDataForTrack:(DRTrack*)track intoBuffer:(char*)buffer + length:(uint32_t)bufferLength atAddress:(uint64_t)address + blockSize:(uint32_t)blockSize ioFlags:(uint32_t*)flags; +- (uint32_t) producePreGapForTrack:(DRTrack*)track + intoBuffer:(char*)buffer length:(uint32_t)bufferLength + atAddress:(uint64_t)address blockSize:(uint32_t)blockSize + ioFlags:(uint32_t*)flags; +- (BOOL) verifyDataForTrack:(DRTrack*)track inBuffer:(const char*)buffer + length:(uint32_t)bufferLength atAddress:(uint64_t)address + blockSize:(uint32_t)blockSize ioFlags:(uint32_t*)flags; +- (uint32_t) producePreGapForTrack:(DRTrack*)track + intoBuffer:(char*)buffer length:(uint32_t)bufferLength + atAddress:(uint64_t)address blockSize:(uint32_t)blockSize + ioFlags:(uint32_t*)flags; +@end + +@implementation OpenDiskDevice + +- (OpenDiskDevice*) initWithDevice: (DRDevice*) device_ +{ + [super init]; + + device = device_; + tracks = [[NSMutableArray alloc] init]; + return self; +} + +- (void) dealloc +{ + [tracks release]; + [super dealloc]; +} + +- (bool) isDiskPresent +{ + return [device isValid] + && [[[device status] objectForKey: DRDeviceMediaStateKey] + isEqualTo: DRDeviceMediaStateMediaPresent]; +} + +- (int) getNumAvailableAudioBlocks +{ + return [[[[device status] objectForKey: DRDeviceMediaInfoKey] + objectForKey: DRDeviceMediaBlocksFreeKey] intValue]; +} + +- (void) addSourceTrack: (JUCE_NAMESPACE::AudioSource*) source_ numSamples: (int) numSamples_ +{ + AudioTrackProducer* p = [[AudioTrackProducer alloc] initWithAudioSource: source_ numSamples: numSamples_]; + DRTrack* t = [[DRTrack alloc] initWithProducer: p]; + [p setupTrackProperties: t]; + + [tracks addObject: t]; + + [t release]; + [p release]; +} + +- (void) burn: (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener*) listener errorString: (JUCE_NAMESPACE::String*) error + ejectAfterwards: (bool) shouldEject isFake: (bool) peformFakeBurnForTesting +{ + DRBurn* burn = [DRBurn burnForDevice: device]; + + if (! [device acquireExclusiveAccess]) + { + *error = "Couldn't open or write to the CD device"; + return; + } + + [device acquireMediaReservation]; + + NSMutableDictionary* d = [[burn properties] mutableCopy]; + [d autorelease]; + [d setObject: [NSNumber numberWithBool: peformFakeBurnForTesting] forKey: DRBurnTestingKey]; + [d setObject: [NSNumber numberWithBool: false] forKey: DRBurnVerifyDiscKey]; + [d setObject: (shouldEject ? DRBurnCompletionActionEject : DRBurnCompletionActionMount) + forKey: DRBurnCompletionActionKey]; + [burn setProperties: d]; + + [burn writeLayout: tracks]; + + for (;;) + { + JUCE_NAMESPACE::Thread::sleep (300); + float progress = [[[burn status] objectForKey: DRStatusPercentCompleteKey] floatValue]; + + if (listener != 0 && listener->audioCDBurnProgress (progress)) + { + [burn abort]; + *error = "User cancelled the write operation"; + break; + } + + if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateFailed]) + { + *error = "Write operation failed"; + break; + } + else if ([[[burn status] objectForKey: DRStatusStateKey] isEqualTo: DRStatusStateDone]) + { + break; + } + + NSString* err = (NSString*) [[[burn status] objectForKey: DRErrorStatusKey] + objectForKey: DRErrorStatusErrorStringKey]; + + if ([err length] > 0) + { + *error = JUCE_NAMESPACE::String::fromUTF8 ((JUCE_NAMESPACE::uint8*) [err UTF8String]); + break; + } + } + + [device releaseMediaReservation]; + [device releaseExclusiveAccess]; +} +@end + +@implementation AudioTrackProducer + +- (AudioTrackProducer*) init: (int) lengthInFrames_ +{ + lengthInFrames = lengthInFrames_; + readPosition = 0; + return self; +} + +- (void) setupTrackProperties: (DRTrack*) track +{ + NSMutableDictionary* p = [[track properties] mutableCopy]; + [p setObject:[DRMSF msfWithFrames: lengthInFrames] forKey: DRTrackLengthKey]; + [p setObject:[NSNumber numberWithUnsignedShort:2352] forKey: DRBlockSizeKey]; + [p setObject:[NSNumber numberWithInt:0] forKey: DRDataFormKey]; + [p setObject:[NSNumber numberWithInt:0] forKey: DRBlockTypeKey]; + [p setObject:[NSNumber numberWithInt:0] forKey: DRTrackModeKey]; + [p setObject:[NSNumber numberWithInt:0] forKey: DRSessionFormatKey]; + + [track setProperties: p]; + [p release]; +} + +- (AudioTrackProducer*) initWithAudioSource: (JUCE_NAMESPACE::AudioSource*) source_ numSamples: (int) lengthInSamples +{ + AudioTrackProducer* s = [self init: (lengthInSamples + 587) / 588]; + + if (s != nil) + s->source = source_; + + return s; +} + +- (void) dealloc +{ + if (source != 0) + { + source->releaseResources(); + delete source; + } + + [super dealloc]; +} + +- (void) cleanupTrackAfterBurn: (DRTrack*) track +{ +} + +- (BOOL) cleanupTrackAfterVerification: (DRTrack*) track +{ + return true; +} + +- (uint64_t) estimateLengthOfTrack: (DRTrack*) track +{ + return lengthInFrames; +} + +- (BOOL) prepareTrack: (DRTrack*) track forBurn: (DRBurn*) burn + toMedia: (NSDictionary*) mediaInfo +{ + if (source != 0) + source->prepareToPlay (44100 / 75, 44100); + + readPosition = 0; + return true; +} + +- (BOOL) prepareTrackForVerification: (DRTrack*) track +{ + if (source != 0) + source->prepareToPlay (44100 / 75, 44100); + + return true; +} + +- (uint32_t) produceDataForTrack: (DRTrack*) track intoBuffer: (char*) buffer + length: (uint32_t) bufferLength atAddress: (uint64_t) address + blockSize: (uint32_t) blockSize ioFlags: (uint32_t*) flags +{ + if (source != 0) + { + const int numSamples = JUCE_NAMESPACE::jmin (bufferLength / 4, (lengthInFrames * (44100 / 75)) - readPosition); + + if (numSamples > 0) + { + JUCE_NAMESPACE::AudioSampleBuffer tempBuffer (2, numSamples); + + JUCE_NAMESPACE::AudioSourceChannelInfo info; + info.buffer = &tempBuffer; + info.startSample = 0; + info.numSamples = numSamples; + + source->getNextAudioBlock (info); + + JUCE_NAMESPACE::AudioDataConverters::convertFloatToInt16LE (tempBuffer.getSampleData (0), + buffer, numSamples, 4); + JUCE_NAMESPACE::AudioDataConverters::convertFloatToInt16LE (tempBuffer.getSampleData (1), + buffer + 2, numSamples, 4); + + readPosition += numSamples; + } + + return numSamples * 4; + } + + return 0; +} + +- (uint32_t) producePreGapForTrack: (DRTrack*) track + intoBuffer: (char*) buffer length: (uint32_t) bufferLength + atAddress: (uint64_t) address blockSize: (uint32_t) blockSize + ioFlags: (uint32_t*) flags +{ + zeromem (buffer, bufferLength); + return bufferLength; +} + +- (BOOL) verifyDataForTrack: (DRTrack*) track inBuffer: (const char*) buffer + length: (uint32_t) bufferLength atAddress: (uint64_t) address + blockSize: (uint32_t) blockSize ioFlags: (uint32_t*) flags +{ + return true; +} + +@end + +BEGIN_JUCE_NAMESPACE + +AudioCDBurner::AudioCDBurner (const int deviceIndex) + : internal (0) +{ + OpenDiskDevice* dev = [[OpenDiskDevice alloc] initWithDevice: [[DRDevice devices] objectAtIndex: deviceIndex]]; + + internal = (void*) dev; +} + +AudioCDBurner::~AudioCDBurner() +{ + OpenDiskDevice* dev = (OpenDiskDevice*) internal; + + if (dev != 0) + [dev release]; +} + +AudioCDBurner* AudioCDBurner::openDevice (const int deviceIndex) +{ + AudioCDBurner* b = new AudioCDBurner (deviceIndex); + + if (b->internal == 0) + deleteAndZero (b); + + return b; +} + +static NSArray* findDiskBurnerDevices() +{ + NSMutableArray* results = [NSMutableArray array]; + NSArray* devs = [DRDevice devices]; + + if (devs != 0) + { + int num = [devs count]; + int i; + for (i = 0; i < num; ++i) + { + NSDictionary* dic = [[devs objectAtIndex: i] info]; + NSString* name = [dic valueForKey: DRDeviceProductNameKey]; + if (name != nil) + [results addObject: name]; + } + } + + return results; +} + +const StringArray AudioCDBurner::findAvailableDevices() +{ + NSArray* names = findDiskBurnerDevices(); + StringArray s; + + for (unsigned int i = 0; i < [names count]; ++i) + s.add (String::fromUTF8 ((JUCE_NAMESPACE::uint8*) [[names objectAtIndex: i] UTF8String])); + + return s; +} + +bool AudioCDBurner::isDiskPresent() const +{ + OpenDiskDevice* dev = (OpenDiskDevice*) internal; + + return dev != 0 && [dev isDiskPresent]; +} + +int AudioCDBurner::getNumAvailableAudioBlocks() const +{ + OpenDiskDevice* dev = (OpenDiskDevice*) internal; + + return [dev getNumAvailableAudioBlocks]; +} + +bool AudioCDBurner::addAudioTrack (AudioSource* source, int numSamps) +{ + OpenDiskDevice* dev = (OpenDiskDevice*) internal; + + if (dev != 0) + { + [dev addSourceTrack: source numSamples: numSamps]; + return true; + } + + return false; +} + +const String AudioCDBurner::burn (JUCE_NAMESPACE::AudioCDBurner::BurnProgressListener* listener, + const bool ejectDiscAfterwards, + const bool peformFakeBurnForTesting) +{ + String error ("Couldn't open or write to the CD device"); + + OpenDiskDevice* dev = (OpenDiskDevice*) internal; + + if (dev != 0) + { + error = String::empty; + [dev burn: listener + errorString: &error + ejectAfterwards: ejectDiscAfterwards + isFake: peformFakeBurnForTesting]; + } + + return error; +} + +void AudioCDReader::ejectDisk() +{ + const ScopedAutoReleasePool p; + [[NSWorkspace sharedWorkspace] unmountAndEjectDeviceAtPath: juceStringToNS (volumeDir.getFullPathName())]; +} + +#endif +/********* End of inlined file: juce_mac_AudioCDBurner.mm *********/ + +/********* Start of inlined file: juce_mac_Fonts.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +class FontHelper +{ + NSFont* font; + +public: + String name; + bool isBold, isItalic, needsItalicTransform; + float fontSize, totalSize, ascent; + int refCount; + NSMutableDictionary* attributes; + + FontHelper (const String& name_, + const bool bold_, + const bool italic_, + const float size_) + : font (0), + name (name_), + isBold (bold_), + isItalic (italic_), + needsItalicTransform (false), + fontSize (size_), + refCount (1) + { + attributes = [[NSMutableDictionary dictionaryWithObject: [NSNumber numberWithInt: 0] + forKey: NSLigatureAttributeName] retain]; + + font = [NSFont fontWithName: juceStringToNS (name_) size: size_]; + + if (italic_) + { + NSFont* newFont = [[NSFontManager sharedFontManager] convertFont: font toHaveTrait: NSItalicFontMask]; + + if (newFont == font) + needsItalicTransform = true; // couldn't find a proper italic version, so fake it with a transform.. + + font = newFont; + } + + if (bold_) + font = [[NSFontManager sharedFontManager] convertFont: font toHaveTrait: NSBoldFontMask]; + + [font retain]; + + ascent = fabsf ([font ascender]); + totalSize = ascent + fabsf ([font descender]); + } + + ~FontHelper() + { + [font release]; + [attributes release]; + } + + bool getPathAndKerning (const juce_wchar char1, + const juce_wchar char2, + Path* path, + float& kerning, + float* ascent, + float* descent) + { + const ScopedAutoReleasePool pool; + + if (font == 0 + || ! [[font coveredCharacterSet] longCharacterIsMember: (UTF32Char) char1]) + return false; + + String chars; + chars << ' ' << char1 << char2; + + NSTextStorage* textStorage = [[[NSTextStorage alloc] initWithString: juceStringToNS (chars) + attributes: attributes] autorelease]; + NSLayoutManager* layoutManager = [[[NSLayoutManager alloc] init] autorelease]; + NSTextContainer* textContainer = [[[NSTextContainer alloc] init] autorelease]; + [layoutManager addTextContainer: textContainer]; + [textStorage addLayoutManager: layoutManager]; + [textStorage setFont: font]; + + unsigned int glyphIndex = [layoutManager glyphRangeForCharacterRange: NSMakeRange (1, 1) + actualCharacterRange: 0].location; + NSPoint p1 = [layoutManager locationForGlyphAtIndex: glyphIndex]; + NSPoint p2 = [layoutManager locationForGlyphAtIndex: glyphIndex + 1]; + kerning = p2.x - p1.x; + + if (ascent != 0) + *ascent = this->ascent; + + if (descent != 0) + *descent = fabsf ([font descender]); + + if (path != 0) + { + NSBezierPath* bez = [NSBezierPath bezierPath]; + [bez moveToPoint: NSMakePoint (0, 0)]; + [bez appendBezierPathWithGlyph: [layoutManager glyphAtIndex: glyphIndex] + inFont: font]; + + for (int i = 0; i < [bez elementCount]; ++i) + { + NSPoint p[3]; + switch ([bez elementAtIndex: i associatedPoints: p]) + { + case NSMoveToBezierPathElement: + path->startNewSubPath (p[0].x, -p[0].y); + break; + case NSLineToBezierPathElement: + path->lineTo (p[0].x, -p[0].y); + break; + case NSCurveToBezierPathElement: + path->cubicTo (p[0].x, -p[0].y, p[1].x, -p[1].y, p[2].x, -p[2].y); + break; + case NSClosePathBezierPathElement: + path->closeSubPath(); + break; + default: + jassertfalse + break; + } + } + + if (needsItalicTransform) + path->applyTransform (AffineTransform::identity.sheared (-0.15, 0)); + } + + return kerning != 0; + } + + juce_wchar getDefaultChar() + { + return 0; + } +}; + +class FontHelperCache : public Timer, + public DeletedAtShutdown +{ + VoidArray cache; + +public: + FontHelperCache() + { + } + + ~FontHelperCache() + { + for (int i = cache.size(); --i >= 0;) + { + FontHelper* const f = (FontHelper*) cache.getUnchecked(i); + delete f; + } + + clearSingletonInstance(); + } + + FontHelper* getFont (const String& name, + const bool bold, + const bool italic, + const float size = 1024) + { + for (int i = cache.size(); --i >= 0;) + { + FontHelper* const f = (FontHelper*) cache.getUnchecked(i); + + if (f->name == name + && f->isBold == bold + && f->isItalic == italic + && f->fontSize == size) + { + f->refCount++; + return f; + } + } + + FontHelper* const f = new FontHelper (name, bold, italic, size); + cache.add (f); + return f; + } + + void releaseFont (FontHelper* f) + { + for (int i = cache.size(); --i >= 0;) + { + FontHelper* const f2 = (FontHelper*) cache.getUnchecked(i); + + if (f == f2) + { + f->refCount--; + + if (f->refCount == 0) + startTimer (5000); + + break; + } + } + } + + void timerCallback() + { + stopTimer(); + + for (int i = cache.size(); --i >= 0;) + { + FontHelper* const f = (FontHelper*) cache.getUnchecked(i); + + if (f->refCount == 0) + { + cache.remove (i); + delete f; + } + } + + if (cache.size() == 0) + delete this; + } + + juce_DeclareSingleton_SingleThreaded_Minimal (FontHelperCache) +}; + +juce_ImplementSingleton_SingleThreaded (FontHelperCache) + +void Typeface::initialiseTypefaceCharacteristics (const String& fontName, + bool bold, + bool italic, + bool addAllGlyphsToFont) throw() +{ + // This method is only safe to be called from the normal UI thread.. + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + + FontHelper* const helper = FontHelperCache::getInstance() + ->getFont (fontName, bold, italic); + + clear(); + setAscent (helper->ascent / helper->totalSize); + setName (fontName); + setDefaultCharacter (helper->getDefaultChar()); + setBold (bold); + setItalic (italic); + + if (addAllGlyphsToFont) + { + //xxx + jassertfalse + } + + FontHelperCache::getInstance()->releaseFont (helper); +} + +bool Typeface::findAndAddSystemGlyph (juce_wchar character) throw() +{ + // This method is only safe to be called from the normal UI thread.. + jassert (MessageManager::getInstance()->isThisTheMessageThread()); + + if (character == 0) + return false; + + FontHelper* const helper = FontHelperCache::getInstance() + ->getFont (getName(), isBold(), isItalic()); + + Path path; + float width; + bool foundOne = false; + + if (helper->getPathAndKerning (character, T('I'), &path, width, 0, 0)) + { + path.applyTransform (AffineTransform::scale (1.0f / helper->totalSize, + 1.0f / helper->totalSize)); + + addGlyph (character, path, width / helper->totalSize); + + for (int i = 0; i < glyphs.size(); ++i) + { + const TypefaceGlyphInfo* const g = (const TypefaceGlyphInfo*) glyphs.getUnchecked(i); + + float kerning; + if (helper->getPathAndKerning (character, g->getCharacter(), 0, kerning, 0, 0)) + { + kerning = (kerning - width) / helper->totalSize; + + if (kerning != 0) + addKerningPair (character, g->getCharacter(), kerning); + } + + if (helper->getPathAndKerning (g->getCharacter(), character, 0, kerning, 0, 0)) + { + kerning = kerning / helper->totalSize - g->width; + + if (kerning != 0) + addKerningPair (g->getCharacter(), character, kerning); + } + } + + foundOne = true; + } + + FontHelperCache::getInstance()->releaseFont (helper); + return foundOne; +} + +const StringArray Font::findAllTypefaceNames() throw() +{ + StringArray names; + + const ScopedAutoReleasePool pool; + NSArray* fonts = [[NSFontManager sharedFontManager] availableFontFamilies]; + + for (unsigned int i = 0; i < [fonts count]; ++i) + names.add (nsStringToJuce ((NSString*) [fonts objectAtIndex: i])); + + names.sort (true); + return names; +} + +void Typeface::getDefaultFontNames (String& defaultSans, String& defaultSerif, String& defaultFixed) throw() +{ + defaultSans = "Lucida Grande"; + defaultSerif = "Times New Roman"; + defaultFixed = "Monaco"; +} + +#endif +/********* End of inlined file: juce_mac_Fonts.mm *********/ + +/********* Start of inlined file: juce_mac_MessageManager.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +struct CallbackMessagePayload +{ + MessageCallbackFunction* function; + void* parameter; + void* volatile result; + bool volatile hasBeenExecuted; +}; + +/* When you use multiple DLLs which share similarly-named obj-c classes - like + for example having more than one juce plugin loaded into a host, then when a + method is called, the actual code that runs might actually be in a different module + than the one you expect... So any calls to library functions or statics that are + made inside obj-c methods will probably end up getting executed in a different DLL's + memory space. Not a great thing to happen - this obviously leads to bizarre crashes. + + To work around this insanity, I'm only allowing obj-c methods to make calls to + virtual methods of an object that's known to live inside the right module's space. +*/ +class AppDelegateRedirector +{ +public: + AppDelegateRedirector() {} + virtual ~AppDelegateRedirector() {} + + virtual NSApplicationTerminateReply shouldTerminate() + { + if (JUCEApplication::getInstance() != 0) + { + JUCEApplication::getInstance()->systemRequestedQuit(); + return NSTerminateCancel; + } + + return NSTerminateNow; + } + + virtual BOOL openFile (const NSString* filename) + { + if (JUCEApplication::getInstance() != 0) + { + JUCEApplication::getInstance()->anotherInstanceStarted (nsStringToJuce (filename)); + return YES; + } + + return NO; + } + + virtual void openFiles (NSArray* filenames) + { + StringArray files; + for (unsigned int i = 0; i < [filenames count]; ++i) + files.add (nsStringToJuce ((NSString*) [filenames objectAtIndex: i])); + + if (files.size() > 0 && JUCEApplication::getInstance() != 0) + { + JUCEApplication::getInstance()->anotherInstanceStarted (files.joinIntoString (T(" "))); + } + } + + virtual void focusChanged() + { + juce_HandleProcessFocusChange(); + } + + virtual void deliverMessage (void* message) + { + // no need for an mm lock here - deliverMessage locks it + MessageManager::getInstance()->deliverMessage (message); + } + + virtual void performCallback (CallbackMessagePayload* pl) + { + pl->result = (*pl->function) (pl->parameter); + pl->hasBeenExecuted = true; + } + + virtual void deleteSelf() + { + delete this; + } +}; + +END_JUCE_NAMESPACE +using namespace JUCE_NAMESPACE; + +#define JuceAppDelegate MakeObjCClassName(JuceAppDelegate) + +static int numPendingMessages = 0; + +@interface JuceAppDelegate : NSObject +{ +@private + id oldDelegate; + AppDelegateRedirector* redirector; + +@public + bool flushingMessages; +} + +- (JuceAppDelegate*) init; +- (void) dealloc; +- (BOOL) application: (NSApplication*) theApplication openFile: (NSString*) filename; +- (void) application: (NSApplication*) sender openFiles: (NSArray*) filenames; +- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication*) app; +- (void) applicationDidBecomeActive: (NSNotification*) aNotification; +- (void) applicationDidResignActive: (NSNotification*) aNotification; +- (void) applicationWillUnhide: (NSNotification*) aNotification; +- (void) customEvent: (id) data; +- (void) performCallback: (id) info; +- (void) dummyMethod; +@end + +@implementation JuceAppDelegate + +- (JuceAppDelegate*) init +{ + [super init]; + + redirector = new AppDelegateRedirector(); + numPendingMessages = 0; + flushingMessages = false; + + NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; + + if (JUCEApplication::getInstance() != 0) + { + oldDelegate = [NSApp delegate]; + [NSApp setDelegate: self]; + } + else + { + oldDelegate = 0; + [center addObserver: self selector: @selector (applicationDidResignActive:) + name: NSApplicationDidResignActiveNotification object: NSApp]; + + [center addObserver: self selector: @selector (applicationDidBecomeActive:) + name: NSApplicationDidBecomeActiveNotification object: NSApp]; + + [center addObserver: self selector: @selector (applicationWillUnhide:) + name: NSApplicationWillUnhideNotification object: NSApp]; + } + + return self; +} + +- (void) dealloc +{ + if (oldDelegate != 0) + [NSApp setDelegate: oldDelegate]; + + redirector->deleteSelf(); + [super dealloc]; +} + +- (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication*) app +{ + return redirector->shouldTerminate(); +} + +- (BOOL) application: (NSApplication*) app openFile: (NSString*) filename +{ + return redirector->openFile (filename); +} + +- (void) application: (NSApplication*) sender openFiles: (NSArray*) filenames +{ + return redirector->openFiles (filenames); +} + +- (void) applicationDidBecomeActive: (NSNotification*) aNotification +{ + redirector->focusChanged(); +} + +- (void) applicationDidResignActive: (NSNotification*) aNotification +{ + redirector->focusChanged(); +} + +- (void) applicationWillUnhide: (NSNotification*) aNotification +{ + redirector->focusChanged(); +} + +- (void) customEvent: (id) n +{ + atomicDecrement (numPendingMessages); + + NSData* data = (NSData*) n; + void* message = 0; + [data getBytes: &message length: sizeof (message)]; + [data release]; + + if (message != 0 && ! flushingMessages) + redirector->deliverMessage (message); +} + +- (void) performCallback: (id) info +{ + if ([info isKindOfClass: [NSData class]]) + { + CallbackMessagePayload* pl = (CallbackMessagePayload*) [((NSData*) info) bytes]; + + if (pl != 0) + redirector->performCallback (pl); + } + else + { + jassertfalse // should never get here! + } +} + +- (void) dummyMethod {} // (used as a way of running a dummy thread) + +@end + +BEGIN_JUCE_NAMESPACE + +static JuceAppDelegate* juceAppDelegate = 0; + +void MessageManager::runDispatchLoop() +{ + if (! quitMessagePosted) // check that the quit message wasn't already posted.. + { + const ScopedAutoReleasePool pool; + + // must only be called by the message thread! + jassert (isThisTheMessageThread()); + + [NSApp run]; + } +} + +void MessageManager::stopDispatchLoop() +{ + quitMessagePosted = true; + [NSApp stop: nil]; + [NSApp activateIgnoringOtherApps: YES]; // (if the app is inactive, it sits there and ignores the quit request until the next time it gets activated) + [NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1]; +} + +static bool isEventBlockedByModalComps (NSEvent* e) +{ + if (Component::getNumCurrentlyModalComponents() == 0) + return false; + + NSWindow* const w = [e window]; + if (w == 0 || [w worksWhenModal]) + return false; + + bool isKey = false, isInputAttempt = false; + + switch ([e type]) + { + case NSKeyDown: + case NSKeyUp: + isKey = isInputAttempt = true; + break; + + case NSLeftMouseDown: + case NSRightMouseDown: + case NSOtherMouseDown: + isInputAttempt = true; + break; + + case NSLeftMouseDragged: + case NSRightMouseDragged: + case NSLeftMouseUp: + case NSRightMouseUp: + case NSOtherMouseUp: + case NSOtherMouseDragged: + if (Component::getComponentUnderMouse() != 0) + return false; + break; + + case NSMouseMoved: + case NSMouseEntered: + case NSMouseExited: + case NSCursorUpdate: + case NSScrollWheel: + case NSTabletPoint: + case NSTabletProximity: + break; + + default: + return false; + } + + for (int i = ComponentPeer::getNumPeers(); --i >= 0;) + { + ComponentPeer* const peer = ComponentPeer::getPeer (i); + NSView* const compView = (NSView*) peer->getNativeHandle(); + + if ([compView window] == w) + { + if (isKey) + { + if (compView == [w firstResponder]) + return false; + } + else + { + if (NSPointInRect ([compView convertPoint: [e locationInWindow] fromView: nil], + [compView bounds])) + return false; + } + } + } + + if (isInputAttempt) + { + if (! [NSApp isActive]) + [NSApp activateIgnoringOtherApps: YES]; + + Component* const modal = Component::getCurrentlyModalComponent (0); + if (modal != 0) + modal->inputAttemptWhenModal(); + } + + return true; +} + +bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor) +{ + const ScopedAutoReleasePool pool; + jassert (isThisTheMessageThread()); // must only be called by the message thread + + uint32 endTime = Time::getMillisecondCounter() + millisecondsToRunFor; + NSDate* endDate = [NSDate dateWithTimeIntervalSinceNow: millisecondsToRunFor * 0.001]; + + while (! quitMessagePosted) + { + const ScopedAutoReleasePool pool; + + [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode + beforeDate: endDate]; + + NSEvent* e = [NSApp nextEventMatchingMask: NSAnyEventMask + untilDate: endDate + inMode: NSDefaultRunLoopMode + dequeue: YES]; + + if (e != 0 && ! isEventBlockedByModalComps (e)) + [NSApp sendEvent: e]; + + if (Time::getMillisecondCounter() >= endTime) + break; + } + + return ! quitMessagePosted; +} + +void MessageManager::doPlatformSpecificInitialisation() +{ + if (juceAppDelegate == 0) + juceAppDelegate = [[JuceAppDelegate alloc] init]; + + // This launches a dummy thread, which forces Cocoa to initialise NSThreads + // correctly (needed prior to 10.5) + if (! [NSThread isMultiThreaded]) + [NSThread detachNewThreadSelector: @selector (dummyMethod) + toTarget: juceAppDelegate + withObject: nil]; + + initialiseMainMenu(); +} + +void MessageManager::doPlatformSpecificShutdown() +{ + if (juceAppDelegate != 0) + { + [[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: juceAppDelegate]; + [[NSNotificationCenter defaultCenter] removeObserver: juceAppDelegate]; + + // Annoyingly, cancelPerformSelectorsWithTarget can't actually cancel the messages + // sent by performSelectorOnMainThread, so need to manually flush these before quitting.. + juceAppDelegate->flushingMessages = true; + + for (int i = 100; --i >= 0 && numPendingMessages > 0;) + { + const ScopedAutoReleasePool pool; + [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode + beforeDate: [NSDate dateWithTimeIntervalSinceNow: 5 * 0.001]]; + } + + [juceAppDelegate release]; + juceAppDelegate = 0; + } +} + +bool juce_postMessageToSystemQueue (void* message) +{ + atomicIncrement (numPendingMessages); + + [juceAppDelegate performSelectorOnMainThread: @selector (customEvent:) + withObject: (id) [[NSData alloc] initWithBytes: &message length: (int) sizeof (message)] + waitUntilDone: NO]; + return true; +} + +void MessageManager::broadcastMessage (const String& value) throw() +{ +} + +void* MessageManager::callFunctionOnMessageThread (MessageCallbackFunction* callback, + void* data) +{ + if (isThisTheMessageThread()) + { + return (*callback) (data); + } + else + { + // If a thread has a MessageManagerLock and then tries to call this method, it'll + // deadlock because the message manager is blocked from running, so can never + // call your function.. + jassert (! MessageManager::getInstance()->currentThreadHasLockedMessageManager()); + + const ScopedAutoReleasePool pool; + + CallbackMessagePayload cmp; + cmp.function = callback; + cmp.parameter = data; + cmp.result = 0; + cmp.hasBeenExecuted = false; + + [juceAppDelegate performSelectorOnMainThread: @selector (performCallback:) + withObject: [NSData dataWithBytesNoCopy: &cmp + length: sizeof (cmp) + freeWhenDone: NO] + waitUntilDone: YES]; + + return cmp.result; + } +} + +#endif +/********* End of inlined file: juce_mac_MessageManager.mm *********/ + +/********* Start of inlined file: juce_mac_WebBrowserComponent.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#if JUCE_INCLUDED_FILE && JUCE_WEB_BROWSER + +END_JUCE_NAMESPACE + +#define DownloadClickDetector MakeObjCClassName(DownloadClickDetector) + +@interface DownloadClickDetector : NSObject +{ + JUCE_NAMESPACE::WebBrowserComponent* ownerComponent; +} + +- (DownloadClickDetector*) initWithWebBrowserOwner: (JUCE_NAMESPACE::WebBrowserComponent*) ownerComponent; + +- (void) webView: (WebView*) webView decidePolicyForNavigationAction: (NSDictionary*) actionInformation + request: (NSURLRequest*) request + frame: (WebFrame*) frame + decisionListener: (id) listener; +@end + +@implementation DownloadClickDetector + +- (DownloadClickDetector*) initWithWebBrowserOwner: (JUCE_NAMESPACE::WebBrowserComponent*) ownerComponent_ +{ + [super init]; + ownerComponent = ownerComponent_; + return self; +} + +- (void) webView: (WebView*) sender decidePolicyForNavigationAction: (NSDictionary*) actionInformation + request: (NSURLRequest*) request + frame: (WebFrame*) frame + decisionListener: (id ) listener +{ + NSURL* url = [actionInformation valueForKey: @"WebActionOriginalURLKey"]; + + if (ownerComponent->pageAboutToLoad (nsStringToJuce ([url absoluteString]))) + [listener use]; + else + [listener ignore]; +} + +@end + +BEGIN_JUCE_NAMESPACE + +class WebBrowserComponentInternal : public NSViewComponent +{ +public: + WebBrowserComponentInternal (WebBrowserComponent* owner) + { + webView = [[WebView alloc] initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f) + frameName: @"" + groupName: @""]; + setView (webView); + + clickListener = [[DownloadClickDetector alloc] initWithWebBrowserOwner: owner]; + [webView setPolicyDelegate: clickListener]; + } + + ~WebBrowserComponentInternal() + { + [webView setPolicyDelegate: nil]; + [clickListener release]; + setView (0); + } + + void goToURL (const String& url, + const StringArray* headers, + const MemoryBlock* postData) + { + NSMutableURLRequest* r + = [NSMutableURLRequest requestWithURL: [NSURL URLWithString: juceStringToNS (url)] + cachePolicy: NSURLRequestUseProtocolCachePolicy + timeoutInterval: 30.0]; + + if (postData != 0 && postData->getSize() > 0) + { + [r setHTTPMethod: @"POST"]; + [r setHTTPBody: [NSData dataWithBytes: postData->getData() + length: postData->getSize()]]; + } + + if (headers != 0) + { + for (int i = 0; i < headers->size(); ++i) + { + const String headerName ((*headers)[i].upToFirstOccurrenceOf (T(":"), false, false).trim()); + const String headerValue ((*headers)[i].fromFirstOccurrenceOf (T(":"), false, false).trim()); + + [r setValue: juceStringToNS (headerValue) + forHTTPHeaderField: juceStringToNS (headerName)]; + } + } + + stop(); + [[webView mainFrame] loadRequest: r]; + } + + void goBack() + { + [webView goBack]; + } + + void goForward() + { + [webView goForward]; + } + + void stop() + { + [webView stopLoading: nil]; + } + + void refresh() + { + [webView reload: nil]; + } + +private: + WebView* webView; + DownloadClickDetector* clickListener; +}; + +WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_) + : browser (0), + blankPageShown (false), + unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_) +{ + setOpaque (true); + + addAndMakeVisible (browser = new WebBrowserComponentInternal (this)); +} + +WebBrowserComponent::~WebBrowserComponent() +{ + deleteAndZero (browser); +} + +void WebBrowserComponent::goToURL (const String& url, + const StringArray* headers, + const MemoryBlock* postData) +{ + lastURL = url; + + lastHeaders.clear(); + if (headers != 0) + lastHeaders = *headers; + + lastPostData.setSize (0); + if (postData != 0) + lastPostData = *postData; + + blankPageShown = false; + + browser->goToURL (url, headers, postData); +} + +void WebBrowserComponent::stop() +{ + browser->stop(); +} + +void WebBrowserComponent::goBack() +{ + lastURL = String::empty; + blankPageShown = false; + browser->goBack(); +} + +void WebBrowserComponent::goForward() +{ + lastURL = String::empty; + browser->goForward(); +} + +void WebBrowserComponent::refresh() +{ + browser->refresh(); +} + +void WebBrowserComponent::paint (Graphics& g) +{ +} + +void WebBrowserComponent::checkWindowAssociation() +{ + if (isShowing()) + { + if (blankPageShown) + goBack(); + } + else + { + if (unloadPageWhenBrowserIsHidden && ! blankPageShown) + { + // when the component becomes invisible, some stuff like flash + // carries on playing audio, so we need to force it onto a blank + // page to avoid this, (and send it back when it's made visible again). + + blankPageShown = true; + browser->goToURL ("about:blank", 0, 0); + } + } +} + +void WebBrowserComponent::reloadLastURL() +{ + if (lastURL.isNotEmpty()) + { + goToURL (lastURL, &lastHeaders, &lastPostData); + lastURL = String::empty; + } +} + +void WebBrowserComponent::parentHierarchyChanged() +{ + checkWindowAssociation(); +} + +void WebBrowserComponent::resized() +{ + browser->setSize (getWidth(), getHeight()); +} + +void WebBrowserComponent::visibilityChanged() +{ + checkWindowAssociation(); +} + +bool WebBrowserComponent::pageAboutToLoad (const String& url) +{ + return true; +} + +#endif +/********* End of inlined file: juce_mac_WebBrowserComponent.mm *********/ + +/********* Start of inlined file: juce_mac_CoreAudio.cpp *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +#ifndef JUCE_COREAUDIO_ERROR_LOGGING_ENABLED + #define JUCE_COREAUDIO_ERROR_LOGGING_ENABLED 1 +#endif + +#undef log +#if JUCE_COREAUDIO_LOGGING_ENABLED + #define log(a) Logger::writeToLog (a) +#else + #define log(a) +#endif + +#undef OK +#if JUCE_COREAUDIO_ERROR_LOGGING_ENABLED + static bool logAnyErrors_CoreAudio (const OSStatus err, const int lineNum) + { + if (err == noErr) + return true; + + Logger::writeToLog (T("CoreAudio error: ") + String (lineNum) + T(" - ") + String::toHexString ((int)err)); + jassertfalse + return false; + } + + #define OK(a) logAnyErrors_CoreAudio (a, __LINE__) +#else + #define OK(a) (a == noErr) +#endif + +class CoreAudioInternal : public Timer +{ +public: + + CoreAudioInternal (AudioDeviceID id) + : inputLatency (0), + outputLatency (0), + callback (0), +#if ! MACOS_10_4_OR_EARLIER + audioProcID (0), +#endif + inputDevice (0), + isSlaveDevice (false), + deviceID (id), + started (false), + sampleRate (0), + bufferSize (512), + audioBuffer (0), + numInputChans (0), + numOutputChans (0), + callbacksAllowed (true), + numInputChannelInfos (0), + numOutputChannelInfos (0), + tempInputBuffers (0), + tempOutputBuffers (0), + inputChannelInfo (0), + outputChannelInfo (0) + { + jassert (deviceID != 0); + + updateDetailsFromDevice(); + + AudioObjectPropertyAddress pa; + pa.mSelector = kAudioObjectPropertySelectorWildcard; + pa.mScope = kAudioObjectPropertyScopeWildcard; + pa.mElement = kAudioObjectPropertyElementWildcard; + + AudioObjectAddPropertyListener (deviceID, &pa, deviceListenerProc, this); + } + + ~CoreAudioInternal() + { + AudioObjectPropertyAddress pa; + pa.mSelector = kAudioObjectPropertySelectorWildcard; + pa.mScope = kAudioObjectPropertyScopeWildcard; + pa.mElement = kAudioObjectPropertyElementWildcard; + + AudioObjectRemovePropertyListener (deviceID, &pa, deviceListenerProc, this); + + stop (false); + delete inputDevice; + + juce_free (audioBuffer); + juce_free (tempInputBuffers); + juce_free (tempOutputBuffers); + juce_free (inputChannelInfo); + juce_free (outputChannelInfo); + } + + void allocateTempBuffers() + { + const int tempBufSize = bufferSize + 4; + juce_free (audioBuffer); + audioBuffer = (float*) juce_calloc ((numInputChans + numOutputChans) * tempBufSize * sizeof (float)); + + juce_free (tempInputBuffers); + tempInputBuffers = (float**) juce_calloc (sizeof (float*) * (numInputChans + 2)); + juce_free (tempOutputBuffers); + tempOutputBuffers = (float**) juce_calloc (sizeof (float*) * (numOutputChans + 2)); + + int i, count = 0; + for (i = 0; i < numInputChans; ++i) + tempInputBuffers[i] = audioBuffer + count++ * tempBufSize; + + for (i = 0; i < numOutputChans; ++i) + tempOutputBuffers[i] = audioBuffer + count++ * tempBufSize; + } + + // returns the number of actual available channels + void fillInChannelInfo (const bool input) + { + int chanNum = 0; + UInt32 size; + + AudioObjectPropertyAddress pa; + pa.mSelector = kAudioDevicePropertyStreamConfiguration; + pa.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + pa.mElement = kAudioObjectPropertyElementMaster; + + if (OK (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, 0, &size))) + { + AudioBufferList* const bufList = (AudioBufferList*) juce_calloc (size); + + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, bufList))) + { + const int numStreams = bufList->mNumberBuffers; + + for (int i = 0; i < numStreams; ++i) + { + const AudioBuffer& b = bufList->mBuffers[i]; + + for (unsigned int j = 0; j < b.mNumberChannels; ++j) + { + String name; + + { + uint8 channelName [256]; + zerostruct (channelName); + UInt32 nameSize = sizeof (channelName); + UInt32 channelNum = chanNum + 1; + pa.mSelector = kAudioDevicePropertyChannelName; + + if (AudioObjectGetPropertyData (deviceID, &pa, sizeof (channelNum), &channelNum, &nameSize, channelName) == noErr) + name = String::fromUTF8 (channelName, nameSize); + } + + if (input) + { + if (activeInputChans[chanNum]) + { + inputChannelInfo [numInputChannelInfos].streamNum = i; + inputChannelInfo [numInputChannelInfos].dataOffsetSamples = j; + inputChannelInfo [numInputChannelInfos].dataStrideSamples = b.mNumberChannels; + ++numInputChannelInfos; + } + + if (name.isEmpty()) + name << "Input " << (chanNum + 1); + + inChanNames.add (name); + } + else + { + if (activeOutputChans[chanNum]) + { + outputChannelInfo [numOutputChannelInfos].streamNum = i; + outputChannelInfo [numOutputChannelInfos].dataOffsetSamples = j; + outputChannelInfo [numOutputChannelInfos].dataStrideSamples = b.mNumberChannels; + ++numOutputChannelInfos; + } + + if (name.isEmpty()) + name << "Output " << (chanNum + 1); + + outChanNames.add (name); + } + + ++chanNum; + } + } + } + + juce_free (bufList); + } + } + + void updateDetailsFromDevice() + { + stopTimer(); + + if (deviceID == 0) + return; + + const ScopedLock sl (callbackLock); + + Float64 sr; + UInt32 size = sizeof (Float64); + + AudioObjectPropertyAddress pa; + pa.mSelector = kAudioDevicePropertyNominalSampleRate; + pa.mScope = kAudioObjectPropertyScopeWildcard; + pa.mElement = kAudioObjectPropertyElementMaster; + + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, &sr))) + sampleRate = sr; + + UInt32 framesPerBuf; + size = sizeof (framesPerBuf); + + pa.mSelector = kAudioDevicePropertyBufferFrameSize; + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, &framesPerBuf))) + { + bufferSize = framesPerBuf; + allocateTempBuffers(); + } + + bufferSizes.clear(); + + pa.mSelector = kAudioDevicePropertyBufferFrameSizeRange; + + if (OK (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, 0, &size))) + { + AudioValueRange* ranges = (AudioValueRange*) juce_calloc (size); + + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, ranges))) + { + bufferSizes.add ((int) ranges[0].mMinimum); + + for (int i = 32; i < 8192; i += 32) + { + for (int j = size / sizeof (AudioValueRange); --j >= 0;) + { + if (i >= ranges[j].mMinimum && i <= ranges[j].mMaximum) + { + bufferSizes.addIfNotAlreadyThere (i); + break; + } + } + } + + if (bufferSize > 0) + bufferSizes.addIfNotAlreadyThere (bufferSize); + } + + juce_free (ranges); + } + + if (bufferSizes.size() == 0 && bufferSize > 0) + bufferSizes.add (bufferSize); + + sampleRates.clear(); + const double possibleRates[] = { 44100.0, 48000.0, 88200.0, 96000.0, 176400.0, 192000.0 }; + String rates; + + pa.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; + + if (OK (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, 0, &size))) + { + AudioValueRange* ranges = (AudioValueRange*) juce_calloc (size); + + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, ranges))) + { + for (int i = 0; i < numElementsInArray (possibleRates); ++i) + { + bool ok = false; + + for (int j = size / sizeof (AudioValueRange); --j >= 0;) + if (possibleRates[i] >= ranges[j].mMinimum - 2 && possibleRates[i] <= ranges[j].mMaximum + 2) + ok = true; + + if (ok) + { + sampleRates.add (possibleRates[i]); + rates << possibleRates[i] << T(" "); + } + } + } + + juce_free (ranges); + } + + if (sampleRates.size() == 0 && sampleRate > 0) + { + sampleRates.add (sampleRate); + rates << sampleRate; + } + + log (T("sr: ") + rates); + + inputLatency = 0; + outputLatency = 0; + UInt32 lat; + size = sizeof (lat); + pa.mSelector = kAudioDevicePropertyLatency; + pa.mScope = kAudioDevicePropertyScopeInput; + //if (AudioDeviceGetProperty (deviceID, 0, true, kAudioDevicePropertyLatency, &size, &lat) == noErr) + if (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, &lat) == noErr) + inputLatency = (int) lat; + + pa.mScope = kAudioDevicePropertyScopeOutput; + size = sizeof (lat); + + if (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, &lat) == noErr) + outputLatency = (int) lat; + + log (T("lat: ") + String (inputLatency) + T(" ") + String (outputLatency)); + + inChanNames.clear(); + outChanNames.clear(); + + juce_free (inputChannelInfo); + inputChannelInfo = (CallbackDetailsForChannel*) juce_calloc (sizeof (CallbackDetailsForChannel) * (numInputChans + 2)); + numInputChannelInfos = 0; + + juce_free (outputChannelInfo); + outputChannelInfo = (CallbackDetailsForChannel*) juce_calloc (sizeof (CallbackDetailsForChannel) * (numOutputChans + 2)); + numOutputChannelInfos = 0; + + fillInChannelInfo (true); + fillInChannelInfo (false); + } + + const StringArray getSources (bool input) + { + StringArray s; + int num = 0; + OSType* types = getAllDataSourcesForDevice (deviceID, input, num); + + if (types != 0) + { + for (int i = 0; i < num; ++i) + { + AudioValueTranslation avt; + char buffer[256]; + + avt.mInputData = (void*) &(types[i]); + avt.mInputDataSize = sizeof (UInt32); + avt.mOutputData = buffer; + avt.mOutputDataSize = 256; + + UInt32 transSize = sizeof (avt); + + AudioObjectPropertyAddress pa; + pa.mSelector = kAudioDevicePropertyDataSourceNameForID; + pa.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + pa.mElement = kAudioObjectPropertyElementMaster; + + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &transSize, &avt))) + { + DBG (buffer); + s.add (buffer); + } + } + + juce_free (types); + } + + return s; + } + + int getCurrentSourceIndex (bool input) const + { + OSType currentSourceID = 0; + UInt32 size = sizeof (currentSourceID); + int result = -1; + + AudioObjectPropertyAddress pa; + pa.mSelector = kAudioDevicePropertyDataSource; + pa.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + pa.mElement = kAudioObjectPropertyElementMaster; + + if (deviceID != 0) + { + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, ¤tSourceID))) + { + int num = 0; + OSType* const types = getAllDataSourcesForDevice (deviceID, input, num); + + if (types != 0) + { + for (int i = 0; i < num; ++i) + { + if (types[num] == currentSourceID) + { + result = i; + break; + } + } + + juce_free (types); + } + } + } + + return result; + } + + void setCurrentSourceIndex (int index, bool input) + { + if (deviceID != 0) + { + int num = 0; + OSType* types = getAllDataSourcesForDevice (deviceID, input, num); + + if (types != 0) + { + if (((unsigned int) index) < (unsigned int) num) + { + AudioObjectPropertyAddress pa; + pa.mSelector = kAudioDevicePropertyDataSource; + pa.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + pa.mElement = kAudioObjectPropertyElementMaster; + + OSType typeId = types[index]; + + OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (typeId), &typeId)); + } + + juce_free (types); + } + } + } + + const String reopen (const BitArray& inputChannels, + const BitArray& outputChannels, + double newSampleRate, + int bufferSizeSamples) + { + String error; + log ("CoreAudio reopen"); + callbacksAllowed = false; + stopTimer(); + + stop (false); + + activeInputChans = inputChannels; + activeInputChans.setRange (inChanNames.size(), + activeInputChans.getHighestBit() + 1 - inChanNames.size(), + false); + + activeOutputChans = outputChannels; + activeOutputChans.setRange (outChanNames.size(), + activeOutputChans.getHighestBit() + 1 - outChanNames.size(), + false); + + numInputChans = activeInputChans.countNumberOfSetBits(); + numOutputChans = activeOutputChans.countNumberOfSetBits(); + + // set sample rate + AudioObjectPropertyAddress pa; + pa.mSelector = kAudioDevicePropertyNominalSampleRate; + pa.mScope = kAudioObjectPropertyScopeWildcard; + pa.mElement = kAudioObjectPropertyElementMaster; + Float64 sr = newSampleRate; + + if (! OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (sr), &sr))) + { + error = "Couldn't change sample rate"; + } + else + { + // change buffer size + UInt32 framesPerBuf = bufferSizeSamples; + pa.mSelector = kAudioDevicePropertyBufferFrameSize; + + if (! OK (AudioObjectSetPropertyData (deviceID, &pa, 0, 0, sizeof (framesPerBuf), &framesPerBuf))) + { + error = "Couldn't change buffer size"; + } + else + { + // Annoyingly, after changing the rate and buffer size, some devices fail to + // correctly report their new settings until some random time in the future, so + // after calling updateDetailsFromDevice, we need to manually bodge these values + // to make sure we're using the correct numbers.. + updateDetailsFromDevice(); + sampleRate = newSampleRate; + bufferSize = bufferSizeSamples; + + if (sampleRates.size() == 0) + error = "Device has no available sample-rates"; + else if (bufferSizes.size() == 0) + error = "Device has no available buffer-sizes"; + else if (inputDevice != 0) + error = inputDevice->reopen (inputChannels, + outputChannels, + newSampleRate, + bufferSizeSamples); + } + } + + callbacksAllowed = true; + return error; + } + + bool start (AudioIODeviceCallback* cb) + { + if (! started) + { + callback = 0; + + if (deviceID != 0) + { +#if MACOS_10_4_OR_EARLIER + if (OK (AudioDeviceAddIOProc (deviceID, audioIOProc, (void*) this))) +#else + if (OK (AudioDeviceCreateIOProcID (deviceID, audioIOProc, (void*) this, &audioProcID))) +#endif + { + if (OK (AudioDeviceStart (deviceID, audioIOProc))) + { + started = true; + } + else + { +#if MACOS_10_4_OR_EARLIER + OK (AudioDeviceRemoveIOProc (deviceID, audioIOProc)); +#else + OK (AudioDeviceDestroyIOProcID (deviceID, audioProcID)); + audioProcID = 0; +#endif + } + } + } + } + + if (started) + { + const ScopedLock sl (callbackLock); + callback = cb; + } + + if (inputDevice != 0) + return started && inputDevice->start (cb); + else + return started; + } + + void stop (bool leaveInterruptRunning) + { + callbackLock.enter(); + callback = 0; + callbackLock.exit(); + + if (started + && (deviceID != 0) + && ! leaveInterruptRunning) + { + OK (AudioDeviceStop (deviceID, audioIOProc)); + +#if MACOS_10_4_OR_EARLIER + OK (AudioDeviceRemoveIOProc (deviceID, audioIOProc)); +#else + OK (AudioDeviceDestroyIOProcID (deviceID, audioProcID)); + audioProcID = 0; +#endif + started = false; + + callbackLock.enter(); + callbackLock.exit(); + + // wait until it's definately stopped calling back.. + for (int i = 40; --i >= 0;) + { + Thread::sleep (50); + + UInt32 running = 0; + UInt32 size = sizeof (running); + + AudioObjectPropertyAddress pa; + pa.mSelector = kAudioDevicePropertyDeviceIsRunning; + pa.mScope = kAudioObjectPropertyScopeWildcard; + pa.mElement = kAudioObjectPropertyElementMaster; + + OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, &running)); + + if (running == 0) + break; + } + + callbackLock.enter(); + callbackLock.exit(); + } + + if (inputDevice != 0) + inputDevice->stop (leaveInterruptRunning); + } + + double getSampleRate() const + { + return sampleRate; + } + + int getBufferSize() const + { + return bufferSize; + } + + void audioCallback (const AudioBufferList* inInputData, + AudioBufferList* outOutputData) + { + int i; + const ScopedLock sl (callbackLock); + + if (callback != 0) + { + if (inputDevice == 0) + { + for (i = numInputChans; --i >= 0;) + { + const CallbackDetailsForChannel& info = inputChannelInfo[i]; + float* dest = tempInputBuffers [i]; + const float* src = ((const float*) inInputData->mBuffers[info.streamNum].mData) + + info.dataOffsetSamples; + const int stride = info.dataStrideSamples; + + if (stride != 0) // if this is zero, info is invalid + { + for (int j = bufferSize; --j >= 0;) + { + *dest++ = *src; + src += stride; + } + } + } + } + + if (! isSlaveDevice) + { + if (inputDevice == 0) + { + callback->audioDeviceIOCallback ((const float**) tempInputBuffers, + numInputChans, + tempOutputBuffers, + numOutputChans, + bufferSize); + } + else + { + jassert (inputDevice->bufferSize == bufferSize); + + // Sometimes the two linked devices seem to get their callbacks in + // parallel, so we need to lock both devices to stop the input data being + // changed while inside our callback.. + const ScopedLock sl (inputDevice->callbackLock); + + callback->audioDeviceIOCallback ((const float**) inputDevice->tempInputBuffers, + inputDevice->numInputChans, + tempOutputBuffers, + numOutputChans, + bufferSize); + } + + for (i = numOutputChans; --i >= 0;) + { + const CallbackDetailsForChannel& info = outputChannelInfo[i]; + const float* src = tempOutputBuffers [i]; + float* dest = ((float*) outOutputData->mBuffers[info.streamNum].mData) + + info.dataOffsetSamples; + const int stride = info.dataStrideSamples; + + if (stride != 0) // if this is zero, info is invalid + { + for (int j = bufferSize; --j >= 0;) + { + *dest = *src++; + dest += stride; + } + } + } + } + } + else + { + for (i = jmin (numOutputChans, numOutputChannelInfos); --i >= 0;) + { + const CallbackDetailsForChannel& info = outputChannelInfo[i]; + float* dest = ((float*) outOutputData->mBuffers[info.streamNum].mData) + + info.dataOffsetSamples; + const int stride = info.dataStrideSamples; + + if (stride != 0) // if this is zero, info is invalid + { + for (int j = bufferSize; --j >= 0;) + { + *dest = 0.0f; + dest += stride; + } + } + } + } + } + + // called by callbacks + void deviceDetailsChanged() + { + if (callbacksAllowed) + startTimer (100); + } + + void timerCallback() + { + stopTimer(); + log ("CoreAudio device changed callback"); + + const double oldSampleRate = sampleRate; + const int oldBufferSize = bufferSize; + updateDetailsFromDevice(); + + if (oldBufferSize != bufferSize || oldSampleRate != sampleRate) + { + callbacksAllowed = false; + stop (false); + updateDetailsFromDevice(); + callbacksAllowed = true; + } + } + + CoreAudioInternal* getRelatedDevice() const + { + UInt32 size = 0; + CoreAudioInternal* result = 0; + + AudioObjectPropertyAddress pa; + pa.mSelector = kAudioDevicePropertyRelatedDevices; + pa.mScope = kAudioObjectPropertyScopeWildcard; + pa.mElement = kAudioObjectPropertyElementMaster; + + if (deviceID != 0 + && AudioObjectGetPropertyDataSize (deviceID, &pa, 0, 0, &size) == noErr + && size > 0) + { + AudioDeviceID* devs = (AudioDeviceID*) juce_calloc (size); + + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, devs))) + { + for (unsigned int i = 0; i < size / sizeof (AudioDeviceID); ++i) + { + if (devs[i] != deviceID && devs[i] != 0) + { + result = new CoreAudioInternal (devs[i]); + + const bool thisIsInput = inChanNames.size() > 0 && outChanNames.size() == 0; + const bool otherIsInput = result->inChanNames.size() > 0 && result->outChanNames.size() == 0; + + if (thisIsInput != otherIsInput + || (inChanNames.size() + outChanNames.size() == 0) + || (result->inChanNames.size() + result->outChanNames.size()) == 0) + break; + + deleteAndZero (result); + } + } + } + + juce_free (devs); + } + + return result; + } + + juce_UseDebuggingNewOperator + + int inputLatency, outputLatency; + BitArray activeInputChans, activeOutputChans; + StringArray inChanNames, outChanNames; + Array sampleRates; + Array bufferSizes; + AudioIODeviceCallback* callback; +#if ! MACOS_10_4_OR_EARLIER + AudioDeviceIOProcID audioProcID; +#endif + + CoreAudioInternal* inputDevice; + bool isSlaveDevice; + +private: + CriticalSection callbackLock; + AudioDeviceID deviceID; + bool started; + double sampleRate; + int bufferSize; + float* audioBuffer; + int numInputChans, numOutputChans; + bool callbacksAllowed; + + struct CallbackDetailsForChannel + { + int streamNum; + int dataOffsetSamples; + int dataStrideSamples; + }; + + int numInputChannelInfos, numOutputChannelInfos; + CallbackDetailsForChannel* inputChannelInfo; + CallbackDetailsForChannel* outputChannelInfo; + float** tempInputBuffers; + float** tempOutputBuffers; + + CoreAudioInternal (const CoreAudioInternal&); + const CoreAudioInternal& operator= (const CoreAudioInternal&); + + static OSStatus audioIOProc (AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* device) + { + ((CoreAudioInternal*) device)->audioCallback (inInputData, outOutputData); + return noErr; + } + + static OSStatus deviceListenerProc (AudioDeviceID /*inDevice*/, UInt32 /*inLine*/, const AudioObjectPropertyAddress* pa, void* inClientData) + { + CoreAudioInternal* const intern = (CoreAudioInternal*) inClientData; + + switch (pa->mSelector) + { + case kAudioDevicePropertyBufferSize: + case kAudioDevicePropertyBufferFrameSize: + case kAudioDevicePropertyNominalSampleRate: + case kAudioDevicePropertyStreamFormat: + case kAudioDevicePropertyDeviceIsAlive: + intern->deviceDetailsChanged(); + break; + + case kAudioDevicePropertyBufferSizeRange: + case kAudioDevicePropertyVolumeScalar: + case kAudioDevicePropertyMute: + case kAudioDevicePropertyPlayThru: + case kAudioDevicePropertyDataSource: + case kAudioDevicePropertyDeviceIsRunning: + break; + } + + return noErr; + } + + static OSType* getAllDataSourcesForDevice (AudioDeviceID deviceID, const bool input, int& num) + { + OSType* types = 0; + UInt32 size = 0; + num = 0; + + AudioObjectPropertyAddress pa; + pa.mSelector = kAudioDevicePropertyDataSources; + pa.mScope = kAudioObjectPropertyScopeWildcard; + pa.mElement = kAudioObjectPropertyElementMaster; + + if (deviceID != 0 + && OK (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, 0, &size))) + { + types = (OSType*) juce_calloc (size); + + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, types))) + { + num = size / sizeof (OSType); + } + else + { + juce_free (types); + types = 0; + } + } + + return types; + } +}; + +class CoreAudioIODevice : public AudioIODevice +{ +public: + CoreAudioIODevice (const String& deviceName, + AudioDeviceID inputDeviceId, + const int inputIndex_, + AudioDeviceID outputDeviceId, + const int outputIndex_) + : AudioIODevice (deviceName, "CoreAudio"), + inputIndex (inputIndex_), + outputIndex (outputIndex_), + isOpen_ (false), + isStarted (false) + { + internal = 0; + CoreAudioInternal* device = 0; + + if (outputDeviceId == 0 || outputDeviceId == inputDeviceId) + { + jassert (inputDeviceId != 0); + + device = new CoreAudioInternal (inputDeviceId); + } + else + { + device = new CoreAudioInternal (outputDeviceId); + + if (inputDeviceId != 0) + { + CoreAudioInternal* secondDevice = new CoreAudioInternal (inputDeviceId); + + device->inputDevice = secondDevice; + secondDevice->isSlaveDevice = true; + } + } + + internal = device; + + AudioObjectPropertyAddress pa; + pa.mSelector = kAudioObjectPropertySelectorWildcard; + pa.mScope = kAudioObjectPropertyScopeWildcard; + pa.mElement = kAudioObjectPropertyElementWildcard; + + AudioObjectAddPropertyListener (kAudioObjectSystemObject, &pa, hardwareListenerProc, internal); + } + + ~CoreAudioIODevice() + { + AudioObjectPropertyAddress pa; + pa.mSelector = kAudioObjectPropertySelectorWildcard; + pa.mScope = kAudioObjectPropertyScopeWildcard; + pa.mElement = kAudioObjectPropertyElementWildcard; + + AudioObjectRemovePropertyListener (kAudioObjectSystemObject, &pa, hardwareListenerProc, internal); + + delete internal; + } + + const StringArray getOutputChannelNames() + { + return internal->outChanNames; + } + + const StringArray getInputChannelNames() + { + if (internal->inputDevice != 0) + return internal->inputDevice->inChanNames; + else + return internal->inChanNames; + } + + int getNumSampleRates() + { + return internal->sampleRates.size(); + } + + double getSampleRate (int index) + { + return internal->sampleRates [index]; + } + + int getNumBufferSizesAvailable() + { + return internal->bufferSizes.size(); + } + + int getBufferSizeSamples (int index) + { + return internal->bufferSizes [index]; + } + + int getDefaultBufferSize() + { + for (int i = 0; i < getNumBufferSizesAvailable(); ++i) + if (getBufferSizeSamples(i) >= 512) + return getBufferSizeSamples(i); + + return 512; + } + + const String open (const BitArray& inputChannels, + const BitArray& outputChannels, + double sampleRate, + int bufferSizeSamples) + { + isOpen_ = true; + + if (bufferSizeSamples <= 0) + bufferSizeSamples = getDefaultBufferSize(); + + lastError = internal->reopen (inputChannels, outputChannels, sampleRate, bufferSizeSamples); + isOpen_ = lastError.isEmpty(); + return lastError; + } + + void close() + { + isOpen_ = false; + internal->stop (false); + } + + bool isOpen() + { + return isOpen_; + } + + int getCurrentBufferSizeSamples() + { + return internal != 0 ? internal->getBufferSize() : 512; + } + + double getCurrentSampleRate() + { + return internal != 0 ? internal->getSampleRate() : 0; + } + + int getCurrentBitDepth() + { + return 32; // no way to find out, so just assume it's high.. + } + + const BitArray getActiveOutputChannels() const + { + return internal != 0 ? internal->activeOutputChans : BitArray(); + } + + const BitArray getActiveInputChannels() const + { + BitArray chans; + + if (internal != 0) + { + chans = internal->activeInputChans; + + if (internal->inputDevice != 0) + chans.orWith (internal->inputDevice->activeInputChans); + } + + return chans; + } + + int getOutputLatencyInSamples() + { + if (internal == 0) + return 0; + + // this seems like a good guess at getting the latency right - comparing + // this with a round-trip measurement, it gets it to within a few millisecs + // for the built-in mac soundcard + return internal->outputLatency + internal->getBufferSize() * 2; + } + + int getInputLatencyInSamples() + { + if (internal == 0) + return 0; + + return internal->inputLatency + internal->getBufferSize() * 2; + } + + void start (AudioIODeviceCallback* callback) + { + if (internal != 0 && ! isStarted) + { + if (callback != 0) + callback->audioDeviceAboutToStart (this); + + isStarted = true; + internal->start (callback); + } + } + + void stop() + { + if (isStarted && internal != 0) + { + AudioIODeviceCallback* const lastCallback = internal->callback; + + isStarted = false; + internal->stop (true); + + if (lastCallback != 0) + lastCallback->audioDeviceStopped(); + } + } + + bool isPlaying() + { + if (internal->callback == 0) + isStarted = false; + + return isStarted; + } + + const String getLastError() + { + return lastError; + } + + int inputIndex, outputIndex; + + juce_UseDebuggingNewOperator + +private: + CoreAudioInternal* internal; + bool isOpen_, isStarted; + String lastError; + + static OSStatus hardwareListenerProc (AudioDeviceID /*inDevice*/, UInt32 /*inLine*/, const AudioObjectPropertyAddress* pa, void* inClientData) + { + CoreAudioInternal* const intern = (CoreAudioInternal*) inClientData; + + switch (pa->mSelector) + { + case kAudioHardwarePropertyDevices: + intern->deviceDetailsChanged(); + break; + + case kAudioHardwarePropertyDefaultOutputDevice: + case kAudioHardwarePropertyDefaultInputDevice: + case kAudioHardwarePropertyDefaultSystemOutputDevice: + break; + } + + return noErr; + } + + CoreAudioIODevice (const CoreAudioIODevice&); + const CoreAudioIODevice& operator= (const CoreAudioIODevice&); +}; + +class CoreAudioIODeviceType : public AudioIODeviceType +{ +public: + + CoreAudioIODeviceType() + : AudioIODeviceType (T("CoreAudio")), + hasScanned (false) + { + } + + ~CoreAudioIODeviceType() + { + } + + void scanForDevices() + { + hasScanned = true; + + inputDeviceNames.clear(); + outputDeviceNames.clear(); + inputIds.clear(); + outputIds.clear(); + + UInt32 size; + + AudioObjectPropertyAddress pa; + pa.mSelector = kAudioHardwarePropertyDevices; + pa.mScope = kAudioObjectPropertyScopeWildcard; + pa.mElement = kAudioObjectPropertyElementMaster; + + if (OK (AudioObjectGetPropertyDataSize (kAudioObjectSystemObject, &pa, 0, 0, &size))) + { + AudioDeviceID* const devs = (AudioDeviceID*) juce_calloc (size); + + if (OK (AudioObjectGetPropertyData (kAudioObjectSystemObject, &pa, 0, 0, &size, devs))) + { + static bool alreadyLogged = false; + const int num = size / sizeof (AudioDeviceID); + for (int i = 0; i < num; ++i) + { + char name [1024]; + size = sizeof (name); + pa.mSelector = kAudioDevicePropertyDeviceName; + + if (OK (AudioObjectGetPropertyData (devs[i], &pa, 0, 0, &size, name))) + { + const String nameString (String::fromUTF8 ((const uint8*) name, strlen (name))); + + if (! alreadyLogged) + log (T("CoreAudio device: ") + nameString); + + const int numIns = getNumChannels (devs[i], true); + const int numOuts = getNumChannels (devs[i], false); + + if (numIns > 0) + { + inputDeviceNames.add (nameString); + inputIds.add (devs[i]); + } + + if (numOuts > 0) + { + outputDeviceNames.add (nameString); + outputIds.add (devs[i]); + } + } + } + + alreadyLogged = true; + } + + juce_free (devs); + } + + inputDeviceNames.appendNumbersToDuplicates (false, true); + outputDeviceNames.appendNumbersToDuplicates (false, true); + } + + const StringArray getDeviceNames (const bool wantInputNames) const + { + jassert (hasScanned); // need to call scanForDevices() before doing this + + if (wantInputNames) + return inputDeviceNames; + else + return outputDeviceNames; + } + + int getDefaultDeviceIndex (const bool forInput) const + { + jassert (hasScanned); // need to call scanForDevices() before doing this + + AudioDeviceID deviceID; + UInt32 size = sizeof (deviceID); + + // if they're asking for any input channels at all, use the default input, so we + // get the built-in mic rather than the built-in output with no inputs.. + + AudioObjectPropertyAddress pa; + pa.mSelector = forInput ? kAudioHardwarePropertyDefaultInputDevice : kAudioHardwarePropertyDefaultOutputDevice; + pa.mScope = kAudioObjectPropertyScopeWildcard; + pa.mElement = kAudioObjectPropertyElementMaster; + + if (AudioObjectGetPropertyData (kAudioObjectSystemObject, &pa, 0, 0, &size, &deviceID) == noErr) + { + if (forInput) + { + for (int i = inputIds.size(); --i >= 0;) + if (inputIds[i] == deviceID) + return i; + } + else + { + for (int i = outputIds.size(); --i >= 0;) + if (outputIds[i] == deviceID) + return i; + } + } + + return 0; + } + + int getIndexOfDevice (AudioIODevice* device, const bool asInput) const + { + jassert (hasScanned); // need to call scanForDevices() before doing this + + CoreAudioIODevice* const d = dynamic_cast (device); + if (d == 0) + return -1; + + return asInput ? d->inputIndex + : d->outputIndex; + } + + bool hasSeparateInputsAndOutputs() const { return true; } + + AudioIODevice* createDevice (const String& outputDeviceName, + const String& inputDeviceName) + { + jassert (hasScanned); // need to call scanForDevices() before doing this + + const int inputIndex = inputDeviceNames.indexOf (inputDeviceName); + const int outputIndex = outputDeviceNames.indexOf (outputDeviceName); + + String deviceName (outputDeviceName); + if (deviceName.isEmpty()) + deviceName = inputDeviceName; + + if (index >= 0) + return new CoreAudioIODevice (deviceName, + inputIds [inputIndex], + inputIndex, + outputIds [outputIndex], + outputIndex); + + return 0; + } + + juce_UseDebuggingNewOperator + +private: + StringArray inputDeviceNames, outputDeviceNames; + Array inputIds, outputIds; + + bool hasScanned; + + static int getNumChannels (AudioDeviceID deviceID, bool input) + { + int total = 0; + UInt32 size; + + AudioObjectPropertyAddress pa; + pa.mSelector = kAudioDevicePropertyStreamConfiguration; + pa.mScope = input ? kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + pa.mElement = kAudioObjectPropertyElementMaster; + + if (OK (AudioObjectGetPropertyDataSize (deviceID, &pa, 0, 0, &size))) + { + AudioBufferList* const bufList = (AudioBufferList*) juce_calloc (size); + + if (OK (AudioObjectGetPropertyData (deviceID, &pa, 0, 0, &size, bufList))) + { + const int numStreams = bufList->mNumberBuffers; + + for (int i = 0; i < numStreams; ++i) + { + const AudioBuffer& b = bufList->mBuffers[i]; + total += b.mNumberChannels; + } + } + + juce_free (bufList); + } + + return total; + } + + CoreAudioIODeviceType (const CoreAudioIODeviceType&); + const CoreAudioIODeviceType& operator= (const CoreAudioIODeviceType&); +}; + +AudioIODeviceType* juce_createAudioIODeviceType_CoreAudio() +{ + return new CoreAudioIODeviceType(); +} + +#undef log + +#endif +/********* End of inlined file: juce_mac_CoreAudio.cpp *********/ + +/********* Start of inlined file: juce_mac_CoreMidi.cpp *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + +#undef log +#define log(a) Logger::writeToLog(a) + +static bool logAnyErrorsMidi (const OSStatus err, const int lineNum) +{ + if (err == noErr) + return true; + + log (T("CoreMidi error: ") + String (lineNum) + T(" - ") + String::toHexString ((int)err)); + jassertfalse + return false; +} + +#undef OK +#define OK(a) logAnyErrorsMidi(a, __LINE__) + +static const String getEndpointName (MIDIEndpointRef endpoint, bool isExternal) +{ + String result; + CFStringRef str = 0; + + MIDIObjectGetStringProperty (endpoint, kMIDIPropertyName, &str); + + if (str != 0) + { + result = PlatformUtilities::cfStringToJuceString (str); + CFRelease (str); + str = 0; + } + + MIDIEntityRef entity = 0; + MIDIEndpointGetEntity (endpoint, &entity); + + if (entity == 0) + return result; // probably virtual + + if (result.isEmpty()) + { + // endpoint name has zero length - try the entity + MIDIObjectGetStringProperty (entity, kMIDIPropertyName, &str); + + if (str != 0) + { + result += PlatformUtilities::cfStringToJuceString (str); + CFRelease (str); + str = 0; + } + } + + // now consider the device's name + MIDIDeviceRef device = 0; + MIDIEntityGetDevice (entity, &device); + if (device == 0) + return result; + + MIDIObjectGetStringProperty (device, kMIDIPropertyName, &str); + + if (str != 0) + { + const String s (PlatformUtilities::cfStringToJuceString (str)); + CFRelease (str); + + // if an external device has only one entity, throw away + // the endpoint name and just use the device name + if (isExternal && MIDIDeviceGetNumberOfEntities (device) < 2) + { + result = s; + } + else if (! result.startsWithIgnoreCase (s)) + { + // prepend the device name to the entity name + result = (s + T(" ") + result).trimEnd(); + } + } + + return result; +} + +static const String getConnectedEndpointName (MIDIEndpointRef endpoint) +{ + String result; + + // Does the endpoint have connections? + CFDataRef connections = 0; + int numConnections = 0; + + MIDIObjectGetDataProperty (endpoint, kMIDIPropertyConnectionUniqueID, &connections); + + if (connections != 0) + { + numConnections = CFDataGetLength (connections) / sizeof (MIDIUniqueID); + + if (numConnections > 0) + { + const SInt32* pid = reinterpret_cast (CFDataGetBytePtr (connections)); + + for (int i = 0; i < numConnections; ++i, ++pid) + { + MIDIUniqueID uid = EndianS32_BtoN (*pid); + MIDIObjectRef connObject; + MIDIObjectType connObjectType; + OSStatus err = MIDIObjectFindByUniqueID (uid, &connObject, &connObjectType); + + if (err == noErr) + { + String s; + + if (connObjectType == kMIDIObjectType_ExternalSource + || connObjectType == kMIDIObjectType_ExternalDestination) + { + // Connected to an external device's endpoint (10.3 and later). + s = getEndpointName (static_cast (connObject), true); + } + else + { + // Connected to an external device (10.2) (or something else, catch-all) + CFStringRef str = 0; + MIDIObjectGetStringProperty (connObject, kMIDIPropertyName, &str); + + if (str != 0) + { + s = PlatformUtilities::cfStringToJuceString (str); + CFRelease (str); + } + } + + if (s.isNotEmpty()) + { + if (result.isNotEmpty()) + result += (", "); + + result += s; + } + } + } + } + + CFRelease (connections); + } + + if (result.isNotEmpty()) + return result; + + // Here, either the endpoint had no connections, or we failed to obtain names for any of them. + return getEndpointName (endpoint, false); +} + +const StringArray MidiOutput::getDevices() +{ + StringArray s; + + const ItemCount num = MIDIGetNumberOfDestinations(); + for (ItemCount i = 0; i < num; ++i) + { + MIDIEndpointRef dest = MIDIGetDestination (i); + + if (dest != 0) + { + String name (getConnectedEndpointName (dest)); + + if (name.isEmpty()) + name = ""; + + s.add (name); + } + else + { + s.add (""); + } + } + + return s; +} + +int MidiOutput::getDefaultDeviceIndex() +{ + return 0; +} + +static MIDIClientRef globalMidiClient; +static bool hasGlobalClientBeenCreated = false; + +static bool makeSureClientExists() +{ + if (! hasGlobalClientBeenCreated) + { + String name (T("JUCE")); + + if (JUCEApplication::getInstance() != 0) + name = JUCEApplication::getInstance()->getApplicationName(); + + CFStringRef appName = PlatformUtilities::juceStringToCFString (name); + + hasGlobalClientBeenCreated = OK (MIDIClientCreate (appName, 0, 0, &globalMidiClient)); + CFRelease (appName); + } + + return hasGlobalClientBeenCreated; +} + +struct MidiPortAndEndpoint +{ + MIDIPortRef port; + MIDIEndpointRef endPoint; +}; + +MidiOutput* MidiOutput::openDevice (int index) +{ + MidiOutput* mo = 0; + + if (((unsigned int) index) < (unsigned int) MIDIGetNumberOfDestinations()) + { + MIDIEndpointRef endPoint = MIDIGetDestination (index); + + CFStringRef pname; + if (OK (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname))) + { + log (T("CoreMidi - opening out: ") + PlatformUtilities::cfStringToJuceString (pname)); + + if (makeSureClientExists()) + { + MIDIPortRef port; + + if (OK (MIDIOutputPortCreate (globalMidiClient, pname, &port))) + { + MidiPortAndEndpoint* mpe = new MidiPortAndEndpoint(); + mpe->port = port; + mpe->endPoint = endPoint; + + mo = new MidiOutput(); + mo->internal = (void*)mpe; + } + } + + CFRelease (pname); + } + } + + return mo; +} + +MidiOutput::~MidiOutput() +{ + MidiPortAndEndpoint* const mpe = (MidiPortAndEndpoint*)internal; + MIDIPortDispose (mpe->port); + delete mpe; +} + +void MidiOutput::reset() +{ +} + +bool MidiOutput::getVolume (float& leftVol, float& rightVol) +{ + return false; +} + +void MidiOutput::setVolume (float leftVol, float rightVol) +{ +} + +void MidiOutput::sendMessageNow (const MidiMessage& message) +{ + MidiPortAndEndpoint* const mpe = (MidiPortAndEndpoint*)internal; + + if (message.isSysEx()) + { + const int maxPacketSize = 256; + int pos = 0, bytesLeft = message.getRawDataSize(); + const int numPackets = (bytesLeft + maxPacketSize - 1) / maxPacketSize; + MIDIPacketList* const packets = (MIDIPacketList*) juce_malloc (32 * numPackets + message.getRawDataSize()); + packets->numPackets = numPackets; + + MIDIPacket* p = packets->packet; + + for (int i = 0; i < numPackets; ++i) + { + p->timeStamp = 0; + p->length = jmin (maxPacketSize, bytesLeft); + memcpy (p->data, message.getRawData() + pos, p->length); + pos += p->length; + bytesLeft -= p->length; + p = MIDIPacketNext (p); + } + + MIDISend (mpe->port, mpe->endPoint, packets); + juce_free (packets); + } + else + { + MIDIPacketList packets; + packets.numPackets = 1; + packets.packet[0].timeStamp = 0; + packets.packet[0].length = message.getRawDataSize(); + *(int*) (packets.packet[0].data) = *(const int*) message.getRawData(); + + MIDISend (mpe->port, mpe->endPoint, &packets); + } +} + +const StringArray MidiInput::getDevices() +{ + StringArray s; + + const ItemCount num = MIDIGetNumberOfSources(); + for (ItemCount i = 0; i < num; ++i) + { + MIDIEndpointRef source = MIDIGetSource (i); + + if (source != 0) + { + String name (getConnectedEndpointName (source)); + + if (name.isEmpty()) + name = ""; + + s.add (name); + } + else + { + s.add (""); + } + } + + return s; +} + +int MidiInput::getDefaultDeviceIndex() +{ + return 0; +} + +struct MidiPortAndCallback +{ + MidiInput* input; + MIDIPortRef port; + MIDIEndpointRef endPoint; + MidiInputCallback* callback; + MemoryBlock pendingData; + int pendingBytes; + double pendingDataTime; + bool active; +}; + +static CriticalSection callbackLock; +static VoidArray activeCallbacks; + +static void processSysex (MidiPortAndCallback* const mpe, const uint8*& d, int& size, const double time) +{ + if (*d == 0xf0) + { + mpe->pendingBytes = 0; + mpe->pendingDataTime = time; + } + + mpe->pendingData.ensureSize (mpe->pendingBytes + size, false); + uint8* totalMessage = (uint8*) mpe->pendingData.getData(); + + uint8* dest = totalMessage + mpe->pendingBytes; + + while (size > 0) + { + if (mpe->pendingBytes > 0 && *d >= 0x80) + { + if (*d >= 0xfa || *d == 0xf8) + { + mpe->callback->handleIncomingMidiMessage (mpe->input, MidiMessage (*d, time)); + ++d; + --size; + } + else + { + if (*d == 0xf7) + { + *dest++ = *d++; + mpe->pendingBytes++; + --size; + } + + break; + } + } + else + { + *dest++ = *d++; + mpe->pendingBytes++; + --size; + } + } + + if (totalMessage [mpe->pendingBytes - 1] == 0xf7) + { + mpe->callback->handleIncomingMidiMessage (mpe->input, MidiMessage (totalMessage, + mpe->pendingBytes, + mpe->pendingDataTime)); + mpe->pendingBytes = 0; + } + else + { + mpe->callback->handlePartialSysexMessage (mpe->input, + totalMessage, + mpe->pendingBytes, + mpe->pendingDataTime); + } +} + +static void midiInputProc (const MIDIPacketList* pktlist, + void* readProcRefCon, + void* srcConnRefCon) +{ + double time = Time::getMillisecondCounterHiRes() * 0.001; + const double originalTime = time; + + MidiPortAndCallback* const mpe = (MidiPortAndCallback*) readProcRefCon; + const ScopedLock sl (callbackLock); + + if (activeCallbacks.contains (mpe) && mpe->active) + { + const MIDIPacket* packet = &pktlist->packet[0]; + + for (unsigned int i = 0; i < pktlist->numPackets; ++i) + { + const uint8* d = (const uint8*) (packet->data); + int size = packet->length; + + while (size > 0) + { + time = originalTime; + + if (mpe->pendingBytes > 0 || d[0] == 0xf0) + { + processSysex (mpe, d, size, time); + } + else + { + int used = 0; + const MidiMessage m (d, size, used, 0, time); + + if (used <= 0) + { + jassertfalse // malformed midi message + break; + } + else + { + mpe->callback->handleIncomingMidiMessage (mpe->input, m); + } + + size -= used; + d += used; + } + } + + packet = MIDIPacketNext (packet); + } + } +} + +MidiInput* MidiInput::openDevice (int index, MidiInputCallback* callback) +{ + MidiInput* mi = 0; + + if (((unsigned int) index) < (unsigned int) MIDIGetNumberOfSources()) + { + MIDIEndpointRef endPoint = MIDIGetSource (index); + + if (endPoint != 0) + { + CFStringRef pname; + + if (OK (MIDIObjectGetStringProperty (endPoint, kMIDIPropertyName, &pname))) + { + log (T("CoreMidi - opening inp: ") + PlatformUtilities::cfStringToJuceString (pname)); + + if (makeSureClientExists()) + { + MIDIPortRef port; + + MidiPortAndCallback* const mpe = new MidiPortAndCallback(); + mpe->active = false; + + if (OK (MIDIInputPortCreate (globalMidiClient, pname, midiInputProc, mpe, &port))) + { + if (OK (MIDIPortConnectSource (port, endPoint, 0))) + { + mpe->port = port; + mpe->endPoint = endPoint; + mpe->callback = callback; + mpe->pendingBytes = 0; + mpe->pendingData.ensureSize (128); + + mi = new MidiInput (getDevices() [index]); + mpe->input = mi; + mi->internal = (void*) mpe; + + const ScopedLock sl (callbackLock); + activeCallbacks.add (mpe); + } + else + { + OK (MIDIPortDispose (port)); + delete mpe; + } + } + else + { + delete mpe; + } + } + } + + CFRelease (pname); + } + } + + return mi; +} + +MidiInput::MidiInput (const String& name_) + : name (name_) +{ +} + +MidiInput::~MidiInput() +{ + MidiPortAndCallback* const mpe = (MidiPortAndCallback*) internal; + mpe->active = false; + + callbackLock.enter(); + activeCallbacks.removeValue (mpe); + callbackLock.exit(); + + OK (MIDIPortDisconnectSource (mpe->port, mpe->endPoint)); + OK (MIDIPortDispose (mpe->port)); + delete mpe; +} + +void MidiInput::start() +{ + MidiPortAndCallback* const mpe = (MidiPortAndCallback*) internal; + const ScopedLock sl (callbackLock); + mpe->active = true; +} + +void MidiInput::stop() +{ + MidiPortAndCallback* const mpe = (MidiPortAndCallback*) internal; + const ScopedLock sl (callbackLock); + mpe->active = false; +} + +#undef log + +#endif +/********* End of inlined file: juce_mac_CoreMidi.cpp *********/ + +/********* Start of inlined file: juce_mac_CameraDevice.mm *********/ +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#if JUCE_INCLUDED_FILE && JUCE_QUICKTIME && JUCE_USE_CAMERA + +#define QTCaptureCallbackDelegate MakeObjCClassName(QTCaptureCallbackDelegate) + +class QTCameraDeviceInteral; + +END_JUCE_NAMESPACE + +@interface QTCaptureCallbackDelegate : NSObject +{ +@public + CameraDevice* owner; + QTCameraDeviceInteral* internal; + Time* firstRecordedTime; +} + +- (QTCaptureCallbackDelegate*) initWithOwner: (CameraDevice*) owner internalDev: (QTCameraDeviceInteral*) d; +- (void) dealloc; + +- (void) captureOutput: (QTCaptureOutput*) captureOutput + didOutputVideoFrame: (CVImageBufferRef) videoFrame + withSampleBuffer: (QTSampleBuffer*) sampleBuffer + fromConnection: (QTCaptureConnection*) connection; + +- (void) captureOutput: (QTCaptureFileOutput*) captureOutput + didOutputSampleBuffer: (QTSampleBuffer*) sampleBuffer + fromConnection: (QTCaptureConnection*) connection; + +@end + +BEGIN_JUCE_NAMESPACE + +class QTCameraDeviceInteral +{ +public: + QTCameraDeviceInteral (CameraDevice* owner, int index) + { + const ScopedAutoReleasePool pool; + + session = [[QTCaptureSession alloc] init]; + + NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo]; + device = (QTCaptureDevice*) [devs objectAtIndex: index]; + input = 0; + fileOutput = 0; + imageOutput = 0; + callbackDelegate = [[QTCaptureCallbackDelegate alloc] initWithOwner: owner + internalDev: this]; + + NSError* err = 0; + [device retain]; + [device open: &err]; + + if (err == 0) + { + input = [[QTCaptureDeviceInput alloc] initWithDevice: device]; + + [session addInput: input error: &err]; + + if (err == 0) + { + resetFile(); + + imageOutput = [[QTCaptureDecompressedVideoOutput alloc] init]; + [imageOutput setDelegate: callbackDelegate]; + + if (err == 0) + { + [session startRunning]; + return; + } + } + } + + openingError = nsStringToJuce ([err description]); + DBG (openingError); + } + + ~QTCameraDeviceInteral() + { + [session stopRunning]; + [session removeOutput: imageOutput]; + + [session release]; + [input release]; + [device release]; + [fileOutput release]; + [imageOutput release]; + [callbackDelegate release]; + } + + void resetFile() + { + [session removeOutput: fileOutput]; + [fileOutput release]; + fileOutput = [[QTCaptureMovieFileOutput alloc] init]; + [fileOutput setDelegate: callbackDelegate]; + } + + void addListener (CameraImageListener* listenerToAdd) + { + const ScopedLock sl (listenerLock); + + if (listeners.size() == 0) + [session addOutput: imageOutput error: nil]; + + listeners.addIfNotAlreadyThere (listenerToAdd); + } + + void removeListener (CameraImageListener* listenerToRemove) + { + const ScopedLock sl (listenerLock); + listeners.removeValue (listenerToRemove); + + if (listeners.size() == 0) + [session removeOutput: imageOutput]; + } + + static void drawNSBitmapIntoJuceImage (Image& dest, NSBitmapImageRep* source) + { + const ScopedAutoReleasePool pool; + int lineStride, pixelStride; + uint8* pixels = dest.lockPixelDataReadWrite (0, 0, dest.getWidth(), dest.getHeight(), + lineStride, pixelStride); + + NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes: &pixels + pixelsWide: dest.getWidth() + pixelsHigh: dest.getHeight() + bitsPerSample: 8 + samplesPerPixel: pixelStride + hasAlpha: dest.hasAlphaChannel() + isPlanar: NO + colorSpaceName: NSCalibratedRGBColorSpace + bitmapFormat: (NSBitmapFormat) 0 + bytesPerRow: lineStride + bitsPerPixel: pixelStride * 8]; + + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext: [NSGraphicsContext graphicsContextWithBitmapImageRep: rep]]; + + [source drawAtPoint: NSZeroPoint]; + + [[NSGraphicsContext currentContext] flushGraphics]; + [NSGraphicsContext restoreGraphicsState]; + + uint8* start = pixels; + for (int h = dest.getHeight(); --h >= 0;) + { + uint8* p = start; + start += lineStride; + + for (int i = dest.getWidth(); --i >= 0;) + { +#if JUCE_BIG_ENDIAN + const uint8 oldp3 = p[3]; + const uint8 oldp1 = p[1]; + p[3] = p[0]; + p[0] = oldp1; + p[1] = p[2]; + p[2] = oldp3; +#else + const uint8 oldp0 = p[0]; + p[0] = p[2]; + p[2] = oldp0; +#endif + + p += pixelStride; + } + } + + dest.releasePixelDataReadWrite (pixels); + } + + void callListeners (NSBitmapImageRep* bitmap) + { + Image image (Image::ARGB, [bitmap size].width, [bitmap size].height, false); + drawNSBitmapIntoJuceImage (image, bitmap); + + const ScopedLock sl (listenerLock); + + for (int i = listeners.size(); --i >= 0;) + { + CameraImageListener* l = (CameraImageListener*) listeners[i]; + + if (l != 0) + l->imageReceived (image); + } + } + + QTCaptureDevice* device; + QTCaptureDeviceInput* input; + QTCaptureSession* session; + QTCaptureMovieFileOutput* fileOutput; + QTCaptureDecompressedVideoOutput* imageOutput; + QTCaptureCallbackDelegate* callbackDelegate; + String openingError; + + VoidArray listeners; + CriticalSection listenerLock; +}; + +END_JUCE_NAMESPACE +@implementation QTCaptureCallbackDelegate + +- (QTCaptureCallbackDelegate*) initWithOwner: (CameraDevice*) owner_ + internalDev: (QTCameraDeviceInteral*) d +{ + [super init]; + owner = owner_; + internal = d; + firstRecordedTime = 0; + return self; +} + +- (void) dealloc +{ + delete firstRecordedTime; + [super dealloc]; +} + +- (void) captureOutput: (QTCaptureOutput*) captureOutput + didOutputVideoFrame: (CVImageBufferRef) videoFrame + withSampleBuffer: (QTSampleBuffer*) sampleBuffer + fromConnection: (QTCaptureConnection*) connection +{ + const ScopedAutoReleasePool pool; + CIImage* image = [CIImage imageWithCVImageBuffer: videoFrame]; + NSBitmapImageRep* bitmap = [[[NSBitmapImageRep alloc] initWithCIImage: image] autorelease]; + + internal->callListeners (bitmap); +} + +- (void) captureOutput: (QTCaptureFileOutput*) captureOutput + didOutputSampleBuffer: (QTSampleBuffer*) sampleBuffer + fromConnection: (QTCaptureConnection*) connection +{ + if (firstRecordedTime == 0) + firstRecordedTime = new Time (Time::getCurrentTime()); +} + +@end +BEGIN_JUCE_NAMESPACE + +class QTCaptureViewerComp : public NSViewComponent +{ +public: + QTCaptureViewerComp (CameraDevice* const cameraDevice, QTCameraDeviceInteral* const internal) + { + const ScopedAutoReleasePool pool; + captureView = [[QTCaptureView alloc] init]; + [captureView setCaptureSession: internal->session]; + + setSize (640, 480); // xxx need to somehow get the movie size - how? + setView (captureView); + } + + ~QTCaptureViewerComp() + { + setView (0); + [captureView setCaptureSession: nil]; + [captureView release]; + } + + QTCaptureView* captureView; +}; + +CameraDevice::CameraDevice (const String& name_, int index) + : name (name_) +{ + isRecording = false; + QTCameraDeviceInteral* d = new QTCameraDeviceInteral (this, index); + internal = d; +} + +CameraDevice::~CameraDevice() +{ + stopRecording(); + delete (QTCameraDeviceInteral*) internal; + internal = 0; +} + +Component* CameraDevice::createViewerComponent() +{ + return new QTCaptureViewerComp (this, (QTCameraDeviceInteral*) internal); +} + +const String CameraDevice::getFileExtension() +{ + return ".mov"; +} + +void CameraDevice::startRecordingToFile (const File& file) +{ + stopRecording(); + + QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal; + deleteAndZero (d->callbackDelegate->firstRecordedTime); + file.deleteFile(); + [d->fileOutput recordToOutputFileURL: [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]]; + [d->session addOutput: d->fileOutput error: nil]; + isRecording = true; +} + +const Time CameraDevice::getTimeOfFirstRecordedFrame() const +{ + QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal; + if (d->callbackDelegate->firstRecordedTime != 0) + return *d->callbackDelegate->firstRecordedTime; + + return Time(); +} + +void CameraDevice::stopRecording() +{ + if (isRecording) + { + QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal; + d->resetFile(); + isRecording = false; + } +} + +void CameraDevice::addListener (CameraImageListener* listenerToAdd) +{ + QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal; + + if (listenerToAdd != 0) + d->addListener (listenerToAdd); +} + +void CameraDevice::removeListener (CameraImageListener* listenerToRemove) +{ + QTCameraDeviceInteral* const d = (QTCameraDeviceInteral*) internal; + + if (listenerToRemove != 0) + d->removeListener (listenerToRemove); +} + +const StringArray CameraDevice::getAvailableDevices() +{ + const ScopedAutoReleasePool pool; + + StringArray results; + NSArray* devs = [QTCaptureDevice inputDevicesWithMediaType: QTMediaTypeVideo]; + + for (int i = 0; i < [devs count]; ++i) + { + QTCaptureDevice* dev = (QTCaptureDevice*) [devs objectAtIndex: i]; + results.add (nsStringToJuce ([dev localizedDisplayName])); + } + + return results; +} + +CameraDevice* CameraDevice::openDevice (int index, + int minWidth, int minHeight, + int maxWidth, int maxHeight) +{ + CameraDevice* d = new CameraDevice (getAvailableDevices() [index], index); + + if (((QTCameraDeviceInteral*) (d->internal))->openingError.isEmpty()) + return d; + + delete d; + return 0; +} + +#endif +/********* End of inlined file: juce_mac_CameraDevice.mm *********/ + +*/ +#endif + +END_JUCE_NAMESPACE +/********* End of inlined file: juce_iphone_NativeCode.mm *********/ + +#endif diff --git a/juce_amalgamated.h b/juce_amalgamated.h index 40a2706728..5600a8ab5f 100644 --- a/juce_amalgamated.h +++ b/juce_amalgamated.h @@ -145,7 +145,7 @@ On Windows, if you enable this, you'll need to have the QuickTime SDK installed, and its header files will need to be on your include path. */ -#if ! (defined (JUCE_QUICKTIME) || defined (LINUX) || (defined (_WIN32) && ! defined (_MSC_VER))) +#if ! (defined (JUCE_QUICKTIME) || defined (LINUX) || defined (TARGET_OS_IPHONE) || defined (TARGET_IPHONE_SIMULATOR) || (defined (_WIN32) && ! defined (_MSC_VER))) #define JUCE_QUICKTIME 1 #endif @@ -326,6 +326,8 @@ #else #if defined (LINUX) || defined (__linux__) #define JUCE_LINUX 1 + #elif TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + #define JUCE_IPHONE 1 #else #define JUCE_MAC 1 #endif @@ -389,6 +391,20 @@ #endif #endif +#if JUCE_IPHONE + #include + + #ifndef NDEBUG + #define JUCE_DEBUG 1 + #endif + + #ifdef __LITTLE_ENDIAN__ + #define JUCE_LITTLE_ENDIAN 1 + #else + #define JUCE_BIG_ENDIAN 1 + #endif +#endif + #if JUCE_LINUX #ifdef _DEBUG @@ -494,6 +510,8 @@ #endif #elif JUCE_MAC #define juce_breakDebugger Debugger(); + #elif JUCE_IPHONE + #define juce_breakDebugger assert (false); #elif JUCE_LINUX #define juce_breakDebugger kill (0, SIGTRAP); #endif @@ -1079,7 +1097,7 @@ const float float_Pi = 3.14159265358979323846f; /** Swaps the byte-order in an integer from little to big-endianness or vice-versa. */ forcedinline uint32 swapByteOrder (uint32 n) throw() { -#if JUCE_MAC +#if JUCE_MAC || JUCE_IPHONE // Mac version return CFSwapInt32 (n); #elif JUCE_GCC @@ -1113,7 +1131,7 @@ inline uint16 swapByteOrder (const uint16 n) throw() inline uint64 swapByteOrder (const uint64 value) throw() { -#if JUCE_MAC +#if JUCE_MAC || JUCE_IPHONE return CFSwapInt64 (value); #elif JUCE_USE_INTRINSICS return _byteswap_uint64 (value); @@ -2536,7 +2554,7 @@ BEGIN_JUCE_NAMESPACE // Atomic increment/decrement operations.. -#if JUCE_MAC && ! DOXYGEN +#if (JUCE_MAC || JUCE_IPHONE) && ! DOXYGEN #if ! MACOS_10_3_OR_EARLIER @@ -7147,7 +7165,7 @@ public: const String& bodyText, const StringArray& filesToAttach); -#if JUCE_MAC || DOXYGEN +#if JUCE_MAC || JUCE_IPHONE || DOXYGEN /** MAC ONLY - Turns a Core CF String into a juce one. */ static const String cfStringToJuceString (CFStringRef cfString); @@ -7288,7 +7306,7 @@ public: #endif }; -#if JUCE_MAC +#if JUCE_MAC || JUCE_IPHONE /** A handy C++ wrapper that creates and deletes an NSAutoreleasePool object using RAII. @@ -7303,6 +7321,10 @@ private: void* pool; }; +#endif + +#if JUCE_MAC + /** A wrapper class for picking up events from an Apple IR remote control device. @@ -8302,10 +8324,12 @@ public: @param addresses an array into which the MAC addresses should be copied @param maxNum the number of elements in this array - @param littleEndian the endianness of the numbers to return. Note that - the default values of this parameter are different on - Mac/PC to avoid breaking old software that was written - before this parameter was added (when the two systems + @param littleEndian the endianness of the numbers to return. If this is true, + the least-significant byte of each number is the first byte + of the mac address. If false, the least significant byte is + the last number. Note that the default values of this parameter + are different on Mac/PC to avoid breaking old software that was + written before this parameter was added (when the two systems defaulted to using different endiannesses). In newer software you probably want to specify an explicit value for this. diff --git a/src/core/juce_Atomic.h b/src/core/juce_Atomic.h index 63299af9be..00d0be9c4f 100644 --- a/src/core/juce_Atomic.h +++ b/src/core/juce_Atomic.h @@ -31,7 +31,7 @@ //============================================================================== -#if JUCE_MAC && ! DOXYGEN +#if (JUCE_MAC || JUCE_IPHONE) && ! DOXYGEN #if ! MACOS_10_3_OR_EARLIER //============================================================================== diff --git a/src/core/juce_DataConversions.h b/src/core/juce_DataConversions.h index 1478c94f50..877b8eb158 100644 --- a/src/core/juce_DataConversions.h +++ b/src/core/juce_DataConversions.h @@ -38,7 +38,7 @@ /** Swaps the byte-order in an integer from little to big-endianness or vice-versa. */ forcedinline uint32 swapByteOrder (uint32 n) throw() { -#if JUCE_MAC +#if JUCE_MAC || JUCE_IPHONE // Mac version return CFSwapInt32 (n); #elif JUCE_GCC @@ -72,7 +72,7 @@ inline uint16 swapByteOrder (const uint16 n) throw() inline uint64 swapByteOrder (const uint64 value) throw() { -#if JUCE_MAC +#if JUCE_MAC || JUCE_IPHONE return CFSwapInt64 (value); #elif JUCE_USE_INTRINSICS return _byteswap_uint64 (value); diff --git a/src/core/juce_PlatformDefs.h b/src/core/juce_PlatformDefs.h index 89f49504d2..32f1a9978a 100644 --- a/src/core/juce_PlatformDefs.h +++ b/src/core/juce_PlatformDefs.h @@ -48,6 +48,8 @@ #else #if defined (LINUX) || defined (__linux__) #define JUCE_LINUX 1 + #elif TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + #define JUCE_IPHONE 1 #else #define JUCE_MAC 1 #endif @@ -113,6 +115,21 @@ #endif #endif +//============================================================================== +#if JUCE_IPHONE + #include + + #ifndef NDEBUG + #define JUCE_DEBUG 1 + #endif + + #ifdef __LITTLE_ENDIAN__ + #define JUCE_LITTLE_ENDIAN 1 + #else + #define JUCE_BIG_ENDIAN 1 + #endif +#endif + //============================================================================== #if JUCE_LINUX @@ -223,6 +240,8 @@ #endif #elif JUCE_MAC #define juce_breakDebugger Debugger(); + #elif JUCE_IPHONE + #define juce_breakDebugger assert (false); #elif JUCE_LINUX #define juce_breakDebugger kill (0, SIGTRAP); #endif diff --git a/src/core/juce_PlatformUtilities.h b/src/core/juce_PlatformUtilities.h index f4c1099da4..d58763c44f 100644 --- a/src/core/juce_PlatformUtilities.h +++ b/src/core/juce_PlatformUtilities.h @@ -46,7 +46,7 @@ public: const String& bodyText, const StringArray& filesToAttach); -#if JUCE_MAC || DOXYGEN +#if JUCE_MAC || JUCE_IPHONE || DOXYGEN //============================================================================== /** MAC ONLY - Turns a Core CF String into a juce one. */ static const String cfStringToJuceString (CFStringRef cfString); @@ -191,7 +191,7 @@ public: }; -#if JUCE_MAC +#if JUCE_MAC || JUCE_IPHONE //============================================================================== /** A handy C++ wrapper that creates and deletes an NSAutoreleasePool object @@ -207,6 +207,10 @@ private: void* pool; }; +#endif + +#if JUCE_MAC + //============================================================================== /** A wrapper class for picking up events from an Apple IR remote control device. diff --git a/src/core/juce_SystemStats.cpp b/src/core/juce_SystemStats.cpp index cf6e2af391..142674067d 100644 --- a/src/core/juce_SystemStats.cpp +++ b/src/core/juce_SystemStats.cpp @@ -78,10 +78,9 @@ void JUCE_PUBLIC_FUNCTION initialiseJuce_NonGUI() juceInitialisedNonGUI = true; DBG (SystemStats::getJUCEVersion()); - Random::getSystemRandom().setSeedRandomly(); // (calling this more than once improves its randomness) juce_initialiseStrings(); SystemStats::initialiseStats(); - Random::getSystemRandom().setSeedRandomly(); // (calling this more than once improves its randomness) + Random::getSystemRandom().setSeedRandomly(); // (mustn't call this before initialiseStats() because it relies on the time being set up) } } diff --git a/src/core/juce_SystemStats.h b/src/core/juce_SystemStats.h index 456a7952af..29a6baa230 100644 --- a/src/core/juce_SystemStats.h +++ b/src/core/juce_SystemStats.h @@ -147,10 +147,12 @@ public: @param addresses an array into which the MAC addresses should be copied @param maxNum the number of elements in this array - @param littleEndian the endianness of the numbers to return. Note that - the default values of this parameter are different on - Mac/PC to avoid breaking old software that was written - before this parameter was added (when the two systems + @param littleEndian the endianness of the numbers to return. If this is true, + the least-significant byte of each number is the first byte + of the mac address. If false, the least significant byte is + the last number. Note that the default values of this parameter + are different on Mac/PC to avoid breaking old software that was + written before this parameter was added (when the two systems defaulted to using different endiannesses). In newer software you probably want to specify an explicit value for this. diff --git a/src/gui/components/filebrowser/juce_FileTreeComponent.cpp b/src/gui/components/filebrowser/juce_FileTreeComponent.cpp index 16dc423f31..bd835c36cf 100644 --- a/src/gui/components/filebrowser/juce_FileTreeComponent.cpp +++ b/src/gui/components/filebrowser/juce_FileTreeComponent.cpp @@ -141,7 +141,7 @@ public: void paintItem (Graphics& g, int width, int height) { - if (file != File::nonexistent && ! isDirectory) + if (file != File::nonexistent) { updateIcon (true); diff --git a/src/io/network/juce_Socket.cpp b/src/io/network/juce_Socket.cpp index d5e11ab9b5..cfc0401dd1 100644 --- a/src/io/network/juce_Socket.cpp +++ b/src/io/network/juce_Socket.cpp @@ -38,7 +38,7 @@ #include #include #else - #if MACOSX_DEPLOYMENT_TARGET <= MAC_OS_X_VERSION_10_4 + #if (MACOSX_DEPLOYMENT_TARGET <= MAC_OS_X_VERSION_10_4) && ! (TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR) #include #endif #endif @@ -57,6 +57,12 @@ BEGIN_JUCE_NAMESPACE #include "../../threads/juce_ScopedLock.h" #include "../../threads/juce_Thread.h" +#if defined (JUCE_LINUX) || defined (JUCE_MAC) || defined (JUCE_IPHONE) + typedef socklen_t juce_socklen_t; +#else + typedef int juce_socklen_t; +#endif + //============================================================================== #if JUCE_WIN32 @@ -191,12 +197,7 @@ static int waitForReadiness (const int handle, const bool forReading, { int opt; - -#if defined (JUCE_LINUX) || defined (JUCE_MAC) - socklen_t len = sizeof (opt); -#else - int len = sizeof (opt); -#endif + juce_socklen_t len = sizeof (opt); if (getsockopt (handle, SOL_SOCKET, SO_ERROR, (char*) &opt, &len) < 0 || opt != 0) @@ -470,12 +471,7 @@ StreamingSocket* StreamingSocket::waitForNextConnection() const if (connected && isListener) { struct sockaddr address; - -#if defined (JUCE_LINUX) || defined (JUCE_MAC) - socklen_t len = sizeof (sockaddr); -#else - int len = sizeof (sockaddr); -#endif + juce_socklen_t len = sizeof (sockaddr); const int newSocket = (int) accept (handle, &address, &len); if (newSocket >= 0 && connected) @@ -580,12 +576,7 @@ bool DatagramSocket::connect (const String& remoteHostName, DatagramSocket* DatagramSocket::waitForNextConnection() const { struct sockaddr address; - -#if defined (JUCE_LINUX) || defined (JUCE_MAC) - socklen_t len = sizeof (sockaddr); -#else - int len = sizeof (sockaddr); -#endif + juce_socklen_t len = sizeof (sockaddr); while (waitUntilReady (true, -1) == 1) { diff --git a/src/juce_amalgamated_template.cpp b/src/juce_amalgamated_template.cpp index e296826927..3fcfb22f22 100644 --- a/src/juce_amalgamated_template.cpp +++ b/src/juce_amalgamated_template.cpp @@ -45,6 +45,8 @@ #include "native/windows/juce_win32_NativeIncludes.h" #elif defined (LINUX) #include "native/linux/juce_linux_NativeIncludes.h" +#elif TARGET_OS_IPHONE || TARGET_IPHONE_SIMULATOR + #include "native/mac/juce_iphone_NativeIncludes.h" #else #include "native/mac/juce_mac_NativeIncludes.h" #endif @@ -347,3 +349,7 @@ #if JUCE_MAC #include "native/juce_mac_NativeCode.mm" #endif + +#if JUCE_IPHONE + #include "native/juce_iphone_NativeCode.mm" +#endif diff --git a/src/native/juce_iphone_NativeCode.mm b/src/native/juce_iphone_NativeCode.mm new file mode 100644 index 0000000000..79b1c8ea87 --- /dev/null +++ b/src/native/juce_iphone_NativeCode.mm @@ -0,0 +1,111 @@ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-9 by Raw Material Software Ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the GNU General + Public License (Version 2), as published by the Free Software Foundation. + A copy of the license is included in the JUCE distribution, or can be found + online at www.gnu.org/licenses. + + JUCE 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. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.rawmaterialsoftware.com/juce for more information. + + ============================================================================== +*/ + +/* + This file wraps together all the mac-specific code, so that + we can include all the native headers just once, and compile all our + platform-specific stuff in one big lump, keeping it out of the way of + the rest of the codebase. +*/ + +#include "mac/juce_iphone_NativeIncludes.h" + +BEGIN_JUCE_NAMESPACE + +//============================================================================== +#include "../core/juce_Singleton.h" +#include "../core/juce_Random.h" +#include "../core/juce_SystemStats.h" +#include "../threads/juce_Process.h" +#include "../threads/juce_Thread.h" +#include "../threads/juce_InterProcessLock.h" +#include "../io/files/juce_FileInputStream.h" +#include "../io/files/juce_NamedPipe.h" +#include "../io/network/juce_URL.h" +#include "../core/juce_PlatformUtilities.h" +#include "../text/juce_LocalisedStrings.h" +#include "../utilities/juce_DeletedAtShutdown.h" +#include "../application/juce_Application.h" +#include "../utilities/juce_SystemClipboard.h" +#include "../events/juce_MessageManager.h" +#include "../gui/graphics/contexts/juce_LowLevelGraphicsSoftwareRenderer.h" +#include "../gui/graphics/imaging/juce_ImageFileFormat.h" +#include "../gui/graphics/imaging/juce_CameraDevice.h" +#include "../gui/components/windows/juce_AlertWindow.h" +#include "../gui/components/juce_Desktop.h" +#include "../gui/components/menus/juce_MenuBarModel.h" +#include "../gui/components/special/juce_OpenGLComponent.h" +#include "../gui/components/special/juce_QuickTimeMovieComponent.h" +#include "../gui/components/mouse/juce_DragAndDropContainer.h" +#include "../gui/components/keyboard/juce_KeyPressMappingSet.h" +#include "../gui/components/special/juce_NSViewComponent.h" +#include "../gui/components/layout/juce_ComponentMovementWatcher.h" +#include "../gui/components/special/juce_WebBrowserComponent.h" +#include "../gui/components/filebrowser/juce_FileChooser.h" +#include "../audio/audio_file_formats/juce_AudioCDBurner.h" +#include "../audio/audio_file_formats/juce_AudioCDReader.h" +#include "../audio/audio_sources/juce_AudioSource.h" +#include "../audio/dsp/juce_AudioDataConverters.h" +#include "../audio/devices/juce_AudioIODeviceType.h" +#include "../audio/devices/juce_MidiOutput.h" +#include "../audio/devices/juce_MidiInput.h" +#undef Point + + +//============================================================================== +#define JUCE_INCLUDED_FILE 1 + +// Now include the actual code files.. + +#include "mac/juce_mac_ObjCSuffix.h" +#include "mac/juce_mac_Strings.mm" +#include "mac/juce_mac_SystemStats.mm" +#include "mac/juce_mac_Network.mm" +#include "common/juce_posix_NamedPipe.cpp" +#include "mac/juce_mac_Threads.mm" +#include "common/juce_posix_SharedCode.h" +#include "mac/juce_mac_Files.mm" +#include "mac/juce_iphone_MiscUtilities.mm" +#include "mac/juce_mac_Debugging.mm" + +#if ! JUCE_ONLY_BUILD_CORE_LIBRARY + /*#include "mac/juce_mac_NSViewComponentPeer.mm" + #include "mac/juce_mac_MouseCursor.mm" + #include "mac/juce_mac_NSViewComponent.mm" + #include "mac/juce_mac_AppleRemote.mm" + #include "mac/juce_mac_OpenGLComponent.mm" + #include "mac/juce_mac_MainMenu.mm" + #include "mac/juce_mac_FileChooser.mm" + #include "mac/juce_mac_QuickTimeMovieComponent.mm" + #include "mac/juce_mac_AudioCDBurner.mm" + #include "mac/juce_mac_Fonts.mm" + #include "mac/juce_mac_MessageManager.mm" + #include "mac/juce_mac_WebBrowserComponent.mm" + #include "mac/juce_mac_CoreAudio.cpp" + #include "mac/juce_mac_CoreMidi.cpp" + #include "mac/juce_mac_CameraDevice.mm"*/ +#endif + +END_JUCE_NAMESPACE diff --git a/src/native/juce_mac_NativeCode.mm b/src/native/juce_mac_NativeCode.mm index 12e593cd96..7c3e7a68c7 100644 --- a/src/native/juce_mac_NativeCode.mm +++ b/src/native/juce_mac_NativeCode.mm @@ -73,33 +73,12 @@ BEGIN_JUCE_NAMESPACE #include "../audio/devices/juce_MidiInput.h" #undef Point -//============================================================================== -/** This suffix is used for naming all Obj-C classes that are used inside juce. - - Because of the flat naming structure used by Obj-C, you can get horrible situations where - two DLLs are loaded into a host, each of which uses classes with the same names, and these get - cross-linked so that when you make a call to a class that you thought was private, it ends up - actually calling into a similarly named class in the other module's address space. - - By changing this macro to a unique value, you ensure that all the obj-C classes in your app - have unique names, and should avoid this problem. - - If you're using the amalgamated version, you can just set this macro to something unique before - you include juce_amalgamated.cpp. -*/ -#ifndef JUCE_ObjCExtraSuffix - #define JUCE_ObjCExtraSuffix 3 -#endif - -#define appendMacro1(a, b, c, d) a ## _ ## b ## _ ## c ## _ ## d -#define appendMacro2(a, b, c, d) appendMacro1(a, b, c, d) -#define MakeObjCClassName(rootName) appendMacro2 (rootName, JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_ObjCExtraSuffix) - //============================================================================== #define JUCE_INCLUDED_FILE 1 // Now include the actual code files.. +#include "mac/juce_mac_ObjCSuffix.h" #include "mac/juce_mac_Strings.mm" #include "mac/juce_mac_SystemStats.mm" #include "mac/juce_mac_Network.mm" diff --git a/src/native/linux/juce_linux_Network.cpp b/src/native/linux/juce_linux_Network.cpp index 5e03523f5e..d3dba90d16 100644 --- a/src/native/linux/juce_linux_Network.cpp +++ b/src/native/linux/juce_linux_Network.cpp @@ -54,7 +54,7 @@ int SystemStats::getMACAddresses (int64* addresses, int maxNum, const bool littl { int64 a = 0; for (int j = 6; --j >= 0;) - a = (a << 8) | (uint8) ifr.ifr_hwaddr.sa_data[j]; + a = (a << 8) | (uint8) ifr.ifr_hwaddr.sa_data [littleEndian ? j : (5 - j)]; *addresses++ = a; ++numResults; diff --git a/src/native/mac/juce_iphone_MiscUtilities.mm b/src/native/mac/juce_iphone_MiscUtilities.mm new file mode 100644 index 0000000000..0da5e8b18c --- /dev/null +++ b/src/native/mac/juce_iphone_MiscUtilities.mm @@ -0,0 +1,135 @@ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-9 by Raw Material Software Ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the GNU General + Public License (Version 2), as published by the Free Software Foundation. + A copy of the license is included in the JUCE distribution, or can be found + online at www.gnu.org/licenses. + + JUCE 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. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.rawmaterialsoftware.com/juce for more information. + + ============================================================================== +*/ + +// (This file gets included by juce_mac_NativeCode.mm, rather than being +// compiled on its own). +#ifdef JUCE_INCLUDED_FILE + + +//============================================================================== +ScopedAutoReleasePool::ScopedAutoReleasePool() +{ + pool = [[NSAutoreleasePool alloc] init]; +} + +ScopedAutoReleasePool::~ScopedAutoReleasePool() +{ + [((NSAutoreleasePool*) pool) release]; +} + +//============================================================================== +void PlatformUtilities::beep() +{ + //xxx + //AudioServicesPlaySystemSound (); +} + +//============================================================================== +void PlatformUtilities::addItemToDock (const File& file) +{ +} + + +//============================================================================== +#if ! JUCE_ONLY_BUILD_CORE_LIBRARY + +bool AlertWindow::showNativeDialogBox (const String& title, + const String& bodyText, + bool isOkCancel) +{ + const ScopedAutoReleasePool pool; + + UIAlertView *alert = [[[UIAlertView alloc] initWithTitle: juceStringToNS (title) + message: juceStringToNS (title) + delegate: nil + cancelButtonTitle: @"OK" + otherButtonTitles: (isOkCancel ? @"Cancel" : nil), nil] autorelease]; + alert.cancelButtonIndex = alert.firstOtherButtonIndex; + [alert show]; + + // xxx need to use a delegate to find which button was clicked + return false; +} + +//============================================================================== +bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray& files, const bool canMoveFiles) +{ + jassertfalse // not implemented! + return false; +} + +bool DragAndDropContainer::performExternalDragDropOfText (const String& text) +{ + jassertfalse // not implemented! + return false; +} + +//============================================================================== +bool Desktop::canUseSemiTransparentWindows() throw() +{ + return true; +} + +void Desktop::getMousePosition (int& x, int& y) throw() +{ + x = 0; + y = 0; +} + +void Desktop::setMousePosition (int x, int y) throw() +{ +} + +//============================================================================== +void Desktop::setScreenSaverEnabled (const bool isEnabled) throw() +{ + [[UIApplication sharedApplication] setIdleTimerDisabled: ! isEnabled]; +} + +bool Desktop::isScreenSaverEnabled() throw() +{ + return ! [[UIApplication sharedApplication] isIdleTimerDisabled]; +} + + +//============================================================================== +void juce_updateMultiMonitorInfo (Array & monitorCoords, const bool clipToWorkArea) throw() +{ + const ScopedAutoReleasePool pool; + monitorCoords.clear(); + + CGRect r = clipToWorkArea ? [[UIScreen mainScreen] applicationFrame] + : [[UIScreen mainScreen] bounds]; + + monitorCoords.add (Rectangle ((int) r.origin.x, + (int) r.origin.y, + (int) r.size.width, + (int) r.size.height)); +} + + +#endif + +#endif diff --git a/src/native/mac/juce_iphone_NativeIncludes.h b/src/native/mac/juce_iphone_NativeIncludes.h new file mode 100644 index 0000000000..481125738f --- /dev/null +++ b/src/native/mac/juce_iphone_NativeIncludes.h @@ -0,0 +1,58 @@ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-9 by Raw Material Software Ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the GNU General + Public License (Version 2), as published by the Free Software Foundation. + A copy of the license is included in the JUCE distribution, or can be found + online at www.gnu.org/licenses. + + JUCE 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. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.rawmaterialsoftware.com/juce for more information. + + ============================================================================== +*/ + +#ifndef __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ +#define __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ + +/* + This file wraps together all the mac-specific code, so that + we can include all the native headers just once, and compile all our + platform-specific stuff in one big lump, keeping it out of the way of + the rest of the codebase. +*/ + +#include "../../core/juce_StandardHeader.h" + +#import +#import +#import +#import +#import +#import +#include +#include +#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#endif // __JUCE_MAC_NATIVEINCLUDES_JUCEHEADER__ diff --git a/src/native/mac/juce_mac_Files.mm b/src/native/mac/juce_mac_Files.mm index bb9180d32b..c60aed8bdc 100644 --- a/src/native/mac/juce_mac_Files.mm +++ b/src/native/mac/juce_mac_Files.mm @@ -33,28 +33,6 @@ */ //============================================================================== -const unsigned int macTimeToUnixTimeDiff = 0x7c25be90; - -static uint64 utcDateTimeToUnixTime (const UTCDateTime& d) throw() -{ - if (d.highSeconds == 0 && d.lowSeconds == 0 && d.fraction == 0) - return 0; - - return (((((uint64) d.highSeconds) << 32) | (uint64) d.lowSeconds) * 1000) - + ((d.fraction * 1000) >> 16) - - 2082844800000ll; -} - -static void unixTimeToUtcDateTime (uint64 t, UTCDateTime& d) throw() -{ - if (t != 0) - t += 2082844800000ll; - - d.highSeconds = (t / 1000) >> 32; - d.lowSeconds = (t / 1000) & (uint64) 0xffffffff; - d.fraction = ((t % 1000) << 16) / 1000; -} - void juce_getFileTimes (const String& fileName, int64& modificationTime, int64& accessTime, @@ -64,24 +42,13 @@ void juce_getFileTimes (const String& fileName, accessTime = 0; creationTime = 0; - FSRef fileRef; - if (PlatformUtilities::makeFSRefFromPath (&fileRef, fileName)) + struct stat info; + const int res = stat (fileName.toUTF8(), &info); + if (res == 0) { - FSRefParam info; - zerostruct (info); - - info.ref = &fileRef; - info.whichInfo = kFSCatInfoAllDates; - - FSCatalogInfo catInfo; - info.catInfo = &catInfo; - - if (PBGetCatalogInfoSync (&info) == noErr) - { - creationTime = utcDateTimeToUnixTime (catInfo.createDate); - accessTime = utcDateTimeToUnixTime (catInfo.accessDate); - modificationTime = utcDateTimeToUnixTime (catInfo.contentModDate); - } + modificationTime = (int64) info.st_mtime * 1000; + accessTime = (int64) info.st_atime * 1000; + creationTime = (int64) info.st_ctime * 1000; } } @@ -90,59 +57,29 @@ bool juce_setFileTimes (const String& fileName, int64 accessTime, int64 creationTime) throw() { - FSRef fileRef; - if (PlatformUtilities::makeFSRefFromPath (&fileRef, fileName)) - { - FSRefParam info; - zerostruct (info); + struct utimbuf times; + times.actime = (time_t) (accessTime / 1000); + times.modtime = (time_t) (modificationTime / 1000); - info.ref = &fileRef; - info.whichInfo = kFSCatInfoAllDates; - - FSCatalogInfo catInfo; - info.catInfo = &catInfo; - - if (PBGetCatalogInfoSync (&info) == noErr) - { - if (creationTime != 0) - unixTimeToUtcDateTime (creationTime, catInfo.createDate); - - if (modificationTime != 0) - unixTimeToUtcDateTime (modificationTime, catInfo.contentModDate); - - if (accessTime != 0) - unixTimeToUtcDateTime (accessTime, catInfo.accessDate); - - return PBSetCatalogInfoSync (&info) == noErr; - } - } - - return false; + return utime (fileName.toUTF8(), ×) == 0; } bool juce_setFileReadOnly (const String& fileName, bool isReadOnly) throw() { - const char* const fileNameUTF8 = fileName.toUTF8(); - struct stat info; - const int res = stat (fileNameUTF8, &info); + const int res = stat (fileName.toUTF8(), &info); + if (res != 0) + return false; - bool ok = false; + info.st_mode &= 0777; // Just permissions - if (res == 0) - { - info.st_mode &= 0777; // Just permissions + if (isReadOnly) + info.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); + else + // Give everybody write permission? + info.st_mode |= S_IWUSR | S_IWGRP | S_IWOTH; - if (isReadOnly) - info.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); - else - // Give everybody write permission? - info.st_mode |= S_IWUSR | S_IWGRP | S_IWOTH; - - ok = chmod (fileNameUTF8, info.st_mode) == 0; - } - - return ok; + return chmod (fileName.toUTF8(), info.st_mode) == 0; } bool juce_copyFile (const String& src, const String& dst) throw() @@ -202,22 +139,29 @@ bool File::isOnHardDisk() const throw() bool File::isOnRemovableDrive() const throw() { - const ScopedAutoReleasePool pool; - BOOL removable = false; +#if JUCE_IPHONE + return false; // xxx is this possible? +#else + const ScopedAutoReleasePool pool; + BOOL removable = false; - [[NSWorkspace sharedWorkspace] - getFileSystemInfoForPath: juceStringToNS (getFullPathName()) - isRemovable: &removable - isWritable: nil - isUnmountable: nil - description: nil - type: nil]; + [[NSWorkspace sharedWorkspace] + getFileSystemInfoForPath: juceStringToNS (getFullPathName()) + isRemovable: &removable + isWritable: nil + isUnmountable: nil + description: nil + type: nil]; return removable; +#endif } static bool juce_isHiddenFile (const String& path) throw() { +#if JUCE_IPHONE + return File (path).getFileName().startsWithChar (T('.')); +#else FSRef ref; if (! PlatformUtilities::makeFSRefFromPath (&ref, path)) return false; @@ -229,6 +173,7 @@ static bool juce_isHiddenFile (const String& path) throw() return (((FolderInfo*) &info.finderInfo)->finderFlags & kIsInvisible) != 0; return (((FileInfo*) &info.finderInfo)->finderFlags & kIsInvisible) != 0; +#endif } bool File::isHidden() const throw() @@ -350,15 +295,10 @@ const String File::getVersion() const throw() //============================================================================== const File File::getLinkedTarget() const throw() { - FSRef ref; - Boolean targetIsAFolder, wasAliased; + NSString* dest = [[NSFileManager defaultManager] destinationOfSymbolicLinkAtPath: juceStringToNS (getFullPathName()) error: nil]; - if (PlatformUtilities::makeFSRefFromPath (&ref, getFullPathName()) - && (FSResolveAliasFileWithMountFlags (&ref, true, &targetIsAFolder, &wasAliased, 0) == noErr) - && wasAliased) - { - return File (PlatformUtilities::makePathFromFSRef (&ref)); - } + if (dest != nil) + return File (nsStringToJuce (dest)); return *this; } @@ -369,6 +309,9 @@ bool File::moveToTrash() const throw() if (! exists()) return true; +#if JUCE_IPHONE + return deleteFile(); //xxx is there a trashcan on the iPhone? +#else const ScopedAutoReleasePool pool; NSString* p = juceStringToNS (getFullPathName()); @@ -379,100 +322,36 @@ bool File::moveToTrash() const throw() destination: @"" files: [NSArray arrayWithObject: [p lastPathComponent]] tag: nil ]; +#endif } //============================================================================== struct FindFileStruct { - String parentDir, wildCard; - DIR* dir; - - bool getNextMatch (String& result, bool* const isDir, bool* const isHidden, int64* const fileSize, - Time* const modTime, Time* const creationTime, bool* const isReadOnly) throw() - { - const char* const wildCardUTF8 = wildCard.toUTF8(); - - for (;;) - { - struct dirent* const de = readdir (dir); - - if (de == 0) - break; - - if (fnmatch (wildCardUTF8, de->d_name, 0) == 0) - { - result = String::fromUTF8 ((const uint8*) de->d_name); - - const String path (parentDir + result); - - if (isDir != 0 || fileSize != 0) - { - struct stat info; - const bool statOk = juce_stat (path, info); - - if (isDir != 0) - *isDir = path.isEmpty() || (statOk && ((info.st_mode & S_IFDIR) != 0)); - - if (isHidden != 0) - *isHidden = (de->d_name[0] == '.') - || juce_isHiddenFile (path); - - if (fileSize != 0) - *fileSize = statOk ? info.st_size : 0; - } - - if (modTime != 0 || creationTime != 0) - { - int64 m, a, c; - juce_getFileTimes (path, m, a, c); - - if (modTime != 0) - *modTime = m; - - if (creationTime != 0) - *creationTime = c; - } - - if (isReadOnly != 0) - *isReadOnly = ! juce_canWriteToFile (path); - - return true; - } - } - - return false; - } + NSDirectoryEnumerator* enumerator; + String parentDir; }; -// returns 0 on failure void* juce_findFileStart (const String& directory, const String& wildCard, String& firstResultFile, bool* isDir, bool* isHidden, int64* fileSize, Time* modTime, Time* creationTime, bool* isReadOnly) throw() { - DIR* const d = opendir (directory.toUTF8()); + NSDirectoryEnumerator* e = [[NSFileManager defaultManager] enumeratorAtPath: juceStringToNS (directory)]; - if (d != 0) + if (e != 0) { - FindFileStruct* const ff = new FindFileStruct(); + FindFileStruct* ff = new FindFileStruct(); + ff->enumerator = [e retain]; ff->parentDir = directory; - if (!ff->parentDir.endsWithChar (File::separator)) + if (! ff->parentDir.endsWithChar (File::separator)) ff->parentDir += File::separator; - ff->wildCard = wildCard; - ff->dir = d; - - if (ff->getNextMatch (firstResultFile, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly)) - { + if (juce_findFileNext (ff, firstResultFile, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly)) return ff; - } - else - { - firstResultFile = String::empty; - isDir = false; - closedir (d); - delete ff; - } + + [e release]; + delete ff; } return 0; @@ -481,23 +360,55 @@ void* juce_findFileStart (const String& directory, const String& wildCard, Strin bool juce_findFileNext (void* handle, String& resultFile, bool* isDir, bool* isHidden, int64* fileSize, Time* modTime, Time* creationTime, bool* isReadOnly) throw() { - FindFileStruct* const ff = (FindFileStruct*) handle; + FindFileStruct* ff = (FindFileStruct*) handle; + NSString* file; - if (ff != 0) - return ff->getNextMatch (resultFile, isDir, isHidden, fileSize, modTime, creationTime, isReadOnly); + if (ff == 0 || (file = [ff->enumerator nextObject]) == 0) + return false; - return false; + [ff->enumerator skipDescendents]; + resultFile = nsStringToJuce (file); + + const String path (ff->parentDir + resultFile); + + if (isDir != 0 || fileSize != 0) + { + struct stat info; + const bool statOk = juce_stat (path, info); + + if (isDir != 0) + *isDir = statOk && ((info.st_mode & S_IFDIR) != 0); + + if (isHidden != 0) + *isHidden = juce_isHiddenFile (path); + + if (fileSize != 0) + *fileSize = statOk ? info.st_size : 0; + } + + if (modTime != 0 || creationTime != 0) + { + int64 m, a, c; + juce_getFileTimes (path, m, a, c); + + if (modTime != 0) + *modTime = m; + + if (creationTime != 0) + *creationTime = c; + } + + if (isReadOnly != 0) + *isReadOnly = ! juce_canWriteToFile (path); + + return true; } void juce_findFileClose (void* handle) throw() { - FindFileStruct* const ff = (FindFileStruct*)handle; - - if (ff != 0) - { - closedir (ff->dir); - delete ff; - } + FindFileStruct* ff = (FindFileStruct*) handle; + [ff->enumerator release]; + delete ff; } //============================================================================== @@ -525,6 +436,9 @@ bool juce_launchExecutable (const String& pathAndArguments) throw() bool juce_launchFile (const String& fileName, const String& parameters) throw() { +#if JUCE_IPHONE + return false; // is this possible? +#else const ScopedAutoReleasePool pool; if (parameters.isEmpty()) @@ -560,9 +474,11 @@ bool juce_launchFile (const String& fileName, } return ok; +#endif } //============================================================================== +#if ! JUCE_IPHONE bool PlatformUtilities::makeFSRefFromPath (FSRef* destFSRef, const String& path) { return FSPathMakeRef ((const UInt8*) path.toUTF8(), destFSRef, 0) == noErr; @@ -580,18 +496,24 @@ const String PlatformUtilities::makePathFromFSRef (FSRef* file) return PlatformUtilities::convertToPrecomposedUnicode (result); } +#endif //============================================================================== OSType PlatformUtilities::getTypeOfFile (const String& filename) { const ScopedAutoReleasePool pool; - return NSHFSTypeCodeFromFileType (NSHFSTypeOfFile (juceStringToNS (filename))); + NSDictionary* fileDict = [[NSFileManager defaultManager] fileAttributesAtPath: juceStringToNS (filename) traverseLink: NO]; + return (OSType) [fileDict objectForKey: NSFileHFSTypeCode]; } bool PlatformUtilities::isBundle (const String& filename) { +#if JUCE_IPHONE + return false; // xxx can't find a sensible way to do this without trying to open the bundle.. +#else const ScopedAutoReleasePool pool; return [[NSWorkspace sharedWorkspace] isFilePackageAtPath: juceStringToNS (filename)]; +#endif } #endif diff --git a/src/native/mac/juce_mac_NativeIncludes.h b/src/native/mac/juce_mac_NativeIncludes.h index 18dc326f08..381f4a6982 100644 --- a/src/native/mac/juce_mac_NativeIncludes.h +++ b/src/native/mac/juce_mac_NativeIncludes.h @@ -46,9 +46,6 @@ #import #import #import -#import -#import -#import #import #include @@ -59,6 +56,9 @@ #include #include #include +#include +#include +#include #if MACOS_10_4_OR_EARLIER #include diff --git a/src/native/mac/juce_mac_Network.mm b/src/native/mac/juce_mac_Network.mm index 388265a7f9..36786f9e4b 100644 --- a/src/native/mac/juce_mac_Network.mm +++ b/src/native/mac/juce_mac_Network.mm @@ -28,84 +28,42 @@ #if JUCE_INCLUDED_FILE //============================================================================== -static bool getEthernetIterator (io_iterator_t* matchingServices) throw() -{ - mach_port_t masterPort; - - if (IOMasterPort (MACH_PORT_NULL, &masterPort) == KERN_SUCCESS) - { - CFMutableDictionaryRef dict = IOServiceMatching (kIOEthernetInterfaceClass); - - if (dict != 0) - { - CFMutableDictionaryRef propDict = CFDictionaryCreateMutable (kCFAllocatorDefault, - 0, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks); - - if (propDict != 0) - { - CFDictionarySetValue (propDict, CFSTR (kIOPrimaryInterface), kCFBooleanTrue); - - CFDictionarySetValue (dict, CFSTR (kIOPropertyMatchKey), propDict); - CFRelease (propDict); - } - } - - return IOServiceGetMatchingServices (masterPort, dict, matchingServices) == KERN_SUCCESS; - } - - return false; -} - int SystemStats::getMACAddresses (int64* addresses, int maxNum, const bool littleEndian) throw() { + #ifndef IFT_ETHER + #define IFT_ETHER 6 + #endif + + ifaddrs* addrs = 0; int numResults = 0; - io_iterator_t it; - if (getEthernetIterator (&it)) + if (getifaddrs (&addrs) == 0) { - io_object_t i; + const ifaddrs* cursor = addrs; - while ((i = IOIteratorNext (it)) != 0) + while (cursor != 0 && numResults < maxNum) { - io_object_t controller; - - if (IORegistryEntryGetParentEntry (i, kIOServicePlane, &controller) == KERN_SUCCESS) + if (cursor->ifa_addr->sa_family == AF_LINK) { - CFTypeRef data = IORegistryEntryCreateCFProperty (controller, - CFSTR (kIOMACAddress), - kCFAllocatorDefault, - 0); - if (data != 0) + const sockaddr_dl* const sadd = (const sockaddr_dl*) cursor->ifa_addr; + + if (sadd->sdl_type == IFT_ETHER) { - UInt8 addr [kIOEthernetAddressSize]; - zeromem (addr, sizeof (addr)); + const uint8* const addr = ((const uint8*) sadd->sdl_data) + sadd->sdl_nlen; - CFDataGetBytes ((CFDataRef) data, CFRangeMake (0, sizeof (addr)), addr); - CFRelease (data); - - int64 a = 0; + uint64 a = 0; for (int i = 6; --i >= 0;) - a = (a << 8) | addr[i]; + a = (a << 8) | addr [littleEndian ? i : (5 - i)]; - if (! littleEndian) - a = (int64) swapByteOrder ((uint64) a); - - if (numResults < maxNum) - { - *addresses++ = a; - ++numResults; - } + *addresses++ = (int64) a; + ++numResults; } - - IOObjectRelease (controller); } - IOObjectRelease (i); + cursor = cursor->ifa_next; } - IOObjectRelease (it); + freeifaddrs (addrs); } return numResults; @@ -117,6 +75,11 @@ bool PlatformUtilities::launchEmailWithAttachments (const String& targetEmailAdd const String& bodyText, const StringArray& filesToAttach) { +#if JUCE_IPHONE + //xxx probably need to use MFMailComposeViewController + jassertfalse + return false; +#else const ScopedAutoReleasePool pool; String script; @@ -152,6 +115,7 @@ bool PlatformUtilities::launchEmailWithAttachments (const String& targetEmailAdd [s release]; return ok; +#endif } //============================================================================== diff --git a/src/native/mac/juce_mac_ObjCSuffix.h b/src/native/mac/juce_mac_ObjCSuffix.h new file mode 100644 index 0000000000..64e8b3a55f --- /dev/null +++ b/src/native/mac/juce_mac_ObjCSuffix.h @@ -0,0 +1,46 @@ +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-9 by Raw Material Software Ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the GNU General + Public License (Version 2), as published by the Free Software Foundation. + A copy of the license is included in the JUCE distribution, or can be found + online at www.gnu.org/licenses. + + JUCE 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. + + ------------------------------------------------------------------------------ + + To release a closed-source product which uses JUCE, commercial licenses are + available: visit www.rawmaterialsoftware.com/juce for more information. + + ============================================================================== +*/ + + +/** This suffix is used for naming all Obj-C classes that are used inside juce. + + Because of the flat naming structure used by Obj-C, you can get horrible situations where + two DLLs are loaded into a host, each of which uses classes with the same names, and these get + cross-linked so that when you make a call to a class that you thought was private, it ends up + actually calling into a similarly named class in the other module's address space. + + By changing this macro to a unique value, you ensure that all the obj-C classes in your app + have unique names, and should avoid this problem. + + If you're using the amalgamated version, you can just set this macro to something unique before + you include juce_amalgamated.cpp. +*/ +#ifndef JUCE_ObjCExtraSuffix + #define JUCE_ObjCExtraSuffix 3 +#endif + +#define appendMacro1(a, b, c, d) a ## _ ## b ## _ ## c ## _ ## d +#define appendMacro2(a, b, c, d) appendMacro1(a, b, c, d) +#define MakeObjCClassName(rootName) appendMacro2 (rootName, JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_ObjCExtraSuffix) diff --git a/src/native/mac/juce_mac_Strings.mm b/src/native/mac/juce_mac_Strings.mm index 153ab017af..cd11998e44 100644 --- a/src/native/mac/juce_mac_Strings.mm +++ b/src/native/mac/juce_mac_Strings.mm @@ -103,6 +103,10 @@ CFStringRef PlatformUtilities::juceStringToCFString (const String& s) const String PlatformUtilities::convertToPrecomposedUnicode (const String& s) { +#if JUCE_IPHONE + const ScopedAutoReleasePool pool; + return nsStringToJuce ([juceStringToNS (s) precomposedStringWithCanonicalMapping]); +#else UnicodeMapping map; map.unicodeEncoding = CreateTextEncoding (kTextEncodingUnicodeDefault, @@ -156,6 +160,7 @@ const String PlatformUtilities::convertToPrecomposedUnicode (const String& s) } return result; +#endif } //============================================================================== @@ -163,16 +168,25 @@ const String PlatformUtilities::convertToPrecomposedUnicode (const String& s) void SystemClipboard::copyTextToClipboard (const String& text) throw() { +#if JUCE_IPHONE + [[UIPasteboard generalPasteboard] setValue: juceStringToNS (text) + forPasteboardType: (NSString*) kUTTypePlainText]; +#else [[NSPasteboard generalPasteboard] declareTypes: [NSArray arrayWithObject: NSStringPboardType] owner: nil]; [[NSPasteboard generalPasteboard] setString: juceStringToNS (text) forType: NSStringPboardType]; +#endif } const String SystemClipboard::getTextFromClipboard() throw() { +#if JUCE_IPHONE + NSString* text = [[UIPasteboard generalPasteboard] valueForPasteboardType: (NSString*) kUTTypePlainText]; +#else NSString* text = [[NSPasteboard generalPasteboard] stringForType: NSStringPboardType]; +#endif return text == 0 ? String::empty : nsStringToJuce (text); diff --git a/src/native/mac/juce_mac_SystemStats.mm b/src/native/mac/juce_mac_SystemStats.mm index 8dd1eff6ab..32e32e080d 100644 --- a/src/native/mac/juce_mac_SystemStats.mm +++ b/src/native/mac/juce_mac_SystemStats.mm @@ -27,7 +27,8 @@ // compiled on its own). #ifdef JUCE_INCLUDED_FILE -static int64 highResTimerFrequency; +static int64 highResTimerFrequency = 0; +static double highResTimerToMillisecRatio = 0; #if JUCE_INTEL @@ -83,11 +84,13 @@ void SystemStats::initialiseStats() throw() { initialised = true; +#if JUCE_MAC // extremely annoying: adding this line stops the apple menu items from working. Of // course, not adding it means that carbon windows (e.g. in plugins) won't get // any events. //NSApplicationLoad(); [NSApplication sharedApplication]; +#endif #if JUCE_INTEL { @@ -101,7 +104,10 @@ void SystemStats::initialiseStats() throw() } #endif - highResTimerFrequency = (int64) AudioGetHostClockFrequency(); + mach_timebase_info_data_t timebase; + (void) mach_timebase_info (&timebase); + highResTimerFrequency = (int64) (1.0e9 * timebase.denom / timebase.numer); + highResTimerToMillisecRatio = timebase.numer / (1.0e6 * timebase.denom); String s (SystemStats::getJUCEVersion()); @@ -213,27 +219,19 @@ int SystemStats::getNumCpus() throw() } //============================================================================== -static int64 juce_getMicroseconds() throw() -{ - UnsignedWide t; - Microseconds (&t); - return (((int64) t.hi) << 32) | t.lo; -} - uint32 juce_millisecondsSinceStartup() throw() { - return (uint32) (juce_getMicroseconds() / 1000); + return (uint32) (mach_absolute_time() * highResTimerToMillisecRatio); } double Time::getMillisecondCounterHiRes() throw() { - // xxx might be more accurate to use a scaled AudioGetCurrentHostTime? - return juce_getMicroseconds() * 0.001; + return mach_absolute_time() * highResTimerToMillisecRatio; } int64 Time::getHighResolutionTicks() throw() { - return (int64) AudioGetCurrentHostTime(); + return (int64) mach_absolute_time(); } int64 Time::getHighResolutionTicksPerSecond() throw() @@ -243,7 +241,7 @@ int64 Time::getHighResolutionTicksPerSecond() throw() int64 SystemStats::getClockCycleCounter() throw() { - return (int64) AudioGetCurrentHostTime(); + return (int64) mach_absolute_time(); } bool Time::setSystemTimeToThisTime() const throw() diff --git a/src/native/mac/juce_mac_Threads.mm b/src/native/mac/juce_mac_Threads.mm index 8ca5ba616a..15529adf7a 100644 --- a/src/native/mac/juce_mac_Threads.mm +++ b/src/native/mac/juce_mac_Threads.mm @@ -97,7 +97,11 @@ void Thread::setCurrentThreadAffinityMask (const uint32 affinityMask) throw() //============================================================================== bool Process::isForegroundProcess() throw() { +#if JUCE_MAC return [NSApp isActive]; +#else + return true; // xxx change this if more than one app is ever possible on the iPhone! +#endif } void Process::raisePrivilege() diff --git a/src/native/windows/juce_win32_Network.cpp b/src/native/windows/juce_win32_Network.cpp index f49bdf21fd..72ecbf1bdd 100644 --- a/src/native/windows/juce_win32_Network.cpp +++ b/src/native/windows/juce_win32_Network.cpp @@ -338,12 +338,9 @@ static int getMACAddressesViaNetBios (int64* addresses, int maxNum, const bool l { if (astat.adapt.adapter_type == 0xfe) { - int64 mac = 0; - for (unsigned int i = 0; i < 6; ++i) - mac = (mac << 8) | astat.adapt.adapter_address[i]; - - if (littleEndian) - mac = (int64) swapByteOrder ((uint64) mac); + uint64 mac = 0; + for (int i = 6; --i >= 0;) + mac = (mac << 8) | astat.adapt.adapter_address [littleEndian ? i : (5 - i)]; if (numFound < maxNum && mac != 0) addresses [numFound++] = mac; diff --git a/src/utilities/juce_PropertiesFile.cpp b/src/utilities/juce_PropertiesFile.cpp index 70ff717a58..6f7b42bd74 100644 --- a/src/utilities/juce_PropertiesFile.cpp +++ b/src/utilities/juce_PropertiesFile.cpp @@ -268,7 +268,7 @@ const File PropertiesFile::getDefaultAppSettingsFile (const String& applicationN // mustn't have illegal characters in this name.. jassert (applicationName == File::createLegalFileName (applicationName)); -#if JUCE_MAC +#if JUCE_MAC || JUCE_IPHONE File dir (commonToAllUsers ? "/Library/Preferences" : "~/Library/Preferences");